/* libmspack -- a library for working with Microsoft compression formats. * (C) 2003-2019 Stuart Caie * * libmspack is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License (LGPL) version 2.1 * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ using System; using System.IO; namespace LibMSPackSharp { /// /// A structure which abstracts file I/O and memory management. /// /// The library always uses the SystemImpl structure for interaction /// with the file system and to allocate, free and copy all memory.It also /// uses it to send literal messages to the library user. /// /// When the library is compiled normally, passing null to a compressor or /// decompressor constructor will result in a default SystemImpl being /// used, where all methods are implemented with the standard C library. /// However, all constructors support being given a custom created /// SystemImpl structure, with the library user's own methods. This /// allows for more abstract interaction, such as reading and writing files /// directly to memory, or from a network socket or pipe. /// /// Implementors of an SystemImpl structure should read all /// documentation entries for every structure member, and write methods /// which conform to those standards. /// public class SystemImpl { /// /// Opens a file for reading, writing, appending or updating. /// /// /// a self-referential pointer to the SystemImpl /// structure whose Open() method is being called. If /// this pointer is required by Close(), Read(), Write(), /// Seek() or Tell(), it should be stored in the result /// structure at this time. /// /// /// the file to be opened. It is passed directly from the /// library caller without being modified, so it is up to /// the caller what this parameter actually represents. /// /// One of the values /// /// a pointer to a mspack_file structure. This structure officially /// contains no members, its true contents are up to the /// SystemImpl implementor. It should contain whatever is needed /// for other SystemImpl methods to operate. Returning the null /// pointer indicates an error condition. /// public Func Open; /// /// Closes a previously opened file. If any memory was allocated for this /// particular file handle, it should be freed at this time. /// /// the file to close /// public Action Close; /// /// Reads a given number of bytes from an open file. /// /// the file to read from /// the location where the read bytes should be stored /// the number of bytes to read from the file. /// /// the number of bytes successfully read (this can be less than /// the number requested), zero to mark the end of file, or less /// than zero to indicate an error. The library does not "retry" /// reads and assumes short reads are due to EOF, so you should /// avoid returning short reads because of transient errors. /// /// /// public Func Read; /// /// Writes a given number of bytes to an open file. /// /// the file to write to /// the location where the written bytes should be read from /// the number of bytes to write to the file. /// /// the number of bytes successfully written, this can be less /// than the number requested. Zero or less can indicate an error /// where no bytes at all could be written. All cases where less /// bytes were written than requested are considered by the library /// to be an error. /// /// /// public Func Write; /// /// Seeks to a specific file offset within an open file. /// /// Sometimes the library needs to know the length of a file. It does /// this by seeking to the end of the file with seek(file, 0, /// MSPACK_SYS_SEEK_END), then calling Tell(). Implementations may want /// to make a special case for this. /// /// Due to the potentially varying 32/64 bit datatype int on some /// architectures, the #MSPACK_SYS_SELFTEST macro MUST be used before /// using the library. If not, the error caused by the library passing an /// inappropriate stackframe to Seek() is subtle and hard to trace. /// /// the file to be seeked /// an offset to seek, measured in bytes /// One of the values /// zero for success, non-zero for an error /// /// public Func Seek; /// /// Returns the current file position (in bytes) of the given file. /// /// the file whose file position is wanted /// the current file position of the file /// /// public Func Tell; /// /// Used to send messages from the library to the user. /// /// Occasionally, the library generates warnings or other messages in /// plain english to inform the human user. These are informational only /// and can be ignored if not wanted. /// /// /// may be a file handle returned from Open() if this message /// pertains to a specific open file, or null if not related to /// a specific file. /// /// a printf() style format string. It does NOT include a trailing newline. /// public Action Message; /// /// Allocates memory. /// /// /// a self-referential pointer to the SystemImpl /// structure whose Alloc() method is being called. /// /// the number of bytes to allocate /// /// a pointer to the requested number of bytes, or null if /// not enough memory is available /// /// public Func Alloc; /// /// Frees memory. /// /// the memory to be freed. null is accepted and ignored. /// public Action Free; /// /// Copies from one region of memory to another. /// /// The regions of memory are guaranteed not to overlap, are usually less /// than 256 bytes, and may not be aligned. Please note that the source /// parameter comes before the destination parameter, unlike the standard /// C function memcpy(). /// /// the region of memory to copy from /// the region of memory to copy to /// the size of the memory region, in bytes public Action Copy; /// /// A null pointer to mark the end of SystemImpl. It must equal null. /// /// Should the SystemImpl structure extend in the future, this null /// will be seen, rather than have an invalid method pointer called. /// public readonly object NullPtr = null; #region Helpers /// /// Returns the length of a file opened for reading /// public static Error GetFileLength(SystemImpl system, object file, out long length) { length = 0; long current; if (system == null || file == null) return Error.MSPACK_ERR_OPEN; // Get current offset current = system.Tell(file); // Seek to end of file if (!system.Seek(file, 0, SeekMode.MSPACK_SYS_SEEK_END)) return Error.MSPACK_ERR_SEEK; // Get offset of end of file length = system.Tell(file); // Seek back to original offset if (!system.Seek(file, current, SeekMode.MSPACK_SYS_SEEK_START)) return Error.MSPACK_ERR_SEEK; return Error.MSPACK_ERR_OK; } /// /// Validates a system structure /// public static bool ValidSystem(SystemImpl sys) { return (sys != null) && (sys.Open != null) && (sys.Close != null) && (sys.Read != null) && (sys.Write != null) && (sys.Seek != null) && (sys.Tell != null) && (sys.Message != null) && (sys.Alloc != null) && (sys.Free != null) && (sys.Copy != null) && (sys.NullPtr == null); } #endregion #region Default Implementation public static SystemImpl DefaultSystem => new SystemImpl() { Open = DefaultOpen, Close = DefaultClose, Read = DefaultRead, Write = DefaultWrite, Seek = DefaultSeek, Tell = DefaultTell, Message = DefaultMessage, Alloc = DefaultAlloc, Free = DefaultFree, Copy = DefaultCopy, }; private static object DefaultOpen(SystemImpl self, string filename, OpenMode mode) { DefaultFileImpl fileHandle = new DefaultFileImpl(); switch (mode) { case OpenMode.MSPACK_SYS_OPEN_READ: fileHandle.FileHandle = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); break; case OpenMode.MSPACK_SYS_OPEN_WRITE: fileHandle.FileHandle = File.Open(filename, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite); break; case OpenMode.MSPACK_SYS_OPEN_UPDATE: fileHandle.FileHandle = File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); break; case OpenMode.MSPACK_SYS_OPEN_APPEND: fileHandle.FileHandle = File.Open(filename, FileMode.Append, FileAccess.ReadWrite, FileShare.ReadWrite); break; default: return null; } return fileHandle; } private static void DefaultClose(object file) { DefaultFileImpl self = file as DefaultFileImpl; if (self != null) self.FileHandle.Close(); } private static int DefaultRead(object file, byte[] buffer, int pointer, int bytes) { DefaultFileImpl self = file as DefaultFileImpl; if (self != null && buffer != null && bytes >= 0) { try { return self.FileHandle.Read(buffer, pointer, bytes); } catch { } } return -1; } private static int DefaultWrite(object file, byte[] buffer, int pointer, int bytes) { DefaultFileImpl self = file as DefaultFileImpl; if (self != null && buffer != null && bytes >= 0) { try { self.FileHandle.Write(buffer, pointer, bytes); } catch { return -1; } return bytes; } return -1; } private static bool DefaultSeek(object file, long offset, SeekMode mode) { DefaultFileImpl self = file as DefaultFileImpl; if (self != null) { switch (mode) { case SeekMode.MSPACK_SYS_SEEK_START: try { self.FileHandle.Seek(offset, SeekOrigin.Begin); return true; } catch { return false; } case SeekMode.MSPACK_SYS_SEEK_CUR: try { self.FileHandle.Seek(offset, SeekOrigin.Current); return true; } catch { return false; } case SeekMode.MSPACK_SYS_SEEK_END: try { self.FileHandle.Seek(offset, SeekOrigin.End); return true; } catch { return false; } default: return false; } } return false; } private static long DefaultTell(object file) { DefaultFileImpl self = file as DefaultFileImpl; return (self != null ? (int)self.FileHandle.Position : 0); } private static void DefaultMessage(object file, string format) { if (file != null) Console.Error.Write($"{(file as DefaultFileImpl)?.Name}: "); Console.Error.Write($"{format}\n"); } private static byte[] DefaultAlloc(SystemImpl self, int bytes) { return new byte[bytes]; } private static void DefaultFree(object buffer) { buffer = null; } private static void DefaultCopy(byte[] src, int srcPtr, byte[] dest, int destPtr, int bytes) { Array.Copy(src, srcPtr, dest, destPtr, bytes); } #endregion } }