23 #include <boost/thread.hpp>
41 int ret = db.get_mpf()->get_fileid(fileid.
value);
43 throw std::runtime_error(
strprintf(
"BerkeleyBatch: Can't open database %s (get_fileid failed with %d)", filename, ret));
47 if (fileid == item.second && &fileid != &item.second) {
48 throw std::runtime_error(
strprintf(
"BerkeleyBatch: Can't open database %s (duplicates fileid %s from %s)", filename,
49 HexStr(item.second.value), item.first));
55 std::map<std::string, BerkeleyEnvironment> g_dbenvs;
65 fs::path env_directory;
66 if (fs::is_regular_file(wallet_path)) {
70 env_directory = wallet_path.parent_path();
71 database_filename = wallet_path.filename().string();
75 env_directory = wallet_path;
76 database_filename =
"wallet.dat";
83 return &g_dbenvs.emplace(std::piecewise_construct, std::forward_as_tuple(env_directory.string()), std::forward_as_tuple(env_directory)).first->second;
97 for (
auto& db :
mapDb) {
107 int ret =
dbenv->close(0);
109 LogPrintf(
"%s: Error %d closing database environment: %s\n", __func__, ret, DbEnv::strerror(ret));
111 DbEnv((u_int32_t)0).remove(
strPath.c_str(), 0);
116 dbenv.reset(
new DbEnv(DB_CXX_NO_EXCEPTIONS));
136 boost::this_thread::interruption_point();
141 LogPrintf(
"Cannot obtain a lock on wallet directory %s. Another instance of PIVX may be using it.\n",
strPath);
145 fs::path pathLogDir = pathIn /
"database";
147 fs::path pathErrorFile = pathIn /
"db.log";
148 LogPrintf(
"BerkeleyEnvironment::Open: LogDir=%s ErrorFile=%s\n", pathLogDir.string(), pathErrorFile.string());
150 unsigned int nEnvFlags = 0;
152 nEnvFlags |= DB_PRIVATE;
154 dbenv->set_lg_dir(pathLogDir.string().c_str());
155 dbenv->set_cachesize(0, 0x100000, 1);
156 dbenv->set_lg_bsize(0x10000);
157 dbenv->set_lg_max(1048576);
158 dbenv->set_lk_max_locks(40000);
159 dbenv->set_lk_max_objects(40000);
161 dbenv->set_flags(DB_AUTO_COMMIT, 1);
162 dbenv->set_flags(DB_TXN_WRITE_NOSYNC, 1);
163 dbenv->log_set_config(DB_LOG_AUTO_REMOVE, 1);
175 LogPrintf(
"%s: Error %d opening database environment: %s\n", __func__, ret, DbEnv::strerror(ret));
176 int ret2 =
dbenv->close(0);
178 LogPrintf(
"%s: Error %d closing failed database environment: %s\n", __func__, ret2, DbEnv::strerror(ret2));
183 fs::path pathDatabaseBak = pathIn /
strprintf(
"database.%d.bak",
GetTime());
185 fs::rename(pathLogDir, pathDatabaseBak);
186 LogPrintf(
"Moved old %s to %s. Retrying.\n", pathLogDir.string(), pathDatabaseBak.string());
187 }
catch (
const fs::filesystem_error&) {
208 throw std::runtime_error(
"BerkeleyEnvironment::MakeMock : Already initialized");
210 boost::this_thread::interruption_point();
214 dbenv->set_cachesize(1, 0, 1);
215 dbenv->set_lg_bsize(10485760 * 4);
216 dbenv->set_lg_max(10485760);
217 dbenv->set_lk_max_locks(10000);
218 dbenv->set_lk_max_objects(10000);
219 dbenv->set_flags(DB_AUTO_COMMIT, 1);
220 dbenv->log_set_config(DB_LOG_IN_MEMORY, 1);
221 int ret =
dbenv->open(
nullptr,
231 throw std::runtime_error(
strprintf(
"BerkeleyEnvironment::MakeMock : Error %d opening database environment.", ret));
242 Db db(
dbenv.get(), 0);
243 int result = db.verify(strFile.c_str(),
nullptr,
nullptr, 0);
246 else if (recoverFunc ==
nullptr)
250 bool fRecovered = (*recoverFunc)(fs::path(
strPath) / strFile, out_backup_filename);
256 std::string filename;
267 newFilename =
strprintf(
"%s.%d.bak", filename, now);
269 int result =
env->
dbenv->dbrename(
nullptr, filename.c_str(),
nullptr,
270 newFilename.c_str(), DB_AUTO_COMMIT);
272 LogPrintf(
"Renamed %s to %s\n", filename, newFilename);
274 LogPrintf(
"Failed to rename %s to %s\n", filename, newFilename);
278 std::vector<BerkeleyEnvironment::KeyValPair> salvagedData;
279 bool fSuccess =
env->
Salvage(newFilename,
true, salvagedData);
280 if (salvagedData.empty())
282 LogPrintf(
"Salvage(aggressive) found no records in %s.\n", newFilename);
285 LogPrintf(
"Salvage(aggressive) found %u records\n", salvagedData.size());
287 std::unique_ptr<Db> pdbCopy = std::make_unique<Db>(
env->
dbenv.get(), 0);
288 int ret = pdbCopy->open(
nullptr,
295 LogPrintf(
"Cannot create database file %s\n", filename);
303 if (recoverKVcallback)
307 if (!(*recoverKVcallback)(callbackDataIn, ssKey, ssValue))
310 Dbt datKey(&row.first[0], row.first.size());
311 Dbt datValue(&row.second[0], row.second.size());
312 int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE);
324 std::string walletFile;
328 LogPrintf(
"Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0));
329 LogPrintf(
"Using wallet %s\n", walletFile);
332 fs::path wallet_file_path(walletFile);
333 if (walletFile != wallet_file_path.filename().string())
334 return UIError(
strprintf(
_(
"Wallet %s resides outside data directory %s"), walletFile, walletDir.string()));
337 errorStr =
strprintf(
_(
"Error initializing wallet database environment %s!"), walletDir);
346 std::string walletFile;
350 if (fs::exists(walletDir / walletFile))
352 std::string backup_filename;
356 warningStr =
strprintf(
_(
"Warning: Wallet file corrupt, data salvaged!"
357 " Original %s saved as %s in %s; if"
358 " your balance or transactions are incorrect you should"
359 " restore from a backup."),
360 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 = std::make_unique<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", 100) * 1024 : 0, nMinutes, 0);
566 if (
mapDb[strFile] !=
nullptr) {
568 Db* pdb =
mapDb[strFile];
571 mapDb[strFile] =
nullptr;
580 std::unique_lock<RecursiveMutex> 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 = std::make_unique<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) {
709 dbenv->txn_checkpoint(0, 0, 0);
712 dbenv->lsn_reset(strFile.c_str(), 0);
722 dbenv->log_archive(&listp, DB_ARCH_REMOVE);
725 fs::remove_all(fs::path(
strPath) /
"database");
747 nRefCount += (*mi).second;
751 if (nRefCount == 0) {
752 boost::this_thread::interruption_point();
795 fs::path pathDest(strDest);
796 if (fs::is_directory(pathDest))
800 if (fs::equivalent(pathSrc, pathDest)) {
801 LogPrintf(
"cannot backup to wallet source file %s\n", pathDest.string());
805 #if BOOST_VERSION >= 107400
806 fs::copy_file(pathSrc.c_str(), pathDest, fs::copy_options::overwrite_existing);
807 #elif BOOST_VERSION >= 105800
808 fs::copy_file(pathSrc.c_str(), pathDest, fs::copy_option::overwrite_if_exists);
810 LogPrintf(
"copied %s to %s\n",
strFile, pathDest.string());
812 }
catch (
const fs::filesystem_error& e) {
std::string GetArg(const std::string &strArg, const std::string &strDefault) const
Return string argument or default value.
bool GetBoolArg(const std::string &strArg, bool fDefault) const
Return boolean argument or default value.
RAII class that provides access to a Berkeley database.
bool WriteVersion(int nVersion)
static bool VerifyEnvironment(const fs::path &file_path, std::string &errorStr)
bool Exists(const K &key)
static bool PeriodicFlush(BerkeleyDatabase &database)
static bool VerifyDatabaseFile(const fs::path &file_path, std::string &warningStr, std::string &errorStr, BerkeleyEnvironment::recoverFunc_type recoverFunc)
int ReadAtCursor(Dbc *pcursor, CDataStream &ssKey, CDataStream &ssValue, bool setRange=false)
BerkeleyEnvironment * env
static bool Recover(const fs::path &file_path, void *callbackDataIn, bool(*recoverKVcallback)(void *callbackData, CDataStream ssKey, CDataStream ssValue), std::string &out_backup_filename)
BerkeleyBatch(BerkeleyDatabase &database, const char *pszMode="r+", bool fFlushOnCloseIn=true)
static bool Rewrite(BerkeleyDatabase &database, const char *pszSkip=nullptr)
An instance of this class represents one database.
bool Rewrite(const char *pszSkip=nullptr)
Rewrite the entire database on disk, with the exception of key pszSkip if non-zero.
void Flush(bool shutdown)
Make sure all changes are flushed to disk.
BerkeleyEnvironment * env
BerkeleyDB specific.
bool Backup(const std::string &strDest)
Back up the entire database to a file.
void IncrementUpdateCounter()
bool IsDummy()
Return whether this database handle is a dummy for testing.
std::atomic< unsigned int > nUpdateCounter
std::unordered_map< std::string, WalletDatabaseFileId > m_fileids
std::map< std::string, Db * > mapDb
fs::path Directory() const
std::map< std::string, int > mapFileUseCount
bool(* recoverFunc_type)(const fs::path &file_path, std::string &out_backup_filename)
BerkeleyEnvironment(const fs::path &env_directory)
VerifyResult Verify(const std::string &strFile, recoverFunc_type recoverFunc, std::string &out_backup_filename)
std::pair< std::vector< unsigned char >, std::vector< unsigned char > > KeyValPair
Salvage data from a file that Verify says is bad.
VerifyResult
Verify that database file strFile is OK.
DbTxn * TxnBegin(int flags=DB_TXN_WRITE_NOSYNC)
std::unique_ptr< DbEnv > dbenv
std::condition_variable_any m_db_in_use
void CheckpointLSN(const std::string &strFile)
void Flush(bool fShutdown)
bool Salvage(const std::string &strFile, bool fAggressive, std::vector< KeyValPair > &vResult)
void CloseDb(const std::string &strFile)
BerkeleyEnvironment * GetWalletEnv(const fs::path &wallet_path, std::string &database_filename)
Get BerkeleyEnvironment and database filename given a wallet path.
#define LogPrint(category,...)
FILE * fopen(const fs::path &p, const char *mode)
std::string get_filesystem_error_message(const fs::filesystem_error &e)
u_int8_t value[DB_FILE_ID_LEN]
bool operator==(const WalletDatabaseFileId &rhs) const
#define AssertLockNotHeld(cs)
#define TRY_LOCK(cs, name)
bool TryCreateDirectories(const fs::path &p)
Ignores exceptions thrown by Boost's create_directories if the requested directory exists.
bool LockDirectory(const fs::path &directory, const std::string &lockfile_name, bool probe_only)
std::string _(const char *psz)
Translation function: Call Translate signal on UI interface, which returns a Optional result.
std::string HexStr(const Span< const uint8_t > s)
Convert a span of bytes to a lower-case hexadecimal string.
std::vector< unsigned char > ParseHex(const char *psz)
int64_t GetTimeMillis()
Returns the system time (not mockable)
void MilliSleep(int64_t n)
int64_t GetTime()
DEPRECATED Use either GetSystemTimeInSeconds (not mockable) or GetTime<T> (mockable)