Skip to content

Commit ffc4d04

Browse files
author
MarcoFalke
committed
Merge #20275: wallet: List all wallets in non-SQLite and non-BDB builds
f3d870f wallet: List all wallets in non-SQLite or non-BDB builds (Russell Yanofsky) d70dc89 refactor: Consolidate redundant wallet database path and exists functions (Russell Yanofsky) 6a7a636 refactor: Drop call to GetWalletEnv in wallet salvage code (Russell Yanofsky) 6ee9cbd refactor: Replace ListWalletDir() function with ListDatabases() (Russell Yanofsky) 5aaeb6c MOVEONLY: Move IsBDBFile, IsSQLiteFile, and ListWalletDir (Russell Yanofsky) Pull request description: This PR does not change behavior when bitcoin is built normally with both the SQLite and BDB libraries. It just makes non-SQLite and non-BDB builds more similar to the normal build. Specifically: - It makes wallet directory lists always include all wallets so wallets don't appear missing depending on the build. - It now triggers specific "Build does not support SQLite database format" and "Build does not support Berkeley DB database format" errors if a wallet can't be loaded instead of the more ambiguous and scary "Data is not in recognized format" error. Both changes are implemented in the last commit. The previous commits are just refactoring cleanups that make the last commit possible and consolidate and reduce code. ACKs for top commit: achow101: ACK f3d870f promag: Tested ACK f3d870f. Tested a --without-sqlite build with sqlite wallets. Tree-SHA512: 029ad21559dbc338b5f351d05113c51bc25bce830f4f4e18bcd82287bc528275347a60249da65b91d252632aeb70b25d057bd59c704bfcaafb9f790bc5b59762
2 parents 6a48063 + f3d870f commit ffc4d04

File tree

13 files changed

+143
-166
lines changed

13 files changed

+143
-166
lines changed

src/wallet/bdb.cpp

Lines changed: 5 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,13 @@ bool WalletDatabaseFileId::operator==(const WalletDatabaseFileId& rhs) const
5353
}
5454

5555
/**
56-
* @param[in] wallet_path Path to wallet directory. Or (for backwards compatibility only) a path to a berkeley btree data file inside a wallet directory.
57-
* @param[out] database_filename Filename of berkeley btree data file inside the wallet directory.
56+
* @param[in] env_directory Path to environment directory
5857
* @return A shared pointer to the BerkeleyEnvironment object for the wallet directory, never empty because ~BerkeleyEnvironment
5958
* erases the weak pointer from the g_dbenvs map.
6059
* @post A new BerkeleyEnvironment weak pointer is inserted into g_dbenvs if the directory path key was not already in the map.
6160
*/
62-
std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& wallet_path, std::string& database_filename)
61+
std::shared_ptr<BerkeleyEnvironment> GetBerkeleyEnv(const fs::path& env_directory)
6362
{
64-
fs::path env_directory;
65-
SplitWalletPath(wallet_path, env_directory, database_filename);
6663
LOCK(cs_db);
6764
auto inserted = g_dbenvs.emplace(env_directory.string(), std::weak_ptr<BerkeleyEnvironment>());
6865
if (inserted.second) {
@@ -808,21 +805,14 @@ std::unique_ptr<DatabaseBatch> BerkeleyDatabase::MakeBatch(bool flush_on_close)
808805
return MakeUnique<BerkeleyBatch>(*this, false, flush_on_close);
809806
}
810807

811-
bool ExistsBerkeleyDatabase(const fs::path& path)
812-
{
813-
fs::path env_directory;
814-
std::string data_filename;
815-
SplitWalletPath(path, env_directory, data_filename);
816-
return IsBDBFile(env_directory / data_filename);
817-
}
818-
819808
std::unique_ptr<BerkeleyDatabase> MakeBerkeleyDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error)
820809
{
810+
fs::path data_file = BDBDataFile(path);
821811
std::unique_ptr<BerkeleyDatabase> db;
822812
{
823813
LOCK(cs_db); // Lock env.m_databases until insert in BerkeleyDatabase constructor
824-
std::string data_filename;
825-
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(path, data_filename);
814+
std::string data_filename = data_file.filename().string();
815+
std::shared_ptr<BerkeleyEnvironment> env = GetBerkeleyEnv(data_file.parent_path());
826816
if (env->m_databases.count(data_filename)) {
827817
error = Untranslated(strprintf("Refusing to load database. Data file '%s' is already loaded.", (env->Directory() / data_filename).string()));
828818
status = DatabaseStatus::FAILED_ALREADY_LOADED;
@@ -839,28 +829,3 @@ std::unique_ptr<BerkeleyDatabase> MakeBerkeleyDatabase(const fs::path& path, con
839829
status = DatabaseStatus::SUCCESS;
840830
return db;
841831
}
842-
843-
bool IsBDBFile(const fs::path& path)
844-
{
845-
if (!fs::exists(path)) return false;
846-
847-
// A Berkeley DB Btree file has at least 4K.
848-
// This check also prevents opening lock files.
849-
boost::system::error_code ec;
850-
auto size = fs::file_size(path, ec);
851-
if (ec) LogPrintf("%s: %s %s\n", __func__, ec.message(), path.string());
852-
if (size < 4096) return false;
853-
854-
fsbridge::ifstream file(path, std::ios::binary);
855-
if (!file.is_open()) return false;
856-
857-
file.seekg(12, std::ios::beg); // Magic bytes start at offset 12
858-
uint32_t data = 0;
859-
file.read((char*) &data, sizeof(data)); // Read 4 bytes of file to compare against magic
860-
861-
// Berkeley DB Btree magic bytes, from:
862-
// https://github.com/file/file/blob/5824af38469ec1ca9ac3ffd251e7afe9dc11e227/magic/Magdir/database#L74-L75
863-
// - big endian systems - 00 05 31 62
864-
// - little endian systems - 62 31 05 00
865-
return data == 0x00053162 || data == 0x62310500;
866-
}

src/wallet/bdb.h

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,8 @@ class BerkeleyEnvironment
8383
}
8484
};
8585

86-
/** Get BerkeleyEnvironment and database filename given a wallet path. */
87-
std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& wallet_path, std::string& database_filename);
88-
89-
/** Check format of database file */
90-
bool IsBDBFile(const fs::path& path);
86+
/** Get BerkeleyEnvironment given a directory path. */
87+
std::shared_ptr<BerkeleyEnvironment> GetBerkeleyEnv(const fs::path& env_directory);
9188

9289
class BerkeleyBatch;
9390

@@ -226,9 +223,6 @@ class BerkeleyBatch : public DatabaseBatch
226223

227224
std::string BerkeleyDatabaseVersion();
228225

229-
//! Check if Berkeley database exists at specified path.
230-
bool ExistsBerkeleyDatabase(const fs::path& path);
231-
232226
//! Return object giving access to Berkeley database at specified path.
233227
std::unique_ptr<BerkeleyDatabase> MakeBerkeleyDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error);
234228

src/wallet/db.cpp

Lines changed: 112 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,130 @@
33
// Distributed under the MIT software license, see the accompanying
44
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
55

6+
#include <chainparams.h>
67
#include <fs.h>
8+
#include <logging.h>
79
#include <wallet/db.h>
810

911
#include <string>
1012

11-
void SplitWalletPath(const fs::path& wallet_path, fs::path& env_directory, std::string& database_filename)
13+
std::vector<fs::path> ListDatabases(const fs::path& wallet_dir)
14+
{
15+
const size_t offset = wallet_dir.string().size() + 1;
16+
std::vector<fs::path> paths;
17+
boost::system::error_code ec;
18+
19+
for (auto it = fs::recursive_directory_iterator(wallet_dir, ec); it != fs::recursive_directory_iterator(); it.increment(ec)) {
20+
if (ec) {
21+
LogPrintf("%s: %s %s\n", __func__, ec.message(), it->path().string());
22+
continue;
23+
}
24+
25+
try {
26+
// Get wallet path relative to walletdir by removing walletdir from the wallet path.
27+
// This can be replaced by boost::filesystem::lexically_relative once boost is bumped to 1.60.
28+
const fs::path path = it->path().string().substr(offset);
29+
30+
if (it->status().type() == fs::directory_file &&
31+
(IsBDBFile(BDBDataFile(it->path())) || IsSQLiteFile(SQLiteDataFile(it->path())))) {
32+
// Found a directory which contains wallet.dat btree file, add it as a wallet.
33+
paths.emplace_back(path);
34+
} else if (it.level() == 0 && it->symlink_status().type() == fs::regular_file && IsBDBFile(it->path())) {
35+
if (it->path().filename() == "wallet.dat") {
36+
// Found top-level wallet.dat btree file, add top level directory ""
37+
// as a wallet.
38+
paths.emplace_back();
39+
} else {
40+
// Found top-level btree file not called wallet.dat. Current bitcoin
41+
// software will never create these files but will allow them to be
42+
// opened in a shared database environment for backwards compatibility.
43+
// Add it to the list of available wallets.
44+
paths.emplace_back(path);
45+
}
46+
}
47+
} catch (const std::exception& e) {
48+
LogPrintf("%s: Error scanning %s: %s\n", __func__, it->path().string(), e.what());
49+
it.no_push();
50+
}
51+
}
52+
53+
return paths;
54+
}
55+
56+
fs::path BDBDataFile(const fs::path& wallet_path)
1257
{
1358
if (fs::is_regular_file(wallet_path)) {
1459
// Special case for backwards compatibility: if wallet path points to an
1560
// existing file, treat it as the path to a BDB data file in a parent
1661
// directory that also contains BDB log files.
17-
env_directory = wallet_path.parent_path();
18-
database_filename = wallet_path.filename().string();
62+
return wallet_path;
1963
} else {
2064
// Normal case: Interpret wallet path as a directory path containing
2165
// data and log files.
22-
env_directory = wallet_path;
23-
database_filename = "wallet.dat";
66+
return wallet_path / "wallet.dat";
2467
}
2568
}
69+
70+
fs::path SQLiteDataFile(const fs::path& path)
71+
{
72+
return path / "wallet.dat";
73+
}
74+
75+
bool IsBDBFile(const fs::path& path)
76+
{
77+
if (!fs::exists(path)) return false;
78+
79+
// A Berkeley DB Btree file has at least 4K.
80+
// This check also prevents opening lock files.
81+
boost::system::error_code ec;
82+
auto size = fs::file_size(path, ec);
83+
if (ec) LogPrintf("%s: %s %s\n", __func__, ec.message(), path.string());
84+
if (size < 4096) return false;
85+
86+
fsbridge::ifstream file(path, std::ios::binary);
87+
if (!file.is_open()) return false;
88+
89+
file.seekg(12, std::ios::beg); // Magic bytes start at offset 12
90+
uint32_t data = 0;
91+
file.read((char*) &data, sizeof(data)); // Read 4 bytes of file to compare against magic
92+
93+
// Berkeley DB Btree magic bytes, from:
94+
// https://github.com/file/file/blob/5824af38469ec1ca9ac3ffd251e7afe9dc11e227/magic/Magdir/database#L74-L75
95+
// - big endian systems - 00 05 31 62
96+
// - little endian systems - 62 31 05 00
97+
return data == 0x00053162 || data == 0x62310500;
98+
}
99+
100+
bool IsSQLiteFile(const fs::path& path)
101+
{
102+
if (!fs::exists(path)) return false;
103+
104+
// A SQLite Database file is at least 512 bytes.
105+
boost::system::error_code ec;
106+
auto size = fs::file_size(path, ec);
107+
if (ec) LogPrintf("%s: %s %s\n", __func__, ec.message(), path.string());
108+
if (size < 512) return false;
109+
110+
fsbridge::ifstream file(path, std::ios::binary);
111+
if (!file.is_open()) return false;
112+
113+
// Magic is at beginning and is 16 bytes long
114+
char magic[16];
115+
file.read(magic, 16);
116+
117+
// Application id is at offset 68 and 4 bytes long
118+
file.seekg(68, std::ios::beg);
119+
char app_id[4];
120+
file.read(app_id, 4);
121+
122+
file.close();
123+
124+
// Check the magic, see https://sqlite.org/fileformat2.html
125+
std::string magic_str(magic, 16);
126+
if (magic_str != std::string("SQLite format 3", 16)) {
127+
return false;
128+
}
129+
130+
// Check the application id matches our network magic
131+
return memcmp(Params().MessageStart(), app_id, 4) == 0;
132+
}

src/wallet/db.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,14 @@ enum class DatabaseStatus {
223223
FAILED_ENCRYPT,
224224
};
225225

226+
/** Recursively list database paths in directory. */
227+
std::vector<fs::path> ListDatabases(const fs::path& path);
228+
226229
std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error);
227230

231+
fs::path BDBDataFile(const fs::path& path);
232+
fs::path SQLiteDataFile(const fs::path& path);
233+
bool IsBDBFile(const fs::path& path);
234+
bool IsSQLiteFile(const fs::path& path);
235+
228236
#endif // BITCOIN_WALLET_DB_H

src/wallet/interfaces.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ class WalletClientImpl : public WalletClient
551551
std::vector<std::string> listWalletDir() override
552552
{
553553
std::vector<std::string> paths;
554-
for (auto& path : ListWalletDir()) {
554+
for (auto& path : ListDatabases(GetWalletDir())) {
555555
paths.push_back(path.string());
556556
}
557557
return paths;

src/wallet/rpcwallet.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2537,7 +2537,7 @@ static RPCHelpMan listwalletdir()
25372537
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
25382538
{
25392539
UniValue wallets(UniValue::VARR);
2540-
for (const auto& path : ListWalletDir()) {
2540+
for (const auto& path : ListDatabases(GetWalletDir())) {
25412541
UniValue wallet(UniValue::VOBJ);
25422542
wallet.pushKV("name", path.string());
25432543
wallets.push_back(wallet);

src/wallet/salvage.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ bool RecoverDatabaseFile(const fs::path& file_path, bilingual_str& error, std::v
3232
std::unique_ptr<WalletDatabase> database = MakeDatabase(file_path, options, status, error);
3333
if (!database) return false;
3434

35-
std::string filename;
36-
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, filename);
35+
BerkeleyDatabase& berkeley_database = static_cast<BerkeleyDatabase&>(*database);
36+
std::string filename = berkeley_database.Filename();
37+
std::shared_ptr<BerkeleyEnvironment> env = berkeley_database.env;
3738

3839
if (!env->Open(error)) {
3940
return false;

src/wallet/sqlite.cpp

Lines changed: 2 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
#include <sqlite3.h>
1818
#include <stdint.h>
1919

20-
static const char* const DATABASE_FILENAME = "wallet.dat";
2120
static constexpr int32_t WALLET_SCHEMA_VERSION = 0;
2221

2322
static Mutex g_sqlite_mutex;
@@ -568,17 +567,11 @@ bool SQLiteBatch::TxnAbort()
568567
return res == SQLITE_OK;
569568
}
570569

571-
bool ExistsSQLiteDatabase(const fs::path& path)
572-
{
573-
const fs::path file = path / DATABASE_FILENAME;
574-
return fs::symlink_status(file).type() == fs::regular_file && IsSQLiteFile(file);
575-
}
576-
577570
std::unique_ptr<SQLiteDatabase> MakeSQLiteDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error)
578571
{
579-
const fs::path file = path / DATABASE_FILENAME;
580572
try {
581-
auto db = MakeUnique<SQLiteDatabase>(path, file);
573+
fs::path data_file = SQLiteDataFile(path);
574+
auto db = MakeUnique<SQLiteDatabase>(data_file.parent_path(), data_file);
582575
if (options.verify && !db->Verify(error)) {
583576
status = DatabaseStatus::FAILED_VERIFY;
584577
return nullptr;
@@ -596,37 +589,3 @@ std::string SQLiteDatabaseVersion()
596589
{
597590
return std::string(sqlite3_libversion());
598591
}
599-
600-
bool IsSQLiteFile(const fs::path& path)
601-
{
602-
if (!fs::exists(path)) return false;
603-
604-
// A SQLite Database file is at least 512 bytes.
605-
boost::system::error_code ec;
606-
auto size = fs::file_size(path, ec);
607-
if (ec) LogPrintf("%s: %s %s\n", __func__, ec.message(), path.string());
608-
if (size < 512) return false;
609-
610-
fsbridge::ifstream file(path, std::ios::binary);
611-
if (!file.is_open()) return false;
612-
613-
// Magic is at beginning and is 16 bytes long
614-
char magic[16];
615-
file.read(magic, 16);
616-
617-
// Application id is at offset 68 and 4 bytes long
618-
file.seekg(68, std::ios::beg);
619-
char app_id[4];
620-
file.read(app_id, 4);
621-
622-
file.close();
623-
624-
// Check the magic, see https://sqlite.org/fileformat2.html
625-
std::string magic_str(magic, 16);
626-
if (magic_str != std::string("SQLite format 3", 16)) {
627-
return false;
628-
}
629-
630-
// Check the application id matches our network magic
631-
return memcmp(Params().MessageStart(), app_id, 4) == 0;
632-
}

src/wallet/sqlite.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,8 @@ class SQLiteDatabase : public WalletDatabase
113113
sqlite3* m_db{nullptr};
114114
};
115115

116-
bool ExistsSQLiteDatabase(const fs::path& path);
117116
std::unique_ptr<SQLiteDatabase> MakeSQLiteDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error);
118117

119118
std::string SQLiteDatabaseVersion();
120-
bool IsSQLiteFile(const fs::path& path);
121119

122120
#endif // BITCOIN_WALLET_SQLITE_H

src/wallet/test/db_tests.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@
1313

1414
BOOST_FIXTURE_TEST_SUITE(db_tests, BasicTestingSetup)
1515

16+
static std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& path, std::string& database_filename)
17+
{
18+
fs::path data_file = BDBDataFile(path);
19+
database_filename = data_file.filename().string();
20+
return GetBerkeleyEnv(data_file.parent_path());
21+
}
22+
1623
BOOST_AUTO_TEST_CASE(getwalletenv_file)
1724
{
1825
std::string test_name = "test_name.dat";

0 commit comments

Comments
 (0)