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.
2019-07-23 10:35:33 -04:00
using System ;
2013-03-26 19:27:26 +01:00
using System.Collections.Generic ;
using System.Linq ;
2017-01-09 11:35:38 +01:00
using Duplicati.Library.Interface ;
2019-07-23 10:35:33 -04:00
using Duplicati.Library.Common.IO ;
2013-05-08 20:17:07 +02:00
using Duplicati.Library.Main.Database ;
using Duplicati.Library.Main.Volumes ;
2019-04-29 00:57:02 +02:00
using System.Security.Cryptography ;
2021-04-04 11:17:13 -07:00
using Duplicati.Library.Utility ;
2013-03-26 19:27:26 +01:00
2013-05-08 20:17:07 +02:00
namespace Duplicati.Library.Main.Operation
2013-03-26 19:27:26 +01:00
{
2013-05-25 16:40:15 +02:00
internal class RestoreHandler
{
2018-03-12 14:07:11 +01:00
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging . Log . LogTagFromType < RestoreHandler >();
2018-05-23 21:18:01 -07:00
private readonly string m_backendurl ;
private readonly Options m_options ;
2013-03-26 19:27:26 +01:00
private byte [] m_blockbuffer ;
2019-07-23 10:35:33 -04:00
private readonly RestoreResults m_result ;
2018-11-02 17:45:00 +01:00
private static readonly string DIRSEP = Util . DirectorySeparatorString ;
2013-03-26 19:27:26 +01:00
2013-05-25 16:40:15 +02:00
public RestoreHandler ( string backendurl , Options options , RestoreResults result )
2013-03-08 22:24:54 +01:00
{
2013-03-26 19:27:26 +01:00
m_options = options ;
m_backendurl = backendurl ;
2013-05-25 16:40:15 +02:00
m_result = result ;
2013-03-26 19:27:26 +01:00
}
/// <summary>
/// Gets the compression module by parsing the filename
/// </summary>
/// <param name="filename">The filename to parse</param>
/// <returns>The compression module</returns>
public static string GetCompressionModule ( string filename )
{
var tmp = VolumeBase . ParseFilename ( filename );
if ( tmp == null )
2018-03-12 14:07:11 +01:00
throw new UserInformationException ( string . Format ( "Unable to parse filename to valid entry: {0}" , filename ), "FailedToParseRemoteName" );
2013-03-26 19:27:26 +01:00
return tmp . CompressionModule ;
}
2015-04-08 21:01:36 +02:00
public static RecreateDatabaseHandler . NumberedFilterFilelistDelegate FilterNumberedFilelist ( DateTime time , long [] versions , bool singleTimeMatch = false )
2013-05-11 12:03:15 +02:00
{
if ( time . Kind == DateTimeKind . Unspecified )
throw new Exception ( "Unspecified datetime instance, must be either local or UTC" );
// Make sure the resolution is the same (i.e. no milliseconds)
2015-09-08 16:43:42 +02:00
if ( time . Ticks > 0 )
time = Library . Utility . Utility . DeserializeDateTime ( Library . Utility . Utility . SerializeDateTime ( time )). ToUniversalTime ();
2013-05-11 12:03:15 +02:00
return
_lst =>
{
// Unwrap, so we do not query the remote storage twice
var lst = ( from n in _lst
2013-06-26 21:52:59 +02:00
where n . FileType == RemoteVolumeType . Files
2013-05-11 12:03:15 +02:00
orderby n . Time descending
select n ). ToArray ();
2013-06-26 21:52:59 +02:00
var numbers = lst . Zip ( Enumerable . Range ( 0 , lst . Length ), ( a , b ) => new KeyValuePair < long , IParsedVolume >( b , a )). ToList ();
2013-05-11 12:03:15 +02:00
if ( time . Ticks > 0 && versions != null && versions . Length > 0 )
return from n in numbers
2015-04-08 21:01:36 +02:00
where ( singleTimeMatch ? n . Value . Time == time : n . Value . Time <= time ) && versions . Contains ( n . Key )
2013-05-11 12:03:15 +02:00
select n ;
else if ( time . Ticks > 0 )
return from n in numbers
2015-04-08 21:01:36 +02:00
where ( singleTimeMatch ? n . Value . Time == time : n . Value . Time <= time )
2013-05-11 12:03:15 +02:00
select n ;
else if ( versions != null && versions . Length > 0 )
return from n in numbers
where versions . Contains ( n . Key )
select n ;
else
return numbers ;
};
}
2013-05-13 22:32:05 +02:00
public void Run ( string [] paths , Library . Utility . IFilter filter = null )
2013-08-22 20:52:54 +02:00
{
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Restore_Begin );
2013-06-29 11:41:53 +02:00
// If we have both target paths and a filter, combine into a single filter
2013-08-22 20:52:54 +02:00
filter = Library . Utility . JoinedFilterExpression . Join ( new Library . Utility . FilterExpression ( paths ), filter );
2016-09-15 11:39:27 +02:00
2018-11-02 21:34:07 +01:00
if (! m_options . NoLocalDb && SystemIO . IO_OS . FileExists ( m_options . Dbpath ))
2013-05-13 22:32:05 +02:00
{
2015-04-08 20:33:30 +02:00
using ( var db = new LocalRestoreDatabase ( m_options . Dbpath ))
2013-08-22 20:52:54 +02:00
{
db . SetResult ( m_result );
DoRun ( db , filter , m_result );
2014-07-16 00:57:57 +02:00
db . WriteResults ();
2013-08-22 20:52:54 +02:00
}
2013-05-25 16:40:15 +02:00
2013-05-13 22:32:05 +02:00
return ;
}
2013-05-25 16:40:15 +02:00
2013-08-22 20:52:54 +02:00
2018-03-12 14:07:11 +01:00
Logging . Log . WriteInformationMessage ( LOGTAG , "NoLocalDatabase" , "No local database, building a temporary database" );
2013-08-22 20:52:54 +02:00
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Restore_RecreateDatabase );
2013-03-26 19:27:26 +01:00
2014-01-19 23:25:38 +01:00
using ( var tmpdb = new Library . Utility . TempFile ())
2013-03-26 19:27:26 +01:00
{
2013-05-13 22:32:05 +02:00
RecreateDatabaseHandler . NumberedFilterFilelistDelegate filelistfilter = FilterNumberedFilelist ( m_options . Time , m_options . Version );
2013-03-26 19:27:26 +01:00
// Simultaneously with downloading blocklists, we patch as much as we can from the blockvolumes
// This prevents repeated downloads, except for cases where the blocklists refer blocks
// that have been previously handled. A local blockvolume cache can reduce this issue
2015-04-08 20:33:30 +02:00
using ( var database = new LocalRestoreDatabase ( tmpdb ))
2013-03-26 19:27:26 +01:00
{
2015-03-04 16:23:44 +01:00
using ( var metadatastorage = new RestoreHandlerMetadataStorage ())
{
2018-09-26 21:15:11 -07:00
// TODO: When UpdateMissingBlocksTable is implemented, the localpatcher
// (removed in revision 9ce1e807 ("Remove unused variables and fields") can be activated
2015-03-04 16:23:44 +01:00
// and this will reduce the need for multiple downloads of the same volume
// TODO: This will need some work to preserve the missing block list for use with --fh-dryrun
m_result . RecreateDatabaseResults = new RecreateDatabaseResults ( m_result );
2018-03-12 14:07:11 +01:00
using ( new Logging . Timer ( LOGTAG , "RecreateTempDbForRestore" , "Recreate temporary database for restore" ))
2015-03-04 16:23:44 +01:00
new RecreateDatabaseHandler ( m_backendurl , m_options , ( RecreateDatabaseResults ) m_result . RecreateDatabaseResults )
2018-09-26 21:12:13 -07:00
. DoRun ( database , false , filter , filelistfilter , null );
2015-03-04 16:23:44 +01:00
if (! m_options . SkipMetadata )
2018-10-06 13:30:13 -07:00
ApplyStoredMetadata ( m_options , metadatastorage );
2015-03-04 16:23:44 +01:00
}
2013-04-04 20:34:26 +02:00
2014-01-19 23:26:59 +01:00
//If we have --version set, we need to adjust, as the db has only the required versions
//TODO: Bit of a hack to set options that way
if ( m_options . Version != null && m_options . Version . Length > 0 )
m_options . RawOptions [ "version" ] = string . Join ( "," , Enumerable . Range ( 0 , m_options . Version . Length ). Select ( x => x . ToString ()));
2016-09-15 11:39:27 +02:00
DoRun ( database , filter , m_result );
2013-03-26 19:27:26 +01:00
}
}
}
2015-03-04 16:23:44 +01:00
private static void PatchWithBlocklist ( LocalRestoreDatabase database , BlockVolumeReader blocks , Options options , RestoreResults result , byte [] blockbuffer , RestoreHandlerMetadataStorage metadatastorage )
2013-03-26 19:27:26 +01:00
{
2013-08-23 22:18:13 +02:00
var blocksize = options . Blocksize ;
2013-09-07 21:35:23 +02:00
var updateCounter = 0L ;
2015-01-27 22:21:43 +01:00
var fullblockverification = options . FullBlockVerification ;
2021-04-04 11:17:13 -07:00
using ( var blockhasher = HashFactory . CreateHasher ( options . BlockHashAlgorithm ))
2016-02-27 22:06:58 +01:00
using ( var blockmarker = database . CreateBlockMarker ())
2015-04-08 20:33:30 +02:00
using ( var volumekeeper = database . GetMissingBlockData ( blocks , options . Blocksize ))
2013-03-26 19:27:26 +01:00
{
2014-11-05 21:43:08 +01:00
foreach ( var restorelist in volumekeeper . FilesWithMissingBlocks )
2013-03-26 19:27:26 +01:00
{
2013-08-23 22:18:13 +02:00
var targetpath = restorelist . Path ;
2018-03-12 14:07:11 +01:00
2013-08-23 22:18:13 +02:00
if ( options . Dryrun )
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteDryrunMessage ( LOGTAG , "WouldPatchFile" , "Would patch file with remote data: {0}" , targetpath );
2013-08-23 22:18:13 +02:00
}
else
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "PatchingFile" , "Patching file with remote data: {0}" , targetpath );
2013-08-23 22:18:13 +02:00
try
{
2018-11-02 21:34:07 +01:00
var folderpath = SystemIO . IO_OS . PathGetDirectoryName ( targetpath );
if (! options . Dryrun && ! SystemIO . IO_OS . DirectoryExists ( folderpath ))
2013-08-23 22:18:13 +02:00
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "CreateMissingFolder" , null , "Creating missing folder {0} for file {1}" , folderpath , targetpath );
2018-11-02 21:34:07 +01:00
SystemIO . IO_OS . DirectoryCreate ( folderpath );
2013-08-23 22:18:13 +02:00
}
// TODO: Much faster if we iterate the volume and checks what blocks are used,
// because the compressors usually like sequential reading
2018-11-02 21:34:07 +01:00
using ( var file = SystemIO . IO_OS . FileOpenWrite ( targetpath ))
2013-08-23 22:18:13 +02:00
foreach ( var targetblock in restorelist . Blocks )
{
file . Position = targetblock . Offset ;
var size = blocks . ReadBlock ( targetblock . Key , blockbuffer );
if ( targetblock . Size == size )
{
2015-01-27 22:21:43 +01:00
var valid = ! fullblockverification ;
if (! valid )
{
var key = Convert . ToBase64String ( blockhasher . ComputeHash ( blockbuffer , 0 , size ));
if ( targetblock . Key == key )
valid = true ;
else
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "InvalidBlock" , null , "Invalid block detected for {0}, expected hash: {1}, actual hash: {2}" , targetpath , targetblock . Key , key );
2015-01-27 22:21:43 +01:00
}
if ( valid )
{
file . Write ( blockbuffer , 0 , size );
blockmarker . SetBlockRestored ( restorelist . FileID , targetblock . Offset / blocksize , targetblock . Key , size , false );
}
2016-01-21 22:26:37 +01:00
}
else
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "WrongBlockSize" , null , "Block with hash {0} should have size {1} but has size {2}" , targetblock . Key , targetblock . Size , size );
2016-01-21 22:26:37 +01:00
}
2013-08-23 22:18:13 +02:00
}
2013-09-07 21:35:23 +02:00
2016-02-18 21:01:58 +01:00
if ((++ updateCounter ) % 20 == 0 )
2013-08-23 22:18:13 +02:00
blockmarker . UpdateProcessed ( result . OperationProgressUpdater );
}
catch ( Exception ex )
2013-05-25 22:20:32 +02:00
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "PatchFailed" , ex , "Failed to patch file: \"{0}\", message: {1}, message: {1}" , targetpath , ex . Message );
2018-06-12 09:31:39 +02:00
if ( options . UnittestMode )
throw ;
2013-05-25 22:20:32 +02:00
}
2014-11-05 21:43:08 +01:00
}
}
2014-11-19 14:15:11 +01:00
if (! options . SkipMetadata )
2014-11-05 21:43:08 +01:00
{
2014-11-19 14:15:11 +01:00
foreach ( var restoremetadata in volumekeeper . MetadataWithMissingBlocks )
2014-11-05 21:43:08 +01:00
{
2014-11-19 14:15:11 +01:00
var targetpath = restoremetadata . Path ;
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "RecordingMetadata" , "Recording metadata from remote data: {0}" , targetpath );
2014-11-19 14:15:11 +01:00
2015-03-04 16:23:44 +01:00
try
2013-08-23 22:18:13 +02:00
{
2015-03-04 16:23:44 +01:00
// TODO: When we support multi-block metadata this needs to deal with it
using ( var ms = new System . IO . MemoryStream ())
2014-11-05 21:43:08 +01:00
{
2015-03-04 16:23:44 +01:00
foreach ( var targetblock in restoremetadata . Blocks )
2014-11-19 14:15:11 +01:00
{
2015-03-04 16:23:44 +01:00
ms . Position = targetblock . Offset ;
var size = blocks . ReadBlock ( targetblock . Key , blockbuffer );
if ( targetblock . Size == size )
2014-11-05 21:43:08 +01:00
{
2015-03-04 16:23:44 +01:00
ms . Write ( blockbuffer , 0 , size );
blockmarker . SetBlockRestored ( restoremetadata . FileID , targetblock . Offset / blocksize , targetblock . Key , size , true );
}
2014-11-19 14:15:11 +01:00
}
2015-03-04 16:23:44 +01:00
ms . Position = 0 ;
metadatastorage . Add ( targetpath , ms );
//blockmarker.RecordMetadata(restoremetadata.FileID, ms);
2014-11-05 21:43:08 +01:00
}
2013-08-23 22:18:13 +02:00
}
2015-03-04 16:23:44 +01:00
catch ( Exception ex )
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "MetatdataRecordFailed" , ex , "Failed to record metadata for file: \"{0}\", message: {1}" , targetpath , ex . Message );
2018-06-12 09:31:39 +02:00
if ( options . UnittestMode )
throw ;
2015-03-04 16:23:44 +01:00
}
2013-08-23 22:18:13 +02:00
}
2013-03-26 19:27:26 +01:00
}
2013-09-07 21:35:23 +02:00
blockmarker . UpdateProcessed ( result . OperationProgressUpdater );
2018-03-12 14:07:11 +01:00
blockmarker . Commit ();
2013-03-26 19:27:26 +01:00
}
}
2018-10-06 13:30:13 -07:00
private static void ApplyStoredMetadata ( Options options , RestoreHandlerMetadataStorage metadatastorage )
2015-03-04 16:23:44 +01:00
{
foreach ( var metainfo in metadatastorage . Records )
{
var targetpath = metainfo . Key ;
if ( options . Dryrun )
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteDryrunMessage ( LOGTAG , "WouldPatchMetadata" , "Would patch metadata with remote data: {0}" , targetpath );
2015-03-04 16:23:44 +01:00
}
else
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "PatchingMetadata" , "Patching metadata with remote data: {0}" , targetpath );
2015-03-04 16:23:44 +01:00
try
{
2018-06-14 15:59:33 +02:00
var folderpath = Duplicati . Library . Utility . Utility . GetParent ( targetpath , false );
2018-11-02 21:34:07 +01:00
if (! options . Dryrun && ! SystemIO . IO_OS . DirectoryExists ( folderpath ))
2015-03-04 16:23:44 +01:00
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "CreateMissingFolder" , null , "Creating missing folder {0} for target {1}" , folderpath , targetpath );
2018-11-02 21:34:07 +01:00
SystemIO . IO_OS . DirectoryCreate ( folderpath );
2015-03-04 16:23:44 +01:00
}
2018-06-07 13:26:28 +02:00
ApplyMetadata ( targetpath , metainfo . Value , options . RestorePermissions , options . RestoreSymlinkMetadata , options . Dryrun );
2015-03-04 16:23:44 +01:00
}
catch ( Exception ex )
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "MetadataWriteFailed" , ex , "Failed to apply metadata to file: \"{0}\", message: {1}" , targetpath , ex . Message );
2018-06-12 09:31:39 +02:00
if ( options . UnittestMode )
throw ;
2015-03-04 16:23:44 +01:00
}
}
}
}
2013-05-25 16:40:15 +02:00
private void DoRun ( LocalDatabase dbparent , Library . Utility . IFilter filter , RestoreResults result )
2013-08-22 20:52:54 +02:00
{
//In this case, we check that the remote storage fits with the database.
//We can then query the database and find the blocks that we need to do the restore
2015-04-08 20:33:30 +02:00
using ( var database = new LocalRestoreDatabase ( dbparent ))
2013-08-22 20:52:54 +02:00
using ( var backend = new BackendManager ( m_backendurl , m_options , result . BackendWriter , database ))
2015-03-04 16:23:44 +01:00
using ( var metadatastorage = new RestoreHandlerMetadataStorage ())
2013-08-22 20:52:54 +02:00
{
2014-01-19 23:26:59 +01:00
database . SetResult ( m_result );
2015-04-08 20:33:30 +02:00
Utility . UpdateOptionsFromDb ( database , m_options );
2013-08-22 20:52:54 +02:00
Utility . VerifyParameters ( database , m_options );
2015-04-08 20:33:30 +02:00
m_blockbuffer = new byte [ m_options . Blocksize ];
2013-08-22 20:52:54 +02:00
if (! m_options . NoBackendverification )
{
2020-02-29 16:33:32 -06:00
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Restore_PreRestoreVerify );
2020-04-05 11:41:48 -07:00
FilelistProcessor . VerifyRemoteList ( backend , m_options , database , result . BackendWriter , false , null );
2013-08-22 20:52:54 +02:00
}
//Figure out what files are to be patched, and what blocks are needed
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Restore_CreateFileList );
2018-03-12 14:07:11 +01:00
using ( new Logging . Timer ( LOGTAG , "PrepareBlockList" , "PrepareBlockList" ))
2013-08-22 20:52:54 +02:00
PrepareBlockAndFileList ( database , m_options , filter , result );
//Make the entire output setup
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Restore_CreateTargetFolders );
2018-03-12 14:07:11 +01:00
using ( new Logging . Timer ( LOGTAG , "CreateDirectory" , "CreateDirectory" ))
2013-08-22 20:52:54 +02:00
CreateDirectoryStructure ( database , m_options , result );
2013-05-20 13:48:44 +02:00
2013-05-29 22:15:13 +02:00
//If we are patching an existing target folder, do not touch stuff that is already updated
2013-08-22 20:52:54 +02:00
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Restore_ScanForExistingFiles );
2021-04-04 11:17:13 -07:00
using ( var blockhasher = HashFactory . CreateHasher ( m_options . BlockHashAlgorithm ))
using ( var filehasher = HashFactory . CreateHasher ( m_options . FileHashAlgorithm ))
2018-03-12 14:07:11 +01:00
using ( new Logging . Timer ( LOGTAG , "ScanForExistingTargetBlocks" , "ScanForExistingTargetBlocks" ))
2013-08-22 20:52:54 +02:00
ScanForExistingTargetBlocks ( database , m_blockbuffer , blockhasher , filehasher , m_options , result );
2013-03-26 19:27:26 +01:00
2013-05-29 22:15:13 +02:00
//Look for existing blocks in the original source files only
2021-04-04 11:17:13 -07:00
using ( var blockhasher = HashFactory . CreateHasher ( m_options . BlockHashAlgorithm ))
using ( new Logging . Timer ( LOGTAG , "ScanForExistingSourceBlocksFast" , "ScanForExistingSourceBlocksFast" ))
2013-08-22 20:52:54 +02:00
if (! m_options . NoLocalBlocks && ! string . IsNullOrEmpty ( m_options . Restorepath ))
{
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Restore_ScanForLocalBlocks );
ScanForExistingSourceBlocksFast ( database , m_options , m_blockbuffer , blockhasher , result );
}
2013-04-28 12:19:21 +02:00
2014-05-15 12:47:16 +02:00
if ( m_result . TaskControlRendevouz () == TaskControlState . Stop )
{
backend . WaitForComplete ( database , null );
return ;
}
2013-08-22 20:52:54 +02:00
// If other local files already have the blocks we want, we use them instead of downloading
if ( m_options . PatchWithLocalBlocks )
{
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Restore_PatchWithLocalBlocks );
2021-04-04 11:17:13 -07:00
using ( var blockhasher = HashFactory . CreateHasher ( m_options . BlockHashAlgorithm ))
using ( new Logging . Timer ( LOGTAG , "PatchWithLocalBlocks" , "PatchWithLocalBlocks" ))
2015-03-04 16:23:44 +01:00
ScanForExistingSourceBlocks ( database , m_options , m_blockbuffer , blockhasher , result , metadatastorage );
2013-08-22 20:52:54 +02:00
}
2014-05-15 12:47:16 +02:00
if ( m_result . TaskControlRendevouz () == TaskControlState . Stop )
{
backend . WaitForComplete ( database , null );
return ;
}
2013-08-22 20:52:54 +02:00
// Fill BLOCKS with remote sources
2016-03-03 20:15:27 +01:00
List < IRemoteVolume > volumes ;
2018-03-12 14:07:11 +01:00
using ( new Logging . Timer ( LOGTAG , "GetMissingVolumes" , "GetMissingVolumes" ))
2016-03-03 20:15:27 +01:00
volumes = database . GetMissingVolumes (). ToList ();
2013-03-26 19:27:26 +01:00
2013-08-22 20:52:54 +02:00
if ( volumes . Count > 0 )
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteInformationMessage ( LOGTAG , "RemoteFileCount" , "{0} remote files are required to restore" , volumes . Count );
2013-08-22 20:52:54 +02:00
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Restore_DownloadingRemoteFiles );
}
2013-03-26 19:27:26 +01:00
2013-08-23 22:18:13 +02:00
var brokenFiles = new List < string >();
2016-09-15 11:39:27 +02:00
foreach ( var blockvolume in new AsyncDownloader ( volumes , backend ))
try
{
2014-05-15 12:47:16 +02:00
if ( m_result . TaskControlRendevouz () == TaskControlState . Stop )
{
backend . WaitForComplete ( database , null );
return ;
}
2016-09-15 11:39:27 +02:00
using ( var tmpfile = blockvolume . TempFile )
using ( var blocks = new BlockVolumeReader ( GetCompressionModule ( blockvolume . Name ), tmpfile , m_options ))
2015-03-04 16:23:44 +01:00
PatchWithBlocklist ( database , blocks , m_options , result , m_blockbuffer , metadatastorage );
2016-09-15 11:39:27 +02:00
}
catch ( Exception ex )
{
2013-08-23 22:18:13 +02:00
brokenFiles . Add ( blockvolume . Name );
2018-03-12 14:07:11 +01:00
Logging . Log . WriteErrorMessage ( LOGTAG , "PatchingFailed" , ex , "Failed to patch with remote file: \"{0}\", message: {1}" , blockvolume . Name , ex . Message );
2014-05-15 12:47:16 +02:00
if ( ex is System . Threading . ThreadAbortException )
throw ;
2016-09-15 11:39:27 +02:00
}
2015-03-04 16:23:44 +01:00
2020-11-22 08:45:26 -08:00
var fileErrors = 0L ;
2017-08-12 14:09:55 +01:00
// Restore empty files. They might not have any blocks so don't appear in any volume.
2020-07-01 16:13:04 -07:00
foreach ( var file in database . GetFilesToRestore ( true ). Where ( item => item . Length == 0 ))
{
2020-11-22 08:56:53 -08:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "RestoreEmptyFile" , "Restoring empty file \"{0}\"" , file . Path );
2020-07-01 16:13:04 -07:00
2020-10-03 14:09:50 +02:00
try
{
SystemIO . IO_OS . DirectoryCreate ( SystemIO . IO_OS . PathGetDirectoryName ( file . Path ));
// Just create the file and close it right away, empty statement is intentional.
using ( SystemIO . IO_OS . FileCreate ( file . Path ))
{
}
}
catch ( Exception ex )
2018-04-25 17:19:13 +02:00
{
2020-11-22 08:45:26 -08:00
fileErrors ++;
Logging . Log . WriteErrorMessage ( LOGTAG , "RestoreFileFailed" , ex , "Failed to restore empty file: \"{0}\". Error message was: {1}" , file . Path , ex . Message );
2020-10-03 14:09:50 +02:00
if ( ex is System . Threading . ThreadAbortException )
throw ;
2018-04-25 17:19:13 +02:00
}
2017-08-12 14:09:55 +01:00
}
2016-03-05 02:00:28 +01:00
// Enforcing the length of files is now already done during ScanForExistingTargetBlocks
// and thus not necessary anymore.
2015-03-04 16:23:44 +01:00
// Apply metadata
if (! m_options . SkipMetadata )
2018-10-06 13:30:13 -07:00
ApplyStoredMetadata ( m_options , metadatastorage );
2013-08-23 22:18:13 +02:00
2014-05-15 12:47:16 +02:00
if ( m_result . TaskControlRendevouz () == TaskControlState . Stop )
return ;
2013-08-22 20:52:54 +02:00
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Restore_PostRestoreVerify );
2014-12-30 16:10:45 +01:00
if ( m_options . PerformRestoredFileVerification )
{
// After all blocks in the files are restored, verify the file hash
2021-04-04 11:17:13 -07:00
using ( var filehasher = HashFactory . CreateHasher ( m_options . FileHashAlgorithm ))
using ( new Logging . Timer ( LOGTAG , "RestoreVerification" , "RestoreVerification" ))
2016-03-05 02:00:28 +01:00
foreach ( var file in database . GetFilesToRestore ( true ))
2013-05-29 22:15:13 +02:00
{
2014-12-30 16:10:45 +01:00
try
2014-05-15 12:47:16 +02:00
{
2014-12-30 16:10:45 +01:00
if ( m_result . TaskControlRendevouz () == TaskControlState . Stop )
{
backend . WaitForComplete ( database , null );
return ;
}
2014-05-15 12:47:16 +02:00
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "TestFileIntegrity" , "Testing restored file integrity: {0}" , file . Path );
2013-05-29 22:15:13 +02:00
2014-12-30 16:10:45 +01:00
string key ;
long size ;
2018-11-02 21:34:07 +01:00
using ( var fs = SystemIO . IO_OS . FileOpenRead ( file . Path ))
2014-12-30 16:10:45 +01:00
{
size = fs . Length ;
key = Convert . ToBase64String ( filehasher . ComputeHash ( fs ));
}
if ( key != file . Hash )
throw new Exception ( string . Format ( "Failed to restore file: \"{0}\". File hash is {1}, expected hash is {2}" , file . Path , key , file . Hash ));
2018-12-12 12:14:11 -02:00
result . RestoredFiles ++;
2014-12-30 16:10:45 +01:00
result . SizeOfRestoredFiles += size ;
}
catch ( Exception ex )
2013-05-29 22:15:13 +02:00
{
2014-12-30 16:10:45 +01:00
fileErrors ++;
2020-10-03 14:09:50 +02:00
Logging . Log . WriteErrorMessage ( LOGTAG , "RestoreFileFailed" , ex , "Failed to restore file: \"{0}\". Error message was: {1}" , file . Path , ex . Message );
2014-12-30 16:10:45 +01:00
if ( ex is System . Threading . ThreadAbortException )
throw ;
2013-05-29 22:15:13 +02:00
}
2013-05-25 16:40:15 +02:00
}
2014-12-30 16:10:45 +01:00
}
2013-08-23 22:18:13 +02:00
2016-03-14 10:34:14 +01:00
if ( fileErrors > 0 && brokenFiles . Count > 0 )
2018-03-12 14:07:11 +01:00
Logging . Log . WriteInformationMessage ( LOGTAG , "RestoreFailures" , "Failed to restore {0} files, additionally the following files failed to download, which may be the cause:{1}{2}" , fileErrors , Environment . NewLine , string . Join ( Environment . NewLine , brokenFiles ));
2016-03-14 10:34:14 +01:00
else if ( fileErrors > 0 )
2018-03-12 14:07:11 +01:00
Logging . Log . WriteInformationMessage ( LOGTAG , "RestoreFailures" , "Failed to restore {0} files" , fileErrors );
2018-12-12 12:14:11 -02:00
else if ( result . RestoredFiles == 0 )
2018-11-08 08:15:41 -02:00
Logging . Log . WriteWarningMessage ( LOGTAG , "NoFilesRestored" , null , "Restore completed without errors but no files were restored" );
2013-03-26 19:27:26 +01:00
// Drop the temp tables
database . DropRestoreTable ();
2013-04-09 20:43:27 +02:00
backend . WaitForComplete ( database , null );
2013-03-26 19:27:26 +01:00
}
2013-05-25 16:40:15 +02:00
2013-08-22 20:52:54 +02:00
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Restore_Complete );
2014-04-22 12:36:21 +02:00
result . EndTime = DateTime . UtcNow ;
2013-03-26 19:27:26 +01:00
}
2018-06-07 13:26:28 +02:00
private static void ApplyMetadata ( string path , System . IO . Stream stream , bool restorePermissions , bool restoreSymlinkMetadata , bool dryrun )
2013-03-26 19:27:26 +01:00
{
2014-11-05 21:43:08 +01:00
using ( var tr = new System . IO . StreamReader ( stream ))
using ( var jr = new Newtonsoft . Json . JsonTextReader ( tr ))
{
var metadata = new Newtonsoft . Json . JsonSerializer (). Deserialize < Dictionary < string , string >>( jr );
string k ;
long t ;
2014-11-06 01:23:31 +01:00
System . IO . FileAttributes fa ;
2016-09-15 11:39:27 +02:00
// If this is dry-run, we stop after having deserialized the metadata
if ( dryrun )
return ;
2016-04-27 15:37:27 +02:00
2017-11-26 10:53:14 -08:00
var isDirTarget = path . EndsWith ( DIRSEP , StringComparison . Ordinal );
2014-11-15 17:43:29 +01:00
var targetpath = isDirTarget ? path . Substring ( 0 , path . Length - 1 ) : path ;
2014-11-06 01:23:31 +01:00
// Make the symlink first, otherwise we cannot apply metadata to it
if ( metadata . TryGetValue ( "CoreSymlinkTarget" , out k ))
2018-11-02 21:34:07 +01:00
SystemIO . IO_OS . CreateSymlink ( targetpath , k , isDirTarget );
2016-09-15 11:39:27 +02:00
// If the target is a folder, make sure we create it first
2018-11-02 21:34:07 +01:00
else if ( isDirTarget && ! SystemIO . IO_OS . DirectoryExists ( targetpath ))
SystemIO . IO_OS . DirectoryCreate ( targetpath );
2014-11-06 01:23:31 +01:00
2018-06-07 13:26:28 +02:00
// Avoid setting restoring symlink metadata, as that writes the symlink target, not the symlink itself
2018-11-02 21:34:07 +01:00
if (! restoreSymlinkMetadata && Snapshots . SnapshotUtility . IsSymlink ( SystemIO . IO_OS , targetpath ))
2018-06-07 13:26:28 +02:00
{
Logging . Log . WriteVerboseMessage ( LOGTAG , "no-symlink-metadata-restored" , "Not applying metadata to symlink: {0}" , targetpath );
return ;
}
2014-11-05 21:43:08 +01:00
if ( metadata . TryGetValue ( "CoreLastWritetime" , out k ) && long . TryParse ( k , out t ))
2014-11-15 17:43:29 +01:00
{
if ( isDirTarget )
2018-11-02 21:34:07 +01:00
SystemIO . IO_OS . DirectorySetLastWriteTimeUtc ( targetpath , new DateTime ( t , DateTimeKind . Utc ));
2014-11-15 17:43:29 +01:00
else
2018-11-02 21:34:07 +01:00
SystemIO . IO_OS . FileSetLastWriteTimeUtc ( targetpath , new DateTime ( t , DateTimeKind . Utc ));
2014-11-15 17:43:29 +01:00
}
2014-11-06 01:23:31 +01:00
if ( metadata . TryGetValue ( "CoreCreatetime" , out k ) && long . TryParse ( k , out t ))
2014-11-15 17:43:29 +01:00
{
if ( isDirTarget )
2018-11-02 21:34:07 +01:00
SystemIO . IO_OS . DirectorySetCreationTimeUtc ( targetpath , new DateTime ( t , DateTimeKind . Utc ));
2014-11-15 17:43:29 +01:00
else
2018-11-02 21:34:07 +01:00
SystemIO . IO_OS . FileSetCreationTimeUtc ( targetpath , new DateTime ( t , DateTimeKind . Utc ));
2014-11-15 17:43:29 +01:00
}
2014-11-06 01:23:31 +01:00
if ( metadata . TryGetValue ( "CoreAttributes" , out k ) && Enum . TryParse ( k , true , out fa ))
2018-11-02 21:34:07 +01:00
SystemIO . IO_OS . SetFileAttributes ( targetpath , fa );
2014-11-06 01:23:31 +01:00
2018-11-02 21:34:07 +01:00
SystemIO . IO_OS . SetMetadata ( path , metadata , restorePermissions );
2014-11-05 21:43:08 +01:00
}
2013-03-26 19:27:26 +01:00
}
2013-05-25 16:40:15 +02:00
private static void ScanForExistingSourceBlocksFast ( LocalRestoreDatabase database , Options options , byte [] blockbuffer , System . Security . Cryptography . HashAlgorithm hasher , RestoreResults result )
2013-04-28 12:19:21 +02:00
{
// Fill BLOCKS with data from known local source files
using ( var blockmarker = database . CreateBlockMarker ())
{
2013-09-07 21:35:23 +02:00
var updateCount = 0L ;
2015-04-08 20:33:30 +02:00
foreach ( var entry in database . GetFilesAndSourceBlocksFast ( options . Blocksize ))
2016-09-15 11:39:27 +02:00
{
2013-04-28 12:19:21 +02:00
var targetpath = entry . TargetPath ;
var targetfileid = entry . TargetFileID ;
var sourcepath = entry . SourcePath ;
var patched = false ;
2016-09-15 11:39:27 +02:00
try
{
2018-11-02 21:34:07 +01:00
if ( SystemIO . IO_OS . FileExists ( sourcepath ))
2016-09-15 11:39:27 +02:00
{
2018-11-02 21:34:07 +01:00
var folderpath = SystemIO . IO_OS . PathGetDirectoryName ( targetpath );
if (! options . Dryrun && ! SystemIO . IO_OS . DirectoryExists ( folderpath ))
2016-09-15 11:39:27 +02:00
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "CreateMissingFolder" , null , "Creating missing folder {0} for file {1}" , folderpath , targetpath );
2018-11-02 21:34:07 +01:00
SystemIO . IO_OS . DirectoryCreate ( folderpath );
2016-09-15 11:39:27 +02:00
}
2018-11-02 21:34:07 +01:00
using ( var targetstream = options . Dryrun ? null : SystemIO . IO_OS . FileOpenWrite ( targetpath ))
2016-09-15 11:39:27 +02:00
{
try
{
2018-11-02 21:34:07 +01:00
using ( var sourcestream = SystemIO . IO_OS . FileOpenRead ( sourcepath ))
2016-09-15 11:39:27 +02:00
{
foreach ( var block in entry . Blocks )
{
2014-05-15 12:47:16 +02:00
if ( result . TaskControlRendevouz () == TaskControlState . Stop )
return ;
2014-11-05 21:43:08 +01:00
//TODO: Handle metadata
2016-09-15 11:39:27 +02:00
if ( sourcestream . Length > block . Offset )
{
sourcestream . Position = block . Offset ;
2018-05-30 17:42:59 -07:00
int size = Library . Utility . Utility . ForceStreamRead ( sourcestream , blockbuffer , blockbuffer . Length );
2016-09-15 11:39:27 +02:00
if ( size == block . Size )
{
var key = Convert . ToBase64String ( hasher . ComputeHash ( blockbuffer , 0 , size ));
if ( key == block . Hash )
{
2013-05-29 22:15:13 +02:00
patched = true ;
2016-09-15 11:39:27 +02:00
if (! options . Dryrun )
{
targetstream . Position = block . Offset ;
targetstream . Write ( blockbuffer , 0 , size );
}
blockmarker . SetBlockRestored ( targetfileid , block . Index , key , block . Size , false );
}
}
}
}
}
}
catch ( Exception ex )
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "PatchingFileLocalFailed" , ex , "Failed to patch file: \"{0}\" with data from local file \"{1}\", message: {2}" , targetpath , sourcepath , ex . Message );
2014-05-15 12:47:16 +02:00
if ( ex is System . Threading . ThreadAbortException )
throw ;
2016-09-15 11:39:27 +02:00
}
}
2016-02-27 22:06:58 +01:00
if ((++ updateCount ) % 20 == 0 )
2014-05-15 12:47:16 +02:00
{
2013-09-07 21:35:23 +02:00
blockmarker . UpdateProcessed ( result . OperationProgressUpdater );
2014-05-15 12:47:16 +02:00
if ( result . TaskControlRendevouz () == TaskControlState . Stop )
return ;
}
2013-08-23 22:18:13 +02:00
2016-09-15 11:39:27 +02:00
}
2013-05-29 22:15:13 +02:00
else
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "LocalSourceMissing" , "Local source file not found: {0}" , sourcepath );
2013-05-29 22:15:13 +02:00
}
2016-09-15 11:39:27 +02:00
}
2013-04-28 12:19:21 +02:00
catch ( Exception ex )
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "PatchingFileLocalFailed" , ex , "Failed to patch file: \"{0}\" with local data, message: {1}" , targetpath , ex . Message );
2014-05-15 12:47:16 +02:00
if ( ex is System . Threading . ThreadAbortException )
throw ;
2018-06-12 09:31:39 +02:00
if ( options . UnittestMode )
throw ;
2013-04-28 12:19:21 +02:00
}
2013-05-29 22:15:13 +02:00
if ( patched )
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "FilePatchedWithLocal" , "Target file is patched with some local data: {0}" , targetpath );
2013-05-29 22:15:13 +02:00
else
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "FilePatchedWithLocal" , "Target file is not patched any local data: {0}" , targetpath );
2013-05-29 22:15:13 +02:00
2013-05-08 19:57:13 +02:00
if ( patched && options . Dryrun )
2018-03-12 14:07:11 +01:00
Logging . Log . WriteDryrunMessage ( LOGTAG , "WouldPatchWithLocal" , "Would patch file with local data: {0}" , targetpath );
2016-09-15 11:39:27 +02:00
}
2013-09-07 21:35:23 +02:00
blockmarker . UpdateProcessed ( result . OperationProgressUpdater );
2018-03-12 14:07:11 +01:00
blockmarker . Commit ();
2013-04-28 12:19:21 +02:00
}
}
2015-03-04 16:23:44 +01:00
private static void ScanForExistingSourceBlocks ( LocalRestoreDatabase database , Options options , byte [] blockbuffer , System . Security . Cryptography . HashAlgorithm hasher , RestoreResults result , RestoreHandlerMetadataStorage metadatastorage )
2013-03-26 19:27:26 +01:00
{
// Fill BLOCKS with data from known local source files
using ( var blockmarker = database . CreateBlockMarker ())
{
2013-09-07 21:35:23 +02:00
var updateCount = 0L ;
2015-04-08 20:33:30 +02:00
foreach ( var restorelist in database . GetFilesAndSourceBlocks ( options . SkipMetadata , options . Blocksize ))
2013-03-26 19:27:26 +01:00
{
var targetpath = restorelist . TargetPath ;
2013-04-27 10:20:15 +02:00
var targetfileid = restorelist . TargetFileID ;
2013-03-29 14:13:31 +01:00
var patched = false ;
2013-03-26 19:27:26 +01:00
try
{
2014-05-15 12:47:16 +02:00
if ( result . TaskControlRendevouz () == TaskControlState . Stop )
return ;
2018-11-02 21:34:07 +01:00
var folderpath = SystemIO . IO_OS . PathGetDirectoryName ( targetpath );
if (! options . Dryrun && ! SystemIO . IO_OS . DirectoryExists ( folderpath ))
2016-09-15 11:39:27 +02:00
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "CreateMissingFolder" , null , "Creating missing folder {0} for file {1}" , folderpath , targetpath );
2018-11-02 21:34:07 +01:00
SystemIO . IO_OS . DirectoryCreate ( folderpath );
2016-09-15 11:39:27 +02:00
}
2013-05-21 21:15:03 +02:00
2018-11-02 21:34:07 +01:00
using ( var file = options . Dryrun ? null : SystemIO . IO_OS . FileOpenWrite ( targetpath ))
2013-03-26 19:27:26 +01:00
foreach ( var targetblock in restorelist . Blocks )
{
foreach ( var source in targetblock . Blocksources )
{
try
{
2014-05-15 12:47:16 +02:00
if ( result . TaskControlRendevouz () == TaskControlState . Stop )
return ;
2014-11-05 21:43:08 +01:00
2018-11-02 21:34:07 +01:00
if ( SystemIO . IO_OS . FileExists ( source . Path ))
2014-11-05 21:43:08 +01:00
{
if ( source . IsMetadata )
{
// TODO: Handle this by reconstructing
// metadata from file and checking the hash
continue ;
}
else
2013-03-26 19:27:26 +01:00
{
2018-11-02 21:34:07 +01:00
using ( var sourcefile = SystemIO . IO_OS . FileOpenRead ( source . Path ))
2013-03-26 19:27:26 +01:00
{
2014-11-05 21:43:08 +01:00
sourcefile . Position = source . Offset ;
2018-05-30 17:42:59 -07:00
int size = Library . Utility . Utility . ForceStreamRead ( sourcefile , blockbuffer , blockbuffer . Length );
2014-11-05 21:43:08 +01:00
if ( size == targetblock . Size )
2013-03-26 19:27:26 +01:00
{
2014-11-05 21:43:08 +01:00
var key = Convert . ToBase64String ( hasher . ComputeHash ( blockbuffer , 0 , size ));
if ( key == targetblock . Hash )
{
2016-09-15 11:39:27 +02:00
if (! options . Dryrun )
2014-11-05 21:43:08 +01:00
{
if ( targetblock . IsMetadata )
2015-03-04 16:23:44 +01:00
metadatastorage . Add ( targetpath , new System . IO . MemoryStream ( blockbuffer , 0 , size ));
2014-11-05 21:43:08 +01:00
else
2016-03-16 01:04:55 +01:00
{
file . Position = targetblock . Offset ;
file . Write ( blockbuffer , 0 , size );
}
2014-11-05 21:43:08 +01:00
}
2016-09-15 11:39:27 +02:00
2014-11-05 21:43:08 +01:00
blockmarker . SetBlockRestored ( targetfileid , targetblock . Index , key , targetblock . Size , false );
patched = true ;
break ;
}
2013-03-26 19:27:26 +01:00
}
}
}
2014-11-05 21:43:08 +01:00
}
2013-03-26 19:27:26 +01:00
}
catch ( Exception ex )
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "PatchingFileLocalFailed" , ex , "Failed to patch file: \"{0}\" with data from local file \"{1}\", message: {2}" , targetpath , source . Path , ex . Message );
2014-05-15 12:47:16 +02:00
if ( ex is System . Threading . ThreadAbortException )
throw ;
2013-03-26 19:27:26 +01:00
}
}
}
2013-08-23 22:18:13 +02:00
2020-11-21 13:58:16 -08:00
if ((++ updateCount ) % 20 == 0 )
blockmarker . UpdateProcessed ( result . OperationProgressUpdater );
2013-03-26 19:27:26 +01:00
}
catch ( Exception ex )
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "PatchingFileLocalFailed" , ex , "Failed to patch file: \"{0}\" with local data, message: {1}" , targetpath , ex . Message );
2018-06-12 09:31:39 +02:00
if ( options . UnittestMode )
throw ;
2013-03-26 19:27:26 +01:00
}
2013-03-29 14:13:31 +01:00
2013-05-29 22:15:13 +02:00
if ( patched )
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "FilePatchedWithLocal" , "Target file is patched with some local data: {0}" , targetpath );
2013-05-29 22:15:13 +02:00
else
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "FilePatchedWithLocal" , "Target file is not patched any local data: {0}" , targetpath );
2013-05-29 22:15:13 +02:00
2013-05-08 19:57:13 +02:00
if ( patched && options . Dryrun )
2018-03-12 14:07:11 +01:00
Logging . Log . WriteDryrunMessage ( LOGTAG , "WouldPatchWithLocal" , string . Format ( "Would patch file with local data: {0}" , targetpath ));
2013-03-26 19:27:26 +01:00
}
2013-09-07 21:35:23 +02:00
blockmarker . UpdateProcessed ( result . OperationProgressUpdater );
2018-03-12 14:07:11 +01:00
blockmarker . Commit ();
2013-03-26 19:27:26 +01:00
}
}
2013-05-25 16:40:15 +02:00
private static void PrepareBlockAndFileList ( LocalRestoreDatabase database , Options options , Library . Utility . IFilter filter , RestoreResults result )
2013-08-23 22:18:13 +02:00
{
// Create a temporary table FILES by selecting the files from fileset that matches a specific operation id
// Delete all entries from the temp table that are excluded by the filter(s)
2018-03-12 14:07:11 +01:00
using ( new Logging . Timer ( LOGTAG , "PrepareRestoreFileList" , "PrepareRestoreFileList" ))
2013-08-23 22:18:13 +02:00
{
2018-03-12 14:07:11 +01:00
var c = database . PrepareRestoreFilelist ( options . Time , options . Version , filter );
2013-08-23 22:18:13 +02:00
result . OperationProgressUpdater . UpdatefileCount ( c . Item1 , c . Item2 , true );
}
2013-05-13 22:32:05 +02:00
2018-03-12 14:07:11 +01:00
using ( new Logging . Timer ( LOGTAG , "SetTargetPaths" , "SetTargetPaths" ))
2016-09-15 11:39:27 +02:00
if (! string . IsNullOrEmpty ( options . Restorepath ))
{
// Find the largest common prefix
2017-01-06 23:01:54 +01:00
var largest_prefix = options . DontCompressRestorePaths ? "" : database . GetLargestPrefix ();
2018-10-27 12:17:07 +02:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "MappingRestorePath" , "Mapping restore path prefix to \"{0}\" to \"{1}\"" , largest_prefix , Util . AppendDirSeparator ( options . Restorepath ));
2013-05-29 22:15:13 +02:00
2016-09-15 11:39:27 +02:00
// Set the target paths, special care with C:\ and /
2018-10-27 12:17:07 +02:00
database . SetTargetPaths ( largest_prefix , Util . AppendDirSeparator ( options . Restorepath ));
2016-09-15 11:39:27 +02:00
}
else
{
database . SetTargetPaths ( "" , "" );
}
2013-03-26 19:27:26 +01:00
// Create a temporary table BLOCKS that lists all blocks that needs to be recovered
2018-03-12 14:07:11 +01:00
using ( new Logging . Timer ( LOGTAG , "FindMissingBlocks" , "FindMissingBlocks" ))
database . FindMissingBlocks ( options . SkipMetadata );
2016-02-27 22:06:58 +01:00
// Create temporary tables and triggers that automatically track progress
2018-03-12 14:07:11 +01:00
using ( new Logging . Timer ( LOGTAG , "CreateProgressTracker" , "CreateProgressTracker" ))
2016-02-27 22:06:58 +01:00
database . CreateProgressTracker ( false );
2013-03-26 19:27:26 +01:00
}
2013-05-25 16:40:15 +02:00
private static void CreateDirectoryStructure ( LocalRestoreDatabase database , Options options , RestoreResults result )
2016-09-15 11:39:27 +02:00
{
// This part is not protected by try/catch as we need the target folder to exist
if (! string . IsNullOrEmpty ( options . Restorepath ))
2018-11-02 21:34:07 +01:00
if (! SystemIO . IO_OS . DirectoryExists ( options . Restorepath ))
2013-05-29 22:15:13 +02:00
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "CreateFolder" , "Creating folder: {0}" , options . Restorepath );
2013-05-29 22:15:13 +02:00
2016-09-15 11:39:27 +02:00
if ( options . Dryrun )
2018-03-12 14:07:11 +01:00
Logging . Log . WriteDryrunMessage ( LOGTAG , "WouldCreateFolder" , "Would create folder: {0}" , options . Restorepath );
2016-09-15 11:39:27 +02:00
else
2018-11-02 21:34:07 +01:00
SystemIO . IO_OS . DirectoryCreate ( options . Restorepath );
2013-05-29 22:15:13 +02:00
}
2013-05-21 21:15:03 +02:00
2013-03-26 19:27:26 +01:00
foreach ( var folder in database . GetTargetFolders ())
{
try
{
2014-05-15 12:47:16 +02:00
if ( result . TaskControlRendevouz () == TaskControlState . Stop )
return ;
2018-11-02 21:34:07 +01:00
if (! SystemIO . IO_OS . DirectoryExists ( folder ))
2013-05-25 16:40:15 +02:00
{
2018-12-12 12:14:11 -02:00
result . RestoredFolders ++;
2016-09-15 11:39:27 +02:00
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "CreateFolder" , "Creating folder: {0}" , folder );
2013-05-29 22:15:13 +02:00
2016-09-15 11:39:27 +02:00
if ( options . Dryrun )
2018-03-12 14:07:11 +01:00
Logging . Log . WriteDryrunMessage ( LOGTAG , "WouldCreateFolder" , "Would create folder: {0}" , folder );
2016-09-15 11:39:27 +02:00
else
2018-11-02 21:34:07 +01:00
SystemIO . IO_OS . DirectoryCreate ( folder );
2013-05-25 16:40:15 +02:00
}
2013-03-26 19:27:26 +01:00
}
catch ( Exception ex )
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "FolderCreateFailed" , ex , "Failed to create folder: \"{0}\", message: {1}" , folder , ex . Message );
2018-06-12 09:31:39 +02:00
if ( options . UnittestMode )
throw ;
2013-03-26 19:27:26 +01:00
}
}
}
2013-06-01 14:22:10 +02:00
private static void ScanForExistingTargetBlocks ( LocalRestoreDatabase database , byte [] blockbuffer , System . Security . Cryptography . HashAlgorithm blockhasher , System . Security . Cryptography . HashAlgorithm filehasher , Options options , RestoreResults result )
2013-03-26 19:27:26 +01:00
{
// Scan existing files for existing BLOCKS
2013-08-22 20:52:54 +02:00
using ( var blockmarker = database . CreateBlockMarker ())
2013-03-26 19:27:26 +01:00
{
2013-09-07 21:35:23 +02:00
var updateCount = 0L ;
2013-08-22 20:52:54 +02:00
foreach ( var restorelist in database . GetExistingFilesWithBlocks ())
2013-03-26 19:27:26 +01:00
{
2013-06-01 14:22:10 +02:00
var rename = ! options . Overwrite ;
2013-03-26 19:27:26 +01:00
var targetpath = restorelist . TargetPath ;
2013-04-27 10:20:15 +02:00
var targetfileid = restorelist . TargetFileID ;
2013-06-01 14:22:10 +02:00
var targetfilehash = restorelist . TargetHash ;
2016-03-05 02:00:28 +01:00
var targetfilelength = restorelist . Length ;
2018-11-02 21:34:07 +01:00
if ( SystemIO . IO_OS . FileExists ( targetpath ))
2013-03-26 19:27:26 +01:00
{
try
{
2014-05-15 12:47:16 +02:00
if ( result . TaskControlRendevouz () == TaskControlState . Stop )
return ;
2013-06-01 14:22:10 +02:00
2018-11-02 21:34:07 +01:00
var currentfilelength = SystemIO . IO_OS . FileLength ( targetpath );
2016-03-12 13:47:03 +01:00
var wasTruncated = false ;
// Adjust file length in overwrite mode if necessary (smaller is ok, will be extended during restore)
// We do it before scanning for blocks. This allows full verification on files that only needs to
// be truncated (i.e. forthwritten log files).
if (! rename && currentfilelength > targetfilelength )
{
2018-11-02 21:34:07 +01:00
var currentAttr = SystemIO . IO_OS . GetFileAttributes ( targetpath );
2016-03-12 13:47:03 +01:00
if (( currentAttr & System . IO . FileAttributes . ReadOnly ) != 0 ) // clear readonly attribute
{
2018-03-12 14:07:11 +01:00
if ( options . Dryrun )
2018-06-20 09:20:45 +02:00
Logging . Log . WriteDryrunMessage ( LOGTAG , "WouldResetReadOnlyAttribute" , "Would reset read-only attribute on file: {0}" , targetpath );
2018-11-02 21:34:07 +01:00
else SystemIO . IO_OS . SetFileAttributes ( targetpath , currentAttr & ~ System . IO . FileAttributes . ReadOnly );
2016-03-12 13:47:03 +01:00
}
if ( options . Dryrun )
2018-03-12 14:07:11 +01:00
Logging . Log . WriteDryrunMessage ( LOGTAG , "WouldTruncateFile" , "Would truncate file '{0}' to length of {1:N0} bytes" , targetpath , targetfilelength );
2016-03-12 13:47:03 +01:00
else
{
2018-11-02 21:34:07 +01:00
using ( var file = SystemIO . IO_OS . FileOpenWrite ( targetpath ))
2016-03-12 13:47:03 +01:00
file . SetLength ( targetfilelength );
currentfilelength = targetfilelength ;
}
wasTruncated = true ;
}
2014-11-05 21:43:08 +01:00
2016-03-05 02:00:28 +01:00
// If file size does not match and we have to rename on conflict,
2016-03-12 13:47:03 +01:00
// the whole scan can be skipped here because all blocks have to be restored anyway.
2016-03-05 02:00:28 +01:00
// For the other cases, we will check block and and file hashes and look for blocks
// to be restored and files that can already be verified.
if (! rename || currentfilelength == targetfilelength )
{
2016-03-12 13:47:03 +01:00
// a file hash for verification will only be necessary if the file has exactly
// the wanted size so we have a chance to already mark the file as data-verified.
bool calcFileHash = ( currentfilelength == targetfilelength );
2016-03-05 02:00:28 +01:00
if ( calcFileHash ) filehasher . Initialize ();
2018-11-02 21:34:07 +01:00
using ( var file = SystemIO . IO_OS . FileOpenRead ( targetpath ))
2016-03-05 02:00:28 +01:00
using ( var block = new Blockprocessor ( file , blockbuffer ))
foreach ( var targetblock in restorelist . Blocks )
2013-03-26 19:27:26 +01:00
{
2016-03-05 02:00:28 +01:00
var size = block . Readblock ();
if ( size <= 0 )
break ;
//TODO: Handle Metadata
bool blockhashmatch = false ;
if ( size == targetblock . Size )
2013-03-26 19:27:26 +01:00
{
2016-03-05 02:00:28 +01:00
// Parallelize file hash calculation on rename. Running read-only on same array should not cause conflicts or races.
// Actually, in future always calculate the file hash and mark the file data as already verified.
System . Threading . Tasks . Task calcFileHashTask = null ;
if ( calcFileHash )
calcFileHashTask = System . Threading . Tasks . Task . Run (
() => filehasher . TransformBlock ( blockbuffer , 0 , size , blockbuffer , 0 ));
var key = Convert . ToBase64String ( blockhasher . ComputeHash ( blockbuffer , 0 , size ));
if ( calcFileHashTask != null ) calcFileHashTask . Wait (); // wait because blockbuffer will be overwritten.
if ( key == targetblock . Hash )
{
blockmarker . SetBlockRestored ( targetfileid , targetblock . Index , key , size , false );
blockhashmatch = true ;
}
}
if ( calcFileHash && ! blockhashmatch ) // will not be necessary anymore
2013-03-26 19:27:26 +01:00
{
2016-03-05 02:00:28 +01:00
filehasher . TransformFinalBlock ( blockbuffer , 0 , 0 ); // So a new initialize will not throw
calcFileHash = false ;
if ( rename ) // file does not match. So break.
break ;
2013-03-26 19:27:26 +01:00
}
}
2016-03-05 02:00:28 +01:00
bool fullfilehashmatch = false ;
if ( calcFileHash ) // now check if files are identical
{
filehasher . TransformFinalBlock ( blockbuffer , 0 , 0 );
var filekey = Convert . ToBase64String ( filehasher . Hash );
fullfilehashmatch = ( filekey == targetfilehash );
2013-03-26 19:27:26 +01:00
}
2016-03-05 02:00:28 +01:00
2016-03-12 13:47:03 +01:00
if (! rename && ! fullfilehashmatch && ! wasTruncated ) // Reset read-only attribute (if set) to overwrite
2016-03-05 02:00:28 +01:00
{
2018-11-02 21:34:07 +01:00
var currentAttr = SystemIO . IO_OS . GetFileAttributes ( targetpath );
2016-03-05 02:00:28 +01:00
if (( currentAttr & System . IO . FileAttributes . ReadOnly ) != 0 )
{
2018-03-12 14:07:11 +01:00
if ( options . Dryrun )
Logging . Log . WriteDryrunMessage ( LOGTAG , "WouldResetReadOnlyAttribyte" , "Would reset read-only attribute on file: {0}" , targetpath );
2018-11-02 21:34:07 +01:00
else SystemIO . IO_OS . SetFileAttributes ( targetpath , currentAttr & ~ System . IO . FileAttributes . ReadOnly );
2016-03-05 02:00:28 +01:00
}
}
if ( fullfilehashmatch )
2013-06-01 14:22:10 +02:00
{
2016-02-27 22:06:58 +01:00
//TODO: Check metadata to trigger rename? If metadata changed, it will still be restored for the file in-place.
2016-03-05 02:00:28 +01:00
blockmarker . SetFileDataVerified ( targetfileid );
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "TargetExistsInCorrectVersion" , "Target file exists{1} and is correct version: {0}" , targetpath , wasTruncated ? " (but was truncated)" : "" );
2013-06-01 14:22:10 +02:00
rename = false ;
}
2016-03-05 02:00:28 +01:00
else if ( rename )
2013-08-22 20:52:54 +02:00
{
// The new file will have none of the correct blocks,
// even if the scanned file had some
blockmarker . SetAllBlocksMissing ( targetfileid );
}
2013-06-01 14:22:10 +02:00
}
2013-08-23 22:18:13 +02:00
2016-02-27 22:06:58 +01:00
if ((++ updateCount ) % 20 == 0 )
2014-05-15 12:47:16 +02:00
{
2013-09-07 21:35:23 +02:00
blockmarker . UpdateProcessed ( result . OperationProgressUpdater );
2014-05-15 12:47:16 +02:00
if ( result . TaskControlRendevouz () == TaskControlState . Stop )
return ;
}
2013-03-26 19:27:26 +01:00
}
catch ( Exception ex )
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "TargetFileReadError" , ex , "Failed to read target file: \"{0}\", message: {1}" , targetpath , ex . Message );
2014-05-15 12:47:16 +02:00
if ( ex is System . Threading . ThreadAbortException )
throw ;
2018-06-12 09:31:39 +02:00
if ( options . UnittestMode )
throw ;
2013-06-01 14:22:10 +02:00
}
}
else
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "MissingTargetFile" , "Target file does not exist: {0}" , targetpath );
2013-06-01 14:22:10 +02:00
rename = false ;
2013-03-26 19:27:26 +01:00
}
2013-06-01 14:22:10 +02:00
if ( rename )
{
//Select a new filename
2018-11-02 21:34:07 +01:00
var ext = SystemIO . IO_OS . PathGetExtension ( targetpath ) ?? "" ;
2017-11-26 10:53:14 -08:00
if (! string . IsNullOrEmpty ( ext ) && ! ext . StartsWith ( "." , StringComparison . Ordinal ))
2013-06-01 14:22:10 +02:00
ext = "." + ext ;
// First we try with a simple date append, assuming that there are not many conflicts there
2018-11-02 21:34:07 +01:00
var newname = SystemIO . IO_OS . PathChangeExtension ( targetpath , null ) + "." + database . RestoreTime . ToLocalTime (). ToString ( "yyyy-MM-dd" , System . Globalization . CultureInfo . InvariantCulture );
2013-08-22 20:52:54 +02:00
var tr = newname + ext ;
var c = 0 ;
2018-11-02 21:34:07 +01:00
while ( SystemIO . IO_OS . FileExists ( tr ) && c < 1000 )
2013-08-22 20:52:54 +02:00
{
try
{
// If we have a file with the correct name,
// it is most likely the file we want
filehasher . Initialize ();
string key ;
2018-11-02 21:34:07 +01:00
using ( var file = SystemIO . IO_OS . FileOpenRead ( tr ))
2013-08-22 20:52:54 +02:00
key = Convert . ToBase64String ( filehasher . ComputeHash ( file ));
if ( key == targetfilehash )
{
2016-02-27 22:06:58 +01:00
//TODO: Also needs metadata check to make correct decision.
// We stick to the policy to restore metadata in place, if data ok. So, metadata block may be restored.
blockmarker . SetAllBlocksRestored ( targetfileid , false );
2016-03-05 02:00:28 +01:00
blockmarker . SetFileDataVerified ( targetfileid );
2013-08-22 20:52:54 +02:00
break ;
}
}
catch ( Exception ex )
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "FailedToReadRestoreTarget" , ex , "Failed to read candidate restore target {0}" , tr );
2018-06-12 09:31:39 +02:00
if ( options . UnittestMode )
throw ;
2013-08-22 20:52:54 +02:00
}
tr = newname + " (" + ( c ++). ToString () + ")" + ext ;
2013-06-01 14:22:10 +02:00
}
2013-08-22 20:52:54 +02:00
newname = tr ;
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "TargetFileRetargeted" , "Target file exists and will be restored to: {0}" , newname );
2013-06-01 14:22:10 +02:00
database . UpdateTargetPath ( targetfileid , newname );
}
2013-03-26 19:27:26 +01:00
}
2013-09-07 21:35:23 +02:00
blockmarker . UpdateProcessed ( result . OperationProgressUpdater );
2018-03-12 14:07:11 +01:00
blockmarker . Commit ();
2013-03-26 19:27:26 +01:00
}
}
}
}