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