00001 /******************************************************************************* 00002 00003 @file FileConduit.d 00004 00005 Copyright (c) 2004 Kris Bell 00006 00007 This software is provided 'as-is', without any express or implied 00008 warranty. In no event will the authors be held liable for damages 00009 of any kind arising from the use of this software. 00010 00011 Permission is hereby granted to anyone to use this software for any 00012 purpose, including commercial applications, and to alter it and/or 00013 redistribute it freely, subject to the following restrictions: 00014 00015 1. The origin of this software must not be misrepresented; you must 00016 not claim that you wrote the original software. If you use this 00017 software in a product, an acknowledgment within documentation of 00018 said product would be appreciated but is not required. 00019 00020 2. Altered source versions must be plainly marked as such, and must 00021 not be misrepresented as being the original software. 00022 00023 3. This notice may not be removed or altered from any distribution 00024 of the source. 00025 00026 4. Derivative works are permitted, but they must carry this notice 00027 in full and credit the original source. 00028 00029 00030 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 00031 00032 00033 @version Initial version, March 2004 00034 @author Kris 00035 John Reimer 00036 Anders F Bjorklund (Darwin patches) 00037 00038 00039 *******************************************************************************/ 00040 00041 module mango.io.FileConduit; 00042 00043 public import mango.base.System; 00044 00045 public import mango.io.Buffer, 00046 mango.io.FilePath, 00047 mango.io.FileProxy, 00048 mango.io.FileStyle; 00049 00050 private import mango.io.Conduit, 00051 mango.io.Exception, 00052 mango.io.ConduitStyle; 00053 00054 /******************************************************************************* 00055 00056 Bring in native Unix functions. Note that the functions read() 00057 and close() will actually hide the inherited Conduit.read() and 00058 Resource.close() if the import is placed within the body of the 00059 FileConduit class. Apparently this behaviour is 'by design', so 00060 fair warning to those who might expect otherwise. 00061 00062 *******************************************************************************/ 00063 00064 version (Posix) 00065 { 00066 version (linux) 00067 { 00068 private import std.c.linux.linux; 00069 alias std.c.linux.linux posix; 00070 } 00071 00072 version (darwin) 00073 { 00074 private import std.c.darwin.darwin; 00075 alias std.c.darwin.darwin posix; 00076 } 00077 00078 private extern (C) int ftruncate (int, int); 00079 } 00080 00081 /******************************************************************************* 00082 00083 Bring in native Windows functions 00084 00085 *******************************************************************************/ 00086 00087 version (Win32) 00088 { 00089 private import std.c.windows.windows; 00090 00091 private extern (Windows) 00092 { 00093 HANDLE GetStdHandle (DWORD); 00094 BOOL SetEndOfFile (HANDLE); 00095 00096 BOOL UnmapViewOfFile (LPCVOID); 00097 BOOL FlushViewOfFile (LPCVOID, DWORD); 00098 LPVOID MapViewOfFile (HANDLE, DWORD, DWORD, DWORD, DWORD); 00099 HANDLE CreateFileMappingA (HANDLE, LPSECURITY_ATTRIBUTES, DWORD, DWORD, DWORD, LPCTSTR); 00100 } 00101 } 00102 00103 00104 /******************************************************************************* 00105 00106 Implements a means of reading and writing a generic file. Conduits 00107 are the primary means of accessing external data, and are usually 00108 routed through a Buffer. File conduit extends the generic conduit 00109 by providing file-specific methods to set the file size, seek to a 00110 specific file position, and so on. Also provided is a factory for 00111 create a memory-mapped Buffer upon a file. 00112 00113 Serial input and output is straightforward. In this example we 00114 copy a file directly to the console: 00115 00116 @code 00117 // open a file for reading 00118 FileConduit fc = new FileConduit ("test.txt"); 00119 00120 // stream directly to console 00121 Stdio.stdout.copy (fc); 00122 @endcode 00123 00124 And here we copy one file to another: 00125 00126 @code 00127 // open a file for reading 00128 FileConduit from = new FileConduit ("test.txt"); 00129 00130 // open another for writing 00131 FileConduit to = new FileConduit ("copy.txt", FileStyle.WriteTruncate); 00132 00133 // copy file 00134 to.copy (from); 00135 @endcode 00136 00137 FileConduit can just as easily handle random IO. Here we see how 00138 a Reader and Writer are used to perform simple input and output: 00139 00140 @code 00141 // open a file for reading 00142 FileConduit fc = new FileConduit ("random.bin", FileStyle.ReadWriteCreate); 00143 00144 // construct (binary) reader & writer upon this conduit 00145 Reader r = new Reader(fc); 00146 Writer w = new Writer(fc); 00147 00148 int x=10, y=20; 00149 00150 // write some data 00151 w.put(x).put(y); 00152 00153 // flush output since IO is buffered 00154 w.flush(); 00155 00156 // rewind to file start 00157 fc.seek (0); 00158 00159 // read data back again, but swap destinations 00160 r.get(y).get(x); 00161 00162 assert (y==10); 00163 assert (x==20); 00164 00165 fc.close(); 00166 @endcode 00167 00168 See FilePath, FileProxy, and FileSystem for additional functionality 00169 related to file manipulation. Note that classes CompositeWriter and 00170 CompositeReader may be more appropriate for the writing & reading of 00171 structured data. Doxygen has a hard time with D version() statements, 00172 so part of this class is documented within FileConduit::VersionWin32 00173 instead. 00174 00175 *******************************************************************************/ 00176 00177 class FileConduit : Conduit, ISeekable 00178 { 00179 // the file we're working with 00180 private FilePath path; 00181 00182 // expose conduit copy() method also (alias is truly non-intuitive ...) 00183 alias Conduit.copy copy; 00184 00185 /*********************************************************************** 00186 00187 Create a FileConduit with the provided path and style. 00188 00189 ***********************************************************************/ 00190 00191 this (char[] name, FileStyle style = FileStyle.ReadExisting) 00192 { 00193 this (new FilePath(name), style); 00194 } 00195 00196 /*********************************************************************** 00197 00198 Create a FileConduit from the provided proxy and style. 00199 00200 ***********************************************************************/ 00201 00202 this (FileProxy proxy, FileStyle style = FileStyle.ReadExisting) 00203 { 00204 this (proxy.getPath(), style); 00205 } 00206 00207 /*********************************************************************** 00208 00209 Create a FileConduit with the provided path and style. 00210 00211 ***********************************************************************/ 00212 00213 this (FilePath path, FileStyle style = FileStyle.ReadExisting) 00214 { 00215 // say we're seekable 00216 super (style, true); 00217 00218 // remember who we are 00219 this.path = path; 00220 00221 // open the file 00222 _open (style); 00223 00224 // bump lock count 00225 acquire(); 00226 } 00227 00228 /*********************************************************************** 00229 00230 Create a FileConduit on the provided FileDevice. This is 00231 strictly for adapting existing devices such as Stdout and 00232 friends. 00233 00234 ***********************************************************************/ 00235 00236 package this (FileDevice device) 00237 { 00238 // say we're not seekable 00239 super (device, false); 00240 00241 // devices don't have a path (bug?) 00242 this.path = null; 00243 00244 // open the file 00245 _reopen (device); 00246 00247 // bump lock count 00248 acquire(); 00249 } 00250 00251 /*********************************************************************** 00252 00253 Callback to close the file. This is invoked from the Resource 00254 base-class when the resource is being closed. 00255 00256 ***********************************************************************/ 00257 00258 protected override void closure () 00259 { 00260 super.closure (); 00261 _close (); 00262 } 00263 00264 /*********************************************************************** 00265 00266 Return the FilePath used by this file. 00267 00268 ***********************************************************************/ 00269 00270 FilePath getPath () 00271 { 00272 return path; 00273 } 00274 00275 /*********************************************************************** 00276 00277 Move the file position to the given offset (from the file 00278 start) and return the adjusted postion. 00279 00280 ***********************************************************************/ 00281 00282 long seek (long offset) 00283 { 00284 return seek (offset, SeekAnchor.Begin); 00285 } 00286 00287 /*********************************************************************** 00288 00289 Return the current file position. 00290 00291 ***********************************************************************/ 00292 00293 long getPosition () 00294 { 00295 return seek (0, SeekAnchor.Current); 00296 } 00297 00298 00299 /*********************************************************************** 00300 00301 Create a Buffer of the default FileConduit size, and 00302 associate it with this conduit 00303 00304 ***********************************************************************/ 00305 00306 override IBuffer createBuffer() 00307 { 00308 const int DefaultFileBufferSize = 1024 * 16; 00310 IBuffer buffer = new Buffer (DefaultFileBufferSize); 00311 00312 buffer.setConduit (this); 00313 return buffer; 00314 } 00315 00316 /*********************************************************************** 00317 00318 Return the total length of this file. 00319 00320 ***********************************************************************/ 00321 00322 long length () 00323 { 00324 long pos, 00325 ret; 00326 00327 pos = getPosition (); 00328 ret = seek (0, SeekAnchor.End); 00329 seek (pos); 00330 return ret; 00331 } 00332 00333 /*********************************************************************** 00334 00335 Transfer the content of another file to this one. Returns a 00336 reference to this class on success, or throws an IOException 00337 upon failure. 00338 00339 ***********************************************************************/ 00340 00341 FileConduit copy (FilePath source) 00342 { 00343 auto FileConduit fc = new FileConduit (source); 00344 super.copy (fc); 00345 return this; 00346 } 00347 00348 /*********************************************************************** 00349 00350 Windows-specific code 00351 00352 ***********************************************************************/ 00353 00354 version(Win32) 00355 { 00356 private HANDLE handle; 00357 private bool appending; 00358 00359 /*************************************************************** 00360 00361 Throw an IOException noting the last error 00362 00363 ***************************************************************/ 00364 00365 private void exception () 00366 { 00367 throw new IOException (path.toString() ~ ": " ~ 00368 System.error); 00369 } 00370 00371 /*************************************************************** 00372 00373 Gain access to the standard IO handles (console etc). 00374 00375 ***************************************************************/ 00376 00377 private void _reopen (FileDevice device) 00378 { 00379 static const DWORD[] id = [ 00380 cast(DWORD) -10, 00381 cast(DWORD) -11, 00382 cast(DWORD) -12 00383 ]; 00384 static const char[][] f = [ 00385 "CONIN$\0", 00386 "CONOUT$\0", 00387 "CONOUT$\0" 00388 ]; 00389 00390 handle = GetStdHandle (id[device.id]); 00391 if (! handle) 00392 handle = CreateFileA (f[device.id], 00393 GENERIC_READ | GENERIC_WRITE, 00394 FILE_SHARE_READ | FILE_SHARE_WRITE, 00395 null, OPEN_EXISTING, 0, null); 00396 if (! handle) 00397 exception(); 00398 } 00399 00400 /*************************************************************** 00401 00402 Open a file with the provided style. 00403 00404 ***************************************************************/ 00405 00406 private void _open (FileStyle style) 00407 { 00408 DWORD attr, 00409 share, 00410 access, 00411 create; 00412 00413 alias DWORD[] Flags; 00414 00415 static const Flags Access = 00416 [ 00417 0, // invalid 00418 GENERIC_READ, 00419 GENERIC_WRITE, 00420 GENERIC_READ | GENERIC_WRITE, 00421 ]; 00422 00423 static const Flags Create = 00424 [ 00425 OPEN_EXISTING, // must exist 00426 CREATE_ALWAYS, // create always 00427 TRUNCATE_EXISTING, // must exist 00428 CREATE_ALWAYS, // (for appending) 00429 ]; 00430 00431 static const Flags Share = 00432 [ 00433 FILE_SHARE_READ, 00434 FILE_SHARE_WRITE, 00435 FILE_SHARE_READ | FILE_SHARE_WRITE, 00436 ]; 00437 00438 static const Flags Attr = 00439 [ 00440 0, 00441 FILE_FLAG_RANDOM_ACCESS, 00442 FILE_FLAG_SEQUENTIAL_SCAN, 00443 0, 00444 FILE_FLAG_WRITE_THROUGH, 00445 ]; 00446 00447 attr = Attr[style.cache]; 00448 share = Share[style.share]; 00449 create = Create[style.open]; 00450 access = Access[style.access]; 00451 00452 handle = CreateFileW (path.toUtf16, access, share, 00453 null, create, 00454 attr | FILE_ATTRIBUTE_NORMAL, 00455 cast(HANDLE) null); 00456 00457 if (handle == INVALID_HANDLE_VALUE) 00458 exception (); 00459 00460 // move to end of file? 00461 if (style.open == style.Open.Append) 00462 appending = true; 00463 } 00464 00465 /*************************************************************** 00466 00467 Close the underlying file 00468 00469 ***************************************************************/ 00470 00471 private void _close () 00472 { 00473 if (! CloseHandle (handle)) 00474 exception (); 00475 } 00476 00477 /*************************************************************** 00478 00479 Read a chunk of bytes from the file into the provided 00480 array (typically that belonging to an IBuffer) 00481 00482 ***************************************************************/ 00483 00484 protected override int reader (void[] dst) 00485 { 00486 DWORD read; 00487 void *p = dst; 00488 00489 if (! ReadFile (handle, p, dst.length, &read, null)) 00490 exception (); 00491 00492 if (read == 0 && dst.length > 0) 00493 return Eof; 00494 return read; 00495 } 00496 00497 /*************************************************************** 00498 00499 Write a chunk of bytes to the file from the provided 00500 array (typically that belonging to an IBuffer) 00501 00502 ***************************************************************/ 00503 00504 protected override int writer (void[] src) 00505 { 00506 DWORD written; 00507 00508 // try to emulate the Unix O_APPEND mode 00509 if (appending) 00510 SetFilePointer (handle, 0, null, SeekAnchor.End); 00511 00512 if (! WriteFile (handle, src, src.length, &written, null)) 00513 exception (); 00514 00515 return written; 00516 } 00517 00518 /*************************************************************** 00519 00520 Set the file size to be that of the current seek 00521 position. The file must be writable for this to 00522 succeed. 00523 00524 ***************************************************************/ 00525 00526 void truncate () 00527 { 00528 // must have Generic_Write access 00529 if (! SetEndOfFile (handle)) 00530 exception (); 00531 } 00532 00533 /*************************************************************** 00534 00535 Set the file seek position to the specified offset 00536 from the given anchor. 00537 00538 ***************************************************************/ 00539 00540 long seek (long offset, SeekAnchor anchor) 00541 { 00542 LONG high = (offset >> 32); 00543 long result = SetFilePointer (handle, cast(LONG) offset, 00544 &high, anchor); 00545 00546 if (result == -1 && 00547 GetLastError() != ERROR_SUCCESS) 00548 exception (); 00549 00550 return result + (cast(long) high << 32); 00551 } 00552 } 00553 00554 00555 /*********************************************************************** 00556 00557 Unix-specific code. Note that some methods are 32bit only 00558 00559 ***********************************************************************/ 00560 00561 version (Posix) 00562 { 00563 private int handle = -1; 00564 00565 /*************************************************************** 00566 00567 Throw an IOException noting the last error 00568 00569 ***************************************************************/ 00570 00571 private void exception () 00572 { 00573 throw new IOException (path.toString() ~ ": " ~ 00574 System.error); 00575 } 00576 00577 /*************************************************************** 00578 00579 Gain access to the standard IO handles (console etc). 00580 00581 ***************************************************************/ 00582 00583 private void _reopen (FileDevice device) 00584 { 00585 handle = device.id; 00586 } 00587 00588 /*************************************************************** 00589 00590 Open a file with the provided style. 00591 00592 ***************************************************************/ 00593 00594 private void _open (FileStyle style) 00595 { 00596 int share, 00597 access; 00598 00599 alias int[] Flags; 00600 00601 static const Flags Access = 00602 [ 00603 0, // invalid 00604 O_RDONLY, 00605 O_WRONLY, 00606 O_RDWR, 00607 ]; 00608 00609 static const Flags Create = 00610 [ 00611 0, // open existing 00612 O_CREAT, // create always 00613 O_TRUNC, // must exist 00614 O_APPEND | O_CREAT, 00615 ]; 00616 00617 // this is not the same as Windows sharing, 00618 // but it's perhaps a reasonable approximation 00619 static const Flags Share = 00620 [ 00621 0640, // read access 00622 0620, // write access 00623 0660, // read & write 00624 ]; 00625 00626 share = Share[style.share]; 00627 access = Access[style.access] | Create[style.open]; 00628 00629 handle = posix.open (path.toUtf8, access, share); 00630 if (handle == -1) 00631 exception (); 00632 } 00633 00634 /*************************************************************** 00635 00636 Close the underlying file 00637 00638 ***************************************************************/ 00639 00640 private void _close () 00641 { 00642 if (posix.close (handle) == -1) 00643 exception (); 00644 } 00645 00646 /*************************************************************** 00647 00648 Read a chunk of bytes from the file into the provided 00649 array (typically that belonging to an IBuffer) 00650 00651 ***************************************************************/ 00652 00653 protected override int reader (void[] dst) 00654 { 00655 int read = posix.read (handle, dst, dst.length); 00656 if (read == -1) 00657 exception (); 00658 else 00659 if (read == 0 && dst.length > 0) 00660 return Eof; 00661 return read; 00662 } 00663 00664 /*************************************************************** 00665 00666 Write a chunk of bytes to the file from the provided 00667 array (typically that belonging to an IBuffer) 00668 00669 ***************************************************************/ 00670 00671 protected override int writer (void[] src) 00672 { 00673 int written = posix.write (handle, src, src.length); 00674 if (written == -1) 00675 exception (); 00676 return written; 00677 } 00678 00679 /*************************************************************** 00680 00681 32bit only ... 00682 00683 Set the file size to be that of the current seek 00684 position. The file must be writable for this to 00685 succeed. 00686 00687 ***************************************************************/ 00688 00689 void truncate () 00690 { 00691 // set filesize to be current seek-position 00692 if (ftruncate (handle, getPosition()) == -1) 00693 exception (); 00694 } 00695 00696 /*************************************************************** 00697 00698 32bit only ... 00699 00700 Set the file seek position to the specified offset 00701 from the given anchor. 00702 00703 ***************************************************************/ 00704 00705 long seek (long offset, SeekAnchor anchor) 00706 { 00707 int result = posix.lseek (handle, offset, anchor); 00708 if (result == -1) 00709 exception (); 00710 return result; 00711 } 00712 } 00713 } 00714 00715 00716 /******************************************************************************* 00717 00718 Class to handle memory-mapped files 00719 00720 *******************************************************************************/ 00721 00722 class MappedFile : MappedBuffer 00723 { 00724 version (Windows) 00725 { 00726 private FileConduit host; 00727 private void* base; 00728 private HANDLE mmFile; 00729 00730 /*************************************************************** 00731 00732 Construct a MappedBuffer upon the given FileConduit. 00733 One should set the file size using seek() & truncate() 00734 to setup the available working space. 00735 00736 ***************************************************************/ 00737 00738 this (FileConduit host) 00739 { 00740 this.host = host; 00741 00742 // can only do 32bit mapping on 32bit platform 00743 long size = host.length; 00744 ConduitStyle style = host.getStyle; 00745 00746 DWORD flags = PAGE_READWRITE; 00747 if (style.access == ConduitStyle.Access.Read) 00748 flags = PAGE_READONLY; 00749 00750 mmFile = CreateFileMappingA (host.handle, null, flags, 0, 0, null); 00751 if (mmFile is null) 00752 host.exception (); 00753 00754 flags = FILE_MAP_WRITE; 00755 if (style.access == ConduitStyle.Access.Read) 00756 flags = FILE_MAP_READ; 00757 00758 base = MapViewOfFile (mmFile, flags, 0, 0, 0); 00759 if (base is null) 00760 host.exception (); 00761 00762 void[] mem = base[0..size]; 00763 setValidContent (mem); 00764 } 00765 00766 /*************************************************************** 00767 00768 Ensure this is closed when GC'd 00769 00770 ***************************************************************/ 00771 00772 ~this () 00773 { 00774 close(); 00775 } 00776 00777 /*************************************************************** 00778 00779 Close this mapped buffer 00780 00781 ***************************************************************/ 00782 00783 void close () 00784 { 00785 if (base) 00786 UnmapViewOfFile (base); 00787 00788 if (mmFile) 00789 CloseHandle (mmFile); 00790 00791 mmFile = null; 00792 base = null; 00793 } 00794 00795 /*************************************************************** 00796 00797 Flush dirty content out to the drive. This 00798 fails with error 33 if the file content is 00799 virgin. Opening a file for ReadWriteExists 00800 followed by a flush() will cause this. 00801 00802 ***************************************************************/ 00803 00804 void flush () 00805 { 00806 // flush all dirty pages 00807 if (! FlushViewOfFile (base, 0)) 00808 host.exception(); 00809 } 00810 } 00811 00812 00813 version (Posix) 00814 { 00815 // not yet supported 00816 private this (){}; 00817 } 00818 } 00819 00820 00821 /******************************************************************************* 00822 00823 Class used to wrap an existing file-oriented handle, such as Stdout 00824 and its cohorts. 00825 00826 *******************************************************************************/ 00827 00828 class FileDevice : ConduitStyle 00829 { 00830 private uint id; 00831 00832 this (uint id, Access access) 00833 in { 00834 assert (id < 3); 00835 } 00836 body 00837 { 00838 super (access); 00839 this.id = id; 00840 } 00841 } 00842 00843