/* Tesses.VirtualFilesystem a library for virtual filesystems in .NET Copyright (C) 2023 Mike Nolan This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ using System; using Tesses.VirtualFilesystem; using Tesses.VirtualFilesystem.Extensions; using System.IO; using System.Threading.Tasks; using System.Threading; using System.Linq; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; using System.ComponentModel; namespace Tesses.VirtualFilesystem.Filesystems { public class LocalFileSystem : SyncFileSystem { public override string ConvertPathFromUnixPath(UnixPath path) { return path.ToLocal(); } public override UnixPath ConvertPathToUnixPath(string path) { return UnixPath.FromLocal(path); } public override void CreateDirectory(UnixPath directory) { Directory.CreateDirectory(ConvertPathFromUnixPath(directory)); } public override void DeleteDirectory(UnixPath path) { Directory.Delete(ConvertPathFromUnixPath(path)); } public override void DeleteFile(UnixPath path) { File.Delete(ConvertPathFromUnixPath(path)); } public override bool DirectoryExists(UnixPath path) { return Directory.Exists(ConvertPathFromUnixPath(path)); } public override IEnumerable EnumerateFileSystemEntries(UnixPath path) { foreach(var item in Directory.EnumerateFileSystemEntries(ConvertPathFromUnixPath(path))) { yield return ConvertPathToUnixPath(item); } } public override bool FileExists(UnixPath path) { return File.Exists(ConvertPathFromUnixPath(path)); } public override DateTime GetCreationTime(UnixPath path) { return FileExists(path) ? File.GetCreationTime(ConvertPathFromUnixPath(path)) : Directory.GetCreationTime(ConvertPathFromUnixPath(path)); } public override DateTime GetLastAccessTime(UnixPath path) { return FileExists(path) ? File.GetLastAccessTime(ConvertPathFromUnixPath(path)) : Directory.GetLastAccessTime(ConvertPathFromUnixPath(path)); } public override DateTime GetLastWriteTime(UnixPath path) { return FileExists(path) ? File.GetLastAccessTime(ConvertPathFromUnixPath(path)) : Directory.GetLastWriteTime(ConvertPathFromUnixPath(path)); } public override void MoveDirectory(UnixPath src, UnixPath dest) { Directory.Move(ConvertPathFromUnixPath(src),ConvertPathFromUnixPath(dest)); } public override void MoveFile(UnixPath src, UnixPath dest) { File.Move(ConvertPathFromUnixPath(src),ConvertPathFromUnixPath(dest)); } public override Stream Open(UnixPath path, FileMode mode, FileAccess access, FileShare share) { return File.Open(ConvertPathFromUnixPath(path),mode,access,share); } public override void SetCreationTime(UnixPath path, DateTime time) { if(FileExists(path)) { File.SetCreationTime(ConvertPathFromUnixPath(path),time); } else { Directory.SetCreationTime(ConvertPathFromUnixPath(path),time); } } public override void SetLastAccessTime(UnixPath path, DateTime time) { if(FileExists(path)) { File.SetLastAccessTime(ConvertPathFromUnixPath(path),time); } else { Directory.SetLastAccessTime(ConvertPathFromUnixPath(path),time); } } private class LocalWatcher : IVirtualWatcher { FileSystemWatcher watcher; LocalFileSystem _fs; public LocalWatcher(LocalFileSystem fs,UnixPath path) { watcher=new FileSystemWatcher(fs.ConvertPathFromUnixPath(path)); watcher.Changed += (sender,e)=>{ Changed?.Invoke(this,new VirtualWatcherChangedArgs(fs,e.ChangeType,fs.ConvertPathToUnixPath(e.FullPath))); }; watcher.Created += (sender,e)=>{ Created?.Invoke(this,new VirtualWatcherChangedArgs(fs,e.ChangeType,fs.ConvertPathToUnixPath(e.FullPath))); }; watcher.Deleted += (sender,e)=>{ Deleted?.Invoke(this,new VirtualWatcherChangedArgs(fs,e.ChangeType,fs.ConvertPathToUnixPath(e.FullPath))); }; watcher.Error += (sender,e)=>{ Error?.Invoke(this,e); }; watcher.Renamed += (sender,e)=>{ Renamed?.Invoke(this,new VirtualWatcherRenamedEventArgs(fs,e.ChangeType,fs.ConvertPathToUnixPath(e.FullPath),fs.ConvertPathToUnixPath(e.OldFullPath))); }; _fs=fs; } public int InternalBufferSize { get => watcher.InternalBufferSize; set => watcher.InternalBufferSize=value;} public NotifyFilters NotifyFilter { get => watcher.NotifyFilter; set => watcher.NotifyFilter=value; } public bool EnableRaisingEvents { get => watcher.EnableRaisingEvents; set => watcher.EnableRaisingEvents=value; } public string Filter { get => watcher.Filter; set => watcher.Filter=value; } public bool IncludeSubdirectories { get => watcher.IncludeSubdirectories; set => watcher.IncludeSubdirectories=value;} public event EventHandler Changed; public event EventHandler Created; public event EventHandler Deleted; public event EventHandler Renamed; public event EventHandler Error; public void Dispose() { this.watcher.Dispose(); } } public override IVirtualWatcher WatchDirectory(UnixPath dir) { return new LocalWatcher(this,dir); } public override void CreateHardlink(UnixPath src, UnixPath dest) { if(DirectoryExists(src)) return; if(Environment.OSVersion.Platform == PlatformID.Win32NT) { WindowsNative.CreateHardlink(ConvertPathFromUnixPath(dest),ConvertPathFromUnixPath(src),IntPtr.Zero); } else { Mono.Unix.Native.Syscall.link(ConvertPathFromUnixPath(src),ConvertPathFromUnixPath(dest)); } } public override void CreateSymlink(UnixPath src, UnixPath dest) { if(Environment.OSVersion.Platform == PlatformID.Win32NT) { int flag = DirectoryExists(src) ? 0x1 : 0; WindowsNative.CreateSymbolicLinkFix(ConvertPathFromUnixPath(dest),ConvertPathFromUnixPath(src),flag); }else{ Mono.Unix.Native.Syscall.symlink(ConvertPathFromUnixPath(src),ConvertPathFromUnixPath(dest)); } } public override bool SymlinkExists(UnixPath file) { if(Environment.OSVersion.Platform == PlatformID.Win32NT) { return File.GetAttributes(ConvertPathFromUnixPath(file)).HasFlag(FileAttributes.ReparsePoint); }else{ /* Mono.Unix.Native.Stat buf; if(Mono.Unix.Native.Syscall.stat(ConvertPathFromUnixPath(file),out buf) == 0) return buf.st_mode.HasFlag(Mono.Unix.Native.FilePermissions.S_IFLNK);*/ return File.GetAttributes(ConvertPathFromUnixPath(file)).HasFlag(FileAttributes.ReparsePoint); } } private static class WindowsNative { [DllImport("kernel32.dll", EntryPoint = "CreateSymbolicLinkW", CharSet = CharSet.Unicode, SetLastError = true)] static extern int CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, int dwFlags); [DllImport("kernel32.dll", EntryPoint = "CreateHardLinkW", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool CreateHardlink(string lpSymlinkFileName, string lpTargetFileName, IntPtr dwFlags); public static int CreateSymbolicLinkFix(string lpSymlinkFileName, string lpTargetFileName, int dwFlags) { var result = CreateSymbolicLink(lpSymlinkFileName, lpTargetFileName, dwFlags); if (result == 1) return 0; // Success return Marshal.GetLastWin32Error(); } private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); private const uint FILE_READ_EA = 0x0008; private const uint FILE_FLAG_BACKUP_SEMANTICS = 0x2000000; [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] static extern uint GetFinalPathNameByHandle(IntPtr hFile, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder lpszFilePath, uint cchFilePath, uint dwFlags); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool CloseHandle(IntPtr hObject); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern IntPtr CreateFile( [MarshalAs(UnmanagedType.LPTStr)] string filename, [MarshalAs(UnmanagedType.U4)] uint access, [MarshalAs(UnmanagedType.U4)] FileShare share, IntPtr securityAttributes, // optional SECURITY_ATTRIBUTES struct or IntPtr.Zero [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition, [MarshalAs(UnmanagedType.U4)] uint flagsAndAttributes, IntPtr templateFile); public static string GetFinalPathName(string path) { var h = CreateFile(path, FILE_READ_EA, FileShare.ReadWrite | FileShare.Delete, IntPtr.Zero, FileMode.Open, FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero); if (h == INVALID_HANDLE_VALUE) throw new Win32Exception(); try { var sb = new StringBuilder(1024); var res = GetFinalPathNameByHandle(h, sb, 1024, 0); if (res == 0) throw new Win32Exception(); return sb.ToString(); } finally { CloseHandle(h); } } } public override bool CanWatch(UnixPath path) { return true; } public override UnixPath ReadLink(UnixPath file) { if(!SymlinkExists(file)) return file; if(Environment.OSVersion.Platform == PlatformID.Win32NT) { return ConvertPathToUnixPath(WindowsNative.GetFinalPathName(ConvertPathFromUnixPath(file))); }else{ StringBuilder b=new StringBuilder(4096); if(Mono.Unix.Native.Syscall.readlink(ConvertPathFromUnixPath(file),b)>=0) { return ConvertPathToUnixPath(b.ToString()); } } return file; } public override void SetLastWriteTime(UnixPath path, DateTime time) { if(FileExists(path)) { File.SetLastWriteTime(ConvertPathFromUnixPath(path),time); } else { Directory.SetLastWriteTime(ConvertPathFromUnixPath(path),time); } } } }