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