diff options
Diffstat (limited to 'src/wallet/db.cpp')
-rw-r--r-- | src/wallet/db.cpp | 108 |
1 files changed, 78 insertions, 30 deletions
diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp index 23c6279128..88463d10b2 100644 --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -52,20 +52,44 @@ void CheckUniqueFileid(const CDBEnv& env, const std::string& filename, Db& db) } } } + +CCriticalSection cs_db; +std::map<std::string, CDBEnv> g_dbenvs; //!< Map from directory name to open db environment. } // namespace +CDBEnv* GetWalletEnv(const fs::path& wallet_path, std::string& database_filename) +{ + fs::path env_directory = wallet_path.parent_path(); + database_filename = wallet_path.filename().string(); + LOCK(cs_db); + // Note: An ununsed temporary CDBEnv object may be created inside the + // emplace function if the key already exists. This is a little inefficient, + // but not a big concern since the map will be changed in the future to hold + // pointers instead of objects, anyway. + return &g_dbenvs.emplace(std::piecewise_construct, std::forward_as_tuple(env_directory.string()), std::forward_as_tuple(env_directory)).first->second; +} + // // CDB // -CDBEnv bitdb; - -void CDBEnv::EnvShutdown() +void CDBEnv::Close() { if (!fDbEnvInit) return; fDbEnvInit = false; + + for (auto& db : mapDb) { + auto count = mapFileUseCount.find(db.first); + assert(count == mapFileUseCount.end() || count->second == 0); + if (db.second) { + db.second->close(0); + delete db.second; + db.second = nullptr; + } + } + int ret = dbenv->close(0); if (ret != 0) LogPrintf("CDBEnv::EnvShutdown: Error %d shutting down database environment: %s\n", ret, DbEnv::strerror(ret)); @@ -80,29 +104,24 @@ void CDBEnv::Reset() fMockDb = false; } -CDBEnv::CDBEnv() +CDBEnv::CDBEnv(const fs::path& dir_path) : strPath(dir_path.string()) { Reset(); } CDBEnv::~CDBEnv() { - EnvShutdown(); + Close(); } -void CDBEnv::Close() -{ - EnvShutdown(); -} - -bool CDBEnv::Open(const fs::path& pathIn, bool retry) +bool CDBEnv::Open(bool retry) { if (fDbEnvInit) return true; boost::this_thread::interruption_point(); - strPath = pathIn.string(); + fs::path pathIn = strPath; if (!LockDirectory(pathIn, ".walletlock")) { LogPrintf("Cannot obtain a lock on wallet directory %s. Another instance of bitcoin may be using it.\n", strPath); return false; @@ -150,7 +169,7 @@ bool CDBEnv::Open(const fs::path& pathIn, bool retry) // failure is ok (well, not really, but it's not worse than what we started with) } // try opening it again one more time - if (!Open(pathIn, false)) { + if (!Open(false /* retry */)) { // if it still fails, it probably means we can't even create the database env return false; } @@ -209,12 +228,15 @@ CDBEnv::VerifyResult CDBEnv::Verify(const std::string& strFile, recoverFunc_type return RECOVER_FAIL; // Try to recover: - bool fRecovered = (*recoverFunc)(strFile, out_backup_filename); + bool fRecovered = (*recoverFunc)(fs::path(strPath) / strFile, out_backup_filename); return (fRecovered ? RECOVER_OK : RECOVER_FAIL); } -bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& newFilename) +bool CDB::Recover(const fs::path& file_path, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& newFilename) { + std::string filename; + CDBEnv* env = GetWalletEnv(file_path, filename); + // Recovery procedure: // move wallet file to walletfilename.timestamp.bak // Call Salvage with fAggressive=true to @@ -225,7 +247,7 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco int64_t now = GetTime(); newFilename = strprintf("%s.%d.bak", filename, now); - int result = bitdb.dbenv->dbrename(nullptr, filename.c_str(), nullptr, + int result = env->dbenv->dbrename(nullptr, filename.c_str(), nullptr, newFilename.c_str(), DB_AUTO_COMMIT); if (result == 0) LogPrintf("Renamed %s to %s\n", filename, newFilename); @@ -236,7 +258,7 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco } std::vector<CDBEnv::KeyValPair> salvagedData; - bool fSuccess = bitdb.Salvage(newFilename, true, salvagedData); + bool fSuccess = env->Salvage(newFilename, true, salvagedData); if (salvagedData.empty()) { LogPrintf("Salvage(aggressive) found no records in %s.\n", newFilename); @@ -244,7 +266,7 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco } LogPrintf("Salvage(aggressive) found %u records\n", salvagedData.size()); - std::unique_ptr<Db> pdbCopy = MakeUnique<Db>(bitdb.dbenv.get(), 0); + std::unique_ptr<Db> pdbCopy = MakeUnique<Db>(env->dbenv.get(), 0); int ret = pdbCopy->open(nullptr, // Txn pointer filename.c_str(), // Filename "main", // Logical db name @@ -257,7 +279,7 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco return false; } - DbTxn* ptxn = bitdb.TxnBegin(); + DbTxn* ptxn = env->TxnBegin(); for (CDBEnv::KeyValPair& row : salvagedData) { if (recoverKVcallback) @@ -279,8 +301,12 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco return fSuccess; } -bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& walletDir, std::string& errorStr) +bool CDB::VerifyEnvironment(const fs::path& file_path, std::string& errorStr) { + std::string walletFile; + CDBEnv* env = GetWalletEnv(file_path, walletFile); + fs::path walletDir = env->Directory(); + LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0)); LogPrintf("Using wallet %s\n", walletFile); @@ -291,7 +317,7 @@ bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& walle return false; } - if (!bitdb.Open(walletDir, true)) { + if (!env->Open(true /* retry */)) { errorStr = strprintf(_("Error initializing wallet database environment %s!"), walletDir); return false; } @@ -299,12 +325,16 @@ bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& walle return true; } -bool CDB::VerifyDatabaseFile(const std::string& walletFile, const fs::path& walletDir, std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc) +bool CDB::VerifyDatabaseFile(const fs::path& file_path, std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc) { + std::string walletFile; + CDBEnv* env = GetWalletEnv(file_path, walletFile); + fs::path walletDir = env->Directory(); + if (fs::exists(walletDir / walletFile)) { std::string backup_filename; - CDBEnv::VerifyResult r = bitdb.Verify(walletFile, recoverFunc, backup_filename); + CDBEnv::VerifyResult r = env->Verify(walletFile, recoverFunc, backup_filename); if (r == CDBEnv::RECOVER_OK) { warningStr = strprintf(_("Warning: Wallet file corrupt, data salvaged!" @@ -414,8 +444,8 @@ CDB::CDB(CWalletDBWrapper& dbw, const char* pszMode, bool fFlushOnCloseIn) : pdb nFlags |= DB_CREATE; { - LOCK(env->cs_db); - if (!env->Open(GetWalletDir())) + LOCK(cs_db); + if (!env->Open(false /* retry */)) throw std::runtime_error("CDB: Failed to open database environment."); pdb = env->mapDb[strFilename]; @@ -442,7 +472,25 @@ CDB::CDB(CWalletDBWrapper& dbw, const char* pszMode, bool fFlushOnCloseIn) : pdb if (ret != 0) { throw std::runtime_error(strprintf("CDB: Error %d, can't open database %s", ret, strFilename)); } - CheckUniqueFileid(*env, strFilename, *pdb_temp); + + // Call CheckUniqueFileid on the containing BDB environment to + // avoid BDB data consistency bugs that happen when different data + // files in the same environment have the same fileid. + // + // Also call CheckUniqueFileid on all the other g_dbenvs to prevent + // bitcoin from opening the same data file through another + // environment when the file is referenced through equivalent but + // not obviously identical symlinked or hard linked or bind mounted + // paths. In the future a more relaxed check for equal inode and + // device ids could be done instead, which would allow opening + // different backup copies of a wallet at the same time. Maybe even + // more ideally, an exclusive lock for accessing the database could + // be implemented, so no equality checks are needed at all. (Newer + // versions of BDB have an set_lk_exclusive method for this + // purpose, but the older version we use does not.) + for (auto& env : g_dbenvs) { + CheckUniqueFileid(env.second, strFilename, *pdb_temp); + } pdb = pdb_temp.release(); env->mapDb[strFilename] = pdb; @@ -490,7 +538,7 @@ void CDB::Close() Flush(); { - LOCK(env->cs_db); + LOCK(cs_db); --env->mapFileUseCount[strFile]; } } @@ -518,7 +566,7 @@ bool CDB::Rewrite(CWalletDBWrapper& dbw, const char* pszSkip) const std::string& strFile = dbw.strFile; while (true) { { - LOCK(env->cs_db); + LOCK(cs_db); if (!env->mapFileUseCount.count(strFile) || env->mapFileUseCount[strFile] == 0) { // Flush log data to the dat file env->CloseDb(strFile); @@ -646,7 +694,7 @@ bool CDB::PeriodicFlush(CWalletDBWrapper& dbw) bool ret = false; CDBEnv *env = dbw.env; const std::string& strFile = dbw.strFile; - TRY_LOCK(bitdb.cs_db,lockDb); + TRY_LOCK(cs_db, lockDb); if (lockDb) { // Don't do this if any databases are in use @@ -694,7 +742,7 @@ bool CWalletDBWrapper::Backup(const std::string& strDest) while (true) { { - LOCK(env->cs_db); + LOCK(cs_db); if (!env->mapFileUseCount.count(strFile) || env->mapFileUseCount[strFile] == 0) { // Flush log data to the dat file |