using System.Security.AccessControl; using DokanNet; using FileAccess = DokanNet.FileAccess; namespace DevDrive; public class FloppyFileSystem : IDokanOperations { private const string DefaultVolumeName = "Dev Disk"; private const string FileSystemName = "FAT"; private const uint VolumeSerial = 0x19950101; private readonly FloppyStorage _storage; private readonly string _volumeName; public FloppyFileSystem(string? volumeName = null, long totalBytes = FloppyStorage.DefaultCapacity, long freeBytes = FloppyStorage.DefaultCapacity) { _storage = new FloppyStorage(totalBytes, freeBytes); _volumeName = volumeName ?? DefaultVolumeName; } public FloppyFileSystem(Dictionary? symlinks, string? volumeName, long totalBytes, long freeBytes) { _storage = new FloppyStorage(totalBytes, freeBytes); _volumeName = volumeName ?? DefaultVolumeName; if (symlinks != null) { foreach (var (path, target) in symlinks) { _storage.CreateSymlink(path, target, true); } } } public NtStatus CreateFile( string fileName, FileAccess access, FileShare share, FileMode mode, FileOptions options, FileAttributes attributes, IDokanFileInfo info) { var path = FloppyStorage.NormalizePath(fileName); if (SymlinkHandler.TryResolveSymlink(path, _storage, out var realPath) && realPath != null) { return SymlinkHandler.CreateRealFileOrDirectory(realPath, info.IsDirectory, mode); } var node = _storage.GetNode(path); var parentPath = FloppyStorage.GetParentPath(path); var parentExists = _storage.Exists(parentPath); if (info.IsDirectory) { switch (mode) { case FileMode.CreateNew: if (node != null) { return DokanResult.FileExists; } if (!parentExists) { return DokanResult.PathNotFound; } var newDir = _storage.CreateNode(path, true); if (newDir == null) { return DokanResult.Error; } return DokanResult.Success; case FileMode.Open: if (node == null) { return DokanResult.PathNotFound; } if (!node.IsDirectory) { return DokanResult.NotADirectory; } return DokanResult.Success; default: return DokanResult.Success; } } var pathExists = node != null; var pathIsDirectory = node?.IsDirectory ?? false; switch (mode) { case FileMode.Open: if (!pathExists) { return DokanResult.FileNotFound; } if (pathIsDirectory) { info.IsDirectory = true; } break; case FileMode.CreateNew: if (pathExists) { return DokanResult.FileExists; } if (!parentExists) { return DokanResult.PathNotFound; } _storage.CreateNode(path, false); break; case FileMode.Create: if (pathExists && pathIsDirectory) { return DokanResult.AccessDenied; } if (!parentExists) { return DokanResult.PathNotFound; } if (pathExists) { var existing = _storage.GetNode(path); if (existing != null) { existing.Data = []; existing.LastWriteTime = DateTime.Now; } } else { _storage.CreateNode(path, false); } break; case FileMode.OpenOrCreate: if (!pathExists) { if (!parentExists) { return DokanResult.PathNotFound; } _storage.CreateNode(path, false); } else if (pathIsDirectory) { info.IsDirectory = true; } break; case FileMode.Truncate: if (!pathExists) { return DokanResult.FileNotFound; } if (pathIsDirectory) { return DokanResult.AccessDenied; } var truncNode = _storage.GetNode(path); if (truncNode != null) { truncNode.Data = []; truncNode.LastWriteTime = DateTime.Now; } break; case FileMode.Append: if (!pathExists) { if (!parentExists) { return DokanResult.PathNotFound; } _storage.CreateNode(path, false); } break; } return DokanResult.Success; } public void Cleanup(string fileName, IDokanFileInfo info) { if (info.IsDirectory && _storage.Exists(FloppyStorage.NormalizePath(fileName))) { return; } } public void CloseFile(string fileName, IDokanFileInfo info) { } public NtStatus ReadFile( string fileName, byte[] buffer, out int bytesRead, long offset, IDokanFileInfo info) { var path = FloppyStorage.NormalizePath(fileName); if (SymlinkHandler.TryResolveSymlink(path, _storage, out var realPath) && realPath != null) { return SymlinkHandler.ReadRealFile(realPath, buffer, out bytesRead, offset); } var node = _storage.GetNode(path); if (node == null || node.IsDirectory) { bytesRead = 0; return DokanResult.FileNotFound; } node.LastAccessTime = DateTime.Now; if (offset >= node.Data.Length) { bytesRead = 0; return DokanResult.Success; } var toRead = Math.Min(buffer.Length, (int)(node.Data.Length - offset)); Array.Copy(node.Data, offset, buffer, 0, toRead); bytesRead = toRead; return DokanResult.Success; } public NtStatus WriteFile( string fileName, byte[] buffer, out int bytesWritten, long offset, IDokanFileInfo info) { var path = FloppyStorage.NormalizePath(fileName); if (SymlinkHandler.TryResolveSymlink(path, _storage, out var realPath) && realPath != null) { return SymlinkHandler.WriteRealFile(realPath, buffer, out bytesWritten, offset); } var node = _storage.GetNode(path); if (node == null || node.IsDirectory) { bytesWritten = 0; return DokanResult.FileNotFound; } if (info.PagingIo) { if (offset >= node.Data.Length) { bytesWritten = 0; return DokanResult.Success; } var toWrite = Math.Min(buffer.Length, (int)(node.Data.Length - offset)); Array.Copy(buffer, 0, node.Data, offset, toWrite); bytesWritten = toWrite; return DokanResult.Success; } var newLength = offset + buffer.Length; var additionalBytes = newLength - node.Data.Length; if (additionalBytes > 0 && !_storage.HasEnoughSpace(additionalBytes)) { bytesWritten = 0; return DokanResult.DiskFull; } if (newLength > node.Data.Length) { var newData = new byte[newLength]; Array.Copy(node.Data, newData, node.Data.Length); node.Data = newData; } Array.Copy(buffer, 0, node.Data, offset, buffer.Length); bytesWritten = buffer.Length; node.LastWriteTime = DateTime.Now; return DokanResult.Success; } public NtStatus FlushFileBuffers(string fileName, IDokanFileInfo info) { return DokanResult.Success; } public NtStatus GetFileInformation( string fileName, out FileInformation fileInfo, IDokanFileInfo info) { var path = FloppyStorage.NormalizePath(fileName); if (SymlinkHandler.TryResolveSymlink(path, _storage, out var realPath) && realPath != null) { if (SymlinkHandler.GetRealFileInfo(realPath, out fileInfo)) { return DokanResult.Success; } return DokanResult.FileNotFound; } var node = _storage.GetNode(path); if (node == null) { fileInfo = default; return DokanResult.FileNotFound; } fileInfo = new FileInformation { FileName = node.Name, Attributes = node.Attributes, CreationTime = node.CreationTime, LastAccessTime = node.LastAccessTime, LastWriteTime = node.LastWriteTime, Length = node.FileSize }; return DokanResult.Success; } public NtStatus FindFiles( string fileName, out IList files, IDokanFileInfo info) { var path = FloppyStorage.NormalizePath(fileName); if (SymlinkHandler.TryResolveSymlink(path, _storage, out var realPath) && realPath != null) { files = SymlinkHandler.ListRealDirectory(realPath); return DokanResult.Success; } var children = _storage.GetChildren(path); files = children.Select(node => new FileInformation { FileName = node.Name, Attributes = node.Attributes, CreationTime = node.CreationTime, LastAccessTime = node.LastAccessTime, LastWriteTime = node.LastWriteTime, Length = node.FileSize }).ToList(); return DokanResult.Success; } public NtStatus FindFilesWithPattern( string fileName, string searchPattern, out IList files, IDokanFileInfo info) { files = []; return DokanResult.NotImplemented; } public NtStatus SetFileAttributes( string fileName, FileAttributes attributes, IDokanFileInfo info) { var path = FloppyStorage.NormalizePath(fileName); var node = _storage.GetNode(path); if (node == null) { return DokanResult.FileNotFound; } node.Attributes = attributes; return DokanResult.Success; } public NtStatus SetFileTime( string fileName, DateTime? creationTime, DateTime? lastAccessTime, DateTime? lastWriteTime, IDokanFileInfo info) { var path = FloppyStorage.NormalizePath(fileName); var node = _storage.GetNode(path); if (node == null) { return DokanResult.FileNotFound; } if (creationTime.HasValue) { node.CreationTime = creationTime.Value; } if (lastAccessTime.HasValue) { node.LastAccessTime = lastAccessTime.Value; } if (lastWriteTime.HasValue) { node.LastWriteTime = lastWriteTime.Value; } return DokanResult.Success; } public NtStatus DeleteFile(string fileName, IDokanFileInfo info) { var path = FloppyStorage.NormalizePath(fileName); var node = _storage.GetNode(path); if (node == null) { return DokanResult.FileNotFound; } if (node.IsDirectory) { return DokanResult.AccessDenied; } return DokanResult.Success; } public NtStatus DeleteDirectory(string fileName, IDokanFileInfo info) { var path = FloppyStorage.NormalizePath(fileName); var node = _storage.GetNode(path); if (node == null) { return DokanResult.PathNotFound; } if (!node.IsDirectory) { return DokanResult.NotADirectory; } var children = _storage.GetChildren(path); if (children.Any()) { return DokanResult.DirectoryNotEmpty; } return DokanResult.Success; } public NtStatus MoveFile( string oldName, string newName, bool replace, IDokanFileInfo info) { var success = _storage.MoveNode(oldName, newName, replace); return success ? DokanResult.Success : DokanResult.FileNotFound; } public NtStatus SetEndOfFile(string fileName, long length, IDokanFileInfo info) { var path = FloppyStorage.NormalizePath(fileName); var node = _storage.GetNode(path); if (node == null || node.IsDirectory) { return DokanResult.FileNotFound; } var additionalBytes = length - node.Data.Length; if (additionalBytes > 0 && !_storage.HasEnoughSpace(additionalBytes)) { return DokanResult.DiskFull; } if (length != node.Data.Length) { var newData = new byte[length]; var copyLength = Math.Min(length, node.Data.Length); Array.Copy(node.Data, newData, copyLength); node.Data = newData; node.LastWriteTime = DateTime.Now; } return DokanResult.Success; } public NtStatus SetAllocationSize(string fileName, long length, IDokanFileInfo info) { var path = FloppyStorage.NormalizePath(fileName); var node = _storage.GetNode(path); if (node == null || node.IsDirectory) { return DokanResult.FileNotFound; } if (length < node.Data.Length) { var newData = new byte[length]; Array.Copy(node.Data, newData, length); node.Data = newData; node.LastWriteTime = DateTime.Now; } return DokanResult.Success; } public NtStatus LockFile( string fileName, long offset, long length, IDokanFileInfo info) { return DokanResult.Success; } public NtStatus UnlockFile( string fileName, long offset, long length, IDokanFileInfo info) { return DokanResult.Success; } public NtStatus GetDiskFreeSpace( out long freeBytesAvailable, out long totalNumberOfBytes, out long totalNumberOfFreeBytes, IDokanFileInfo info) { freeBytesAvailable = _storage.FreeSpace; totalNumberOfBytes = _storage.Capacity; totalNumberOfFreeBytes = _storage.FreeSpace; return DokanResult.Success; } public NtStatus GetVolumeInformation( out string volumeLabel, out FileSystemFeatures features, out string fileSystemName, out uint maximumComponentLength, IDokanFileInfo info) { volumeLabel = _volumeName; fileSystemName = FileSystemName; maximumComponentLength = 255; features = FileSystemFeatures.CasePreservedNames | FileSystemFeatures.UnicodeOnDisk; return DokanResult.Success; } public NtStatus GetFileSecurity( string fileName, out FileSystemSecurity? security, AccessControlSections sections, IDokanFileInfo info) { var path = FloppyStorage.NormalizePath(fileName); var node = _storage.GetNode(path); if (node == null) { security = null; return DokanResult.FileNotFound; } security = node.Security; return DokanResult.Success; } public NtStatus SetFileSecurity( string fileName, FileSystemSecurity security, AccessControlSections sections, IDokanFileInfo info) { var path = FloppyStorage.NormalizePath(fileName); var node = _storage.GetNode(path); if (node == null) { return DokanResult.FileNotFound; } node.Security = security; return DokanResult.Success; } public NtStatus Mounted(string mountPoint, IDokanFileInfo info) { Console.WriteLine($"Drive mounted at {mountPoint}"); return DokanResult.Success; } public NtStatus Unmounted(IDokanFileInfo info) { Console.WriteLine("Drive unmounted"); return DokanResult.Success; } public NtStatus FindStreams( string fileName, out IList streams, IDokanFileInfo info) { streams = []; return DokanResult.NotImplemented; } }