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