using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using Mono.Fuse.NETStandard; using Mono.Unix.Native; using RomRepoMgr.Database; using RomRepoMgr.Database.Models; using SharpCompress.Compressors; using SharpCompress.Compressors.LZMA; namespace RomRepoMgr.Core.Filesystem { // TODO: Invalidate caches // TODO: Mount options // TODO: Do not show machines or romsets with no ROMs in repo // TODO: Last handle goes negative public sealed class Fuse : FileSystem { readonly ConcurrentDictionary> _directoryCache; readonly ConcurrentDictionary _fileStatHandleCache; readonly ConcurrentDictionary> _machineFilesCache; readonly ConcurrentDictionary _streamsCache; readonly Vfs _vfs; long _lastHandle; string _umountToken; public Fuse(Vfs vfs) { _directoryCache = new ConcurrentDictionary>(); _lastHandle = 0; _machineFilesCache = new ConcurrentDictionary>(); _streamsCache = new ConcurrentDictionary(); _fileStatHandleCache = new ConcurrentDictionary(); Name = "romrepombgrfs"; _vfs = vfs; } public static bool IsAvailable { get { try { IntPtr fuse = dlopen("libfuse.so.2", 2); if(fuse == IntPtr.Zero) return false; dlclose(fuse); IntPtr helper = dlopen("libMonoFuseHelper.so", 2); if(helper == IntPtr.Zero) { helper = dlopen("./libMonoFuseHelper.so", 2); if(helper == IntPtr.Zero) return false; } dlclose(helper); return true; } catch(Exception e) { return false; } } } [DllImport("libdl")] static extern IntPtr dlopen(string filename, int flags); [DllImport("libdl")] static extern int dlclose(IntPtr handle); protected override void Dispose(bool disposing) { if(!disposing) return; // TODO: Close streams manually } protected override Errno OnGetPathStatus(string path, out Stat stat) { stat = new Stat(); string[] pieces = _vfs.SplitPath(path); if(pieces.Length == 0) { stat.st_mode = FilePermissions.S_IFDIR | NativeConvert.FromOctalPermissionString("0555"); stat.st_nlink = 2; return 0; } long romSetId = _vfs.GetRomSetId(pieces[0]); if(romSetId <= 0) { if(pieces[0] != ".fuse_umount" || _umountToken == null) return Errno.ENOENT; stat = new Stat { st_mode = FilePermissions.S_IFREG | NativeConvert.FromOctalPermissionString("0444"), st_nlink = 1, st_ctime = NativeConvert.ToTimeT(DateTime.UtcNow), st_mtime = NativeConvert.ToTimeT(DateTime.UtcNow), st_blksize = 0, st_blocks = 0, st_ino = 0, st_size = 0 }; return 0; } RomSet romSet = _vfs.GetRomSet(romSetId); if(romSet == null) return Errno.ENOENT; if(pieces.Length == 1) { stat.st_mode = FilePermissions.S_IFDIR | NativeConvert.FromOctalPermissionString("0555"); stat.st_nlink = 2; stat.st_ctime = NativeConvert.ToTimeT(romSet.CreatedOn.ToUniversalTime()); stat.st_mtime = NativeConvert.ToTimeT(romSet.UpdatedOn.ToUniversalTime()); return 0; } CachedMachine machine = _vfs.GetMachine(romSetId, pieces[1]); if(machine == null) return Errno.ENOENT; if(pieces.Length == 2) { stat = new Stat { st_mode = FilePermissions.S_IFDIR | NativeConvert.FromOctalPermissionString("0555"), st_nlink = 2, st_ctime = NativeConvert.ToTimeT(machine.CreationDate.ToUniversalTime()), st_mtime = NativeConvert.ToTimeT(machine.ModificationDate.ToUniversalTime()) }; return 0; } _machineFilesCache.TryGetValue(machine.Id, out ConcurrentDictionary cachedMachineFiles); if(cachedMachineFiles == null) { using var ctx = Context.Create(Settings.Settings.Current.DatabasePath); cachedMachineFiles = new ConcurrentDictionary(); foreach(FileByMachine machineFile in ctx.FilesByMachines.Where(fbm => fbm.Machine.Id == machine.Id && fbm.File.IsInRepo)) { var cachedFile = new CachedFile { Id = machineFile.File.Id, Crc32 = machineFile.File.Crc32, Md5 = machineFile.File.Md5, Sha1 = machineFile.File.Sha1, Sha256 = machineFile.File.Sha256, Sha384 = machineFile.File.Sha384, Sha512 = machineFile.File.Sha512, Size = machineFile.File.Size, CreatedOn = machineFile.File.CreatedOn, UpdatedOn = machineFile.File.UpdatedOn }; cachedMachineFiles[machineFile.Name] = cachedFile; } _machineFilesCache[machine.Id] = cachedMachineFiles; } if(!cachedMachineFiles.TryGetValue(pieces[2], out CachedFile file)) return Errno.ENOENT; if(pieces.Length == 3) { stat = new Stat { st_mode = FilePermissions.S_IFREG | NativeConvert.FromOctalPermissionString("0444"), st_nlink = 1, st_ctime = NativeConvert.ToTimeT(file.CreatedOn.ToUniversalTime()), st_mtime = NativeConvert.ToTimeT(file.UpdatedOn.ToUniversalTime()), st_blksize = 512, st_blocks = (long)(file.Size / 512), st_ino = file.Id, st_size = (long)file.Size }; return 0; } return Errno.ENOSYS; } protected override Errno OnReadSymbolicLink(string link, out string target) { target = null; return Errno.EOPNOTSUPP; } protected override Errno OnCreateSpecialFile(string file, FilePermissions perms, ulong dev) => Errno.EROFS; protected override Errno OnCreateDirectory(string directory, FilePermissions mode) => Errno.EROFS; protected override Errno OnRemoveFile(string file) => Errno.EROFS; protected override Errno OnRemoveDirectory(string directory) => Errno.EROFS; protected override Errno OnCreateSymbolicLink(string target, string link) => Errno.EROFS; protected override Errno OnRenamePath(string oldPath, string newPath) => Errno.EROFS; protected override Errno OnCreateHardLink(string oldPath, string link) => Errno.EROFS; protected override Errno OnChangePathPermissions(string path, FilePermissions mode) => Errno.EROFS; protected override Errno OnChangePathOwner(string path, long owner, long group) => Errno.EROFS; protected override Errno OnTruncateFile(string file, long length) => Errno.EROFS; protected override Errno OnChangePathTimes(string path, ref Utimbuf buf) => Errno.EROFS; protected override Errno OnOpenHandle(string path, OpenedPathInfo info) { string[] pieces = _vfs.SplitPath(path); if(pieces.Length == 0) return Errno.EISDIR; long romSetId = _vfs.GetRomSetId(pieces[0]); if(romSetId <= 0) return Errno.ENOENT; RomSet romSet = _vfs.GetRomSet(romSetId); if(romSet == null) return Errno.ENOENT; if(pieces.Length == 1) return Errno.EISDIR; CachedMachine machine = _vfs.GetMachine(romSetId, pieces[1]); if(machine == null) return Errno.ENOENT; if(pieces.Length == 2) return Errno.EISDIR; _machineFilesCache.TryGetValue(machine.Id, out ConcurrentDictionary cachedMachineFiles); if(cachedMachineFiles == null) { using var ctx = Context.Create(Settings.Settings.Current.DatabasePath); cachedMachineFiles = new ConcurrentDictionary(); foreach(FileByMachine machineFile in ctx.FilesByMachines.Where(fbm => fbm.Machine.Id == machine.Id && fbm.File.IsInRepo)) { var cachedFile = new CachedFile { Id = machineFile.File.Id, Crc32 = machineFile.File.Crc32, Md5 = machineFile.File.Md5, Sha1 = machineFile.File.Sha1, Sha256 = machineFile.File.Sha256, Sha384 = machineFile.File.Sha384, Sha512 = machineFile.File.Sha512, Size = machineFile.File.Size, CreatedOn = machineFile.File.CreatedOn, UpdatedOn = machineFile.File.UpdatedOn }; cachedMachineFiles[machineFile.Name] = cachedFile; } _machineFilesCache[machine.Id] = cachedMachineFiles; } if(!cachedMachineFiles.TryGetValue(pieces[2], out CachedFile file)) return Errno.ENOENT; if(pieces.Length > 3) return Errno.ENOSYS; if(file.Sha384 == null) return Errno.ENOENT; if(info.OpenAccess.HasFlag(OpenFlags.O_APPEND) || info.OpenAccess.HasFlag(OpenFlags.O_CREAT) || info.OpenAccess.HasFlag(OpenFlags.O_EXCL) || info.OpenAccess.HasFlag(OpenFlags.O_TRUNC)) return Errno.EROFS; byte[] sha384Bytes = new byte[48]; string sha384 = file.Sha384; for(int i = 0; i < 48; i++) { if(sha384[i * 2] >= 0x30 && sha384[i * 2] <= 0x39) sha384Bytes[i] = (byte)((sha384[i * 2] - 0x30) * 0x10); else if(sha384[i * 2] >= 0x41 && sha384[i * 2] <= 0x46) sha384Bytes[i] = (byte)((sha384[i * 2] - 0x37) * 0x10); else if(sha384[i * 2] >= 0x61 && sha384[i * 2] <= 0x66) sha384Bytes[i] = (byte)((sha384[i * 2] - 0x57) * 0x10); if(sha384[(i * 2) + 1] >= 0x30 && sha384[(i * 2) + 1] <= 0x39) sha384Bytes[i] += (byte)(sha384[(i * 2) + 1] - 0x30); else if(sha384[(i * 2) + 1] >= 0x41 && sha384[(i * 2) + 1] <= 0x46) sha384Bytes[i] += (byte)(sha384[(i * 2) + 1] - 0x37); else if(sha384[(i * 2) + 1] >= 0x61 && sha384[(i * 2) + 1] <= 0x66) sha384Bytes[i] += (byte)(sha384[(i * 2) + 1] - 0x57); } string sha384B32 = Base32.ToBase32String(sha384Bytes); string repoPath = Path.Combine(Settings.Settings.Current.RepositoryPath, "files", sha384B32[0].ToString(), sha384B32[1].ToString(), sha384B32[2].ToString(), sha384B32[3].ToString(), sha384B32[4].ToString(), sha384B32 + ".lz"); if(!File.Exists(repoPath)) return Errno.ENOENT; _lastHandle++; info.Handle = new IntPtr(_lastHandle); _streamsCache[_lastHandle] = Stream.Synchronized(new ForcedSeekStream((long)file.Size, new FileStream(repoPath, FileMode.Open, FileAccess.Read), CompressionMode.Decompress)); _fileStatHandleCache[_lastHandle] = new Stat { st_mode = FilePermissions.S_IFREG | NativeConvert.FromOctalPermissionString("0444"), st_nlink = 1, st_ctime = NativeConvert.ToTimeT(file.CreatedOn.ToUniversalTime()), st_mtime = NativeConvert.ToTimeT(file.UpdatedOn.ToUniversalTime()), st_blksize = 512, st_blocks = (long)(file.Size / 512), st_ino = file.Id, st_size = (long)file.Size }; return 0; } protected override Errno OnReadHandle(string file, OpenedPathInfo info, byte[] buf, long offset, out int bytesWritten) { bytesWritten = 0; if(!_streamsCache.TryGetValue(info.Handle.ToInt64(), out Stream fileStream)) return Errno.EBADF; fileStream.Position = offset; bytesWritten = fileStream.Read(buf, 0, buf.Length); return 0; } protected override Errno OnWriteHandle(string file, OpenedPathInfo info, byte[] buf, long offset, out int bytesRead) { bytesRead = 0; return Errno.EROFS; } protected override Errno OnGetFileSystemStatus(string path, out Statvfs buf) { _vfs.GetInfo(out ulong files, out ulong totalSize); buf = new Statvfs { f_bsize = 512, f_frsize = 512, f_blocks = totalSize / 512, f_bavail = 0, f_files = files, f_ffree = 0, f_favail = 0, f_fsid = 0xFFFFFFFF, f_flag = 0, f_namemax = 255 }; return 0; } protected override Errno OnFlushHandle(string file, OpenedPathInfo info) => Errno.ENOSYS; protected override Errno OnReleaseHandle(string file, OpenedPathInfo info) { if(!_streamsCache.TryGetValue(info.Handle.ToInt64(), out Stream fileStream)) return Errno.EBADF; fileStream.Close(); _streamsCache.TryRemove(info.Handle.ToInt64(), out _); _fileStatHandleCache.TryRemove(info.Handle.ToInt64(), out _); return 0; } protected override Errno OnSynchronizeHandle(string file, OpenedPathInfo info, bool onlyUserData) => Errno.EOPNOTSUPP; protected override Errno OnSetPathExtendedAttribute(string path, string name, byte[] value, XattrFlags flags) { if(_umountToken == null) return Errno.EROFS; if(path != "/.fuse_umount") return Errno.EROFS; if(name != _umountToken) return Errno.EROFS; if(value?.Length != 0) return Errno.EROFS; _umountToken = null; Stop(); return 0; } protected override Errno OnGetPathExtendedAttribute(string path, string name, byte[] value, out int bytesWritten) { bytesWritten = 0; string[] pieces = _vfs.SplitPath(path); if(pieces.Length == 0) return Errno.ENODATA; long romSetId = _vfs.GetRomSetId(pieces[0]); if(romSetId <= 0) return Errno.ENOENT; RomSet romSet = _vfs.GetRomSet(romSetId); if(romSet == null) return Errno.ENOENT; if(pieces.Length == 1) return Errno.ENODATA; CachedMachine machine = _vfs.GetMachine(romSetId, pieces[1]); if(machine == null) return Errno.ENOENT; if(pieces.Length == 2) return Errno.ENODATA; _machineFilesCache.TryGetValue(machine.Id, out ConcurrentDictionary cachedMachineFiles); if(cachedMachineFiles == null) { using var ctx = Context.Create(Settings.Settings.Current.DatabasePath); cachedMachineFiles = new ConcurrentDictionary(); foreach(FileByMachine machineFile in ctx.FilesByMachines.Where(fbm => fbm.Machine.Id == machine.Id && fbm.File.IsInRepo)) { var cachedFile = new CachedFile { Id = machineFile.File.Id, Crc32 = machineFile.File.Crc32, Md5 = machineFile.File.Md5, Sha1 = machineFile.File.Sha1, Sha256 = machineFile.File.Sha256, Sha384 = machineFile.File.Sha384, Sha512 = machineFile.File.Sha512, Size = machineFile.File.Size, CreatedOn = machineFile.File.CreatedOn, UpdatedOn = machineFile.File.UpdatedOn }; cachedMachineFiles[machineFile.Name] = cachedFile; } _machineFilesCache[machine.Id] = cachedMachineFiles; } if(!cachedMachineFiles.TryGetValue(pieces[2], out CachedFile file)) return Errno.ENOENT; if(pieces.Length > 3) return Errno.ENOSYS; string hash = null; switch(name) { case "user.crc32": hash = file.Crc32; break; case "user.md5": hash = file.Md5; break; case "user.sha1": hash = file.Sha1; break; case "user.sha256": hash = file.Sha256; break; case "user.sha384": hash = file.Sha384; break; case "user.sha512": hash = file.Sha512; break; } if(hash == null) return Errno.ENODATA; byte[] xattr = new byte[hash.Length / 2]; for(int i = 0; i < xattr.Length; i++) { if(hash[i * 2] >= 0x30 && hash[i * 2] <= 0x39) xattr[i] = (byte)((hash[i * 2] - 0x30) * 0x10); else if(hash[i * 2] >= 0x41 && hash[i * 2] <= 0x46) xattr[i] = (byte)((hash[i * 2] - 0x37) * 0x10); else if(hash[i * 2] >= 0x61 && hash[i * 2] <= 0x66) xattr[i] = (byte)((hash[i * 2] - 0x57) * 0x10); if(hash[(i * 2) + 1] >= 0x30 && hash[(i * 2) + 1] <= 0x39) xattr[i] += (byte)(hash[(i * 2) + 1] - 0x30); else if(hash[(i * 2) + 1] >= 0x41 && hash[(i * 2) + 1] <= 0x46) xattr[i] += (byte)(hash[(i * 2) + 1] - 0x37); else if(hash[(i * 2) + 1] >= 0x61 && hash[(i * 2) + 1] <= 0x66) xattr[i] += (byte)(hash[(i * 2) + 1] - 0x57); } if(value == null) { bytesWritten = xattr.Length; return 0; } int maxSize = value.Length > xattr.Length ? xattr.Length : value.Length; Array.Copy(xattr, 0, value, 0, maxSize); bytesWritten = maxSize; return 0; } protected override Errno OnListPathExtendedAttributes(string path, out string[] names) { names = null; string[] pieces = _vfs.SplitPath(path); if(pieces.Length == 0) return 0; long romSetId = _vfs.GetRomSetId(pieces[0]); if(romSetId <= 0) return Errno.ENOENT; RomSet romSet = _vfs.GetRomSet(romSetId); if(romSet == null) return Errno.ENOENT; if(pieces.Length == 1) return 0; CachedMachine machine = _vfs.GetMachine(romSetId, pieces[1]); if(machine == null) return Errno.ENOENT; if(pieces.Length == 2) return 0; _machineFilesCache.TryGetValue(machine.Id, out ConcurrentDictionary cachedMachineFiles); if(cachedMachineFiles == null) { using var ctx = Context.Create(Settings.Settings.Current.DatabasePath); cachedMachineFiles = new ConcurrentDictionary(); foreach(FileByMachine machineFile in ctx.FilesByMachines.Where(fbm => fbm.Machine.Id == machine.Id && fbm.File.IsInRepo)) { var cachedFile = new CachedFile { Id = machineFile.File.Id, Crc32 = machineFile.File.Crc32, Md5 = machineFile.File.Md5, Sha1 = machineFile.File.Sha1, Sha256 = machineFile.File.Sha256, Sha384 = machineFile.File.Sha384, Sha512 = machineFile.File.Sha512, Size = machineFile.File.Size, CreatedOn = machineFile.File.CreatedOn, UpdatedOn = machineFile.File.UpdatedOn }; cachedMachineFiles[machineFile.Name] = cachedFile; } _machineFilesCache[machine.Id] = cachedMachineFiles; } if(!cachedMachineFiles.TryGetValue(pieces[2], out CachedFile file)) return Errno.ENOENT; if(pieces.Length > 3) return Errno.ENOSYS; List xattrs = new List(); if(file.Crc32 != null) xattrs.Add("user.crc32"); if(file.Md5 != null) xattrs.Add("user.md5"); if(file.Sha1 != null) xattrs.Add("user.sha1"); if(file.Sha256 != null) xattrs.Add("user.sha256"); if(file.Sha384 != null) xattrs.Add("user.sha384"); if(file.Sha512 != null) xattrs.Add("user.sha512"); names = xattrs.ToArray(); return 0; } protected override Errno OnRemovePathExtendedAttribute(string path, string name) => Errno.EROFS; protected override Errno OnOpenDirectory(string directory, OpenedPathInfo info) { try { if(directory == "/") { using var ctx = Context.Create(Settings.Settings.Current.DatabasePath); List entries = new List { new DirectoryEntry("."), new DirectoryEntry("..") }; ConcurrentDictionary rootCache = new ConcurrentDictionary(); foreach(RomSet set in ctx.RomSets) { string name = set.Name.Replace('/', '∕'); if(entries.Any(e => e.Name == name)) name = Path.GetFileNameWithoutExtension(set.Filename)?.Replace('/', '∕'); if(entries.Any(e => e.Name == name) || name == null) name = Path.GetFileNameWithoutExtension(set.Sha384); if(name == null) continue; entries.Add(new DirectoryEntry(name)); rootCache[name] = set.Id; } _lastHandle++; info.Handle = new IntPtr(_lastHandle); _directoryCache[_lastHandle] = entries; return 0; } string[] pieces = directory.Split("/", StringSplitOptions.RemoveEmptyEntries); if(pieces.Length == 0) return Errno.ENOENT; long romSetId = _vfs.GetRomSetId(pieces[0]); if(romSetId <= 0) return Errno.ENOENT; RomSet romSet = _vfs.GetRomSet(romSetId); if(romSet == null) return Errno.ENOENT; ConcurrentDictionary machines = _vfs.GetMachinesFromRomSet(romSetId); if(pieces.Length == 1) { List entries = new List { new DirectoryEntry("."), new DirectoryEntry("..") }; entries.AddRange(machines.Select(mach => new DirectoryEntry(mach.Key))); _lastHandle++; info.Handle = new IntPtr(_lastHandle); _directoryCache[_lastHandle] = entries; return 0; } CachedMachine machine = _vfs.GetMachine(romSetId, pieces[1]); if(machine == null) return Errno.ENOENT; _machineFilesCache.TryGetValue(machine.Id, out ConcurrentDictionary cachedMachineFiles); if(cachedMachineFiles == null) { using var ctx = Context.Create(Settings.Settings.Current.DatabasePath); cachedMachineFiles = new ConcurrentDictionary(); foreach(FileByMachine machineFile in ctx.FilesByMachines.Where(fbm => fbm.Machine.Id == machine.Id && fbm.File.IsInRepo)) { var file = new CachedFile { Id = machineFile.File.Id, Crc32 = machineFile.File.Crc32, Md5 = machineFile.File.Md5, Sha1 = machineFile.File.Sha1, Sha256 = machineFile.File.Sha256, Sha384 = machineFile.File.Sha384, Sha512 = machineFile.File.Sha512, Size = machineFile.File.Size, CreatedOn = machineFile.File.CreatedOn, UpdatedOn = machineFile.File.UpdatedOn }; cachedMachineFiles[machineFile.Name] = file; } _machineFilesCache[machine.Id] = cachedMachineFiles; } if(pieces.Length == 2) { List entries = new List { new DirectoryEntry("."), new DirectoryEntry("..") }; entries.AddRange(cachedMachineFiles.Select(file => new DirectoryEntry(file.Key))); _lastHandle++; info.Handle = new IntPtr(_lastHandle); _directoryCache[_lastHandle] = entries; return 0; } // TODO: DATs with subfolders as game name if(pieces.Length >= 3) return Errno.EISDIR; return Errno.ENOENT; } catch(Exception e) { Console.WriteLine(e); throw; } } protected override Errno OnReadDirectory(string directory, OpenedPathInfo info, out IEnumerable paths) { paths = null; if(!_directoryCache.TryGetValue(info.Handle.ToInt64(), out List cache)) return Errno.EBADF; paths = cache; return 0; } protected override Errno OnReleaseDirectory(string directory, OpenedPathInfo info) { if(!_directoryCache.TryGetValue(info.Handle.ToInt64(), out _)) return Errno.EBADF; _directoryCache.Remove(info.Handle.ToInt64(), out _); return 0; } protected override Errno OnSynchronizeDirectory(string directory, OpenedPathInfo info, bool onlyUserData) => Errno.ENOSYS; protected override Errno OnAccessPath(string path, AccessModes mode) { string[] pieces = _vfs.SplitPath(path); if(pieces.Length == 0) return mode.HasFlag(AccessModes.W_OK) ? Errno.EROFS : 0; long romSetId = _vfs.GetRomSetId(pieces[0]); if(romSetId <= 0) return Errno.ENOENT; RomSet romSet = _vfs.GetRomSet(romSetId); if(romSet == null) return Errno.ENOENT; if(pieces.Length == 1) return mode.HasFlag(AccessModes.W_OK) ? Errno.EROFS : 0; CachedMachine machine = _vfs.GetMachine(romSetId, pieces[1]); if(machine == null) return Errno.ENOENT; if(pieces.Length == 2) return mode.HasFlag(AccessModes.W_OK) ? Errno.EROFS : 0; _machineFilesCache.TryGetValue(machine.Id, out ConcurrentDictionary cachedMachineFiles); if(cachedMachineFiles == null) { using var ctx = Context.Create(Settings.Settings.Current.DatabasePath); cachedMachineFiles = new ConcurrentDictionary(); foreach(FileByMachine machineFile in ctx.FilesByMachines.Where(fbm => fbm.Machine.Id == machine.Id && fbm.File.IsInRepo)) { var cachedFile = new CachedFile { Id = machineFile.File.Id, Crc32 = machineFile.File.Crc32, Md5 = machineFile.File.Md5, Sha1 = machineFile.File.Sha1, Sha256 = machineFile.File.Sha256, Sha384 = machineFile.File.Sha384, Sha512 = machineFile.File.Sha512, Size = machineFile.File.Size, CreatedOn = machineFile.File.CreatedOn, UpdatedOn = machineFile.File.UpdatedOn }; cachedMachineFiles[machineFile.Name] = cachedFile; } _machineFilesCache[machine.Id] = cachedMachineFiles; } if(!cachedMachineFiles.TryGetValue(pieces[2], out CachedFile _)) return Errno.ENOENT; if(pieces.Length > 3) return Errno.ENOSYS; return mode.HasFlag(AccessModes.W_OK) ? Errno.EROFS : 0; } protected override Errno OnCreateHandle(string file, OpenedPathInfo info, FilePermissions mode) => Errno.EROFS; protected override Errno OnTruncateHandle(string file, OpenedPathInfo info, long length) => Errno.EROFS; protected override Errno OnGetHandleStatus(string file, OpenedPathInfo info, out Stat buf) { buf = new Stat(); if(!_fileStatHandleCache.TryGetValue(info.Handle.ToInt64(), out Stat fileStat)) return Errno.EBADF; buf = fileStat; return 0; } protected override Errno OnLockHandle(string file, OpenedPathInfo info, FcntlCommand cmd, ref Flock @lock) => Errno.EOPNOTSUPP; protected override Errno OnMapPathLogicalToPhysicalIndex(string path, ulong logical, out ulong physical) { physical = ulong.MaxValue; return Errno.EOPNOTSUPP; } [DllImport("libc", SetLastError = true)] static extern int setxattr(string path, string name, IntPtr value, long size, int flags); public void Umount() { var rnd = new Random(); byte[] token = new byte[64]; rnd.NextBytes(token); _umountToken = Base32.ToBase32String(token); setxattr(Path.Combine(MountPoint, ".fuse_umount"), _umountToken, IntPtr.Zero, 0, 0); } sealed class CachedFile { public ulong Id { get; set; } public ulong Size { get; set; } public string Crc32 { get; set; } public string Md5 { get; set; } public string Sha1 { get; set; } public string Sha256 { get; set; } public string Sha384 { get; set; } public string Sha512 { get; set; } public DateTime CreatedOn { get; set; } public DateTime UpdatedOn { get; set; } } } }