LCOV - code coverage report
Current view: top level - src/wallet - bdb.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 363 579 62.7 %
Date: 2026-06-25 07:23:51 Functions: 55 65 84.6 %

          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

Generated by: LCOV version 1.16