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 Duplicati.Library.Interface ;
2013-04-04 20:34:26 +02:00
using Newtonsoft.Json ;
2017-12-25 04:12:19 +07:00
using System ;
using System.Collections.Generic ;
2013-04-04 20:34:26 +02:00
using System.IO ;
2019-09-10 13:07:57 -04:00
using Duplicati.Library.Utility ;
2013-04-04 20:34:26 +02:00
2013-05-08 20:17:07 +02:00
namespace Duplicati.Library.Main.Volumes
2013-04-04 20:34:26 +02:00
{
public abstract class VolumeReaderBase : VolumeBase , IDisposable
{
2019-09-10 13:07:57 -04:00
public bool IsFullBackup { get ; set ; }
2019-10-19 10:56:21 -07:00
protected readonly bool m_disposeCompression = false ;
2013-04-04 20:34:26 +02:00
protected ICompression m_compression ;
2017-12-25 04:12:19 +07:00
protected Stream m_stream ;
2019-09-10 13:07:57 -04:00
2017-12-25 04:12:19 +07:00
private static ICompression LoadCompressor ( string compressor , Stream stream , Options options )
2013-04-04 20:34:26 +02:00
{
2017-12-25 04:12:19 +07:00
var tmp = DynamicLoader . CompressionLoader . GetModule ( compressor , stream , Interface . ArchiveMode . Read , options . RawOptions );
2013-04-04 20:34:26 +02:00
if ( tmp == null )
2017-12-25 04:12:19 +07:00
{
var name = "[stream]" ;
2019-09-29 20:16:28 -07:00
if ( stream is FileStream fileStream )
name = fileStream . Name ;
2017-12-25 04:12:19 +07:00
throw new Exception ( string . Format ( "Unable to create {0} decompressor on file {1}" , compressor , name ));
}
2013-04-04 20:34:26 +02:00
return tmp ;
}
2017-12-25 04:12:19 +07:00
private static ICompression LoadCompressor ( string compressor , string file , Options options , out Stream stream )
{
stream = new System . IO . FileStream ( file , FileMode . Open , FileAccess . Read , FileShare . Read );
return LoadCompressor ( compressor , stream , options );
}
2018-04-07 17:58:46 -07:00
protected VolumeReaderBase ( string compressor , string file , Options options )
2017-12-25 04:12:19 +07:00
: base ( options )
2013-04-04 20:34:26 +02:00
{
2017-12-25 04:12:19 +07:00
m_compression = LoadCompressor ( compressor , file , options , out m_stream );
2019-09-10 13:07:57 -04:00
ReadFileset ();
ReadManifests ( options );
2013-04-04 20:34:26 +02:00
m_disposeCompression = true ;
}
2018-04-07 17:58:46 -07:00
protected VolumeReaderBase ( ICompression compression , Options options )
2013-04-04 20:34:26 +02:00
: base ( options )
{
m_compression = compression ;
2017-12-25 04:12:19 +07:00
ReadManifests ( options );
}
2019-09-10 13:07:57 -04:00
private void ReadFileset ()
{
using ( var s = m_compression . OpenRead ( FILESET_FILENAME ))
{
if ( s == null )
{
IsFullBackup = new FilesetData (). IsFullBackup ; // use default value
}
else
{
using ( var fs = new StreamReader ( s , ENCODING ))
{
FilesetData fileset = JsonConvert . DeserializeObject < FilesetData >( fs . ReadToEnd ());
IsFullBackup = fileset . IsFullBackup ;
}
}
}
}
2017-12-25 04:12:19 +07:00
private void ReadManifests ( Options options )
{
2019-09-10 13:07:57 -04:00
if ( options . DontReadManifests ) return ;
using ( var s = m_compression . OpenRead ( MANIFEST_FILENAME ))
2013-04-04 20:34:26 +02:00
{
2019-09-10 13:07:57 -04:00
if ( s == null )
2013-05-08 19:57:13 +02:00
{
2019-09-10 13:07:57 -04:00
throw new InvalidManifestException ( "No manifest file found in volume" );
}
2017-12-25 04:12:19 +07:00
2019-09-10 13:07:57 -04:00
using ( var fs = new StreamReader ( s , ENCODING ))
{
ManifestData . VerifyManifest ( fs . ReadToEnd (), m_blocksize , options . BlockHashAlgorithm , options . FileHashAlgorithm );
}
}
}
public static FilesetData GetFilesetData ( string compressor , string file , Options options )
{
using ( var stream = new FileStream ( file , FileMode . Open , FileAccess . Read , FileShare . Read ))
using ( var c = LoadCompressor ( compressor , stream , options ))
using ( var s = c . OpenRead ( FILESET_FILENAME ))
{
if ( s == null )
{
return new FilesetData (); // return default
}
using ( var fs = new StreamReader ( s , ENCODING ))
{
return JsonConvert . DeserializeObject < FilesetData >( fs . ReadToEnd ());
2013-05-08 19:57:13 +02:00
}
2013-04-04 20:34:26 +02:00
}
}
2015-04-08 21:01:36 +02:00
public static void UpdateOptionsFromManifest ( string compressor , string file , Options options )
{
2017-12-25 04:12:19 +07:00
using ( var stream = new System . IO . FileStream ( file , FileMode . Open , FileAccess . Read , FileShare . Read ))
using ( var c = LoadCompressor ( compressor , stream , options ))
2015-04-08 21:01:36 +02:00
using ( var s = c . OpenRead ( MANIFEST_FILENAME ))
using ( var fs = new StreamReader ( s , ENCODING ))
{
var d = JsonConvert . DeserializeObject < ManifestData >( fs . ReadToEnd ());
if ( d . Version > ManifestData . VERSION )
throw new InvalidManifestException ( "Version" , d . Version . ToString (), ManifestData . VERSION . ToString ());
string n ;
if (! options . RawOptions . TryGetValue ( "blocksize" , out n ) || string . IsNullOrEmpty ( n ))
2019-09-10 13:07:57 -04:00
options . RawOptions [ "blocksize" ] = d . Blocksize + "b" ;
2015-04-08 21:01:36 +02:00
if (! options . RawOptions . TryGetValue ( "block-hash-algorithm" , out n ) || string . IsNullOrEmpty ( n ))
options . RawOptions [ "block-hash-algorithm" ] = d . BlockHash ;
if (! options . RawOptions . TryGetValue ( "file-hash-algorithm" , out n ) || string . IsNullOrEmpty ( n ))
options . RawOptions [ "file-hash-algorithm" ] = d . FileHash ;
}
}
2013-04-04 20:34:26 +02:00
public virtual void Dispose ()
{
if ( m_disposeCompression && m_compression != null )
{
m_compression . Dispose ();
m_compression = null ;
2017-12-25 04:12:19 +07:00
m_stream ?. Dispose ();
m_stream = null ;
2013-04-04 20:34:26 +02:00
}
}
2016-03-17 21:41:42 +01:00
public static IEnumerable < string > ReadBlocklist ( ICompression compression , string filename , long hashsize )
{
var buffer = new byte [ hashsize ];
2017-12-25 04:12:19 +07:00
using ( var fs = compression . OpenRead ( filename ))
2016-03-17 21:41:42 +01:00
{
int s ;
2018-05-09 17:37:54 +02:00
var read = 0L ;
2016-03-17 21:41:42 +01:00
while (( s = Library . Utility . Utility . ForceStreamRead ( fs , buffer , buffer . Length )) != 0 )
2017-12-25 04:12:19 +07:00
{
2016-03-17 21:41:42 +01:00
if ( s != buffer . Length )
2018-05-09 17:37:54 +02:00
throw new InvalidDataException ( $"Premature End-of-stream encountered while reading blocklist hashes for {filename}. Got {s} bytes of {buffer.Length} at offset {read * buffer.Length}" );
2016-03-17 21:41:42 +01:00
2018-05-09 17:37:54 +02:00
read ++;
2016-03-17 21:41:42 +01:00
yield return Convert . ToBase64String ( buffer );
}
}
}
2013-04-04 20:34:26 +02:00
protected static object SkipJsonToken ( JsonReader reader , JsonToken type )
{
if (! reader . Read () || reader . TokenType != type )
throw new InvalidDataException ( string . Format ( "Invalid JSON, expected \"{0}\", but got {1}, {2}" , type , reader . TokenType , reader . Value ));
return reader . Value ;
}
protected static object ReadJsonProperty ( JsonReader reader , string propertyname , JsonToken type )
{
var p = SkipJsonToken ( reader , JsonToken . PropertyName );
if ( p == null || p . ToString () != propertyname )
throw new InvalidDataException ( string . Format ( "Invalid JSON, expected property \"{0}\", but got {1}, {2}" , propertyname , reader . TokenType , reader . Value ));
return SkipJsonToken ( reader , type );
}
protected static string ReadJsonStringProperty ( JsonReader reader , string propertyname )
{
return ReadJsonProperty ( reader , propertyname , JsonToken . String ). ToString ();
}
protected static long ReadJsonInt64Property ( JsonReader reader , string propertyname )
{
return Convert . ToInt64 ( ReadJsonProperty ( reader , propertyname , JsonToken . Integer ));
}
protected static DateTime ReadJsonDateTimeProperty ( JsonReader reader , string propertyname )
{
2013-05-08 21:29:59 +02:00
return Library . Utility . Utility . DeserializeDateTime ( ReadJsonStringProperty ( reader , propertyname ));
2013-04-04 20:34:26 +02:00
}
}
}