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