//#define COSMOSDEBUG using System; using System.Collections.Generic; using Cosmos.Common.Extensions; using Cosmos.HAL.BlockDevice; using Cosmos.System.FileSystem.FAT.Listing; using Cosmos.System.FileSystem.Listing; using JetBrains.Annotations; namespace Cosmos.System.FileSystem.FAT { internal class FatFileSystem : FileSystem { internal class Fat { [NotNull] private readonly FatFileSystem mFileSystem; private readonly ulong mFirstSector; /// /// Initializes a new instance of the class. /// /// The file system. /// Tje first sector of the FAT table. public Fat([NotNull] FatFileSystem aFileSystem, ulong aFirstSector) { if (aFileSystem == null) { throw new ArgumentNullException(nameof(aFileSystem)); } mFileSystem = aFileSystem; mFirstSector = aFirstSector; } /// /// Gets the fat chain. /// /// The first cluster. /// Size of a data to be stored in bytes. /// An array of cluster numbers for the fat chain. /// [NotNull] public ulong[] GetFatChain(ulong aFirstCluster, uint aDataSize = 0) { Global.mFileSystemDebugger.SendInternal("Fat.GetFatChain:"); Global.mFileSystemDebugger.SendInternal("aFirstCluster ="); Global.mFileSystemDebugger.SendInternal((uint) aFirstCluster); Global.mFileSystemDebugger.SendInternal("aDataSize ="); Global.mFileSystemDebugger.SendInternal(aDataSize); var xReturn = new ulong[0]; ulong xCurrentCluster = aFirstCluster; ulong xValue; uint xClustersRequired = aDataSize/mFileSystem.BytesPerCluster; if (aDataSize%mFileSystem.BytesPerCluster != 0) { xClustersRequired++; } GetFatEntry(xCurrentCluster, out xValue); Array.Resize(ref xReturn, xReturn.Length + 1); xReturn[xReturn.Length - 1] = xCurrentCluster; Global.mFileSystemDebugger.SendInternal("After GetFatEntry:"); Global.mFileSystemDebugger.SendInternal("xCurrentCluster ="); Global.mFileSystemDebugger.SendInternal((uint) xCurrentCluster); Global.mFileSystemDebugger.SendInternal("xValue ="); Global.mFileSystemDebugger.SendInternal((uint) xValue); while (!FatEntryIsEof(xValue)) { Global.mFileSystemDebugger.SendInternal("After FatEntryIsEof:"); xCurrentCluster = xValue; GetFatEntry(xCurrentCluster, out xValue); Array.Resize(ref xReturn, xReturn.Length + 1); if (!FatEntryIsEof(xValue)) { xReturn[xReturn.Length - 1] = xValue; } else { xReturn[xReturn.Length - 1] = xCurrentCluster; } Global.mFileSystemDebugger.SendInternal("xCurrentCluster ="); Global.mFileSystemDebugger.SendInternal((uint) xCurrentCluster); Global.mFileSystemDebugger.SendInternal("xValue ="); Global.mFileSystemDebugger.SendInternal((uint) xValue); } if (xClustersRequired > xReturn.Length) { ulong xNewClusters = (uint) xReturn.Length - xClustersRequired; Global.mFileSystemDebugger.SendInternal("Allocating new clusters."); Global.mFileSystemDebugger.SendInternal("xNewClusters ="); Global.mFileSystemDebugger.SendInternal((uint) xNewClusters); for (ulong i = 0; i < xNewClusters; i++) { xCurrentCluster = GetNextUnallocatedFatEntry(); ulong xLastFatEntry = xReturn[xReturn.Length - 1]; SetFatEntry(xLastFatEntry, xCurrentCluster); SetFatEntry(xCurrentCluster, FatEntryEofValue()); Array.Resize(ref xReturn, xReturn.Length + 1); xReturn[xReturn.Length - 1] = xCurrentCluster; } } return xReturn; } /// /// Gets the next unallocated fat entry. /// /// The index of the next unallocated FAT entry. /// Failed to find an unallocated FAT entry. public uint GetNextUnallocatedFatEntry() { Global.mFileSystemDebugger.SendInternal("Fat.GetNextUnallocatedFatEntry:"); var xSector = new byte[mFileSystem.BytesPerSector]; uint xEntryNumber = 0; for (uint i = 0; i < mFileSystem.FatSectorCount; i++) { ReadFatTableSector(i, xSector); for (uint j = 0; j < xSector.Length/4; j += 4) { uint xEntryValue = xSector.ToUInt32(j); xEntryNumber++; if (xEntryValue == 0) { Global.mFileSystemDebugger.SendInternal("xEntryNumber ="); Global.mFileSystemDebugger.SendInternal(xEntryNumber); Global.mFileSystemDebugger.SendInternal("xEntryValue ="); Global.mFileSystemDebugger.SendInternal(xEntryValue); Global.mFileSystemDebugger.SendInternal("Offset ="); Global.mFileSystemDebugger.SendInternal(xEntryNumber*4); return xEntryNumber; } } } // TODO: What should we return if no available entry is found. throw new Exception("Failed to find an unallocated FAT entry."); } /// /// Reads the fat table sector. /// /// The sector number. /// The sector data. private void ReadFatTableSector(ulong xSectorNum, [NotNull] byte[] aData) { Global.mFileSystemDebugger.SendInternal("Fat.ReadFatTableSector:"); Global.mFileSystemDebugger.SendInternal("xSectorNum ="); Global.mFileSystemDebugger.SendInternal((uint) xSectorNum); ulong xSectorToRead = mFirstSector + xSectorNum; mFileSystem.mDevice.ReadBlock(xSectorToRead, 1, aData); Global.mFileSystemDebugger.SendInternal("aData.Length ="); Global.mFileSystemDebugger.SendInternal((uint) aData.Length); if (aData == null) { throw new ArgumentNullException(nameof(aData)); } } /// /// Writes the fat table sector. /// /// The sector number. /// The data to write. private void WriteFatTableSector(ulong xSectorNum, [NotNull] byte[] aData) { Global.mFileSystemDebugger.SendInternal("Fat.WriteFatTableSector:"); Global.mFileSystemDebugger.SendInternal("xSectorNum ="); Global.mFileSystemDebugger.SendInternal((uint) xSectorNum); if (aData == null) { throw new ArgumentNullException(nameof(aData)); } Global.mFileSystemDebugger.SendInternal("aData.Length ="); Global.mFileSystemDebugger.SendInternal((uint) aData.Length); ulong xSectorToRead = mFirstSector + xSectorNum; mFileSystem.mDevice.WriteBlock(xSectorToRead, 1, aData); } /// /// Gets a fat entry. /// /// The cluster number. /// The entry value. /// Unknown file system type. private void GetFatEntry(ulong aClusterNum, out ulong aValue) { Global.mFileSystemDebugger.SendInternal("Fat.GetFatEntry:"); ulong xOffset = aClusterNum*8; ulong xSectorNumber = xOffset/mFileSystem.BytesPerSector; ulong xSectorOffset = xSectorNumber*mFileSystem.BytesPerSector + xOffset; var xSector = new byte[mFileSystem.BytesPerSector]; Global.mFileSystemDebugger.SendInternal("aClusterNum ="); Global.mFileSystemDebugger.SendInternal((uint) aClusterNum); Global.mFileSystemDebugger.SendInternal("xOffset ="); Global.mFileSystemDebugger.SendInternal((uint) xOffset); Global.mFileSystemDebugger.SendInternal("xSectorNumber ="); Global.mFileSystemDebugger.SendInternal((uint) xSectorNumber); Global.mFileSystemDebugger.SendInternal("xSectorOffset ="); Global.mFileSystemDebugger.SendInternal((uint) xSectorOffset); ReadFatTableSector(xSectorNumber, xSector); switch (mFileSystem.mFatType) { case FatTypeEnum.Fat12: // We now access the FAT entry as a WORD just as we do for FAT16, but if the cluster number is // EVEN, we only want the low 12-bits of the 16-bits we fetch. If the cluster number is ODD // we want the high 12-bits of the 16-bits we fetch. uint xResult = xSector.ToUInt16(xSectorOffset); if ((aClusterNum & 0x01) == 0) { aValue = xResult & 0x0FFF; // Even } else { aValue = xResult >> 4; // Odd } break; case FatTypeEnum.Fat16: aValue = xSector.ToUInt16(xSectorOffset); break; case FatTypeEnum.Fat32: aValue = xSector.ToUInt32(xSectorOffset) & 0x0FFFFFFF; break; default: throw new Exception("Unknown file system type."); } Global.mFileSystemDebugger.SendInternal($"Fat.GetFatEntry:"); Global.mFileSystemDebugger.SendInternal("aValue ="); Global.mFileSystemDebugger.SendInternal((uint) aValue); } /// /// Sets a fat entry. /// /// The cluster number. /// The value. /// TODO: Sector Span private void SetFatEntry(ulong aClusterNum, ulong aValue) { Global.mFileSystemDebugger.SendInternal($"Fat.SetFatEntry:"); ulong xOffset = aClusterNum*8; ulong xSectorNumber = xOffset/mFileSystem.BytesPerSector; ulong xSectorOffset = xSectorNumber*mFileSystem.BytesPerSector - xOffset; var xSector = new byte[mFileSystem.BytesPerSector]; Global.mFileSystemDebugger.SendInternal("aClusterNum ="); Global.mFileSystemDebugger.SendInternal((uint) aClusterNum); Global.mFileSystemDebugger.SendInternal("aValue ="); Global.mFileSystemDebugger.SendInternal((uint) aValue); ReadFatTableSector(xSectorNumber, xSector); switch (mFileSystem.mFatType) { case FatTypeEnum.Fat12: if (xOffset == mFileSystem.BytesPerSector - 1) { #warning TODO: Sector Span throw new Exception("TODO: Sector Span"); /* This cluster access spans a sector boundary in the FAT */ /* There are a number of strategies to handling this. The */ /* easiest is to always load FAT sectors into memory */ /* in pairs if the volume is FAT12 (if you want to load */ /* FAT sector N, you also load FAT sector N+1 immediately */ /* following it in memory unless sector N is the last FAT */ /* sector). It is assumed that this is the strategy used here */ /* which makes this if test for a sector boundary span */ /* unnecessary. */ } // We now access the FAT entry as a WORD just as we do for FAT16, but if the cluster number is // EVEN, we only want the low 12-bits of the 16-bits we fetch. If the cluster number is ODD // we want the high 12-bits of the 16-bits we fetch. xSector.SetUInt16(xSectorOffset, (ushort) aValue); break; case FatTypeEnum.Fat16: xSector.SetUInt16(xSectorOffset, (ushort) aValue); break; default: xSector.SetUInt32(xSectorOffset, (uint) aValue); break; } WriteFatTableSector(xSectorNumber, xSector); } /// /// Is the FAT entry EOF? /// /// The entry index. /// /// Unknown file system type. private bool FatEntryIsEof(ulong aIndex) { switch (mFileSystem.mFatType) { case FatTypeEnum.Fat12: return aIndex >= 0xFF8; case FatTypeEnum.Fat16: return aIndex >= 0xFFF8; case FatTypeEnum.Fat32: return aIndex >= 0xFFFFFF8; default: throw new Exception("Unknown file system type."); } } /// /// The the EOF value for a specific FAT type. /// /// The EOF value. /// Unknown file system type. private ulong FatEntryEofValue() { switch (mFileSystem.mFatType) { case FatTypeEnum.Fat12: return 0x0FFF; case FatTypeEnum.Fat16: return 0xFFFF; case FatTypeEnum.Fat32: return 0x0FFFFFFF; default: throw new Exception("Unknown file system type."); } } } public readonly uint BytesPerCluster; public readonly uint BytesPerSector; public readonly uint ClusterCount; public readonly uint DataSector; // First Data Sector public readonly uint DataSectorCount; public readonly uint FatSectorCount; private readonly FatTypeEnum mFatType; public readonly uint NumberOfFATs; public readonly uint ReservedSectorCount; public readonly uint RootCluster; // FAT32 public readonly uint RootEntryCount; public readonly uint RootSector; // FAT12/16 public readonly uint RootSectorCount; // FAT12/16, FAT32 remains 0 public readonly uint SectorsPerCluster; public readonly uint TotalSectorCount; [NotNull] private readonly Fat[] mFats; /// /// Initializes a new instance of the class. /// /// The partition. /// The root path. /// FAT signature not found. public FatFileSystem([NotNull] Partition aDevice, [NotNull] string aRootPath) : base(aDevice, aRootPath) { if (aDevice == null) { throw new ArgumentNullException(nameof(aDevice)); } if (string.IsNullOrEmpty(aRootPath)) { throw new ArgumentException("Argument is null or empty", nameof(aRootPath)); } var xBPB = mDevice.NewBlockArray(1); mDevice.ReadBlock(0UL, 1U, xBPB); ushort xSig = xBPB.ToUInt16(510); if (xSig != 0xAA55) { throw new Exception("FAT signature not found."); } BytesPerSector = xBPB.ToUInt16(11); SectorsPerCluster = xBPB[13]; BytesPerCluster = BytesPerSector*SectorsPerCluster; ReservedSectorCount = xBPB.ToUInt16(14); NumberOfFATs = xBPB[16]; RootEntryCount = xBPB.ToUInt16(17); TotalSectorCount = xBPB.ToUInt16(19); if (TotalSectorCount == 0) { TotalSectorCount = xBPB.ToUInt32(32); } // FATSz FatSectorCount = xBPB.ToUInt16(22); if (FatSectorCount == 0) { FatSectorCount = xBPB.ToUInt32(36); } DataSectorCount = TotalSectorCount - (ReservedSectorCount + NumberOfFATs*FatSectorCount + ReservedSectorCount); // Computation rounds down. ClusterCount = DataSectorCount/SectorsPerCluster; // Determine the FAT type. Do not use another method - this IS the official and // proper way to determine FAT type. // Comparisons are purposefully < and not <= // FAT16 starts at 4085, FAT32 starts at 65525 if (ClusterCount < 4085) { mFatType = FatTypeEnum.Fat12; } else if (ClusterCount < 65525) { mFatType = FatTypeEnum.Fat16; } else { mFatType = FatTypeEnum.Fat32; } if (mFatType == FatTypeEnum.Fat32) { RootCluster = xBPB.ToUInt32(44); } else { RootSector = ReservedSectorCount + NumberOfFATs*FatSectorCount; RootSectorCount = (RootEntryCount*32 + (BytesPerSector - 1))/BytesPerSector; } DataSector = ReservedSectorCount + NumberOfFATs*FatSectorCount + RootSectorCount; mFats = new Fat[NumberOfFATs]; for (ulong i = 0; i < NumberOfFATs; i++) { mFats[i] = new Fat(this, (ReservedSectorCount + i*FatSectorCount)); } } [NotNull] internal Fat GetFat(int aTableNumber) { if (mFats.Length > aTableNumber) { return mFats[aTableNumber]; } throw new Exception("The fat table number doesn't exist."); } [NotNull] internal byte[] NewClusterArray() { return new byte[BytesPerCluster]; } private void ReadInternal(ulong aFirstCluster, [NotNull] out byte[] aData) { Global.mFileSystemDebugger.SendInternal("FatFileSystem.ReadInternal:"); Global.mFileSystemDebugger.SendInternal("aFirstCluster ="); Global.mFileSystemDebugger.SendInternal((uint) aFirstCluster); if (mFatType == FatTypeEnum.Fat32) { aData = NewClusterArray(); ulong xSector = DataSector + (aFirstCluster - 2)*SectorsPerCluster; mDevice.ReadBlock(xSector, SectorsPerCluster, aData); } else { aData = mDevice.NewBlockArray(1); mDevice.ReadBlock(aFirstCluster, RootSectorCount, aData); } Global.mFileSystemDebugger.SendInternal("aData.Length ="); Global.mFileSystemDebugger.SendInternal((uint) aData.Length); } private void WriteInternal(ulong aFirstCluster, [NotNull] byte[] aData) { Global.mFileSystemDebugger.SendInternal("FatFileSystem.WriteInternal:"); Global.mFileSystemDebugger.SendInternal("aFirstCluster ="); Global.mFileSystemDebugger.SendInternal((uint) aFirstCluster); if (aData == null) { throw new ArgumentNullException(nameof(aData)); } Global.mFileSystemDebugger.SendInternal("aData.Length ="); Global.mFileSystemDebugger.SendInternal((uint) aData.Length); if (mFatType == FatTypeEnum.Fat32) { ulong xSector = DataSector + (aFirstCluster - 2)*SectorsPerCluster; mDevice.WriteBlock(xSector, SectorsPerCluster, aData); } else { mDevice.WriteBlock(aFirstCluster, RootSectorCount, aData); } } internal void Read(ulong aFirstCluster, [NotNull] out byte[] aData, ulong aSize = 0, ulong aOffset = 0) { if (aSize == 0) { aSize = BytesPerCluster; } if (aSize > BytesPerCluster - aOffset) { throw new NotImplementedException("TODO: Add cluster spanning read."); } aData = new byte[aSize]; byte[] xTempData; ReadInternal(aFirstCluster, out xTempData); Array.Copy(xTempData, (long) aOffset, aData, 0, (long) aSize); } internal void Write(ulong aFirstCluster, [NotNull] byte[] aData, ulong aSize = 0, ulong aOffset = 0) { if (aSize == 0) { aSize = BytesPerCluster; } if (aSize > BytesPerCluster - aOffset) { throw new NotImplementedException("TODO: Add cluster spanning write."); } byte[] xTempData; ReadInternal(aFirstCluster, out xTempData); Array.Copy(aData, (long) aOffset, xTempData, 0, (long) aSize); WriteInternal(aFirstCluster, aData); } public static bool IsDeviceFat([NotNull] Partition aDevice) { if (aDevice == null) { throw new ArgumentNullException(nameof(aDevice)); } var xBPB = aDevice.NewBlockArray(1); aDevice.ReadBlock(0UL, 1U, xBPB); ushort xSig = xBPB.ToUInt16(510); if (xSig != 0xAA55) { return false; } return true; } public override void DisplayFileSystemInfo() { global::System.Console.WriteLine("-------File System--------"); global::System.Console.WriteLine("Bytes per Cluster: " + BytesPerCluster); global::System.Console.WriteLine("Bytes per Sector: " + BytesPerSector); global::System.Console.WriteLine("Cluster Count: " + ClusterCount); global::System.Console.WriteLine("Data Sector: " + DataSector); global::System.Console.WriteLine("Data Sector Count: " + DataSectorCount); global::System.Console.WriteLine("FAT Sector Count: " + FatSectorCount); global::System.Console.WriteLine("FAT Type: " + mFatType); global::System.Console.WriteLine("Number of FATS: " + NumberOfFATs); global::System.Console.WriteLine("Reserved Sector Count: " + ReservedSectorCount); global::System.Console.WriteLine("Root Cluster: " + RootCluster); global::System.Console.WriteLine("Root Entry Count: " + RootEntryCount); global::System.Console.WriteLine("Root Sector: " + RootSector); global::System.Console.WriteLine("Root Sector Count: " + RootSectorCount); global::System.Console.WriteLine("Sectors per Cluster: " + SectorsPerCluster); global::System.Console.WriteLine("Total Sector Count: " + TotalSectorCount); } public override List GetDirectoryListing(DirectoryEntry baseDirectory) { Global.mFileSystemDebugger.SendInternal("FatFileSystem.GetDirectoryListing:"); var result = new List(); List fatListing; { var xEntry = (FatDirectoryEntry) baseDirectory; fatListing = xEntry.ReadDirectoryContents(); } for (int i = 0; i < fatListing.Count; i++) { result.Add(fatListing[i]); } return result; } public override DirectoryEntry GetRootDirectory() { Global.mFileSystemDebugger.SendInternal("FatFileSystem.GetRootDirectory:"); var xRootEntry = new FatDirectoryEntry(this, null, mRootPath, mRootPath, RootCluster); return xRootEntry; } public override DirectoryEntry CreateDirectory(DirectoryEntry aParentDirectory, string aNewDirectory) { Global.mFileSystemDebugger.SendInternal("FatFileSystem.CreateDirectory:"); if (aParentDirectory == null) { throw new ArgumentNullException(nameof(aParentDirectory)); } Global.mFileSystemDebugger.SendInternal("aParentDirectory.Name ="); Global.mFileSystemDebugger.SendInternal(aParentDirectory?.mName); if (string.IsNullOrEmpty(aNewDirectory)) { throw new ArgumentNullException(nameof(aNewDirectory)); } Global.mFileSystemDebugger.SendInternal("aNewDirectory ="); Global.mFileSystemDebugger.SendInternal(aNewDirectory); var xParentDirectory = (FatDirectoryEntry) aParentDirectory; var xDirectoryEntryToAdd = xParentDirectory.AddDirectoryEntry(aNewDirectory, DirectoryEntryTypeEnum.Directory); return xDirectoryEntryToAdd; } public override DirectoryEntry CreateFile(DirectoryEntry aParentDirectory, string aNewFile) { Global.mFileSystemDebugger.SendInternal("FatFileSystem.CreateFile:"); if (aParentDirectory == null) { throw new ArgumentNullException(nameof(aParentDirectory)); } Global.mFileSystemDebugger.SendInternal("aParentDirectory.Name ="); Global.mFileSystemDebugger.SendInternal(aParentDirectory?.mName); if (string.IsNullOrEmpty(aNewFile)) { throw new ArgumentNullException(nameof(aNewFile)); } Global.mFileSystemDebugger.SendInternal("aNewFile ="); Global.mFileSystemDebugger.SendInternal(aNewFile); var xParentDirectory = (FatDirectoryEntry) aParentDirectory; var xDirectoryEntryToAdd = xParentDirectory.AddDirectoryEntry(aNewFile, DirectoryEntryTypeEnum.File); return xDirectoryEntryToAdd; } private enum FatTypeEnum { Unknown, Fat12, Fat16, Fat32 } } }