//#define COSMOSDEBUG using System; using System.Collections.Generic; using System.IO; using Cosmos.HAL.BlockDevice; using Cosmos.System.FileSystem.FAT; using Cosmos.System.FileSystem.Listing; using Cosmos.System.FileSystem.VFS; namespace Cosmos.System.FileSystem { // ReSharper disable once InconsistentNaming /// /// Cosmos default virtual file system. /// /// public class CosmosVFS : VFSBase { private List mPartitions; private List mFileSystems; private FileSystem mCurrentFileSystem; private List mRegisteredFileSystems; /// /// Initializes the virtual file system. /// /// Thrown on I/O exception. /// Thrown on memory error. /// Thrown on memory error. /// Thrown on memory error. /// Thrown on memory error. /// Thrown on memory error. /// Thrown on fatal error. /// Thrown on fatal error. /// Thrown on memory error. /// Thrown on fatal error. public override void Initialize() { mPartitions = new List(); mFileSystems = new List(); mRegisteredFileSystems = new List(); RegisterFileSystem(new FatFileSystemFactory()); InitializePartitions(); if (mPartitions.Count > 0) { InitializeFileSystems(); } } /// /// Register file system. /// /// A file system to register. public override void RegisterFileSystem(FileSystemFactory aFileSystemFactory) { Global.mFileSystemDebugger.SendInternal($"Registering filesystem {aFileSystemFactory.Name}"); mRegisteredFileSystems.Add(aFileSystemFactory); } /// /// Creates a new file. /// /// The full path including the file to create. /// DirectoryEntry value. /// /// /// Thrown if aPath is null. /// aNewDirectory is null or empty. /// memory error. /// /// /// Thrown on memory error / unknown directory entry type. /// Thrown when data lenght is greater then Int32.MaxValue. /// /// /// Thrown when data size invalid. /// invalid directory entry type. /// the entry at aPath is not a file. /// Thrown when the parent directory of aPath is not a directory. /// memory error. /// /// /// /// /// Thrown if aPath length is zero. /// Thrown if aPath is invalid. /// memory error. /// /// /// Thrown on memory error. /// Thrown on fatal error (contact support). /// Thrown on fatal error (contact support). /// Thrown on memory error. /// Thrown when The aPath is longer than the system defined maximum length. public override DirectoryEntry CreateFile(string aPath) { Global.mFileSystemDebugger.SendInternal("--- CosmosVFS.CreateFile ---"); if (aPath == null) { throw new ArgumentNullException(nameof(aPath)); } if (aPath.Length == 0) { throw new ArgumentException("aPath is empty"); } Global.mFileSystemDebugger.SendInternal("aPath ="); Global.mFileSystemDebugger.SendInternal(aPath); if (File.Exists(aPath)) { Global.mFileSystemDebugger.SendInternal("File already exists."); return GetFile(aPath); } Global.mFileSystemDebugger.SendInternal("File doesn't exist."); string xFileToCreate = Path.GetFileName(aPath); Global.mFileSystemDebugger.SendInternal("After GetFileName"); Global.mFileSystemDebugger.SendInternal("xFileToCreate ="); Global.mFileSystemDebugger.SendInternal(xFileToCreate); string xParentDirectory = Path.GetDirectoryName(aPath); Global.mFileSystemDebugger.SendInternal("After removing last path part"); Global.mFileSystemDebugger.SendInternal("xParentDirectory ="); Global.mFileSystemDebugger.SendInternal(xParentDirectory); DirectoryEntry xParentEntry = GetDirectory(xParentDirectory); if (xParentEntry == null) { Global.mFileSystemDebugger.SendInternal("Parent directory doesn't exist."); xParentEntry = CreateDirectory(xParentDirectory); } Global.mFileSystemDebugger.SendInternal("Parent directory exists."); var xFS = GetFileSystemFromPath(xParentDirectory); return xFS.CreateFile(xParentEntry, xFileToCreate); } /// /// Creates a directory. /// /// The full path including the directory to create. /// DirectoryEntry value. /// /// /// Thrown if aPath is null. /// aNewDirectory is null or empty. /// memory error. /// /// /// Thrown on memory error / unknown directory entry type. /// Thrown when data lenght is greater then Int32.MaxValue. /// /// /// Thrown when data size invalid. /// invalid directory entry type. /// the entry at aPath is not a directory. /// memory error. /// /// /// /// /// Thrown if aPath length is zero. /// Thrown if aPath is invalid. /// memory error. /// /// /// Thrown on memory error. /// Thrown on fatal error (contact support). /// Thrown on fatal error (contact support). /// Thrown on memory error. /// Thrown when The aPath is longer than the system defined maximum length. public override DirectoryEntry CreateDirectory(string aPath) { Global.mFileSystemDebugger.SendInternal("-- CosmosVFS.CreateDirectory ---"); if (aPath == null) { throw new ArgumentNullException(nameof(aPath)); } if (aPath.Length == 0) { throw new ArgumentException("aPath length is zero"); } Global.mFileSystemDebugger.SendInternal("aPath = " + aPath); if (Directory.Exists(aPath)) { Global.mFileSystemDebugger.SendInternal("Path already exists."); return GetDirectory(aPath); } Global.mFileSystemDebugger.SendInternal("Path doesn't exist."); aPath = aPath.TrimEnd(DirectorySeparatorChar, AltDirectorySeparatorChar); string xDirectoryToCreate = Path.GetFileName(aPath); Global.mFileSystemDebugger.SendInternal("After GetFileName"); Global.mFileSystemDebugger.SendInternal("xDirectoryToCreate = " + xDirectoryToCreate); string xParentDirectory = Path.GetDirectoryName(aPath); Global.mFileSystemDebugger.SendInternal("After removing last path part"); Global.mFileSystemDebugger.SendInternal("xParentDirectory = " + xParentDirectory); DirectoryEntry xParentEntry = GetDirectory(xParentDirectory); if (xParentEntry == null) { Global.mFileSystemDebugger.SendInternal("Parent directory doesn't exist."); xParentEntry = CreateDirectory(xParentDirectory); } Global.mFileSystemDebugger.SendInternal("Parent directory exists."); var xFS = GetFileSystemFromPath(xParentDirectory); return xFS.CreateDirectory(xParentEntry, xDirectoryToCreate); } /// /// Deletes a file. /// /// The full path. /// TRUE on success. public override bool DeleteFile(DirectoryEntry aPath) { try { var xFS = GetFileSystemFromPath(aPath.mFullPath); xFS.DeleteFile(aPath); return true; } catch { return false; } } /// /// Deletes an empty directory. /// /// The full path. /// TRUE on success. public override bool DeleteDirectory(DirectoryEntry aPath) { try { if (GetDirectoryListing(aPath).Count > 0) { throw new Exception("Directory is not empty"); } var xFS = GetFileSystemFromPath(aPath.mFullPath); xFS.DeleteDirectory(aPath); return true; } catch { return false; } } /// /// Gets the directory listing for a path. /// /// The full path. /// DirectoryEntry list value. /// /// /// Thrown if aPath is null or empty. /// Root path is null or empty. /// Memory error. /// Fatal error. /// /// /// /// /// Thrown if aFS is null. /// Root directory is null. /// Memory error. /// /// /// /// /// Thrown when root directory address is smaller then root directory address. /// Memory error. /// /// /// /// /// Thrown when aPath is too deep. /// Data lenght is greater then Int32.MaxValue. /// /// /// /// /// Thrown when unable to determine filesystem for path: + aPath. /// data size invalid. /// invalid directory entry type. /// path not found. /// /// /// Thrown on memory error. public override List GetDirectoryListing(string aPath) { Global.mFileSystemDebugger.SendInternal("-- CosmosVFS.GetDirectoryListing --"); var xFS = GetFileSystemFromPath(aPath); var xDirectory = DoGetDirectoryEntry(aPath, xFS); return xFS.GetDirectoryListing(xDirectory); } /// /// Gets the directory listing for a directory entry. /// /// The directory entry. /// DirectoryEntry type list value. /// /// /// Thrown if aDirectory is null or empty. /// aDirectory.mFullPath is null or empty. /// Memory error. /// Fatal error. /// /// /// /// /// Thrown if aFS is null. /// Root directory is null. /// Memory error. /// /// /// /// /// Thrown when root directory address is smaller then root directory address. /// Memory error. /// /// /// /// /// Thrown when aDirectory.mFullPath is too deep. /// Data lenght is greater then Int32.MaxValue. /// /// /// /// /// Thrown when unable to determine filesystem for path: + aDirectory.mFullPath. /// data size invalid. /// invalid directory entry type. /// path not found. /// /// /// Thrown on memory error. public override List GetDirectoryListing(DirectoryEntry aDirectory) { if (aDirectory == null || String.IsNullOrEmpty(aDirectory.mFullPath)) { throw new ArgumentException("Argument is null or empty", nameof(aDirectory)); } return GetDirectoryListing(aDirectory.mFullPath); } /// /// Gets the directory entry for a directory. /// /// The full path path. /// A directory entry for the directory. /// Thrown when the entry at aPath is not a directory. public override DirectoryEntry GetDirectory(string aPath) { try { var xFileSystem = GetFileSystemFromPath(aPath); var xEntry = DoGetDirectoryEntry(aPath, xFileSystem); if ((xEntry != null) && (xEntry.mEntryType == DirectoryEntryTypeEnum.Directory)) { return xEntry; } } catch (Exception) { Global.mFileSystemDebugger.SendInternal("CosmosVFS.GetDirectory - DoGetDirectoryEntry failed, returning null. aPath = " + aPath); return null; } throw new Exception(aPath + " was found, but is not a directory."); } /// /// Gets the directory entry for a file. /// /// The full path. /// A directory entry for the file. /// Thrown when the entry at aPath is not a file. public override DirectoryEntry GetFile(string aPath) { try { var xFileSystem = GetFileSystemFromPath(aPath); var xEntry = DoGetDirectoryEntry(aPath, xFileSystem); if ((xEntry != null) && (xEntry.mEntryType == DirectoryEntryTypeEnum.File)) { return xEntry; } } catch (Exception) { Global.mFileSystemDebugger.SendInternal("CosmosVFS.GetFile - DoGetDirectoryEntry failed, returning null. aPath = " + aPath); return null; } throw new Exception(aPath + " was found, but is not a file."); } /// /// Gets the volumes for all registered file systems. /// /// A list of directory entries for all volumes. /// Thrown if filesystem is null. /// Thrown when root directory address is smaller then root directory address. /// Thrown when root path is null or empty. public override List GetVolumes() { List xVolumes = new List(); for (int i = 0; i < mFileSystems.Count; i++) { xVolumes.Add(GetVolume(mFileSystems[i])); } return xVolumes; } /// /// Gets the directory entry for a volume. /// /// The volume root path. /// A directory entry for the volume. /// Thrown when aPath is null or empty. /// Unable to determine filesystem for path: + aPath /// Thrown if filesystem is null. /// Thrown when root directory address is smaller then root directory address. public override DirectoryEntry GetVolume(string aPath) { if (string.IsNullOrEmpty(aPath)) { return null; } var xFileSystem = GetFileSystemFromPath(aPath); if (xFileSystem != null) { return GetVolume(xFileSystem); } return null; } /// /// Gets the attributes for a File / Directory. /// /// The path of the File / Directory. /// The File / Directory attributes. /// /// /// Thrown if aPath is null or empty. /// Thrown when aFS root path is null or empty. /// Thrown on memory error. /// Fatal error. /// /// /// /// /// Thrown if aFS is null. /// Thrown when root directory is null. /// Thrown on memory error. /// /// /// /// /// Thrown when root directory address is smaller then root directory address. /// Thrown on memory error. /// /// /// /// /// Thrown when aPath is too deep. /// Thrown when data lenght is greater then Int32.MaxValue. /// /// /// /// /// Thrown when data size invalid. /// Thrown on invalid directory entry type. /// Thrown when aPath entry not found. /// Thrown when unable to determine filesystem for path: + aPath. /// Thrown aPath is neither a file neither a directory. /// /// /// Thrown on memory error. public override FileAttributes GetFileAttributes(string aPath) { /* * We are limiting ourselves to the simpler attributes File and Directory for now. * I think that in the end FAT does not support anything else */ Global.mFileSystemDebugger.SendInternal($"CosmosVFS.GetFileAttributes() for path {aPath}"); var xFileSystem = GetFileSystemFromPath(aPath); var xEntry = DoGetDirectoryEntry(aPath, xFileSystem); if (xEntry == null) throw new Exception($"{aPath} is neither a file neither a directory"); switch (xEntry.mEntryType) { case DirectoryEntryTypeEnum.File: Global.mFileSystemDebugger.SendInternal($"It is a File"); return FileAttributes.Normal; case DirectoryEntryTypeEnum.Directory: Global.mFileSystemDebugger.SendInternal($"It is a Directory"); return FileAttributes.Directory; case DirectoryEntryTypeEnum.Unknown: default: throw new Exception($"{aPath} is neither a file neither a directory"); } } /// /// Sets the attributes for a File / Directory. /// Not implemented. /// /// The path of the File / Directory. /// The attributes of the File / Directory. /// Thrown always public override void SetFileAttributes(string aPath, FileAttributes fileAttributes) { throw new NotImplementedException("SetFileAttributes not implemented"); } /// /// Initializes the partitions for all block devices. /// /// Thrown on I/O exception. protected virtual void InitializePartitions() { for (int i = 0; i < BlockDevice.Devices.Count; i++) { if (BlockDevice.Devices[i] is Partition) { mPartitions.Add((Partition)BlockDevice.Devices[i]); } } if (mPartitions.Count > 0) { for (int i = 0; i < mPartitions.Count; i++) { Global.mFileSystemDebugger.SendInternal("Partition #: "); Global.mFileSystemDebugger.SendInternal(i + 1); global::System.Console.WriteLine("Partition #: " + (i + 1)); Global.mFileSystemDebugger.SendInternal("Block Size:"); Global.mFileSystemDebugger.SendInternal(mPartitions[i].BlockSize); global::System.Console.WriteLine("Block Size: " + mPartitions[i].BlockSize + " bytes"); Global.mFileSystemDebugger.SendInternal("Block Count:"); Global.mFileSystemDebugger.SendInternal(mPartitions[i].BlockCount); global::System.Console.WriteLine("Block Count: " + mPartitions[i].BlockCount); Global.mFileSystemDebugger.SendInternal("Size:"); Global.mFileSystemDebugger.SendInternal(mPartitions[i].BlockCount * mPartitions[i].BlockSize / 1024 / 1024); global::System.Console.WriteLine("Size: " + mPartitions[i].BlockCount * mPartitions[i].BlockSize / 1024 / 1024 + " MB"); } } else { global::System.Console.WriteLine("No partitions found!"); } } /// /// Initializes the file system for all partitions. /// /// Thrown if null partition exists. /// Thrown when data lenght is greater then Int32.MaxValue. /// Thrown on memory error. /// Thrown on memory error. /// Thrown on memory error. /// Thrown on I/O exception. /// Thrown on fatal error. /// Thrown on fatal error. /// Thrown on memory error. /// Thrown on fatal error. protected virtual void InitializeFileSystems() { for (int i = 0; i < mPartitions.Count; i++) { string xRootPath = string.Concat(i, VolumeSeparatorChar, DirectorySeparatorChar); var xSize = (long)(mPartitions[i].BlockCount * mPartitions[i].BlockSize / 1024 / 1024); // 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) { if (fs.IsType(mPartitions[i])) { Global.mFileSystemDebugger.SendInternal($"Partion {i} has a {fs.Name} filesystem"); mFileSystems.Add(fs.Create(mPartitions[i], xRootPath, xSize)); } } if ((mFileSystems.Count > 0) && (mFileSystems[mFileSystems.Count - 1].RootPath == xRootPath)) { string xMessage = string.Concat("Initialized ", mFileSystems.Count, " filesystem(s)..."); global::System.Console.WriteLine(xMessage); mFileSystems[i].DisplayFileSystemInfo(); } else { string xMessage = string.Concat("No filesystem found on partition #", i); global::System.Console.WriteLine(xMessage); } } } /// /// Gets the file system from a path. /// /// The path. /// The file system for the path. /// Thrown when aPath is null or empty. /// Unable to determine filesystem for path: + aPath private FileSystem GetFileSystemFromPath(string aPath) { Global.mFileSystemDebugger.SendInternal("--- CosmosVFS.GetFileSystemFromPath ---"); if (String.IsNullOrEmpty(aPath)) { throw new ArgumentException("Argument is null or empty", nameof(aPath)); } Global.mFileSystemDebugger.SendInternal("aPath = " + aPath); string xPath = Path.GetPathRoot(aPath); Global.mFileSystemDebugger.SendInternal("xPath after GetPathRoot = " + xPath); if ((mCurrentFileSystem != null) && (xPath == mCurrentFileSystem.RootPath)) { Global.mFileSystemDebugger.SendInternal("Returning current file system."); return mCurrentFileSystem; } for (int i = 0; i < mFileSystems.Count; i++) { if (mFileSystems[i].RootPath == xPath) { Global.mFileSystemDebugger.SendInternal("Found filesystem."); mCurrentFileSystem = mFileSystems[i]; return mCurrentFileSystem; } } throw new Exception("Unable to determine filesystem for path: " + aPath); } /// /// Attempts to get a directory entry for a path in a file system. /// /// The path. /// The file system. /// A directory entry for the path. /// /// /// Thrown if aPath is null or empty. /// Thrown when aFS root path is null or empty. /// Thrown on memory error. /// Fatal error. /// /// /// /// /// Thrown if aFS is null. /// Thrown when root directory is null. /// Thrown on memory error. /// /// /// /// /// Thrown when root directory address is smaller then root directory address. /// Thrown on memory error. /// /// /// /// /// Thrown when aPath is too deep. /// Thrown when data lenght is greater then Int32.MaxValue. /// /// /// /// /// Thrown when data size invalid. /// Thrown on invalid directory entry type. /// Thrown when aPath entry not found. /// /// /// Thrown on memory error. private DirectoryEntry DoGetDirectoryEntry(string aPath, FileSystem aFS) { Global.mFileSystemDebugger.SendInternal("--- CosmosVFS.DoGetDirectoryEntry ---"); if (String.IsNullOrEmpty(aPath)) { throw new ArgumentException("Argument is null or empty", nameof(aPath)); } if (aFS == null) { throw new ArgumentNullException(nameof(aFS)); } Global.mFileSystemDebugger.SendInternal("aPath = " + aPath); string[] xPathParts = VFSManager.SplitPath(aPath); DirectoryEntry xBaseDirectory = GetVolume(aFS); if (xPathParts.Length == 1) { Global.mFileSystemDebugger.SendInternal("Returning the volume."); return xBaseDirectory; } // start at index 1, because 0 is the volume for (int i = 1; i < xPathParts.Length; i++) { var xPathPart = xPathParts[i].ToLower(); var xPartFound = false; var xListing = aFS.GetDirectoryListing(xBaseDirectory); for (int j = 0; j < xListing.Count; j++) { var xListingItem = xListing[j]; string xListingItemName = xListingItem.mName.ToLower(); xPathPart = xPathPart.ToLower(); if (xListingItemName == xPathPart) { xBaseDirectory = xListingItem; Global.mFileSystemDebugger.SendInternal("Now checking: " + xBaseDirectory.mFullPath); xPartFound = true; break; } } if (!xPartFound) { throw new Exception("Path part '" + xPathPart + "' not found!"); } } Global.mFileSystemDebugger.SendInternal("Returning: " + xBaseDirectory.mFullPath); return xBaseDirectory; } /// /// Gets the root directory entry for a volume in a file system. /// /// The file system containing the volume. /// A directory entry for the volume. /// Thrown if aFS is null. /// Thrown when root directory address is smaller then root directory address. /// Thrown when root path is null or empty. private DirectoryEntry GetVolume(FileSystem aFS) { Global.mFileSystemDebugger.SendInternal("--- CosmosVFS.GetVolume ---"); if (aFS == null) { Global.mFileSystemDebugger.SendInternal("File system is null."); throw new ArgumentNullException(nameof(aFS)); } 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. /// Thrown if driveId length is smaller then 2, or greater than Int32.MaxValue. 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; } /// /// Get total size in bytes. /// /// A drive id to get the size of. /// long value. /// Thrown when aDriveId is null or empty. /// Unable to determine filesystem for path: + aDriveId public override long GetTotalSize(string aDriveId) { var xFs = GetFileSystemFromPath(aDriveId); /* We have to return it in bytes */ return xFs.Size * 1024 * 1024; } /// /// Get available free space. /// /// A drive id to get the size of. /// long value. /// Thrown when aDriveId is null or empty. /// Unable to determine filesystem for path: + aDriveId public override long GetAvailableFreeSpace(string aDriveId) { var xFs = GetFileSystemFromPath(aDriveId); return xFs.AvailableFreeSpace; } /// /// Get total free space. /// /// A drive id to get the size of. /// long value. /// Thrown when aDriveId is null or empty. /// Unable to determine filesystem for path: + aDriveId public override long GetTotalFreeSpace(string aDriveId) { var xFs = GetFileSystemFromPath(aDriveId); return xFs.TotalFreeSpace; } /// /// Get file system type. /// /// A drive id. /// string value. /// Thrown when aDriveId is null or empty. /// Unable to determine filesystem for path: + aDriveId public override string GetFileSystemType(string aDriveId) { var xFs = GetFileSystemFromPath(aDriveId); return xFs.Type; } /// /// Get file system label. /// /// A drive id. /// string value. /// Thrown when aDriveId is null or empty. /// Unable to determine filesystem for path: + aDriveId public override string GetFileSystemLabel(string aDriveId) { var xFs = GetFileSystemFromPath(aDriveId); return xFs.Label; } /// /// Set file system type. /// /// A drive id. /// A label to be set. /// Thrown when aDriveId is null or empty. /// Unable to determine filesystem for path: + aDriveId public override void SetFileSystemLabel(string aDriveId, string aLabel) { Global.mFileSystemDebugger.SendInternal("--- CosmosVFS.SetFileSystemLabel ---"); Global.mFileSystemDebugger.SendInternal($"aDriveId {aDriveId} aLabel {aLabel}"); var xFs = GetFileSystemFromPath(aDriveId); xFs.Label = aLabel; } /// /// Format partition. /// /// A drive id. /// A drive format. /// Quick format. /// /// /// Thrown when the data length is 0 or greater then Int32.MaxValue. /// Entrys matadata offset value is invalid. /// Fatal error (contact support). /// /// /// Thrown when filesystem is null / memory error. /// /// /// Thrown when aDriveId is null or empty. /// Data length is 0. /// Root path is null or empty. /// Memory error. /// /// /// /// /// Unable to determine filesystem for path: + aDriveId. /// Thrown when data size invalid. /// Thrown on unknown file system type. /// Thrown on fatal error (contact support). /// /// /// Thrown when data lenght is greater then Int32.MaxValue. /// Thrown on memory error. /// Thrown when FAT type is unknown. /// Thrown on fatal error (contact support). /// Thrown on fatal error (contact support). /// Thrown when the data in aData is corrupted. /// Thrown when FAT type is unknown. public override void Format(string aDriveId, string aDriveFormat, bool aQuick) { var xFs = GetFileSystemFromPath(aDriveId); xFs.Format(aDriveFormat, aQuick); } } }