Files
DevDrive/FloppyFileSystem.cs

632 lines
17 KiB
C#

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<string, string>? 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<FileInformation> 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<FileInformation> 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<FileInformation> streams,
IDokanFileInfo info)
{
streams = [];
return DokanResult.NotImplemented;
}
}