diff --git a/Tests/Cosmos.Kernel.Tests.Fat/System.IO/DriveInfoTest.cs b/Tests/Cosmos.Kernel.Tests.Fat/System.IO/DriveInfoTest.cs new file mode 100644 index 000000000..aedce347f --- /dev/null +++ b/Tests/Cosmos.Kernel.Tests.Fat/System.IO/DriveInfoTest.cs @@ -0,0 +1,136 @@ +using System.IO; +using Cosmos.TestRunner; +using Cosmos.Debug.Kernel; +using System; + +namespace Cosmos.Kernel.Tests.Fat.System.IO +{ + public class DriveInfoTest + { + /// + /// Tests System.IO.DriveInfo plugs. + /// + public static void Execute(Debugger mDebugger) + { + string driveName = @"0:\"; + var MyDrive = new DriveInfo(driveName); + + mDebugger.Send("START TEST: Get Name"); + + Assert.IsTrue(MyDrive.Name == driveName, "DriveInfo.Name failed drive has wrong name"); + + mDebugger.Send("END TEST"); + + mDebugger.Send("START TEST: Get TotalSize"); + long MyDriveSize = MyDrive.TotalSize; + + mDebugger.Send($"Size is {MyDriveSize}"); + + Assert.IsTrue(MyDriveSize > 0, "DriveInfo.TotalSize failed: invalid size"); + mDebugger.Send("END TEST"); + + mDebugger.Send("START TEST: Get AvailableFreeSpace"); + long MyDriveAvailableFreeSpace = MyDrive.AvailableFreeSpace; + + mDebugger.Send($"AvailableFreeSpace {MyDriveAvailableFreeSpace}"); + + Assert.IsTrue(MyDriveAvailableFreeSpace >= 0, "DriveInfo.AvailableFreeSpace failed: invalid size"); + + Assert.IsFalse(MyDriveAvailableFreeSpace > MyDrive.TotalSize, "DriveInfo.AvailableFreeSpace failed: more than TotalSize"); + + mDebugger.Send("END TEST"); + + mDebugger.Send("START TEST: Get TotalFreeSpace"); + long MyDriveTotalFreeSpace = MyDrive.TotalFreeSpace; + + mDebugger.Send($"TotalFreeSpace {MyDriveTotalFreeSpace}"); + + Assert.IsTrue(MyDriveTotalFreeSpace >= 0, "DriveInfo.TotalFreeSpace failed: invalid size"); + + Assert.IsFalse(MyDriveTotalFreeSpace > MyDrive.TotalSize, "DriveInfo.TotalFreeSpace failed: more than TotalSize"); + + /* + * If disk quotas are enabled AvailableFreeSpace and TotalFreeSpace could be different numbers but TotalFreeSpace + * should be always >= of AvailableFreeSpace + */ + Assert.IsTrue(MyDriveTotalFreeSpace >= MyDriveAvailableFreeSpace, "DriveInfo.MyDriveTotalFreeSpace failed: less than AvailableFreeSpace"); + + mDebugger.Send("END TEST"); + + mDebugger.Send("START TEST: Get RootDirectory"); + + var xDi = MyDrive.RootDirectory; + + Assert.IsTrue(xDi.Name == MyDrive.Name, "RootDirectory failed"); + mDebugger.Send("END TEST"); + + mDebugger.Send("START TEST: Get DriveFormat"); + + Assert.IsTrue(MyDrive.DriveFormat == "FAT32", "DriveFormat failed"); + mDebugger.Send("END TEST"); + + mDebugger.Send("START TEST: Get VolumeLabel"); + + string OriginalVolumeLabel = MyDrive.VolumeLabel; + + mDebugger.Send($"Volume Label of {MyDrive.Name} is {MyDrive.VolumeLabel}"); + + Assert.IsTrue(OriginalVolumeLabel == "COSMOS", "VolumeLabel Get failed not the expected value"); + + mDebugger.Send("END TEST"); + + mDebugger.Send("START TEST: Set VolumeLabel to "); + // Now try to change it... + String NewVolumeLabel = "TEST"; + mDebugger.Send($"Changing Volume Label to {NewVolumeLabel}..."); + MyDrive.VolumeLabel = NewVolumeLabel; + + string SetVolumeLabel = MyDrive.VolumeLabel; + + mDebugger.Send($"Volume Label of {MyDrive.Name} is {SetVolumeLabel}"); + + Assert.IsTrue(SetVolumeLabel == NewVolumeLabel, "VolumeLabel Set failed: not the expected value"); + + mDebugger.Send("END TEST"); + + mDebugger.Send("START TEST: Set VolumeLabel (restoring original label)"); + // Now try to change it... + mDebugger.Send($"Changing Volume Label to {OriginalVolumeLabel}..."); + MyDrive.VolumeLabel = OriginalVolumeLabel; + + SetVolumeLabel = MyDrive.VolumeLabel; + + mDebugger.Send($"Volume Label of {MyDrive.Name} is {SetVolumeLabel}"); + + Assert.IsTrue(SetVolumeLabel == OriginalVolumeLabel, "VolumeLabel Set failed: not the expected value"); + + mDebugger.Send("END TEST"); + + mDebugger.Send("START TEST: Testing isReady status of the Drive"); + + Assert.IsTrue(MyDrive.IsReady, "IsReady failed drive not ready"); + + mDebugger.Send("END TEST"); + + mDebugger.Send("START TEST: Testing DriveType"); + + Assert.IsTrue(MyDrive.DriveType == DriveType.Fixed, "DriveType failed drive not of Fixed type"); + + mDebugger.Send("END TEST"); + + mDebugger.Send("START TEST: Getting all drives info"); + + DriveInfo[] xDrives = DriveInfo.GetDrives(); + + Assert.IsFalse(xDrives == null, "GetDrives failed: null array returned"); + + // I suppose that at least a drive is recognized by Cosmos + Assert.IsTrue(xDrives.Length > 0, "GetDrives failed: no drive recognized"); + + /* Check that only the Name property of the first one is the same of driveName */ + Assert.IsTrue(xDrives[0].Name == driveName, "GetDrives failed: first drive is not the same of MyDrive (0:)"); + + mDebugger.Send("END TEST"); + } + } +} diff --git a/Tests/Cosmos.Kernel.Tests.Fat2/Kernel.cs b/Tests/Cosmos.Kernel.Tests.Fat2/Kernel.cs index 5a4a2bfac..1e94db18f 100644 --- a/Tests/Cosmos.Kernel.Tests.Fat2/Kernel.cs +++ b/Tests/Cosmos.Kernel.Tests.Fat2/Kernel.cs @@ -42,6 +42,7 @@ namespace Cosmos.Kernel.Tests.Fat2 mDebugger.Send("Run"); FileTest.Execute(mDebugger); + DriveInfoTest.Execute(mDebugger); TestController.Completed(); } diff --git a/source/Cosmos.System2/FileSystem/CosmosVFS.cs b/source/Cosmos.System2/FileSystem/CosmosVFS.cs index 494035375..76298a200 100644 --- a/source/Cosmos.System2/FileSystem/CosmosVFS.cs +++ b/source/Cosmos.System2/FileSystem/CosmosVFS.cs @@ -19,12 +19,9 @@ namespace Cosmos.System.FileSystem public class CosmosVFS : VFSBase { private List mPartitions; - - private List mFileSystems; - - private FileSystem mCurrentFileSystem; + private List mRegisteredFileSystems; /// /// Initializes the virtual file system. @@ -33,6 +30,9 @@ namespace Cosmos.System.FileSystem { mPartitions = new List(); mFileSystems = new List(); + mRegisteredFileSystems = new List(); + + RegisterFileSystem(new FatFileSystemFactory()); InitializePartitions(); if (mPartitions.Count > 0) @@ -41,6 +41,12 @@ namespace Cosmos.System.FileSystem } } + public override void RegisterFileSystem(FileSystemFactory aFileSystemFactory) + { + Global.mFileSystemDebugger.SendInternal($"Registering filesystem {aFileSystemFactory.Name}"); + mRegisteredFileSystems.Add(aFileSystemFactory); + } + /// /// Creates a new file. /// @@ -400,14 +406,15 @@ namespace Cosmos.System.FileSystem { string xRootPath = string.Concat(i, VolumeSeparatorChar, DirectorySeparatorChar); var xSize = (long)(mPartitions[i].BlockCount * mPartitions[i].BlockSize / 1024 / 1024); - switch (FileSystem.GetFileSystemType(mPartitions[i])) + + // We 'probe' the partition with all the FileSystem registered until we find a Filesystem that can read / write to it + foreach (var fs in mRegisteredFileSystems) { - case FileSystemType.FAT: + if (fs.IsType(mPartitions[i])) + { + Global.mFileSystemDebugger.SendInternal($"Partion {i} has a {fs.Name} filesystem"); mFileSystems.Add(new FatFileSystem(mPartitions[i], xRootPath, xSize)); - break; - default: - global::System.Console.WriteLine("Unknown filesystem type!"); - return; + } } if ((mFileSystems.Count > 0) && (mFileSystems[mFileSystems.Count - 1].mRootPath == xRootPath)) @@ -547,5 +554,70 @@ namespace Cosmos.System.FileSystem return aFS.GetRootDirectory(); } + + /// + /// Verifies if driveId is a valid id for a drive. + /// + /// The id of the drive. + /// true if the drive id is valid, false otherwise. + public override bool IsValidDriveId(string driveId) + { + Global.mFileSystemDebugger.SendInternal($"driveId is {driveId} after normalization"); + + /* We need to remove ':\' to get only the numeric value */ + driveId = driveId.Remove(driveId.Length - 2); + Global.mFileSystemDebugger.SendInternal($"driveId is now {driveId}"); + + /* + * Cosmos Drive name is really similar to DOS / Windows but a number instead of a letter is used, it is not limited + * to 1 character but any number is valid + */ + + bool isOK = Int32.TryParse(driveId, out int val); + Global.mFileSystemDebugger.SendInternal($"isOK is {isOK}"); + + return isOK; + } + + public override long GetTotalSize(string aDriveId) + { + var xFs = GetFileSystemFromPath(aDriveId); + + return xFs.mSize; + } + + public override long GetAvailableFreeSpace(string aDriveId) + { + var xFs = GetFileSystemFromPath(aDriveId); + + return xFs.mAvailableFreeSpace; + } + + public override long GetTotalFreeSpace(string aDriveId) + { + var xFs = GetFileSystemFromPath(aDriveId); + + return xFs.mTotalFreeSpace; + } + + public override string GetFileSystemType(string aDriveId) + { + var xFs = GetFileSystemFromPath(aDriveId); + + return xFs.mType; + } + + public override string GetFileSystemLabel(string aDriveId) + { + var xFs = GetFileSystemFromPath(aDriveId); + + return xFs.mLabel; + } + + public override void SetFileSystemLabel(string aDriveId, string aLabel) + { + var xFs = GetFileSystemFromPath(aDriveId); + xFs.mLabel = aLabel; + } } } diff --git a/source/Cosmos.System2/FileSystem/FAT/FatFileSystem.cs b/source/Cosmos.System2/FileSystem/FAT/FatFileSystem.cs index 9683fdd5e..fc2e12f79 100644 --- a/source/Cosmos.System2/FileSystem/FAT/FatFileSystem.cs +++ b/source/Cosmos.System2/FileSystem/FAT/FatFileSystem.cs @@ -353,6 +353,24 @@ namespace Cosmos.System.FileSystem.FAT private readonly Fat[] mFats; + public override string mType + { + get + { + switch (mFatType) + { + case FatTypeEnum.Fat12: + return "FAT12"; + case FatTypeEnum.Fat16: + return "FAT16"; + case FatTypeEnum.Fat32: + return "FAT32"; + default: + throw new Exception("Unknown FAT file system type."); + } + } + } + /// /// Initializes a new instance of the class. /// @@ -587,7 +605,7 @@ namespace Cosmos.System.FileSystem.FAT } var result = new List(); - var xEntry = (FatDirectoryEntry)baseDirectory; + var xEntry = (FatDiretoryEntry)baseDirectory; var fatListing = xEntry.ReadDirectoryContents(); for (int i = 0; i < fatListing.Count; i++) @@ -601,7 +619,7 @@ namespace Cosmos.System.FileSystem.FAT { Global.mFileSystemDebugger.SendInternal("-- FatFileSystem.GetRootDirectory --"); - var xRootEntry = new FatDirectoryEntry(this, null, mRootPath, mSize, mRootPath, RootCluster); + var xRootEntry = new FatDiretoryEntry(this, null, mRootPath, mSize, mRootPath, RootCluster); return xRootEntry; } @@ -623,7 +641,7 @@ namespace Cosmos.System.FileSystem.FAT throw new ArgumentNullException(nameof(aNewDirectory)); } - var xParentDirectory = (FatDirectoryEntry)aParentDirectory; + var xParentDirectory = (FatDiretoryEntry)aParentDirectory; var xDirectoryEntryToAdd = xParentDirectory.AddDirectoryEntry(aNewDirectory, DirectoryEntryTypeEnum.Directory); return xDirectoryEntryToAdd; } @@ -646,7 +664,8 @@ namespace Cosmos.System.FileSystem.FAT throw new ArgumentNullException(nameof(aNewFile)); } - var xParentDirectory = (FatDirectoryEntry)aParentDirectory; + var xParentDirectory = (FatDiretoryEntry)aParentDirectory; + var xDirectoryEntryToAdd = xParentDirectory.AddDirectoryEntry(aNewFile, DirectoryEntryTypeEnum.File); return xDirectoryEntryToAdd; } @@ -658,7 +677,7 @@ namespace Cosmos.System.FileSystem.FAT throw new ArgumentNullException(nameof(aDirectoryEntry)); } - var xDirectoryEntry = (FatDirectoryEntry)aDirectoryEntry; + var xDirectoryEntry = (FatDiretoryEntry)aDirectoryEntry; xDirectoryEntry.DeleteDirectoryEntry(); } @@ -670,7 +689,7 @@ namespace Cosmos.System.FileSystem.FAT throw new ArgumentNullException(nameof(aDirectoryEntry)); } - var xDirectoryEntry = (FatDirectoryEntry)aDirectoryEntry; + var xDirectoryEntry = (FatDiretoryEntry)aDirectoryEntry; var entries = xDirectoryEntry.GetFatTable(); @@ -682,6 +701,64 @@ namespace Cosmos.System.FileSystem.FAT xDirectoryEntry.DeleteDirectoryEntry(); } + public override string mLabel + { + /* + * In the FAT filesystem the name field of RootDirectory is - in reality - the Volume Label + */ + get + { + Global.mFileSystemDebugger.SendInternal("-- FatFileSystem.mLabel --"); + var RootDirectory = (FatDiretoryEntry) GetRootDirectory(); + + var VolumeId = RootDirectory.FindVolumeId(); + if (VolumeId == null) + { + Global.mFileSystemDebugger.SendInternal("No VolumeID, returning drive name"); + return RootDirectory.mName; + } + + Global.mFileSystemDebugger.SendInternal($"Volume label is {VolumeId.mName}"); + return VolumeId.mName; + } + set + { + Global.mFileSystemDebugger.SendInternal($"Setting Volume label to {value}"); + + var RootDirectory = (FatDiretoryEntry) GetRootDirectory(); + + var VolumeId = RootDirectory.FindVolumeId(); + if (VolumeId != null) + { + VolumeId.SetName(value); + return; + } + + Global.mFileSystemDebugger.SendInternal("No VolumeID found, let's create it!"); + + VolumeId = RootDirectory.CreateVolumeId(value); + } + } + + public override long mAvailableFreeSpace + { + get + { + var RootDirectory = (FatDiretoryEntry)GetRootDirectory(); + // We do not support "user quotas" for now so this is effectively the same then mTotalFreeSpace + return mSize - RootDirectory.GetUsedSpace(); + } + } + + public override long mTotalFreeSpace + { + get + { + var RootDirectory = (FatDiretoryEntry)GetRootDirectory(); + return mSize - RootDirectory.GetUsedSpace(); + } + } + private enum FatTypeEnum { Unknown, diff --git a/source/Cosmos.System2/FileSystem/FAT/FatStream.cs b/source/Cosmos.System2/FileSystem/FAT/FatStream.cs index 3579348eb..acef2b9b3 100644 --- a/source/Cosmos.System2/FileSystem/FAT/FatStream.cs +++ b/source/Cosmos.System2/FileSystem/FAT/FatStream.cs @@ -15,7 +15,7 @@ namespace Cosmos.System.FileSystem.FAT protected long mPosition; - private readonly FatDirectoryEntry mDirectoryEntry; + private readonly FatDiretoryEntry mDirectoryEntry; private readonly FatFileSystem mFS; @@ -29,7 +29,7 @@ namespace Cosmos.System.FileSystem.FAT private long mSize; - public FatStream(FatDirectoryEntry aEntry) + public FatStream(FatDiretoryEntry aEntry) { if (aEntry == null) { diff --git a/source/Cosmos.System2/FileSystem/FAT/Listing/FatDiretoryEntry.cs b/source/Cosmos.System2/FileSystem/FAT/Listing/FatDiretoryEntry.cs index 0df5601e6..5887811b6 100644 --- a/source/Cosmos.System2/FileSystem/FAT/Listing/FatDiretoryEntry.cs +++ b/source/Cosmos.System2/FileSystem/FAT/Listing/FatDiretoryEntry.cs @@ -10,7 +10,7 @@ using Cosmos.System.FileSystem.Listing; namespace Cosmos.System.FileSystem.FAT.Listing { - internal class FatDirectoryEntry : DirectoryEntry + internal class FatDiretoryEntry : DirectoryEntry { private readonly uint mEntryHeaderDataOffset; @@ -18,9 +18,9 @@ namespace Cosmos.System.FileSystem.FAT.Listing // Size is UInt32 because FAT doesn't support bigger. // Don't change to UInt64 - public FatDirectoryEntry( + public FatDiretoryEntry( FatFileSystem aFileSystem, - FatDirectoryEntry aParent, + FatDiretoryEntry aParent, string aFullPath, string aName, long aSize, @@ -31,6 +31,7 @@ namespace Cosmos.System.FileSystem.FAT.Listing { if (aFirstCluster < aFileSystem.RootCluster) { + Global.mFileSystemDebugger.SendInternal($"aFirstCluster {aFirstCluster} < aFileSystem.RootCluster {aFileSystem.RootCluster}"); throw new ArgumentOutOfRangeException(nameof(aFirstCluster)); } @@ -38,9 +39,9 @@ namespace Cosmos.System.FileSystem.FAT.Listing mEntryHeaderDataOffset = aEntryHeaderDataOffset; } - public FatDirectoryEntry( + public FatDiretoryEntry( FatFileSystem aFileSystem, - FatDirectoryEntry aParent, + FatDiretoryEntry aParent, string aFullPath, long aSize, string aName, @@ -143,7 +144,7 @@ namespace Cosmos.System.FileSystem.FAT.Listing GetFatTable(); } - public FatDirectoryEntry AddDirectoryEntry(string aName, DirectoryEntryTypeEnum aEntryType) + public FatDiretoryEntry AddDirectoryEntry(string aName, DirectoryEntryTypeEnum aEntryType) { Global.mFileSystemDebugger.SendInternal("-- FatDirectoryEntry.AddDirectoryEntry --"); Global.mFileSystemDebugger.SendInternal("aName ="); @@ -203,7 +204,7 @@ namespace Cosmos.System.FileSystem.FAT.Listing } int n = 1; - List xDirectoryEntries = ReadDirectoryContents(true); + List xDirectoryEntries = ReadDirectoryContents(true); string[] xShortFilenames = new string[xDirectoryEntries.Count]; for (int i = 0; i < xDirectoryEntries.Count; i++) @@ -279,7 +280,7 @@ namespace Cosmos.System.FileSystem.FAT.Listing Global.mFileSystemDebugger.SendInternal("xEntryHeaderDataOffset ="); Global.mFileSystemDebugger.SendInternal(xEntryHeaderDataOffset); - var xNewEntry = new FatDirectoryEntry((FatFileSystem)mFileSystem, this, xFullPath, aName, 0, xFirstCluster, xEntryHeaderDataOffset, aEntryType); + var xNewEntry = new FatDiretoryEntry((FatFileSystem)mFileSystem, this, xFullPath, aName, 0, xFirstCluster, xEntryHeaderDataOffset, aEntryType); xNewEntry.AllocateDirectoryEntry(xShortName); @@ -289,6 +290,8 @@ namespace Cosmos.System.FileSystem.FAT.Listing throw new ArgumentOutOfRangeException(nameof(aEntryType), "Unknown directory entry type."); } + private bool IsRootDirectory() => (mParent == null) ? true : false; + public void DeleteDirectoryEntry() { if (mEntryType == DirectoryEntryTypeEnum.Unknown) @@ -296,25 +299,23 @@ namespace Cosmos.System.FileSystem.FAT.Listing throw new NotImplementedException(); } - if (mParent != null) + if (IsRootDirectory()) { - var xData = ((FatDirectoryEntry)mParent).GetDirectoryEntryData(); - - var xEntryOffset = mEntryHeaderDataOffset - 32; - - while (xData[xEntryOffset + 11] == FatDirectoryEntryAttributeConsts.LongName) - { - xData[xEntryOffset] = FatDirectoryEntryAttributeConsts.UnusedOrDeletedEntry; - xEntryOffset -= 32; - } - - ((FatDirectoryEntry)mParent).SetDirectoryEntryData(xData); + throw new Exception("Root directory can not be deleted"); } - else + + var xData = ((FatDiretoryEntry)mParent).GetDirectoryEntryData(); + + var xEntryOffset = mEntryHeaderDataOffset - 32; + + while (xData[xEntryOffset + 11] == FatDirectoryEntryAttributeConsts.LongName) { - throw new Exception("Parent directory is null"); + xData[xEntryOffset] = FatDirectoryEntryAttributeConsts.UnusedOrDeletedEntry; + xEntryOffset -= 32; } + ((FatDiretoryEntry)mParent).SetDirectoryEntryData(xData); + SetDirectoryEntryMetadataValue(FatDirectoryEntryMetadata.FirstByte, FatDirectoryEntryAttributeConsts.UnusedOrDeletedEntry); // GetFatTable calls GetFatChain, which "refreshes" the FAT table and clusters @@ -322,16 +323,16 @@ namespace Cosmos.System.FileSystem.FAT.Listing } /// - /// Retrieves a of objects that represent the Directory Entries inside this Directory + /// Retrieves a of objects that represent the Directory Entries inside this Directory /// /// Returns a of the Directory Entries inside this Directory - public List ReadDirectoryContents(bool aReturnShortFilenames = false) + public List ReadDirectoryContents(bool aReturnShortFilenames = false) { Global.mFileSystemDebugger.SendInternal("-- FatDirectoryEntry.ReadDirectoryContents --"); var xData = GetDirectoryEntryData(); - var xResult = new List(); - FatDirectoryEntry xParent = this; + var xResult = new List(); + FatDiretoryEntry xParent = this; //TODO: Change xLongName to StringBuilder string xLongName = ""; @@ -474,7 +475,7 @@ namespace Cosmos.System.FileSystem.FAT.Listing } string xFullPath = Path.Combine(mFullPath, xName); - var xEntry = new FatDirectoryEntry(((FatFileSystem)mFileSystem), xParent, xFullPath, xName, xSize, xFirstCluster, i, DirectoryEntryTypeEnum.File); + var xEntry = new FatDiretoryEntry(((FatFileSystem)mFileSystem), xParent, xFullPath, xName, xSize, xFirstCluster, i, DirectoryEntryTypeEnum.File); xResult.Add(xEntry); Global.mFileSystemDebugger.SendInternal(xEntry.mName + " - " + xEntry.mSize + " bytes"); } @@ -482,7 +483,7 @@ namespace Cosmos.System.FileSystem.FAT.Listing { string xFullPath = Path.Combine(mFullPath, xName); uint xSize = xData.ToUInt32(i + 28); - var xEntry = new FatDirectoryEntry(((FatFileSystem)mFileSystem), xParent, xFullPath, xName, xSize, xFirstCluster, i, DirectoryEntryTypeEnum.Directory); + var xEntry = new FatDiretoryEntry(((FatFileSystem)mFileSystem), xParent, xFullPath, xName, xSize, xFirstCluster, i, DirectoryEntryTypeEnum.Directory); Global.mFileSystemDebugger.SendInternal(xEntry.mName + " " + xEntry.mSize + " bytes : Attrib = " + xAttrib + ", Status = " + xStatus); xResult.Add(xEntry); } @@ -502,6 +503,62 @@ namespace Cosmos.System.FileSystem.FAT.Listing return xResult; } + public FatDiretoryEntry FindVolumeId() + { + if (!IsRootDirectory()) + { + throw new Exception("VolumeId can be found only in Root Directory"); + } + + Global.mFileSystemDebugger.SendInternal("-- FatDirectoryEntry.FindVolumeId --"); + + var xData = GetDirectoryEntryData(); + FatDiretoryEntry xParent = this; + + FatDiretoryEntry xResult = null; + for (uint i = 0; i < xData.Length; i = i + 32) + { + byte xAttrib = xData[i + 11]; + + //if ((xAttrib & FatDirectoryEntryAttributeConsts.VolumeID) != FatDirectoryEntryAttributeConsts.VolumeID) + if (xAttrib != FatDirectoryEntryAttributeConsts.VolumeID) + continue; + + Global.mFileSystemDebugger.SendInternal("VolumeID Found"); + /* The Label in FAT could be only a shortName (limited to 11 characters) so it is more easy */ + string xName = xData.GetAsciiString(i, 11).TrimEnd(); + string xFullPath = Path.Combine(mFullPath, xName); + /* Probably can be OK to hardcode 0 here */ + uint xSize = xData.ToUInt32(i + 28); + //uint xFirstCluster = (uint)(xData.ToUInt16(i + 20) << 16 | xData.ToUInt16(i + 26)); + uint xFirstCluster = xParent.mFirstClusterNum; + + Global.mFileSystemDebugger.SendInternal($"VolumeID Found xName {xName} xFullPath {xFullPath} xSize {xSize} xFirstCluster {xFirstCluster}"); + + xResult = new FatDiretoryEntry(((FatFileSystem)mFileSystem), xParent, xFullPath, xName, xSize, xFirstCluster, i, DirectoryEntryTypeEnum.File); + break; + } + + if (xResult == null) + Global.mFileSystemDebugger.SendInternal($"VolumeID not found, returning null"); + + return xResult; + } + + public FatDiretoryEntry CreateVolumeId(string name) + { + if (!IsRootDirectory()) + { + throw new Exception("VolumeId can be created only in Root Directory"); + } + + // VolumeId is really a special type of File with attribute 'VolumeID' set + var VolumeId = AddDirectoryEntry(name, DirectoryEntryTypeEnum.File); + VolumeId.SetDirectoryEntryMetadataValue(FatDirectoryEntryMetadata.Attributes, FatDirectoryEntryAttributeConsts.VolumeID); + + return VolumeId; + } + /// /// Tries to find an empty space for a directory entry and returns the offset to that space if successful, otherwise throws an exception. /// @@ -614,23 +671,21 @@ namespace Cosmos.System.FileSystem.FAT.Listing Global.mFileSystemDebugger.SendInternal("aValue ="); Global.mFileSystemDebugger.SendInternal(aValue); - if (mParent != null) - { - var xData = ((FatDirectoryEntry)mParent).GetDirectoryEntryData(); - - if (xData.Length > 0) - { - var xValue = new byte[aEntryMetadata.DataLength]; - xValue.SetUInt32(0, aValue); - uint offset = mEntryHeaderDataOffset + aEntryMetadata.DataOffset; - Array.Copy(xValue, 0, xData, offset, aEntryMetadata.DataLength); - ((FatDirectoryEntry)mParent).SetDirectoryEntryData(xData); - } - } - else + if (IsRootDirectory()) { throw new Exception("Root directory metadata can not be changed using the file stream."); } + + var xData = ((FatDiretoryEntry)mParent).GetDirectoryEntryData(); + + if (xData.Length > 0) + { + var xValue = new byte[aEntryMetadata.DataLength]; + xValue.SetUInt32(0, aValue); + uint offset = mEntryHeaderDataOffset + aEntryMetadata.DataOffset; + Array.Copy(xValue, 0, xData, offset, aEntryMetadata.DataLength); + ((FatDiretoryEntry)mParent).SetDirectoryEntryData(xData); + } } internal void SetDirectoryEntryMetadataValue(FatDirectoryEntryMetadata aEntryMetadata, long aValue) @@ -639,25 +694,23 @@ namespace Cosmos.System.FileSystem.FAT.Listing Global.mFileSystemDebugger.SendInternal("aValue ="); Global.mFileSystemDebugger.SendInternal(aValue); - if (mParent != null) - { - var xData = ((FatDirectoryEntry)mParent).GetDirectoryEntryData(); - - if (xData.Length > 0) - { - var xValue = new byte[aEntryMetadata.DataLength]; - xValue.SetUInt32(0, (uint)aValue); - uint offset = mEntryHeaderDataOffset + aEntryMetadata.DataOffset; - Global.mFileSystemDebugger.SendInternal("offset ="); - Global.mFileSystemDebugger.SendInternal(offset); - Array.Copy(xValue, 0, xData, offset, aEntryMetadata.DataLength); - ((FatDirectoryEntry)mParent).SetDirectoryEntryData(xData); - } - } - else + if (IsRootDirectory()) { throw new Exception("Root directory metadata can not be changed using the file stream."); } + + var xData = ((FatDiretoryEntry)mParent).GetDirectoryEntryData(); + + if (xData.Length > 0) + { + var xValue = new byte[aEntryMetadata.DataLength]; + xValue.SetUInt32(0, (uint)aValue); + uint offset = mEntryHeaderDataOffset + aEntryMetadata.DataOffset; + Global.mFileSystemDebugger.SendInternal("offset ="); + Global.mFileSystemDebugger.SendInternal(offset); + Array.Copy(xValue, 0, xData, offset, aEntryMetadata.DataLength); + ((FatDiretoryEntry)mParent).SetDirectoryEntryData(xData); + } } internal void SetDirectoryEntryMetadataValue(FatDirectoryEntryMetadata aEntryMetadata, string aValue) @@ -666,7 +719,12 @@ namespace Cosmos.System.FileSystem.FAT.Listing Global.mFileSystemDebugger.SendInternal("aValue ="); Global.mFileSystemDebugger.SendInternal(aValue); - var xData = ((FatDirectoryEntry)mParent).GetDirectoryEntryData(); + if (IsRootDirectory()) + { + throw new Exception("Root directory metadata can not be changed using the file stream."); + } + + var xData = ((FatDiretoryEntry)mParent).GetDirectoryEntryData(); if (xData.Length > 0) { @@ -676,7 +734,7 @@ namespace Cosmos.System.FileSystem.FAT.Listing uint offset = mEntryHeaderDataOffset + aEntryMetadata.DataOffset; Array.Copy(xValue, 0, xData, offset, aEntryMetadata.DataLength); - ((FatDirectoryEntry)mParent).SetDirectoryEntryData(xData); + ((FatDiretoryEntry)mParent).SetDirectoryEntryData(xData); } } @@ -791,5 +849,100 @@ namespace Cosmos.System.FileSystem.FAT.Listing return xChecksum; } + + private long GetDirectoryEntrySize(byte[] DirectoryEntryData) + { + long xResult = 0; + + for (uint i = 0; i < DirectoryEntryData.Length; i = i + 32) + { + byte xAttrib = DirectoryEntryData[i + 11]; + byte xStatus = DirectoryEntryData[i]; + + if (xAttrib == FatDirectoryEntryAttributeConsts.LongName) + { + //Global.mFileSystemDebugger.SendInternal($"-- FatDirectoryEntry.GetDirectoryEntrySize() LongName DirEntry skipped!"); + continue; + } + + if (xStatus == 0x00) + { + //Global.mFileSystemDebugger.SendInternal(" : Attrib = " + xAttrib + ", Status = " + xStatus); + break; + } + + switch (xStatus) + { + case 0x05: + // Japanese characters - We dont handle these + continue; + case 0x2E: + // Dot entry + continue; + case FatDirectoryEntryAttributeConsts.UnusedOrDeletedEntry: + // Empty slot, skip it + continue; + + default: + break; + } + + int xTest = xAttrib & (FatDirectoryEntryAttributeConsts.Directory | FatDirectoryEntryAttributeConsts.VolumeID); + + switch (xTest) + { + // Normal file + case 0: + uint xSize = DirectoryEntryData.ToUInt32(i + 28); + xResult += xSize; + break; + + case FatDirectoryEntryAttributeConsts.Directory: + //Global.mFileSystemDebugger.SendInternal($"-- FatDirectoryEntry.GetDirectoryEntrySize() found directory: recursing!"); + + uint xFirstCluster = (uint)(DirectoryEntryData.ToUInt16(i + 20) << 16 | DirectoryEntryData.ToUInt16(i + 26)); + byte[] xDirData; + ((FatFileSystem)mFileSystem).Read(xFirstCluster, out xDirData); + + xResult += GetDirectoryEntrySize(xDirData); + break; + + case FatDirectoryEntryAttributeConsts.VolumeID: + //Global.mFileSystemDebugger.SendInternal(": skipped"); + continue; + + default: + //Global.mFileSystemDebugger.SendInternal(": skipped"); + continue; + } + } + + //Global.mFileSystemDebugger.SendInternal($"-- FatDirectoryEntry.GetDirectoryEntrySize() is {xResult} bytes"); + return xResult; + } + + /* + * Please note that this could become slower and slower as the partion becomes greater this could be optimized in two ways: + * 1. Compute the value using this function on FS inizialization and write the difference between TotalSpace and the computed + * value to the specif field of 'FS Information Sector' of FAT32 + * 2. Compute the value using this function on FS inizialization and write the difference between TotalSpace and the computed + * value in a sort of memory cache in VFS itself + * + * In any case if one of this two methods will be used in the future when a file is removed or new data are written on it, + * the value on the field should be always updated. + */ + public override long GetUsedSpace() + { + Global.mFileSystemDebugger.SendInternal($"-- FatDirectoryEntry.GetUsedSpace() on Directory {mName} ---"); + + long xResult = 0; + + var xData = GetDirectoryEntryData(); + + xResult += GetDirectoryEntrySize(xData); + + Global.mFileSystemDebugger.SendInternal($"-- FatDirectoryEntry.GetUsedSpace() is {xResult} bytes"); + return xResult; + } } } diff --git a/source/Cosmos.System2/FileSystem/FatFileSystemFactory.cs b/source/Cosmos.System2/FileSystem/FatFileSystemFactory.cs new file mode 100644 index 000000000..68c84fd2d --- /dev/null +++ b/source/Cosmos.System2/FileSystem/FatFileSystemFactory.cs @@ -0,0 +1,23 @@ +using Cosmos.HAL.BlockDevice; +using Cosmos.System.FileSystem.FAT; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Cosmos.System.FileSystem +{ + public class FatFileSystemFactory : FileSystemFactory + { + public override string Name { get => "FAT"; } + + /// + /// Initializes a new instance of the class. + /// + /// The partition. + /// The root path. + /// FAT signature not found. + public override FileSystem Create(Partition aDevice, string aRootPath, long aSize) => new FatFileSystem(aDevice, aRootPath, aSize); + + public override bool IsType(Partition aDevice) => FatFileSystem.IsDeviceFat(aDevice); + } +} diff --git a/source/Cosmos.System2/FileSystem/FileSystem.cs b/source/Cosmos.System2/FileSystem/FileSystem.cs index 5486127f2..6e8f2a2ab 100644 --- a/source/Cosmos.System2/FileSystem/FileSystem.cs +++ b/source/Cosmos.System2/FileSystem/FileSystem.cs @@ -16,16 +16,6 @@ namespace Cosmos.System.FileSystem mSize = aSize; } - public static FileSystemType GetFileSystemType(Partition aDevice) - { - if (FatFileSystem.IsDeviceFat(aDevice)) - { - return FileSystemType.FAT; - } - - return FileSystemType.Unknown; - } - public abstract void DisplayFileSystemInfo(); public abstract List GetDirectoryListing(DirectoryEntry baseDirectory); @@ -45,5 +35,13 @@ namespace Cosmos.System.FileSystem public string mRootPath { get; } public long mSize { get; } + + public abstract long mAvailableFreeSpace { get; } + + public abstract long mTotalFreeSpace { get; } + + public abstract string mType { get; } + + public abstract string mLabel { get; set; } } } diff --git a/source/Cosmos.System2/FileSystem/FileSystemFactory.cs b/source/Cosmos.System2/FileSystem/FileSystemFactory.cs new file mode 100644 index 000000000..ce2471798 --- /dev/null +++ b/source/Cosmos.System2/FileSystem/FileSystemFactory.cs @@ -0,0 +1,16 @@ +using Cosmos.HAL.BlockDevice; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Cosmos.System.FileSystem +{ + public class FileSystemFactory + { + public virtual string Name { get; private set; } + + public virtual FileSystem Create(Partition aDevice, string aRootPath, long aSize) => null; + + public virtual bool IsType(Partition aDevice) => false; + } +} diff --git a/source/Cosmos.System2/FileSystem/FileSystemType.cs b/source/Cosmos.System2/FileSystem/FileSystemType.cs index 1197e3253..d11e5e06f 100644 --- a/source/Cosmos.System2/FileSystem/FileSystemType.cs +++ b/source/Cosmos.System2/FileSystem/FileSystemType.cs @@ -2,7 +2,7 @@ { public enum FileSystemType { + Unknown = 0, FAT, - Unknown } -} \ No newline at end of file +} diff --git a/source/Cosmos.System2/FileSystem/Listing/DirectoryEntry.cs b/source/Cosmos.System2/FileSystem/Listing/DirectoryEntry.cs index 4ac7ef2cb..95749a93b 100644 --- a/source/Cosmos.System2/FileSystem/Listing/DirectoryEntry.cs +++ b/source/Cosmos.System2/FileSystem/Listing/DirectoryEntry.cs @@ -79,5 +79,7 @@ namespace Cosmos.System.FileSystem.Listing public abstract void SetSize(long aSize); public abstract Stream GetFileStream(); + + public abstract long GetUsedSpace(); } -} \ No newline at end of file +} diff --git a/source/Cosmos.System2/FileSystem/VFS/VFSBase.cs b/source/Cosmos.System2/FileSystem/VFS/VFSBase.cs index 1c5770379..57b8ec731 100644 --- a/source/Cosmos.System2/FileSystem/VFS/VFSBase.cs +++ b/source/Cosmos.System2/FileSystem/VFS/VFSBase.cs @@ -8,6 +8,8 @@ namespace Cosmos.System.FileSystem.VFS { public abstract void Initialize(); + public abstract void RegisterFileSystem(FileSystemFactory aFileSystemFactory); + public abstract DirectoryEntry CreateFile(string aPath); public abstract DirectoryEntry CreateDirectory(string aPath); @@ -37,5 +39,19 @@ namespace Cosmos.System.FileSystem.VFS public static char AltDirectorySeparatorChar { get { return '/'; } } public static char VolumeSeparatorChar { get { return ':'; } } + + public abstract bool IsValidDriveId(string driveId); + + public abstract long GetTotalSize(string aDriveId); + + public abstract long GetAvailableFreeSpace(string aDriveId); + + public abstract long GetTotalFreeSpace(string aDriveId); + + public abstract string GetFileSystemType(string aDriveId); + + public abstract string GetFileSystemLabel(string aDriveId); + + public abstract void SetFileSystemLabel(string aDriveId, string aLabel); } } diff --git a/source/Cosmos.System2/FileSystem/VFS/VFSManager.cs b/source/Cosmos.System2/FileSystem/VFS/VFSManager.cs index b4418ff5d..de21c6c03 100644 --- a/source/Cosmos.System2/FileSystem/VFS/VFSManager.cs +++ b/source/Cosmos.System2/FileSystem/VFS/VFSManager.cs @@ -201,19 +201,20 @@ namespace Cosmos.System.FileSystem.VFS return mVFS.GetVolumes(); } + public static void RegisterFileSystem(FileSystemFactory aFileSystemFactory) + { + mVFS.RegisterFileSystem(aFileSystemFactory); + } + public static List GetLogicalDrives() { Global.mFileSystemDebugger.SendInternal("--- VFSManager.GetLogicalDrives ---"); - //TODO: Directory.GetLogicalDrives() will call this. - return null; - - /* List xDrives = new List(); - foreach (FilesystemEntry entry in GetVolumes()) - xDrives.Add(entry.Name + Path.VolumeSeparatorChar + Path.DirectorySeparatorChar); - return xDrives.ToArray(); - */ + foreach (DirectoryEntry entry in GetVolumes()) + xDrives.Add(entry.mName + Path.VolumeSeparatorChar + Path.DirectorySeparatorChar); + + return xDrives; } public static List InternalGetFileDirectoryNames( @@ -430,6 +431,43 @@ namespace Cosmos.System.FileSystem.VFS Global.mFileSystemDebugger.SendInternal("--- VFSManager.GetFileAttributes ---"); mVFS.SetFileAttributes(aPath, fileAttributes); } + + public static bool IsValidDriveId(string aPath) + { + Global.mFileSystemDebugger.SendInternal("--- VFSManager.GetFileAttributes ---"); + return mVFS.IsValidDriveId(aPath); + } + + public static long GetTotalSize(string aDriveId) + { + return mVFS.GetTotalSize(aDriveId); + } + + public static long GetAvailableFreeSpace(string aDriveId) + { + return mVFS.GetAvailableFreeSpace(aDriveId); + } + + public static long GetTotalFreeSpace(string aDriveId) + { + return mVFS.GetTotalFreeSpace(aDriveId); + } + + public static string GetFileSystemType(string aDriveId) + { + return mVFS.GetFileSystemType(aDriveId); + } + + public static string GetFileSystemLabel(string aDriveId) + { + return mVFS.GetFileSystemLabel(aDriveId); + } + + public static void SetFileSystemLabel(string aDriveId, string aLabel) + { + mVFS.SetFileSystemLabel(aDriveId, aLabel); + } + #region Helpers public static char GetAltDirectorySeparatorChar() diff --git a/source/Cosmos.System2_Plugs/System/IO/CosmosDriveInfo.cs b/source/Cosmos.System2_Plugs/System/IO/CosmosDriveInfo.cs new file mode 100644 index 000000000..bd05171ed --- /dev/null +++ b/source/Cosmos.System2_Plugs/System/IO/CosmosDriveInfo.cs @@ -0,0 +1,115 @@ +//#define COSMOSDEBUG +using Cosmos.System; +using Cosmos.System.FileSystem; +using Cosmos.System.FileSystem.VFS; +using IL2CPU.API.Attribs; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Cosmos.System_Plugs.System.IO +{ + [Plug(Target = typeof(DriveInfo))] + public static class DriveInfoImpl + { + public static string NormalizeDriveName(string driveName) + { + string name; + + if (driveName.Length == 1) + name = driveName + ":\\"; + else + { + name = Path.GetPathRoot(driveName); + // Disallow null or empty drive letters and UNC paths + if (name == null || name.Length == 0 || name.StartsWith("\\\\", StringComparison.Ordinal)) + throw new ArgumentException("Argument must be drive identifier or root dir"); + } + // We want to normalize to have a trailing backslash so we don't have two equivalent forms and + // because some Win32 API don't work without it. + if (name.Length == 2 && name[1] == ':') + { + name = name + "\\"; + } + + if (!VFSManager.IsValidDriveId(name)) + { + throw new ArgumentException("Argument must be drive identifier or root dir"); + } + + return name; + } + + public static long get_AvailableFreeSpace(DriveInfo aThis) + { + Global.mFileSystemDebugger.SendInternal($"Getting Available Free Space of {aThis.Name}"); + + return VFSManager.GetAvailableFreeSpace(aThis.Name); + } + + public static long get_TotalFreeSpace(DriveInfo aThis) + { + Global.mFileSystemDebugger.SendInternal($"Getting Total Free Space of {aThis.Name}"); + + return VFSManager.GetTotalFreeSpace(aThis.Name); + } + + public static long get_TotalSize(DriveInfo aThis) + { + Global.mFileSystemDebugger.SendInternal($"Getting size of {aThis.Name}"); + + return VFSManager.GetTotalSize(aThis.Name); + } + + public static string get_DriveFormat(DriveInfo aThis) + { + Global.mFileSystemDebugger.SendInternal($"Getting format of {aThis.Name}"); + + return VFSManager.GetFileSystemType(aThis.Name); + } + + public static string get_VolumeLabel(DriveInfo aThis) + { + Global.mFileSystemDebugger.SendInternal($"Getting label of {aThis.Name}"); + + return VFSManager.GetFileSystemLabel(aThis.Name); + } + + public static void set_VolumeLabel(DriveInfo aThis, string aLabel) + { + Global.mFileSystemDebugger.SendInternal($"Setting label of {aThis.Name} with {aLabel}"); + + VFSManager.SetFileSystemLabel(aThis.Name, aLabel); + } + + /* For now I'm forcing IsReady to be always true as only fixed drives are supported in Cosmos for now */ + public static bool get_IsReady(DriveInfo aThis) + { + Global.mFileSystemDebugger.SendInternal($"Getting isReady status of {aThis.Name}"); + return true; + } + + /* For now I'm forcing DriveType to always be 'Fixed' as only fixed drives are supported in Cosmos for now */ + public static DriveType get_DriveType(DriveInfo aThis) + { + Global.mFileSystemDebugger.SendInternal($"Getting DriveType of {aThis.Name}"); + return DriveType.Fixed; + } + + public static DriveInfo[] GetDrives() + { + Global.mFileSystemDebugger.SendInternal("GetDrives called"); + + List drives = VFSManager.GetLogicalDrives(); + + DriveInfo[] result = new DriveInfo[drives.Count]; + for (int i = 0; i < drives.Count; i++) + { + result[i] = new DriveInfo(drives[i]); + } + + return result; + } + } +} diff --git a/source/Cosmos.System2_Plugs/System/Int32Impl.cs b/source/Cosmos.System2_Plugs/System/Int32Impl.cs index 76d855515..ce9acf32c 100644 --- a/source/Cosmos.System2_Plugs/System/Int32Impl.cs +++ b/source/Cosmos.System2_Plugs/System/Int32Impl.cs @@ -46,5 +46,20 @@ namespace Cosmos.System_Plugs.System return result; } + + /* .Net Core TryParse is calling Number.TryParse() that does NRE in Cosmos, plugged it for now */ + public static bool TryParse(string s, out int result) + { + try + { + result = Int32.Parse(s); + return true; + } + catch + { + result = 0; + return false; + } + } } }