| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802 | /******************************************************************************* copyright: Copyright (c) 2004 Kris Bell. All rights reserved license: BSD style: $(LICENSE) version: Mar 2004: Initial release$(BR) Dec 2006: Outback release$(BR) Nov 2008: relocated and simplified authors: Kris, John Reimer, Anders F Bjorklund (Darwin patches), Chris Sauls (Win95 file support) *******************************************************************************/ module tango.io.device.File; private import tango.sys.Common; private import tango.io.device.Device; private import stdc = tango.stdc.stringz; private import tango.core.Octal; /******************************************************************************* platform-specific functions *******************************************************************************/ version (Win32) private import Utf = tango.text.convert.Utf; else private import tango.stdc.posix.unistd; /******************************************************************************* Implements a means of reading and writing a generic file. Conduits are the primary means of accessing external data, and File extends the basic pattern by providing file-specific methods to set the file size, seek to a specific file position and so on. Serial input and output is straightforward. In this example we copy a file directly to the console: --- // open a file for reading auto from = new File ("test.txt"); // stream directly to console Stdout.copy (from); --- And here we copy one file to another: --- // open file for reading auto from = new File ("test.txt"); // open another for writing auto to = new File ("copy.txt", File.WriteCreate); // copy file and close to.copy.close; from.close; --- You can use InputStream.load() to load a file directly into memory: --- auto file = new File ("test.txt"); auto content = file.load; file.close; --- Or use a convenience static function within File: --- auto content = File.get ("test.txt"); --- A more explicit version with a similar result would be: --- // open file for reading auto file = new File ("test.txt"); // create an array to house the entire file auto content = new char [file.length]; // read the file content. Return value is the number of bytes read auto bytes = file.read (content); file.close; --- Conversely, one may write directly to a File like so: --- // open file for writing auto to = new File ("text.txt", File.WriteCreate); // write an array of content to it auto bytes = to.write (content); --- There are equivalent static functions, File.set() and File.append(), which set or append file content respectively. File can happily handle random I/O. Here we use seek() to relocate the file pointer: --- // open a file for reading and writing auto file = new File ("random.bin", File.ReadWriteCreate); // write some data file.write ("testing"); // rewind to file start file.seek (0); // read data back again char[10] tmp; auto bytes = file.read (tmp); file.close; --- Note that File is unbuffered by default - wrap an instance within tango.io.stream.Buffered for buffered I/O. Compile with -version=Win32SansUnicode to enable Win95 & Win32s file support. *******************************************************************************/ class File : Device, Device.Seek, Device.Truncate { public alias Device.read read; public alias Device.write write; /*********************************************************************** Fits into 32 bits ... ***********************************************************************/ align(1) struct Style { Access access; /// Access rights. Open open; /// How to open. Share share; /// How to share. Cache cache; /// How to cache. } /*********************************************************************** ***********************************************************************/ enum Access : ubyte { Read = 0x01, /// Is readable. Write = 0x02, /// Is writable. ReadWrite = 0x03, /// Both. } /*********************************************************************** ***********************************************************************/ enum Open : ubyte { Exists=0, /// Must exist. Create, /// Create or truncate. Sedate, /// Create if necessary. Append, /// Create if necessary. New, /// Can't exist. }; /*********************************************************************** ***********************************************************************/ enum Share : ubyte { None=0, /// No sharing. Read, /// Shared reading. ReadWrite, /// Open for anything. }; /*********************************************************************** ***********************************************************************/ enum Cache : ubyte { None = 0x00, /// Don't optimize. Random = 0x01, /// Optimize for random. Stream = 0x02, /// Optimize for stream. WriteThru = 0x04, /// Backing-cache flag. }; /*********************************************************************** Read an existing file. ***********************************************************************/ enum Style ReadExisting = {Access.Read, Open.Exists}; /*********************************************************************** Read an existing file. ***********************************************************************/ enum Style ReadShared = {Access.Read, Open.Exists, Share.Read}; /*********************************************************************** Write on an existing file. Do not create. ***********************************************************************/ enum Style WriteExisting = {Access.Write, Open.Exists}; /*********************************************************************** Write on a clean file. Create if necessary. ***********************************************************************/ enum Style WriteCreate = {Access.Write, Open.Create}; /*********************************************************************** Write at the end of the file. ***********************************************************************/ enum Style WriteAppending = {Access.Write, Open.Append}; /*********************************************************************** Read and write an existing file. ***********************************************************************/ enum Style ReadWriteExisting = {Access.ReadWrite, Open.Exists}; /*********************************************************************** Read & write on a clean file. Create if necessary. ***********************************************************************/ enum Style ReadWriteCreate = {Access.ReadWrite, Open.Create}; /*********************************************************************** Read and Write. Use existing file if present. ***********************************************************************/ enum Style ReadWriteOpen = {Access.ReadWrite, Open.Sedate}; // the file we're working with private const(char)[] path_; // the style we're opened with private Style style_; /*********************************************************************** Create a File for use with open(). Note that File is unbuffered by default - wrap an instance within tango.io.stream.Buffered for buffered I/O. ***********************************************************************/ this () { } /*********************************************************************** Create a File with the provided path and style. Note that File is unbuffered by default - wrap an instance within tango.io.stream.Buffered for buffered I/O. ***********************************************************************/ this (const(char[]) path, Style style = ReadExisting) { open (path, style); } /*********************************************************************** Return the Style used for this file. ***********************************************************************/ @property const Style style () { return style_; } /*********************************************************************** Return the path used by this file. ***********************************************************************/ override immutable(char)[] toString () { return path_.idup; } /*********************************************************************** Convenience function to return the content of a file. Returns a slice of the provided output buffer, where that has sufficient capacity, and allocates from the heap where the file content is larger. Content size is determined via the file-system, per File.length, although that may be misleading for some *nix systems. An alternative is to use File.load which loads content until an Eof is encountered. ***********************************************************************/ static void[] get (const(char)[] path, void[] dst = null) { scope file = new File (path); // allocate enough space for the entire file auto len = cast(size_t) file.length; if (dst.length < len){ if (dst is null){ // avoid setting the noscan attribute, one should maybe change the return type dst=new ubyte[](len); } else { dst.length = len; } } //read the content len = file.read (dst); if (len is file.Eof) file.error ("File.read :: unexpected eof"); return dst [0 .. len]; } /*********************************************************************** Convenience function to set file content and length to reflect the given array. ***********************************************************************/ static void set (const(char)[] path, const(void)[] content) { scope file = new File (path, ReadWriteCreate); file.write (content); } /*********************************************************************** Convenience function to append content to a file. ***********************************************************************/ static void append (const(char)[] path, const(void)[] content) { scope file = new File (path, WriteAppending); file.write (content); } /*********************************************************************** Windows-specific code ***********************************************************************/ version(Win32) { /*************************************************************** Low level open for sub-classes that need to apply specific attributes. Return: False in case of failure. ***************************************************************/ protected bool open (const(char)[] path, Style style, DWORD addattr) { DWORD attr, share, access, create; alias DWORD[] Flags; static const Flags Access = [ 0, // invalid GENERIC_READ, GENERIC_WRITE, GENERIC_READ | GENERIC_WRITE, ]; static const Flags Create = [ OPEN_EXISTING, // must exist CREATE_ALWAYS, // truncate always OPEN_ALWAYS, // create if needed OPEN_ALWAYS, // (for appending) CREATE_NEW // can't exist ]; static const Flags Share = [ 0, FILE_SHARE_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, ]; static const Flags Attr = [ 0, FILE_FLAG_RANDOM_ACCESS, FILE_FLAG_SEQUENTIAL_SCAN, 0, FILE_FLAG_WRITE_THROUGH, ]; // remember our settings assert(path); path_ = path; style_ = style; attr = Attr[style.cache] | addattr; share = Share[style.share]; create = Create[style.open]; access = Access[style.access]; version(TangoRuntime) if (scheduler) attr |= FILE_FLAG_OVERLAPPED;// + FILE_FLAG_NO_BUFFERING; // zero terminate the path char[512] zero = void; auto name = stdc.toStringz (path, zero); version (Win32SansUnicode) io.handle = CreateFileA (name, access, share, null, create, attr | FILE_ATTRIBUTE_NORMAL, null); else { // convert to utf16 wchar[512] convert = void; auto wide = Utf.toString16 (name[0..path.length+1], convert); // open the file io.handle = CreateFileW (wide.ptr, access, share, null, create, attr | FILE_ATTRIBUTE_NORMAL, null); } if (io.handle is INVALID_HANDLE_VALUE) return false; // reset extended error SetLastError (ERROR_SUCCESS); // move to end of file? if (style.open is Open.Append) *(cast(long*) &io.asynch.Offset) = -1; else io.track = true; // monitor this handle for async I/O? version(TangoRuntime) if (scheduler) scheduler.open (io.handle, toString); return true; } /*************************************************************** Open a file with the provided style. ***************************************************************/ void open (const(char[]) path, Style style = ReadExisting) { if (! open (path, style, 0)) error(); } /*************************************************************** Set the file size to be that of the current seek position. The file must be writable for this to succeed. ***************************************************************/ void truncate () { truncate (position); } /*************************************************************** Set the file size to be the specified length. The file must be writable for this to succeed. ***************************************************************/ override void truncate (long size) { auto s = seek (size); assert (s is size); // must have Generic_Write access if (! SetEndOfFile (io.handle)) error(); } /*************************************************************** Set the file seek position to the specified offset from the given anchor. ***************************************************************/ override long seek (long offset, Anchor anchor = Anchor.Begin) { long newOffset; // hack to ensure overlapped.Offset and file location // are correctly in synch ... if (anchor is Anchor.Current) SetFilePointerEx (io.handle, *cast(LARGE_INTEGER*) &io.asynch.Offset, cast(PLARGE_INTEGER) &newOffset, 0); if (! SetFilePointerEx (io.handle, *cast(LARGE_INTEGER*) &offset, cast(PLARGE_INTEGER) &newOffset, anchor)) error(); return (*cast(long*) &io.asynch.Offset) = newOffset; } /*************************************************************** Return the current file position. ***************************************************************/ @property long position () { return *cast(long*) &io.asynch.Offset; } /*************************************************************** Return the total length of this file. ***************************************************************/ @property long length () { long len; if (! GetFileSizeEx (io.handle, cast(PLARGE_INTEGER) &len)) error(); return len; } /*************************************************************** Instructs the OS to flush it's internal buffers to the disk device. NOTE: Due to OS and hardware design, data flushed cannot be guaranteed to be actually on disk-platters. Actual durability of data depends on write-caches, barriers, presence of battery-backup, filesystem and OS-support. ***************************************************************/ void sync () { if (! FlushFileBuffers (io.handle)) error(); } } /*********************************************************************** Unix-specific code. Note that some methods are 32bit only. ***********************************************************************/ version (Posix) { /*************************************************************** Low level open for sub-classes that need to apply specific attributes. Return: False in case of failure. ***************************************************************/ protected bool open (const(char[]) path, Style style, int addflags, int access = octal!(666)) { alias int[] Flags; enum O_LARGEFILE = 0x8000; enum Flags Access = [ 0, // invalid O_RDONLY, O_WRONLY, O_RDWR, ]; enum Flags Create = [ 0, // open existing O_CREAT | O_TRUNC, // truncate always O_CREAT, // create if needed O_APPEND | O_CREAT, // append O_CREAT | O_EXCL, // can't exist ]; enum short[] Locks = [ F_WRLCK, // no sharing F_RDLCK, // shared read ]; // remember our settings assert(path); path_ = path; style_ = style; // zero terminate and convert to utf16 char[512] zero = void; auto name = stdc.toStringz (path, zero); auto mode = Access[style.access] | Create[style.open]; // always open as a large file handle = posix.open (name, mode | O_LARGEFILE | addflags, access); if (handle is -1) return false; return true; } /*************************************************************** Open a file with the provided style. Note that files default to no-sharing. That is, they are locked exclusively to the host process unless otherwise stipulated. We do this in order to expose the same default behaviour as Win32. $(B No file locking for borked POSIX.) ***************************************************************/ void open (const(char[]) path, Style style = ReadExisting) { if (! open (path, style, 0)) error(); } /*************************************************************** Set the file size to be that of the current seek position. The file must be writable for this to succeed. ***************************************************************/ void truncate () { truncate (position); } /*************************************************************** Set the file size to be the specified length. The file must be writable for this to succeed. ***************************************************************/ override void truncate (long size) { // set filesize to be current seek-position if (posix.ftruncate (handle, cast(off_t) size) is -1) error(); } /*************************************************************** Set the file seek position to the specified offset from the given anchor. ***************************************************************/ override long seek (long offset, Anchor anchor = Anchor.Begin) { long result = posix.lseek (handle, cast(off_t) offset, anchor); if (result is -1) error(); return result; } /*************************************************************** Return the current file position. ***************************************************************/ @property long position () { return seek (0, Anchor.Current); } /*************************************************************** Return the total length of this file. ***************************************************************/ @property long length () { stat_t stats = void; if (posix.fstat (handle, &stats)) error(); return cast(long) stats.st_size; } /*************************************************************** Instructs the OS to flush it's internal buffers to the disk device. NOTE: due to OS and hardware design, data flushed cannot be guaranteed to be actually on disk-platters. Actual durability of data depends on write-caches, barriers, presence of battery-backup, filesystem and OS-support. ***************************************************************/ void sync () { if (fsync (handle)) error(); } } } debug (File) { import tango.io.Stdout; void main() { char[10] ff; auto file = new File("file.d"); auto content = cast(char[]) file.load (file); assert (content.length is file.length); assert (file.read(ff) is file.Eof); assert (file.position is content.length); file.seek (0); assert (file.position is 0); assert (file.read(ff) is 10); assert (file.position is 10); assert (file.seek(0, file.Anchor.Current) is 10); assert (file.seek(0, file.Anchor.Current) is 10); } } |