//#define COSMOSDEBUG using System; using System.IO; using Cosmos.System.FileSystem.FAT.Listing; namespace Cosmos.System.FileSystem.FAT { /// /// FAT stream class. /// internal class FatStream : Stream { protected byte[] mReadBuffer; protected long? mReadBufferPosition; protected long mPosition; private readonly FatDirectoryEntry mDirectoryEntry; private readonly FatFileSystem mFS; //TODO: In future we might read this in as needed rather than // all at once. This structure will also consume 2% of file size in RAM // (for default cluster size of 2kb, ie 4 bytes per cluster) // so we might consider a way to flush it and only keep parts. // Example, a 100 MB file will require 2MB for this structure. That is // probably acceptable for the mid term future. private uint[] mFatTable; private long mSize; /// /// Initializes a new instance of the class. /// /// A directory entry to open stream to. /// Thrown when aEntry is null / corrupted. /// Thrown when FAT table not found or null / out of memory / invalid aData size. /// Thrown when the size of the chain is less then zero. /// Thrown when the number of clusters in the FAT entry is greater than Int32.MaxValue /// Thrown when FAT type is unknown. /// Thrown when FAT type is unknown. public FatStream(FatDirectoryEntry aEntry) { mDirectoryEntry = aEntry ?? throw new ArgumentNullException(nameof(aEntry)); mFS = aEntry.GetFileSystem(); mFatTable = aEntry.GetFatTable(); mSize = aEntry.mSize; if (mFatTable == null) { throw new Exception("The fat chain returned for the directory entry was null."); } } /// /// Check if can seek the stream. /// Returns true. /// public override bool CanSeek => true; /// /// Check if can read the stream. /// Returns true. /// public override bool CanRead => true; /// /// Check if can write the stream. /// Returns true. /// public override bool CanWrite => true; /// /// Get the length of the stream. /// public sealed override long Length { get { Global.mFileSystemDebugger.SendInternal("-- FatStream.get_Length --"); Global.mFileSystemDebugger.SendInternal("Length ="); Global.mFileSystemDebugger.SendInternal(mSize); return mSize; } } /// /// Get and set the position in the stream. /// /// (set) Thrown when value is smaller than 0L. public override long Position { get { Global.mFileSystemDebugger.SendInternal("-- FatStream.get_Position --"); Global.mFileSystemDebugger.SendInternal("Position ="); Global.mFileSystemDebugger.SendInternal(mPosition); return mPosition; } set { if (value < 0L) { throw new ArgumentOutOfRangeException(nameof(value)); } Global.mFileSystemDebugger.SendInternal("-- FatStream.set_Position --"); Global.mFileSystemDebugger.SendInternal("Position ="); Global.mFileSystemDebugger.SendInternal(mPosition); mPosition = value; } } /// /// Flush stream. /// Not implemented. /// /// Always thrown. public override void Flush() { throw new NotImplementedException(); } /// /// Seek the position in the stream. /// /// The offset. /// The position in the stream to start the seek from. /// Possible values: /// /// SeekOrigin.Begin /// SeekOrigin.Current /// SeekOrigin.End /// /// /// long value. /// Thrown when invalid seek position in passed. public override long Seek(long offset, SeekOrigin origin) { switch (origin) { case SeekOrigin.Begin: Position = offset; break; case SeekOrigin.Current: Position += offset; break; case SeekOrigin.End: Position = Length + offset; break; default: throw new NotImplementedException(); } return Position; } /// /// Set the length of the stream. /// /// Stream length. /// /// /// Thrown when trying to change root directory matadata. /// data size invalid. /// invalid directory entry type. /// FAT table not found. /// out of memory. /// invalid aData size. /// /// /// Thrown when data lenght is greater then Int32.MaxValue. /// /// /// Thrown when entrys aValue is null. /// Thrown when entrys aData is null. /// Out of memory. /// /// /// Thrown on fatal error (contact support). /// Thrown on fatal error (contact support). /// Thrown when the data in aValue is corrupted. /// /// /// Thrown when the data length is 0 or greater then Int32.MaxValue. /// Entrys matadata offset value is invalid. /// Thrown when aSize is smaller than 0. /// /// /// /// /// Thrown when aName is null or empty string. /// aData length is 0. /// FAT type is unknown. /// /// /// Thrown when FAT type is unknown. public override void SetLength(long value) { Global.mFileSystemDebugger.SendInternal("-- FatStream.SetLength --"); Global.mFileSystemDebugger.SendInternal("value ="); Global.mFileSystemDebugger.SendInternal(value); Global.mFileSystemDebugger.SendInternal("mFatTable.Length ="); Global.mFileSystemDebugger.SendInternal(mFatTable.Length); mDirectoryEntry.SetSize(value); mSize = value; mFatTable = mDirectoryEntry.GetFatTable(); } /// /// Read data from stream. /// /// A destination buffer. /// A offset in the buffer. /// Number of bytes to read. /// int value. /// Thrown if aCount or aOffset is smaller than 0 or bigger than Int32.MaxValue. /// Thrown on invalid offset length. /// Thrown when data lenght is greater then Int32.MaxValue. /// Thrown when data size invalid. /// Thrown when aBuffer is null / memory error. /// Thrown on fatal error. /// Thrown on fatal error. /// Thrown on memory error. public override int Read(byte[] aBuffer, int aOffset, int aCount) { Global.mFileSystemDebugger.SendInternal("-- FatStream.Read --"); Global.mFileSystemDebugger.SendInternal("aBuffer.Length = " + aBuffer.Length); Global.mFileSystemDebugger.SendInternal("aOffset = " + aOffset); Global.mFileSystemDebugger.SendInternal("aCount = " + aCount); if (aCount < 0) { throw new ArgumentOutOfRangeException(nameof(aCount)); } if (aOffset < 0) { throw new ArgumentOutOfRangeException(nameof(aOffset)); } if (aCount + aOffset > aBuffer?.Length) { throw new ArgumentException("Invalid offset length."); } if (mFatTable.Length == 0 || mFatTable[0] == 0) { return 0; } if (mPosition >= mDirectoryEntry.mSize) { return 0; } long xMaxReadableBytes = mDirectoryEntry.mSize - mPosition; long xCount = aCount; long xOffset = aOffset; if (xCount > xMaxReadableBytes) { xCount = xMaxReadableBytes; } long xClusterSize = mFS.BytesPerCluster; while (xCount > 0) { long xClusterIdx = mPosition / xClusterSize; long xPosInCluster = mPosition % xClusterSize; mFS.Read(mFatTable[(int)xClusterIdx], out byte[] xCluster); long xReadSize; if (xPosInCluster + xCount > xClusterSize) { xReadSize = xClusterSize - xPosInCluster; // -1 } else { xReadSize = xCount; } Global.mFileSystemDebugger.SendInternal("xClusterIdx = " + xClusterIdx); Global.mFileSystemDebugger.SendInternal("xPosInCluster = " + xPosInCluster); Global.mFileSystemDebugger.SendInternal("xReadSize = " + xReadSize); Array.Copy(xCluster, xPosInCluster, aBuffer, xOffset, xReadSize); xOffset += xReadSize; xCount -= xReadSize; mPosition += xReadSize; } return (int)xOffset; } /// /// Write to stream. /// /// A source buffer. /// A offset in the buffer. /// Number of bytes to read. /// /// /// Thrown when trying to change root directory matadata. /// data size invalid. /// invalid directory entry type. /// FAT table not found. /// out of memory. /// invalid aData size. /// /// /// Thrown when data lenght is greater then Int32.MaxValue. /// /// /// Thrown when entrys aValue is null. /// Thrown when entrys aData is null. /// Thrown when aBuffer is null. /// Out of memory. /// /// /// Thrown on fatal error (contact support). /// Thrown on fatal error (contact support). /// Thrown when the data in aValue is corrupted. /// /// /// Thrown if aCount or aOffset is smaller than 0 or bigger than Int32.MaxValue. /// Thrown when the data length is 0 or greater then Int32.MaxValue. /// Entrys matadata offset value is invalid. /// Thrown when aSize is smaller than 0. /// /// /// /// /// Thrown on invalid offset length. /// Thrown when aName is null or empty string. /// aData length is 0. /// FAT type is unknown. /// /// /// Thrown when FAT type is unknown. public override void Write(byte[] aBuffer, int aOffset, int aCount) { Global.mFileSystemDebugger.SendInternal("-- FatStream.Write --"); Global.mFileSystemDebugger.SendInternal("aBuffer.Length ="); Global.mFileSystemDebugger.SendInternal(aBuffer.Length); Global.mFileSystemDebugger.SendInternal("aOffset ="); Global.mFileSystemDebugger.SendInternal(aOffset); Global.mFileSystemDebugger.SendInternal("aCount ="); Global.mFileSystemDebugger.SendInternal(aCount); if (aCount < 0) { throw new ArgumentOutOfRangeException(nameof(aCount)); } if (aOffset < 0) { throw new ArgumentOutOfRangeException(nameof(aOffset)); } if (aOffset + aCount > aBuffer.Length) { throw new ArgumentException("Invalid offset length."); } long xCount = aCount; long xClusterSize = mFS.BytesPerCluster; long xOffset = aOffset; long xTotalLength = (mPosition + xCount); if (xTotalLength > Length) { SetLength(xTotalLength); } while (xCount > 0) { long xWriteSize; long xClusterIdx = mPosition / xClusterSize; long xPosInCluster = mPosition % xClusterSize; if (xPosInCluster + xCount > xClusterSize) { xWriteSize = xClusterSize - xPosInCluster; } else { xWriteSize = xCount; } mFS.Read(mFatTable[xClusterIdx], out byte[] xCluster); Array.Copy(aBuffer, aOffset, xCluster, (int)xPosInCluster, (int)xWriteSize); mFS.Write(mFatTable[xClusterIdx], xCluster); Global.mFileSystemDebugger.SendInternal("xClusterIdx ="); Global.mFileSystemDebugger.SendInternal(xClusterIdx); Global.mFileSystemDebugger.SendInternal("xPosInCluster ="); Global.mFileSystemDebugger.SendInternal(xPosInCluster); Global.mFileSystemDebugger.SendInternal("xWriteSize ="); Global.mFileSystemDebugger.SendInternal(xWriteSize); xOffset += xWriteSize; xCount -= xWriteSize; aOffset += (int)xWriteSize; mPosition += xWriteSize; } } } }