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 00035 @author Kris 00036 John Reimer 00037 Anders F Bjorklund (Darwin patches) 00038 Chris Sauls (Win95 file support) 00039 00040 *******************************************************************************/ 00041 00042 module mango.io.FileConduit; 00043 00044 public import mango.io.FilePath, 00045 mango.io.FileProxy, 00046 mango.io.FileStyle; 00047 00048 private import mango.sys.OS; 00049 00050 private import mango.io.Buffer, 00051 mango.io.Conduit, 00052 mango.io.ConduitStyle, 00053 mango.io.DeviceConduit; 00054 00055 version (Win32) 00056 { 00057 private extern (Windows) 00058 { 00059 BOOL SetEndOfFile (HANDLE); 00060 BOOL UnmapViewOfFile (LPCVOID); 00061 BOOL FlushViewOfFile (LPCVOID, DWORD); 00062 LPVOID MapViewOfFile (HANDLE, DWORD, DWORD, DWORD, DWORD); 00063 HANDLE CreateFileMappingA (HANDLE, LPSECURITY_ATTRIBUTES, DWORD, DWORD, DWORD, LPCTSTR); 00064 } 00065 } 00066 else 00067 private extern (C) int ftruncate (int, int); 00068 00069 00070 /******************************************************************************* 00071 00072 Implements a means of reading and writing a generic file. Conduits 00073 are the primary means of accessing external data, and are usually 00074 routed through a Buffer. File conduit extends the generic conduit 00075 by providing file-specific methods to set the file size, seek to a 00076 specific file position, and so on. Also provided is a class for 00077 creating a memory-mapped Buffer upon a file. 00078 00079 Serial input and output is straightforward. In this example we 00080 copy a file directly to the console: 00081 00082 @code 00083 // open a file for reading 00084 FileConduit from = new FileConduit ("test.txt"); 00085 00086 // stream directly to console 00087 Stdout.conduit.copy (from); 00088 @endcode 00089 00090 And here we copy one file to another: 00091 00092 @code 00093 // open a file for reading 00094 FileConduit from = new FileConduit ("test.txt"); 00095 00096 // open another for writing 00097 FileConduit to = new FileConduit ("copy.txt", FileStyle.WriteTruncate); 00098 00099 // copy file 00100 to.copy (from); 00101 @endcode 00102 00103 FileConduit can just as easily handle random IO. Here we see how 00104 a Reader and Writer are used to perform simple input and output: 00105 00106 @code 00107 // open a file for reading 00108 FileConduit fc = new FileConduit ("random.bin", FileStyle.ReadWriteCreate); 00109 00110 // construct (binary) reader & writer upon this conduit 00111 Reader read = new Reader (fc); 00112 Writer write = new Writer (fc); 00113 00114 int x=10, y=20; 00115 00116 // write some data, and flush output since IO is buffered 00117 write (x) (y) (); 00118 00119 // rewind to file start 00120 fc.seek (0); 00121 00122 // read data back again, but swap destinations 00123 read (y) (x); 00124 00125 assert (y==10); 00126 assert (x==20); 00127 00128 fc.close(); 00129 @endcode 00130 00131 FileConduits can also be used directly, without Readers, Writers, or 00132 Buffers. To load a file directly into local-memory one might do this: 00133 00134 @code 00135 // open file for reading 00136 FileConduit fc = new FileConduit ("test.txt"); 00137 00138 // create an array to house the entire file 00139 char[] content = new char[fc.length]; 00140 00141 // read the file content. Return value is the number of bytes read 00142 int bytesRead = fc.read (content); 00143 @endcode 00144 00145 Conversely, one may write directly to a FileConduit, like so: 00146 00147 @code 00148 // open file for writing 00149 FileConduit to = new FileConduit ("text.txt", FileStyle.WriteTruncate); 00150 00151 // write an array of content to it 00152 int bytesWritten = fc.write (content); 00153 @endcode 00154 00155 00156 See File, FilePath, FileProxy, FileConst, FileScan, and FileSystem for 00157 additional functionality related to file manipulation. 00158 00159 Doxygen has a hard time with D version() statements, so part of this 00160 class is documented within FileConduit::VersionWin32 instead. 00161 00162 Compile with -version=Win32SansUnicode to enable Win95 & Win32s file 00163 support. 00164 00165 *******************************************************************************/ 00166 00167 class FileConduit : DeviceConduit, ISeekable 00168 { 00169 // the file we're working with 00170 private FilePath path; 00171 00172 // expose deviceconduit.copy() methods also 00173 alias DeviceConduit.copy copy; 00174 alias DeviceConduit.read read; 00175 alias DeviceConduit.write write; 00176 00177 /*********************************************************************** 00178 00179 Create a FileConduit with the provided path and style. 00180 00181 ***********************************************************************/ 00182 00183 this (char[] name, FileStyle style = FileStyle.ReadExisting) 00184 { 00185 this (new FilePath(name), style); 00186 } 00187 00188 /*********************************************************************** 00189 00190 Create a FileConduit from the provided proxy and style. 00191 00192 ***********************************************************************/ 00193 00194 this (FileProxy proxy, FileStyle style = FileStyle.ReadExisting) 00195 { 00196 this (proxy.getPath(), style); 00197 } 00198 00199 /*********************************************************************** 00200 00201 Create a FileConduit with the provided path and style. 00202 00203 ***********************************************************************/ 00204 00205 this (FilePath path, FileStyle style = FileStyle.ReadExisting) 00206 { 00207 // say we're seekable 00208 super (style, true); 00209 00210 // remember who we are 00211 this.path = path; 00212 00213 // open the file 00214 open (style); 00215 00216 // bump lock count 00217 acquire(); 00218 } 00219 00220 /*********************************************************************** 00221 00222 Return the FilePath used by this file. 00223 00224 ***********************************************************************/ 00225 00226 FilePath getPath () 00227 { 00228 return path; 00229 } 00230 00231 /*********************************************************************** 00232 00233 Move the file position to the given offset (from the file 00234 start) and return the adjusted postion. 00235 00236 ***********************************************************************/ 00237 00238 long seek (long offset) 00239 { 00240 return seek (offset, SeekAnchor.Begin); 00241 } 00242 00243 /*********************************************************************** 00244 00245 Return the current file position. 00246 00247 ***********************************************************************/ 00248 00249 long getPosition () 00250 { 00251 return seek (0, SeekAnchor.Current); 00252 } 00253 00254 /*********************************************************************** 00255 00256 Return the total length of this file. 00257 00258 ***********************************************************************/ 00259 00260 long length () 00261 { 00262 long pos, 00263 ret; 00264 00265 pos = getPosition (); 00266 ret = seek (0, SeekAnchor.End); 00267 seek (pos); 00268 return ret; 00269 } 00270 00271 /*********************************************************************** 00272 00273 Transfer the content of another file to this one. Returns a 00274 reference to this class on success, or throws an IOException 00275 upon failure. 00276 00277 ***********************************************************************/ 00278 00279 FileConduit copy (FilePath source) 00280 { 00281 auto FileConduit fc = new FileConduit (source); 00282 super.copy (fc); 00283 return this; 00284 } 00285 00286 /*********************************************************************** 00287 00288 Return the name used by this file. 00289 00290 ***********************************************************************/ 00291 00292 protected override char[] getName () 00293 { 00294 return path.toString; 00295 } 00296 00297 00298 /*********************************************************************** 00299 00300 Windows-specific code 00301 00302 ***********************************************************************/ 00303 00304 version(Win32) 00305 { 00306 private bool appending; 00307 00308 /*************************************************************** 00309 00310 Open a file with the provided style. 00311 00312 ***************************************************************/ 00313 00314 protected void open (FileStyle style) 00315 { 00316 DWORD attr, 00317 share, 00318 access, 00319 create; 00320 00321 alias DWORD[] Flags; 00322 00323 static const Flags Access = 00324 [ 00325 0, // invalid 00326 GENERIC_READ, 00327 GENERIC_WRITE, 00328 GENERIC_READ | GENERIC_WRITE, 00329 ]; 00330 00331 static const Flags Create = 00332 [ 00333 OPEN_EXISTING, // must exist 00334 CREATE_ALWAYS, // create always 00335 TRUNCATE_EXISTING, // must exist 00336 CREATE_ALWAYS, // (for appending) 00337 ]; 00338 00339 static const Flags Share = 00340 [ 00341 FILE_SHARE_READ, 00342 FILE_SHARE_WRITE, 00343 FILE_SHARE_READ | FILE_SHARE_WRITE, 00344 ]; 00345 00346 static const Flags Attr = 00347 [ 00348 0, 00349 FILE_FLAG_RANDOM_ACCESS, 00350 FILE_FLAG_SEQUENTIAL_SCAN, 00351 0, 00352 FILE_FLAG_WRITE_THROUGH, 00353 ]; 00354 00355 attr = Attr[style.cache]; 00356 share = Share[style.share]; 00357 create = Create[style.open]; 00358 access = Access[style.access]; 00359 00360 version (Win32SansUnicode) 00361 handle = CreateFileA (path.toUtf8, access, share, 00362 null, create, 00363 attr | FILE_ATTRIBUTE_NORMAL, 00364 cast(HANDLE) null); 00365 else 00366 handle = CreateFileW (path.toUtf16, access, share, 00367 null, create, 00368 attr | FILE_ATTRIBUTE_NORMAL, 00369 cast(HANDLE) null); 00370 00371 if (handle == INVALID_HANDLE_VALUE) 00372 error (); 00373 00374 // move to end of file? 00375 if (style.open == style.Open.Append) 00376 appending = true; 00377 } 00378 00379 /*************************************************************** 00380 00381 Write a chunk of bytes to the file from the provided 00382 array (typically that belonging to an IBuffer) 00383 00384 ***************************************************************/ 00385 00386 int write (void[] src) 00387 { 00388 DWORD written; 00389 00390 // try to emulate the Unix O_APPEND mode 00391 if (appending) 00392 SetFilePointer (handle, 0, null, SeekAnchor.End); 00393 00394 return super.write (src); 00395 } 00396 00397 /*************************************************************** 00398 00399 Set the file size to be that of the current seek 00400 position. The file must be writable for this to 00401 succeed. 00402 00403 ***************************************************************/ 00404 00405 void truncate () 00406 { 00407 // must have Generic_Write access 00408 if (! SetEndOfFile (handle)) 00409 error (); 00410 } 00411 00412 /*************************************************************** 00413 00414 Set the file seek position to the specified offset 00415 from the given anchor. 00416 00417 ***************************************************************/ 00418 00419 long seek (long offset, SeekAnchor anchor) 00420 { 00421 LONG high = (offset >> 32); 00422 long result = SetFilePointer (handle, cast(LONG) offset, 00423 &high, anchor); 00424 00425 if (result == -1 && 00426 GetLastError() != ERROR_SUCCESS) 00427 error (); 00428 00429 return result + (cast(long) high << 32); 00430 } 00431 } 00432 00433 00434 /*********************************************************************** 00435 00436 Unix-specific code. Note that some methods are 32bit only 00437 00438 ***********************************************************************/ 00439 00440 version (Posix) 00441 { 00442 /*************************************************************** 00443 00444 Open a file with the provided style. 00445 00446 ***************************************************************/ 00447 00448 protected void open (FileStyle style) 00449 { 00450 int share, 00451 access; 00452 00453 alias int[] Flags; 00454 00455 static const Flags Access = 00456 [ 00457 0, // invalid 00458 O_RDONLY, 00459 O_WRONLY, 00460 O_RDWR, 00461 ]; 00462 00463 static const Flags Create = 00464 [ 00465 0, // open existing 00466 O_CREAT, // create always 00467 O_TRUNC, // must exist 00468 O_APPEND | O_CREAT, 00469 ]; 00470 00471 // this is not the same as Windows sharing, 00472 // but it's perhaps a reasonable approximation 00473 static const Flags Share = 00474 [ 00475 0640, // read access 00476 0620, // write access 00477 0660, // read & write 00478 ]; 00479 00480 share = Share[style.share]; 00481 access = Access[style.access] | Create[style.open]; 00482 00483 handle = posix.open (path.toUtf8, access, share); 00484 if (handle == -1) 00485 error (); 00486 } 00487 00488 /*************************************************************** 00489 00490 32bit only ... 00491 00492 Set the file size to be that of the current seek 00493 position. The file must be writable for this to 00494 succeed. 00495 00496 ***************************************************************/ 00497 00498 void truncate () 00499 { 00500 // set filesize to be current seek-position 00501 if (ftruncate (handle, getPosition()) == -1) 00502 error (); 00503 } 00504 00505 /*************************************************************** 00506 00507 32bit only ... 00508 00509 Set the file seek position to the specified offset 00510 from the given anchor. 00511 00512 ***************************************************************/ 00513 00514 long seek (long offset, SeekAnchor anchor) 00515 { 00516 int result = posix.lseek (handle, offset, anchor); 00517 if (result == -1) 00518 error (); 00519 return result; 00520 } 00521 } 00522 } 00523 00524 00525 00526 /******************************************************************************* 00527 00528 Class to handle memory-mapped files 00529 00530 *******************************************************************************/ 00531 version (Mapped) 00532 { 00533 class MappedFile : MappedBuffer 00534 { 00535 version (Win32) 00536 { 00537 private FileConduit host; 00538 private void* base; 00539 private HANDLE mmFile; 00540 00541 /*************************************************************** 00542 00543 Construct a MappedBuffer upon the given FileConduit. 00544 One should set the file size using seek() & truncate() 00545 to setup the available working space. 00546 00547 ***************************************************************/ 00548 00549 this (FileConduit host) 00550 { 00551 this.host = host; 00552 00553 // can only do 32bit mapping on 32bit platform 00554 long size = host.length; 00555 ConduitStyle style = host.getStyle; 00556 00557 DWORD flags = PAGE_READWRITE; 00558 if (style.access == ConduitStyle.Access.Read) 00559 flags = PAGE_READONLY; 00560 00561 mmFile = CreateFileMappingA (host.handle, null, flags, 0, 0, null); 00562 if (mmFile is null) 00563 host.error (); 00564 00565 flags = FILE_MAP_WRITE; 00566 if (style.access == ConduitStyle.Access.Read) 00567 flags = FILE_MAP_READ; 00568 00569 base = MapViewOfFile (mmFile, flags, 0, 0, 0); 00570 if (base is null) 00571 host.error (); 00572 00573 void[] mem = base[0..size]; 00574 setValidContent (mem); 00575 } 00576 00577 /*************************************************************** 00578 00579 Ensure this is closed when GC'd 00580 00581 ***************************************************************/ 00582 00583 ~this () 00584 { 00585 close (); 00586 } 00587 00588 /*************************************************************** 00589 00590 Close this mapped buffer 00591 00592 ***************************************************************/ 00593 00594 void close () 00595 { 00596 if (base) 00597 UnmapViewOfFile (base); 00598 00599 if (mmFile) 00600 CloseHandle (mmFile); 00601 00602 mmFile = null; 00603 base = null; 00604 } 00605 00606 /*************************************************************** 00607 00608 Flush dirty content out to the drive. This 00609 fails with error 33 if the file content is 00610 virgin. Opening a file for ReadWriteExists 00611 followed by a flush() will cause this. 00612 00613 ***************************************************************/ 00614 00615 void flush () 00616 { 00617 // flush all dirty pages 00618 if (! FlushViewOfFile (base, 0)) 00619 host.error (); 00620 } 00621 } 00622 00623 00624 version (Posix) 00625 { 00626 // not yet supported 00627 private this (){}; 00628 } 00629 } 00630 }