From d9f34424c750aae3610c3bcb6132ab2f9b803de9 Mon Sep 17 00:00:00 2001 From: Gerard Smit Date: Fri, 24 May 2024 13:20:01 +0200 Subject: [PATCH] Add ResolveLinkTarget --- .../FileSystems/FileSystemEntryRedirect.cs | 5 ++ .../FileSystems/TestPhysicalFileSystem.cs | 6 ++ src/Zio/FileSystems/ComposeFileSystem.cs | 8 ++ src/Zio/FileSystems/FileSystem.cs | 16 +++- src/Zio/FileSystems/MemoryFileSystem.cs | 6 ++ src/Zio/FileSystems/MountFileSystem.cs | 9 ++ src/Zio/FileSystems/PhysicalFileSystem.cs | 33 ++++++++ src/Zio/FileSystems/ZipArchiveFileSystem.cs | 6 ++ src/Zio/IFileSystem.cs | 6 ++ src/Zio/Interop.cs | 83 +++++++++++++++++++ 10 files changed, 177 insertions(+), 1 deletion(-) diff --git a/src/Zio.Tests/FileSystems/FileSystemEntryRedirect.cs b/src/Zio.Tests/FileSystems/FileSystemEntryRedirect.cs index 83ebb22..e1237cf 100644 --- a/src/Zio.Tests/FileSystems/FileSystemEntryRedirect.cs +++ b/src/Zio.Tests/FileSystems/FileSystemEntryRedirect.cs @@ -125,6 +125,11 @@ protected override void CreateSymbolicLinkImpl(UPath path, UPath pathToTarget) _fs.CreateSymbolicLink(path, pathToTarget); } + protected override UPath? ResolveLinkTargetImpl(UPath linkPath) + { + return _fs.ResolveLinkTarget(linkPath); + } + protected override IEnumerable EnumeratePathsImpl(UPath path, string searchPattern, SearchOption searchOption, SearchTarget searchTarget) { return _fs.GetDirectoryEntry(path).EnumerateEntries(searchPattern, searchOption, searchTarget).Select(e => e.Path); diff --git a/src/Zio.Tests/FileSystems/TestPhysicalFileSystem.cs b/src/Zio.Tests/FileSystems/TestPhysicalFileSystem.cs index 0560c7f..a14669c 100644 --- a/src/Zio.Tests/FileSystems/TestPhysicalFileSystem.cs +++ b/src/Zio.Tests/FileSystems/TestPhysicalFileSystem.cs @@ -442,6 +442,9 @@ public void TestDirectorySymlink() // CreateSymbolicLink fs.CreateSymbolicLink(pathDest, pathSource); + // ResolveSymbolicLink + Assert.Equal(pathSource, fs.ResolveLinkTarget(pathDest)); + // FileExists Assert.True(fs.FileExists(filePathDest)); Assert.Equal(buffer.Length, fs.GetFileLength(filePathDest)); @@ -487,6 +490,9 @@ public void TestFileSymlink() // CreateSymbolicLink fs.CreateSymbolicLink(pathDest, pathSource); + // ResolveSymbolicLink + Assert.Equal(pathSource, fs.ResolveLinkTarget(pathDest)); + // FileExists Assert.True(fs.FileExists(pathDest)); diff --git a/src/Zio/FileSystems/ComposeFileSystem.cs b/src/Zio/FileSystems/ComposeFileSystem.cs index 6d1a2df..546ddb7 100644 --- a/src/Zio/FileSystems/ComposeFileSystem.cs +++ b/src/Zio/FileSystems/ComposeFileSystem.cs @@ -192,6 +192,14 @@ protected override void CreateSymbolicLinkImpl(UPath path, UPath pathToTarget) FallbackSafe.CreateSymbolicLink(ConvertPathToDelegate(path), ConvertPathToDelegate(pathToTarget)); } + /// + protected override UPath? ResolveLinkTargetImpl(UPath linkPath) + { + var path = FallbackSafe.ResolveLinkTarget(ConvertPathToDelegate(linkPath)); + + return path.HasValue ? ConvertPathFromDelegate(path.Value) : default(UPath?); + } + // ---------------------------------------------- // Search API // ---------------------------------------------- diff --git a/src/Zio/FileSystems/FileSystem.cs b/src/Zio/FileSystems/FileSystem.cs index c6c2821..ce4ea0f 100644 --- a/src/Zio/FileSystems/FileSystem.cs +++ b/src/Zio/FileSystems/FileSystem.cs @@ -444,7 +444,21 @@ public void CreateSymbolicLink(UPath path, UPath pathToTarget) /// The path of the symbolic link to create. /// The path of the target for the symbolic link. protected abstract void CreateSymbolicLinkImpl(UPath path, UPath pathToTarget); - + + + /// + public UPath? ResolveLinkTarget(UPath linkPath) + { + AssertNotDisposed(); + return ResolveLinkTargetImpl(ValidatePath(linkPath)); + } + + /// + /// Resolves the target of a symbolic link. + /// + /// The path of the symbolic link to resolve. + protected abstract UPath? ResolveLinkTargetImpl(UPath linkPath); + // ---------------------------------------------- // Search API // ---------------------------------------------- diff --git a/src/Zio/FileSystems/MemoryFileSystem.cs b/src/Zio/FileSystems/MemoryFileSystem.cs index e2b1fb1..88669c6 100644 --- a/src/Zio/FileSystems/MemoryFileSystem.cs +++ b/src/Zio/FileSystems/MemoryFileSystem.cs @@ -759,6 +759,12 @@ protected override void CreateSymbolicLinkImpl(UPath path, UPath pathToTarget) throw new NotSupportedException("Symbolic links are not supported by MemoryFileSystem"); } + /// + protected override UPath? ResolveLinkTargetImpl(UPath linkPath) + { + return null; + } + // ---------------------------------------------- // Search API // ---------------------------------------------- diff --git a/src/Zio/FileSystems/MountFileSystem.cs b/src/Zio/FileSystems/MountFileSystem.cs index 6da1b57..f08b7a5 100644 --- a/src/Zio/FileSystems/MountFileSystem.cs +++ b/src/Zio/FileSystems/MountFileSystem.cs @@ -565,6 +565,15 @@ protected override void CreateSymbolicLinkImpl(UPath path, UPath pathToTarget) } } + /// + protected override UPath? ResolveLinkTargetImpl(UPath linkPath) + { + var mountfs = TryGetMountOrNext(ref linkPath, out var mountPath); + var path = mountfs?.ResolveLinkTarget(linkPath); + + return path != null ? CombinePrefix(mountPath, path.Value) : default(UPath?); + } + /// protected override IEnumerable EnumeratePathsImpl(UPath path, string searchPattern, SearchOption searchOption, SearchTarget searchTarget) { diff --git a/src/Zio/FileSystems/PhysicalFileSystem.cs b/src/Zio/FileSystems/PhysicalFileSystem.cs index be12a75..0b9ec43 100644 --- a/src/Zio/FileSystems/PhysicalFileSystem.cs +++ b/src/Zio/FileSystems/PhysicalFileSystem.cs @@ -515,6 +515,39 @@ protected override void CreateSymbolicLinkImpl(UPath path, UPath pathToTarget) #endif } + /// + protected override UPath? ResolveLinkTargetImpl(UPath linkPath) + { + if (IsWithinSpecialDirectory(linkPath)) + { + throw new UnauthorizedAccessException($"The access to `{linkPath}` is denied"); + } + + var systemPath = ConvertPathToInternal(linkPath); + bool isDirectory; + + if (File.Exists(systemPath)) + { + isDirectory = false; + } + else if (Directory.Exists(systemPath)) + { + isDirectory = true; + } + else + { + return null; + } + +#if NET7_0_OR_GREATER + var systemResult = isDirectory ? Directory.ResolveLinkTarget(systemPath, true)?.FullName : File.ResolveLinkTarget(systemPath, true)?.FullName; +#else + var systemResult = IsOnWindows ? Interop.Windows.GetFinalPathName(systemPath) : Interop.Unix.readlink(systemPath); +#endif + + return systemResult != null ? ConvertPathFromInternal(systemResult) : default(UPath?); + } + // ---------------------------------------------- // Search API // ---------------------------------------------- diff --git a/src/Zio/FileSystems/ZipArchiveFileSystem.cs b/src/Zio/FileSystems/ZipArchiveFileSystem.cs index 196f442..575c7ad 100644 --- a/src/Zio/FileSystems/ZipArchiveFileSystem.cs +++ b/src/Zio/FileSystems/ZipArchiveFileSystem.cs @@ -823,6 +823,12 @@ protected override void CreateSymbolicLinkImpl(UPath path, UPath pathToTarget) throw new NotSupportedException("Symbolic links are not supported by ZipArchiveFileSystem"); } + /// + protected override UPath? ResolveLinkTargetImpl(UPath linkPath) + { + return null; + } + /// protected override IFileSystemWatcher WatchImpl(UPath path) { diff --git a/src/Zio/IFileSystem.cs b/src/Zio/IFileSystem.cs index 6588269..7d665a3 100644 --- a/src/Zio/IFileSystem.cs +++ b/src/Zio/IFileSystem.cs @@ -170,6 +170,12 @@ public interface IFileSystem : IDisposable /// The path of the target for the symbolic link. void CreateSymbolicLink(UPath path, UPath pathToTarget); + /// + /// Resolves the target of a symbolic link. + /// + /// The path of the symbolic link to resolve. + UPath? ResolveLinkTarget(UPath linkPath); + // ---------------------------------------------- // Search API // ---------------------------------------------- diff --git a/src/Zio/Interop.cs b/src/Zio/Interop.cs index 8d4a4d9..8325462 100644 --- a/src/Zio/Interop.cs +++ b/src/Zio/Interop.cs @@ -3,7 +3,10 @@ // See the license.txt file in the project root for more information. #if !NET7_0_OR_GREATER +using System.ComponentModel; +using System.IO; using System.Runtime.InteropServices; +using System.Text; namespace Zio; @@ -11,9 +14,78 @@ internal static class Interop { public static class Windows { + 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)] public static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, SymbolicLink dwFlags); + [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] + public static extern uint GetFinalPathNameByHandle(IntPtr hFile, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder lpszFilePath, uint cchFilePath, uint dwFlags); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public 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, + [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(); + } + + // Trim '\\?\' + if (sb.Length >= 4 && sb[0] == '\\' && sb[1] == '\\' && sb[2] == '?' && sb[3] == '\\') + { + sb.Remove(0, 4); + + // Trim 'UNC\' + if (sb.Length >= 4 && sb[0] == 'U' && sb[1] == 'N' && sb[2] == 'C' && sb[3] == '\\') + { + sb.Remove(0, 4); + + // Add the default UNC prefix + sb.Insert(0, @"\\"); + } + } + + return sb.ToString(); + } + finally + { + CloseHandle(h); + } + } + public enum SymbolicLink { File = 0, @@ -25,6 +97,17 @@ public static class Unix { [DllImport("libc", SetLastError = true)] public static extern int symlink(string target, string linkpath); + + [DllImport ("libc")] + private static extern int readlink (string path, byte[] buffer, int buflen); + + public static string? readlink(string path) + { + var buf = new byte[1024]; + var ret = readlink(path, buf, buf.Length); + + return ret == -1 ? null : Encoding.Default.GetString(buf, 0, ret); + } } } #endif \ No newline at end of file