2024-02-28 15:45:30 +01:00
// Copyright (C) 2024, 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
// DEALINGS IN THE SOFTWARE.
using System ;
2013-03-08 22:24:54 +01:00
using System.Collections.Generic ;
using System.Linq ;
2018-09-09 14:42:40 +02:00
using System.Security.Cryptography ;
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 string m_backendurl ;
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 );
2013-07-22 16:54:19 +02:00
public delegate void BlockVolumePostProcessor ( string volumename , BlockVolumeReader reader );
2013-03-08 22:24:54 +01:00
2013-07-22 16:54:19 +02:00
public RecreateDatabaseHandler ( string backendurl , Options options , RecreateDatabaseResults result )
{
m_options = options ;
m_backendurl = backendurl ;
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>
/// <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>
public void Run ( string path , Library . Utility . IFilter filter = null , NumberedFilterFilelistDelegate filelistfilter = null , BlockVolumePostProcessor blockprocessor = null )
{
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
2016-04-06 20:40:34 +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 );
DoRun ( db , false , filter , filelistfilter , blockprocessor );
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>
2015-04-08 21:01:36 +02:00
public void RunUpdate ( Library . Utility . IFilter filter = null , NumberedFilterFilelistDelegate filelistfilter = null , BlockVolumePostProcessor blockprocessor = null )
2013-04-04 20:34:26 +02:00
{
2015-04-08 21:01:36 +02:00
if (! m_options . RepairOnlyPaths )
2018-03-12 14:07:11 +01: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
2016-04-06 20:40:34 +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
2015-04-08 21:01:36 +02:00
Utility . UpdateOptionsFromDb ( db , m_options , null );
DoRun ( db , true , filter , filelistfilter , blockprocessor );
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>
internal void DoRun ( LocalDatabase dbparent , bool updating , Library . Utility . IFilter filter = null , NumberedFilterFilelistDelegate filelistfilter = null , BlockVolumePostProcessor blockprocessor = null )
{
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.
2013-07-22 16:54:19 +02:00
using ( var restoredb = new LocalRecreateDatabase ( dbparent , m_options ))
using ( var backend = new BackendManager ( m_backendurl , m_options , m_result . BackendWriter , restoredb ))
2013-03-08 22:24:54 +01:00
{
2016-09-15 11:39:27 +02:00
restoredb . RepairInProgress = true ;
2023-11-05 18:19:08 +01:00
var expRecreateDb = false ; // experimental recreate db code flag
2018-06-30 17:28:23 +02:00
var autoDetectBlockSize = !( m_options . HasBlocksize && restoredb . GetDbOptions (). ContainsKey ( "blocksize" ));
2013-07-22 16:54:19 +02:00
var volumeIds = new Dictionary < string , long >();
2023-11-05 18:19:08 +01:00
if ( string . Equals ( Environment . GetEnvironmentVariable ( "EXPERIMENTAL_RECREATEDB_DUPLICATI" ) ?? string . Empty , "1" )) {
expRecreateDb = true ;
}
2013-04-04 20:34:26 +02:00
2013-07-22 16:54:19 +02:00
var rawlist = backend . List ();
2016-09-15 11:39:27 +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
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
if ( remotefiles . Length == 0 )
{
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
{
var tmp =
2016-09-15 11:39:27 +02:00
( from x in rawlist
let n = VolumeBase . ParseFilename ( x )
where
n != null
2013-07-22 16:54:19 +02:00
select n . Prefix ). ToArray ();
var types = tmp . Distinct (). ToArray ();
if ( tmp . Length == 0 )
2018-03-12 14:07:11 +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
2018-03-12 14:07:11 +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" );
2015-08-26 12:38:49 +01: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
2013-07-22 16:54:19 +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 ;
if ( updating )
{
Utility . UpdateOptionsFromDb ( restoredb , m_options );
Utility . VerifyParameters ( restoredb , m_options );
}
2014-08-26 15:01:56 +02:00
2013-07-22 16:54:19 +02:00
//Record all blocksets and files needed
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 )
{
foreach ( var n in filelists )
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 );
}
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
2013-07-22 16:54:19 +02:00
foreach ( var entry in new AsyncDownloader ( filelistWork , backend ))
try
2013-03-08 22:24:54 +01:00
{
2014-05-15 12:47:16 +02:00
if ( m_result . TaskControlRendevouz () == TaskControlState . Stop )
{
backend . WaitForComplete ( restoredb , null );
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
2013-07-22 16:54:19 +02:00
using ( var tmpfile = entry . TempFile )
2013-03-08 22:24:54 +01:00
{
2016-03-13 17:54:59 +01:00
isFirstFilelist = false ;
2013-07-22 16:54:19 +02:00
if ( entry . Hash != null && entry . Size > 0 )
restoredb . UpdateRemoteVolume ( entry . Name , RemoteVolumeState . Verified , entry . Size , entry . 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
2018-06-30 17:28:23 +02:00
if (! hasUpdatedOptions && (! updating || autoDetectBlockSize ))
2015-04-08 21:01:36 +02:00
{
VolumeReaderBase . UpdateOptionsFromManifest ( parsed . CompressionModule , tmpfile , m_options );
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 );
2019-09-10 12:51:42 -04:00
// retrieve fileset data from dlist
var filesetData = VolumeReaderBase . GetFilesetData ( parsed . CompressionModule , tmpfile , m_options );
// update fileset using filesetData
restoredb . UpdateFullBackupStateInFileset ( filesetid , filesetData . IsFullBackup );
2019-09-02 18:20:21 -04:00
2013-07-22 16:54:19 +02:00
using ( var filelistreader = new FilesetVolumeReader ( parsed . CompressionModule , tmpfile , m_options ))
foreach ( var fe in filelistreader . Files . Where ( x => Library . Utility . FilterExpression . Matches ( filter , x . Path )))
{
try
{
2016-04-04 18:11:48 +02:00
var expectedmetablocks = ( fe . Metasize + blocksize - 1 ) / blocksize ;
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 );
else
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 :
2018-03-12 14:07:11 +01: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
}
2015-04-08 21:01:36 +02:00
//Make sure we write the config
if (! updating )
Utility . VerifyParameters ( restoredb , m_options , tr );
2018-03-12 14:07:11 +01:00
using ( new Logging . Timer ( LOGTAG , "CommitUpdateFilesetFromRemote" , "CommitUpdateFilesetFromRemote" ))
2013-07-22 16:54:19 +02:00
tr . Commit ();
}
2023-11-05 18:19:08 +01: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
using ( var hashalg = HashFactory . CreateHasher ( m_options . BlockHashAlgorithm ))
2015-04-08 21:01:36 +02:00
using ( var tr = restoredb . BeginTransaction ())
{
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
where n . FileType == RemoteVolumeType . Index
select new RemoteVolume ( n . File ) as IRemoteVolume ). ToList ();
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 ;
2013-07-22 16:54:19 +02:00
2015-04-08 21:01:36 +02:00
foreach ( var sf in new AsyncDownloader ( indexfiles , backend ))
try
2014-05-15 12:47:16 +02:00
{
2015-04-08 21:01:36 +02:00
if ( m_result . TaskControlRendevouz () == TaskControlState . Stop )
{
backend . WaitForComplete ( restoredb , null );
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
2015-04-08 21:01:36 +02:00
using ( var tmpfile = sf . TempFile )
2013-03-08 22:24:54 +01:00
{
2015-04-08 21:01:36 +02:00
if ( sf . Hash != null && sf . Size > 0 )
restoredb . UpdateRemoteVolume ( sf . Name , RemoteVolumeState . Verified , sf . Size , sf . Hash , tr );
using ( var svr = new IndexVolumeReader ( RestoreHandler . GetCompressionModule ( sf . Name ), tmpfile , m_options , hashsize ))
2013-03-08 22:24:54 +01:00
{
2015-04-08 21:01:36 +02:00
foreach ( var a in svr . Volumes )
{
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 );
2018-05-10 00:57:51 +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 ));
2021-04-04 11:17:13 -07:00
Logging . Log . WriteErrorMessage ( LOGTAG , "MissingFileDetected" , null , "Remote file referenced as {0} by {1}, but not found in list, registering a missing remote file" , filename , sf . Name );
2018-05-10 00:57:51 +02:00
missing = true ;
volumeID = restoredb . RegisterRemoteVolume ( filename , p . FileType , RemoteVolumeState . Temporary , tr );
2015-08-24 17:03:14 +01:00
}
2023-11-05 18:19:08 +01:00
bool anyChange = false ;
2015-04-08 21:01:36 +02:00
//Add all block/volume mappings
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 );
2015-04-08 21:01:36 +02:00
restoredb . AddIndexBlockLink ( restoredb . GetRemoteVolumeID ( sf . Name ), volumeID , tr );
}
2013-07-22 16:54:19 +02: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
2015-04-08 21:01:36 +02:00
foreach ( var b in svr . BlockLists )
2023-11-05 18:19:08 +01:00
restoredb . AddTempBlockListHash ( b . Hash , b . Blocklist , tr );
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
2018-03-12 14:07:11 +01:00
Logging . Log . WriteErrorMessage ( LOGTAG , "IndexFileProcessingFailed" , ex , "Failed to process index file: {0}" , sf . 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
}
2013-03-08 22:24:54 +01:00
2018-03-12 14:07:11 +01:00
using ( new Logging . Timer ( LOGTAG , "CommitRecreateDb" , "CommitRecreatedDb" ))
2015-04-08 21:01:36 +02:00
tr . Commit ();
2013-07-22 16:54:19 +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
2018-06-12 08:29:55 +02:00
// In some cases we have a stale reference from an index file to a deleted block file
2018-06-12 09:31:39 +02:00
if (! m_options . UnittestMode )
restoredb . CleanupMissingVolumes ();
2018-06-12 08:29:55 +02:00
2023-11-05 18:19:08 +01: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
restoredb . FindMissingBlocklistHashes ( hashsize , m_options . Blocksize , null );
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
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 )
2018-06-11 14:42:13 +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 ;
2018-06-11 14:42:13 +02:00
foreach ( var sf in new AsyncDownloader ( lst , backend ))
{
try
2014-05-15 12:47:16 +02:00
{
2018-06-11 14:42:13 +02:00
using ( var tmpfile = sf . TempFile )
using ( var rd = new BlockVolumeReader ( RestoreHandler . GetCompressionModule ( sf . Name ), tmpfile , m_options ))
using ( var tr = restoredb . BeginTransaction ())
2015-04-08 21:01:36 +02:00
{
2018-06-11 14:42:13 +02:00
if ( m_result . TaskControlRendevouz () == TaskControlState . Stop )
{
backend . WaitForComplete ( restoredb , null );
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
var volumeid = restoredb . GetRemoteVolumeID ( sf . Name );
restoredb . UpdateRemoteVolume ( sf . Name , RemoteVolumeState . Uploaded , sf . Size , sf . Hash , tr );
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 );
// 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
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 ) {
Logging . Log . WriteWarningMessage ( LOGTAG , "UpdatingTables" , null , "Unexpected changes caused by block {0}" , sf . Name );
}
if ( expRecreateDb )
restoredb . AddBlockAndBlockSetEntryFromTemp ( hashsize , m_options . Blocksize , tr , false );
else
restoredb . FindMissingBlocklistHashes ( hashsize , m_options . Blocksize , tr );
}
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 )
blockprocessor ( sf . Name , rd );
}
}
catch ( Exception ex )
{
Logging . Log . WriteWarningMessage ( LOGTAG , "FailedRebuildingWithFile" , ex , "Failed to use information from {0} to rebuild database: {1}" , sf . 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
2021-04-04 11:17:13 -07:00
backend . WaitForComplete ( restoredb , null );
// In some cases we have a stale reference from an index file to a deleted block file
2018-06-12 09:31:39 +02:00
if (! m_options . UnittestMode )
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 )
{
foreach ( var compmodule in Library . DynamicLoader . CompressionLoader . Keys )
foreach ( var encmodule in Library . DynamicLoader . EncryptionLoader . Keys . Union ( new string [] { "" }))
{
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 ()
{
}
}
}