Move DevDrive project to repo root
This commit is contained in:
631
FloppyFileSystem.cs
Normal file
631
FloppyFileSystem.cs
Normal file
@@ -0,0 +1,631 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user