Skip to content

Commit

Permalink
Add ResolveLinkTarget
Browse files Browse the repository at this point in the history
  • Loading branch information
GerardSmit committed May 24, 2024
1 parent b216f9e commit d9f3442
Show file tree
Hide file tree
Showing 10 changed files with 177 additions and 1 deletion.
5 changes: 5 additions & 0 deletions src/Zio.Tests/FileSystems/FileSystemEntryRedirect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<UPath> EnumeratePathsImpl(UPath path, string searchPattern, SearchOption searchOption, SearchTarget searchTarget)
{
return _fs.GetDirectoryEntry(path).EnumerateEntries(searchPattern, searchOption, searchTarget).Select(e => e.Path);
Expand Down
6 changes: 6 additions & 0 deletions src/Zio.Tests/FileSystems/TestPhysicalFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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));

Expand Down
8 changes: 8 additions & 0 deletions src/Zio/FileSystems/ComposeFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,14 @@ protected override void CreateSymbolicLinkImpl(UPath path, UPath pathToTarget)
FallbackSafe.CreateSymbolicLink(ConvertPathToDelegate(path), ConvertPathToDelegate(pathToTarget));
}

/// <inheritdoc />
protected override UPath? ResolveLinkTargetImpl(UPath linkPath)
{
var path = FallbackSafe.ResolveLinkTarget(ConvertPathToDelegate(linkPath));

return path.HasValue ? ConvertPathFromDelegate(path.Value) : default(UPath?);
}

// ----------------------------------------------
// Search API
// ----------------------------------------------
Expand Down
16 changes: 15 additions & 1 deletion src/Zio/FileSystems/FileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,21 @@ public void CreateSymbolicLink(UPath path, UPath pathToTarget)
/// <param name="path">The path of the symbolic link to create.</param>
/// <param name="pathToTarget">The path of the target for the symbolic link.</param>
protected abstract void CreateSymbolicLinkImpl(UPath path, UPath pathToTarget);



/// <inheritdoc />
public UPath? ResolveLinkTarget(UPath linkPath)
{
AssertNotDisposed();
return ResolveLinkTargetImpl(ValidatePath(linkPath));
}

/// <summary>
/// Resolves the target of a symbolic link.
/// </summary>
/// <param name="linkPath">The path of the symbolic link to resolve.</param>
protected abstract UPath? ResolveLinkTargetImpl(UPath linkPath);

// ----------------------------------------------
// Search API
// ----------------------------------------------
Expand Down
6 changes: 6 additions & 0 deletions src/Zio/FileSystems/MemoryFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,12 @@ protected override void CreateSymbolicLinkImpl(UPath path, UPath pathToTarget)
throw new NotSupportedException("Symbolic links are not supported by MemoryFileSystem");
}

/// <inheritdoc />
protected override UPath? ResolveLinkTargetImpl(UPath linkPath)
{
return null;
}

// ----------------------------------------------
// Search API
// ----------------------------------------------
Expand Down
9 changes: 9 additions & 0 deletions src/Zio/FileSystems/MountFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,15 @@ protected override void CreateSymbolicLinkImpl(UPath path, UPath pathToTarget)
}
}

/// <inheritdoc />
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?);
}

/// <inheritdoc />
protected override IEnumerable<UPath> EnumeratePathsImpl(UPath path, string searchPattern, SearchOption searchOption, SearchTarget searchTarget)
{
Expand Down
33 changes: 33 additions & 0 deletions src/Zio/FileSystems/PhysicalFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,39 @@ protected override void CreateSymbolicLinkImpl(UPath path, UPath pathToTarget)
#endif
}

/// <inheritdoc />
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
// ----------------------------------------------
Expand Down
6 changes: 6 additions & 0 deletions src/Zio/FileSystems/ZipArchiveFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,12 @@ protected override void CreateSymbolicLinkImpl(UPath path, UPath pathToTarget)
throw new NotSupportedException("Symbolic links are not supported by ZipArchiveFileSystem");
}

/// <inheritdoc />
protected override UPath? ResolveLinkTargetImpl(UPath linkPath)
{
return null;
}

/// <inheritdoc />
protected override IFileSystemWatcher WatchImpl(UPath path)
{
Expand Down
6 changes: 6 additions & 0 deletions src/Zio/IFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,12 @@ public interface IFileSystem : IDisposable
/// <param name="pathToTarget">The path of the target for the symbolic link.</param>
void CreateSymbolicLink(UPath path, UPath pathToTarget);

/// <summary>
/// Resolves the target of a symbolic link.
/// </summary>
/// <param name="linkPath">The path of the symbolic link to resolve.</param>
UPath? ResolveLinkTarget(UPath linkPath);

// ----------------------------------------------
// Search API
// ----------------------------------------------
Expand Down
83 changes: 83 additions & 0 deletions src/Zio/Interop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,89 @@
// 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;

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,
Expand All @@ -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

0 comments on commit d9f3442

Please sign in to comment.