diff --git a/src/wallet/bdb.h b/src/wallet/bdb.h --- a/src/wallet/bdb.h +++ b/src/wallet/bdb.h @@ -82,7 +82,7 @@ GetWalletEnv(const fs::path &wallet_path, std::string &database_filename); /** Check format of database file */ -bool IsBerkeleyBtree(const fs::path &path); +bool IsBDBFile(const fs::path &path); class BerkeleyBatch; diff --git a/src/wallet/bdb.cpp b/src/wallet/bdb.cpp --- a/src/wallet/bdb.cpp +++ b/src/wallet/bdb.cpp @@ -893,7 +893,7 @@ fs::path env_directory; std::string data_filename; SplitWalletPath(path, env_directory, data_filename); - return IsBerkeleyBtree(env_directory / data_filename); + return IsBDBFile(env_directory / data_filename); } std::unique_ptr @@ -925,3 +925,36 @@ status = DatabaseStatus::SUCCESS; return db; } + +bool IsBDBFile(const fs::path &path) { + if (!fs::exists(path)) { + return false; + } + + // A Berkeley DB Btree file has at least 4K. + // This check also prevents opening lock files. + boost::system::error_code ec; + auto size = fs::file_size(path, ec); + if (ec) { + LogPrintf("%s: %s %s\n", __func__, ec.message(), path.string()); + } + if (size < 4096) { + return false; + } + + fsbridge::ifstream file(path, std::ios::binary); + if (!file.is_open()) { + return false; + } + + file.seekg(12, std::ios::beg); // Magic bytes start at offset 12 + uint32_t data = 0; + file.read((char *)&data, + sizeof(data)); // Read 4 bytes of file to compare against magic + + // Berkeley DB Btree magic bytes, from: + // https://github.com/file/file/blob/5824af38469ec1ca9ac3ffd251e7afe9dc11e227/magic/Magdir/database#L74-L75 + // - big endian systems - 00 05 31 62 + // - little endian systems - 62 31 05 00 + return data == 0x00053162 || data == 0x62310500; +} diff --git a/src/wallet/db.h b/src/wallet/db.h --- a/src/wallet/db.h +++ b/src/wallet/db.h @@ -13,6 +13,7 @@ #include #include +#include #include struct bilingual_str; @@ -216,11 +217,13 @@ enum class DatabaseFormat { BERKELEY, + SQLITE, }; struct DatabaseOptions { bool require_existing = false; bool require_create = false; + std::optional require_format; uint64_t create_flags = 0; SecureString create_passphrase; bool verify = true; diff --git a/src/wallet/sqlite.h b/src/wallet/sqlite.h --- a/src/wallet/sqlite.h +++ b/src/wallet/sqlite.h @@ -120,5 +120,6 @@ DatabaseStatus &status, bilingual_str &error); std::string SQLiteDatabaseVersion(); +bool IsSQLiteFile(const fs::path &path); #endif // BITCOIN_WALLET_SQLITE_H diff --git a/src/wallet/sqlite.cpp b/src/wallet/sqlite.cpp --- a/src/wallet/sqlite.cpp +++ b/src/wallet/sqlite.cpp @@ -603,7 +603,9 @@ } bool ExistsSQLiteDatabase(const fs::path &path) { - return false; + const fs::path file = path / DATABASE_FILENAME; + return fs::symlink_status(file).type() == fs::regular_file && + IsSQLiteFile(file); } std::unique_ptr @@ -627,3 +629,33 @@ std::string SQLiteDatabaseVersion() { return std::string(sqlite3_libversion()); } + +bool IsSQLiteFile(const fs::path &path) { + if (!fs::exists(path)) { + return false; + } + + // A SQLite Database file is at least 512 bytes. + boost::system::error_code ec; + auto size = fs::file_size(path, ec); + if (ec) { + LogPrintf("%s: %s %s\n", __func__, ec.message(), path.string()); + } + if (size < 512) { + return false; + } + + fsbridge::ifstream file(path, std::ios::binary); + if (!file.is_open()) { + return false; + } + + // Magic is at beginning and is 16 bytes long + char magic[16]; + file.read(magic, 16); + file.close(); + + // Check the magic, see https://sqlite.org/fileformat2.html + std::string magic_str(magic, 16); + return magic_str == std::string("SQLite format 3", 16); +} diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -1131,6 +1132,17 @@ if (ExistsBerkeleyDatabase(path)) { format = DatabaseFormat::BERKELEY; } + if (ExistsSQLiteDatabase(path)) { + if (format) { + error = + Untranslated(strprintf("Failed to load database path '%s'. " + "Data is in ambiguous format.", + path.string())); + status = DatabaseStatus::FAILED_BAD_FORMAT; + return nullptr; + } + format = DatabaseFormat::SQLITE; + } } else if (options.require_existing) { error = Untranslated( strprintf("Failed to load database path '%s'. Path does not exist.", @@ -1155,6 +1167,26 @@ return nullptr; } + // A db already exists so format is set, but options also specifies the + // format, so make sure they agree + if (format && options.require_format && format != options.require_format) { + error = Untranslated(strprintf("Failed to load database path '%s'. " + "Data is not in required format.", + path.string())); + status = DatabaseStatus::FAILED_BAD_FORMAT; + return nullptr; + } + + // Format is not set when a db doesn't already exist, so use the format + // specified by the options if it is set. + if (!format && options.require_format) { + format = options.require_format; + } + + if (format && format == DatabaseFormat::SQLITE) { + return MakeSQLiteDatabase(path, options, status, error); + } + return MakeBerkeleyDatabase(path, options, status, error); } diff --git a/src/wallet/walletutil.cpp b/src/wallet/walletutil.cpp --- a/src/wallet/walletutil.cpp +++ b/src/wallet/walletutil.cpp @@ -7,6 +7,8 @@ #include #include +bool ExistsBerkeleyDatabase(const fs::path &path); + fs::path GetWalletDir() { fs::path path; @@ -29,40 +31,6 @@ return path; } -bool IsBerkeleyBtree(const fs::path &path) { - if (!fs::exists(path)) { - return false; - } - - // A Berkeley DB Btree file has at least 4K. - // This check also prevents opening lock files. - boost::system::error_code ec; - auto size = fs::file_size(path, ec); - if (ec) { - LogPrintf("%s: %s %s\n", __func__, ec.message(), path.string()); - } - if (size < 4096) { - return false; - } - - fsbridge::ifstream file(path, std::ios::binary); - if (!file.is_open()) { - return false; - } - - // Magic bytes start at offset 12 - file.seekg(12, std::ios::beg); - uint32_t data = 0; - // Read 4 bytes of file to compare against magic - file.read((char *)&data, sizeof(data)); - - // Berkeley DB Btree magic bytes, from: - // https://github.com/file/file/blob/5824af38469ec1ca9ac3ffd251e7afe9dc11e227/magic/Magdir/database#L74-L75 - // - big endian systems - 00 05 31 62 - // - little endian systems - 62 31 05 00 - return data == 0x00053162 || data == 0x62310500; -} - std::vector ListWalletDir() { const fs::path wallet_dir = GetWalletDir(); const size_t offset = wallet_dir.string().size() + 1; @@ -83,13 +51,13 @@ const fs::path path = it->path().string().substr(offset); if (it->status().type() == fs::directory_file && - IsBerkeleyBtree(it->path() / "wallet.dat")) { + ExistsBerkeleyDatabase(it->path())) { // Found a directory which contains wallet.dat btree file, add it as // a wallet. paths.emplace_back(path); } else if (it.level() == 0 && it->symlink_status().type() == fs::regular_file && - IsBerkeleyBtree(it->path())) { + ExistsBerkeleyDatabase(it->path())) { if (it->path().filename() == "wallet.dat") { // Found top-level wallet.dat btree file, add top level // directory "" as a wallet.