Changeset View
Changeset View
Standalone View
Standalone View
src/wallet/db.cpp
Show All 29 Lines | |||||
void CDBEnv::EnvShutdown() { | void CDBEnv::EnvShutdown() { | ||||
if (!fDbEnvInit) { | if (!fDbEnvInit) { | ||||
return; | return; | ||||
} | } | ||||
fDbEnvInit = false; | fDbEnvInit = false; | ||||
int ret = dbenv->close(0); | int ret = dbenv->close(0); | ||||
if (ret != 0) { | if (ret != 0) { | ||||
LogPrintf("CDBEnv::EnvShutdown: Error %d shutting down database " | LogPrint(BCLog::DB, | ||||
"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() { | ||||
Show All 23 Lines | bool CDBEnv::Open(const fs::path &pathIn) { | ||||
} | } | ||||
boost::this_thread::interruption_point(); | boost::this_thread::interruption_point(); | ||||
strPath = pathIn.string(); | strPath = pathIn.string(); | ||||
fs::path pathLogDir = pathIn / "database"; | fs::path pathLogDir = pathIn / "database"; | ||||
TryCreateDirectories(pathLogDir); | TryCreateDirectories(pathLogDir); | ||||
fs::path pathErrorFile = pathIn / "db.log"; | fs::path pathErrorFile = pathIn / "db.log"; | ||||
LogPrintf("CDBEnv::Open: LogDir=%s ErrorFile=%s\n", pathLogDir.string(), | LogPrint(BCLog::DB, "CDBEnv::Open: LogDir=%s ErrorFile=%s\n", | ||||
pathErrorFile.string()); | pathLogDir.string(), pathErrorFile.string()); | ||||
unsigned int nEnvFlags = 0; | unsigned int nEnvFlags = 0; | ||||
if (gArgs.GetBoolArg("-privdb", DEFAULT_WALLET_PRIVDB)) { | if (gArgs.GetBoolArg("-privdb", DEFAULT_WALLET_PRIVDB)) { | ||||
nEnvFlags |= DB_PRIVATE; | nEnvFlags |= DB_PRIVATE; | ||||
} | } | ||||
dbenv->set_lg_dir(pathLogDir.string().c_str()); | dbenv->set_lg_dir(pathLogDir.string().c_str()); | ||||
// 1 MiB should be enough for just the wallet | // 1 MiB should be enough for just the wallet | ||||
▲ Show 20 Lines • Show All 83 Lines • ▼ Show 20 Lines | bool CDB::Recover(const std::string &filename, void *callbackDataIn, | ||||
// 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 = bitdb.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); | LogPrint(BCLog::DB, "Renamed %s to %s\n", filename, newFilename); | ||||
} else { | } else { | ||||
LogPrintf("Failed to rename %s to %s\n", filename, newFilename); | LogPrint(BCLog::DB, "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 = bitdb.Salvage(newFilename, true, salvagedData); | ||||
if (salvagedData.empty()) { | if (salvagedData.empty()) { | ||||
LogPrintf("Salvage(aggressive) found no records in %s.\n", newFilename); | LogPrint(BCLog::DB, "Salvage(aggressive) found no records in %s.\n", | ||||
newFilename); | |||||
return false; | return false; | ||||
} | } | ||||
LogPrintf("Salvage(aggressive) found %u records\n", salvagedData.size()); | LogPrint(BCLog::DB, "Salvage(aggressive) found %u records\n", | ||||
salvagedData.size()); | |||||
std::unique_ptr<Db> pdbCopy(new Db(bitdb.dbenv, 0)); | std::unique_ptr<Db> pdbCopy(new Db(bitdb.dbenv, 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); | LogPrint(BCLog::DB, "Cannot create database file %s\n", filename); | ||||
return false; | return false; | ||||
} | } | ||||
DbTxn *ptxn = bitdb.TxnBegin(); | DbTxn *ptxn = bitdb.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); | ||||
Show All 12 Lines | bool CDB::Recover(const std::string &filename, void *callbackDataIn, | ||||
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 std::string &walletFile, | ||||
const fs::path &dataDir, std::string &errorStr) { | const fs::path &dataDir, std::string &errorStr) { | ||||
LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0)); | LogPrint(BCLog::DB, "Using BerkeleyDB version %s\n", | ||||
LogPrintf("Using wallet %s\n", walletFile); | DbEnv::version(0, 0, 0)); | ||||
LogPrint(BCLog::DB, "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 data directory %s"), | errorStr = strprintf(_("Wallet %s resides outside data directory %s"), | ||||
walletFile, dataDir.string()); | walletFile, dataDir.string()); | ||||
return false; | return false; | ||||
} | } | ||||
if (!bitdb.Open(dataDir)) { | if (!bitdb.Open(dataDir)) { | ||||
// try moving the database env out of the way | // try moving the database env out of the way | ||||
fs::path pathDatabase = dataDir / "database"; | fs::path pathDatabase = dataDir / "database"; | ||||
fs::path pathDatabaseBak = | fs::path pathDatabaseBak = | ||||
dataDir / strprintf("database.%d.bak", GetTime()); | dataDir / strprintf("database.%d.bak", GetTime()); | ||||
try { | try { | ||||
fs::rename(pathDatabase, pathDatabaseBak); | fs::rename(pathDatabase, pathDatabaseBak); | ||||
LogPrintf("Moved old %s to %s. Retrying.\n", pathDatabase.string(), | LogPrint(BCLog::DB, "Moved old %s to %s. Retrying.\n", | ||||
pathDatabaseBak.string()); | pathDatabase.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 we | // failure is ok (well, not really, but it's not worse than what we | ||||
// started with) | // started with) | ||||
} | } | ||||
// try again | // try again | ||||
if (!bitdb.Open(dataDir)) { | if (!bitdb.Open(dataDir)) { | ||||
// 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 | ||||
▲ Show 20 Lines • Show All 47 Lines • ▼ Show 20 Lines | if (fAggressive) { | ||||
flags |= DB_AGGRESSIVE; | flags |= DB_AGGRESSIVE; | ||||
} | } | ||||
std::stringstream strDump; | std::stringstream strDump; | ||||
Db db(dbenv, 0); | Db db(dbenv, 0); | ||||
int result = db.verify(strFile.c_str(), nullptr, &strDump, flags); | int result = db.verify(strFile.c_str(), nullptr, &strDump, flags); | ||||
if (result == DB_VERIFY_BAD) { | if (result == DB_VERIFY_BAD) { | ||||
LogPrintf("CDBEnv::Salvage: Database salvage found errors, all data " | LogPrint(BCLog::DB, | ||||
"CDBEnv::Salvage: Database salvage found errors, all data " | |||||
"may not be recoverable.\n"); | "may not be recoverable.\n"); | ||||
if (!fAggressive) { | if (!fAggressive) { | ||||
LogPrintf("CDBEnv::Salvage: Rerun with aggressive mode to ignore " | LogPrint(BCLog::DB, | ||||
"CDBEnv::Salvage: Rerun with aggressive mode to ignore " | |||||
"errors and continue.\n"); | "errors and continue.\n"); | ||||
return false; | return false; | ||||
} | } | ||||
} | } | ||||
if (result != 0 && result != DB_VERIFY_BAD) { | if (result != 0 && result != DB_VERIFY_BAD) { | ||||
LogPrintf("CDBEnv::Salvage: Database salvage failed with result %d.\n", | LogPrint(BCLog::DB, | ||||
"CDBEnv::Salvage: Database salvage failed with result %d.\n", | |||||
result); | result); | ||||
return false; | return false; | ||||
} | } | ||||
// Format of bdb dump is ascii lines: | // Format of bdb dump is ascii lines: | ||||
// header lines... | // header lines... | ||||
// HEADER=END | // HEADER=END | ||||
// hexadecimal key | // hexadecimal key | ||||
// hexadecimal value | // hexadecimal value | ||||
Show All 10 Lines | bool CDBEnv::Salvage(const std::string &strFile, bool fAggressive, | ||||
while (!strDump.eof() && keyHex != DATA_END) { | while (!strDump.eof() && keyHex != DATA_END) { | ||||
getline(strDump, keyHex); | getline(strDump, keyHex); | ||||
if (keyHex != DATA_END) { | if (keyHex != DATA_END) { | ||||
if (strDump.eof()) { | if (strDump.eof()) { | ||||
break; | break; | ||||
} | } | ||||
getline(strDump, valueHex); | getline(strDump, valueHex); | ||||
if (valueHex == DATA_END) { | if (valueHex == DATA_END) { | ||||
LogPrintf("CDBEnv::Salvage: WARNING: Number of keys in data " | LogPrint(BCLog::DB, | ||||
"CDBEnv::Salvage: WARNING: Number of keys in data " | |||||
"does not match number of values.\n"); | "does not match number of values.\n"); | ||||
break; | break; | ||||
} | } | ||||
vResult.push_back(make_pair(ParseHex(keyHex), ParseHex(valueHex))); | vResult.push_back(make_pair(ParseHex(keyHex), ParseHex(valueHex))); | ||||
} | } | ||||
} | } | ||||
if (keyHex != DATA_END) { | if (keyHex != DATA_END) { | ||||
LogPrintf("CDBEnv::Salvage: WARNING: Unexpected end of file while " | LogPrint(BCLog::DB, | ||||
"CDBEnv::Salvage: WARNING: Unexpected end of file while " | |||||
"reading salvage output.\n"); | "reading salvage output.\n"); | ||||
return false; | return false; | ||||
} | } | ||||
return (result == 0); | return (result == 0); | ||||
} | } | ||||
void CDBEnv::CheckpointLSN(const std::string &strFile) { | void CDBEnv::CheckpointLSN(const std::string &strFile) { | ||||
dbenv->txn_checkpoint(0, 0, 0); | dbenv->txn_checkpoint(0, 0, 0); | ||||
▲ Show 20 Lines • Show All 137 Lines • ▼ Show 20 Lines | while (true) { | ||||
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; | ||||
LogPrintf("CDB::Rewrite: Rewriting %s...\n", strFile); | LogPrint(BCLog::DB, "CDB::Rewrite: Rewriting %s...\n", strFile); | ||||
std::string strFileRes = strFile + ".rewrite"; | std::string strFileRes = strFile + ".rewrite"; | ||||
{ | { | ||||
// surround usage of db with extra {} | // surround usage of db with extra {} | ||||
CDB db(dbw, "r"); | CDB db(dbw, "r"); | ||||
Db *pdbCopy = new Db(env->dbenv, 0); | Db *pdbCopy = new Db(env->dbenv, 0); | ||||
int ret = pdbCopy->open(nullptr, // Txn pointer | int ret = pdbCopy->open(nullptr, // Txn pointer | ||||
strFileRes.c_str(), // Filename | strFileRes.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( | LogPrint( | ||||
BCLog::DB, | |||||
"CDB::Rewrite: Can't create database file %s\n", | "CDB::Rewrite: Can't create database file %s\n", | ||||
strFileRes); | strFileRes); | ||||
fSuccess = false; | fSuccess = false; | ||||
} | } | ||||
Dbc *pcursor = db.GetCursor(); | Dbc *pcursor = db.GetCursor(); | ||||
if (pcursor) | if (pcursor) | ||||
while (fSuccess) { | while (fSuccess) { | ||||
▲ Show 20 Lines • Show All 44 Lines • ▼ Show 20 Lines | while (true) { | ||||
} | } | ||||
Db dbB(env->dbenv, 0); | Db dbB(env->dbenv, 0); | ||||
if (dbB.rename(strFileRes.c_str(), nullptr, strFile.c_str(), | if (dbB.rename(strFileRes.c_str(), nullptr, strFile.c_str(), | ||||
0)) { | 0)) { | ||||
fSuccess = false; | fSuccess = false; | ||||
} | } | ||||
} | } | ||||
if (!fSuccess) { | if (!fSuccess) { | ||||
LogPrintf( | LogPrint( | ||||
BCLog::DB, | |||||
"CDB::Rewrite: Failed to rewrite database file %s\n", | "CDB::Rewrite: Failed to rewrite database file %s\n", | ||||
strFileRes); | strFileRes); | ||||
} | } | ||||
return fSuccess; | return fSuccess; | ||||
} | } | ||||
} | } | ||||
MilliSleep(100); | MilliSleep(100); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 112 Lines • ▼ Show 20 Lines | while (true) { | ||||
fs::path pathSrc = GetDataDir() / strFile; | fs::path pathSrc = GetDataDir() / strFile; | ||||
fs::path pathDest(strDest); | fs::path pathDest(strDest); | ||||
if (fs::is_directory(pathDest)) { | if (fs::is_directory(pathDest)) { | ||||
pathDest /= strFile; | pathDest /= strFile; | ||||
} | } | ||||
try { | try { | ||||
if (fs::equivalent(pathSrc, pathDest)) { | if (fs::equivalent(pathSrc, pathDest)) { | ||||
LogPrintf("cannot backup to wallet source file %s\n", | LogPrint(BCLog::DB, | ||||
"cannot backup to wallet source file %s\n", | |||||
pathDest.string()); | pathDest.string()); | ||||
return false; | return false; | ||||
} | } | ||||
fs::copy_file(pathSrc, pathDest, | fs::copy_file(pathSrc, pathDest, | ||||
fs::copy_option::overwrite_if_exists); | fs::copy_option::overwrite_if_exists); | ||||
LogPrintf("copied %s to %s\n", strFile, pathDest.string()); | LogPrint(BCLog::DB, "copied %s to %s\n", strFile, | ||||
pathDest.string()); | |||||
return true; | return true; | ||||
} catch (const fs::filesystem_error &e) { | } catch (const fs::filesystem_error &e) { | ||||
LogPrintf("error copying %s to %s - %s\n", strFile, | LogPrint(BCLog::DB, "error copying %s to %s - %s\n", | ||||
pathDest.string(), e.what()); | strFile, pathDest.string(), e.what()); | ||||
return false; | return false; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
MilliSleep(100); | MilliSleep(100); | ||||
} | } | ||||
return false; | return false; | ||||
} | } | ||||
void CWalletDBWrapper::Flush(bool shutdown) { | void CWalletDBWrapper::Flush(bool shutdown) { | ||||
if (!IsDummy()) { | if (!IsDummy()) { | ||||
env->Flush(shutdown); | env->Flush(shutdown); | ||||
} | } | ||||
} | } |