2024-04-23 17:06:32 +02:00
// Copyright (C) 2024, The Duplicati Team
2024-03-04 12:21:53 +01:00
// https://duplicati.com, hello@duplicati.com
2014-04-07 11:59:33 +02:00
//
2024-03-04 12:21:53 +01:00
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
2014-04-07 11:59:33 +02:00
//
2024-03-04 12:21:53 +01:00
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
2014-04-07 11:59:33 +02:00
//
2024-03-04 12:21:53 +01:00
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
2024-02-28 15:45:30 +01:00
2014-04-07 11:59:33 +02:00
using System ;
2018-06-28 10:50:17 +02:00
using System.IO ;
2020-02-17 09:34:39 -08:00
using Duplicati.Library.Common ;
2024-03-01 14:30:28 +01:00
using Duplicati.Library.Common.IO ;
2024-04-23 17:06:32 +02:00
using Duplicati.Library.Interface ;
2020-02-17 09:34:39 -08:00
2014-04-07 11:59:33 +02:00
namespace Duplicati.Library.SQLiteHelper
{
public static class SQLiteLoader
2018-06-28 10:50:17 +02:00
{
2018-06-14 21:43:20 +02:00
/// <summary>
2018-05-15 11:29:08 +02:00
/// The tag used for logging
/// </summary>
2018-06-14 21:43:20 +02:00
private static readonly string LOGTAG = Logging . Log . LogTagFromType ( typeof ( SQLiteLoader ));
2018-05-15 11:29:08 +02:00
2018-06-21 18:08:52 +02:00
/// <summary>
/// Helper method with logic to handle opening a database in possibly encrypted format
/// </summary>
/// <param name="con">The SQLite connection object</param>
2018-06-28 10:50:17 +02:00
/// <param name="databasePath">The location of Duplicati's database.</param>
2024-04-23 17:06:32 +02:00
/// <param name="decryptionPassword">The password to use for decryption.</param>
public static void OpenDatabase ( System . Data . IDbConnection con , string databasePath , string decryptionPassword )
2018-06-21 18:08:52 +02:00
{
2024-04-23 17:06:32 +02:00
if ( SQLiteRC4Decrypter . IsDatabaseEncrypted ( databasePath ))
{
Logging . Log . WriteWarningMessage ( LOGTAG , "SQLiteRC4Decrypter" , null , "Database is encrypted, attempting to decrypt..." );
try
{
SQLiteRC4Decrypter . DecryptSQLiteFile ( databasePath , decryptionPassword );
Logging . Log . WriteInformationMessage ( LOGTAG , "SQLiteRC4Decrypter" , "Database decrypted successfully." );
}
catch ( Exception ex )
{
Logging . Log . WriteErrorMessage ( LOGTAG , "SQLiteRC4Decrypter" , ex , "Failed to decrypt database" );
throw new UserInformationException ( "The database appears to be encrypted, but the decrypting failed. Please check the password." , "RC4DecryptionFailed" , ex );
}
}
2018-06-21 18:08:52 +02:00
try
{
//Attempt to open in preferred state
2018-06-28 10:50:17 +02:00
OpenSQLiteFile ( con , databasePath );
2018-06-21 18:58:55 +02:00
TestSQLiteFile ( con );
2018-06-21 18:08:52 +02:00
}
catch
{
2024-04-23 17:06:32 +02:00
try { con . Dispose (); }
catch { }
2018-06-21 18:08:52 +02:00
2024-04-23 17:06:32 +02:00
throw ;
2018-06-21 18:08:52 +02:00
}
2024-04-23 17:06:32 +02:00
if ( con . State != System . Data . ConnectionState . Open )
throw new UserInformationException ( "Failed to open database for unknown reason, check the logs to see error messages" , "DatabaseOpenFailed" );
2018-06-21 18:08:52 +02:00
}
2017-01-15 23:09:47 +01:00
/// <summary>
2018-06-11 18:50:11 +02:00
/// Loads an SQLite connection instance and opening the database
2017-01-15 23:09:47 +01:00
/// </summary>
/// <returns>The SQLite connection instance.</returns>
2018-06-11 18:50:11 +02:00
public static System . Data . IDbConnection LoadConnection ()
2017-01-15 23:09:47 +01:00
{
System . Data . IDbConnection con = null ;
2018-06-19 08:41:07 +02:00
SetEnvironmentVariablesForSQLiteTempDir ();
2018-06-14 22:00:59 +02:00
2017-01-15 23:09:47 +01:00
try
{
con = ( System . Data . IDbConnection ) Activator . CreateInstance ( Duplicati . Library . SQLiteHelper . SQLiteLoader . SQLiteConnectionType );
2018-06-11 18:50:11 +02:00
}
2018-06-11 19:10:14 +02:00
catch ( Exception ex )
2018-06-11 18:50:11 +02:00
{
2018-06-11 19:10:14 +02:00
Logging . Log . WriteErrorMessage ( LOGTAG , "FailedToLoadConnectionSQLite" , ex , "Failed to load connection." );
2018-06-11 22:06:06 +02:00
DisposeConnection ( con );
2018-06-11 18:50:11 +02:00
throw ;
2018-06-28 10:50:17 +02:00
}
2018-06-11 18:50:11 +02:00
return con ;
}
/// <summary>
/// Loads an SQLite connection instance and opening the database
/// </summary>
/// <returns>The SQLite connection instance.</returns>
/// <param name="targetpath">The optional path to the database.</param>
public static System . Data . IDbConnection LoadConnection ( string targetpath )
{
2018-06-28 10:50:17 +02:00
if ( string . IsNullOrWhiteSpace ( targetpath ))
throw new ArgumentNullException ( nameof ( targetpath ));
2018-06-11 18:50:11 +02:00
System . Data . IDbConnection con = LoadConnection ();
try
{
2018-06-28 10:50:17 +02:00
OpenSQLiteFile ( con , targetpath );
2017-01-15 23:09:47 +01:00
}
2018-06-11 19:10:14 +02:00
catch ( Exception ex )
2017-01-15 23:09:47 +01:00
{
2018-06-11 19:10:14 +02:00
Logging . Log . WriteErrorMessage ( LOGTAG , "FailedToLoadConnectionSQLite" , ex , @"Failed to load connection with path '{0}'." , targetpath );
2018-06-11 22:06:06 +02:00
DisposeConnection ( con );
2017-01-15 23:09:47 +01:00
throw ;
}
2023-03-19 00:48:10 +01:00
// set custom Sqlite options
var opts = Environment . GetEnvironmentVariable ( "CUSTOMSQLITEOPTIONS_DUPLICATI" );
if ( opts != null ) {
var topts = opts . Split ( new char []{ ';' }, StringSplitOptions . RemoveEmptyEntries );
if ( topts . Length > 0 ) {
using ( var cmd = con . CreateCommand ()) {
foreach ( var opt in topts ) {
Logging . Log . WriteVerboseMessage ( LOGTAG , "CustomSQLiteOption" , @"Setting custom SQLite option '{0}'." , opt );
try
{
cmd . CommandText = string . Format ( "pragma {0}" , opt );
cmd . ExecuteNonQuery ();
}
catch ( Exception ex )
{
Logging . Log . WriteErrorMessage ( LOGTAG , "CustomSQLiteOption" , ex , @"Error setting custom SQLite option '{0}'." , opt );
}
}
}
}
}
2017-01-15 23:09:47 +01:00
return con ;
}
2014-04-07 11:59:33 +02:00
/// <summary>
/// Returns the SQLiteCommand type for the current architecture
/// </summary>
public static Type SQLiteConnectionType
{
get
{
2024-03-05 08:55:13 +01:00
return typeof ( System . Data . SQLite . SQLiteConnection );
}
}
/// <summary>
/// Returns the version string from the SQLite type
/// </summary>
public static string SQLiteVersion
{
get
{
var versionString = SQLiteConnectionType . GetProperty ( "SQLiteVersion" )?. GetValue ( null , null ) as string ;
if ( string . IsNullOrWhiteSpace ( versionString ))
{
// Support for Microsoft.Data.SQLite
// NOTE: Has an issue with ? as position parameters
var inst = Activator . CreateInstance ( SQLiteConnectionType );
versionString = SQLiteConnectionType . GetProperty ( "ServerVersion" )?. GetValue ( inst , null ) as string ;
}
return versionString ;
2014-04-07 11:59:33 +02:00
}
}
2018-06-11 22:06:06 +02:00
2018-06-19 08:41:07 +02:00
/// <summary>
/// Set environment variables to be used by SQLite to determine which folder to use for temporary files.
/// From SQLite's documentation, SQLITE_TMPDIR is used for unix-like systems.
/// For Windows, TMP and TEMP environment variables are used.
/// </summary>
private static void SetEnvironmentVariablesForSQLiteTempDir ()
{
System . Environment . SetEnvironmentVariable ( "SQLITE_TMPDIR" , Library . Utility . TempFolder . SystemTempPath );
System . Environment . SetEnvironmentVariable ( "TMP" , Library . Utility . TempFolder . SystemTempPath );
System . Environment . SetEnvironmentVariable ( "TEMP" , Library . Utility . TempFolder . SystemTempPath );
}
2018-06-11 22:06:06 +02:00
2018-06-28 10:50:17 +02:00
/// <summary>
/// Wrapper to dispose the SQLite connection
/// </summary>
/// <param name="con">The connection to close.</param>
2018-06-11 22:06:06 +02:00
private static void DisposeConnection ( System . Data . IDbConnection con )
{
if ( con != null )
try { con . Dispose (); }
2018-06-28 10:50:17 +02:00
catch ( Exception ex ) { Logging . Log . WriteExplicitMessage ( LOGTAG , "ConnectionDisposeError" , ex , "Failed to dispose connection" ); }
}
2018-06-27 13:36:08 +02:00
/// <summary>
/// Opens the SQLite file in the given connection, creating the file if required
/// </summary>
/// <param name="con">The connection to use.</param>
/// <param name="path">Path to the file to open, which may not exist.</param>
2018-06-21 18:58:55 +02:00
private static void OpenSQLiteFile ( System . Data . IDbConnection con , string path )
{
2018-06-22 20:01:23 +02:00
// Check if SQLite database exists before opening a connection to it.
// This information is used to 'fix' permissions on a newly created file.
2018-06-27 13:36:08 +02:00
var fileExists = false ;
2018-11-02 22:13:25 +01:00
if (! Platform . IsClientWindows )
2018-06-22 20:01:23 +02:00
fileExists = File . Exists ( path );
2018-06-21 18:58:55 +02:00
con . ConnectionString = "Data Source=" + path ;
2018-06-28 10:50:17 +02:00
con . Open ();
2018-06-27 13:36:08 +02:00
2018-06-28 10:50:17 +02:00
// If we are non-Windows, make the file only accessible by the current user
2018-11-02 22:13:25 +01:00
if (! Platform . IsClientWindows && ! fileExists )
2018-06-27 13:36:08 +02:00
SetUnixPermissionUserRWOnly ( path );
}
/// <summary>
/// Sets the unix permission user read-write Only.
/// </summary>
/// <param name="path">The file to set permissions on.</param>
/// <remarks> Make sure we do not inline this, as we might eventually load Mono.Posix, which is not present on Windows</remarks>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
private static void SetUnixPermissionUserRWOnly ( string path )
{
2024-03-01 14:30:28 +01:00
var fi = PosixFile . GetUserGroupAndPermissions ( path );
PosixFile . SetUserGroupAndPermissions (
2018-06-27 13:36:08 +02:00
path ,
2018-06-28 09:28:14 +02:00
fi . UID ,
fi . GID ,
2018-06-27 13:36:08 +02:00
0x180 /* FilePermissions.S_IRUSR | FilePermissions.S_IWUSR*/
);
2018-06-21 18:58:55 +02:00
}
2018-06-28 10:50:17 +02:00
/// <summary>
/// Tests the SQLite connection, throwing an exception if the connection does not work
/// </summary>
/// <param name="con">The connection to test.</param>
2018-06-21 18:58:55 +02:00
private static void TestSQLiteFile ( System . Data . IDbConnection con )
{
// Do a dummy query to make sure we have a working db
using ( var cmd = con . CreateCommand ())
{
cmd . CommandText = "SELECT COUNT(*) FROM SQLITE_MASTER" ;
cmd . ExecuteScalar ();
}
2018-06-11 22:06:06 +02:00
}
2014-04-07 11:59:33 +02:00
}
}