2025-01-10 09:24:54 +01:00
// Copyright (C) 2025, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
2025-05-13 09:00:09 +02:00
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
2025-01-10 09:24:54 +01:00
// Software is furnished to do so, subject to the following conditions:
2025-05-13 09:00:09 +02:00
//
// The above copyright notice and this permission notice shall be included in
2025-01-10 09:24:54 +01:00
// all copies or substantial portions of the Software.
2025-05-13 09:00:09 +02:00
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
2024-05-21 15:48:29 +02:00
// DEALINGS IN THE SOFTWARE.
2024-02-28 15:45:30 +01:00
using System ;
2013-03-08 22:24:54 +01:00
using System.Collections.Generic ;
2025-02-06 21:23:07 +01:00
using System.IO ;
2013-03-08 22:24:54 +01:00
using System.Linq ;
2025-06-19 09:47:03 +02:00
using System.Threading ;
2025-01-28 08:54:50 +01:00
using System.Threading.Tasks ;
2017-01-09 11:35:38 +01:00
using Duplicati.Library.Interface ;
2013-05-08 20:17:07 +02:00
using Duplicati.Library.Main.Database ;
using Duplicati.Library.Main.Volumes ;
2021-04-04 11:17:13 -07:00
using Duplicati.Library.Utility ;
2013-03-08 22:24:54 +01:00
2013-05-08 20:17:07 +02:00
namespace Duplicati.Library.Main.Operation
2013-03-08 22:24:54 +01:00
{
2013-05-25 16:40:15 +02:00
internal class RecreateDatabaseHandler : IDisposable
2013-03-08 22:24:54 +01:00
{
2018-03-12 14:07:11 +01:00
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging . Log . LogTagFromType < RecreateDatabaseHandler >();
2018-05-23 21:18:01 -07:00
private readonly Options m_options ;
private readonly RecreateDatabaseResults m_result ;
2013-03-08 22:24:54 +01:00
2013-05-11 12:03:15 +02:00
public delegate IEnumerable < KeyValuePair < long , IParsedVolume >> NumberedFilterFilelistDelegate ( IEnumerable < IParsedVolume > filelist );
2024-05-21 15:48:29 +02:00
public delegate void BlockVolumePostProcessor ( string volumename , BlockVolumeReader reader );
2013-03-08 22:24:54 +01:00
2025-01-28 08:54:50 +01:00
public RecreateDatabaseHandler ( Options options , RecreateDatabaseResults result )
2013-07-22 16:54:19 +02:00
{
m_options = options ;
2013-05-25 16:40:15 +02:00
m_result = result ;
2013-07-22 16:54:19 +02:00
}
/// <summary>
2025-06-18 08:26:20 +02:00
/// Run the recreate procedure.
2013-07-22 16:54:19 +02:00
/// </summary>
2025-06-18 08:26:20 +02:00
/// <param name="path">Path to the database that will be created.</param>
/// <param name="backendManager">The backend manager to use for downloading files.</param>
/// <param name="filter">Filters the files in a filelist to prevent downloading unwanted data.</param>
/// <param name="filelistfilter">A filter that can be used to disregard certain remote files, intended to be used to select a certain filelist.</param>
/// <param name="blockprocessor">A callback hook that can be used to work with downloaded block volumes, intended to be use to recover data blocks while processing blocklists.</param>
2025-03-14 10:41:14 +01:00
public async Task RunAsync ( string path , IBackendManager backendManager , IFilter filter , NumberedFilterFilelistDelegate filelistfilter , BlockVolumePostProcessor blockprocessor )
2013-07-22 16:54:19 +02:00
{
2025-03-14 10:41:14 +01:00
if ( File . Exists ( path ))
2018-03-12 14:07:11 +01:00
throw new UserInformationException ( string . Format ( "Cannot recreate database because file already exists: {0}" , path ), "RecreateTargetDatabaseExists" );
2013-07-22 16:54:19 +02:00
2025-06-18 11:53:11 +02:00
await using var db =
2025-06-19 11:41:39 +02:00
await LocalDatabase . CreateLocalDatabaseAsync ( path , "Recreate" , true , null , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-03-20 15:59:14 +01:00
2025-05-13 09:04:51 +02:00
await DoRunAsync ( backendManager , db , false , filter , filelistfilter , blockprocessor ). ConfigureAwait ( false );
// Ensure database is consistent after the recreate
2025-06-12 08:34:29 +02:00
await db
2025-06-19 09:47:03 +02:00
. VerifyConsistency ( m_options . Blocksize , m_options . BlockhashSize , true , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2013-07-22 16:54:19 +02:00
}
/// <summary>
2025-06-18 08:26:20 +02:00
/// Updates a database with new path information from a remote fileset.
2013-07-22 16:54:19 +02:00
/// </summary>
2025-06-18 08:26:20 +02:00
/// <param name="backendManager">The backend manager to use for downloading files.</param>
/// <param name="filter">Filters the files in a filelist to prevent downloading unwanted data.</param>
/// <param name="filelistfilter">A filter that can be used to disregard certain remote files, intended to be used to select a certain filelist.</param>
/// <param name="blockprocessor">A callback hook that can be used to work with downloaded block volumes, intended to be use to recover data blocks while processing blocklists.</param>
2025-03-14 10:41:14 +01:00
public async Task RunUpdateAsync ( IBackendManager backendManager , Library . Utility . IFilter filter , NumberedFilterFilelistDelegate filelistfilter , BlockVolumePostProcessor blockprocessor )
2013-04-04 20:34:26 +02:00
{
2015-04-08 21:01:36 +02:00
if (! m_options . RepairOnlyPaths )
2024-07-29 05:49:04 -04:00
throw new UserInformationException ( string . Format ( "Can only update with paths, try setting --{0}" , "repair-only-paths" ), "RepairUpdateRequiresPathsOnly" );
2016-09-13 21:55:15 +02:00
2025-06-18 11:53:11 +02:00
await using var db =
2025-06-19 11:41:39 +02:00
await LocalDatabase . CreateLocalDatabaseAsync ( m_options . Dbpath , "Recreate" , true , null , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-06-19 09:47:03 +02:00
if (( await db . FindMatchingFilesets ( m_options . Time , m_options . Version , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false )). Any ())
2015-04-08 21:01:36 +02:00
{
2025-05-13 09:04:51 +02:00
if ( m_options . IgnoreUpdateIfVersionExists )
2025-04-23 17:05:40 +02:00
{
2025-05-13 09:04:51 +02:00
Logging . Log . WriteInformationMessage ( LOGTAG , "UpdateVersionAlreadyExists" , "The version(s) being updated to already exists, ignoring update request" );
return ;
2025-04-23 17:05:40 +02:00
}
2015-04-08 21:01:36 +02:00
2025-05-13 09:04:51 +02:00
throw new UserInformationException ( "The version(s) being updated to, already exists" , "UpdateVersionAlreadyExists" );
}
2016-09-13 21:55:15 +02:00
2025-05-13 09:04:51 +02:00
// Mark as incomplete
2025-06-19 09:47:03 +02:00
await db . PartiallyRecreated ( m_result . TaskControl . ProgressToken , true ). ConfigureAwait ( false );
2024-05-21 15:48:29 +02:00
2025-06-12 08:34:29 +02:00
var preexistingOptionsInDatabase =
2025-06-19 09:47:03 +02:00
await Utility . ContainsOptionsForVerification ( db , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-06-19 09:47:03 +02:00
await Utility . UpdateOptionsFromDb ( db , m_options , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2024-05-21 15:48:29 +02:00
2025-05-13 09:04:51 +02:00
// Make sure the options have not changed between calls, unless there are no previous options
if ( preexistingOptionsInDatabase )
2025-06-19 09:47:03 +02:00
await Utility . VerifyOptionsAndUpdateDatabase ( db , m_options , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-05-13 09:04:51 +02:00
await DoRunAsync ( backendManager , db , true , filter , filelistfilter , blockprocessor ). ConfigureAwait ( false );
2015-04-08 21:01:36 +02:00
}
2013-03-08 22:24:54 +01:00
2015-04-08 21:01:36 +02:00
/// <summary>
2025-06-18 08:26:20 +02:00
/// Run the recreate procedure.
2015-04-08 21:01:36 +02:00
/// </summary>
2025-06-18 08:26:20 +02:00
/// <param name="backendManager">The backend manager to use for downloading files.</param>
/// <param name="dbparent">The database to restore into.</param>
/// <param name="updating">True if this is an update call, false otherwise.</param>
/// <param name="filter">A filter that can be used to disregard certain remote files, intended to be used to select a certain filelist.</param>
/// <param name="filelistfilter">Filters the files in a filelist to prevent downloading unwanted data.</param>
/// <param name="blockprocessor">A callback hook that can be used to work with downloaded block volumes, intended to be use to recover data blocks while processing blocklists.</param>
/// <returns>A task that completes when the operation is done.</returns>
2025-03-14 10:41:14 +01:00
internal async Task DoRunAsync ( IBackendManager backendManager , LocalDatabase dbparent , bool updating , IFilter filter = null , NumberedFilterFilelistDelegate filelistfilter = null , BlockVolumePostProcessor blockprocessor = null )
2015-04-08 21:01:36 +02:00
{
2014-08-26 15:01:56 +02:00
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Recreate_Running );
2013-03-08 22:24:54 +01:00
//We build a local database in steps.
2025-06-18 11:53:11 +02:00
await using var restoredb =
2025-06-19 09:47:03 +02:00
await LocalRecreateDatabase . CreateAsync ( dbparent , m_options , null , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-06-19 09:47:03 +02:00
await restoredb . RepairInProgress ( m_result . TaskControl . ProgressToken , true ). ConfigureAwait ( false );
2026-03-14 12:02:53 +01:00
// If a filelist filter is applied, we're only recreating a partial database (e.g., for a specific version restore).
// Mark it as partially recreated so it cannot be used for backups or other operations that require a complete database.
if ( filelistfilter != null )
await restoredb . PartiallyRecreated ( m_result . TaskControl . ProgressToken , true ). ConfigureAwait ( false );
2025-05-13 09:04:51 +02:00
var expRecreateDb = false ; // experimental recreate db code flag
var volumeIds = new Dictionary < string , long >();
if ( string . Equals ( Environment . GetEnvironmentVariable ( "EXPERIMENTAL_RECREATEDB_DUPLICATI" ) ?? string . Empty , "1" ))
2013-03-08 22:24:54 +01:00
{
2025-05-13 09:04:51 +02:00
expRecreateDb = true ;
}
2024-05-21 15:48:29 +02:00
2025-05-13 09:04:51 +02:00
var rawlist = await backendManager . ListAsync ( m_result . TaskControl . ProgressToken ). ConfigureAwait ( false );
2013-04-04 20:34:26 +02:00
2025-05-13 09:04:51 +02:00
//First step is to examine the remote storage to see what
// kind of data we can find
var remotefiles =
( from x in rawlist
let n = VolumeBase . ParseFilename ( x )
where
n != null
&&
n . Prefix == m_options . Prefix
select n ). ToArray (); //ToArray() ensures that we do not remote-request it multiple times
2024-05-21 15:48:29 +02:00
2025-05-13 09:04:51 +02:00
if ( remotefiles . Length == 0 )
{
if (! rawlist . Any ())
throw new UserInformationException ( "No files were found at the remote location, perhaps the target url is incorrect?" , "EmptyRemoteLocation" );
else
{
var tmp =
2013-07-22 16:54:19 +02:00
( from x in rawlist
2024-05-21 15:48:29 +02:00
let n = VolumeBase . ParseFilename ( x )
where
n != null
2025-05-13 09:04:51 +02:00
select n . Prefix ). ToArray ();
2013-07-22 16:54:19 +02:00
2025-05-13 09:04:51 +02:00
var types = tmp . Distinct (). ToArray ();
if ( tmp . Length == 0 )
throw new UserInformationException ( string . Format ( "Found {0} files at the remote storage, but none that could be parsed" , rawlist . Count ()), "EmptyRemoteLocation" );
else if ( types . Length == 1 )
throw new UserInformationException ( string . Format ( "Found {0} parse-able files with the prefix {1}, did you forget to set the backup prefix?" , tmp . Length , types [ 0 ]), "EmptyRemoteLocationWithPrefix" );
2013-07-22 16:54:19 +02:00
else
2025-05-13 09:04:51 +02:00
throw new UserInformationException ( string . Format ( "Found {0} parse-able files (of {1} files) with different prefixes: {2}, did you forget to set the backup prefix?" , tmp . Length , rawlist . Count (), string . Join ( ", " , types )), "EmptyRemoteLocationWithPrefix" );
2013-07-22 16:54:19 +02:00
}
2025-05-13 09:04:51 +02:00
}
2013-04-08 22:21:45 +02:00
2025-05-13 09:04:51 +02:00
if ( string . IsNullOrWhiteSpace ( m_options . Passphrase ) && remotefiles . Any ( x => ! string . IsNullOrWhiteSpace ( x . EncryptionModule )))
throw new UserInformationException ( "The remote files are encrypted, but no passphrase was provided" , "MissingPassphrase" );
2025-04-03 17:07:28 +02:00
2025-05-13 09:04:51 +02:00
//Then we select the filelist we should work with,
// and create the filelist table to fit
IEnumerable < IParsedVolume > filelists =
from n in remotefiles
where n . FileType == RemoteVolumeType . Files
orderby n . Time descending
select n ;
2013-03-08 22:24:54 +01:00
2025-05-13 09:04:51 +02:00
if (! filelists . Any ())
throw new UserInformationException ( "No filelists found on the remote destination" , "EmptyRemoteLocation" );
2024-05-21 15:48:29 +02:00
2025-05-13 09:04:51 +02:00
if ( filelistfilter != null )
filelists = filelistfilter ( filelists ). Select ( x => x . Value ). ToArray ();
2013-03-08 22:24:54 +01:00
2025-05-13 09:04:51 +02:00
if (! filelists . Any ())
throw new UserInformationException ( "No filelists" , "NoMatchingRemoteFilelists" );
2015-08-26 12:38:49 +01:00
2025-05-13 09:04:51 +02:00
// If we are updating, all files should be accounted for
foreach ( var fl in remotefiles )
2025-06-12 08:34:29 +02:00
volumeIds [ fl . File . Name ] = updating
2025-06-19 09:47:03 +02:00
? await restoredb . GetRemoteVolumeID ( fl . File . Name , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false )
2025-06-19 09:47:03 +02:00
: await restoredb . RegisterRemoteVolume ( fl . File . Name , fl . FileType , fl . File . Size , RemoteVolumeState . Uploaded , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2013-07-22 16:54:19 +02:00
2025-05-13 09:04:51 +02:00
var hasUpdatedOptions = false ;
2015-04-08 21:01:36 +02:00
2025-06-03 21:44:55 +02:00
// Record all blocksets and files needed
2025-05-22 06:14:51 +02:00
var filelistWork = ( from n in filelists orderby n . Time select new RemoteVolume ( n . File ) as IRemoteVolume ). ToList ();
Logging . Log . WriteInformationMessage ( LOGTAG , "RebuildStarted" , "Rebuild database started, downloading {0} filelists" , filelistWork . Count );
2025-05-13 09:04:51 +02:00
2025-05-22 06:14:51 +02:00
var progress = 0 ;
2025-05-13 09:04:51 +02:00
2025-05-22 06:14:51 +02:00
// Register the files we are working with, if not already updated
if ( updating )
{
foreach ( var n in filelists )
if ( volumeIds [ n . File . Name ] == - 1 )
2025-06-12 08:34:29 +02:00
volumeIds [ n . File . Name ] = await restoredb
2025-06-19 09:47:03 +02:00
. RegisterRemoteVolume ( n . File . Name , n . FileType , RemoteVolumeState . Uploaded , n . File . Size , new TimeSpan ( 0 ), m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-05-22 06:14:51 +02:00
}
2016-03-18 13:25:20 +01:00
2025-05-22 06:14:51 +02:00
var isFirstFilelist = true ;
await foreach ( var ( tmpfile , hash , size , name ) in backendManager . GetFilesOverlappedAsync ( filelistWork , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false ))
{
var entry = new RemoteVolume ( name , hash , size );
try
2025-05-13 09:04:51 +02:00
{
2025-05-22 06:14:51 +02:00
if (! await m_result . TaskControl . ProgressRendevouz (). ConfigureAwait ( false ))
2025-05-13 09:04:51 +02:00
{
2025-05-22 06:14:51 +02:00
await backendManager . WaitForEmptyAsync ( restoredb , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false );
m_result . EndTime = DateTime . UtcNow ;
2025-06-11 11:48:16 +02:00
// Implicit rollback
2025-06-12 08:34:29 +02:00
await restoredb . Transaction
2025-06-19 09:47:03 +02:00
. RollBackAsync ( m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-05-22 06:14:51 +02:00
return ;
}
2025-05-13 09:04:51 +02:00
2025-05-22 06:14:51 +02:00
progress ++;
if ( filelistWork . Count == 1 && m_options . RepairOnlyPaths )
{
m_result . OperationProgressUpdater . UpdateProgress ( 0.5f );
}
else
{
m_result . OperationProgressUpdater . UpdateProgress ((( float ) progress / filelistWork . Count ()) * ( m_options . RepairOnlyPaths ? 1f : 0.2f ));
Logging . Log . WriteVerboseMessage ( LOGTAG , "ProcessingFilelistVolumes" , "Processing filelist volume {0} of {1}" , progress , filelistWork . Count );
}
2014-08-26 15:01:56 +02:00
2025-05-22 06:14:51 +02:00
using ( tmpfile )
{
isFirstFilelist = false ;
2025-05-13 09:04:51 +02:00
2025-05-22 06:14:51 +02:00
if (! string . IsNullOrWhiteSpace ( hash ) && size > 0 )
2025-06-12 08:34:29 +02:00
await restoredb
2025-06-19 09:47:03 +02:00
. UpdateRemoteVolume ( entry . Name , RemoteVolumeState . Verified , size , hash , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-05-13 09:04:51 +02:00
2025-05-22 06:14:51 +02:00
var parsed = VolumeBase . ParseFilename ( entry . Name );
2025-05-13 09:04:51 +02:00
2025-05-22 06:14:51 +02:00
using var stream = new FileStream ( tmpfile , FileMode . Open , FileAccess . Read , FileShare . Read );
using var compressor = DynamicLoader . CompressionLoader . GetModule ( parsed . CompressionModule , stream , ArchiveMode . Read , m_options . RawOptions );
if ( compressor == null )
throw new UserInformationException ( string . Format ( "Failed to load compression module: {0}" , parsed . CompressionModule ), "FailedToLoadCompressionModule" );
2025-05-13 09:04:51 +02:00
2025-05-22 06:14:51 +02:00
if (! hasUpdatedOptions )
{
VolumeReaderBase . UpdateOptionsFromManifest ( compressor , m_options );
hasUpdatedOptions = true ;
}
2025-05-13 09:04:51 +02:00
2025-05-22 06:14:51 +02:00
// Create timestamped operations based on the file timestamp
2025-06-12 08:34:29 +02:00
var filesetid = await restoredb
2025-06-19 09:47:03 +02:00
. CreateFileset ( volumeIds [ entry . Name ], parsed . Time , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-05-13 09:04:51 +02:00
2025-06-19 09:47:03 +02:00
await RecreateFilesetFromRemoteList ( restoredb , compressor , filesetid , m_options , filter , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-05-13 09:04:51 +02:00
}
2025-05-22 06:14:51 +02:00
}
catch ( Exception ex )
{
Logging . Log . WriteWarningMessage ( LOGTAG , "FileProcessingFailed" , ex , "Failed to process file: {0}" , entry . Name );
if ( ex . IsAbortException ())
2015-10-26 09:14:44 +01:00
{
2025-05-22 06:14:51 +02:00
m_result . EndTime = DateTime . UtcNow ;
2025-06-11 11:48:16 +02:00
// Implicit rollback
2025-06-12 08:34:29 +02:00
await restoredb . Transaction
2025-06-19 09:47:03 +02:00
. RollBackAsync ( m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-05-22 06:14:51 +02:00
throw ;
}
2025-05-13 09:04:51 +02:00
2025-05-22 06:14:51 +02:00
if ( isFirstFilelist && ex is System . Security . Cryptography . CryptographicException )
{
m_result . EndTime = DateTime . UtcNow ;
2025-06-11 11:48:16 +02:00
// Implicit rollback
2025-06-12 08:34:29 +02:00
await restoredb . Transaction
2025-06-19 09:47:03 +02:00
. RollBackAsync ( m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-05-22 06:14:51 +02:00
throw ;
}
2025-05-13 09:04:51 +02:00
2025-05-22 06:14:51 +02:00
if ( m_options . UnittestMode )
2025-06-03 21:44:55 +02:00
{
2025-06-11 11:48:16 +02:00
// Implicit rollback
2025-06-12 08:34:29 +02:00
await restoredb . Transaction
2025-06-19 09:47:03 +02:00
. RollBackAsync ( m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-05-22 06:14:51 +02:00
throw ;
2025-06-03 21:44:55 +02:00
}
2025-05-13 09:04:51 +02:00
}
2025-05-22 06:14:51 +02:00
}
2025-05-13 09:04:51 +02:00
2025-05-22 06:14:51 +02:00
//Make sure we write the config if it has been read from a manifest
if ( hasUpdatedOptions )
2025-06-19 09:47:03 +02:00
await Utility . VerifyOptionsAndUpdateDatabase ( restoredb , m_options , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-05-13 09:04:51 +02:00
2025-05-22 06:14:51 +02:00
using ( new Logging . Timer ( LOGTAG , "CommitUpdateFilesetFromRemote" , "CommitUpdateFilesetFromRemote" ))
2025-06-12 08:34:29 +02:00
await restoredb . Transaction
2025-06-19 09:47:03 +02:00
. CommitAsync ( m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2024-05-21 15:48:29 +02:00
2025-05-13 09:04:51 +02:00
// do we stop after just handling the dlist files ?
// (if yes, we never will be able to do a backup !)
if (! m_options . RepairOnlyPaths )
{
var hashsize = 0 ;
//Grab all index files, and update the block table
using ( var hashalg = HashFactory . CreateHasher ( m_options . BlockHashAlgorithm ))
{
hashsize = hashalg . HashSize / 8 ;
var indexfiles = (
from n in remotefiles
where n . FileType == RemoteVolumeType . Index
select new RemoteVolume ( n . File ) as IRemoteVolume ). ToList ();
Logging . Log . WriteInformationMessage ( LOGTAG , "FilelistsRestored" , "Filelists restored, downloading {0} index files" , indexfiles . Count );
2025-05-22 06:14:51 +02:00
progress = 0 ;
2025-05-13 09:04:51 +02:00
await foreach ( var ( tmpfile , hash , size , name ) in backendManager . GetFilesOverlappedAsync ( indexfiles , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false ))
2025-01-28 08:54:50 +01:00
{
2013-07-22 16:54:19 +02:00
try
2013-03-08 22:24:54 +01:00
{
2025-01-28 08:54:50 +01:00
if (! await m_result . TaskControl . ProgressRendevouz (). ConfigureAwait ( false ))
2014-05-15 12:47:16 +02:00
{
2025-05-22 06:14:51 +02:00
await backendManager . WaitForEmptyAsync ( restoredb , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false );
2016-12-01 23:59:54 +01:00
m_result . EndTime = DateTime . UtcNow ;
2025-06-11 11:48:16 +02:00
// Implicit rollback
2025-06-12 08:34:29 +02:00
await restoredb . Transaction
2025-06-19 09:47:03 +02:00
. RollBackAsync ( m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2014-05-15 12:47:16 +02:00
return ;
2019-09-11 03:43:54 -07:00
}
2014-08-26 15:01:56 +02:00
progress ++;
2025-05-13 09:04:51 +02:00
m_result . OperationProgressUpdater . UpdateProgress (((( float ) progress / indexfiles . Count ) * 0.5f ) + 0.2f );
Logging . Log . WriteVerboseMessage ( LOGTAG , "ProcessingIndexlistVolumes" , "Processing indexlist volume {0} of {1}" , progress , indexfiles . Count );
2014-08-26 15:01:56 +02:00
2025-01-28 08:54:50 +01:00
using ( tmpfile )
2013-03-08 22:24:54 +01:00
{
2025-01-28 08:54:50 +01:00
if (! string . IsNullOrWhiteSpace ( hash ) && size > 0 )
2025-06-12 08:34:29 +02:00
await restoredb
2025-06-19 09:47:03 +02:00
. UpdateRemoteVolume ( name , RemoteVolumeState . Verified , size , hash , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2013-03-08 22:24:54 +01:00
2025-05-13 09:04:51 +02:00
using ( var svr = new IndexVolumeReader ( RestoreHandler . GetCompressionModule ( name ), tmpfile , m_options , hashsize ))
{
foreach ( var a in svr . Volumes )
{
var filename = a . Filename ;
2025-06-12 08:34:29 +02:00
var volumeID = await restoredb
2025-06-19 09:47:03 +02:00
. GetRemoteVolumeID ( filename , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2015-04-08 21:01:36 +02:00
2025-05-13 09:04:51 +02:00
// No such file
if ( volumeID < 0 )
2025-06-12 08:34:29 +02:00
( volumeID , filename ) =
2025-06-19 09:47:03 +02:00
await ProbeForMatchingFilename ( filename , restoredb , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-02-06 21:23:07 +01:00
2025-05-13 09:04:51 +02:00
var missing = false ;
// Still broken, register a missing item
if ( volumeID < 0 )
{
var p = VolumeBase . ParseFilename ( filename );
if ( p == null )
throw new Exception ( string . Format ( "Unable to parse filename: {0}" , filename ));
Logging . Log . WriteWarningMessage ( LOGTAG , "MissingFileDetected" , null , "Remote file referenced as {0} by {1}, but not found in list, registering a missing remote file" , filename , name );
missing = true ;
2025-06-12 08:34:29 +02:00
volumeID = await restoredb
2025-06-19 09:47:03 +02:00
. RegisterRemoteVolume ( filename , p . FileType , RemoteVolumeState . Temporary , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-05-13 09:04:51 +02:00
}
bool anyChange = false ;
//Add all block/volume mappings
foreach ( var b in a . Blocks )
{
2025-06-19 09:47:03 +02:00
anyChange |= ( await restoredb . UpdateBlock ( b . Key , b . Value , volumeID , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false )). Item1 ;
2025-05-13 09:04:51 +02:00
}
2015-04-08 21:01:36 +02:00
2025-06-12 08:34:29 +02:00
await restoredb
2025-06-19 09:47:03 +02:00
. UpdateRemoteVolume ( filename , missing ? RemoteVolumeState . Temporary : RemoteVolumeState . Verified , a . Length , a . Hash , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
await restoredb
. AddIndexBlockLink (
2025-06-19 09:47:03 +02:00
await restoredb . GetRemoteVolumeID ( name , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false ),
volumeID ,
m_result . TaskControl . ProgressToken
2025-06-12 08:34:29 +02:00
)
. ConfigureAwait ( false );
2025-05-13 09:04:51 +02:00
}
2024-05-21 15:48:29 +02:00
2025-05-13 09:04:51 +02:00
//If there are blocklists in the index file, add them to the temp blocklist hashes table
int wrongHashes = 0 ;
foreach ( var b in svr . BlockLists )
{
// Compact might have created undetected invalid blocklist entries in index files due to broken LocalDatabase.GetBlocklists
// If the hash is wrong, recreate will download the dblock volume with the correct file
try
{
// We need to instantiate the list to ensure the verification is
// done before we add it to the database, since we do not have nested transactions
var list = b . Blocklist . ToList ();
2025-06-12 08:34:29 +02:00
await restoredb
2025-06-19 09:47:03 +02:00
. AddTempBlockListHash ( b . Hash , list , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-05-13 09:04:51 +02:00
}
catch ( System . IO . InvalidDataException e )
{
Logging . Log . WriteVerboseMessage ( LOGTAG , "InvalidDataBlocklist" , e , "Exception while processing blocklists in {0}" , name );
++ wrongHashes ;
}
}
if ( wrongHashes != 0 )
{
Logging . Log . WriteWarningMessage ( LOGTAG , "WrongBlocklistHashes" , null , "{0} had invalid blocklists which could not be used. Consider deleting this index file and run repair to recreate it." , name );
}
}
2013-03-08 22:24:54 +01:00
}
}
2013-07-22 16:54:19 +02:00
catch ( Exception ex )
2013-04-04 20:34:26 +02:00
{
2025-05-13 09:04:51 +02:00
//Not fatal
Logging . Log . WriteErrorMessage ( LOGTAG , "IndexFileProcessingFailed" , ex , "Failed to process index file: {0}" , name );
2025-03-20 22:32:04 +01:00
if ( ex . IsAbortException ())
2016-12-01 23:59:54 +01:00
{
m_result . EndTime = DateTime . UtcNow ;
2025-06-11 11:48:16 +02:00
// Implicit rollback
2025-06-12 08:34:29 +02:00
await restoredb . Transaction
2025-06-19 09:47:03 +02:00
. RollBackAsync ( m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2014-05-15 12:47:16 +02:00
throw ;
2016-12-01 23:59:54 +01:00
}
2016-03-13 17:01:35 +01:00
2018-06-12 09:31:39 +02:00
if ( m_options . UnittestMode )
2025-06-03 21:44:55 +02:00
{
2025-06-11 11:48:16 +02:00
// Implicit rollback
2025-06-12 08:34:29 +02:00
await restoredb . Transaction
2025-06-19 09:47:03 +02:00
. RollBackAsync ( m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2018-06-12 09:31:39 +02:00
throw ;
2025-06-03 21:44:55 +02:00
}
2013-07-22 16:54:19 +02:00
}
2025-01-28 08:54:50 +01:00
}
2015-04-08 21:01:36 +02:00
2025-05-13 09:04:51 +02:00
using ( new Logging . Timer ( LOGTAG , "CommitRecreateDb" , "CommitRecreatedDb" ))
2025-06-12 08:34:29 +02:00
await restoredb . Transaction
2025-06-19 09:47:03 +02:00
. CommitAsync ( m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2015-04-08 21:01:36 +02:00
2025-05-13 09:04:51 +02:00
// TODO: In some cases, we can avoid downloading all index files,
// if we are lucky and pick the right ones
2013-07-22 16:54:19 +02:00
}
2024-05-21 15:48:29 +02:00
2025-06-19 09:47:03 +02:00
await restoredb . CleanupMissingVolumes ( m_result . TaskControl . ProgressToken ). ConfigureAwait ( false );
2015-04-08 21:01:36 +02:00
2025-05-13 09:04:51 +02:00
// Update the real tables from the temp tables
if ( expRecreateDb )
// add missing blocks and blocksetentry data (at this point
// we have not yet anything in the blocksetentry table)
2025-06-12 08:34:29 +02:00
await restoredb
2025-06-19 09:47:03 +02:00
. AddBlockAndBlockSetEntryFromTemp ( hashsize , m_options . Blocksize , false , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-05-13 09:04:51 +02:00
else
2025-06-12 08:34:29 +02:00
await restoredb
2025-06-19 09:47:03 +02:00
. FindMissingBlocklistHashes ( hashsize , m_options . Blocksize , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2016-03-18 13:25:20 +01:00
2025-05-13 09:04:51 +02:00
// We have now grabbed as much information as possible,
// if we are still missing data, we must now fetch block files
//We do this in three passes
for ( var i = 0 ; i < 3 ; i ++)
{
// Grab the list matching the pass type
2025-06-12 08:34:29 +02:00
var lst = await restoredb
2025-06-19 09:47:03 +02:00
. GetMissingBlockListVolumes ( i , m_options . Blocksize , hashsize , m_options . RepairForceBlockUse , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ToListAsync ()
. ConfigureAwait ( false );
2025-05-22 14:09:17 +02:00
if ( lst . Count == 0 )
continue ;
2016-03-18 13:25:20 +01:00
2025-05-22 14:09:17 +02:00
var fullist = ": " + string . Join ( ", " , lst . Select ( x => x . Name ));
switch ( i )
2025-05-13 09:04:51 +02:00
{
2025-05-22 14:09:17 +02:00
case 0 :
Logging . Log . WriteVerboseMessage ( LOGTAG , "ProcessingRequiredBlocklistVolumes" , "Processing required {0} blocklist volumes{1}" , lst . Count , fullist );
Logging . Log . WriteInformationMessage ( LOGTAG , "ProcessingRequiredBlocklistVolumes" , "Processing required {0} blocklist volumes{1}" , lst . Count , m_options . FullResult ? fullist : string . Empty );
break ;
case 1 :
Logging . Log . WriteVerboseMessage ( LOGTAG , "ProbingCandicateBlocklistVolumes" , "Probing {0} candidate blocklist volumes{1}" , lst . Count , fullist );
Logging . Log . WriteInformationMessage ( LOGTAG , "ProbingCandicateBlocklistVolumes" , "Probing {0} candidate blocklist volumes{1}" , lst . Count , m_options . FullResult ? fullist : string . Empty );
break ;
default :
Logging . Log . WriteVerboseMessage ( LOGTAG , "ProcessingAllBlocklistVolumes" , "Processing all of the {0} volumes for blocklists{1}" , lst . Count , fullist );
Logging . Log . WriteInformationMessage ( LOGTAG , "ProcessingAllBlocklistVolumes" , "Processing all of the {0} volumes for blocklists{1}" , lst . Count , m_options . FullResult ? fullist : string . Empty );
break ;
2025-05-13 09:04:51 +02:00
}
2024-05-21 15:48:29 +02:00
2025-05-22 06:14:51 +02:00
progress = 0 ;
2025-05-13 09:04:51 +02:00
await foreach ( var ( tmpfile , hash , size , name ) in backendManager . GetFilesOverlappedAsync ( lst , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false ))
{
try
2025-01-28 08:54:50 +01:00
{
2025-05-13 09:04:51 +02:00
using ( tmpfile )
using ( var rd = new BlockVolumeReader ( RestoreHandler . GetCompressionModule ( name ), tmpfile , m_options ))
2014-05-15 12:47:16 +02:00
{
2025-01-28 08:54:50 +01:00
if (! await m_result . TaskControl . ProgressRendevouz (). ConfigureAwait ( false ))
2015-04-08 21:01:36 +02:00
{
2025-05-22 06:14:51 +02:00
await backendManager . WaitForEmptyAsync ( restoredb , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false );
2016-12-01 23:59:54 +01:00
m_result . EndTime = DateTime . UtcNow ;
2025-06-11 11:48:16 +02:00
// Implicit rollback
2025-06-12 08:34:29 +02:00
await restoredb . Transaction
2025-06-19 09:47:03 +02:00
. RollBackAsync ( m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2015-04-08 21:01:36 +02:00
return ;
}
2014-08-26 15:01:56 +02:00
2015-04-08 21:01:36 +02:00
progress ++;
2025-05-13 09:04:51 +02:00
m_result . OperationProgressUpdater . UpdateProgress (((( float ) progress / lst . Count ) * 0.1f ) + 0.7f + ( i * 0.1f ));
Logging . Log . WriteVerboseMessage ( LOGTAG , "ProcessingBlocklistVolumes" , "Pass {0} of 3, processing blocklist volume {1} of {2}" , ( i + 1 ), progress , lst . Count );
2014-08-26 15:01:56 +02:00
2025-06-12 08:34:29 +02:00
var volumeid = await restoredb
2025-06-19 09:47:03 +02:00
. GetRemoteVolumeID ( name , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2024-05-21 15:48:29 +02:00
2025-06-12 08:34:29 +02:00
await restoredb
2025-06-19 09:47:03 +02:00
. UpdateRemoteVolume ( name , RemoteVolumeState . Uploaded , size , hash , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2023-11-30 22:37:07 +01:00
2025-05-13 09:04:51 +02:00
bool anyChange = false ;
// Update the block table so we know about the block/volume map
foreach ( var h in rd . Blocks )
2016-12-01 23:59:54 +01:00
{
2025-06-19 09:47:03 +02:00
anyChange |= ( await restoredb . UpdateBlock ( h . Key , h . Value , volumeid , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false )). Item1 ;
2016-12-01 23:59:54 +01:00
}
2018-06-12 09:31:39 +02:00
2025-05-13 09:04:51 +02:00
// now that we have the blocks/volume relationships, we can go from the (already known from dlist step) blocklisthashes
// to the needed list blocks in the volume, so grab them from the database
// read the blocks list hashes from the volume data (the handled file) and insert them into the temp blocklisthash table
2025-06-19 09:47:03 +02:00
await foreach ( var blocklisthash in restoredb . GetBlockLists ( volumeid , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false ))
2015-04-08 21:01:36 +02:00
{
2025-06-19 09:47:03 +02:00
if ( await restoredb . AddTempBlockListHash ( blocklisthash , rd . ReadBlocklist ( blocklisthash , hashsize ), m_result . TaskControl . ProgressToken ). ConfigureAwait ( false ))
2018-06-11 14:42:13 +02:00
{
2025-05-13 09:04:51 +02:00
anyChange = true ;
2024-05-21 15:48:29 +02:00
}
2025-05-13 09:04:51 +02:00
}
2024-05-21 15:48:29 +02:00
2025-05-13 09:04:51 +02:00
// Update tables if necessary (if no block or hash have been changed by a data volume
// there is no need to run expensive queries - most data volumes have been
// managed successfully by correct index volumes), so we know if we are done
// if any change, add to the block and blocksetentry tables the references found in
// the block lists of the volume saved in the temp blocklisthash table by AddTempBLockListHash
if ( anyChange )
{
if ( i == 2 )
2024-05-21 15:48:29 +02:00
{
2025-05-13 09:04:51 +02:00
Logging . Log . WriteWarningMessage ( LOGTAG , "UpdatingTables" , null , "Unexpected changes caused by block {0}" , name );
2024-05-21 15:48:29 +02:00
}
2025-05-13 09:04:51 +02:00
if ( expRecreateDb )
2025-06-12 08:34:29 +02:00
await restoredb
2025-06-19 09:47:03 +02:00
. AddBlockAndBlockSetEntryFromTemp ( hashsize , m_options . Blocksize , false , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-05-13 09:04:51 +02:00
else
2025-06-12 08:34:29 +02:00
await restoredb
2025-06-19 09:47:03 +02:00
. FindMissingBlocklistHashes ( hashsize , m_options . Blocksize , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-05-13 09:04:51 +02:00
}
2024-05-21 15:48:29 +02:00
2025-05-13 09:04:51 +02:00
using ( new Logging . Timer ( LOGTAG , "CommitRestoredBlocklist" , "CommitRestoredBlocklist" ))
2025-06-12 08:34:29 +02:00
await restoredb . Transaction
2025-06-19 09:47:03 +02:00
. CommitAsync ( m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2018-06-11 14:42:13 +02:00
2025-05-13 09:04:51 +02:00
//At this point we can patch files with data from the block volume
if ( blockprocessor != null )
blockprocessor ( name , rd );
2015-04-08 21:01:36 +02:00
}
2018-06-11 14:42:13 +02:00
}
2025-05-13 09:04:51 +02:00
catch ( Exception ex )
{
Logging . Log . WriteWarningMessage ( LOGTAG , "FailedRebuildingWithFile" , ex , "Failed to use information from {0} to rebuild database: {1}" , name , ex . Message );
if ( m_options . UnittestMode )
2025-06-03 21:44:55 +02:00
{
2025-06-11 11:48:16 +02:00
// Implicit rollback
2025-06-12 08:34:29 +02:00
await restoredb . Transaction
2025-06-19 09:47:03 +02:00
. RollBackAsync ( m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-05-13 09:04:51 +02:00
throw ;
2025-06-03 21:44:55 +02:00
}
2025-05-13 09:04:51 +02:00
}
2015-04-08 21:01:36 +02:00
}
2013-07-22 16:54:19 +02:00
}
2025-05-13 09:04:51 +02:00
}
2018-05-10 00:57:51 +02:00
2025-05-22 06:14:51 +02:00
await backendManager . WaitForEmptyAsync ( restoredb , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false );
2024-11-01 15:56:14 +01:00
2025-05-13 09:04:51 +02:00
if (! m_options . RepairOnlyPaths )
{
// All blocks are collected and added into the Block table
// Find out which blocks are deleted and move them into DeletedBlock,
// so that compact can calculate the unused space
2025-06-19 09:47:03 +02:00
await restoredb . CleanupDeletedBlocks ( m_result . TaskControl . ProgressToken ). ConfigureAwait ( false );
2025-05-13 09:04:51 +02:00
}
2013-04-21 20:00:37 +02:00
2025-06-19 09:47:03 +02:00
await restoredb . CleanupMissingVolumes ( m_result . TaskControl . ProgressToken ). ConfigureAwait ( false );
2016-03-18 13:25:20 +01:00
2026-01-14 09:58:19 +01:00
if (! m_options . RepairOnlyPaths && m_options . StoreMetadataContentInDatabase )
{
await RestoreMetadataContentAsync ( backendManager , restoredb , m_result . TaskControl . ProgressToken )
. ConfigureAwait ( false );
// The metadata content restore is executed after the main recreate commits.
// Ensure the restored Metadataset.Content values are persisted.
await restoredb . Transaction
. CommitAsync ( m_result . TaskControl . ProgressToken )
. ConfigureAwait ( false );
}
2025-05-13 09:04:51 +02:00
if ( m_options . RepairOnlyPaths )
{
Logging . Log . WriteInformationMessage ( LOGTAG , "RecreateOrUpdateOnly" , "Recreate/path-update completed, not running consistency checks" );
}
else
{
Logging . Log . WriteInformationMessage ( LOGTAG , "RecreateCompletedCheckingDatabase" , "Recreate completed, verifying the database consistency" );
2017-01-05 20:58:33 +01:00
2025-05-13 09:04:51 +02:00
//All done, we must verify that we have all blocklist fully intact
// if this fails, the db will not be deleted, so it can be used,
// except to continue a backup
m_result . EndTime = DateTime . UtcNow ;
2017-01-05 20:58:33 +01:00
2025-06-19 09:47:03 +02:00
await using ( var lbfdb = await LocalListBrokenFilesDatabase . CreateAsync ( restoredb , null , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false ))
2025-05-13 09:04:51 +02:00
{
2025-06-12 08:34:29 +02:00
var broken = await lbfdb
2025-06-19 09:47:03 +02:00
. GetBrokenFilesets ( new DateTime ( 0 ), null , m_result . TaskControl . ProgressToken )
. CountAsync ( cancellationToken : m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-05-13 09:04:51 +02:00
if ( broken != 0 )
throw new UserInformationException ( string . Format ( "Recreated database has missing blocks and {0} broken filelists. Consider using \"{1}\" and \"{2}\" to purge broken data from the remote store and the database." , broken , "list-broken-files" , "purge-broken-files" ), "DatabaseIsBrokenConsiderPurge" );
}
2016-03-18 13:25:20 +01:00
2026-03-14 12:02:53 +01:00
// When a filelist filter is applied, we are intentionally only recreating a subset
// of the database (e.g., restoring a specific version with --no-local-db).
// In this case, we need to use the lax consistency check that allows volumes
// without filesets, as only the selected filesets are being recreated.
if ( filelistfilter != null )
{
await restoredb
. VerifyConsistencyForRepair ( m_options . Blocksize , m_options . BlockhashSize , true , m_result . TaskControl . ProgressToken )
. ConfigureAwait ( false );
}
else
{
await restoredb
. VerifyConsistency ( m_options . Blocksize , m_options . BlockhashSize , true , m_result . TaskControl . ProgressToken )
. ConfigureAwait ( false );
}
2016-03-18 13:25:20 +01:00
2025-05-13 09:04:51 +02:00
Logging . Log . WriteInformationMessage ( LOGTAG , "RecreateCompleted" , "Recreate completed, and consistency checks completed, marking database as complete" );
2016-12-01 23:59:54 +01:00
2025-06-19 09:47:03 +02:00
await restoredb . RepairInProgress ( m_result . TaskControl . ProgressToken , false ). ConfigureAwait ( false );
2013-03-08 22:24:54 +01:00
}
2025-05-13 09:04:51 +02:00
m_result . EndTime = DateTime . UtcNow ;
2013-03-08 22:24:54 +01:00
}
2026-01-14 09:58:19 +01:00
private sealed record MetadataBlockRef ( long MetadataId , long BlockIndex , string BlockHash , long BlockSize );
private sealed class MetadataRecreateState
{
public long MetadataId { get ; }
public long MetaLength { get ; }
public int ExpectedBlockCount { get ; set ; }
// Index -> block bytes
public SortedDictionary < long , byte []> Blocks { get ; } = new ();
public MetadataRecreateState ( long metadataId , long metaLength )
{
MetadataId = metadataId ;
MetaLength = metaLength ;
}
}
private async Task RestoreMetadataContentAsync ( IBackendManager backendManager , LocalRecreateDatabase restoredb , CancellationToken cancellationToken )
{
// Metadata content restoration requires the block structure to be present.
if ( m_options . RepairOnlyPaths )
return ;
// Gather block references for each metadataset that is missing content.
var volumeBlocks = new Dictionary < string , List < MetadataBlockRef >>( StringComparer . Ordinal );
var volumeInfo = new Dictionary < string , RemoteVolume >( StringComparer . Ordinal );
var metaStates = new Dictionary < long , MetadataRecreateState >();
await foreach ( var info in restoredb . GetMissingMetadataBlocks ( cancellationToken ). ConfigureAwait ( false ))
{
var metadataId = info . MetadataId ;
var metaLength = info . MetaLength ;
var blockIndex = info . BlockIndex ;
var blockHash = info . BlockHash ;
var blockSize = info . BlockSize ;
var volumeName = info . VolumeName ;
var volumeHash = info . VolumeHash ;
var volumeSize = info . VolumeSize ;
if ( string . IsNullOrEmpty ( volumeName ) || string . IsNullOrEmpty ( blockHash ) || blockSize <= 0 )
continue ;
if (! metaStates . TryGetValue ( metadataId , out var state ))
metaStates [ metadataId ] = state = new MetadataRecreateState ( metadataId , metaLength );
state . ExpectedBlockCount ++;
if (! volumeBlocks . TryGetValue ( volumeName , out var list ))
volumeBlocks [ volumeName ] = list = new List < MetadataBlockRef >();
list . Add ( new MetadataBlockRef ( metadataId , blockIndex , blockHash , blockSize ));
if (! volumeInfo . ContainsKey ( volumeName ))
volumeInfo [ volumeName ] = new RemoteVolume ( volumeName , volumeHash , volumeSize );
}
if ( volumeInfo . Count == 0 )
return ;
Logging . Log . WriteInformationMessage ( LOGTAG , "RestoringMetadataContent" , "Restoring metadata content into the database by downloading {0} block volume(s)" , volumeInfo . Count );
// Download all required block volumes and extract metadata blocks.
var volumesToDownload = volumeInfo . Values . Cast < IRemoteVolume >(). ToList ();
await foreach ( var ( tmpfile , hash , size , name ) in backendManager . GetFilesOverlappedAsync ( volumesToDownload , cancellationToken ). ConfigureAwait ( false ))
{
using ( tmpfile )
{
if (! await m_result . TaskControl . ProgressRendevouz (). ConfigureAwait ( false ))
return ;
if (! volumeBlocks . TryGetValue ( name , out var blocksForVolume ) || blocksForVolume . Count == 0 )
continue ;
try
{
using var reader = new BlockVolumeReader ( RestoreHandler . GetCompressionModule ( name ), tmpfile , m_options );
foreach ( var bref in blocksForVolume )
{
if (! metaStates . TryGetValue ( bref . MetadataId , out var state ))
continue ;
// Already captured
if ( state . Blocks . ContainsKey ( bref . BlockIndex ))
continue ;
if ( bref . BlockSize > int . MaxValue )
{
Logging . Log . WriteWarningMessage ( LOGTAG , "MetadataBlockTooLarge" , null , "Skipping metadata block {0} for metadataset {1} because block size {2} exceeds supported range" , bref . BlockHash , bref . MetadataId , bref . BlockSize );
continue ;
}
var buf = new byte [( int ) bref . BlockSize ];
var read = reader . ReadBlock ( bref . BlockHash , buf );
if ( read != buf . Length )
{
// Trim buffer to actual read length.
Array . Resize ( ref buf , read );
}
state . Blocks [ bref . BlockIndex ] = buf ;
// If we have all blocks for this metadataset, assemble and store content now.
if ( state . Blocks . Count == state . ExpectedBlockCount )
{
try
{
var ms = new MemoryStream ( checked (( int ) Math . Min ( state . MetaLength , int . MaxValue )));
foreach ( var kvp in state . Blocks . OrderBy ( x => x . Key ))
{
if ( ms . Length >= state . MetaLength )
break ;
var remaining = state . MetaLength - ms . Length ;
var take = ( int ) Math . Min ( remaining , kvp . Value . LongLength );
ms . Write ( kvp . Value , 0 , take );
}
if ( ms . Length != state . MetaLength )
{
Logging . Log . WriteWarningMessage ( LOGTAG , "MetadataContentIncomplete" , null , "Failed to restore metadataset content for {0}: expected {1} bytes, got {2} bytes" , state . MetadataId , state . MetaLength , ms . Length );
}
else
{
var content = Library . Utility . Utility . GetStringWithoutBOM ( ms , false );
await restoredb . SetMetadataContent ( state . MetadataId , content , cancellationToken ). ConfigureAwait ( false );
}
}
catch ( Exception ex )
{
Logging . Log . WriteWarningMessage ( LOGTAG , "MetadataContentRestoreFailed" , ex , "Failed to restore metadataset content for {0}" , state . MetadataId );
}
finally
{
metaStates . Remove ( state . MetadataId );
}
}
}
}
catch ( Exception ex )
{
Logging . Log . WriteWarningMessage ( LOGTAG , "MetadataVolumeProcessingFailed" , ex , "Failed to process block volume {0} for metadata content restore" , name );
if ( ex . IsAbortException ())
throw ;
}
}
}
}
2025-06-19 09:47:03 +02:00
/// <summary>
/// Recreate a fileset from the remote filelist.
/// </summary>
/// <param name="restoredb">The database to restore to.</param>
/// <param name="compressor">The compression module to use.</param>
/// <param name="filesetid">The ID of the fileset to recreate.</param>
/// <param name="options">The options to use for the operation.</param>
/// <param name="filter">The filter to apply to the files.</param>
/// <param name="cancellationToken">The cancellation token to monitor for cancellation requests.</param>
/// <returns>A task that completes when the fileset has been recreated.</returns>
public static async Task RecreateFilesetFromRemoteList ( LocalRecreateDatabase restoredb , ICompression compressor , long filesetid , Options options , IFilter filter , CancellationToken cancellationToken )
2025-03-10 14:41:12 +01:00
{
var blocksize = options . Blocksize ;
var hashes_pr_block = blocksize / options . BlockhashSize ;
// retrieve fileset data from dlist
var filesetData = VolumeReaderBase . GetFilesetData ( compressor , options );
// update fileset using filesetData
2025-06-12 08:34:29 +02:00
await restoredb
2025-06-19 09:47:03 +02:00
. UpdateFullBackupStateInFileset ( filesetid , filesetData . IsFullBackup , cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-03-10 14:41:12 +01:00
2025-04-03 22:20:42 +02:00
// clear any existing fileset entries
2025-06-19 09:47:03 +02:00
await restoredb . ClearFilesetEntries ( filesetid , cancellationToken ). ConfigureAwait ( false );
2025-04-03 22:20:42 +02:00
2025-03-10 14:41:12 +01:00
using ( var filelistreader = new FilesetVolumeReader ( compressor , options ))
foreach ( var fe in filelistreader . Files . Where ( x => Library . Utility . FilterExpression . Matches ( filter , x . Path )))
{
try
{
var expectedmetablocks = ( fe . Metasize + blocksize - 1 ) / blocksize ;
var expectedmetablocklisthashes = ( expectedmetablocks + hashes_pr_block - 1 ) / hashes_pr_block ;
if ( expectedmetablocks <= 1 ) expectedmetablocklisthashes = 0 ;
2026-01-27 15:51:30 +01:00
if (! options . AllowRestoreOutsideTargetDirectory )
{
// Check for cross-platform rooted paths
var isAbsolute =
( fe . Path . Length > 0 && fe . Path [ 0 ] == '/' ) ||
( fe . Path . Length > 2 && fe . Path [ 1 ] == ':' && ( fe . Path [ 2 ] == '\\' || fe . Path [ 2 ] == '/' )) ||
fe . Path . StartsWith ( "\\\\" );
2026-02-05 17:15:06 +01:00
// Check for relative path segments (accounting for mixed path separators)
var normalizedPath = fe . Path . Replace ( '\\' , '/' );
var hasRelativeSegments = normalizedPath . Contains ( "/../" )
|| normalizedPath . EndsWith ( "/.." )
|| normalizedPath . StartsWith ( "../" );
2026-01-27 15:51:30 +01:00
if ( hasRelativeSegments )
{
Logging . Log . WriteWarningMessage ( LOGTAG , "PathTraversalDetected" , null , "Path traversal detected in path: {0}" , fe . Path );
continue ;
}
if (! Path . IsPathFullyQualified ( fe . Path ) && ! isAbsolute )
{
Logging . Log . WriteWarningMessage ( LOGTAG , "InvalidPath" , null , "Invalid path: {0}" , fe . Path );
continue ;
}
}
2025-03-10 14:41:12 +01:00
var metadataid = long . MinValue ;
var split = LocalDatabase . SplitIntoPrefixAndName ( fe . Path );
2025-06-12 08:34:29 +02:00
var prefixid = await restoredb
2025-06-19 09:47:03 +02:00
. GetOrCreatePathPrefix ( split . Key , cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-03-10 14:41:12 +01:00
switch ( fe . Type )
{
case FilelistEntryType . Folder :
2025-06-12 08:34:29 +02:00
metadataid = await restoredb
2025-06-19 09:47:03 +02:00
. AddMetadataset ( fe . Metahash , fe . Metasize , fe . MetaBlocklistHashes , expectedmetablocklisthashes , cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
await restoredb
2025-06-19 09:47:03 +02:00
. AddDirectoryEntry ( filesetid , prefixid , split . Value , fe . Time , metadataid , cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-03-10 14:41:12 +01:00
break ;
case FilelistEntryType . File :
var expectedblocks = ( fe . Size + blocksize - 1 ) / blocksize ;
var expectedblocklisthashes = ( expectedblocks + hashes_pr_block - 1 ) / hashes_pr_block ;
if ( expectedblocks <= 1 ) expectedblocklisthashes = 0 ;
2025-06-12 08:34:29 +02:00
var blocksetid = await restoredb
2025-06-19 09:47:03 +02:00
. AddBlockset ( fe . Hash , fe . Size , fe . BlocklistHashes , expectedblocklisthashes , cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
metadataid = await restoredb
2025-06-19 09:47:03 +02:00
. AddMetadataset ( fe . Metahash , fe . Metasize , fe . MetaBlocklistHashes , expectedmetablocklisthashes , cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
await restoredb
2025-06-19 09:47:03 +02:00
. AddFileEntry ( filesetid , prefixid , split . Value , fe . Time , blocksetid , metadataid , cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-03-10 14:41:12 +01:00
if ( fe . Size <= blocksize )
{
if (! string . IsNullOrWhiteSpace ( fe . Blockhash ))
2025-06-12 08:34:29 +02:00
await restoredb
2025-06-19 09:47:03 +02:00
. AddSmallBlocksetLink ( fe . Hash , fe . Blockhash , fe . Blocksize , cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-03-10 14:41:12 +01:00
else if ( options . BlockHashAlgorithm == options . FileHashAlgorithm )
2025-06-12 08:34:29 +02:00
await restoredb
2025-06-19 09:47:03 +02:00
. AddSmallBlocksetLink ( fe . Hash , fe . Hash , fe . Size , cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-03-10 14:41:12 +01:00
else if ( fe . Size > 0 )
Logging . Log . WriteWarningMessage ( LOGTAG , "MissingBlockHash" , null , "No block hash found for file: {0}" , fe . Path );
}
break ;
case FilelistEntryType . Symlink :
2025-06-12 08:34:29 +02:00
metadataid = await restoredb
2025-06-19 09:47:03 +02:00
. AddMetadataset ( fe . Metahash , fe . Metasize , fe . MetaBlocklistHashes , expectedmetablocklisthashes , cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
await restoredb
2025-06-19 09:47:03 +02:00
. AddSymlinkEntry ( filesetid , prefixid , split . Value , fe . Time , metadataid , cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-03-10 14:41:12 +01:00
break ;
default :
Logging . Log . WriteWarningMessage ( LOGTAG , "SkippingUnknownFileEntry" , null , "Skipping file-entry with unknown type {0}: {1} " , fe . Type , fe . Path );
break ;
}
if ( fe . Metasize <= blocksize && ( fe . Type == FilelistEntryType . Folder || fe . Type == FilelistEntryType . File || fe . Type == FilelistEntryType . Symlink ))
{
if (! string . IsNullOrWhiteSpace ( fe . Metablockhash ))
2025-06-12 08:34:29 +02:00
await restoredb
2025-06-19 09:47:03 +02:00
. AddSmallBlocksetLink ( fe . Metahash , fe . Metablockhash , fe . Metasize , cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-03-10 14:41:12 +01:00
else if ( options . BlockHashAlgorithm == options . FileHashAlgorithm )
2025-06-12 08:34:29 +02:00
await restoredb
2025-06-19 09:47:03 +02:00
. AddSmallBlocksetLink ( fe . Metahash , fe . Metahash , fe . Metasize , cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-03-10 14:41:12 +01:00
else
Logging . Log . WriteWarningMessage ( LOGTAG , "MissingMetadataBlockHash" , null , "No block hash found for file metadata: {0}" , fe . Path );
}
}
catch ( Exception ex )
{
Logging . Log . WriteWarningMessage ( LOGTAG , "FileEntryProcessingFailed" , ex , "Failed to process file-entry: {0}" , fe . Path );
}
}
2025-05-13 09:00:09 +02:00
2025-06-19 09:47:03 +02:00
await restoredb . Transaction . CommitAsync ( cancellationToken ). ConfigureAwait ( false );
2025-03-10 14:41:12 +01:00
}
2015-08-24 17:03:14 +01:00
/// <summary>
2025-06-18 08:26:20 +02:00
/// Look in the database for filenames similar to the current filename, but with a different compression and encryption module.
2015-08-24 17:03:14 +01:00
/// </summary>
2025-06-18 08:26:20 +02:00
/// <param name="filename">The filename read and written.</param>
/// <param name="restoredb">The database to query.</param>
2025-06-19 09:47:03 +02:00
/// <param name="cancellationToken">A cancellation token to cancel the operation.</param>
2025-06-18 08:26:20 +02:00
/// <returns>The volume id of the item.</returns>
2025-06-19 09:47:03 +02:00
public static async Task <( long , string )> ProbeForMatchingFilename ( string filename , LocalRecreateDatabase restoredb , CancellationToken cancellationToken )
2015-08-24 17:03:14 +01:00
{
var p = VolumeBase . ParseFilename ( filename );
if ( p != null )
{
2024-05-21 15:48:29 +02:00
foreach ( var compmodule in Library . DynamicLoader . CompressionLoader . Keys )
2025-05-13 09:04:51 +02:00
foreach ( var encmodule in Library . DynamicLoader . EncryptionLoader . Keys . Union ([ "" ]))
2015-08-24 17:03:14 +01:00
{
var testfilename = VolumeBase . GenerateFilename ( p . FileType , p . Prefix , p . Guid , p . Time , compmodule , encmodule );
2025-06-12 08:34:29 +02:00
var tvid = await restoredb
2025-06-19 09:47:03 +02:00
. GetRemoteVolumeID ( testfilename , cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2015-08-24 17:03:14 +01:00
if ( tvid >= 0 )
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "RewritingFilenameMapping" , null , "Unable to find volume {0}, but mapping to matching file {1}" , filename , testfilename );
2015-08-24 17:03:14 +01:00
filename = testfilename ;
2025-05-13 09:00:09 +02:00
return ( tvid , filename );
2015-08-24 17:03:14 +01:00
}
}
}
2025-05-13 09:00:09 +02:00
return (- 1 , filename );
2015-08-24 17:03:14 +01:00
}
2013-03-08 22:24:54 +01:00
public void Dispose ()
{
}
}
}