2025-01-10 09:24:54 +01:00
// Copyright (C) 2025, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// 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
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// 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-01-28 08:54:50 +01:00
using System.Threading ;
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>
/// Run the recreate procedure
/// </summary>
/// <param name="path">Path to the database that will be created</param>
2025-01-28 08:54:50 +01:00
/// <param name="backendManager">The backend manager to use for downloading files</param>
2013-07-22 16:54:19 +02:00
/// <param name="filelistfilter">A filter that can be used to disregard certain remote files, intended to be used to select a certain filelist</param>
2015-04-08 21:01:36 +02:00
/// <param name="filter">Filters the files in a filelist to prevent downloading unwanted data</param>
2013-07-22 16:54:19 +02:00
/// <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-01-28 08:54:50 +01:00
public void Run ( string path , IBackendManager backendManager , IFilter filter , NumberedFilterFilelistDelegate filelistfilter , BlockVolumePostProcessor blockprocessor )
2013-07-22 16:54:19 +02:00
{
if ( System . IO . 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
2024-05-21 15:48:29 +02:00
using ( var db = new LocalDatabase ( path , "Recreate" , true ))
2013-07-22 16:54:19 +02:00
{
2016-02-10 17:38:24 +01:00
m_result . SetDatabase ( db );
2025-01-28 08:54:50 +01:00
DoRun ( backendManager , db , false , filter , filelistfilter , blockprocessor ). Await ();
2016-02-10 17:38:24 +01:00
db . WriteResults ();
2013-07-22 16:54:19 +02:00
}
}
/// <summary>
2015-04-08 21:01:36 +02:00
/// Updates a database with new path information from a remote fileset
2013-07-22 16:54:19 +02:00
/// </summary>
/// <param name="filelistfilter">A filter that can be used to disregard certain remote files, intended to be used to select a certain filelist</param>
2015-04-08 21:01:36 +02:00
/// <param name="filter">Filters the files in a filelist to prevent downloading unwanted data</param>
2013-07-22 16:54:19 +02:00
/// <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-01-28 08:54:50 +01:00
public void RunUpdate ( 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
2024-05-21 15:48:29 +02:00
using ( var db = new LocalDatabase ( m_options . Dbpath , "Recreate" , true ))
2015-04-08 21:01:36 +02:00
{
m_result . SetDatabase ( db );
if ( db . FindMatchingFilesets ( m_options . Time , m_options . Version ). Any ())
2018-05-24 20:16:46 -07:00
throw new UserInformationException ( "The version(s) being updated to, already exists" , "UpdateVersionAlreadyExists" );
2015-04-08 21:01:36 +02:00
2016-09-15 11:39:27 +02:00
// Mark as incomplete
db . PartiallyRecreated = true ;
2016-09-13 21:55:15 +02:00
2024-05-21 15:48:29 +02:00
var preexistingOptionsInDatabase = Utility . ContainsOptionsForVerification ( db );
2015-04-08 21:01:36 +02:00
Utility . UpdateOptionsFromDb ( db , m_options , null );
2024-05-21 15:48:29 +02:00
// Make sure the options have not changed between calls, unless there are no previous options
if ( preexistingOptionsInDatabase )
Utility . VerifyOptionsAndUpdateDatabase ( db , m_options , null );
2025-01-28 08:54:50 +01:00
DoRun ( backendManager , db , true , filter , filelistfilter , blockprocessor ). Await ();
2015-04-08 21:01:36 +02:00
db . WriteResults ();
}
}
2013-03-08 22:24:54 +01:00
2015-04-08 21:01:36 +02:00
/// <summary>
/// Run the recreate procedure
/// </summary>
/// <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>
2016-09-15 11:39:27 +02:00
/// <param name="filelistfilter">Filters the files in a filelist to prevent downloading unwanted data</param>
2015-04-08 21:01:36 +02:00
/// <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-01-28 08:54:50 +01:00
internal async Task DoRun ( IBackendManager backendManager , LocalDatabase dbparent , bool updating , IFilter filter = null , NumberedFilterFilelistDelegate filelistfilter = null , BlockVolumePostProcessor blockprocessor = null )
2015-04-08 21:01:36 +02:00
{
2025-01-28 08:54:50 +01:00
var cancellationToken = CancellationToken . None ;
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.
2024-05-21 15:48:29 +02:00
using ( var restoredb = new LocalRecreateDatabase ( dbparent , m_options ))
2013-03-08 22:24:54 +01:00
{
2016-09-15 11:39:27 +02:00
restoredb . RepairInProgress = true ;
2024-05-21 15:48:29 +02:00
var expRecreateDb = false ; // experimental recreate db code flag
2013-07-22 16:54:19 +02:00
var volumeIds = new Dictionary < string , long >();
2024-05-21 15:48:29 +02:00
if ( string . Equals ( Environment . GetEnvironmentVariable ( "EXPERIMENTAL_RECREATEDB_DUPLICATI" ) ?? string . Empty , "1" ))
{
expRecreateDb = true ;
}
2013-04-04 20:34:26 +02:00
2025-01-28 08:54:50 +01:00
var rawlist = await backendManager . ListAsync ( cancellationToken );
2024-05-21 15:48:29 +02:00
2013-03-08 22:24:54 +01:00
//First step is to examine the remote storage to see what
// kind of data we can find
var remotefiles =
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
&&
n . Prefix == m_options . Prefix
select n ). ToArray (); //ToArray() ensures that we do not remote-request it multiple times
2013-07-22 16:54:19 +02:00
if ( remotefiles . Length == 0 )
{
2025-01-28 08:54:50 +01:00
if ( rawlist . Count () == 0 )
2018-03-12 14:07:11 +01:00
throw new UserInformationException ( "No files were found at the remote location, perhaps the target url is incorrect?" , "EmptyRemoteLocation" );
2013-07-22 16:54:19 +02:00
else
{
2024-05-21 15:48:29 +02:00
var tmp =
2016-09-15 11:39:27 +02:00
( from x in rawlist
2024-05-21 15:48:29 +02:00
let n = VolumeBase . ParseFilename ( x )
where
n != null
select n . Prefix ). ToArray ();
2013-07-22 16:54:19 +02:00
var types = tmp . Distinct (). ToArray ();
if ( tmp . Length == 0 )
2025-01-28 08:54:50 +01:00
throw new UserInformationException ( string . Format ( "Found {0} files at the remote storage, but none that could be parsed" , rawlist . Count ()), "EmptyRemoteLocation" );
2013-07-22 16:54:19 +02:00
else if ( types . Length == 1 )
2018-03-12 14:07:11 +01:00
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-01-28 08:54:50 +01: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
}
}
2013-04-08 22:21:45 +02:00
2013-03-08 22:24:54 +01: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 ;
2018-10-06 16:02:36 -07:00
if (! filelists . Any ())
2018-05-24 20:16:46 -07:00
throw new UserInformationException ( "No filelists found on the remote destination" , "EmptyRemoteLocation" );
2024-05-21 15:48:29 +02:00
2013-03-08 22:24:54 +01:00
if ( filelistfilter != null )
2013-05-13 22:32:05 +02:00
filelists = filelistfilter ( filelists ). Select ( x => x . Value ). ToArray ();
2013-03-08 22:24:54 +01:00
2018-10-06 16:02:36 -07:00
if (! filelists . Any ())
2018-05-24 20:16:46 -07:00
throw new UserInformationException ( "No filelists" , "NoMatchingRemoteFilelists" );
2015-08-26 12:38:49 +01:00
2015-04-08 21:01:36 +02:00
// If we are updating, all files should be accounted for
2024-05-21 15:48:29 +02:00
foreach ( var fl in remotefiles )
2015-08-24 10:50:47 +01:00
volumeIds [ fl . File . Name ] = updating ? restoredb . GetRemoteVolumeID ( fl . File . Name ) : restoredb . RegisterRemoteVolume ( fl . File . Name , fl . FileType , fl . File . Size , RemoteVolumeState . Uploaded );
2013-07-22 16:54:19 +02:00
2015-04-08 21:01:36 +02:00
var hasUpdatedOptions = false ;
2013-07-22 16:54:19 +02:00
//Record all blocksets and files needed
2024-05-21 15:48:29 +02:00
using ( var tr = restoredb . BeginTransaction ())
2013-03-08 22:24:54 +01:00
{
2013-07-22 16:54:19 +02:00
var filelistWork = ( from n in filelists orderby n . Time select new RemoteVolume ( n . File ) as IRemoteVolume ). ToList ();
2018-03-12 14:07:11 +01:00
Logging . Log . WriteInformationMessage ( LOGTAG , "RebuildStarted" , "Rebuild database started, downloading {0} filelists" , filelistWork . Count );
2016-03-18 13:25:20 +01:00
2014-08-26 15:01:56 +02:00
var progress = 0 ;
2015-10-26 09:14:44 +01:00
// Register the files we are working with, if not already updated
if ( updating )
{
2024-05-21 15:48:29 +02:00
foreach ( var n in filelists )
2015-10-26 09:14:44 +01:00
if ( volumeIds [ n . File . Name ] == - 1 )
volumeIds [ n . File . Name ] = restoredb . RegisterRemoteVolume ( n . File . Name , n . FileType , RemoteVolumeState . Uploaded , n . File . Size , new TimeSpan ( 0 ), tr );
}
2024-05-21 15:48:29 +02:00
2016-03-13 17:54:59 +01:00
var isFirstFilelist = true ;
2016-03-15 21:02:13 +01:00
var blocksize = m_options . Blocksize ;
2016-03-26 13:14:05 +01:00
var hashes_pr_block = blocksize / m_options . BlockhashSize ;
2015-10-26 09:14:44 +01:00
2025-02-18 09:18:36 +01:00
await foreach ( var ( tmpfile , hash , size , name ) in backendManager . GetFilesOverlappedAsync ( filelistWork , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false ))
2025-01-28 08:54:50 +01:00
{
var entry = new RemoteVolume ( name , hash , size );
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-01-28 08:54:50 +01:00
await backendManager . WaitForEmptyAsync ( restoredb , tr , cancellationToken ). ConfigureAwait ( false );
2016-12-01 23:59:54 +01:00
m_result . EndTime = DateTime . UtcNow ;
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 ++;
2015-04-08 21:01:36 +02:00
if ( filelistWork . Count == 1 && m_options . RepairOnlyPaths )
2019-09-11 03:43:54 -07:00
{
2015-04-08 21:01:36 +02:00
m_result . OperationProgressUpdater . UpdateProgress ( 0.5f );
2019-09-11 03:43:54 -07:00
}
2015-04-08 21:01:36 +02:00
else
2019-09-11 03:43:54 -07:00
{
2015-04-08 21:01:36 +02:00
m_result . OperationProgressUpdater . UpdateProgress ((( float ) progress / filelistWork . Count ()) * ( m_options . RepairOnlyPaths ? 1f : 0.2f ));
2019-09-11 03:43:54 -07:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "ProcessingFilelistVolumes" , "Processing filelist volume {0} of {1}" , progress , filelistWork . 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
{
2016-03-13 17:54:59 +01:00
isFirstFilelist = false ;
2025-01-28 08:54:50 +01:00
if (! string . IsNullOrWhiteSpace ( hash ) && size > 0 )
restoredb . UpdateRemoteVolume ( entry . Name , RemoteVolumeState . Verified , size , hash , tr );
2013-03-08 22:24:54 +01:00
2013-07-22 16:54:19 +02:00
var parsed = VolumeBase . ParseFilename ( entry . Name );
2015-04-08 21:01:36 +02:00
2025-02-06 21:23:07 +01: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" );
2024-05-21 15:48:29 +02:00
if (! hasUpdatedOptions )
2015-04-08 21:01:36 +02:00
{
2025-02-06 21:23:07 +01:00
VolumeReaderBase . UpdateOptionsFromManifest ( compressor , m_options );
2015-04-08 21:01:36 +02:00
hasUpdatedOptions = true ;
2016-03-18 13:25:20 +01:00
// Recompute the cached sizes
blocksize = m_options . Blocksize ;
2016-03-26 13:14:05 +01:00
hashes_pr_block = blocksize / m_options . BlockhashSize ;
2015-04-08 21:01:36 +02:00
}
2013-07-22 16:54:19 +02:00
// Create timestamped operations based on the file timestamp
var filesetid = restoredb . CreateFileset ( volumeIds [ entry . Name ], parsed . Time , tr );
2024-05-21 15:48:29 +02:00
2019-09-10 12:51:42 -04:00
// retrieve fileset data from dlist
2025-02-06 21:23:07 +01:00
var filesetData = VolumeReaderBase . GetFilesetData ( compressor , m_options );
2024-05-21 15:48:29 +02:00
2019-09-10 12:51:42 -04:00
// update fileset using filesetData
restoredb . UpdateFullBackupStateInFileset ( filesetid , filesetData . IsFullBackup );
2019-09-02 18:20:21 -04:00
2025-02-06 21:23:07 +01:00
using ( var filelistreader = new FilesetVolumeReader ( compressor , m_options ))
2024-05-21 15:48:29 +02:00
foreach ( var fe in filelistreader . Files . Where ( x => Library . Utility . FilterExpression . Matches ( filter , x . Path )))
2013-07-22 16:54:19 +02:00
{
try
{
2024-05-21 15:48:29 +02:00
var expectedmetablocks = ( fe . Metasize + blocksize - 1 ) / blocksize ;
2016-04-04 18:11:48 +02:00
var expectedmetablocklisthashes = ( expectedmetablocks + hashes_pr_block - 1 ) / hashes_pr_block ;
if ( expectedmetablocks <= 1 ) expectedmetablocklisthashes = 0 ;
2017-09-26 20:10:19 -07:00
var metadataid = long . MinValue ;
2018-06-14 10:12:24 +02:00
var split = Database . LocalDatabase . SplitIntoPrefixAndName ( fe . Path );
var prefixid = restoredb . GetOrCreatePathPrefix ( split . Key , tr );
2017-09-26 20:13:40 -07:00
switch ( fe . Type )
2013-07-22 16:54:19 +02:00
{
2017-09-26 20:13:40 -07:00
case FilelistEntryType . Folder :
metadataid = restoredb . AddMetadataset ( fe . Metahash , fe . Metasize , fe . MetaBlocklistHashes , expectedmetablocklisthashes , tr );
2018-06-14 10:12:24 +02:00
restoredb . AddDirectoryEntry ( filesetid , prefixid , split . Value , fe . Time , metadataid , tr );
2017-09-26 20:13:40 -07: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 ;
var blocksetid = restoredb . AddBlockset ( fe . Hash , fe . Size , fe . BlocklistHashes , expectedblocklisthashes , tr );
metadataid = restoredb . AddMetadataset ( fe . Metahash , fe . Metasize , fe . MetaBlocklistHashes , expectedmetablocklisthashes , tr );
2018-06-14 10:12:24 +02:00
restoredb . AddFileEntry ( filesetid , prefixid , split . Value , fe . Time , blocksetid , metadataid , tr );
2017-09-26 20:13:40 -07:00
if ( fe . Size <= blocksize )
{
if (! string . IsNullOrWhiteSpace ( fe . Blockhash ))
restoredb . AddSmallBlocksetLink ( fe . Hash , fe . Blockhash , fe . Blocksize , tr );
else if ( m_options . BlockHashAlgorithm == m_options . FileHashAlgorithm )
restoredb . AddSmallBlocksetLink ( fe . Hash , fe . Hash , fe . Size , tr );
2025-01-28 08:54:50 +01:00
else if ( fe . Size > 0 )
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "MissingBlockHash" , null , "No block hash found for file: {0}" , fe . Path );
2017-09-26 20:13:40 -07:00
}
break ;
case FilelistEntryType . Symlink :
metadataid = restoredb . AddMetadataset ( fe . Metahash , fe . Metasize , fe . MetaBlocklistHashes , expectedmetablocklisthashes , tr );
2018-06-14 10:12:24 +02:00
restoredb . AddSymlinkEntry ( filesetid , prefixid , split . Value , fe . Time , metadataid , tr );
2017-09-26 20:13:40 -07:00
break ;
default :
2024-05-21 15:48:29 +02:00
Logging . Log . WriteWarningMessage ( LOGTAG , "SkippingUnknownFileEntry" , null , "Skipping file-entry with unknown type {0}: {1} " , fe . Type , fe . Path );
2017-09-26 20:13:40 -07:00
break ;
2013-07-22 16:54:19 +02:00
}
2016-04-05 00:20:16 +02:00
if ( fe . Metasize <= blocksize && ( fe . Type == FilelistEntryType . Folder || fe . Type == FilelistEntryType . File || fe . Type == FilelistEntryType . Symlink ))
{
if (! string . IsNullOrWhiteSpace ( fe . Metablockhash ))
restoredb . AddSmallBlocksetLink ( fe . Metahash , fe . Metablockhash , fe . Metasize , tr );
else if ( m_options . BlockHashAlgorithm == m_options . FileHashAlgorithm )
restoredb . AddSmallBlocksetLink ( fe . Metahash , fe . Metahash , fe . Metasize , tr );
else
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "MissingMetadataBlockHash" , null , "No block hash found for file metadata: {0}" , fe . Path );
2016-04-05 00:20:16 +02:00
}
2013-07-22 16:54:19 +02:00
}
catch ( Exception ex )
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "FileEntryProcessingFailed" , ex , "Failed to process file-entry: {0}" , fe . Path );
2013-07-22 16:54:19 +02:00
}
}
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
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "FileProcessingFailed" , ex , "Failed to process file: {0}" , entry . Name );
2014-05-15 12:47:16 +02:00
if ( ex is System . Threading . ThreadAbortException )
2016-12-01 23:59:54 +01:00
{
m_result . EndTime = DateTime . UtcNow ;
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
2016-03-13 17:54:59 +01:00
if ( isFirstFilelist && ex is System . Security . Cryptography . CryptographicException )
2016-12-01 23:59:54 +01:00
{
m_result . EndTime = DateTime . UtcNow ;
2016-03-13 17:01:35 +01:00
throw ;
2016-12-01 23:59:54 +01:00
}
2018-06-12 09:31:39 +02:00
if ( m_options . UnittestMode )
throw ;
2013-07-22 16:54:19 +02:00
}
2025-01-28 08:54:50 +01:00
}
2015-04-08 21:01:36 +02:00
2024-05-21 15:48:29 +02:00
//Make sure we write the config if it has been read from a manifest
if ( hasUpdatedOptions )
Utility . VerifyOptionsAndUpdateDatabase ( restoredb , m_options , tr );
2015-04-08 21:01:36 +02:00
2024-05-21 15:48:29 +02:00
using ( new Logging . Timer ( LOGTAG , "CommitUpdateFilesetFromRemote" , "CommitUpdateFilesetFromRemote" ))
2013-07-22 16:54:19 +02:00
tr . Commit ();
}
2024-05-21 15:48:29 +02:00
// do we stop after just handling the dlist files ?
// (if yes, we never will be able to do a backup !)
2015-04-08 21:01:36 +02:00
if (! m_options . RepairOnlyPaths )
2013-07-22 16:54:19 +02:00
{
2021-04-04 11:17:13 -07:00
var hashsize = 0 ;
2015-04-08 21:01:36 +02:00
//Grab all index files, and update the block table
2021-04-04 11:17:13 -07:00
2024-05-21 15:48:29 +02:00
using ( var hashalg = HashFactory . CreateHasher ( m_options . BlockHashAlgorithm ))
using ( var tr = restoredb . BeginTransaction ())
2015-04-08 21:01:36 +02:00
{
2021-04-04 11:17:13 -07:00
hashsize = hashalg . HashSize / 8 ;
2015-04-08 21:01:36 +02:00
var indexfiles = (
from n in remotefiles
2024-05-21 15:48:29 +02:00
where n . FileType == RemoteVolumeType . Index
select new RemoteVolume ( n . File ) as IRemoteVolume ). ToList ();
2015-04-08 21:01:36 +02:00
2018-03-12 14:07:11 +01:00
Logging . Log . WriteInformationMessage ( LOGTAG , "FilelistsRestored" , "Filelists restored, downloading {0} index files" , indexfiles . Count );
2016-03-18 13:25:20 +01:00
2015-04-08 21:01:36 +02:00
var progress = 0 ;
2024-05-21 15:48:29 +02:00
2025-02-18 09:18:36 +01: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
{
2015-04-08 21:01:36 +02:00
try
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-01-28 08:54:50 +01:00
await backendManager . WaitForEmptyAsync ( restoredb , tr , cancellationToken ). ConfigureAwait ( false );
2016-12-01 23:59:54 +01:00
m_result . EndTime = DateTime . UtcNow ;
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 ++;
m_result . OperationProgressUpdater . UpdateProgress (((( float ) progress / indexfiles . Count ) * 0.5f ) + 0.2f );
2019-09-11 03:43:54 -07:00
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 )
restoredb . UpdateRemoteVolume ( name , RemoteVolumeState . Verified , size , hash , tr );
2024-05-21 15:48:29 +02:00
2025-01-28 08:54:50 +01:00
using ( var svr = new IndexVolumeReader ( RestoreHandler . GetCompressionModule ( name ), tmpfile , m_options , hashsize ))
2013-03-08 22:24:54 +01:00
{
2024-05-21 15:48:29 +02:00
foreach ( var a in svr . Volumes )
2015-04-08 21:01:36 +02:00
{
2015-08-24 17:03:14 +01:00
var filename = a . Filename ;
var volumeID = restoredb . GetRemoteVolumeID ( filename );
// No such file
if ( volumeID < 0 )
2021-04-04 11:17:13 -07:00
volumeID = ProbeForMatchingFilename ( ref filename , restoredb );
2024-05-21 15:48:29 +02:00
var missing = false ;
2015-08-24 17:03:14 +01:00
// 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 ));
2025-01-28 08:54:50 +01:00
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 );
2024-05-21 15:48:29 +02:00
missing = true ;
volumeID = restoredb . RegisterRemoteVolume ( filename , p . FileType , RemoteVolumeState . Temporary , tr );
2015-08-24 17:03:14 +01:00
}
2024-05-21 15:48:29 +02:00
bool anyChange = false ;
2015-04-08 21:01:36 +02:00
//Add all block/volume mappings
2024-05-21 15:48:29 +02:00
foreach ( var b in a . Blocks )
2023-11-05 18:19:08 +01:00
restoredb . UpdateBlock ( b . Key , b . Value , volumeID , tr , ref anyChange );
2013-03-08 22:24:54 +01:00
2023-11-05 18:19:08 +01:00
restoredb . UpdateRemoteVolume ( filename , missing ? RemoteVolumeState . Temporary : RemoteVolumeState . Verified , a . Length , a . Hash , tr );
2025-01-28 08:54:50 +01:00
restoredb . AddIndexBlockLink ( restoredb . GetRemoteVolumeID ( name ), volumeID , tr );
2015-04-08 21:01:36 +02:00
}
2023-11-30 22:37:07 +01:00
2023-11-05 18:19:08 +01:00
//If there are blocklists in the index file, add them to the temp blocklist hashes table
2023-11-30 22:37:07 +01:00
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
{
2024-09-09 17:13:59 +02:00
// 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 ();
restoredb . AddTempBlockListHash ( b . Hash , list , tr );
2023-11-30 22:37:07 +01:00
}
catch ( System . IO . InvalidDataException e )
{
2025-01-28 08:54:50 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "InvalidDataBlocklist" , e , "Exception while processing blocklists in {0}" , name );
2023-11-30 22:37:07 +01:00
++ wrongHashes ;
}
}
if ( wrongHashes != 0 )
{
2025-01-28 08:54:50 +01:00
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 );
2023-11-30 22:37:07 +01:00
}
2015-04-08 21:01:36 +02:00
}
2013-03-08 22:24:54 +01:00
}
2013-07-22 16:54:19 +02:00
}
2015-04-08 21:01:36 +02:00
catch ( Exception ex )
{
//Not fatal
2025-01-28 08:54:50 +01:00
Logging . Log . WriteErrorMessage ( LOGTAG , "IndexFileProcessingFailed" , ex , "Failed to process index file: {0}" , name );
2015-04-08 21:01:36 +02:00
if ( ex is System . Threading . ThreadAbortException )
2016-12-01 23:59:54 +01:00
{
m_result . EndTime = DateTime . UtcNow ;
2015-04-08 21:01:36 +02:00
throw ;
2016-12-01 23:59:54 +01:00
}
2018-06-12 09:31:39 +02:00
if ( m_options . UnittestMode )
throw ;
2015-04-08 21:01:36 +02:00
}
2025-01-28 08:54:50 +01:00
}
2013-03-08 22:24:54 +01:00
2024-05-21 15:48:29 +02:00
using ( new Logging . Timer ( LOGTAG , "CommitRecreateDb" , "CommitRecreatedDb" ))
2015-04-08 21:01:36 +02:00
tr . Commit ();
2024-05-21 15:48:29 +02:00
2015-04-08 21:01:36 +02:00
// TODO: In some cases, we can avoid downloading all index files,
// if we are lucky and pick the right ones
}
2013-03-08 22:24:54 +01:00
2025-01-10 09:24:54 +01:00
restoredb . CleanupMissingVolumes ();
2018-06-12 08:29:55 +02:00
2024-05-21 15:48:29 +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)
restoredb . AddBlockAndBlockSetEntryFromTemp ( hashsize , m_options . Blocksize , null );
else
2023-11-05 18:19:08 +01:00
restoredb . FindMissingBlocklistHashes ( hashsize , m_options . Blocksize , null );
2024-05-21 15:48:29 +02:00
2015-04-08 21:01:36 +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
2024-05-21 15:48:29 +02:00
for ( var i = 0 ; i < 3 ; i ++)
2014-12-31 14:25:17 +01:00
{
2015-04-08 21:01:36 +02:00
// Grab the list matching the pass type
2023-11-05 18:19:08 +01:00
var lst = restoredb . GetMissingBlockListVolumes ( i , m_options . Blocksize , hashsize , m_options . RepairForceBlockUse ). ToList ();
2015-04-08 21:01:36 +02:00
if ( lst . Count > 0 )
2014-12-31 14:25:17 +01:00
{
2018-03-12 14:07:11 +01:00
var fullist = ": " + string . Join ( ", " , lst . Select ( x => x . Name ));
2015-04-08 21:01:36 +02:00
switch ( i )
2024-05-21 15:48:29 +02:00
{
2015-04-08 21:01:36 +02:00
case 0 :
2018-03-12 14:07:11 +01:00
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 );
2015-04-08 21:01:36 +02:00
break ;
case 1 :
2018-03-12 14:07:11 +01:00
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 );
2015-04-08 21:01:36 +02:00
break ;
default :
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "ProcessingAllBlocklistVolumes" , "Processing all of the {0} volumes for blocklists{1}" , lst . Count , fullist );
2023-11-05 18:19:08 +01:00
Logging . Log . WriteInformationMessage ( LOGTAG , "ProcessingAllBlocklistVolumes" , "Processing all of the {0} volumes for blocklists{1}" , lst . Count , m_options . FullResult ? fullist : string . Empty );
2015-04-08 21:01:36 +02:00
break ;
}
2014-12-31 14:25:17 +01:00
}
2015-04-08 21:01:36 +02:00
var progress = 0 ;
2025-02-18 09:18:36 +01:00
await foreach ( var ( tmpfile , hash , size , name ) in backendManager . GetFilesOverlappedAsync ( lst , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false ))
2018-06-11 14:42:13 +02:00
{
try
2014-05-15 12:47:16 +02:00
{
2025-01-28 08:54:50 +01:00
using ( tmpfile )
using ( var rd = new BlockVolumeReader ( RestoreHandler . GetCompressionModule ( name ), tmpfile , m_options ))
2018-06-11 14:42:13 +02:00
using ( var tr = restoredb . BeginTransaction ())
2015-04-08 21:01:36 +02:00
{
2024-12-18 08:27:59 +01:00
if (! m_result . TaskControl . ProgressRendevouz (). Await ())
2018-06-11 14:42:13 +02:00
{
2025-01-28 08:54:50 +01:00
backendManager . WaitForEmptyAsync ( restoredb , tr , cancellationToken ). Await ();
2018-06-11 14:42:13 +02:00
m_result . EndTime = DateTime . UtcNow ;
return ;
}
progress ++;
m_result . OperationProgressUpdater . UpdateProgress (((( float ) progress / lst . Count ) * 0.1f ) + 0.7f + ( i * 0.1f ));
2019-09-11 17:52:57 -07:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "ProcessingBlocklistVolumes" , "Pass {0} of 3, processing blocklist volume {1} of {2}" , ( i + 1 ), progress , lst . Count );
2018-06-11 14:42:13 +02:00
2025-01-28 08:54:50 +01:00
var volumeid = restoredb . GetRemoteVolumeID ( name );
2018-06-11 14:42:13 +02:00
2025-01-28 08:54:50 +01:00
restoredb . UpdateRemoteVolume ( name , RemoteVolumeState . Uploaded , size , hash , tr );
2024-05-21 15:48:29 +02:00
2023-11-05 18:19:08 +01:00
bool anyChange = false ;
2018-06-11 14:42:13 +02:00
// Update the block table so we know about the block/volume map
foreach ( var h in rd . Blocks )
2023-11-05 18:19:08 +01:00
restoredb . UpdateBlock ( h . Key , h . Value , volumeid , tr , ref anyChange );
2024-05-21 15:48:29 +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
2023-11-05 18:19:08 +01:00
// read the blocks list hashes from the volume data (the handled file) and insert them into the temp blocklisthash table
2024-05-21 15:48:29 +02:00
foreach ( var blocklisthash in restoredb . GetBlockLists ( volumeid ))
{
if ( restoredb . AddTempBlockListHash ( blocklisthash , rd . ReadBlocklist ( blocklisthash , hashsize ), tr ))
{
anyChange = true ;
}
}
// 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 )
{
2025-01-28 08:54:50 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "UpdatingTables" , null , "Unexpected changes caused by block {0}" , name );
2024-05-21 15:48:29 +02:00
}
if ( expRecreateDb )
restoredb . AddBlockAndBlockSetEntryFromTemp ( hashsize , m_options . Blocksize , tr , false );
else
2023-11-05 18:19:08 +01:00
restoredb . FindMissingBlocklistHashes ( hashsize , m_options . Blocksize , tr );
2024-05-21 15:48:29 +02:00
}
2018-06-11 14:42:13 +02:00
using ( new Logging . Timer ( LOGTAG , "CommitRestoredBlocklist" , "CommitRestoredBlocklist" ))
tr . Commit ();
//At this point we can patch files with data from the block volume
if ( blockprocessor != null )
2025-01-28 08:54:50 +01:00
blockprocessor ( name , rd );
2018-06-11 14:42:13 +02:00
}
}
catch ( Exception ex )
{
2025-01-28 08:54:50 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "FailedRebuildingWithFile" , ex , "Failed to use information from {0} to rebuild database: {1}" , name , ex . Message );
2018-06-12 09:31:39 +02:00
if ( m_options . UnittestMode )
throw ;
2015-04-08 21:01:36 +02:00
}
2018-06-11 14:42:13 +02:00
}
2015-04-08 21:01:36 +02:00
}
2013-07-22 16:54:19 +02:00
}
2018-05-10 00:57:51 +02:00
2025-01-28 08:54:50 +01:00
backendManager . WaitForEmptyAsync ( restoredb , null , cancellationToken ). Await ();
2021-04-04 11:17:13 -07:00
2024-11-01 15:56:14 +01: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
restoredb . CleanupDeletedBlocks ( null );
}
2025-01-10 09:24:54 +01:00
restoredb . CleanupMissingVolumes ();
2013-04-21 20:00:37 +02:00
2016-09-15 11:39:27 +02:00
if ( m_options . RepairOnlyPaths )
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteInformationMessage ( LOGTAG , "RecreateOrUpdateOnly" , "Recreate/path-update completed, not running consistency checks" );
2016-09-15 11:39:27 +02:00
}
else
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteInformationMessage ( LOGTAG , "RecreateCompletedCheckingDatabase" , "Recreate completed, verifying the database consistency" );
2016-03-18 13:25:20 +01:00
2016-09-15 11:39:27 +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
2016-12-01 23:59:54 +01:00
m_result . EndTime = DateTime . UtcNow ;
2017-01-05 20:58:33 +01:00
using ( var lbfdb = new LocalListBrokenFilesDatabase ( restoredb ))
{
var broken = lbfdb . GetBrokenFilesets ( new DateTime ( 0 ), null , null ). Count ();
if ( broken != 0 )
2018-03-12 14:07:11 +01:00
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" );
2017-01-05 20:58:33 +01:00
}
2016-10-18 13:27:36 +02:00
restoredb . VerifyConsistency ( m_options . Blocksize , m_options . BlockhashSize , true , null );
2016-03-18 13:25:20 +01:00
2018-03-12 14:07:11 +01:00
Logging . Log . WriteInformationMessage ( LOGTAG , "RecreateCompleted" , "Recreate completed, and consistency checks completed, marking database as complete" );
2016-03-18 13:25:20 +01:00
2016-09-15 11:39:27 +02:00
restoredb . RepairInProgress = false ;
}
2016-12-01 23:59:54 +01:00
m_result . EndTime = DateTime . UtcNow ;
2013-03-08 22:24:54 +01:00
}
}
2015-08-24 17:03:14 +01:00
/// <summary>
/// Look in the database for filenames similar to the current filename, but with a different compression and encryption module
/// </summary>
/// <returns>The volume id of the item</returns>
/// <param name="filename">The filename read and written</param>
/// <param name="restoredb">The database to query</param>
public long ProbeForMatchingFilename ( ref string filename , LocalRestoreDatabase restoredb )
{
var p = VolumeBase . ParseFilename ( filename );
if ( p != null )
{
2024-05-21 15:48:29 +02:00
foreach ( var compmodule in Library . DynamicLoader . CompressionLoader . Keys )
foreach ( var encmodule in Library . DynamicLoader . EncryptionLoader . Keys . Union ( new string [] { "" }))
2015-08-24 17:03:14 +01:00
{
var testfilename = VolumeBase . GenerateFilename ( p . FileType , p . Prefix , p . Guid , p . Time , compmodule , encmodule );
var tvid = restoredb . GetRemoteVolumeID ( testfilename );
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 ;
return tvid ;
}
}
}
return - 1 ;
}
2013-03-08 22:24:54 +01:00
public void Dispose ()
{
}
}
}