aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/db.cpp
diff options
context:
space:
mode:
authorRussell Yanofsky <russ@yanofsky.org>2017-11-13 21:25:46 -0500
committerRussell Yanofsky <russ@yanofsky.org>2018-03-03 10:26:55 -0500
commitd8a99f65e53019becdd8d2631396012bafb29741 (patch)
tree7e873886811beddc9bca0c21d21d3c6fc0d7ede3 /src/wallet/db.cpp
parent6012f1caf744ac9b53383d7d10a8f1b70ca2c0e1 (diff)
Allow wallet files in multiple directories
Remove requirement that two wallet files can only be opened at the same time if they are contained in the same directory. This change mostly consists of updates to function signatures (updating functions to take fs::path arguments, instead of combinations of strings, fs::path, and CDBEnv / CWalletDBWrapper arguments).
Diffstat (limited to 'src/wallet/db.cpp')
-rw-r--r--src/wallet/db.cpp108
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