1 /**
2  * Copyright: Copyright Jason White, 2014-2016
3  * License:   $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4  * Authors:   Jason White
5  *
6  * Description:
7  * This module provides a low-level file stream class.
8  *
9  * Synopsis:
10  * ---
11  * // Open a new file in read/write mode. Throws an exception if the file exists.
12  * auto f = File("myfile", FileFlags.readWriteNew);
13  *
14  * // Write an arbitrary array to the stream.
15  * f.write("Hello world!");
16  *
17  * // Seek to the beginning.
18  * f.position = 0;
19  *
20  * // Read in 5 bytes.
21  * char buf[5];
22  * f.read(buf);
23  * assert(buf == "Hello");
24  * ---
25  */
26 module io.file.stream;
27 
28 import io.stream;
29 public import io.file.flags;
30 
31 version (unittest)
32 {
33     import file = std.file; // For easy file creation/deletion.
34     import io.file.temp;
35 }
36 
37 version (Posix)
38 {
39     import core.sys.posix.fcntl;
40     import core.sys.posix.unistd;
41     import core.sys.posix.sys.uio;
42     import std.exception : ErrnoException;
43 
44     version (linux)
45     {
46         extern (C): @system: nothrow:
47         ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
48     }
49 
50     enum
51     {
52         SEEK_SET,
53         SEEK_CUR,
54         SEEK_END
55     }
56 
57     // FIXME: This should be moved into a separate module.
58     alias SysException = ErrnoException;
59 }
60 else version (Windows)
61 {
62     import core.sys.windows.windows;
63 
64     // These are not declared in core.sys.windows.windows
65     extern (Windows) nothrow export
66     {
67         BOOL SetFilePointerEx(
68             HANDLE hFile,
69             long liDistanceToMove,
70             long* lpNewFilePointer,
71             DWORD dwMoveMethod
72         );
73 
74         BOOL FlushFileBuffers(HANDLE hFile);
75         BOOL GetFileSizeEx(HANDLE hFile, long* lpFileSize);
76         DWORD GetFileType(HANDLE hFile);
77 
78         enum
79         {
80             FILE_TYPE_UNKNOWN = 0x0000,
81             FILE_TYPE_DISK    = 0x0001,
82             FILE_TYPE_CHAR    = 0x0002,
83             FILE_TYPE_PIPE    = 0x0003,
84             FILE_TYPE_REMOTE  = 0x8000,
85         }
86 
87         enum
88         {
89             DUPLICATE_CLOSE_SOURCE = 0x00000001,
90             DUPLICATE_SAME_ACCESS  = 0x00000002,
91         }
92     }
93 
94     // FIXME: This should be moved into a separate module.
95     class SysException : Exception
96     {
97         uint errCode;
98 
99         this(string msg, string file = null, size_t line = 0)
100         {
101             import std.windows.syserror : sysErrorString;
102             errCode = GetLastError();
103             super(msg ~ " (" ~ sysErrorString(errCode) ~ ")", file, line);
104         }
105     }
106 }
107 else
108 {
109     static assert(false, "Unsupported platform.");
110 }
111 
112 // FIXME: This should be moved into a separate module.
113 T sysEnforce(T, string file = __FILE__, size_t line = __LINE__)
114     (T value, lazy string msg = null)
115 {
116     if (!value) throw new SysException(msg, file, line);
117     return value;
118 }
119 
120 /**
121  * A cross-platform wrapper around low-level file operations.
122  */
123 struct FileBase
124 {
125     /**
126      * Platform-specific file handle. On Posix systems, this is the file
127      * descriptor `int`. On Windows, this is a `HANDLE`.
128      */
129     version (Posix)
130     {
131         alias Handle = int;
132         enum Handle InvalidHandle = -1;
133     }
134     else version (Windows)
135     {
136         alias Handle = HANDLE;
137         enum Handle InvalidHandle = INVALID_HANDLE_VALUE;
138     }
139 
140     private Handle _h = InvalidHandle;
141 
142     // Name of the file, for debugging purposes only.
143     debug const(char)[] name;
144 
145     /**
146      * Opens or creates a file by name. By default, an existing file is opened
147      * in read-only mode.
148      *
149      * Params:
150      *     name = Path to the file to open.
151      *     flags = How to open the file.
152      *
153      * Example:
154      * ---
155      * // Create a brand-new file and write to it. Throws an exception if the
156      * // file already exists. The file is automatically closed when it falls
157      * // out of scope.
158      * auto f = File("filename", FileFlags.writeNew);
159      * f.write("Hello world!");
160      * ---
161      */
162     this(const(char)[] name, FileFlags flags = FileFlags.readExisting)
163     {
164         debug this.name = name;
165 
166         version (Posix)
167         {
168             import std.string : toStringz;
169 
170             _h = .open(toStringz(name), flags, 0b110_000_000);
171         }
172         else version (Windows)
173         {
174             import std.utf : toUTF16z;
175 
176             _h = .CreateFileW(
177                 name.toUTF16z(),       // File name
178                 flags.access,          // Desired access
179                 flags.share,           // Share mode
180                 null,                  // Security attributes
181                 flags.mode,            // Creation disposition
182                 FILE_ATTRIBUTE_NORMAL, // Flags and attributes
183                 null,                  // Template file handle
184                 );
185         }
186 
187         sysEnforce(_h != InvalidHandle, "Failed to open file '"~ name.idup ~"'");
188     }
189 
190     unittest
191     {
192         import std.exception : ce = collectException;
193 
194         // Ensure files are opened the way they are supposed to be opened.
195 
196         immutable data = "12345678";
197         ubyte[data.length] buf;
198 
199         auto tf = testFile();
200 
201         // Make sure the file does *not* exist
202         try .file.remove(tf.name); catch (Exception e) {}
203 
204         assert( File(tf.name, FileFlags.readExisting).ce);
205         assert( File(tf.name, FileFlags.writeExisting).ce);
206         assert(!File(tf.name, FileFlags.writeNew).ce);
207         assert(!File(tf.name, FileFlags.writeAlways).ce);
208 
209         // Make sure the file *does* exist.
210         .file.write(tf.name, data);
211 
212         assert(!File(tf.name, FileFlags.readExisting).ce);
213         assert(!File(tf.name, FileFlags.writeExisting).ce);
214         assert( File(tf.name, FileFlags.writeNew).ce);
215         assert(!File(tf.name, FileFlags.writeAlways).ce);
216     }
217 
218     /**
219      * Takes control of a file handle.
220      *
221      * It is assumed that we have exclusive control over the file handle and will
222      * be closed upon destruction as usual. If non-exclusive control is desired,
223      * use $(D dup) instead.
224      *
225      * This function is useful in a couple of situations:
226      * $(UL
227      *   $(LI
228      *     The file must be opened with special flags that cannot be obtained
229      *     via $(D FileFlags)
230      *   )
231      *   $(LI
232      *     A special file handle must be opened (e.g., $(D stdout), a pipe).
233      *   )
234      * )
235      *
236      * Params:
237      *   h = The handle to assume control over. For Posix, this is a file
238      *       descriptor ($(D int)). For Windows, this is an object handle ($(D
239      *       HANDLE)).
240      */
241     this(Handle h)
242     {
243         _h = h;
244     }
245 
246     /**
247      * Duplicates the given platform-specific file handle. This is useful for
248      * taking non-exclusive control over a file handle.
249      */
250     static F dup(F = File)(Handle h)
251     {
252         version (Posix)
253         {
254             immutable new_h = .dup(h);
255             sysEnforce(new_h != InvalidHandle, "Failed to duplicate handle");
256             return F(new_h);
257         }
258         else version (Windows)
259         {
260             Handle new_h = void;
261             auto proc = GetCurrentProcess();
262             auto ret = DuplicateHandle(
263                 proc,   // Process with the file handle
264                 h,      // Handle to duplicate
265                 proc,   // Process for the duplicated handle
266                 &new_h, // The duplicated handle
267                 0,      // Access flags, ignored
268                 true,   // Allow this handle to be inherited
269                 DUPLICATE_SAME_ACCESS
270             );
271             sysEnforce(ret, "Failed to duplicate handle");
272             return F(new_h);
273         }
274     }
275 
276     /**
277      * Copying is disabled because references counting should be used instead.
278      */
279     @disable this(this);
280 
281     /**
282      * Closes the stream if it is open. Otherwise, it does nothing.
283      */
284     void close()
285     {
286         // The file handle should only be invalid if the constructor throws an
287         // exception.
288         if (!isOpen) return;
289 
290         version (Posix)
291         {
292             sysEnforce(.close(_h) != -1, "Failed to close file");
293         }
294         else version (Windows)
295         {
296             sysEnforce(CloseHandle(_h), "Failed to close file");
297         }
298 
299         _h = InvalidHandle;
300     }
301 
302     /// Ditto
303     ~this()
304     {
305         close();
306     }
307 
308     /**
309      * Returns the internal file handle. On POSIX, this is a file descriptor. On
310      * Windows, this is an object handle.
311      */
312     @property Handle handle() pure nothrow
313     {
314         return _h;
315     }
316 
317     /**
318      * Returns true if the file is open.
319      */
320     @property bool isOpen()
321     {
322         return _h != InvalidHandle;
323     }
324 
325     /**
326      * Reads data from the file.
327      *
328      * Params:
329      *   buf = The buffer to read the data into. The length of the buffer
330      *         specifies how much data should be read.
331      *
332      * Returns: The number of bytes that were read. 0 indicates that the end of
333      * the file has been reached.
334      */
335     size_t read(scope ubyte[] buf)
336     in { assert(isOpen); }
337     body
338     {
339         version (Posix)
340         {
341             immutable n = .read(_h, buf.ptr, buf.length);
342             sysEnforce(n >= 0, "Failed to read from file");
343             return n;
344         }
345         else version (Windows)
346         {
347             DWORD n = void;
348             sysEnforce(
349                 ReadFile(_h, buf.ptr, cast(uint)buf.length, &n, null),
350                 "Failed to read from file"
351                 );
352             return n;
353         }
354     }
355 
356     unittest
357     {
358         auto tf = testFile();
359 
360         immutable data = "\r\n\n\r\n";
361         file.write(tf.name, data);
362 
363         char[data.length] buf;
364 
365         auto f = File(tf.name, FileFlags.readExisting);
366         assert(buf[0 .. f.read(buf)] == data);
367     }
368 
369     version (Posix) private size_t readv(iovec* iov, ubyte[][] bufs)
370     {
371         for (size_t i = 0; i < bufs.length; ++i)
372         {
373             iov[i].iov_base = cast(void*)bufs[i].ptr;
374             iov[i].iov_len  = bufs[i].length;
375         }
376 
377         immutable n = .readv(_h, iov, cast(int)bufs.length);
378         sysEnforce(n != -1, "Failed to read from file");
379         return n;
380     }
381 
382     /**
383      * Vectorized read.
384      *
385      * Params:
386      *   bufs = The buffers to read the data into. The length of each buffer
387      *          specifies how much data should be read.
388      *
389      * Returns: The number of bytes that were read. 0 indicates that the end of
390      * the file has been reached.
391      */
392     size_t readv(size_t StackSize = 32)(scope ubyte[][] bufs...)
393     {
394         import core.stdc.stdlib : malloc, free;
395 
396         version (Posix)
397         {
398             if (bufs.length <= StackSize)
399             {
400                 iovec[StackSize] iov = void;
401                 return readv(iov.ptr, bufs);
402             }
403             else
404             {
405                 // Not enough space in stack-allocated buffer, fallback to using
406                 // a heap-allocated buffer.
407                 iovec* iov = cast(iovec*)malloc(iovec.sizeof * bufs.length);
408                 scope (exit) free(iov);
409                 return readv(iov, bufs);
410             }
411         }
412         else version (Windows)
413         {
414             // Dumb implementation for Windows.
415             size_t totalLength = 0;
416 
417             foreach (buf; bufs)
418             {
419                 if (immutable n = read(buf))
420                     totalLength += n;
421                 else
422                     break;
423             }
424 
425             return totalLength;
426         }
427     }
428 
429     unittest
430     {
431         auto tf = testFile();
432 
433         immutable ubyte[] data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
434 
435         ubyte[4] buf1;
436         ubyte[3] buf2;
437         ubyte[5] buf3;
438 
439         static assert(buf1.length + buf2.length + buf3.length == data.length);
440 
441         assert(File(tf.name, FileFlags.writeEmpty).write(data) == data.length);
442         assert(File(tf.name, FileFlags.readExisting).readv(buf1, buf2, buf3) == data.length);
443         assert(buf1 ~ buf2 ~ buf3 == data);
444 
445         assert(File(tf.name, FileFlags.writeEmpty).write(data) == data.length);
446         assert(File(tf.name, FileFlags.readExisting).readv!2(buf1, buf2, buf3) == data.length);
447         assert(buf1 ~ buf2 ~ buf3 == data);
448     }
449 
450     /**
451      * Writes data to the file.
452      *
453      * Params:
454      *   data = The data to write to the file. The length of the slice indicates
455      *          how much data should be written.
456      *
457      * Returns: The number of bytes that were written.
458      */
459     size_t write(in ubyte[] data)
460     in { assert(isOpen); }
461     body
462     {
463         version (Posix)
464         {
465             immutable n = .write(_h, data.ptr, data.length);
466             sysEnforce(n != -1, "Failed to write to file");
467             return n;
468         }
469         else version (Windows)
470         {
471             DWORD written = void;
472             sysEnforce(
473                 WriteFile(_h, data.ptr, cast(uint)data.length, &written, null),
474                 "Failed to write to file"
475                 );
476             return written;
477         }
478     }
479 
480     alias put = write;
481 
482     unittest
483     {
484         auto tf = testFile();
485 
486         immutable data = "\r\n\n\r\n";
487         char[data.length] buf;
488 
489         assert(File(tf.name, FileFlags.writeEmpty).write(data) == data.length);
490         assert(File(tf.name, FileFlags.readExisting).read(buf));
491         assert(buf == data);
492     }
493 
494     version (Posix) private size_t writev(iovec* iov, in ubyte[][] bufs)
495     {
496         for (size_t i = 0; i < bufs.length; ++i)
497         {
498             iov[i].iov_base = cast(void*)bufs[i].ptr;
499             iov[i].iov_len  = bufs[i].length;
500         }
501 
502         immutable n = .writev(_h, iov, cast(int)bufs.length);
503         sysEnforce(n != -1, "Failed to write to file");
504         return n;
505     }
506 
507     /**
508      * Vectorized write.
509      *
510      * Params:
511      *   bufs = The buffers to write the data from. The length of each buffer
512      *          specifies how much data should be written.
513      *
514      * Returns: The number of bytes that were written.
515      */
516     size_t writev(size_t StackSize = 32)(in ubyte[][] bufs...)
517     {
518         import core.stdc.stdlib : malloc, free;
519 
520         version (Posix)
521         {
522             if (bufs.length <= StackSize)
523             {
524                 iovec[StackSize] iov = void;
525                 return writev(iov.ptr, bufs);
526             }
527             else
528             {
529                 // Not enough space in stack-allocated buffer, fallback to using
530                 // a heap-allocated buffer.
531                 iovec* iov = cast(iovec*)malloc(iovec.sizeof * bufs.length);
532                 scope (exit) free(iov);
533                 return writev(iov, bufs);
534             }
535         }
536         else version (Windows)
537         {
538             // Dumb implementation for Windows.
539             size_t totalLength = 0;
540 
541             foreach (buf; bufs)
542             {
543                 if (immutable n = write(buf))
544                     totalLength += n;
545                 else
546                     break;
547             }
548 
549             return totalLength;
550         }
551     }
552 
553     unittest
554     {
555         auto tf = testFile();
556 
557         immutable ubyte[] data1 = [1, 2, 3, 4, 5, 6, 7, 8];
558         immutable ubyte[] data2 = [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
559         immutable ubyte[] data3 = [21, 22, 23, 24];
560         immutable totalLength = data1.length + data2.length + data3.length;
561         ubyte[totalLength] buf;
562 
563         assert(File(tf.name, FileFlags.writeEmpty).writev(data1, data2, data3) == totalLength);
564         assert(File(tf.name, FileFlags.readExisting).read(buf) == buf.length);
565         assert(buf == data1 ~ data2 ~ data3);
566 
567         assert(File(tf.name, FileFlags.writeEmpty).writev!2(data1, data2, data3) == totalLength);
568         assert(File(tf.name, FileFlags.readExisting).read(buf) == buf.length);
569         assert(buf == data1 ~ data2 ~ data3);
570     }
571 
572     /**
573      * Seeks relative to a position.
574      *
575      * Params:
576      *   offset = Offset relative to a reference point.
577      *   from   = Optional reference point.
578      */
579     long seekTo(long offset, From from = From.start)
580     in { assert(isOpen); }
581     body
582     {
583         version (Posix)
584         {
585             int whence = void;
586 
587             final switch (from)
588             {
589                 case From.start: whence = SEEK_SET; break;
590                 case From.here:  whence = SEEK_CUR; break;
591                 case From.end:   whence = SEEK_END; break;
592             }
593 
594             immutable pos = .lseek(_h, offset, whence);
595             sysEnforce(pos != -1, "Failed to seek to position");
596             return pos;
597         }
598         else version (Windows)
599         {
600             DWORD whence = void;
601 
602             final switch (from)
603             {
604                 case From.start: whence = FILE_BEGIN;   break;
605                 case From.here:  whence = FILE_CURRENT; break;
606                 case From.end:   whence = FILE_END;     break;
607             }
608 
609             long pos = void;
610             sysEnforce(SetFilePointerEx(_h, offset, &pos, whence),
611                 "Failed to seek to position");
612             return pos;
613         }
614     }
615 
616     unittest
617     {
618         auto tf = testFile();
619 
620         auto f = File(tf.name, FileFlags.readWriteAlways);
621 
622         immutable data = "abcdefghijklmnopqrstuvwxyz";
623         assert(f.write(data) == data.length);
624 
625         assert(f.seekTo(5) == 5);
626         assert(f.skip(5) == 10);
627         assert(f.seekTo(-5, From.end) == data.length - 5);
628 
629         // Test offset beyond the end of the file
630         assert(f.seekTo(int.max) == int.max);
631     }
632 
633     /**
634      * Gets the size of the file.
635      *
636      * Example:
637      * ---
638      * auto f = File("foobar", FileFlags.writeEmpty);
639      * f.write("test");
640      * assert(f.length == 4);
641      * ---
642      */
643     @property long length()
644     in { assert(isOpen); }
645     body
646     {
647         version(Posix)
648         {
649             // Note that this uses stat to get the length of the file instead of
650             // the seek method. This method is safer because it is atomic.
651             stat_t stat = void;
652             sysEnforce(.fstat(_h, &stat) != -1);
653             return stat.st_size;
654         }
655         else version (Windows)
656         {
657             long size = void;
658             sysEnforce(GetFileSizeEx(_h, &size));
659             return size;
660         }
661     }
662 
663     unittest
664     {
665         auto tf = testFile();
666         auto f = File(tf.name, FileFlags.writeEmpty);
667 
668         assert(f.length == 0);
669 
670         immutable data = "0123456789";
671         assert(f.write(data) == data.length);
672         auto m = f.seekTo(3);
673 
674         assert(f.length == data.length);
675 
676         assert(f.position == m);
677     }
678 
679     /**
680      * Sets the length of the file. This can be used to truncate or extend the
681      * length of the file. If the file is extended, the new segment is not
682      * guaranteed to be initialized to zeros.
683      *
684      * Example:
685      * ---
686      * auto f = File("foobar", FileFlags.writeEmpty);
687      * f.length = 42;
688      * assert(f.length == 42);
689      * ---
690      */
691     @property void length(long len)
692     in { assert(isOpen); }
693     body
694     {
695         version (Posix)
696         {
697             sysEnforce(
698                 ftruncate(_h, len) == 0,
699                 "Failed to set the length of the file"
700                 );
701         }
702         else version (Windows)
703         {
704             // FIXME: This is not thread safe because it requires 4 system calls.
705             auto pos = seekTo(0, From.here);
706             seekTo(len); // Seek to the correct position
707             scope (exit) seekTo(pos); // Seek back
708 
709             sysEnforce(
710                 SetEndOfFile(_h),
711                 "Failed to set the length of the file"
712                 );
713         }
714     }
715 
716     unittest
717     {
718         auto tf = testFile();
719         auto f = File(tf.name, FileFlags.writeEmpty);
720         assert(f.length == 0);
721         assert(f.position == 0);
722 
723         // Extend
724         f.length = 100;
725         assert(f.length == 100);
726         assert(f.position == 0);
727 
728         // Truncate
729         f.length = 0;
730         assert(f.length == 0);
731         assert(f.position == 0);
732     }
733 
734     /**
735      * Checks if the file refers to a terminal.
736      *
737      * Example:
738      * ---
739      * assert(stdin.isTerminal);
740      * assert(stderr.isTerminal);
741      * assert(stdout.isTerminal);
742      * assert(!File("test", FileFlags.writeAlways).isTerminal);
743      * ---
744      */
745     @property bool isTerminal()
746     in { assert(isOpen); }
747     body
748     {
749         version (Posix)
750         {
751             return isatty(_h) == 1;
752         }
753         else version (Windows)
754         {
755             return GetFileType(_h) == FILE_TYPE_CHAR;
756         }
757     }
758 
759     unittest
760     {
761         auto tf = testFile();
762         assert(!File(tf.name, FileFlags.writeEmpty).isTerminal);
763     }
764 
765     enum LockType
766     {
767         /**
768          * Shared access to the locked file. Other processes can also access the
769          * file.
770          */
771         read,
772 
773         /**
774          * Exclusive access to the locked file. No other processes may access
775          * the file.
776          */
777         readWrite,
778     }
779 
780     version (Posix)
781     {
782         private int lockImpl(int operation, short type,
783             long start, long length)
784         {
785             flock fl = {
786                 l_type:   type,
787                 l_whence: SEEK_SET,
788                 l_start:  start,
789                 l_len:    (length == long.max) ? 0 : length,
790                 l_pid:    -1,
791             };
792 
793             return .fcntl(_h, operation, &fl);
794         }
795     }
796     else version (Windows)
797     {
798         private BOOL lockImpl(alias F, Flags...)(
799             long start, long length, Flags flags)
800         {
801             import std.conv : to;
802 
803             immutable ULARGE_INTEGER
804                 liStart = {QuadPart: start.to!ulong},
805                 liLength = {QuadPart: length.to!ulong};
806 
807             OVERLAPPED overlapped = {
808                 Offset: liStart.LowPart,
809                 OffsetHigh: liStart.HighPart,
810                 hEvent: null,
811             };
812 
813             return F(_h, flags, 0, liLength.LowPart, liLength.HighPart,
814                 &overlapped);
815         }
816     }
817 
818     /**
819      * Locks the specified file segment. If the file segment is already locked
820      * by another process, waits until the existing lock is released.
821      *
822      * Note that this is a $(I per-process) lock. This locking mechanism should
823      * not be used for thread-level synchronization. For that, use the $(D
824      * synchronized) statement.
825      */
826     void lock(LockType lockType = LockType.readWrite,
827         long start = 0, long length = long.max)
828     in { assert(isOpen); }
829     body
830     {
831         version (Posix)
832         {
833             sysEnforce(
834                 lockImpl(F_SETLKW,
835                     lockType == LockType.readWrite ? F_WRLCK : F_RDLCK,
836                     start, length,
837                 ) != -1, "Failed to lock file"
838             );
839         }
840         else version (Windows)
841         {
842             sysEnforce(
843                 lockImpl!LockFileEx(
844                     start, length,
845                     lockType == LockType.readWrite ? LOCKFILE_EXCLUSIVE_LOCK : 0
846                 ), "Failed to lock file"
847             );
848         }
849     }
850 
851     /**
852      * Like $(D lock), but returns false immediately if the lock is held by
853      * another process. Returns true if the specified region in the file was
854      * successfully locked.
855      */
856     bool tryLock(LockType lockType = LockType.readWrite,
857         long start = 0, long length = long.max)
858     in { assert(isOpen); }
859     body
860     {
861         version (Posix)
862         {
863             import core.stdc.errno;
864 
865             // Set the lock, return immediately if it's being held by another
866             // process.
867             if (lockImpl(F_SETLK,
868                     lockType == LockType.readWrite ? F_WRLCK : F_RDLCK,
869                     start, length) != 0
870                 )
871             {
872                 // Is another process is holding the lock?
873                 if (errno == EACCES || errno == EAGAIN)
874                     return false;
875                 else
876                     sysEnforce(false, "Failed to lock file");
877             }
878 
879             return true;
880         }
881         else version (Windows)
882         {
883             immutable flags = LOCKFILE_FAIL_IMMEDIATELY | (
884                 (lockType == LockType.readWrite) ? LOCKFILE_EXCLUSIVE_LOCK : 0);
885             if (!lockImpl!LockFileEx(start, length, flags))
886             {
887                 if (GetLastError() == ERROR_IO_PENDING ||
888                     GetLastError() == ERROR_LOCK_VIOLATION)
889                     return false;
890                 else
891                     sysEnforce(false, "Failed to lock file");
892             }
893 
894             return true;
895         }
896     }
897 
898     void unlock(long start = 0, long length = long.max)
899     in { assert(isOpen); }
900     body
901     {
902         version (Posix)
903         {
904             sysEnforce(
905                 lockImpl(F_SETLK, F_UNLCK, start, length) != -1,
906                 "Failed to lock file"
907             );
908         }
909         else version (Windows)
910         {
911             sysEnforce(lockImpl!UnlockFileEx(start, length),
912                     "Failed to unlock file");
913         }
914     }
915 
916     /**
917      * Syncs all modified cached data of the file to disk. This includes data
918      * written to the file as well as meta data (e.g., last modified time, last
919      * access time).
920      */
921     void sync()
922     in { assert(isOpen); }
923     body
924     {
925         version (Posix)
926         {
927             sysEnforce(fsync(_h) == 0);
928         }
929         else version (Windows)
930         {
931             sysEnforce(FlushFileBuffers(_h) != 0);
932         }
933     }
934 
935     /**
936      * Copies the rest of this file to the other. The positions of both files
937      * are appropriately incremented, as if one called read()/write() to copy
938      * the file. The number of copied bytes is returned.
939      */
940     version (linux)
941     {
942         size_t copyTo(File other, size_t n = ptrdiff_t.max)
943         in { assert(isOpen && other.isOpen); }
944         body
945         {
946             immutable written = .sendfile(other._h, _h, null, n);
947             sysEnforce(written >= 0, "Failed to copy file.");
948             return written;
949         }
950 
951         unittest
952         {
953             import std.conv : to;
954 
955             auto a = tempFile.file;
956             auto b = tempFile.file;
957             immutable s = "This will be copied to the other file.";
958             a.write(s);
959             a.position = 0;
960             a.copyTo(b);
961             assert(a.position == s.length);
962 
963             b.position = 0;
964 
965             char[s.length] buf;
966             assert(b.read(buf) == s.length);
967             assert(buf == s);
968         }
969     }
970 }
971 
972 unittest
973 {
974     static assert(isSink!FileBase);
975     static assert(isSource!FileBase);
976     static assert(isSeekable!FileBase);
977 }
978 
979 import std.typecons;
980 import io.buffer.fixed;
981 alias UnbufferedFile = RefCounted!(StreamShim!FileBase, RefCountedAutoInitialize.no);
982 alias File = RefCounted!(StreamShim!(FixedBufferBase!FileBase), RefCountedAutoInitialize.no);
983 
984 unittest
985 {
986     import io.buffer.traits;
987 
988     static assert(isSink!UnbufferedFile);
989     static assert(isSource!UnbufferedFile);
990     static assert(isSeekable!UnbufferedFile);
991     static assert(isBufferable!UnbufferedFile);
992 
993     static assert(isSink!File);
994     static assert(isSource!File);
995     static assert(isSeekable!File);
996     static assert(isFlushable!File);
997 }
998 
999 unittest
1000 {
1001     auto tf = testFile();
1002 
1003     immutable message = "The quick brown fox jumps over the lazy dog.";
1004 
1005     char[message.length] buf;
1006 
1007     foreach (bufSize; [0, 1, 2, 8, 16, 64, 256])
1008     {
1009         {
1010             auto f = File(tf.name, FileFlags.writeEmpty);
1011             f.bufferSize = bufSize;
1012             assert(f.bufferSize == bufSize);
1013             assert(f.write(message) == message.length);
1014         }
1015 
1016         {
1017             auto f = File(tf.name, FileFlags.readExisting);
1018             f.bufferSize = bufSize;
1019             assert(f.bufferSize == bufSize);
1020             assert(buf[0 .. f.read(buf)] == message);
1021         }
1022     }
1023 }
1024 
1025 unittest
1026 {
1027     auto tf = testFile();
1028 
1029     immutable data = "The quick brown fox jumps over the lazy dog.";
1030     char[data.length] buffer;
1031 
1032     foreach (bufSize; [0, 1, 2, 8, 16, 64, 4096, 8192])
1033     {
1034         auto f = File(tf.name, FileFlags.readWriteEmpty);
1035         f.bufferSize = bufSize;
1036         assert(f.bufferSize == bufSize);
1037 
1038         assert(f.write(data) == data.length);
1039         f.position = 0;
1040         assert(f.read(buffer) == buffer.length);
1041         assert(buffer == data);
1042     }
1043 }
1044 
1045 version (unittest)
1046 {
1047     /**
1048      * Generates a file name for testing and attempts to delete it on
1049      * destruction.
1050      */
1051     auto testFile(string file = __FILE__, size_t line = __LINE__)
1052     {
1053         import std.conv : text;
1054         import std.path : baseName;
1055         import std.file : tempDir;
1056 
1057         static struct TestFile
1058         {
1059             string name;
1060 
1061             alias name this;
1062 
1063             ~this()
1064             {
1065                 // Don't care if this fails.
1066                 try .file.remove(name); catch (Exception e) {}
1067             }
1068         }
1069 
1070         return TestFile(text(tempDir, "/.deleteme-", baseName(file), ".", line));
1071     }
1072 }