using DokanNet;
namespace DevDrive;
///
/// Handles passthrough operations for symlinks to real file system paths.
///
public static class SymlinkHandler
{
public static bool TryResolveSymlink(string virtualPath, FloppyStorage storage, out string? realPath)
{
realPath = null;
virtualPath = FloppyStorage.NormalizePath(virtualPath);
var node = storage.GetNode(virtualPath);
if (node?.IsSymlink == true)
{
realPath = node.SymlinkTarget;
return true;
}
var parts = virtualPath.Split('\\', StringSplitOptions.RemoveEmptyEntries);
var currentPath = "\\";
for (var i = 0; i < parts.Length; i++)
{
currentPath = currentPath == "\\" ? "\\" + parts[i] : currentPath + "\\" + parts[i];
var currentNode = storage.GetNode(currentPath);
if (currentNode?.IsSymlink == true)
{
var remainingPath = string.Join("\\", parts.Skip(i + 1));
realPath = string.IsNullOrEmpty(remainingPath)
? currentNode.SymlinkTarget
: Path.Combine(currentNode.SymlinkTarget!, remainingPath);
return true;
}
}
return false;
}
public static IList ListRealDirectory(string realPath)
{
var files = new List();
try
{
if (!Directory.Exists(realPath))
{
return files;
}
foreach (var entry in new DirectoryInfo(realPath).EnumerateFileSystemInfos())
{
files.Add(new FileInformation
{
FileName = entry.Name,
Attributes = entry.Attributes,
CreationTime = entry.CreationTime,
LastAccessTime = entry.LastAccessTime,
LastWriteTime = entry.LastWriteTime,
Length = entry is FileInfo fi ? fi.Length : 0
});
}
}
catch
{
}
return files;
}
public static bool GetRealFileInfo(string realPath, out FileInformation fileInfo)
{
fileInfo = default;
try
{
if (File.Exists(realPath))
{
var fi = new FileInfo(realPath);
fileInfo = new FileInformation
{
FileName = fi.Name,
Attributes = fi.Attributes,
CreationTime = fi.CreationTime,
LastAccessTime = fi.LastAccessTime,
LastWriteTime = fi.LastWriteTime,
Length = fi.Length
};
return true;
}
if (Directory.Exists(realPath))
{
var di = new DirectoryInfo(realPath);
fileInfo = new FileInformation
{
FileName = di.Name,
Attributes = di.Attributes,
CreationTime = di.CreationTime,
LastAccessTime = di.LastAccessTime,
LastWriteTime = di.LastWriteTime,
Length = 0
};
return true;
}
}
catch
{
}
return false;
}
public static NtStatus ReadRealFile(string realPath, byte[] buffer, out int bytesRead, long offset)
{
bytesRead = 0;
try
{
using var stream = new FileStream(realPath, System.IO.FileMode.Open, System.IO.FileAccess.Read, FileShare.ReadWrite);
stream.Seek(offset, SeekOrigin.Begin);
bytesRead = stream.Read(buffer, 0, buffer.Length);
return DokanResult.Success;
}
catch (FileNotFoundException)
{
return DokanResult.FileNotFound;
}
catch (UnauthorizedAccessException)
{
return DokanResult.AccessDenied;
}
catch
{
return DokanResult.Error;
}
}
public static NtStatus WriteRealFile(string realPath, byte[] buffer, out int bytesWritten, long offset)
{
bytesWritten = 0;
try
{
using var stream = new FileStream(realPath, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write, FileShare.ReadWrite);
stream.Seek(offset, SeekOrigin.Begin);
stream.Write(buffer, 0, buffer.Length);
bytesWritten = buffer.Length;
return DokanResult.Success;
}
catch (UnauthorizedAccessException)
{
return DokanResult.AccessDenied;
}
catch
{
return DokanResult.Error;
}
}
public static NtStatus CreateRealFileOrDirectory(string realPath, bool isDirectory, FileMode mode)
{
try
{
if (isDirectory)
{
if (mode == FileMode.CreateNew)
{
Directory.CreateDirectory(realPath);
}
return DokanResult.Success;
}
switch (mode)
{
case FileMode.CreateNew:
using (File.Create(realPath)) { }
break;
case FileMode.Create:
case FileMode.OpenOrCreate:
using (new FileStream(realPath, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.ReadWrite)) { }
break;
default:
break;
}
return DokanResult.Success;
}
catch (UnauthorizedAccessException)
{
return DokanResult.AccessDenied;
}
catch
{
return DokanResult.Error;
}
}
}