From c0b1a7729c45ad0430ca2d51547a20e285ab12d2 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Fri, 4 Sep 2020 20:38:43 +0100 Subject: [PATCH] Show disks in virtual filesystem. --- RomRepoMgr.Core/Filesystem/Fuse.cs | 251 +++++++++++++++++++-------- RomRepoMgr.Core/Filesystem/Vfs.cs | 153 ++++++++++++++++ RomRepoMgr.Core/Filesystem/Winfsp.cs | 114 ++++++++++-- 3 files changed, 432 insertions(+), 86 deletions(-) diff --git a/RomRepoMgr.Core/Filesystem/Fuse.cs b/RomRepoMgr.Core/Filesystem/Fuse.cs index 0ba0626..c1bf86c 100644 --- a/RomRepoMgr.Core/Filesystem/Fuse.cs +++ b/RomRepoMgr.Core/Filesystem/Fuse.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Runtime.InteropServices; using Mono.Fuse.NETStandard; using Mono.Unix.Native; -using RomRepoMgr.Database; using RomRepoMgr.Database.Models; namespace RomRepoMgr.Core.Filesystem @@ -141,11 +140,11 @@ namespace RomRepoMgr.Core.Filesystem CachedFile file = _vfs.GetFile(machine.Id, pieces[2]); - if(file == null) - return Errno.ENOENT; - - if(pieces.Length == 3) + if(file != null) { + if(pieces.Length != 3) + return Errno.ENOSYS; + stat = new Stat { st_mode = FilePermissions.S_IFREG | NativeConvert.FromOctalPermissionString("0444"), @@ -161,7 +160,27 @@ namespace RomRepoMgr.Core.Filesystem return 0; } - return Errno.ENOSYS; + CachedDisk disk = _vfs.GetDisk(machine.Id, pieces[2]); + + if(disk == null) + return Errno.ENOENT; + + if(pieces.Length != 3) + return Errno.ENOSYS; + + stat = new Stat + { + st_mode = FilePermissions.S_IFREG | NativeConvert.FromOctalPermissionString("0444"), + st_nlink = 1, + st_ctime = NativeConvert.ToTimeT(disk.CreatedOn.ToUniversalTime()), + st_mtime = NativeConvert.ToTimeT(disk.UpdatedOn.ToUniversalTime()), + st_blksize = 512, + st_blocks = (long)(disk.Size / 512), + st_ino = disk.Id, + st_size = (long)disk.Size + }; + + return 0; } protected override Errno OnReadSymbolicLink(string link, out string target) @@ -221,41 +240,80 @@ namespace RomRepoMgr.Core.Filesystem if(pieces.Length == 2) return Errno.EISDIR; + long handle = 0; + Stat stat; + CachedFile file = _vfs.GetFile(machine.Id, pieces[2]); - if(file == null) - return Errno.ENOENT; + if(file != null) + { + if(pieces.Length > 3) + return Errno.ENOSYS; - if(pieces.Length > 3) - return Errno.ENOSYS; + if(file.Sha384 == null) + return Errno.ENOENT; - 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; - 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; + handle = _vfs.Open(file.Sha384, (long)file.Size); - long handle = _vfs.Open(file.Sha384, (long)file.Size); + 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 + }; + } + else + { + CachedDisk disk = _vfs.GetDisk(machine.Id, pieces[2]); + + if(disk == null) + return Errno.ENOENT; + + if(pieces.Length > 3) + return Errno.ENOSYS; + + if(disk.Sha1 == null && + disk.Md5 == 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; + + handle = _vfs.OpenDisk(disk.Sha1, disk.Md5); + + stat = new Stat + { + st_mode = FilePermissions.S_IFREG | NativeConvert.FromOctalPermissionString("0444"), + st_nlink = 1, + st_ctime = NativeConvert.ToTimeT(disk.CreatedOn.ToUniversalTime()), + st_mtime = NativeConvert.ToTimeT(disk.UpdatedOn.ToUniversalTime()), + st_blksize = 512, + st_blocks = (long)(disk.Size / 512), + st_ino = disk.Id, + st_size = (long)disk.Size + }; + } if(handle <= 0) return Errno.ENOENT; info.Handle = new IntPtr(handle); - _fileStatHandleCache[handle] = 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 - }; + _fileStatHandleCache[handle] = stat; return 0; } @@ -370,40 +428,59 @@ namespace RomRepoMgr.Core.Filesystem CachedFile file = _vfs.GetFile(machine.Id, pieces[2]); - if(file == null) - return Errno.ENOENT; - - if(pieces.Length > 3) - return Errno.ENOSYS; - string hash = null; - switch(name) + if(file != null) { - case "user.crc32": - hash = file.Crc32; + if(pieces.Length > 3) + return Errno.ENOSYS; - break; - case "user.md5": - hash = file.Md5; + switch(name) + { + case "user.crc32": + hash = file.Crc32; - break; - case "user.sha1": - hash = file.Sha1; + break; + case "user.md5": + hash = file.Md5; - break; - case "user.sha256": - hash = file.Sha256; + break; + case "user.sha1": + hash = file.Sha1; - break; - case "user.sha384": - hash = file.Sha384; + break; + case "user.sha256": + hash = file.Sha256; - break; - case "user.sha512": - hash = file.Sha512; + break; + case "user.sha384": + hash = file.Sha384; - break; + break; + case "user.sha512": + hash = file.Sha512; + + break; + } + } + else + { + CachedDisk disk = _vfs.GetDisk(machine.Id, pieces[2]); + + if(disk == null) + return Errno.ENOENT; + + switch(name) + { + case "user.md5": + hash = disk.Md5; + + break; + case "user.sha1": + hash = disk.Sha1; + + break; + } } if(hash == null) @@ -479,34 +556,52 @@ namespace RomRepoMgr.Core.Filesystem if(pieces.Length == 2) return 0; + List xattrs = new List(); + CachedFile file = _vfs.GetFile(machine.Id, pieces[2]); - if(file == null) + if(file != null) + { + if(pieces.Length > 3) + return Errno.ENOSYS; + + 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; + } + + CachedDisk disk = _vfs.GetDisk(machine.Id, pieces[2]); + + if(disk == null) 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) + if(disk.Md5 != null) xattrs.Add("user.md5"); - if(file.Sha1 != null) + if(disk.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; @@ -577,6 +672,7 @@ namespace RomRepoMgr.Core.Filesystem return Errno.ENOENT; ConcurrentDictionary cachedMachineFiles = _vfs.GetFilesFromMachine(machine.Id); + ConcurrentDictionary cachedMachineDisks = _vfs.GetDisksFromMachine(machine.Id); if(pieces.Length == 2) { @@ -587,6 +683,7 @@ namespace RomRepoMgr.Core.Filesystem }; entries.AddRange(cachedMachineFiles.Select(file => new DirectoryEntry(file.Key))); + entries.AddRange(cachedMachineDisks.Select(disk => new DirectoryEntry(disk.Key + ".chd"))); _lastHandle++; info.Handle = new IntPtr(_lastHandle); @@ -666,7 +763,17 @@ namespace RomRepoMgr.Core.Filesystem CachedFile file = _vfs.GetFile(machine.Id, pieces[2]); - if(file == null) + if(file != null) + { + if(pieces.Length > 3) + return Errno.ENOSYS; + + return mode.HasFlag(AccessModes.W_OK) ? Errno.EROFS : 0; + } + + CachedDisk disk = _vfs.GetDisk(machine.Id, pieces[2]); + + if(disk == null) return Errno.ENOENT; if(pieces.Length > 3) diff --git a/RomRepoMgr.Core/Filesystem/Vfs.cs b/RomRepoMgr.Core/Filesystem/Vfs.cs index 128d0e9..a2110f2 100644 --- a/RomRepoMgr.Core/Filesystem/Vfs.cs +++ b/RomRepoMgr.Core/Filesystem/Vfs.cs @@ -18,6 +18,7 @@ namespace RomRepoMgr.Core.Filesystem // TODO: Do not show machines or romsets with no ROMs in repo public class Vfs : IDisposable { + readonly ConcurrentDictionary> _machineDisksCache; readonly ConcurrentDictionary> _machineFilesCache; readonly ConcurrentDictionary> _machinesStatCache; readonly ConcurrentDictionary _romSetsCache; @@ -33,6 +34,7 @@ namespace RomRepoMgr.Core.Filesystem _romSetsCache = new ConcurrentDictionary(); _machinesStatCache = new ConcurrentDictionary>(); _machineFilesCache = new ConcurrentDictionary>(); + _machineDisksCache = new ConcurrentDictionary>(); _streamsCache = new ConcurrentDictionary(); _lastHandle = 0; } @@ -251,6 +253,39 @@ namespace RomRepoMgr.Core.Filesystem return cachedMachineFiles; } + internal ConcurrentDictionary GetDisksFromMachine(ulong id) + { + _machineDisksCache.TryGetValue(id, out ConcurrentDictionary cachedMachineDisks); + + if(cachedMachineDisks != null) + return cachedMachineDisks; + + using var ctx = Context.Create(Settings.Settings.Current.DatabasePath); + + cachedMachineDisks = new ConcurrentDictionary(); + + foreach(DiskByMachine machineDisk in ctx.DisksByMachines.Where(dbm => dbm.Machine.Id == id && + dbm.Disk.IsInRepo && + dbm.Disk.Size != null)) + { + var cachedDisk = new CachedDisk + { + Id = machineDisk.Disk.Id, + Md5 = machineDisk.Disk.Md5, + Sha1 = machineDisk.Disk.Sha1, + Size = machineDisk.Disk.Size ?? 0, + CreatedOn = machineDisk.Disk.CreatedOn, + UpdatedOn = machineDisk.Disk.UpdatedOn + }; + + cachedMachineDisks[machineDisk.Name] = cachedDisk; + } + + _machineDisksCache[id] = cachedMachineDisks; + + return cachedMachineDisks; + } + internal CachedFile GetFile(ulong machineId, string name) { ConcurrentDictionary cachedFiles = GetFilesFromMachine(machineId); @@ -262,6 +297,20 @@ namespace RomRepoMgr.Core.Filesystem return file; } + internal CachedDisk GetDisk(ulong machineId, string name) + { + if(name.EndsWith(".chd", StringComparison.OrdinalIgnoreCase)) + name = name.Substring(0, name.Length - 4); + + ConcurrentDictionary cachedDisks = GetDisksFromMachine(machineId); + + if(cachedDisks == null || + !cachedDisks.TryGetValue(name, out CachedDisk disk)) + return null; + + return disk; + } + internal long Open(string sha384, long fileSize) { byte[] sha384Bytes = new byte[48]; @@ -338,6 +387,100 @@ namespace RomRepoMgr.Core.Filesystem return _rootDirectoryCache.Keys.ToArray(); } + + public long OpenDisk(string sha1, string md5) + { + if(sha1 == null && + md5 == null) + return -1; + + string repoPath = null; + string md5Path = null; + string sha1Path = null; + + if(sha1 != null) + { + byte[] sha1Bytes = new byte[20]; + + for(int i = 0; i < 20; i++) + { + if(sha1[i * 2] >= 0x30 && + sha1[i * 2] <= 0x39) + sha1Bytes[i] = (byte)((sha1[i * 2] - 0x30) * 0x10); + else if(sha1[i * 2] >= 0x41 && + sha1[i * 2] <= 0x46) + sha1Bytes[i] = (byte)((sha1[i * 2] - 0x37) * 0x10); + else if(sha1[i * 2] >= 0x61 && + sha1[i * 2] <= 0x66) + sha1Bytes[i] = (byte)((sha1[i * 2] - 0x57) * 0x10); + + if(sha1[(i * 2) + 1] >= 0x30 && + sha1[(i * 2) + 1] <= 0x39) + sha1Bytes[i] += (byte)(sha1[(i * 2) + 1] - 0x30); + else if(sha1[(i * 2) + 1] >= 0x41 && + sha1[(i * 2) + 1] <= 0x46) + sha1Bytes[i] += (byte)(sha1[(i * 2) + 1] - 0x37); + else if(sha1[(i * 2) + 1] >= 0x61 && + sha1[(i * 2) + 1] <= 0x66) + sha1Bytes[i] += (byte)(sha1[(i * 2) + 1] - 0x57); + } + + string sha1B32 = Base32.ToBase32String(sha1Bytes); + + sha1Path = Path.Combine(Settings.Settings.Current.RepositoryPath, "chd", "sha1", sha1B32[0].ToString(), + sha1B32[1].ToString(), sha1B32[2].ToString(), sha1B32[3].ToString(), + sha1B32[4].ToString(), sha1B32 + ".chd"); + } + + if(md5 != null) + { + byte[] md5Bytes = new byte[16]; + + for(int i = 0; i < 16; i++) + { + if(md5[i * 2] >= 0x30 && + md5[i * 2] <= 0x39) + md5Bytes[i] = (byte)((md5[i * 2] - 0x30) * 0x10); + else if(md5[i * 2] >= 0x41 && + md5[i * 2] <= 0x46) + md5Bytes[i] = (byte)((md5[i * 2] - 0x37) * 0x10); + else if(md5[i * 2] >= 0x61 && + md5[i * 2] <= 0x66) + md5Bytes[i] = (byte)((md5[i * 2] - 0x57) * 0x10); + + if(md5[(i * 2) + 1] >= 0x30 && + md5[(i * 2) + 1] <= 0x39) + md5Bytes[i] += (byte)(md5[(i * 2) + 1] - 0x30); + else if(md5[(i * 2) + 1] >= 0x41 && + md5[(i * 2) + 1] <= 0x46) + md5Bytes[i] += (byte)(md5[(i * 2) + 1] - 0x37); + else if(md5[(i * 2) + 1] >= 0x61 && + md5[(i * 2) + 1] <= 0x66) + md5Bytes[i] += (byte)(md5[(i * 2) + 1] - 0x57); + } + + string md5B32 = Base32.ToBase32String(md5Bytes); + + md5Path = Path.Combine(Settings.Settings.Current.RepositoryPath, "chd", "md5", md5B32[0].ToString(), + md5B32[1].ToString(), md5B32[2].ToString(), md5B32[3].ToString(), + md5B32[4].ToString(), md5B32 + ".chd"); + } + + if(File.Exists(sha1Path)) + repoPath = sha1Path; + else if(File.Exists(md5Path)) + repoPath = md5Path; + + if(repoPath == null) + return -1; + + _lastHandle++; + long handle = _lastHandle; + + _streamsCache[handle] = Stream.Synchronized(new FileStream(repoPath, FileMode.Open, FileAccess.Read)); + + return handle; + } } internal sealed class CachedMachine @@ -360,4 +503,14 @@ namespace RomRepoMgr.Core.Filesystem public DateTime CreatedOn { get; set; } public DateTime UpdatedOn { get; set; } } + + internal sealed class CachedDisk + { + public ulong Id { get; set; } + public ulong Size { get; set; } + public string Md5 { get; set; } + public string Sha1 { get; set; } + public DateTime CreatedOn { get; set; } + public DateTime UpdatedOn { get; set; } + } } \ No newline at end of file diff --git a/RomRepoMgr.Core/Filesystem/Winfsp.cs b/RomRepoMgr.Core/Filesystem/Winfsp.cs index 9ad353e..1c82dc6 100644 --- a/RomRepoMgr.Core/Filesystem/Winfsp.cs +++ b/RomRepoMgr.Core/Filesystem/Winfsp.cs @@ -247,18 +247,62 @@ namespace RomRepoMgr.Core.Filesystem return STATUS_SUCCESS; } - CachedFile file = _vfs.GetFile(machine.Id, pieces[2]); + long handle = 0; + CachedFile file = _vfs.GetFile(machine.Id, pieces[2]); - if(file == null) + if(file != null) + { + if(pieces.Length > 3) + return STATUS_INVALID_DEVICE_REQUEST; + + if(file.Sha384 == null) + return STATUS_OBJECT_NAME_NOT_FOUND; + + handle = _vfs.Open(file.Sha384, (long)file.Size); + + if(handle <= 0) + return STATUS_OBJECT_NAME_NOT_FOUND; + + normalizedName = Path.GetFileName(fileName); + + // TODO: Real allocation size + fileInfo = new FileInfo + { + ChangeTime = (ulong)file.UpdatedOn.ToFileTimeUtc(), + AllocationSize = (file.Size + 511) / 512, + FileSize = file.Size, + CreationTime = (ulong)file.CreatedOn.ToFileTimeUtc(), + FileAttributes = + (uint)(FileAttributes.Normal | FileAttributes.Compressed | FileAttributes.ReadOnly), + IndexNumber = file.Id, + LastAccessTime = (ulong)DateTime.UtcNow.ToFileTimeUtc(), + LastWriteTime = (ulong)file.UpdatedOn.ToFileTimeUtc() + }; + + fileNode = new FileNode + { + FileName = normalizedName, + Info = fileInfo, + Path = fileName, + Handle = handle + }; + + return STATUS_SUCCESS; + } + + CachedDisk disk = _vfs.GetDisk(machine.Id, pieces[2]); + + if(disk == null) return STATUS_OBJECT_NAME_NOT_FOUND; if(pieces.Length > 3) return STATUS_INVALID_DEVICE_REQUEST; - if(file.Sha384 == null) + if(disk.Sha1 == null && + disk.Md5 == null) return STATUS_OBJECT_NAME_NOT_FOUND; - long handle = _vfs.Open(file.Sha384, (long)file.Size); + handle = _vfs.OpenDisk(disk.Sha1, disk.Md5); if(handle <= 0) return STATUS_OBJECT_NAME_NOT_FOUND; @@ -268,14 +312,14 @@ namespace RomRepoMgr.Core.Filesystem // TODO: Real allocation size fileInfo = new FileInfo { - ChangeTime = (ulong)file.UpdatedOn.ToFileTimeUtc(), - AllocationSize = (file.Size + 511) / 512, - FileSize = file.Size, - CreationTime = (ulong)file.CreatedOn.ToFileTimeUtc(), + ChangeTime = (ulong)disk.UpdatedOn.ToFileTimeUtc(), + AllocationSize = (disk.Size + 511) / 512, + FileSize = disk.Size, + CreationTime = (ulong)disk.CreatedOn.ToFileTimeUtc(), FileAttributes = (uint)(FileAttributes.Normal | FileAttributes.Compressed | FileAttributes.ReadOnly), - IndexNumber = file.Id, + IndexNumber = disk.Id, LastAccessTime = (ulong)DateTime.UtcNow.ToFileTimeUtc(), - LastWriteTime = (ulong)file.UpdatedOn.ToFileTimeUtc() + LastWriteTime = (ulong)disk.UpdatedOn.ToFileTimeUtc() }; fileNode = new FileNode @@ -352,6 +396,9 @@ namespace RomRepoMgr.Core.Filesystem ConcurrentDictionary cachedMachineFiles = _vfs.GetFilesFromMachine(node.MachineId); + ConcurrentDictionary cachedMachineDisks = + _vfs.GetDisksFromMachine(node.MachineId); + node.Children = new List { new FileEntry @@ -382,6 +429,23 @@ namespace RomRepoMgr.Core.Filesystem LastWriteTime = (ulong)file.Value.UpdatedOn.ToFileTimeUtc() } })); + + node.Children.AddRange(cachedMachineDisks.Select(disk => new FileEntry + { + FileName = disk.Key + ".chd", + Info = new FileInfo + { + ChangeTime = (ulong)disk.Value.UpdatedOn.ToFileTimeUtc(), + AllocationSize = (disk.Value.Size + 511) / 512, + FileSize = disk.Value.Size, + CreationTime = (ulong)disk.Value.CreatedOn.ToFileTimeUtc(), + FileAttributes = + (uint)(FileAttributes.Normal | FileAttributes.Compressed | FileAttributes.ReadOnly), + IndexNumber = disk.Value.Id, + LastAccessTime = (ulong)DateTime.UtcNow.ToFileTimeUtc(), + LastWriteTime = (ulong)disk.Value.UpdatedOn.ToFileTimeUtc() + } + })); } else if(node.RomSetId > 0) { @@ -525,18 +589,40 @@ namespace RomRepoMgr.Core.Filesystem return STATUS_SUCCESS; } - CachedFile file = _vfs.GetFile(machine.Id, pieces[2]); + long handle = 0; + CachedFile file = _vfs.GetFile(machine.Id, pieces[2]); - if(file == null) + if(file != null) + { + if(pieces.Length > 3) + return STATUS_INVALID_DEVICE_REQUEST; + + if(file.Sha384 == null) + return STATUS_OBJECT_NAME_NOT_FOUND; + + handle = _vfs.Open(file.Sha384, (long)file.Size); + + if(handle <= 0) + return STATUS_OBJECT_NAME_NOT_FOUND; + + fileAttributes = (uint)(FileAttributes.Normal | FileAttributes.Compressed | FileAttributes.ReadOnly); + + return STATUS_SUCCESS; + } + + CachedDisk disk = _vfs.GetDisk(machine.Id, pieces[2]); + + if(disk == null) return STATUS_OBJECT_NAME_NOT_FOUND; if(pieces.Length > 3) return STATUS_INVALID_DEVICE_REQUEST; - if(file.Sha384 == null) + if(disk.Sha1 == null && + disk.Md5 == null) return STATUS_OBJECT_NAME_NOT_FOUND; - long handle = _vfs.Open(file.Sha384, (long)file.Size); + handle = _vfs.OpenDisk(disk.Sha1, disk.Md5); if(handle <= 0) return STATUS_OBJECT_NAME_NOT_FOUND;