1 /******************************************************************************* 2 3 copyright: Copyright (c) 2004 Kris Bell. All rights reserved 4 5 license: BSD style: $(LICENSE) 6 7 version: Mar 2004: Initial release$(BR) 8 Dec 2006: Outback release$(BR) 9 Nov 2008: relocated and simplified 10 11 authors: Kris, 12 John Reimer, 13 Anders F Bjorklund (Darwin patches), 14 Chris Sauls (Win95 file support) 15 16 *******************************************************************************/ 17 18 module tango.io.device.File; 19 20 private import tango.sys.Common; 21 22 private import tango.io.device.Device; 23 24 private import stdc = tango.stdc.stringz; 25 26 private import tango.core.Octal; 27 28 /******************************************************************************* 29 30 platform-specific functions 31 32 *******************************************************************************/ 33 34 version (Windows) 35 private import Utf = tango.text.convert.Utf; 36 else 37 private import tango.stdc.posix.unistd; 38 39 40 /******************************************************************************* 41 42 Implements a means of reading and writing a generic file. Conduits 43 are the primary means of accessing external data, and File 44 extends the basic pattern by providing file-specific methods to 45 set the file size, seek to a specific file position and so on. 46 47 Serial input and output is straightforward. In this example we 48 copy a file directly to the console: 49 --- 50 // open a file for reading 51 auto from = new File ("test.txt"); 52 53 // stream directly to console 54 Stdout.copy (from); 55 --- 56 57 And here we copy one file to another: 58 --- 59 // open file for reading 60 auto from = new File ("test.txt"); 61 62 // open another for writing 63 auto to = new File ("copy.txt", File.WriteCreate); 64 65 // copy file and close 66 to.copy.close; 67 from.close; 68 --- 69 70 You can use InputStream.load() to load a file directly into memory: 71 --- 72 auto file = new File ("test.txt"); 73 auto content = file.load; 74 file.close; 75 --- 76 77 Or use a convenience static function within File: 78 --- 79 auto content = File.get ("test.txt"); 80 --- 81 82 A more explicit version with a similar result would be: 83 --- 84 // open file for reading 85 auto file = new File ("test.txt"); 86 87 // create an array to house the entire file 88 auto content = new char [file.length]; 89 90 // read the file content. Return value is the number of bytes read 91 auto bytes = file.read (content); 92 file.close; 93 --- 94 95 Conversely, one may write directly to a File like so: 96 --- 97 // open file for writing 98 auto to = new File ("text.txt", File.WriteCreate); 99 100 // write an array of content to it 101 auto bytes = to.write (content); 102 --- 103 104 There are equivalent static functions, File.set() and 105 File.append(), which set or append file content respectively. 106 107 File can happily handle random I/O. Here we use seek() to 108 relocate the file pointer: 109 --- 110 // open a file for reading and writing 111 auto file = new File ("random.bin", File.ReadWriteCreate); 112 113 // write some data 114 file.write ("testing"); 115 116 // rewind to file start 117 file.seek (0); 118 119 // read data back again 120 char[10] tmp; 121 auto bytes = file.read (tmp); 122 123 file.close; 124 --- 125 126 Note that File is unbuffered by default - wrap an instance within 127 tango.io.stream.Buffered for buffered I/O. 128 129 Compile with -version=Win32SansUnicode to enable Win95 & Win32s file 130 support. 131 132 *******************************************************************************/ 133 134 class File : Device, Device.Seek, Device.Truncate 135 { 136 public alias Device.read read; 137 public alias Device.write write; 138 139 /*********************************************************************** 140 141 Fits into 32 bits ... 142 143 ***********************************************************************/ 144 145 align(1) struct Style 146 { 147 Access access; /// Access rights. 148 Open open; /// How to open. 149 Share share; /// How to share. 150 Cache cache; /// How to cache. 151 } 152 153 /*********************************************************************** 154 155 ***********************************************************************/ 156 157 enum Access : ubyte { 158 Read = 0x01, /// Is readable. 159 Write = 0x02, /// Is writable. 160 ReadWrite = 0x03, /// Both. 161 } 162 163 /*********************************************************************** 164 165 ***********************************************************************/ 166 167 enum Open : ubyte { 168 Exists=0, /// Must exist. 169 Create, /// Create or truncate. 170 Sedate, /// Create if necessary. 171 Append, /// Create if necessary. 172 New, /// Can't exist. 173 }; 174 175 /*********************************************************************** 176 177 ***********************************************************************/ 178 179 enum Share : ubyte { 180 None=0, /// No sharing. 181 Read, /// Shared reading. 182 ReadWrite, /// Open for anything. 183 }; 184 185 /*********************************************************************** 186 187 ***********************************************************************/ 188 189 enum Cache : ubyte { 190 None = 0x00, /// Don't optimize. 191 Random = 0x01, /// Optimize for random. 192 Stream = 0x02, /// Optimize for stream. 193 WriteThru = 0x04, /// Backing-cache flag. 194 }; 195 196 /*********************************************************************** 197 198 Read an existing file. 199 200 ***********************************************************************/ 201 202 enum Style ReadExisting = {Access.Read, Open.Exists}; 203 204 /*********************************************************************** 205 206 Read an existing file. 207 208 ***********************************************************************/ 209 210 enum Style ReadShared = {Access.Read, Open.Exists, Share.Read}; 211 212 /*********************************************************************** 213 214 Write on an existing file. Do not create. 215 216 ***********************************************************************/ 217 218 enum Style WriteExisting = {Access.Write, Open.Exists}; 219 220 /*********************************************************************** 221 222 Write on a clean file. Create if necessary. 223 224 ***********************************************************************/ 225 226 enum Style WriteCreate = {Access.Write, Open.Create}; 227 228 /*********************************************************************** 229 230 Write at the end of the file. 231 232 ***********************************************************************/ 233 234 enum Style WriteAppending = {Access.Write, Open.Append}; 235 236 /*********************************************************************** 237 238 Read and write an existing file. 239 240 ***********************************************************************/ 241 242 enum Style ReadWriteExisting = {Access.ReadWrite, Open.Exists}; 243 244 /*********************************************************************** 245 246 Read & write on a clean file. Create if necessary. 247 248 ***********************************************************************/ 249 250 enum Style ReadWriteCreate = {Access.ReadWrite, Open.Create}; 251 252 /*********************************************************************** 253 254 Read and Write. Use existing file if present. 255 256 ***********************************************************************/ 257 258 enum Style ReadWriteOpen = {Access.ReadWrite, Open.Sedate}; 259 260 261 // the file we're working with 262 private const(char)[] path_; 263 264 // the style we're opened with 265 private Style style_; 266 267 /*********************************************************************** 268 269 Create a File for use with open(). 270 271 Note that File is unbuffered by default - wrap an instance 272 within tango.io.stream.Buffered for buffered I/O. 273 274 ***********************************************************************/ 275 276 this () 277 { 278 } 279 280 /*********************************************************************** 281 282 Create a File with the provided path and style. 283 284 Note that File is unbuffered by default - wrap an instance 285 within tango.io.stream.Buffered for buffered I/O. 286 287 ***********************************************************************/ 288 289 this (const(char[]) path, Style style = ReadExisting) 290 { 291 open (path, style); 292 } 293 294 /*********************************************************************** 295 296 Return the Style used for this file. 297 298 ***********************************************************************/ 299 300 @property const Style style () 301 { 302 return style_; 303 } 304 305 /*********************************************************************** 306 307 Return the path used by this file. 308 309 ***********************************************************************/ 310 311 override immutable(char)[] toString () 312 { 313 return path_.idup; 314 } 315 316 /*********************************************************************** 317 318 Convenience function to return the content of a file. 319 Returns a slice of the provided output buffer, where 320 that has sufficient capacity, and allocates from the 321 heap where the file content is larger. 322 323 Content size is determined via the file-system, per 324 File.length, although that may be misleading for some 325 *nix systems. An alternative is to use File.load which 326 loads content until an Eof is encountered. 327 328 ***********************************************************************/ 329 330 static void[] get (const(char)[] path, void[] dst = null) 331 { 332 scope file = new File (path); 333 334 // allocate enough space for the entire file 335 auto len = cast(size_t) file.length; 336 if (dst.length < len){ 337 if (dst is null){ // avoid setting the noscan attribute, one should maybe change the return type 338 dst=new ubyte[](len); 339 } else { 340 dst.length = len; 341 } 342 } 343 344 //read the content 345 len = file.read (dst); 346 if (len is file.Eof) 347 file.error ("File.read :: unexpected eof"); 348 349 return dst [0 .. len]; 350 } 351 352 /*********************************************************************** 353 354 Convenience function to set file content and length to 355 reflect the given array. 356 357 ***********************************************************************/ 358 359 static void set (const(char)[] path, const(void)[] content) 360 { 361 scope file = new File (path, ReadWriteCreate); 362 file.write (content); 363 } 364 365 /*********************************************************************** 366 367 Convenience function to append content to a file. 368 369 ***********************************************************************/ 370 371 static void append (const(char)[] path, const(void)[] content) 372 { 373 scope file = new File (path, WriteAppending); 374 file.write (content); 375 } 376 377 /*********************************************************************** 378 379 Windows-specific code 380 381 ***********************************************************************/ 382 383 version(Win32) 384 { 385 /*************************************************************** 386 387 Low level open for sub-classes that need to apply specific 388 attributes. 389 390 Return: False in case of failure. 391 392 ***************************************************************/ 393 394 protected bool open (const(char)[] path, Style style, DWORD addattr) 395 { 396 DWORD attr, 397 share, 398 access, 399 create; 400 401 alias DWORD[] Flags; 402 403 static const Flags Access = 404 [ 405 0, // invalid 406 GENERIC_READ, 407 GENERIC_WRITE, 408 GENERIC_READ | GENERIC_WRITE, 409 ]; 410 411 static const Flags Create = 412 [ 413 OPEN_EXISTING, // must exist 414 CREATE_ALWAYS, // truncate always 415 OPEN_ALWAYS, // create if needed 416 OPEN_ALWAYS, // (for appending) 417 CREATE_NEW // can't exist 418 ]; 419 420 static const Flags Share = 421 [ 422 0, 423 FILE_SHARE_READ, 424 FILE_SHARE_READ | FILE_SHARE_WRITE, 425 ]; 426 427 static const Flags Attr = 428 [ 429 0, 430 FILE_FLAG_RANDOM_ACCESS, 431 FILE_FLAG_SEQUENTIAL_SCAN, 432 0, 433 FILE_FLAG_WRITE_THROUGH, 434 ]; 435 436 // remember our settings 437 assert(path); 438 path_ = path; 439 style_ = style; 440 441 attr = Attr[style.cache] | addattr; 442 share = Share[style.share]; 443 create = Create[style.open]; 444 access = Access[style.access]; 445 446 version(TangoRuntime) 447 if (scheduler) 448 attr |= FILE_FLAG_OVERLAPPED;// + FILE_FLAG_NO_BUFFERING; 449 450 // zero terminate the path 451 char[512] zero = void; 452 auto name = stdc.toStringz (path, zero); 453 454 version (Win32SansUnicode) 455 io.handle = CreateFileA (name, access, share, 456 null, create, 457 attr | FILE_ATTRIBUTE_NORMAL, 458 null); 459 else 460 { 461 // convert to utf16 462 wchar[512] convert = void; 463 auto wide = Utf.toString16 (name[0..path.length+1], convert); 464 465 // open the file 466 io.handle = CreateFileW (wide.ptr, access, share, 467 null, create, 468 attr | FILE_ATTRIBUTE_NORMAL, 469 null); 470 } 471 472 if (io.handle is INVALID_HANDLE_VALUE) 473 return false; 474 475 // reset extended error 476 SetLastError (ERROR_SUCCESS); 477 478 // move to end of file? 479 if (style.open is Open.Append) 480 *(cast(long*) &io.asynch.Offset) = -1; 481 else 482 io.track = true; 483 484 // monitor this handle for async I/O? 485 version(TangoRuntime) 486 if (scheduler) 487 scheduler.open (io.handle, toString); 488 return true; 489 } 490 491 /*************************************************************** 492 493 Open a file with the provided style. 494 495 ***************************************************************/ 496 497 void open (const(char[]) path, Style style = ReadExisting) 498 { 499 if (! open (path, style, 0)) 500 error(); 501 } 502 503 /*************************************************************** 504 505 Set the file size to be that of the current seek 506 position. The file must be writable for this to 507 succeed. 508 509 ***************************************************************/ 510 511 void truncate () 512 { 513 truncate (position); 514 } 515 516 /*************************************************************** 517 518 Set the file size to be the specified length. The 519 file must be writable for this to succeed. 520 521 ***************************************************************/ 522 523 override void truncate (long size) 524 { 525 auto s = seek (size); 526 assert (s is size); 527 528 // must have Generic_Write access 529 if (! SetEndOfFile (io.handle)) 530 error(); 531 } 532 533 /*************************************************************** 534 535 Set the file seek position to the specified offset 536 from the given anchor. 537 538 ***************************************************************/ 539 540 override long seek (long offset, Anchor anchor = Anchor.Begin) 541 { 542 long newOffset; 543 544 // hack to ensure overlapped.Offset and file location 545 // are correctly in synch ... 546 if (anchor is Anchor.Current) 547 SetFilePointerEx (io.handle, 548 *cast(LARGE_INTEGER*) &io.asynch.Offset, 549 cast(PLARGE_INTEGER) &newOffset, 0); 550 551 if (! SetFilePointerEx (io.handle, *cast(LARGE_INTEGER*) 552 &offset, cast(PLARGE_INTEGER) 553 &newOffset, anchor)) 554 error(); 555 556 return (*cast(long*) &io.asynch.Offset) = newOffset; 557 } 558 559 /*************************************************************** 560 561 Return the current file position. 562 563 ***************************************************************/ 564 565 @property long position () 566 { 567 return *cast(long*) &io.asynch.Offset; 568 } 569 570 /*************************************************************** 571 572 Return the total length of this file. 573 574 ***************************************************************/ 575 576 @property long length () 577 { 578 long len; 579 580 if (! GetFileSizeEx (io.handle, cast(PLARGE_INTEGER) &len)) 581 error(); 582 return len; 583 } 584 585 /*************************************************************** 586 587 Instructs the OS to flush it's internal buffers to 588 the disk device. 589 590 NOTE: Due to OS and hardware design, data flushed 591 cannot be guaranteed to be actually on disk-platters. 592 Actual durability of data depends on write-caches, 593 barriers, presence of battery-backup, filesystem and 594 OS-support. 595 596 ***************************************************************/ 597 598 void sync () 599 { 600 if (! FlushFileBuffers (io.handle)) 601 error(); 602 } 603 } 604 605 606 /*********************************************************************** 607 608 Unix-specific code. Note that some methods are 32bit only. 609 610 ***********************************************************************/ 611 612 version (Posix) 613 { 614 /*************************************************************** 615 616 Low level open for sub-classes that need to apply specific 617 attributes. 618 619 Return: 620 False in case of failure. 621 622 ***************************************************************/ 623 624 protected bool open (const(char[]) path, Style style, 625 int addflags, int access = octal!(666)) 626 { 627 alias int[] Flags; 628 629 enum O_LARGEFILE = 0x8000; 630 631 __gshared immutable Flags Access = 632 [ 633 0, // invalid 634 O_RDONLY, 635 O_WRONLY, 636 O_RDWR, 637 ]; 638 639 __gshared immutable Flags Create = 640 [ 641 0, // open existing 642 O_CREAT | O_TRUNC, // truncate always 643 O_CREAT, // create if needed 644 O_APPEND | O_CREAT, // append 645 O_CREAT | O_EXCL, // can't exist 646 ]; 647 648 __gshared immutable short[] Locks = 649 [ 650 F_WRLCK, // no sharing 651 F_RDLCK, // shared read 652 ]; 653 654 // remember our settings 655 assert(path); 656 path_ = path; 657 style_ = style; 658 659 // zero terminate and convert to utf16 660 char[512] zero = void; 661 auto name = stdc.toStringz (path, zero); 662 auto mode = Access[style.access] | Create[style.open]; 663 664 // always open as a large file 665 handle = posix.open (name, mode | O_LARGEFILE | addflags, 666 access); 667 if (handle is -1) 668 return false; 669 670 return true; 671 } 672 673 /*************************************************************** 674 675 Open a file with the provided style. 676 677 Note that files default to no-sharing. That is, 678 they are locked exclusively to the host process 679 unless otherwise stipulated. We do this in order 680 to expose the same default behaviour as Win32. 681 682 $(B No file locking for borked POSIX.) 683 684 ***************************************************************/ 685 686 void open (const(char[]) path, Style style = ReadExisting) 687 { 688 if (! open (path, style, 0)) 689 error(); 690 } 691 692 /*************************************************************** 693 694 Set the file size to be that of the current seek 695 position. The file must be writable for this to 696 succeed. 697 698 ***************************************************************/ 699 700 void truncate () 701 { 702 truncate (position); 703 } 704 705 /*************************************************************** 706 707 Set the file size to be the specified length. The 708 file must be writable for this to succeed. 709 710 ***************************************************************/ 711 712 override void truncate (long size) 713 { 714 // set filesize to be current seek-position 715 if (posix.ftruncate (handle, cast(off_t) size) is -1) 716 error(); 717 } 718 719 /*************************************************************** 720 721 Set the file seek position to the specified offset 722 from the given anchor. 723 724 ***************************************************************/ 725 726 override long seek (long offset, Anchor anchor = Anchor.Begin) 727 { 728 long result = posix.lseek (handle, cast(off_t) offset, anchor); 729 if (result is -1) 730 error(); 731 return result; 732 } 733 734 /*************************************************************** 735 736 Return the current file position. 737 738 ***************************************************************/ 739 740 @property long position () 741 { 742 return seek (0, Anchor.Current); 743 } 744 745 /*************************************************************** 746 747 Return the total length of this file. 748 749 ***************************************************************/ 750 751 @property long length () 752 { 753 stat_t stats = void; 754 if (posix.fstat (handle, &stats)) 755 error(); 756 return cast(long) stats.st_size; 757 } 758 759 /*************************************************************** 760 761 Instructs the OS to flush it's internal buffers to 762 the disk device. 763 764 NOTE: due to OS and hardware design, data flushed 765 cannot be guaranteed to be actually on disk-platters. 766 Actual durability of data depends on write-caches, 767 barriers, presence of battery-backup, filesystem and 768 OS-support. 769 770 ***************************************************************/ 771 772 void sync () 773 { 774 if (fsync (handle)) 775 error(); 776 } 777 } 778 } 779 780 781 debug (File) 782 { 783 import tango.io.Stdout; 784 785 void main() 786 { 787 char[10] ff; 788 789 auto file = new File("file.d"); 790 auto content = cast(char[]) file.load (file); 791 assert (content.length is file.length); 792 assert (file.read(ff) is file.Eof); 793 assert (file.position is content.length); 794 file.seek (0); 795 assert (file.position is 0); 796 assert (file.read(ff) is 10); 797 assert (file.position is 10); 798 assert (file.seek(0, file.Anchor.Current) is 10); 799 assert (file.seek(0, file.Anchor.Current) is 10); 800 } 801 }