From 4aa61b235251fc6f201bb1e1fd0bb71c0e342e9d Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Mon, 15 Jun 2026 22:51:13 +0200 Subject: [PATCH] Avoid using .NET File operations during restore as they normalize paths. --- .../Main/Operation/Restore/FileProcessor.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Duplicati/Library/Main/Operation/Restore/FileProcessor.cs b/Duplicati/Library/Main/Operation/Restore/FileProcessor.cs index cb68e77a6..c7e634088 100644 --- a/Duplicati/Library/Main/Operation/Restore/FileProcessor.cs +++ b/Duplicati/Library/Main/Operation/Restore/FileProcessor.cs @@ -811,7 +811,7 @@ namespace Duplicati.Library.Main.Operation.Restore List verified_blocks = []; // Check if the file exists - if (File.Exists(file.TargetPath)) + if (await restoreDestination.FileExists(file.TargetPath, cancellationToken).ConfigureAwait(false)) { filehasher.Initialize(); try @@ -867,17 +867,19 @@ namespace Duplicati.Library.Main.Operation.Restore if (filehasher.Hash != null && Convert.ToBase64String(filehasher.Hash) == file.Hash) { // Truncate the file if it is larger than the expected size. - FileInfo fi = new(file.TargetPath); - if (file.Length < fi.Length) + var currentLength = await restoreDestination.GetFileLength(file.TargetPath, cancellationToken).ConfigureAwait(false); + if (file.Length < currentLength) { if (options.Dryrun) { - Logging.Log.WriteDryrunMessage(LOGTAG, "DryrunRestore", @$"Would have truncated ""{file.TargetPath}"" from {fi.Length} to {file.Length}"); + Logging.Log.WriteDryrunMessage(LOGTAG, "DryrunRestore", @$"Would have truncated ""{file.TargetPath}"" from {currentLength} to {file.Length}"); } else { // Reopen file with write permission - fi.IsReadOnly = false; // The metadata handler will revert this back later. + // The metadata handler will revert this back later. + if (await restoreDestination.HasReadOnlyAttribute(file.TargetPath, cancellationToken).ConfigureAwait(false)) + await restoreDestination.ClearReadOnlyAttribute(file.TargetPath, cancellationToken).ConfigureAwait(false); try { using var f = await restoreDestination.OpenWrite(file.TargetPath, cancellationToken).ConfigureAwait(false); @@ -931,15 +933,14 @@ namespace Duplicati.Library.Main.Operation.Restore List missing_blocks = []; List verified_blocks = []; - // Check if the file exists - if (File.Exists(file.OriginalPath)) + // Check if the file exists (ignore if this was a remote source) + if (SystemIO.IO_OS.FileExists(file.OriginalPath)) { filehasher.Initialize(); // Open both files, as the target file is still being read to produce the overall file hash, if all the blocks are present across both the target and original files. // Note: The original file is the source file on the local system (not within the restore destination), - // so we use File.OpenRead directly instead of restoreDestination.OpenRead - using var f_original = File.OpenRead(file.OriginalPath); + using var f_original = SystemIO.IO_OS.FileOpenRead(file.OriginalPath); using var f_target = options.Dryrun ? (await restoreDestination.FileExists(file.TargetPath, cancellationToken).ConfigureAwait(false) ? await restoreDestination.OpenRead(file.TargetPath, cancellationToken).ConfigureAwait(false) :