Line data Source code
1 : // Copyright (c) 2009-2010 Satoshi Nakamoto
2 : // Copyright (c) 2009-2021 The Bitcoin Core developers
3 : // Distributed under the MIT software license, see the accompanying
4 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 :
6 : #include <compat/compat.h>
7 : #include <fs.h>
8 : #include <wallet/bdb.h>
9 : #include <wallet/db.h>
10 :
11 : #include <util/strencodings.h>
12 : #include <util/translation.h>
13 :
14 : #include <stdint.h>
15 :
16 : #include <sys/stat.h>
17 :
18 : // Windows may not define S_IRUSR or S_IWUSR. We define both
19 : // here, with the same values as glibc (see stat.h).
20 : #ifdef WIN32
21 : #ifndef S_IRUSR
22 : #define S_IRUSR 0400
23 : #define S_IWUSR 0200
24 : #endif
25 : #endif
26 :
27 : namespace wallet {
28 : namespace {
29 213 : Span<const std::byte> SpanFromDbt(const BerkeleyBatch::SafeDbt& dbt)
30 : {
31 213 : return {reinterpret_cast<const std::byte*>(dbt.get_data()), dbt.get_size()};
32 : }
33 :
34 : //! Make sure database has a unique fileid within the environment. If it
35 : //! doesn't, throw an error. BDB caches do not work properly when more than one
36 : //! open database has the same fileid (values written to one database may show
37 : //! up in reads to other databases).
38 : //!
39 : //! BerkeleyDB generates unique fileids by default
40 : //! (https://docs.oracle.com/cd/E17275_01/html/programmer_reference/program_copy.html),
41 : //! so bitcoin should never create different databases with the same fileid, but
42 : //! this error can be triggered if users manually copy database files.
43 11 : void CheckUniqueFileid(const BerkeleyEnvironment& env, const std::string& filename, Db& db, WalletDatabaseFileId& fileid)
44 : {
45 11 : if (env.IsMock()) return;
46 :
47 11 : int ret = db.get_mpf()->get_fileid(fileid.value);
48 11 : if (ret != 0) {
49 0 : throw std::runtime_error(strprintf("BerkeleyDatabase: Can't open database %s (get_fileid failed with %d)", filename, ret));
50 : }
51 :
52 22 : for (const auto& item : env.m_fileids) {
53 11 : if (fileid == item.second && &fileid != &item.second) {
54 0 : throw std::runtime_error(strprintf("BerkeleyDatabase: Can't open database %s (duplicates fileid %s from %s)", filename,
55 0 : HexStr(item.second.value), item.first));
56 : }
57 : }
58 11 : }
59 :
60 146 : RecursiveMutex cs_db;
61 146 : std::map<std::string, std::weak_ptr<BerkeleyEnvironment>> g_dbenvs GUARDED_BY(cs_db); //!< Map from directory name to db environment.
62 : } // namespace
63 :
64 11 : bool WalletDatabaseFileId::operator==(const WalletDatabaseFileId& rhs) const
65 : {
66 11 : return memcmp(value, &rhs.value, sizeof(value)) == 0;
67 : }
68 :
69 : /**
70 : * @param[in] env_directory Path to environment directory
71 : * @return A shared pointer to the BerkeleyEnvironment object for the wallet directory, never empty because ~BerkeleyEnvironment
72 : * erases the weak pointer from the g_dbenvs map.
73 : * @post A new BerkeleyEnvironment weak pointer is inserted into g_dbenvs if the directory path key was not already in the map.
74 : */
75 19 : std::shared_ptr<BerkeleyEnvironment> GetBerkeleyEnv(const fs::path& env_directory, bool use_shared_memory)
76 : {
77 19 : LOCK(cs_db);
78 19 : auto inserted = g_dbenvs.emplace(fs::PathToString(env_directory), std::weak_ptr<BerkeleyEnvironment>());
79 19 : if (inserted.second) {
80 15 : auto env = std::make_shared<BerkeleyEnvironment>(env_directory, use_shared_memory);
81 15 : inserted.first->second = env;
82 15 : return env;
83 15 : }
84 4 : return inserted.first->second.lock();
85 19 : }
86 :
87 : //
88 : // BerkeleyBatch
89 : //
90 :
91 15 : void BerkeleyEnvironment::Close()
92 : {
93 15 : if (!fDbEnvInit)
94 7 : return;
95 :
96 8 : fDbEnvInit = false;
97 :
98 8 : for (auto& db : m_databases) {
99 0 : BerkeleyDatabase& database = db.second.get();
100 0 : assert(database.m_refcount <= 0);
101 0 : if (database.m_db) {
102 0 : database.m_db->close(0);
103 0 : database.m_db.reset();
104 0 : }
105 : }
106 :
107 8 : FILE* error_file = nullptr;
108 8 : dbenv->get_errfile(&error_file);
109 :
110 8 : int ret = dbenv->close(0);
111 8 : if (ret != 0)
112 0 : LogPrintf("BerkeleyEnvironment::Close: Error %d closing database environment: %s\n", ret, DbEnv::strerror(ret));
113 8 : if (!fMockDb)
114 8 : DbEnv(uint32_t{0}).remove(strPath.c_str(), 0);
115 :
116 8 : if (error_file) fclose(error_file);
117 :
118 8 : UnlockDirectory(fs::PathFromString(strPath), ".walletlock");
119 15 : }
120 :
121 15 : void BerkeleyEnvironment::Reset()
122 : {
123 15 : dbenv.reset(new DbEnv(DB_CXX_NO_EXCEPTIONS));
124 15 : fDbEnvInit = false;
125 15 : fMockDb = false;
126 15 : }
127 :
128 30 : BerkeleyEnvironment::BerkeleyEnvironment(const fs::path& dir_path, bool use_shared_memory) : strPath(fs::PathToString(dir_path)), m_use_shared_memory(use_shared_memory)
129 15 : {
130 15 : Reset();
131 30 : }
132 :
133 30 : BerkeleyEnvironment::~BerkeleyEnvironment()
134 15 : {
135 15 : LOCK(cs_db);
136 15 : g_dbenvs.erase(strPath);
137 15 : Close();
138 30 : }
139 :
140 126 : bool BerkeleyEnvironment::Open(bilingual_str& err)
141 : {
142 126 : if (fDbEnvInit) {
143 118 : return true;
144 : }
145 :
146 8 : fs::path pathIn = fs::PathFromString(strPath);
147 8 : TryCreateDirectories(pathIn);
148 8 : if (!LockDirectory(pathIn, ".walletlock")) {
149 0 : LogPrintf("Cannot obtain a lock on wallet directory %s. Another instance may be using it.\n", strPath);
150 0 : err = strprintf(_("Error initializing wallet database environment %s!"), fs::quoted(fs::PathToString(Directory())));
151 0 : return false;
152 : }
153 :
154 8 : fs::path pathLogDir = pathIn / "database";
155 8 : TryCreateDirectories(pathLogDir);
156 8 : fs::path pathErrorFile = pathIn / "db.log";
157 8 : LogPrintf("BerkeleyEnvironment::Open: LogDir=%s ErrorFile=%s\n", fs::PathToString(pathLogDir), fs::PathToString(pathErrorFile));
158 :
159 8 : unsigned int nEnvFlags = 0;
160 8 : if (!m_use_shared_memory) {
161 8 : nEnvFlags |= DB_PRIVATE;
162 8 : }
163 :
164 8 : dbenv->set_lg_dir(fs::PathToString(pathLogDir).c_str());
165 8 : dbenv->set_cachesize(0, 0x100000, 1); // 1 MiB should be enough for just the wallet
166 8 : dbenv->set_lg_bsize(0x10000);
167 8 : dbenv->set_lg_max(1048576);
168 8 : dbenv->set_lk_max_locks(40000);
169 8 : dbenv->set_lk_max_objects(40000);
170 8 : dbenv->set_errfile(fsbridge::fopen(pathErrorFile, "a")); /// debug
171 8 : dbenv->set_flags(DB_AUTO_COMMIT, 1);
172 8 : dbenv->set_flags(DB_TXN_WRITE_NOSYNC, 1);
173 8 : dbenv->log_set_config(DB_LOG_AUTO_REMOVE, 1);
174 16 : int ret = dbenv->open(strPath.c_str(),
175 : DB_CREATE |
176 : DB_INIT_LOCK |
177 : DB_INIT_LOG |
178 : DB_INIT_MPOOL |
179 : DB_INIT_TXN |
180 : DB_THREAD |
181 8 : DB_RECOVER |
182 8 : nEnvFlags,
183 : S_IRUSR | S_IWUSR);
184 8 : if (ret != 0) {
185 0 : LogPrintf("BerkeleyEnvironment::Open: Error %d opening database environment: %s\n", ret, DbEnv::strerror(ret));
186 0 : int ret2 = dbenv->close(0);
187 0 : if (ret2 != 0) {
188 0 : LogPrintf("BerkeleyEnvironment::Open: Error %d closing failed database environment: %s\n", ret2, DbEnv::strerror(ret2));
189 0 : }
190 0 : Reset();
191 0 : err = strprintf(_("Error initializing wallet database environment %s!"), fs::quoted(fs::PathToString(Directory())));
192 0 : if (ret == DB_RUNRECOVERY) {
193 0 : err += Untranslated(" ") + _("This error could occur if this wallet was not shutdown cleanly and was last loaded using a build with a newer version of Berkeley DB. If so, please use the software that last loaded this wallet");
194 0 : }
195 0 : return false;
196 : }
197 :
198 8 : fDbEnvInit = true;
199 8 : fMockDb = false;
200 8 : return true;
201 126 : }
202 :
203 : //! Construct an in-memory mock Berkeley environment for testing
204 0 : BerkeleyEnvironment::BerkeleyEnvironment() : m_use_shared_memory(false)
205 0 : {
206 0 : Reset();
207 :
208 0 : LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::MakeMock\n");
209 :
210 0 : dbenv->set_cachesize(1, 0, 1);
211 0 : dbenv->set_lg_bsize(10485760 * 4);
212 0 : dbenv->set_lg_max(10485760);
213 0 : dbenv->set_lk_max_locks(10000);
214 0 : dbenv->set_lk_max_objects(10000);
215 0 : dbenv->set_flags(DB_AUTO_COMMIT, 1);
216 0 : dbenv->log_set_config(DB_LOG_IN_MEMORY, 1);
217 0 : int ret = dbenv->open(nullptr,
218 : DB_CREATE |
219 : DB_INIT_LOCK |
220 : DB_INIT_LOG |
221 : DB_INIT_MPOOL |
222 : DB_INIT_TXN |
223 : DB_THREAD |
224 : DB_PRIVATE,
225 : S_IRUSR | S_IWUSR);
226 0 : if (ret > 0) {
227 0 : throw std::runtime_error(strprintf("BerkeleyEnvironment::MakeMock: Error %d opening database environment.", ret));
228 : }
229 :
230 0 : fDbEnvInit = true;
231 0 : fMockDb = true;
232 0 : }
233 :
234 522 : BerkeleyBatch::SafeDbt::SafeDbt()
235 261 : {
236 261 : m_dbt.set_flags(DB_DBT_MALLOC);
237 522 : }
238 :
239 640 : BerkeleyBatch::SafeDbt::SafeDbt(void* data, size_t size)
240 320 : : m_dbt(data, size)
241 320 : {
242 640 : }
243 :
244 1162 : BerkeleyBatch::SafeDbt::~SafeDbt()
245 581 : {
246 581 : if (m_dbt.get_data() != nullptr) {
247 : // Clear memory, e.g. in case it was a private key
248 539 : memory_cleanse(m_dbt.get_data(), m_dbt.get_size());
249 : // under DB_DBT_MALLOC, data is malloced by the Dbt, but must be
250 : // freed by the caller.
251 : // https://docs.oracle.com/cd/E17275_01/html/api_reference/C/dbt.html
252 539 : if (m_dbt.get_flags() & DB_DBT_MALLOC) {
253 219 : free(m_dbt.get_data());
254 219 : }
255 539 : }
256 1162 : }
257 :
258 429 : const void* BerkeleyBatch::SafeDbt::get_data() const
259 : {
260 429 : return m_dbt.get_data();
261 : }
262 :
263 216 : uint32_t BerkeleyBatch::SafeDbt::get_size() const
264 : {
265 216 : return m_dbt.get_size();
266 : }
267 :
268 581 : BerkeleyBatch::SafeDbt::operator Dbt*()
269 : {
270 581 : return &m_dbt;
271 : }
272 :
273 8 : bool BerkeleyDatabase::Verify(bilingual_str& errorStr)
274 : {
275 8 : fs::path walletDir = env->Directory();
276 8 : fs::path file_path = walletDir / m_filename;
277 :
278 8 : LogPrintf("Using BerkeleyDB version %s\n", BerkeleyDatabaseVersion());
279 8 : LogPrintf("Using wallet %s\n", fs::PathToString(file_path));
280 :
281 8 : if (!env->Open(errorStr)) {
282 0 : return false;
283 : }
284 :
285 8 : if (fs::exists(file_path))
286 : {
287 4 : assert(m_refcount == 0);
288 :
289 4 : Db db(env->dbenv.get(), 0);
290 4 : const std::string strFile = fs::PathToString(m_filename);
291 4 : int result = db.verify(strFile.c_str(), nullptr, nullptr, 0);
292 4 : if (result != 0) {
293 0 : errorStr = strprintf(_("%s corrupt. Try using the wallet tool dash-wallet to salvage or restoring a backup."), fs::quoted(fs::PathToString(file_path)));
294 0 : return false;
295 : }
296 4 : }
297 : // also return true if files does not exists
298 8 : return true;
299 8 : }
300 :
301 3 : void BerkeleyEnvironment::CheckpointLSN(const std::string& strFile)
302 : {
303 3 : dbenv->txn_checkpoint(0, 0, 0);
304 3 : if (fMockDb)
305 0 : return;
306 3 : dbenv->lsn_reset(strFile.c_str(), 0);
307 3 : }
308 :
309 24 : BerkeleyDatabase::~BerkeleyDatabase()
310 24 : {
311 8 : if (env) {
312 8 : LOCK(cs_db);
313 8 : env->CloseDb(m_filename);
314 8 : assert(!m_db);
315 8 : size_t erased = env->m_databases.erase(m_filename);
316 8 : assert(erased == 1);
317 8 : env->m_fileids.erase(fs::PathToString(m_filename));
318 8 : }
319 24 : }
320 :
321 236 : BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const bool read_only, bool fFlushOnCloseIn) : m_database(database)
322 236 : {
323 : database.AddRef();
324 : database.Open();
325 : fReadOnly = read_only;
326 : fFlushOnClose = fFlushOnCloseIn;
327 : env = database.env.get();
328 : pdb = database.m_db.get();
329 : strFile = fs::PathToString(database.m_filename);
330 118 : }
331 :
332 118 : void BerkeleyDatabase::Open()
333 : {
334 118 : unsigned int nFlags = DB_THREAD | DB_CREATE;
335 :
336 : {
337 118 : LOCK(cs_db);
338 118 : bilingual_str open_err;
339 118 : if (!env->Open(open_err))
340 0 : throw std::runtime_error("BerkeleyDatabase: Failed to open database environment.");
341 :
342 118 : if (m_db == nullptr) {
343 : int ret;
344 11 : std::unique_ptr<Db> pdb_temp = std::make_unique<Db>(env->dbenv.get(), 0);
345 11 : const std::string strFile = fs::PathToString(m_filename);
346 :
347 11 : bool fMockDb = env->IsMock();
348 11 : if (fMockDb) {
349 0 : DbMpoolFile* mpf = pdb_temp->get_mpf();
350 0 : ret = mpf->set_flags(DB_MPOOL_NOFILE, 1);
351 0 : if (ret != 0) {
352 0 : throw std::runtime_error(strprintf("BerkeleyDatabase: Failed to configure for no temp file backing for database %s", strFile));
353 : }
354 0 : }
355 :
356 22 : ret = pdb_temp->open(nullptr, // Txn pointer
357 11 : fMockDb ? nullptr : strFile.c_str(), // Filename
358 11 : fMockDb ? strFile.c_str() : "main", // Logical db name
359 : DB_BTREE, // Database type
360 11 : nFlags, // Flags
361 : 0);
362 :
363 11 : if (ret != 0) {
364 0 : throw std::runtime_error(strprintf("BerkeleyDatabase: Error %d, can't open database %s", ret, strFile));
365 : }
366 :
367 : // Call CheckUniqueFileid on the containing BDB environment to
368 : // avoid BDB data consistency bugs that happen when different data
369 : // files in the same environment have the same fileid.
370 11 : CheckUniqueFileid(*env, strFile, *pdb_temp, this->env->m_fileids[strFile]);
371 :
372 11 : m_db.reset(pdb_temp.release());
373 :
374 11 : }
375 118 : }
376 118 : }
377 :
378 111 : void BerkeleyBatch::Flush()
379 : {
380 111 : if (activeTxn)
381 0 : return;
382 :
383 : // Flush database activity from memory pool to disk log
384 111 : unsigned int nMinutes = 0;
385 111 : if (fReadOnly)
386 0 : nMinutes = 1;
387 :
388 111 : if (env) { // env is nullptr for dummy databases (i.e. in tests). Don't actually flush if env is nullptr so we don't segfault
389 111 : env->dbenv->txn_checkpoint(nMinutes ? m_database.m_max_log_mb * 1024 : 0, nMinutes, 0);
390 111 : }
391 111 : }
392 :
393 132 : void BerkeleyDatabase::IncrementUpdateCounter()
394 : {
395 132 : ++nUpdateCounter;
396 132 : }
397 :
398 354 : BerkeleyBatch::~BerkeleyBatch()
399 354 : {
400 118 : Close();
401 118 : m_database.RemoveRef();
402 354 : }
403 :
404 118 : void BerkeleyBatch::Close()
405 : {
406 118 : if (!pdb)
407 0 : return;
408 118 : if (activeTxn)
409 0 : activeTxn->abort();
410 118 : activeTxn = nullptr;
411 118 : pdb = nullptr;
412 118 : CloseCursor();
413 :
414 118 : if (fFlushOnClose)
415 111 : Flush();
416 118 : }
417 :
418 16 : void BerkeleyEnvironment::CloseDb(const fs::path& filename)
419 : {
420 : {
421 16 : LOCK(cs_db);
422 16 : auto it = m_databases.find(filename);
423 16 : assert(it != m_databases.end());
424 16 : BerkeleyDatabase& database = it->second.get();
425 16 : if (database.m_db) {
426 : // Close the database handle
427 11 : database.m_db->close(0);
428 11 : database.m_db.reset();
429 11 : }
430 16 : }
431 16 : }
432 :
433 0 : void BerkeleyEnvironment::ReloadDbEnv()
434 : {
435 : // Make sure that no Db's are in use
436 0 : AssertLockNotHeld(cs_db);
437 0 : std::unique_lock<RecursiveMutex> lock(cs_db);
438 0 : m_db_in_use.wait(lock, [this](){
439 0 : for (auto& db : m_databases) {
440 0 : if (db.second.get().m_refcount > 0) return false;
441 : }
442 0 : return true;
443 0 : });
444 :
445 0 : std::vector<fs::path> filenames;
446 0 : for (const auto& it : m_databases) {
447 0 : filenames.push_back(it.first);
448 : }
449 : // Close the individual Db's
450 0 : for (const fs::path& filename : filenames) {
451 0 : CloseDb(filename);
452 : }
453 : // Reset the environment
454 0 : Flush(true); // This will flush and close the environment
455 0 : Reset();
456 0 : bilingual_str open_err;
457 0 : Open(open_err);
458 0 : }
459 :
460 0 : bool BerkeleyDatabase::Rewrite(const char* pszSkip)
461 : {
462 0 : while (true) {
463 : {
464 0 : LOCK(cs_db);
465 0 : const std::string strFile = fs::PathToString(m_filename);
466 0 : if (m_refcount <= 0) {
467 : // Flush log data to the dat file
468 0 : env->CloseDb(m_filename);
469 0 : env->CheckpointLSN(strFile);
470 0 : m_refcount = -1;
471 :
472 0 : bool fSuccess = true;
473 0 : LogPrintf("BerkeleyBatch::Rewrite: Rewriting %s...\n", strFile);
474 0 : std::string strFileRes = strFile + ".rewrite";
475 : { // surround usage of db with extra {}
476 0 : BerkeleyBatch db(*this, true);
477 0 : std::unique_ptr<Db> pdbCopy = std::make_unique<Db>(env->dbenv.get(), 0);
478 :
479 0 : int ret = pdbCopy->open(nullptr, // Txn pointer
480 0 : strFileRes.c_str(), // Filename
481 : "main", // Logical db name
482 : DB_BTREE, // Database type
483 : DB_CREATE, // Flags
484 : 0);
485 0 : if (ret > 0) {
486 0 : LogPrintf("BerkeleyBatch::Rewrite: Can't create database file %s\n", strFileRes);
487 0 : fSuccess = false;
488 0 : }
489 :
490 0 : if (db.StartCursor()) {
491 0 : while (fSuccess) {
492 0 : CDataStream ssKey(SER_DISK, CLIENT_VERSION);
493 0 : CDataStream ssValue(SER_DISK, CLIENT_VERSION);
494 : bool complete;
495 0 : bool ret1 = db.ReadAtCursor(ssKey, ssValue, complete);
496 0 : if (complete) {
497 0 : break;
498 0 : } else if (!ret1) {
499 0 : fSuccess = false;
500 0 : break;
501 : }
502 0 : if (pszSkip &&
503 0 : strncmp((const char*)ssKey.data(), pszSkip, std::min(ssKey.size(), strlen(pszSkip))) == 0)
504 0 : continue;
505 0 : if (strncmp((const char*)ssKey.data(), "\x07version", 8) == 0) {
506 : // Update version:
507 0 : ssValue.clear();
508 0 : ssValue << CLIENT_VERSION;
509 0 : }
510 0 : Dbt datKey(ssKey.data(), ssKey.size());
511 0 : Dbt datValue(ssValue.data(), ssValue.size());
512 0 : int ret2 = pdbCopy->put(nullptr, &datKey, &datValue, DB_NOOVERWRITE);
513 0 : if (ret2 > 0)
514 0 : fSuccess = false;
515 0 : }
516 0 : db.CloseCursor();
517 0 : }
518 0 : if (fSuccess) {
519 0 : db.Close();
520 0 : env->CloseDb(m_filename);
521 0 : if (pdbCopy->close(0))
522 0 : fSuccess = false;
523 0 : } else {
524 0 : pdbCopy->close(0);
525 : }
526 0 : }
527 0 : if (fSuccess) {
528 0 : Db dbA(env->dbenv.get(), 0);
529 0 : if (dbA.remove(strFile.c_str(), nullptr, 0))
530 0 : fSuccess = false;
531 0 : Db dbB(env->dbenv.get(), 0);
532 0 : if (dbB.rename(strFileRes.c_str(), nullptr, strFile.c_str(), 0))
533 0 : fSuccess = false;
534 0 : }
535 0 : if (!fSuccess)
536 0 : LogPrintf("BerkeleyBatch::Rewrite: Failed to rewrite database file %s\n", strFileRes);
537 0 : return fSuccess;
538 0 : }
539 0 : }
540 0 : UninterruptibleSleep(std::chrono::milliseconds{100});
541 : }
542 0 : }
543 :
544 :
545 5 : void BerkeleyEnvironment::Flush(bool fShutdown)
546 : {
547 5 : const auto start{SteadyClock::now()};
548 : // Flush log data to the actual data file on all files that are not in use
549 5 : LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: [%s] Flush(%s)%s\n", strPath, fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started");
550 5 : if (!fDbEnvInit)
551 0 : return;
552 : {
553 5 : LOCK(cs_db);
554 5 : bool no_dbs_accessed = true;
555 10 : for (auto& db_it : m_databases) {
556 5 : const fs::path& filename = db_it.first;
557 5 : int nRefCount = db_it.second.get().m_refcount;
558 5 : if (nRefCount < 0) continue;
559 5 : const std::string strFile = fs::PathToString(filename);
560 5 : LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: Flushing %s (refcount = %d)...\n", strFile, nRefCount);
561 5 : if (nRefCount == 0) {
562 : // Move log data to the dat file
563 5 : CloseDb(filename);
564 5 : LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: %s checkpoint\n", strFile);
565 5 : dbenv->txn_checkpoint(0, 0, 0);
566 5 : LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: %s detach\n", strFile);
567 5 : if (!fMockDb)
568 5 : dbenv->lsn_reset(strFile.c_str(), 0);
569 5 : LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: %s closed\n", strFile);
570 5 : nRefCount = -1;
571 5 : } else {
572 0 : no_dbs_accessed = false;
573 : }
574 5 : }
575 5 : LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: Flush(%s)%s took %15dms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started", Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
576 5 : if (fShutdown) {
577 : char** listp;
578 0 : if (no_dbs_accessed) {
579 0 : dbenv->log_archive(&listp, DB_ARCH_REMOVE);
580 0 : Close();
581 0 : if (!fMockDb) {
582 0 : fs::remove_all(fs::PathFromString(strPath) / "database");
583 0 : }
584 0 : }
585 0 : }
586 5 : }
587 5 : }
588 :
589 0 : bool BerkeleyDatabase::PeriodicFlush()
590 : {
591 : // Don't flush if we can't acquire the lock.
592 0 : TRY_LOCK(cs_db, lockDb);
593 0 : if (!lockDb) return false;
594 :
595 : // Don't flush if any databases are in use
596 0 : for (auto& it : env->m_databases) {
597 0 : if (it.second.get().m_refcount > 0) return false;
598 : }
599 :
600 : // Don't flush if there haven't been any batch writes for this database.
601 0 : if (m_refcount < 0) return false;
602 :
603 0 : const std::string strFile = fs::PathToString(m_filename);
604 0 : LogPrint(BCLog::WALLETDB, "Flushing %s\n", strFile);
605 0 : const auto start{SteadyClock::now()};
606 :
607 : // Flush wallet file so it's self contained
608 0 : env->CloseDb(m_filename);
609 0 : env->CheckpointLSN(strFile);
610 0 : m_refcount = -1;
611 :
612 0 : LogPrint(BCLog::WALLETDB, "Flushed %s %dms\n", strFile, Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
613 :
614 0 : return true;
615 0 : }
616 :
617 3 : bool BerkeleyDatabase::Backup(const std::string& strDest) const
618 : {
619 3 : const std::string strFile = fs::PathToString(m_filename);
620 3 : while (true)
621 : {
622 : {
623 3 : LOCK(cs_db);
624 3 : if (m_refcount <= 0)
625 : {
626 : // Flush log data to the dat file
627 3 : env->CloseDb(m_filename);
628 3 : env->CheckpointLSN(strFile);
629 :
630 : // Copy wallet file
631 3 : fs::path pathSrc = env->Directory() / m_filename;
632 3 : fs::path pathDest(fs::PathFromString(strDest));
633 3 : if (fs::is_directory(pathDest))
634 0 : pathDest /= m_filename;
635 :
636 : try {
637 3 : if (fs::exists(pathDest) && fs::equivalent(pathSrc, pathDest)) {
638 0 : LogPrintf("cannot backup to wallet source file %s\n", fs::PathToString(pathDest));
639 0 : return false;
640 : }
641 :
642 3 : fs::copy_file(pathSrc, pathDest, fs::copy_options::overwrite_existing);
643 3 : LogPrintf("copied %s to %s\n", strFile, fs::PathToString(pathDest));
644 3 : return true;
645 0 : } catch (const fs::filesystem_error& e) {
646 0 : LogPrintf("error copying %s to %s - %s\n", strFile, fs::PathToString(pathDest), fsbridge::get_filesystem_error_message(e));
647 0 : return false;
648 0 : }
649 3 : }
650 3 : }
651 0 : UninterruptibleSleep(std::chrono::milliseconds{100});
652 : }
653 3 : }
654 :
655 5 : void BerkeleyDatabase::Flush()
656 : {
657 5 : env->Flush(false);
658 5 : }
659 :
660 0 : void BerkeleyDatabase::Close()
661 : {
662 0 : env->Flush(true);
663 0 : }
664 :
665 0 : void BerkeleyDatabase::ReloadDbEnv()
666 : {
667 0 : env->ReloadDbEnv();
668 0 : }
669 :
670 9 : bool BerkeleyBatch::StartCursor()
671 : {
672 9 : assert(!m_cursor);
673 9 : if (!pdb)
674 0 : return false;
675 9 : int ret = pdb->cursor(nullptr, &m_cursor, 0);
676 9 : return ret == 0;
677 9 : }
678 :
679 101 : bool BerkeleyBatch::ReadAtCursor(CDataStream& ssKey, CDataStream& ssValue, bool& complete)
680 : {
681 101 : complete = false;
682 101 : if (m_cursor == nullptr) return false;
683 : // Read at cursor
684 101 : SafeDbt datKey;
685 101 : SafeDbt datValue;
686 101 : int ret = m_cursor->get(datKey, datValue, DB_NEXT);
687 101 : if (ret == DB_NOTFOUND) {
688 9 : complete = true;
689 9 : }
690 101 : if (ret != 0)
691 9 : return false;
692 92 : else if (datKey.get_data() == nullptr || datValue.get_data() == nullptr)
693 0 : return false;
694 :
695 : // Convert to streams
696 92 : ssKey.SetType(SER_DISK);
697 92 : ssKey.clear();
698 92 : ssKey.write(SpanFromDbt(datKey));
699 92 : ssValue.SetType(SER_DISK);
700 92 : ssValue.clear();
701 92 : ssValue.write(SpanFromDbt(datValue));
702 92 : return true;
703 101 : }
704 :
705 127 : void BerkeleyBatch::CloseCursor()
706 : {
707 127 : if (!m_cursor) return;
708 9 : m_cursor->close();
709 9 : m_cursor = nullptr;
710 127 : }
711 :
712 1 : bool BerkeleyBatch::TxnBegin()
713 : {
714 1 : if (!pdb || activeTxn)
715 0 : return false;
716 1 : DbTxn* ptxn = env->TxnBegin();
717 1 : if (!ptxn)
718 0 : return false;
719 1 : activeTxn = ptxn;
720 1 : return true;
721 1 : }
722 :
723 1 : bool BerkeleyBatch::TxnCommit()
724 : {
725 1 : if (!pdb || !activeTxn)
726 0 : return false;
727 1 : int ret = activeTxn->commit(0);
728 1 : activeTxn = nullptr;
729 1 : return (ret == 0);
730 1 : }
731 :
732 0 : bool BerkeleyBatch::TxnAbort()
733 : {
734 0 : if (!pdb || !activeTxn)
735 0 : return false;
736 0 : int ret = activeTxn->abort();
737 0 : activeTxn = nullptr;
738 0 : return (ret == 0);
739 0 : }
740 :
741 627 : bool BerkeleyDatabaseSanityCheck()
742 : {
743 : int major, minor;
744 627 : DbEnv::version(&major, &minor, nullptr);
745 :
746 : /* If the major version differs, or the minor version of library is *older*
747 : * than the header that was compiled against, flag an error.
748 : */
749 627 : if (major != DB_VERSION_MAJOR || minor < DB_VERSION_MINOR) {
750 0 : LogPrintf("BerkeleyDB database version conflict: header version is %d.%d, library version is %d.%d\n",
751 : DB_VERSION_MAJOR, DB_VERSION_MINOR, major, minor);
752 0 : return false;
753 : }
754 :
755 627 : return true;
756 627 : }
757 :
758 8 : std::string BerkeleyDatabaseVersion()
759 : {
760 8 : return DbEnv::version(nullptr, nullptr, nullptr);
761 : }
762 :
763 53 : bool BerkeleyBatch::ReadKey(CDataStream&& key, CDataStream& value)
764 : {
765 53 : if (!pdb)
766 0 : return false;
767 :
768 53 : SafeDbt datKey(key.data(), key.size());
769 :
770 53 : SafeDbt datValue;
771 53 : int ret = pdb->get(activeTxn, datKey, datValue, 0);
772 53 : if (ret == 0 && datValue.get_data() != nullptr) {
773 29 : value.clear();
774 29 : value.write(SpanFromDbt(datValue));
775 29 : return true;
776 : }
777 24 : return false;
778 53 : }
779 :
780 132 : bool BerkeleyBatch::WriteKey(CDataStream&& key, CDataStream&& value, bool overwrite)
781 : {
782 132 : if (!pdb)
783 0 : return false;
784 132 : if (fReadOnly)
785 0 : assert(!"Write called on database in read-only mode");
786 :
787 132 : SafeDbt datKey(key.data(), key.size());
788 :
789 132 : SafeDbt datValue(value.data(), value.size());
790 :
791 132 : int ret = pdb->put(activeTxn, datKey, datValue, (overwrite ? 0 : DB_NOOVERWRITE));
792 132 : return (ret == 0);
793 132 : }
794 :
795 3 : bool BerkeleyBatch::EraseKey(CDataStream&& key)
796 : {
797 3 : if (!pdb)
798 0 : return false;
799 3 : if (fReadOnly)
800 0 : assert(!"Erase called on database in read-only mode");
801 :
802 3 : SafeDbt datKey(key.data(), key.size());
803 :
804 3 : int ret = pdb->del(activeTxn, datKey, 0);
805 3 : return (ret == 0 || ret == DB_NOTFOUND);
806 3 : }
807 :
808 0 : bool BerkeleyBatch::HasKey(CDataStream&& key)
809 : {
810 0 : if (!pdb)
811 0 : return false;
812 :
813 0 : SafeDbt datKey(key.data(), key.size());
814 :
815 0 : int ret = pdb->exists(activeTxn, datKey, 0);
816 0 : return ret == 0;
817 0 : }
818 :
819 1 : bool BerkeleyBatch::ErasePrefix(Span<const std::byte> prefix)
820 : {
821 1 : if (!pdb)
822 0 : return false;
823 1 : if (!TxnBegin()) return false;
824 1 : Dbc* cursor = nullptr;
825 : // Transaction argument to cursor is needed when using the cursor to
826 : // write to the database (delete in this case).
827 1 : int ret = pdb->cursor(activeTxn, &cursor, 0);
828 1 : if (ret != 0) {
829 0 : TxnAbort();
830 0 : return false;
831 : }
832 : // const_cast is safe below even though prefix_key is an in/out parameter,
833 : // because we are not using the DB_DBT_USERMEM flag, so BDB will allocate
834 : // and return a different output data pointer
835 1 : Dbt prefix_key{const_cast<std::byte*>(prefix.data()), static_cast<uint32_t>(prefix.size())}, prefix_value{};
836 1 : ret = cursor->get(&prefix_key, &prefix_value, DB_SET_RANGE);
837 3 : for (int flag{DB_CURRENT}; ret == 0; flag = DB_NEXT) {
838 3 : SafeDbt key, value;
839 3 : ret = cursor->get(key, value, flag);
840 3 : if (ret != 0 || key.get_size() < prefix.size() || memcmp(key.get_data(), prefix.data(), prefix.size()) != 0) break;
841 2 : ret = cursor->del(0);
842 3 : }
843 1 : cursor->close();
844 2 : return TxnCommit() && (ret == 0 || ret == DB_NOTFOUND);
845 1 : }
846 :
847 118 : void BerkeleyDatabase::AddRef()
848 : {
849 118 : LOCK(cs_db);
850 118 : if (m_refcount < 0) {
851 0 : m_refcount = 1;
852 0 : } else {
853 118 : m_refcount++;
854 : }
855 118 : }
856 :
857 118 : void BerkeleyDatabase::RemoveRef()
858 : {
859 118 : LOCK(cs_db);
860 118 : m_refcount--;
861 118 : if (env) env->m_db_in_use.notify_all();
862 118 : }
863 :
864 118 : std::unique_ptr<DatabaseBatch> BerkeleyDatabase::MakeBatch(bool flush_on_close)
865 : {
866 118 : return std::make_unique<BerkeleyBatch>(*this, false, flush_on_close);
867 : }
868 :
869 8 : std::unique_ptr<BerkeleyDatabase> MakeBerkeleyDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error)
870 : {
871 8 : fs::path data_file = BDBDataFile(path);
872 8 : std::unique_ptr<BerkeleyDatabase> db;
873 : {
874 8 : LOCK(cs_db); // Lock env.m_databases until insert in BerkeleyDatabase constructor
875 8 : fs::path data_filename = data_file.filename();
876 8 : std::shared_ptr<BerkeleyEnvironment> env = GetBerkeleyEnv(data_file.parent_path(), options.use_shared_memory);
877 8 : if (env->m_databases.count(data_filename)) {
878 0 : error = Untranslated(strprintf("Refusing to load database. Data file '%s' is already loaded.", fs::PathToString(env->Directory() / data_filename)));
879 0 : status = DatabaseStatus::FAILED_ALREADY_LOADED;
880 0 : return nullptr;
881 : }
882 8 : db = std::make_unique<BerkeleyDatabase>(std::move(env), std::move(data_filename), options);
883 8 : }
884 :
885 8 : if (options.verify && !db->Verify(error)) {
886 0 : status = DatabaseStatus::FAILED_VERIFY;
887 0 : return nullptr;
888 : }
889 :
890 8 : status = DatabaseStatus::SUCCESS;
891 8 : return db;
892 8 : }
893 : } // namespace wallet
|