1 // Copyright (c) 2009-2010 Satoshi Nakamoto
2 // Copyright (c) 2009-2016 The Bitcoin Core developers
3 // Distributed under the MIT software license, see the accompanying
4 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
13 #include "utilstrencodings.h"
21 #include <boost/foreach.hpp>
22 #include <boost/thread.hpp>
23 #include <boost/version.hpp>
31 void CDBEnv::EnvShutdown()
37 int ret
= dbenv
->close(0);
39 LogPrintf("CDBEnv::EnvShutdown: Error %d shutting down database environment: %s\n", ret
, DbEnv::strerror(ret
));
41 DbEnv((u_int32_t
)0).remove(strPath
.c_str(), 0);
47 dbenv
= new DbEnv(DB_CXX_NO_EXCEPTIONS
);
52 CDBEnv::CDBEnv() : dbenv(NULL
)
69 bool CDBEnv::Open(const fs::path
& pathIn
)
74 boost::this_thread::interruption_point();
76 strPath
= pathIn
.string();
77 fs::path pathLogDir
= pathIn
/ "database";
78 TryCreateDirectory(pathLogDir
);
79 fs::path pathErrorFile
= pathIn
/ "db.log";
80 LogPrintf("CDBEnv::Open: LogDir=%s ErrorFile=%s\n", pathLogDir
.string(), pathErrorFile
.string());
82 unsigned int nEnvFlags
= 0;
83 if (GetBoolArg("-privdb", DEFAULT_WALLET_PRIVDB
))
84 nEnvFlags
|= DB_PRIVATE
;
86 dbenv
->set_lg_dir(pathLogDir
.string().c_str());
87 dbenv
->set_cachesize(0, 0x100000, 1); // 1 MiB should be enough for just the wallet
88 dbenv
->set_lg_bsize(0x10000);
89 dbenv
->set_lg_max(1048576);
90 dbenv
->set_lk_max_locks(40000);
91 dbenv
->set_lk_max_objects(40000);
92 dbenv
->set_errfile(fsbridge::fopen(pathErrorFile
, "a")); /// debug
93 dbenv
->set_flags(DB_AUTO_COMMIT
, 1);
94 dbenv
->set_flags(DB_TXN_WRITE_NOSYNC
, 1);
95 dbenv
->log_set_config(DB_LOG_AUTO_REMOVE
, 1);
96 int ret
= dbenv
->open(strPath
.c_str(),
107 return error("CDBEnv::Open: Error %d opening database environment: %s\n", ret
, DbEnv::strerror(ret
));
114 void CDBEnv::MakeMock()
117 throw std::runtime_error("CDBEnv::MakeMock: Already initialized");
119 boost::this_thread::interruption_point();
121 LogPrint(BCLog::DB
, "CDBEnv::MakeMock\n");
123 dbenv
->set_cachesize(1, 0, 1);
124 dbenv
->set_lg_bsize(10485760 * 4);
125 dbenv
->set_lg_max(10485760);
126 dbenv
->set_lk_max_locks(10000);
127 dbenv
->set_lk_max_objects(10000);
128 dbenv
->set_flags(DB_AUTO_COMMIT
, 1);
129 dbenv
->log_set_config(DB_LOG_IN_MEMORY
, 1);
130 int ret
= dbenv
->open(NULL
,
140 throw std::runtime_error(strprintf("CDBEnv::MakeMock: Error %d opening database environment.", ret
));
146 CDBEnv::VerifyResult
CDBEnv::Verify(const std::string
& strFile
, bool (*recoverFunc
)(const std::string
& strFile
))
149 assert(mapFileUseCount
.count(strFile
) == 0);
152 int result
= db
.verify(strFile
.c_str(), NULL
, NULL
, 0);
155 else if (recoverFunc
== NULL
)
159 bool fRecovered
= (*recoverFunc
)(strFile
);
160 return (fRecovered
? RECOVER_OK
: RECOVER_FAIL
);
163 bool CDB::Recover(const std::string
& filename
, void *callbackDataIn
, bool (*recoverKVcallback
)(void* callbackData
, CDataStream ssKey
, CDataStream ssValue
))
165 // Recovery procedure:
166 // move wallet file to wallet.timestamp.bak
167 // Call Salvage with fAggressive=true to
168 // get as much data as possible.
169 // Rewrite salvaged data to fresh wallet file
170 // Set -rescan so any missing transactions will be
172 int64_t now
= GetTime();
173 std::string newFilename
= strprintf("wallet.%d.bak", now
);
175 int result
= bitdb
.dbenv
->dbrename(NULL
, filename
.c_str(), NULL
,
176 newFilename
.c_str(), DB_AUTO_COMMIT
);
178 LogPrintf("Renamed %s to %s\n", filename
, newFilename
);
181 LogPrintf("Failed to rename %s to %s\n", filename
, newFilename
);
185 std::vector
<CDBEnv::KeyValPair
> salvagedData
;
186 bool fSuccess
= bitdb
.Salvage(newFilename
, true, salvagedData
);
187 if (salvagedData
.empty())
189 LogPrintf("Salvage(aggressive) found no records in %s.\n", newFilename
);
192 LogPrintf("Salvage(aggressive) found %u records\n", salvagedData
.size());
194 std::unique_ptr
<Db
> pdbCopy(new Db(bitdb
.dbenv
, 0));
195 int ret
= pdbCopy
->open(NULL
, // Txn pointer
196 filename
.c_str(), // Filename
197 "main", // Logical db name
198 DB_BTREE
, // Database type
203 LogPrintf("Cannot create database file %s\n", filename
);
207 DbTxn
* ptxn
= bitdb
.TxnBegin();
208 BOOST_FOREACH(CDBEnv::KeyValPair
& row
, salvagedData
)
210 if (recoverKVcallback
)
212 CDataStream
ssKey(row
.first
, SER_DISK
, CLIENT_VERSION
);
213 CDataStream
ssValue(row
.second
, SER_DISK
, CLIENT_VERSION
);
214 if (!(*recoverKVcallback
)(callbackDataIn
, ssKey
, ssValue
))
217 Dbt
datKey(&row
.first
[0], row
.first
.size());
218 Dbt
datValue(&row
.second
[0], row
.second
.size());
219 int ret2
= pdbCopy
->put(ptxn
, &datKey
, &datValue
, DB_NOOVERWRITE
);
229 bool CDB::VerifyEnvironment(const std::string
& walletFile
, const fs::path
& dataDir
, std::string
& errorStr
)
231 LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0));
232 LogPrintf("Using wallet %s\n", walletFile
);
234 // Wallet file must be a plain filename without a directory
235 if (walletFile
!= fs::basename(walletFile
) + fs::extension(walletFile
))
237 errorStr
= strprintf(_("Wallet %s resides outside data directory %s"), walletFile
, dataDir
.string());
241 if (!bitdb
.Open(dataDir
))
243 // try moving the database env out of the way
244 fs::path pathDatabase
= dataDir
/ "database";
245 fs::path pathDatabaseBak
= dataDir
/ strprintf("database.%d.bak", GetTime());
247 fs::rename(pathDatabase
, pathDatabaseBak
);
248 LogPrintf("Moved old %s to %s. Retrying.\n", pathDatabase
.string(), pathDatabaseBak
.string());
249 } catch (const fs::filesystem_error
&) {
250 // failure is ok (well, not really, but it's not worse than what we started with)
254 if (!bitdb
.Open(dataDir
)) {
255 // if it still fails, it probably means we can't even create the database env
256 errorStr
= strprintf(_("Error initializing wallet database environment %s!"), GetDataDir());
263 bool CDB::VerifyDatabaseFile(const std::string
& walletFile
, const fs::path
& dataDir
, std::string
& warningStr
, std::string
& errorStr
, bool (*recoverFunc
)(const std::string
& strFile
))
265 if (fs::exists(dataDir
/ walletFile
))
267 CDBEnv::VerifyResult r
= bitdb
.Verify(walletFile
, recoverFunc
);
268 if (r
== CDBEnv::RECOVER_OK
)
270 warningStr
= strprintf(_("Warning: Wallet file corrupt, data salvaged!"
271 " Original %s saved as %s in %s; if"
272 " your balance or transactions are incorrect you should"
273 " restore from a backup."),
274 walletFile
, "wallet.{timestamp}.bak", dataDir
);
276 if (r
== CDBEnv::RECOVER_FAIL
)
278 errorStr
= strprintf(_("%s corrupt, salvage failed"), walletFile
);
282 // also return true if files does not exists
286 /* End of headers, beginning of key/value data */
287 static const char *HEADER_END
= "HEADER=END";
288 /* End of key/value data */
289 static const char *DATA_END
= "DATA=END";
291 bool CDBEnv::Salvage(const std::string
& strFile
, bool fAggressive
, std::vector
<CDBEnv::KeyValPair
>& vResult
)
294 assert(mapFileUseCount
.count(strFile
) == 0);
296 u_int32_t flags
= DB_SALVAGE
;
298 flags
|= DB_AGGRESSIVE
;
300 std::stringstream strDump
;
303 int result
= db
.verify(strFile
.c_str(), NULL
, &strDump
, flags
);
304 if (result
== DB_VERIFY_BAD
) {
305 LogPrintf("CDBEnv::Salvage: Database salvage found errors, all data may not be recoverable.\n");
307 LogPrintf("CDBEnv::Salvage: Rerun with aggressive mode to ignore errors and continue.\n");
311 if (result
!= 0 && result
!= DB_VERIFY_BAD
) {
312 LogPrintf("CDBEnv::Salvage: Database salvage failed with result %d.\n", result
);
316 // Format of bdb dump is ascii lines:
325 while (!strDump
.eof() && strLine
!= HEADER_END
)
326 getline(strDump
, strLine
); // Skip past header
328 std::string keyHex
, valueHex
;
329 while (!strDump
.eof() && keyHex
!= DATA_END
) {
330 getline(strDump
, keyHex
);
331 if (keyHex
!= DATA_END
) {
334 getline(strDump
, valueHex
);
335 if (valueHex
== DATA_END
) {
336 LogPrintf("CDBEnv::Salvage: WARNING: Number of keys in data does not match number of values.\n");
339 vResult
.push_back(make_pair(ParseHex(keyHex
), ParseHex(valueHex
)));
343 if (keyHex
!= DATA_END
) {
344 LogPrintf("CDBEnv::Salvage: WARNING: Unexpected end of file while reading salvage output.\n");
348 return (result
== 0);
352 void CDBEnv::CheckpointLSN(const std::string
& strFile
)
354 dbenv
->txn_checkpoint(0, 0, 0);
357 dbenv
->lsn_reset(strFile
.c_str(), 0);
361 CDB::CDB(CWalletDBWrapper
& dbw
, const char* pszMode
, bool fFlushOnCloseIn
) : pdb(NULL
), activeTxn(NULL
)
363 fReadOnly
= (!strchr(pszMode
, '+') && !strchr(pszMode
, 'w'));
364 fFlushOnClose
= fFlushOnCloseIn
;
369 const std::string
&strFilename
= dbw
.strFile
;
371 bool fCreate
= strchr(pszMode
, 'c') != NULL
;
372 unsigned int nFlags
= DB_THREAD
;
378 if (!env
->Open(GetDataDir()))
379 throw std::runtime_error("CDB: Failed to open database environment.");
381 strFile
= strFilename
;
382 ++env
->mapFileUseCount
[strFile
];
383 pdb
= env
->mapDb
[strFile
];
386 pdb
= new Db(env
->dbenv
, 0);
388 bool fMockDb
= env
->IsMock();
390 DbMpoolFile
* mpf
= pdb
->get_mpf();
391 ret
= mpf
->set_flags(DB_MPOOL_NOFILE
, 1);
393 throw std::runtime_error(strprintf("CDB: Failed to configure for no temp file backing for database %s", strFile
));
396 ret
= pdb
->open(NULL
, // Txn pointer
397 fMockDb
? NULL
: strFile
.c_str(), // Filename
398 fMockDb
? strFile
.c_str() : "main", // Logical db name
399 DB_BTREE
, // Database type
406 --env
->mapFileUseCount
[strFile
];
408 throw std::runtime_error(strprintf("CDB: Error %d, can't open database %s", ret
, strFilename
));
411 if (fCreate
&& !Exists(std::string("version"))) {
412 bool fTmp
= fReadOnly
;
414 WriteVersion(CLIENT_VERSION
);
418 env
->mapDb
[strFile
] = pdb
;
428 // Flush database activity from memory pool to disk log
429 unsigned int nMinutes
= 0;
433 env
->dbenv
->txn_checkpoint(nMinutes
? GetArg("-dblogsize", DEFAULT_WALLET_DBLOGSIZE
) * 1024 : 0, nMinutes
, 0);
450 --env
->mapFileUseCount
[strFile
];
454 void CDBEnv::CloseDb(const std::string
& strFile
)
458 if (mapDb
[strFile
] != NULL
) {
459 // Close the database handle
460 Db
* pdb
= mapDb
[strFile
];
463 mapDb
[strFile
] = NULL
;
468 bool CDB::Rewrite(CWalletDBWrapper
& dbw
, const char* pszSkip
)
473 CDBEnv
*env
= dbw
.env
;
474 const std::string
& strFile
= dbw
.strFile
;
478 if (!env
->mapFileUseCount
.count(strFile
) || env
->mapFileUseCount
[strFile
] == 0) {
479 // Flush log data to the dat file
480 env
->CloseDb(strFile
);
481 env
->CheckpointLSN(strFile
);
482 env
->mapFileUseCount
.erase(strFile
);
484 bool fSuccess
= true;
485 LogPrintf("CDB::Rewrite: Rewriting %s...\n", strFile
);
486 std::string strFileRes
= strFile
+ ".rewrite";
487 { // surround usage of db with extra {}
489 Db
* pdbCopy
= new Db(env
->dbenv
, 0);
491 int ret
= pdbCopy
->open(NULL
, // Txn pointer
492 strFileRes
.c_str(), // Filename
493 "main", // Logical db name
494 DB_BTREE
, // Database type
498 LogPrintf("CDB::Rewrite: Can't create database file %s\n", strFileRes
);
502 Dbc
* pcursor
= db
.GetCursor();
505 CDataStream
ssKey(SER_DISK
, CLIENT_VERSION
);
506 CDataStream
ssValue(SER_DISK
, CLIENT_VERSION
);
507 int ret1
= db
.ReadAtCursor(pcursor
, ssKey
, ssValue
);
508 if (ret1
== DB_NOTFOUND
) {
511 } else if (ret1
!= 0) {
517 strncmp(ssKey
.data(), pszSkip
, std::min(ssKey
.size(), strlen(pszSkip
))) == 0)
519 if (strncmp(ssKey
.data(), "\x07version", 8) == 0) {
522 ssValue
<< CLIENT_VERSION
;
524 Dbt
datKey(ssKey
.data(), ssKey
.size());
525 Dbt
datValue(ssValue
.data(), ssValue
.size());
526 int ret2
= pdbCopy
->put(NULL
, &datKey
, &datValue
, DB_NOOVERWRITE
);
532 env
->CloseDb(strFile
);
533 if (pdbCopy
->close(0))
539 Db
dbA(env
->dbenv
, 0);
540 if (dbA
.remove(strFile
.c_str(), NULL
, 0))
542 Db
dbB(env
->dbenv
, 0);
543 if (dbB
.rename(strFileRes
.c_str(), NULL
, strFile
.c_str(), 0))
547 LogPrintf("CDB::Rewrite: Failed to rewrite database file %s\n", strFileRes
);
557 void CDBEnv::Flush(bool fShutdown
)
559 int64_t nStart
= GetTimeMillis();
560 // Flush log data to the actual data file on all files that are not in use
561 LogPrint(BCLog::DB
, "CDBEnv::Flush: Flush(%s)%s\n", fShutdown
? "true" : "false", fDbEnvInit
? "" : " database not started");
566 std::map
<std::string
, int>::iterator mi
= mapFileUseCount
.begin();
567 while (mi
!= mapFileUseCount
.end()) {
568 std::string strFile
= (*mi
).first
;
569 int nRefCount
= (*mi
).second
;
570 LogPrint(BCLog::DB
, "CDBEnv::Flush: Flushing %s (refcount = %d)...\n", strFile
, nRefCount
);
571 if (nRefCount
== 0) {
572 // Move log data to the dat file
574 LogPrint(BCLog::DB
, "CDBEnv::Flush: %s checkpoint\n", strFile
);
575 dbenv
->txn_checkpoint(0, 0, 0);
576 LogPrint(BCLog::DB
, "CDBEnv::Flush: %s detach\n", strFile
);
578 dbenv
->lsn_reset(strFile
.c_str(), 0);
579 LogPrint(BCLog::DB
, "CDBEnv::Flush: %s closed\n", strFile
);
580 mapFileUseCount
.erase(mi
++);
584 LogPrint(BCLog::DB
, "CDBEnv::Flush: Flush(%s)%s took %15dms\n", fShutdown
? "true" : "false", fDbEnvInit
? "" : " database not started", GetTimeMillis() - nStart
);
587 if (mapFileUseCount
.empty()) {
588 dbenv
->log_archive(&listp
, DB_ARCH_REMOVE
);
591 fs::remove_all(fs::path(strPath
) / "database");
597 bool CDB::PeriodicFlush(CWalletDBWrapper
& dbw
)
603 CDBEnv
*env
= dbw
.env
;
604 const std::string
& strFile
= dbw
.strFile
;
605 TRY_LOCK(bitdb
.cs_db
,lockDb
);
608 // Don't do this if any databases are in use
610 std::map
<std::string
, int>::iterator mit
= env
->mapFileUseCount
.begin();
611 while (mit
!= env
->mapFileUseCount
.end())
613 nRefCount
+= (*mit
).second
;
619 boost::this_thread::interruption_point();
620 std::map
<std::string
, int>::iterator mi
= env
->mapFileUseCount
.find(strFile
);
621 if (mi
!= env
->mapFileUseCount
.end())
623 LogPrint(BCLog::DB
, "Flushing %s\n", strFile
);
624 int64_t nStart
= GetTimeMillis();
626 // Flush wallet file so it's self contained
627 env
->CloseDb(strFile
);
628 env
->CheckpointLSN(strFile
);
630 env
->mapFileUseCount
.erase(mi
++);
631 LogPrint(BCLog::DB
, "Flushed %s %dms\n", strFile
, GetTimeMillis() - nStart
);
640 bool CWalletDBWrapper::Rewrite(const char* pszSkip
)
642 return CDB::Rewrite(*this, pszSkip
);
645 bool CWalletDBWrapper::Backup(const std::string
& strDest
)
654 if (!env
->mapFileUseCount
.count(strFile
) || env
->mapFileUseCount
[strFile
] == 0)
656 // Flush log data to the dat file
657 env
->CloseDb(strFile
);
658 env
->CheckpointLSN(strFile
);
659 env
->mapFileUseCount
.erase(strFile
);
662 fs::path pathSrc
= GetDataDir() / strFile
;
663 fs::path
pathDest(strDest
);
664 if (fs::is_directory(pathDest
))
668 fs::copy_file(pathSrc
, pathDest
, fs::copy_option::overwrite_if_exists
);
669 LogPrintf("copied %s to %s\n", strFile
, pathDest
.string());
671 } catch (const fs::filesystem_error
& e
) {
672 LogPrintf("error copying %s to %s - %s\n", strFile
, pathDest
.string(), e
.what());
682 void CWalletDBWrapper::Flush(bool shutdown
)
685 env
->Flush(shutdown
);