20 #include <boost/thread.hpp> 37 int ret = db.get_mpf()->get_fileid(fileid.
value);
39 throw std::runtime_error(
strprintf(
"BerkeleyBatch: Can't open database %s (get_fileid failed with %d)", filename,
ret));
43 if (fileid == item.second && &fileid != &item.second) {
44 throw std::runtime_error(
strprintf(
"BerkeleyBatch: Can't open database %s (duplicates fileid %s from %s)", filename,
45 HexStr(std::begin(item.second.value),
std::end(item.second.value)), item.first));
51 std::map<std::string, BerkeleyEnvironment> g_dbenvs
GUARDED_BY(cs_db);
61 fs::path env_directory;
62 if (fs::is_regular_file(wallet_path)) {
66 env_directory = wallet_path.parent_path();
67 database_filename = wallet_path.filename().string();
71 env_directory = wallet_path;
72 database_filename =
"wallet.dat";
79 return &g_dbenvs.emplace(std::piecewise_construct, std::forward_as_tuple(env_directory.string()), std::forward_as_tuple(env_directory)).first->second;
93 for (
auto& db :
mapDb) {
105 LogPrintf(
"BerkeleyEnvironment::Close: Error %d closing database environment: %s\n",
ret, DbEnv::strerror(
ret));
107 DbEnv((u_int32_t)0).remove(
strPath.c_str(), 0);
112 dbenv.reset(
new DbEnv(DB_CXX_NO_EXCEPTIONS));
132 boost::this_thread::interruption_point();
137 LogPrintf(
"Cannot obtain a lock on wallet directory %s. Another instance of bitcoin may be using it.\n",
strPath);
141 fs::path pathLogDir = pathIn /
"database";
143 fs::path pathErrorFile = pathIn /
"db.log";
144 LogPrintf(
"BerkeleyEnvironment::Open: LogDir=%s ErrorFile=%s\n", pathLogDir.string(), pathErrorFile.string());
146 unsigned int nEnvFlags = 0;
148 nEnvFlags |= DB_PRIVATE;
150 dbenv->set_lg_dir(pathLogDir.string().c_str());
151 dbenv->set_cachesize(0, 0x100000, 1);
152 dbenv->set_lg_bsize(0x10000);
153 dbenv->set_lg_max(1048576);
154 dbenv->set_lk_max_locks(40000);
155 dbenv->set_lk_max_objects(40000);
157 dbenv->set_flags(DB_AUTO_COMMIT, 1);
158 dbenv->set_flags(DB_TXN_WRITE_NOSYNC, 1);
159 dbenv->log_set_config(DB_LOG_AUTO_REMOVE, 1);
171 LogPrintf(
"BerkeleyEnvironment::Open: Error %d opening database environment: %s\n",
ret, DbEnv::strerror(
ret));
172 int ret2 =
dbenv->close(0);
174 LogPrintf(
"BerkeleyEnvironment::Open: Error %d closing failed database environment: %s\n", ret2, DbEnv::strerror(ret2));
179 fs::path pathDatabaseBak = pathIn /
strprintf(
"database.%d.bak",
GetTime());
181 fs::rename(pathLogDir, pathDatabaseBak);
182 LogPrintf(
"Moved old %s to %s. Retrying.\n", pathLogDir.string(), pathDatabaseBak.string());
183 }
catch (
const fs::filesystem_error&) {
204 throw std::runtime_error(
"BerkeleyEnvironment::MakeMock: Already initialized");
206 boost::this_thread::interruption_point();
208 LogPrint(
BCLog::DB,
"BerkeleyEnvironment::MakeMock\n");
210 dbenv->set_cachesize(1, 0, 1);
211 dbenv->set_lg_bsize(10485760 * 4);
212 dbenv->set_lg_max(10485760);
213 dbenv->set_lk_max_locks(10000);
214 dbenv->set_lk_max_objects(10000);
215 dbenv->set_flags(DB_AUTO_COMMIT, 1);
216 dbenv->log_set_config(DB_LOG_IN_MEMORY, 1);
227 throw std::runtime_error(
strprintf(
"BerkeleyEnvironment::MakeMock: Error %d opening database environment.",
ret));
238 Db db(
dbenv.get(), 0);
239 int result = db.verify(strFile.c_str(),
nullptr,
nullptr, 0);
242 else if (recoverFunc ==
nullptr)
246 bool fRecovered = (*recoverFunc)(fs::path(
strPath) / strFile, out_backup_filename);
252 std::string filename;
263 newFilename =
strprintf(
"%s.%d.bak", filename, now);
265 int result =
env->
dbenv->dbrename(
nullptr, filename.c_str(),
nullptr,
266 newFilename.c_str(), DB_AUTO_COMMIT);
268 LogPrintf(
"Renamed %s to %s\n", filename, newFilename);
271 LogPrintf(
"Failed to rename %s to %s\n", filename, newFilename);
275 std::vector<BerkeleyEnvironment::KeyValPair> salvagedData;
276 bool fSuccess =
env->
Salvage(newFilename,
true, salvagedData);
277 if (salvagedData.empty())
279 LogPrintf(
"Salvage(aggressive) found no records in %s.\n", newFilename);
282 LogPrintf(
"Salvage(aggressive) found %u records\n", salvagedData.size());
284 std::unique_ptr<Db> pdbCopy = MakeUnique<Db>(
env->
dbenv.get(), 0);
285 int ret = pdbCopy->open(
nullptr,
292 LogPrintf(
"Cannot create database file %s\n", filename);
300 if (recoverKVcallback)
304 if (!(*recoverKVcallback)(callbackDataIn, ssKey, ssValue))
307 Dbt datKey(&row.first[0], row.first.size());
308 Dbt datValue(&row.second[0], row.second.size());
309 int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE);
321 std::string walletFile;
325 LogPrintf(
"Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0));
326 LogPrintf(
"Using wallet %s\n", walletFile);
329 if (walletFile != fs::basename(walletFile) + fs::extension(walletFile))
331 errorStr =
strprintf(
_(
"Wallet %s resides outside wallet directory %s"), walletFile, walletDir.string());
336 errorStr =
strprintf(
_(
"Error initializing wallet database environment %s!"), walletDir);
345 std::string walletFile;
349 if (fs::exists(walletDir / walletFile))
351 std::string backup_filename;
355 warningStr =
strprintf(
_(
"Warning: Wallet file corrupt, data salvaged!" 356 " Original %s saved as %s in %s; if" 357 " your balance or transactions are incorrect you should" 358 " restore from a backup."),
359 walletFile, backup_filename, walletDir);
363 errorStr =
strprintf(
_(
"%s corrupt, salvage failed"), walletFile);
372 static const char *HEADER_END =
"HEADER=END";
374 static const char *DATA_END =
"DATA=END";
381 u_int32_t
flags = DB_SALVAGE;
383 flags |= DB_AGGRESSIVE;
385 std::stringstream strDump;
387 Db db(
dbenv.get(), 0);
388 int result = db.verify(strFile.c_str(),
nullptr, &strDump,
flags);
389 if (result == DB_VERIFY_BAD) {
390 LogPrintf(
"BerkeleyEnvironment::Salvage: Database salvage found errors, all data may not be recoverable.\n");
392 LogPrintf(
"BerkeleyEnvironment::Salvage: Rerun with aggressive mode to ignore errors and continue.\n");
396 if (result != 0 && result != DB_VERIFY_BAD) {
397 LogPrintf(
"BerkeleyEnvironment::Salvage: Database salvage failed with result %d.\n", result);
410 while (!strDump.eof() && strLine != HEADER_END)
411 getline(strDump, strLine);
413 std::string keyHex, valueHex;
414 while (!strDump.eof() && keyHex != DATA_END) {
415 getline(strDump, keyHex);
416 if (keyHex != DATA_END) {
419 getline(strDump, valueHex);
420 if (valueHex == DATA_END) {
421 LogPrintf(
"BerkeleyEnvironment::Salvage: WARNING: Number of keys in data does not match number of values.\n");
428 if (keyHex != DATA_END) {
429 LogPrintf(
"BerkeleyEnvironment::Salvage: WARNING: Unexpected end of file while reading salvage output.\n");
433 return (result == 0);
439 dbenv->txn_checkpoint(0, 0, 0);
442 dbenv->lsn_reset(strFile.c_str(), 0);
448 fReadOnly = (!strchr(pszMode,
'+') && !strchr(pszMode,
'w'));
454 const std::string &strFilename = database.
strFile;
456 bool fCreate = strchr(pszMode,
'c') !=
nullptr;
457 unsigned int nFlags = DB_THREAD;
464 throw std::runtime_error(
"BerkeleyBatch: Failed to open database environment.");
467 if (
pdb ==
nullptr) {
469 std::unique_ptr<Db> pdb_temp = MakeUnique<Db>(
env->
dbenv.get(), 0);
473 DbMpoolFile* mpf = pdb_temp->get_mpf();
474 ret = mpf->set_flags(DB_MPOOL_NOFILE, 1);
476 throw std::runtime_error(
strprintf(
"BerkeleyBatch: Failed to configure for no temp file backing for database %s", strFilename));
480 ret = pdb_temp->open(
nullptr,
481 fMockDb ?
nullptr : strFilename.c_str(),
482 fMockDb ? strFilename.c_str() :
"main",
488 throw std::runtime_error(
strprintf(
"BerkeleyBatch: Error %d, can't open database %s",
ret, strFilename));
506 for (
const auto&
env : g_dbenvs) {
507 CheckUniqueFileid(
env.second, strFilename, *pdb_temp, this->env->
m_fileids[strFilename]);
510 pdb = pdb_temp.release();
513 if (fCreate && !
Exists(std::string(
"version"))) {
531 unsigned int nMinutes = 0;
535 env->
dbenv->txn_checkpoint(nMinutes ?
gArgs.
GetArg(
"-dblogsize", DEFAULT_WALLET_DBLOGSIZE) * 1024 : 0, nMinutes, 0);
566 if (
mapDb[strFile] !=
nullptr) {
568 Db* pdb =
mapDb[strFile];
571 mapDb[strFile] =
nullptr;
580 std::unique_lock<CCriticalSection> lock(cs_db);
583 if (count.second > 0)
return false;
588 std::vector<std::string> filenames;
589 for (
auto it :
mapDb) {
590 filenames.push_back(it.first);
593 for (
const std::string& filename : filenames) {
618 bool fSuccess =
true;
619 LogPrintf(
"BerkeleyBatch::Rewrite: Rewriting %s...\n",
strFile);
620 std::string strFileRes =
strFile +
".rewrite";
623 std::unique_ptr<Db> pdbCopy = MakeUnique<Db>(
env->
dbenv.get(), 0);
625 int ret = pdbCopy->open(
nullptr,
632 LogPrintf(
"BerkeleyBatch::Rewrite: Can't create database file %s\n", strFileRes);
642 if (ret1 == DB_NOTFOUND) {
645 }
else if (ret1 != 0) {
651 strncmp(ssKey.
data(), pszSkip, std::min(ssKey.
size(), strlen(pszSkip))) == 0)
653 if (strncmp(ssKey.
data(),
"\x07version", 8) == 0) {
656 ssValue << CLIENT_VERSION;
658 Dbt datKey(ssKey.
data(), ssKey.
size());
659 Dbt datValue(ssValue.
data(), ssValue.
size());
660 int ret2 = pdbCopy->put(
nullptr, &datKey, &datValue, DB_NOOVERWRITE);
667 if (pdbCopy->close(0))
675 if (dbA.remove(
strFile.c_str(),
nullptr, 0))
678 if (dbB.rename(strFileRes.c_str(),
nullptr,
strFile.c_str(), 0))
682 LogPrintf(
"BerkeleyBatch::Rewrite: Failed to rewrite database file %s\n", strFileRes);
695 LogPrint(
BCLog::DB,
"BerkeleyEnvironment::Flush: Flush(%s)%s\n", fShutdown ?
"true" :
"false",
fDbEnvInit ?
"" :
" database not started");
702 std::string strFile = (*mi).first;
703 int nRefCount = (*mi).second;
704 LogPrint(
BCLog::DB,
"BerkeleyEnvironment::Flush: Flushing %s (refcount = %d)...\n", strFile, nRefCount);
705 if (nRefCount == 0) {
708 LogPrint(
BCLog::DB,
"BerkeleyEnvironment::Flush: %s checkpoint\n", strFile);
709 dbenv->txn_checkpoint(0, 0, 0);
710 LogPrint(
BCLog::DB,
"BerkeleyEnvironment::Flush: %s detach\n", strFile);
712 dbenv->lsn_reset(strFile.c_str(), 0);
713 LogPrint(
BCLog::DB,
"BerkeleyEnvironment::Flush: %s closed\n", strFile);
718 LogPrint(
BCLog::DB,
"BerkeleyEnvironment::Flush: Flush(%s)%s took %15dms\n", fShutdown ?
"true" :
"false",
fDbEnvInit ?
"" :
" database not started",
GetTimeMillis() - nStart);
722 dbenv->log_archive(&listp, DB_ARCH_REMOVE);
725 fs::remove_all(fs::path(
strPath) /
"database");
748 nRefCount += (*mit).second;
754 boost::this_thread::interruption_point();
798 fs::path pathDest(strDest);
799 if (fs::is_directory(pathDest))
803 if (fs::equivalent(pathSrc, pathDest)) {
804 LogPrintf(
"cannot backup to wallet source file %s\n", pathDest.string());
808 fs::copy_file(pathSrc, pathDest, fs::copy_option::overwrite_if_exists);
809 LogPrintf(
"copied %s to %s\n",
strFile, pathDest.string());
811 }
catch (
const fs::filesystem_error& e) {
void Flush(bool shutdown)
Make sure all changes are flushed to disk.
FILE * fopen(const fs::path &p, const char *mode)
void MilliSleep(int64_t n)
#define TRY_LOCK(cs, name)
VerifyResult
Verify that database file strFile is OK.
std::unordered_map< std::string, WalletDatabaseFileId > m_fileids
UniValue ret(UniValue::VARR)
std::string HexStr(const T itbegin, const T itend, bool fSpaces=false)
bool Exists(const K &key)
An instance of this class represents one database.
Double ended buffer combining vector and stream-like interfaces.
bool Backup(const std::string &strDest)
Back up the entire database to a file.
bool GetBoolArg(const std::string &strArg, bool fDefault) const
Return boolean argument or default value.
fs::path Directory() const
static bool PeriodicFlush(BerkeleyDatabase &database)
std::map< std::string, Db * > mapDb
bool LockDirectory(const fs::path &directory, const std::string lockfile_name, bool probe_only)
DbTxn * TxnBegin(int flags=DB_TXN_WRITE_NOSYNC)
std::map< std::string, int > mapFileUseCount
std::unique_ptr< DbEnv > dbenv
void CheckpointLSN(const std::string &strFile)
std::pair< std::vector< unsigned char >, std::vector< unsigned char > > KeyValPair
Salvage data from a file that Verify says is bad.
bool Rewrite(const char *pszSkip=nullptr)
Rewrite the entire database on disk, with the exception of key pszSkip if non-zero.
BerkeleyEnvironment * GetWalletEnv(const fs::path &wallet_path, std::string &database_filename)
Get BerkeleyEnvironment and database filename given a wallet path.
bool TryCreateDirectories(const fs::path &p)
Ignores exceptions thrown by Boost's create_directories if the requested directory exists...
bool WriteVersion(int nVersion)
RAII class that provides access to a Berkeley database.
bool operator==(const WalletDatabaseFileId &rhs) const
static bool VerifyEnvironment(const fs::path &file_path, std::string &errorStr)
BerkeleyEnvironment(const fs::path &env_directory)
static bool Recover(const fs::path &file_path, void *callbackDataIn, bool(*recoverKVcallback)(void *callbackData, CDataStream ssKey, CDataStream ssValue), std::string &out_backup_filename)
bool Salvage(const std::string &strFile, bool fAggressive, std::vector< KeyValPair > &vResult)
static bool Rewrite(BerkeleyDatabase &database, const char *pszSkip=nullptr)
std::string get_filesystem_error_message(const fs::filesystem_error &e)
void IncrementUpdateCounter()
BerkeleyBatch(BerkeleyDatabase &database, const char *pszMode="r+", bool fFlushOnCloseIn=true)
int ReadAtCursor(Dbc *pcursor, CDataStream &ssKey, CDataStream &ssValue, bool setRange=false)
std::string GetArg(const std::string &strArg, const std::string &strDefault) const
Return string argument or default value.
BerkeleyEnvironment * env
u_int8_t value[DB_FILE_ID_LEN]
void Flush(bool fShutdown)
#define AssertLockNotHeld(cs)
std::atomic< unsigned int > nUpdateCounter
bool(* recoverFunc_type)(const fs::path &file_path, std::string &out_backup_filename)
VerifyResult Verify(const std::string &strFile, recoverFunc_type recoverFunc, std::string &out_backup_filename)
static bool VerifyDatabaseFile(const fs::path &file_path, std::string &warningStr, std::string &errorStr, BerkeleyEnvironment::recoverFunc_type recoverFunc)
int64_t GetTime()
GetTimeMicros() and GetTimeMillis() both return the system time, but in different units...
void CloseDb(const std::string &strFile)
BerkeleyEnvironment * env
BerkeleyDB specific.
std::string _(const char *psz)
Translation function.
std::vector< unsigned char > ParseHex(const char *psz)
std::condition_variable_any m_db_in_use
bool IsDummy()
Return whether this database handle is a dummy for testing.