632 lines
17 KiB
C#
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;
|
|
}
|
|
}
|