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 }