diff --git a/Duplicati/Library/Main/Operation/Restore/Channels.cs b/Duplicati/Library/Main/Operation/Restore/Channels.cs
index 4d14ee055..9b83773da 100644
--- a/Duplicati/Library/Main/Operation/Restore/Channels.cs
+++ b/Duplicati/Library/Main/Operation/Restore/Channels.cs
@@ -19,7 +19,6 @@
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
-using System.Threading.Tasks;
using CoCoL;
using Duplicati.Library.Main.Volumes;
using Duplicati.Library.Utility;
@@ -46,12 +45,12 @@ namespace Duplicati.Library.Main.Operation.Restore
///
/// Channel between and .
///
- public readonly IChannel<(long, Task)> DownloadRequest = ChannelManager.CreateChannel<(long, Task)>(buffersize: BufferSize);
+ public readonly IChannel DownloadRequest = ChannelManager.CreateChannel(buffersize: BufferSize);
///
/// Channel between and
///
- public readonly IChannel<(long, TempFile)> DecryptRequest = ChannelManager.CreateChannel<(long, TempFile)>(buffersize: BufferSize);
+ public readonly IChannel<(long, string, TempFile)> DecryptRequest = ChannelManager.CreateChannel<(long, string, TempFile)>(buffersize: BufferSize);
///
/// Channel between and
diff --git a/Duplicati/Library/Main/Operation/Restore/VolumeDecryptor.cs b/Duplicati/Library/Main/Operation/Restore/VolumeDecryptor.cs
index 2dc5fa8e8..59576fac9 100644
--- a/Duplicati/Library/Main/Operation/Restore/VolumeDecryptor.cs
+++ b/Duplicati/Library/Main/Operation/Restore/VolumeDecryptor.cs
@@ -21,6 +21,7 @@
using System;
using System.Diagnostics;
+using System.Linq;
using System.Threading.Tasks;
using CoCoL;
using Duplicati.Library.Main.Volumes;
@@ -57,15 +58,23 @@ namespace Duplicati.Library.Main.Operation.Restore
Stopwatch sw_decrypt = options.InternalProfiling ? new() : null;
try
{
+ // Taken from BackendManager.GetOperation
+ using var encryption = options.NoEncryption
+ ? null
+ : (DynamicLoader.EncryptionLoader.GetModule(options.EncryptionModule, options.Passphrase, options.RawOptions)
+ ?? throw new Exception(Strings.BackendMananger.EncryptionModuleNotFound(options.EncryptionModule))
+ );
+
while (true)
{
// Get the block request and volume from the `VolumeDownloader` process.
sw_read?.Start();
- var (volume_id, volume) = await self.Input.ReadAsync();
+ var (volume_id, volume_name, volume) = await self.Input.ReadAsync().ConfigureAwait(false);
sw_read?.Stop();
sw_decrypt?.Start();
- var bvr = new BlockVolumeReader(options.CompressionModule, volume, options);
+ var tmpfile = DecryptFile(volume, DetectEncryptionModule(volume_name, options, encryption));
+ var bvr = new BlockVolumeReader(options.CompressionModule, tmpfile, options);
sw_decrypt?.Stop();
sw_write?.Start();
@@ -90,6 +99,83 @@ namespace Duplicati.Library.Main.Operation.Restore
}
});
}
+
+ // Taken from BackendManager.GetOperation
+ public static Library.Utility.TempFile DecryptFile(Library.Utility.TempFile tempFile, Interface.IEncryption? decrypter)
+ {
+ // Support no encryption
+ if (decrypter == null)
+ return tempFile;
+
+ Library.Utility.TempFile? decryptTarget = null;
+
+ // Always dispose the source file
+ using (tempFile)
+ using (new Logging.Timer(LOGTAG, "DecryptFile", "Decrypting " + tempFile))
+ {
+ try
+ {
+ decryptTarget = new Library.Utility.TempFile();
+ try { decrypter.Decrypt(tempFile, decryptTarget); }
+ // If we fail here, make sure that we throw a crypto exception
+ catch (System.Security.Cryptography.CryptographicException) { throw; }
+ catch (Exception ex) { throw new System.Security.Cryptography.CryptographicException(ex.Message, ex); }
+
+ var result = decryptTarget;
+ decryptTarget = null;
+ return result;
+ }
+ finally
+ {
+ // Remove temp files on failure
+ decryptTarget?.Dispose();
+ }
+ }
+ }
+
+ private static Interface.IEncryption? DetectEncryptionModule(string remotefilename, Options options, Interface.IEncryption? encryption)
+ {
+ try
+ {
+ // Auto-guess the encryption module
+ var ext = (System.IO.Path.GetExtension(remotefilename) ?? "").TrimStart('.');
+ if (!ext.Equals(encryption?.FilenameExtension, StringComparison.OrdinalIgnoreCase))
+ {
+ // Check if the file is not encrypted
+ if (DynamicLoader.CompressionLoader.Keys.Contains(ext, StringComparer.OrdinalIgnoreCase))
+ {
+ if (encryption != null)
+ Logging.Log.WriteVerboseMessage(LOGTAG, "AutomaticDecryptionDetection", "Filename extension \"{0}\" does not match encryption module \"{1}\", guessing that it is not encrypted", ext, options.EncryptionModule);
+ return null;
+ }
+ // Check if the file is encrypted with something else
+ else if (DynamicLoader.EncryptionLoader.Keys.Contains(ext, StringComparer.OrdinalIgnoreCase))
+ {
+ Logging.Log.WriteVerboseMessage(LOGTAG, "AutomaticDecryptionDetection", "Filename extension \"{0}\" does not match encryption module \"{1}\", attempting to use matching encryption module", ext, options.EncryptionModule);
+
+ try
+ {
+ return DynamicLoader.EncryptionLoader.GetModule(ext, options.Passphrase, options.RawOptions)
+ ?? encryption;
+ }
+ catch (Exception ex)
+ {
+ Logging.Log.WriteWarningMessage(LOGTAG, "AutomaticDecryptionDetection", ex, "Failed to load encryption module \"{0}\", using specified encryption module \"{1}\"", ext, options.EncryptionModule);
+ }
+ }
+ // Fallback, lets see what happens...
+ else
+ {
+ Logging.Log.WriteVerboseMessage(LOGTAG, "AutomaticDecryptionDetection", "Filename extension \"{0}\" does not match encryption module \"{1}\", attempting to use specified encryption module as no others match", ext, options.EncryptionModule);
+ }
+ }
+
+ return encryption;
+ }
+ // If we fail here, make sure that we throw a crypto exception
+ catch (System.Security.Cryptography.CryptographicException) { throw; }
+ catch (Exception ex) { throw new System.Security.Cryptography.CryptographicException(ex.Message, ex); }
+ }
}
}
\ No newline at end of file
diff --git a/Duplicati/Library/Main/Operation/Restore/VolumeDownloader.cs b/Duplicati/Library/Main/Operation/Restore/VolumeDownloader.cs
index 40a28d772..65c20c92a 100644
--- a/Duplicati/Library/Main/Operation/Restore/VolumeDownloader.cs
+++ b/Duplicati/Library/Main/Operation/Restore/VolumeDownloader.cs
@@ -22,8 +22,10 @@
using System;
using System.Diagnostics;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
using CoCoL;
+using Duplicati.Library.Interface;
using Duplicati.Library.Main.Database;
using Duplicati.Library.Utility;
@@ -43,9 +45,12 @@ namespace Duplicati.Library.Main.Operation.Restore
///
/// Runs the volume downloader process.
///
+ /// The named channels for the restore operation.
+ /// The local restore database, used to find volume information given the volume ID.
+ /// The backend to use for downloading the volumes.
/// The restore options.
/// The restore results.
- public static Task Run(Channels channels, LocalRestoreDatabase db, Options options, RestoreResults results)
+ public static Task Run(Channels channels, LocalRestoreDatabase db, IBackend backend, Options options, RestoreResults results)
{
return AutomationExtensions.RunTask(
new
@@ -65,19 +70,19 @@ namespace Duplicati.Library.Main.Operation.Restore
{
// Get the block request from the `BlockManager` process.
sw_read?.Start();
- var (volume_id, waittask) = await self.Input.ReadAsync();
+ var volume_id = await self.Input.ReadAsync().ConfigureAwait(false);
sw_read?.Stop();
// Trigger the download.
sw_wait?.Start();
- TempFile f = null;
+ TempFile f = new();
+ var (volume_name, size, hash) = db.GetVolumeInfo(volume_id).First();
try
{
- f = await waittask.ConfigureAwait(false);
+ await backend.GetAsync(volume_name, f.Name, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception)
{
- var (volume_name, _, _) = db.GetVolumeInfo(volume_id).First();
lock (results)
results.BrokenRemoteFiles.Add(volume_name);
@@ -87,7 +92,7 @@ namespace Duplicati.Library.Main.Operation.Restore
// Pass the download handle (which may or may not have downloaded already) to the `VolumeDecryptor` process.
sw_write?.Start();
- await self.Output.WriteAsync((volume_id, f));
+ await self.Output.WriteAsync((volume_id, volume_name, f)).ConfigureAwait(false);
sw_write?.Stop();
}
}
@@ -105,6 +110,10 @@ namespace Duplicati.Library.Main.Operation.Restore
Logging.Log.WriteErrorMessage(LOGTAG, "DownloadError", ex, "Error during download");
throw;
}
+ finally
+ {
+ backend.Dispose();
+ }
});
}
}
diff --git a/Duplicati/Library/Main/Operation/Restore/VolumeManager.cs b/Duplicati/Library/Main/Operation/Restore/VolumeManager.cs
index ba14a643d..0c636973c 100644
--- a/Duplicati/Library/Main/Operation/Restore/VolumeManager.cs
+++ b/Duplicati/Library/Main/Operation/Restore/VolumeManager.cs
@@ -20,7 +20,6 @@
// DEALINGS IN THE SOFTWARE.
using CoCoL;
-using Duplicati.Library.Main.Database;
using Duplicati.Library.Main.Volumes;
using Duplicati.Library.Utility;
using System;
@@ -46,11 +45,9 @@ namespace Duplicati.Library.Main.Operation.Restore
///
/// Runs the volume manager process.
///
- /// The local restore database, used to find the volume where a block is stored.
- /// The backend manager, used to fetch the volumes from the backend.
+ /// The named channels for the restore operation.
/// The restore options.
- /// The restore results.
- public static Task Run(Channels channels, LocalRestoreDatabase db, IBackendManager backend, Options options, RestoreResults results)
+ public static Task Run(Channels channels, Options options)
{
return AutomationExtensions.RunTask(
new
@@ -106,13 +103,7 @@ namespace Duplicati.Library.Main.Operation.Restore
}
else
{
- sw_query?.Start();
- var (volume_name, volume_size, volume_hash) = db.GetVolumeInfo(request.VolumeID).First();
- sw_query?.Stop();
- sw_backend?.Start();
- var handle = backend.GetAsync(volume_name, volume_hash, volume_size, results.TaskControl.TransferToken);
- sw_backend?.Stop();
- await self.DownloadRequest.WriteAsync((request.VolumeID, handle));
+ await self.DownloadRequest.WriteAsync(request.VolumeID).ConfigureAwait(false);
in_flight[request.VolumeID] = [request];
}
}
diff --git a/Duplicati/Library/Main/Operation/RestoreHandler.cs b/Duplicati/Library/Main/Operation/RestoreHandler.cs
index 86fd0030c..c119e47a3 100644
--- a/Duplicati/Library/Main/Operation/RestoreHandler.cs
+++ b/Duplicati/Library/Main/Operation/RestoreHandler.cs
@@ -339,8 +339,8 @@ namespace Duplicati.Library.Main.Operation
var filelister = Restore.FileLister.Run(channels, database, m_options, m_result);
var fileprocessors = Enumerable.Range(0, m_options.RestoreFileProcessors).Select(i => Restore.FileProcessor.Run(channels, database, fileprocessor_requests[i], fileprocessor_responses[i], m_options, m_result)).ToArray();
var blockmanager = Restore.BlockManager.Run(channels, database, m_options, fileprocessor_requests, fileprocessor_responses);
- var volumecache = Restore.VolumeManager.Run(channels, database, backendManager, m_options, m_result);
- var volumedownloaders = Enumerable.Range(0, m_options.RestoreVolumeDownloaders).Select(i => Restore.VolumeDownloader.Run(channels, database, m_options, m_result)).ToArray();
+ var volumecache = Restore.VolumeManager.Run(channels, m_options);
+ var volumedownloaders = Enumerable.Range(0, m_options.RestoreVolumeDownloaders).Select(i => Restore.VolumeDownloader.Run(channels, database, backendManager.GetBackend(), m_options, m_result)).ToArray();
var volumedecryptors = Enumerable.Range(0, m_options.RestoreVolumeDecryptors).Select(i => Restore.VolumeDecryptor.Run(channels, m_options)).ToArray();
var volumedecompressors = Enumerable.Range(0, m_options.RestoreVolumeDecompressors).Select(i => Restore.VolumeDecompressor.Run(channels, m_options)).ToArray();