21 #include <boost/thread.hpp> 33 void CheckUniqueFileid(
const CDBEnv& env,
const std::string& filename, Db& db)
37 u_int8_t fileid[DB_FILE_ID_LEN];
38 int ret = db.get_mpf()->get_fileid(fileid);
40 throw std::runtime_error(
strprintf(
"CDB: Can't open database %s (get_fileid failed with %d)", filename, ret));
43 for (
const auto& item : env.
mapDb) {
44 u_int8_t item_fileid[DB_FILE_ID_LEN];
45 if (item.second && item.second->get_mpf()->get_fileid(item_fileid) == 0 &&
46 memcmp(fileid, item_fileid,
sizeof(fileid)) == 0) {
47 const char* item_filename =
nullptr;
48 item.second->get_dbname(&item_filename,
nullptr);
49 throw std::runtime_error(
strprintf(
"CDB: Can't open database %s (duplicates fileid %s from %s)", filename,
50 HexStr(std::begin(item_fileid), std::end(item_fileid)),
51 item_filename ? item_filename :
"(unknown database)"));
69 int ret =
dbenv->close(0);
71 LogPrintf(
"CDBEnv::EnvShutdown: Error %d shutting down database environment: %s\n", ret, DbEnv::strerror(ret));
73 DbEnv((u_int32_t)0).remove(
strPath.c_str(), 0);
79 dbenv =
new DbEnv(DB_CXX_NO_EXCEPTIONS);
106 boost::this_thread::interruption_point();
109 fs::path pathLogDir = pathIn /
"database";
111 fs::path pathErrorFile = pathIn /
"db.log";
112 LogPrintf(
"CDBEnv::Open: LogDir=%s ErrorFile=%s\n", pathLogDir.string(), pathErrorFile.string());
114 unsigned int nEnvFlags = 0;
116 nEnvFlags |= DB_PRIVATE;
118 dbenv->set_lg_dir(pathLogDir.string().c_str());
119 dbenv->set_cachesize(0, 0x100000, 1);
120 dbenv->set_lg_bsize(0x10000);
121 dbenv->set_lg_max(1048576);
122 dbenv->set_lk_max_locks(40000);
123 dbenv->set_lk_max_objects(40000);
125 dbenv->set_flags(DB_AUTO_COMMIT, 1);
126 dbenv->set_flags(DB_TXN_WRITE_NOSYNC, 1);
127 dbenv->log_set_config(DB_LOG_AUTO_REMOVE, 1);
140 return error(
"CDBEnv::Open: Error %d opening database environment: %s\n", ret, DbEnv::strerror(ret));
151 throw std::runtime_error(
"CDBEnv::MakeMock: Already initialized");
153 boost::this_thread::interruption_point();
157 dbenv->set_cachesize(1, 0, 1);
158 dbenv->set_lg_bsize(10485760 * 4);
159 dbenv->set_lg_max(10485760);
160 dbenv->set_lk_max_locks(10000);
161 dbenv->set_lk_max_objects(10000);
162 dbenv->set_flags(DB_AUTO_COMMIT, 1);
163 dbenv->log_set_config(DB_LOG_IN_MEMORY, 1);
164 int ret =
dbenv->open(
nullptr,
174 throw std::runtime_error(
strprintf(
"CDBEnv::MakeMock: Error %d opening database environment.", ret));
186 int result = db.verify(strFile.c_str(),
nullptr,
nullptr, 0);
189 else if (recoverFunc ==
nullptr)
193 bool fRecovered = (*recoverFunc)(strFile, out_backup_filename);
197 bool CDB::Recover(
const std::string& filename,
void *callbackDataIn,
bool (*recoverKVcallback)(
void* callbackData,
CDataStream ssKey,
CDataStream ssValue), std::string& newFilename)
207 newFilename =
strprintf(
"%s.%d.bak", filename, now);
209 int result = bitdb.
dbenv->dbrename(
nullptr, filename.c_str(),
nullptr,
210 newFilename.c_str(), DB_AUTO_COMMIT);
212 LogPrintf(
"Renamed %s to %s\n", filename, newFilename);
215 LogPrintf(
"Failed to rename %s to %s\n", filename, newFilename);
219 std::vector<CDBEnv::KeyValPair> salvagedData;
220 bool fSuccess = bitdb.
Salvage(newFilename,
true, salvagedData);
221 if (salvagedData.empty())
223 LogPrintf(
"Salvage(aggressive) found no records in %s.\n", newFilename);
226 LogPrintf(
"Salvage(aggressive) found %u records\n", salvagedData.size());
228 std::unique_ptr<Db> pdbCopy(
new Db(bitdb.
dbenv, 0));
229 int ret = pdbCopy->open(
nullptr,
236 LogPrintf(
"Cannot create database file %s\n", filename);
244 if (recoverKVcallback)
248 if (!(*recoverKVcallback)(callbackDataIn, ssKey, ssValue))
251 Dbt datKey(&row.first[0], row.first.size());
252 Dbt datValue(&row.second[0], row.second.size());
253 int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE);
266 LogPrintf(
"Using wallet %s\n", walletFile);
269 if (walletFile != fs::basename(walletFile) + fs::extension(walletFile))
271 errorStr =
strprintf(
_(
"Wallet %s resides outside data directory %s"), walletFile, dataDir.string());
275 if (!bitdb.
Open(dataDir))
278 fs::path pathDatabase = dataDir /
"database";
279 fs::path pathDatabaseBak = dataDir /
strprintf(
"database.%d.bak",
GetTime());
281 fs::rename(pathDatabase, pathDatabaseBak);
282 LogPrintf(
"Moved old %s to %s. Retrying.\n", pathDatabase.string(), pathDatabaseBak.string());
283 }
catch (
const fs::filesystem_error&) {
288 if (!bitdb.
Open(dataDir)) {
299 if (fs::exists(dataDir / walletFile))
301 std::string backup_filename;
305 warningStr =
strprintf(
_(
"Warning: Wallet file corrupt, data salvaged!" 306 " Original %s saved as %s in %s; if" 307 " your balance or transactions are incorrect you should" 308 " restore from a backup."),
309 walletFile, backup_filename, dataDir);
313 errorStr =
strprintf(
_(
"%s corrupt, salvage failed"), walletFile);
322 static const char *HEADER_END =
"HEADER=END";
324 static const char *DATA_END =
"DATA=END";
326 bool CDBEnv::Salvage(
const std::string& strFile,
bool fAggressive, std::vector<CDBEnv::KeyValPair>& vResult)
331 u_int32_t flags = DB_SALVAGE;
333 flags |= DB_AGGRESSIVE;
335 std::stringstream strDump;
338 int result = db.verify(strFile.c_str(),
nullptr, &strDump, flags);
339 if (result == DB_VERIFY_BAD) {
340 LogPrintf(
"CDBEnv::Salvage: Database salvage found errors, all data may not be recoverable.\n");
342 LogPrintf(
"CDBEnv::Salvage: Rerun with aggressive mode to ignore errors and continue.\n");
346 if (result != 0 && result != DB_VERIFY_BAD) {
347 LogPrintf(
"CDBEnv::Salvage: Database salvage failed with result %d.\n", result);
360 while (!strDump.eof() && strLine != HEADER_END)
361 getline(strDump, strLine);
363 std::string keyHex, valueHex;
364 while (!strDump.eof() && keyHex != DATA_END) {
365 getline(strDump, keyHex);
366 if (keyHex != DATA_END) {
369 getline(strDump, valueHex);
370 if (valueHex == DATA_END) {
371 LogPrintf(
"CDBEnv::Salvage: WARNING: Number of keys in data does not match number of values.\n");
378 if (keyHex != DATA_END) {
379 LogPrintf(
"CDBEnv::Salvage: WARNING: Unexpected end of file while reading salvage output.\n");
383 return (result == 0);
389 dbenv->txn_checkpoint(0, 0, 0);
392 dbenv->lsn_reset(strFile.c_str(), 0);
398 fReadOnly = (!strchr(pszMode,
'+') && !strchr(pszMode,
'w'));
404 const std::string &strFilename = dbw.
strFile;
406 bool fCreate = strchr(pszMode,
'c') !=
nullptr;
407 unsigned int nFlags = DB_THREAD;
414 throw std::runtime_error(
"CDB: Failed to open database environment.");
417 if (
pdb ==
nullptr) {
419 std::unique_ptr<Db> pdb_temp(
new Db(env->
dbenv, 0));
421 bool fMockDb = env->
IsMock();
423 DbMpoolFile* mpf = pdb_temp->get_mpf();
424 ret = mpf->set_flags(DB_MPOOL_NOFILE, 1);
426 throw std::runtime_error(
strprintf(
"CDB: Failed to configure for no temp file backing for database %s", strFilename));
430 ret = pdb_temp->open(
nullptr,
431 fMockDb ?
nullptr : strFilename.c_str(),
432 fMockDb ? strFilename.c_str() :
"main",
438 throw std::runtime_error(
strprintf(
"CDB: Error %d, can't open database %s", ret, strFilename));
440 CheckUniqueFileid(*env, strFilename, *pdb_temp);
442 pdb = pdb_temp.release();
445 if (fCreate && !
Exists(std::string(
"version"))) {
463 unsigned int nMinutes = 0;
467 env->
dbenv->txn_checkpoint(nMinutes ?
gArgs.
GetArg(
"-dblogsize", DEFAULT_WALLET_DBLOGSIZE) * 1024 : 0, nMinutes, 0);
497 if (mapDb[strFile] !=
nullptr) {
523 bool fSuccess =
true;
524 LogPrintf(
"CDB::Rewrite: Rewriting %s...\n", strFile);
525 std::string strFileRes = strFile +
".rewrite";
528 Db* pdbCopy =
new Db(env->
dbenv, 0);
530 int ret = pdbCopy->open(
nullptr,
537 LogPrintf(
"CDB::Rewrite: Can't create database file %s\n", strFileRes);
547 if (ret1 == DB_NOTFOUND) {
550 }
else if (ret1 != 0) {
556 strncmp(ssKey.
data(), pszSkip,
std::min(ssKey.
size(), strlen(pszSkip))) == 0)
558 if (strncmp(ssKey.
data(),
"\x07version", 8) == 0) {
561 ssValue << CLIENT_VERSION;
563 Dbt datKey(ssKey.
data(), ssKey.
size());
564 Dbt datValue(ssValue.
data(), ssValue.
size());
565 int ret2 = pdbCopy->put(
nullptr, &datKey, &datValue, DB_NOOVERWRITE);
572 if (pdbCopy->close(0))
580 Db dbA(env->
dbenv, 0);
581 if (dbA.remove(strFile.c_str(),
nullptr, 0))
583 Db dbB(env->
dbenv, 0);
584 if (dbB.rename(strFileRes.c_str(),
nullptr, strFile.c_str(), 0))
588 LogPrintf(
"CDB::Rewrite: Failed to rewrite database file %s\n", strFileRes);
602 LogPrint(
BCLog::DB,
"CDBEnv::Flush: Flush(%s)%s\n", fShutdown ?
"true" :
"false", fDbEnvInit ?
"" :
" database not started");
607 std::map<std::string, int>::iterator mi = mapFileUseCount.begin();
608 while (mi != mapFileUseCount.end()) {
609 std::string
strFile = (*mi).first;
610 int nRefCount = (*mi).second;
611 LogPrint(
BCLog::DB,
"CDBEnv::Flush: Flushing %s (refcount = %d)...\n", strFile, nRefCount);
612 if (nRefCount == 0) {
616 dbenv->txn_checkpoint(0, 0, 0);
619 dbenv->lsn_reset(strFile.c_str(), 0);
621 mapFileUseCount.erase(mi++);
625 LogPrint(
BCLog::DB,
"CDBEnv::Flush: Flush(%s)%s took %15dms\n", fShutdown ?
"true" :
"false", fDbEnvInit ?
"" :
" database not started",
GetTimeMillis() - nStart);
628 if (mapFileUseCount.empty()) {
629 dbenv->log_archive(&listp, DB_ARCH_REMOVE);
632 fs::remove_all(fs::path(strPath) /
"database");
651 std::map<std::string, int>::iterator mit = env->
mapFileUseCount.begin();
654 nRefCount += (*mit).second;
660 boost::this_thread::interruption_point();
661 std::map<std::string, int>::iterator mi = env->
mapFileUseCount.find(strFile);
704 fs::path pathDest(strDest);
705 if (fs::is_directory(pathDest))
709 if (fs::equivalent(pathSrc, pathDest)) {
710 LogPrintf(
"cannot backup to wallet source file %s\n", pathDest.string());
714 fs::copy_file(pathSrc, pathDest, fs::copy_option::overwrite_if_exists);
717 }
catch (
const fs::filesystem_error&
e) {
718 LogPrintf(
"error copying %s to %s - %s\n",
strFile, pathDest.string(), e.what());
731 env->
Flush(shutdown);
bool error(const char *fmt, const Args &...args)
std::map< std::string, int > mapFileUseCount
void IncrementUpdateCounter()
DbTxn * TxnBegin(int flags=DB_TXN_WRITE_NOSYNC)
FILE * fopen(const fs::path &p, const char *mode)
void MilliSleep(int64_t n)
#define TRY_LOCK(cs, name)
std::string HexStr(const T itbegin, const T itend, bool fSpaces=false)
void Flush(bool fShutdown)
bool GetBoolArg(const std::string &strArg, bool fDefault)
Return boolean argument or default value.
static bool VerifyDatabaseFile(const std::string &walletFile, const fs::path &dataDir, std::string &warningStr, std::string &errorStr, CDBEnv::recoverFunc_type recoverFunc)
assert(len-trim+(2 *lenIndices)<=WIDTH)
bool Exists(const K &key)
Double ended buffer combining vector and stream-like interfaces.
bool Salvage(const std::string &strFile, bool fAggressive, std::vector< KeyValPair > &vResult)
bool IsDummy()
Return whether this database handle is a dummy for testing.
void Flush(bool shutdown)
Make sure all changes are flushed to disk.
VerifyResult
Verify that database file strFile is OK.
An instance of this class represents one database.
static bool VerifyEnvironment(const std::string &walletFile, const fs::path &dataDir, std::string &errorStr)
std::map< std::string, Db * > mapDb
bool TryCreateDirectories(const fs::path &p)
Ignores exceptions thrown by Boost's create_directories if the requested directory exists...
RAII class that provides access to a Berkeley database.
CDB(CWalletDBWrapper &dbw, const char *pszMode="r+", bool fFlushOnCloseIn=true)
static bool Recover(const std::string &filename, void *callbackDataIn, bool(*recoverKVcallback)(void *callbackData, CDataStream ssKey, CDataStream ssValue), std::string &out_backup_filename)
bool(* recoverFunc_type)(const std::string &strFile, std::string &out_backup_filename)
#define LogPrint(category,...)
bool Backup(const std::string &strDest)
Back up the entire database to a file.
static bool Rewrite(CWalletDBWrapper &dbw, const char *pszSkip=nullptr)
std::string GetArg(const std::string &strArg, const std::string &strDefault)
Return string argument or default value.
void CloseDb(const std::string &strFile)
bool WriteVersion(int nVersion)
int ReadAtCursor(Dbc *pcursor, CDataStream &ssKey, CDataStream &ssValue, bool setRange=false)
bool Open(const fs::path &path)
bool Rewrite(const char *pszSkip=nullptr)
Rewrite the entire database on disk, with the exception of key pszSkip if non-zero.
void CheckpointLSN(const std::string &strFile)
const fs::path & GetDataDir(bool fNetSpecific)
int64_t GetTime()
GetTimeMicros() and GetTimeMillis() both return the system time, but in different units...
std::pair< std::vector< unsigned char >, std::vector< unsigned char > > KeyValPair
Salvage data from a file that Verify says is bad.
static bool PeriodicFlush(CWalletDBWrapper &dbw)
CDBEnv * env
BerkeleyDB specific.
VerifyResult Verify(const std::string &strFile, recoverFunc_type recoverFunc, std::string &out_backup_filename)
std::string _(const char *psz)
Translation function: Call Translate signal on UI interface, which returns a boost::optional result...
std::vector< unsigned char > ParseHex(const char *psz)