Changeset View
Changeset View
Standalone View
Standalone View
src/wallet/db.cpp
Show First 20 Lines • Show All 53 Lines • ▼ Show 20 Lines | for (const auto &item : env.mapDb) { | ||||
throw std::runtime_error(strprintf( | throw std::runtime_error(strprintf( | ||||
"CDB: Can't open database %s (duplicates fileid %s from %s)", | "CDB: Can't open database %s (duplicates fileid %s from %s)", | ||||
filename, | filename, | ||||
HexStr(std::begin(item_fileid), std::end(item_fileid)), | HexStr(std::begin(item_fileid), std::end(item_fileid)), | ||||
item_filename ? item_filename : "(unknown database)")); | item_filename ? item_filename : "(unknown database)")); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
CCriticalSection cs_db; | |||||
//!< Map from directory name to open db environment. | |||||
std::map<std::string, CDBEnv> g_dbenvs; | |||||
} // namespace | } // 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 | // CDB | ||||
// | // | ||||
CDBEnv bitdb; | void CDBEnv::Close() { | ||||
void CDBEnv::EnvShutdown() { | |||||
if (!fDbEnvInit) { | if (!fDbEnvInit) { | ||||
return; | return; | ||||
} | } | ||||
fDbEnvInit = false; | 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); | int ret = dbenv->close(0); | ||||
if (ret != 0) { | if (ret != 0) { | ||||
LogPrintf("CDBEnv::EnvShutdown: Error %d shutting down database " | LogPrintf("CDBEnv::EnvShutdown: Error %d shutting down database " | ||||
"environment: %s\n", | "environment: %s\n", | ||||
ret, DbEnv::strerror(ret)); | ret, DbEnv::strerror(ret)); | ||||
} | } | ||||
if (!fMockDb) { | if (!fMockDb) { | ||||
DbEnv(uint32_t(0)).remove(strPath.c_str(), 0); | DbEnv(uint32_t(0)).remove(strPath.c_str(), 0); | ||||
} | } | ||||
} | } | ||||
void CDBEnv::Reset() { | void CDBEnv::Reset() { | ||||
dbenv.reset(new DbEnv(DB_CXX_NO_EXCEPTIONS)); | dbenv.reset(new DbEnv(DB_CXX_NO_EXCEPTIONS)); | ||||
fDbEnvInit = false; | fDbEnvInit = false; | ||||
fMockDb = false; | fMockDb = false; | ||||
} | } | ||||
CDBEnv::CDBEnv() { | CDBEnv::CDBEnv(const fs::path &dir_path) : strPath(dir_path.string()) { | ||||
Reset(); | Reset(); | ||||
} | } | ||||
CDBEnv::~CDBEnv() { | CDBEnv::~CDBEnv() { | ||||
EnvShutdown(); | Close(); | ||||
} | |||||
void CDBEnv::Close() { | |||||
EnvShutdown(); | |||||
} | } | ||||
bool CDBEnv::Open(const fs::path &pathIn, bool retry) { | bool CDBEnv::Open(bool retry) { | ||||
if (fDbEnvInit) { | if (fDbEnvInit) { | ||||
return true; | return true; | ||||
} | } | ||||
boost::this_thread::interruption_point(); | boost::this_thread::interruption_point(); | ||||
strPath = pathIn.string(); | fs::path pathIn = strPath; | ||||
if (!LockDirectory(pathIn, ".walletlock")) { | if (!LockDirectory(pathIn, ".walletlock")) { | ||||
LogPrintf("Cannot obtain a lock on wallet directory %s. Another " | LogPrintf("Cannot obtain a lock on wallet directory %s. Another " | ||||
"instance of bitcoin may be using it.\n", | "instance of bitcoin may be using it.\n", | ||||
strPath); | strPath); | ||||
return false; | return false; | ||||
} | } | ||||
fs::path pathLogDir = pathIn / "database"; | fs::path pathLogDir = pathIn / "database"; | ||||
Show All 36 Lines | if (ret != 0) { | ||||
fs::rename(pathLogDir, pathDatabaseBak); | fs::rename(pathLogDir, pathDatabaseBak); | ||||
LogPrintf("Moved old %s to %s. Retrying.\n", | LogPrintf("Moved old %s to %s. Retrying.\n", | ||||
pathLogDir.string(), pathDatabaseBak.string()); | pathLogDir.string(), pathDatabaseBak.string()); | ||||
} catch (const fs::filesystem_error &) { | } catch (const fs::filesystem_error &) { | ||||
// failure is ok (well, not really, but it's not worse than what | // failure is ok (well, not really, but it's not worse than what | ||||
// we started with) | // we started with) | ||||
} | } | ||||
// try opening it again one more time | // 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 | // if it still fails, it probably means we can't even create the | ||||
// database env | // database env | ||||
return false; | return false; | ||||
} | } | ||||
} else { | } else { | ||||
return false; | return false; | ||||
} | } | ||||
} | } | ||||
▲ Show 20 Lines • Show All 43 Lines • ▼ Show 20 Lines | CDBEnv::VerifyResult CDBEnv::Verify(const std::string &strFile, | ||||
int result = db.verify(strFile.c_str(), nullptr, nullptr, 0); | int result = db.verify(strFile.c_str(), nullptr, nullptr, 0); | ||||
if (result == 0) { | if (result == 0) { | ||||
return VerifyResult::VERIFY_OK; | return VerifyResult::VERIFY_OK; | ||||
} else if (recoverFunc == nullptr) { | } else if (recoverFunc == nullptr) { | ||||
return VerifyResult::RECOVER_FAIL; | return VerifyResult::RECOVER_FAIL; | ||||
} | } | ||||
// Try to recover: | // Try to recover: | ||||
bool fRecovered = (*recoverFunc)(strFile, out_backup_filename); | bool fRecovered = | ||||
(*recoverFunc)(fs::path(strPath) / strFile, out_backup_filename); | |||||
return (fRecovered ? VerifyResult::RECOVER_OK : VerifyResult::RECOVER_FAIL); | return (fRecovered ? VerifyResult::RECOVER_OK : VerifyResult::RECOVER_FAIL); | ||||
} | } | ||||
bool CDB::Recover(const std::string &filename, void *callbackDataIn, | bool CDB::Recover(const fs::path &file_path, void *callbackDataIn, | ||||
bool (*recoverKVcallback)(void *callbackData, | bool (*recoverKVcallback)(void *callbackData, | ||||
CDataStream ssKey, | CDataStream ssKey, | ||||
CDataStream ssValue), | CDataStream ssValue), | ||||
std::string &newFilename) { | std::string &newFilename) { | ||||
std::string filename; | |||||
CDBEnv *env = GetWalletEnv(file_path, filename); | |||||
// Recovery procedure: | // Recovery procedure: | ||||
// Move wallet file to walletfilename.timestamp.bak | // Move wallet file to walletfilename.timestamp.bak | ||||
// Call Salvage with fAggressive=true to get as much data as possible. | // Call Salvage with fAggressive=true to get as much data as possible. | ||||
// Rewrite salvaged data to fresh wallet file. | // Rewrite salvaged data to fresh wallet file. | ||||
// Set -rescan so any missing transactions will be found. | // Set -rescan so any missing transactions will be found. | ||||
int64_t now = GetTime(); | int64_t now = GetTime(); | ||||
newFilename = strprintf("%s.%d.bak", filename, now); | 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); | newFilename.c_str(), DB_AUTO_COMMIT); | ||||
if (result == 0) { | if (result == 0) { | ||||
LogPrintf("Renamed %s to %s\n", filename, newFilename); | LogPrintf("Renamed %s to %s\n", filename, newFilename); | ||||
} else { | } else { | ||||
LogPrintf("Failed to rename %s to %s\n", filename, newFilename); | LogPrintf("Failed to rename %s to %s\n", filename, newFilename); | ||||
return false; | return false; | ||||
} | } | ||||
std::vector<CDBEnv::KeyValPair> salvagedData; | std::vector<CDBEnv::KeyValPair> salvagedData; | ||||
bool fSuccess = bitdb.Salvage(newFilename, true, salvagedData); | bool fSuccess = env->Salvage(newFilename, true, salvagedData); | ||||
if (salvagedData.empty()) { | if (salvagedData.empty()) { | ||||
LogPrintf("Salvage(aggressive) found no records in %s.\n", newFilename); | LogPrintf("Salvage(aggressive) found no records in %s.\n", newFilename); | ||||
return false; | return false; | ||||
} | } | ||||
LogPrintf("Salvage(aggressive) found %u records\n", salvagedData.size()); | LogPrintf("Salvage(aggressive) found %u records\n", salvagedData.size()); | ||||
std::unique_ptr<Db> pdbCopy = std::make_unique<Db>(bitdb.dbenv.get(), 0); | std::unique_ptr<Db> pdbCopy = std::make_unique<Db>(env->dbenv.get(), 0); | ||||
int ret = pdbCopy->open(nullptr, // Txn pointer | int ret = pdbCopy->open(nullptr, // Txn pointer | ||||
filename.c_str(), // Filename | filename.c_str(), // Filename | ||||
"main", // Logical db name | "main", // Logical db name | ||||
DB_BTREE, // Database type | DB_BTREE, // Database type | ||||
DB_CREATE, // Flags | DB_CREATE, // Flags | ||||
0); | 0); | ||||
if (ret > 0) { | if (ret > 0) { | ||||
LogPrintf("Cannot create database file %s\n", filename); | LogPrintf("Cannot create database file %s\n", filename); | ||||
pdbCopy->close(0); | pdbCopy->close(0); | ||||
return false; | return false; | ||||
} | } | ||||
DbTxn *ptxn = bitdb.TxnBegin(); | DbTxn *ptxn = env->TxnBegin(); | ||||
for (CDBEnv::KeyValPair &row : salvagedData) { | for (CDBEnv::KeyValPair &row : salvagedData) { | ||||
if (recoverKVcallback) { | if (recoverKVcallback) { | ||||
CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION); | CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION); | ||||
CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION); | CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION); | ||||
std::string strType, strErr; | std::string strType, strErr; | ||||
if (!(*recoverKVcallback)(callbackDataIn, ssKey, ssValue)) { | if (!(*recoverKVcallback)(callbackDataIn, ssKey, ssValue)) { | ||||
continue; | continue; | ||||
} | } | ||||
} | } | ||||
Dbt datKey(&row.first[0], row.first.size()); | Dbt datKey(&row.first[0], row.first.size()); | ||||
Dbt datValue(&row.second[0], row.second.size()); | Dbt datValue(&row.second[0], row.second.size()); | ||||
int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE); | int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE); | ||||
if (ret2 > 0) { | if (ret2 > 0) { | ||||
fSuccess = false; | fSuccess = false; | ||||
} | } | ||||
} | } | ||||
ptxn->commit(0); | ptxn->commit(0); | ||||
pdbCopy->close(0); | pdbCopy->close(0); | ||||
return fSuccess; | return fSuccess; | ||||
} | } | ||||
bool CDB::VerifyEnvironment(const std::string &walletFile, | bool CDB::VerifyEnvironment(const fs::path &file_path, std::string &errorStr) { | ||||
const fs::path &walletDir, 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 BerkeleyDB version %s\n", DbEnv::version(0, 0, 0)); | ||||
LogPrintf("Using wallet %s\n", walletFile); | LogPrintf("Using wallet %s\n", walletFile); | ||||
// Wallet file must be a plain filename without a directory | // Wallet file must be a plain filename without a directory | ||||
if (walletFile != fs::basename(walletFile) + fs::extension(walletFile)) { | if (walletFile != fs::basename(walletFile) + fs::extension(walletFile)) { | ||||
errorStr = strprintf(_("Wallet %s resides outside wallet directory %s"), | errorStr = strprintf(_("Wallet %s resides outside wallet directory %s"), | ||||
walletFile, walletDir.string()); | walletFile, walletDir.string()); | ||||
return false; | return false; | ||||
} | } | ||||
if (!bitdb.Open(walletDir, true)) { | if (!env->Open(true /* retry */)) { | ||||
errorStr = strprintf( | errorStr = strprintf( | ||||
_("Error initializing wallet database environment %s!"), walletDir); | _("Error initializing wallet database environment %s!"), walletDir); | ||||
return false; | return false; | ||||
} | } | ||||
return true; | return true; | ||||
} | } | ||||
bool CDB::VerifyDatabaseFile(const std::string &walletFile, | bool CDB::VerifyDatabaseFile(const fs::path &file_path, std::string &warningStr, | ||||
const fs::path &walletDir, std::string &warningStr, | |||||
std::string &errorStr, | std::string &errorStr, | ||||
CDBEnv::recoverFunc_type recoverFunc) { | CDBEnv::recoverFunc_type recoverFunc) { | ||||
std::string walletFile; | |||||
CDBEnv *env = GetWalletEnv(file_path, walletFile); | |||||
fs::path walletDir = env->Directory(); | |||||
if (fs::exists(walletDir / walletFile)) { | if (fs::exists(walletDir / walletFile)) { | ||||
std::string backup_filename; | std::string backup_filename; | ||||
CDBEnv::VerifyResult r = | CDBEnv::VerifyResult r = | ||||
bitdb.Verify(walletFile, recoverFunc, backup_filename); | env->Verify(walletFile, recoverFunc, backup_filename); | ||||
if (r == CDBEnv::VerifyResult::RECOVER_OK) { | if (r == CDBEnv::VerifyResult::RECOVER_OK) { | ||||
warningStr = strprintf( | warningStr = strprintf( | ||||
_("Warning: Wallet file corrupt, data salvaged!" | _("Warning: Wallet file corrupt, data salvaged!" | ||||
" Original %s saved as %s in %s; if" | " Original %s saved as %s in %s; if" | ||||
" your balance or transactions are incorrect you should" | " your balance or transactions are incorrect you should" | ||||
" restore from a backup."), | " restore from a backup."), | ||||
walletFile, backup_filename, walletDir); | walletFile, backup_filename, walletDir); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 102 Lines • ▼ Show 20 Lines | CDB::CDB(CWalletDBWrapper &dbw, const char *pszMode, bool fFlushOnCloseIn) | ||||
bool fCreate = strchr(pszMode, 'c') != nullptr; | bool fCreate = strchr(pszMode, 'c') != nullptr; | ||||
unsigned int nFlags = DB_THREAD; | unsigned int nFlags = DB_THREAD; | ||||
if (fCreate) { | if (fCreate) { | ||||
nFlags |= DB_CREATE; | nFlags |= DB_CREATE; | ||||
} | } | ||||
{ | { | ||||
LOCK(env->cs_db); | LOCK(cs_db); | ||||
if (!env->Open(GetWalletDir())) { | if (!env->Open(false /* retry */)) { | ||||
throw std::runtime_error( | throw std::runtime_error( | ||||
"CDB: Failed to open database environment."); | "CDB: Failed to open database environment."); | ||||
} | } | ||||
pdb = env->mapDb[strFilename]; | pdb = env->mapDb[strFilename]; | ||||
if (pdb == nullptr) { | if (pdb == nullptr) { | ||||
std::unique_ptr<Db> pdb_temp = | std::unique_ptr<Db> pdb_temp = | ||||
std::make_unique<Db>(env->dbenv.get(), 0); | std::make_unique<Db>(env->dbenv.get(), 0); | ||||
Show All 17 Lines | if (fCreate) { | ||||
DB_BTREE, // Database type | DB_BTREE, // Database type | ||||
nFlags, // Flags | nFlags, // Flags | ||||
0); | 0); | ||||
if (ret != 0) { | if (ret != 0) { | ||||
throw std::runtime_error(strprintf( | throw std::runtime_error(strprintf( | ||||
"CDB: Error %d, can't open database %s", ret, strFilename)); | "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 &dbenv : g_dbenvs) { | |||||
CheckUniqueFileid(dbenv.second, strFilename, *pdb_temp); | |||||
} | |||||
pdb = pdb_temp.release(); | pdb = pdb_temp.release(); | ||||
env->mapDb[strFilename] = pdb; | env->mapDb[strFilename] = pdb; | ||||
if (fCreate && !Exists(std::string("version"))) { | if (fCreate && !Exists(std::string("version"))) { | ||||
bool fTmp = fReadOnly; | bool fTmp = fReadOnly; | ||||
fReadOnly = false; | fReadOnly = false; | ||||
WriteVersion(CLIENT_VERSION); | WriteVersion(CLIENT_VERSION); | ||||
Show All 35 Lines | void CDB::Close() { | ||||
} | } | ||||
activeTxn = nullptr; | activeTxn = nullptr; | ||||
pdb = nullptr; | pdb = nullptr; | ||||
if (fFlushOnClose) { | if (fFlushOnClose) { | ||||
Flush(); | Flush(); | ||||
} | } | ||||
LOCK(env->cs_db); | LOCK(cs_db); | ||||
--env->mapFileUseCount[strFile]; | --env->mapFileUseCount[strFile]; | ||||
} | } | ||||
void CDBEnv::CloseDb(const std::string &strFile) { | void CDBEnv::CloseDb(const std::string &strFile) { | ||||
LOCK(cs_db); | LOCK(cs_db); | ||||
if (mapDb[strFile] != nullptr) { | if (mapDb[strFile] != nullptr) { | ||||
// Close the database handle | // Close the database handle | ||||
Db *pdb = mapDb[strFile]; | Db *pdb = mapDb[strFile]; | ||||
pdb->close(0); | pdb->close(0); | ||||
delete pdb; | delete pdb; | ||||
mapDb[strFile] = nullptr; | mapDb[strFile] = nullptr; | ||||
} | } | ||||
} | } | ||||
bool CDB::Rewrite(CWalletDBWrapper &dbw, const char *pszSkip) { | bool CDB::Rewrite(CWalletDBWrapper &dbw, const char *pszSkip) { | ||||
if (dbw.IsDummy()) { | if (dbw.IsDummy()) { | ||||
return true; | return true; | ||||
} | } | ||||
CDBEnv *env = dbw.env; | CDBEnv *env = dbw.env; | ||||
const std::string &strFile = dbw.strFile; | const std::string &strFile = dbw.strFile; | ||||
while (true) { | while (true) { | ||||
{ | { | ||||
LOCK(env->cs_db); | LOCK(cs_db); | ||||
if (!env->mapFileUseCount.count(strFile) || | if (!env->mapFileUseCount.count(strFile) || | ||||
env->mapFileUseCount[strFile] == 0) { | env->mapFileUseCount[strFile] == 0) { | ||||
// Flush log data to the dat file | // Flush log data to the dat file | ||||
env->CloseDb(strFile); | env->CloseDb(strFile); | ||||
env->CheckpointLSN(strFile); | env->CheckpointLSN(strFile); | ||||
env->mapFileUseCount.erase(strFile); | env->mapFileUseCount.erase(strFile); | ||||
bool fSuccess = true; | bool fSuccess = true; | ||||
▲ Show 20 Lines • Show All 138 Lines • ▼ Show 20 Lines | |||||
bool CDB::PeriodicFlush(CWalletDBWrapper &dbw) { | bool CDB::PeriodicFlush(CWalletDBWrapper &dbw) { | ||||
if (dbw.IsDummy()) { | if (dbw.IsDummy()) { | ||||
return true; | return true; | ||||
} | } | ||||
bool ret = false; | bool ret = false; | ||||
CDBEnv *env = dbw.env; | CDBEnv *env = dbw.env; | ||||
const std::string &strFile = dbw.strFile; | const std::string &strFile = dbw.strFile; | ||||
TRY_LOCK(bitdb.cs_db, lockDb); | TRY_LOCK(cs_db, lockDb); | ||||
if (lockDb) { | if (lockDb) { | ||||
// Don't do this if any databases are in use | // Don't do this if any databases are in use | ||||
int nRefCount = 0; | int nRefCount = 0; | ||||
std::map<std::string, int>::iterator mit = env->mapFileUseCount.begin(); | std::map<std::string, int>::iterator mit = env->mapFileUseCount.begin(); | ||||
while (mit != env->mapFileUseCount.end()) { | while (mit != env->mapFileUseCount.end()) { | ||||
nRefCount += (*mit).second; | nRefCount += (*mit).second; | ||||
mit++; | mit++; | ||||
} | } | ||||
Show All 26 Lines | |||||
} | } | ||||
bool CWalletDBWrapper::Backup(const std::string &strDest) { | bool CWalletDBWrapper::Backup(const std::string &strDest) { | ||||
if (IsDummy()) { | if (IsDummy()) { | ||||
return false; | return false; | ||||
} | } | ||||
while (true) { | while (true) { | ||||
{ | { | ||||
LOCK(env->cs_db); | LOCK(cs_db); | ||||
if (!env->mapFileUseCount.count(strFile) || | if (!env->mapFileUseCount.count(strFile) || | ||||
env->mapFileUseCount[strFile] == 0) { | env->mapFileUseCount[strFile] == 0) { | ||||
// Flush log data to the dat file | // Flush log data to the dat file | ||||
env->CloseDb(strFile); | env->CloseDb(strFile); | ||||
env->CheckpointLSN(strFile); | env->CheckpointLSN(strFile); | ||||
env->mapFileUseCount.erase(strFile); | env->mapFileUseCount.erase(strFile); | ||||
// Copy wallet file. | // Copy wallet file. | ||||
Show All 34 Lines |