diff --git a/src/wallet/bdb.h b/src/wallet/bdb.h --- a/src/wallet/bdb.h +++ b/src/wallet/bdb.h @@ -87,6 +87,9 @@ /** Return whether a BDB wallet database is currently loaded. */ bool IsBDBWalletLoaded(const fs::path &wallet_path); +/** Check format of database file */ +bool IsBerkeleyBtree(const fs::path &path); + class BerkeleyBatch; /** @@ -245,4 +248,12 @@ std::string BerkeleyDatabaseVersion(); +//! Check if Berkeley database exists at specified path. +bool ExistsBerkeleyDatabase(const fs::path &path); + +//! Return object giving access to Berkeley database at specified path. +std::unique_ptr +MakeBerkeleyDatabase(const fs::path &path, const DatabaseOptions &options, + DatabaseStatus &status, bilingual_str &error); + #endif // BITCOIN_WALLET_BDB_H diff --git a/src/wallet/bdb.cpp b/src/wallet/bdb.cpp --- a/src/wallet/bdb.cpp +++ b/src/wallet/bdb.cpp @@ -909,3 +909,40 @@ BerkeleyDatabase::MakeBatch(const char *mode, bool flush_on_close) { return std::make_unique(*this, mode, flush_on_close); } + +bool ExistsBerkeleyDatabase(const fs::path &path) { + fs::path env_directory; + std::string data_filename; + SplitWalletPath(path, env_directory, data_filename); + return IsBerkeleyBtree(env_directory / data_filename); +} + +std::unique_ptr +MakeBerkeleyDatabase(const fs::path &path, const DatabaseOptions &options, + DatabaseStatus &status, bilingual_str &error) { + std::unique_ptr db; + { + // Lock env.m_databases until insert in BerkeleyDatabase constructor + LOCK(cs_db); + std::string data_filename; + std::shared_ptr env = + GetWalletEnv(path, data_filename); + if (env->m_databases.count(data_filename)) { + error = Untranslated(strprintf( + "Refusing to load database. Data file '%s' is already loaded.", + (env->Directory() / data_filename).string())); + status = DatabaseStatus::FAILED_ALREADY_LOADED; + return nullptr; + } + db = std::make_unique(std::move(env), + std::move(data_filename)); + } + + if (options.verify && !db->Verify(error)) { + status = DatabaseStatus::FAILED_VERIFY; + return nullptr; + } + + status = DatabaseStatus::SUCCESS; + return db; +} diff --git a/src/wallet/db.h b/src/wallet/db.h --- a/src/wallet/db.h +++ b/src/wallet/db.h @@ -220,4 +220,29 @@ } }; +enum class DatabaseFormat { + BERKELEY, +}; + +struct DatabaseOptions { + bool require_existing = false; + bool require_create = false; + bool verify = true; +}; + +enum class DatabaseStatus { + SUCCESS, + FAILED_BAD_PATH, + FAILED_BAD_FORMAT, + FAILED_ALREADY_LOADED, + FAILED_ALREADY_EXISTS, + FAILED_NOT_FOUND, + FAILED_VERIFY, +}; + +std::unique_ptr MakeDatabase(const fs::path &path, + const DatabaseOptions &options, + DatabaseStatus &status, + bilingual_str &error); + #endif // BITCOIN_WALLET_DB_H diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include #include @@ -1109,6 +1111,53 @@ return m_batch->TxnAbort(); } +std::unique_ptr MakeDatabase(const fs::path &path, + const DatabaseOptions &options, + DatabaseStatus &status, + bilingual_str &error) { + bool exists; + try { + exists = fs::symlink_status(path).type() != fs::file_not_found; + } catch (const fs::filesystem_error &e) { + error = Untranslated( + strprintf("Failed to access database path '%s': %s", path.string(), + fsbridge::get_filesystem_error_message(e))); + status = DatabaseStatus::FAILED_BAD_PATH; + return nullptr; + } + + std::optional format; + if (exists) { + if (ExistsBerkeleyDatabase(path)) { + format = DatabaseFormat::BERKELEY; + } + } else if (options.require_existing) { + error = Untranslated( + strprintf("Failed to load database path '%s'. Path does not exist.", + path.string())); + status = DatabaseStatus::FAILED_NOT_FOUND; + return nullptr; + } + + if (!format && options.require_existing) { + error = Untranslated(strprintf("Failed to load database path '%s'. " + "Data is not in recognized format.", + path.string())); + status = DatabaseStatus::FAILED_BAD_FORMAT; + return nullptr; + } + + if (format && options.require_create) { + error = Untranslated(strprintf( + "Failed to create database path '%s'. Database already exists.", + path.string())); + status = DatabaseStatus::FAILED_ALREADY_EXISTS; + return nullptr; + } + + return MakeBerkeleyDatabase(path, options, status, error); +} + bool IsWalletLoaded(const fs::path &wallet_path) { return IsBDBWalletLoaded(wallet_path); } diff --git a/src/wallet/walletutil.cpp b/src/wallet/walletutil.cpp --- a/src/wallet/walletutil.cpp +++ b/src/wallet/walletutil.cpp @@ -29,7 +29,7 @@ return path; } -static bool IsBerkeleyBtree(const fs::path &path) { +bool IsBerkeleyBtree(const fs::path &path) { if (!fs::exists(path)) { return false; }