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; } } }