Improve link detection on Windows

This PR adds a check that prevents performing link lookups on drive roots as this is reported to fail on some Windows versions.

The PR also makes a link lookup failure a warning instead of failing the restore process.
This commit is contained in:
Kenneth Skovhede
2026-03-03 10:06:53 +01:00
parent 68750ca9f6
commit 2beebb6086
+43 -4
View File
@@ -22,6 +22,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using Duplicati.Library.Logging;
#nullable enable
@@ -29,6 +30,11 @@ namespace Duplicati.Library.Common.IO
{
public static class Util
{
/// <summary>
/// The log tag for messages
/// </summary>
private static readonly string LOGTAG = Log.LogTagFromType(typeof(Util));
/// <summary>
/// A cached instance of the directory separator as a string
/// </summary>
@@ -130,6 +136,26 @@ namespace Duplicati.Library.Common.IO
return true;
}
/// <summary>
/// Checks if the path is a Windows drive root (e.g., "C:\" or "D:\").
/// </summary>
/// <param name="path">The path to check.</param>
/// <returns><c>true</c> if the path is a drive root, <c>false</c> otherwise.</returns>
private static bool IsDriveRoot(string path)
{
if (!OperatingSystem.IsWindows())
return false;
// GetPathRoot returns "C:\" for "C:\" or "C:\Users"
var root = Path.GetPathRoot(path);
if (string.IsNullOrEmpty(root))
return false;
// Check if the input is exactly the root
return string.Equals(path.TrimEnd('\\'), root.TrimEnd('\\'), StringComparison.OrdinalIgnoreCase)
&& root.EndsWith(Path.DirectorySeparatorChar);
}
/// <summary>
/// Get the final resolved path, accounting for symlinks in existing segments.
/// </summary>
@@ -158,11 +184,24 @@ namespace Duplicati.Library.Common.IO
}
// 2. Resolve symlinks for the part of the path that actually exists
FileSystemInfo info = Directory.Exists(current)
? new DirectoryInfo(current)
: new FileInfo(current);
// Skip symlink resolution for Windows drive roots (e.g., "C:\") as
// ResolveLinkTarget can throw DirectoryNotFoundException on some systems
var resolvedPath = current;
if (!IsDriveRoot(current))
{
FileSystemInfo info = Directory.Exists(current)
? new DirectoryInfo(current)
: new FileInfo(current);
var resolvedPath = info.ResolveLinkTarget(returnFinalTarget: true)?.FullName ?? info.FullName;
try
{
resolvedPath = info.ResolveLinkTarget(returnFinalTarget: true)?.FullName ?? info.FullName;
}
catch (Exception ex)
{
Log.WriteWarningMessage(LOGTAG, "ResolveLinkTargetFailed", ex, "Failed to resolve link target for {0}, assuming it is not a link", current);
}
}
// 3. Re-attach the non-existent segments to the resolved base path
while (ghostSegments.Count > 0)