|
|
@ -18,6 +18,7 @@ |
|
|
|
#endif |
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
#include <boost/filesystem.hpp> |
|
|
|
#include <boost/filesystem.hpp> |
|
|
|
|
|
|
|
#include <boost/foreach.hpp> |
|
|
|
#include <boost/thread.hpp> |
|
|
|
#include <boost/thread.hpp> |
|
|
|
#include <boost/version.hpp> |
|
|
|
#include <boost/version.hpp> |
|
|
|
|
|
|
|
|
|
|
@ -145,7 +146,7 @@ void CDBEnv::MakeMock() |
|
|
|
fMockDb = true; |
|
|
|
fMockDb = true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
CDBEnv::VerifyResult CDBEnv::Verify(const std::string& strFile, bool (*recoverFunc)(CDBEnv& dbenv, const std::string& strFile)) |
|
|
|
CDBEnv::VerifyResult CDBEnv::Verify(const std::string& strFile, bool (*recoverFunc)(const std::string& strFile)) |
|
|
|
{ |
|
|
|
{ |
|
|
|
LOCK(cs_db); |
|
|
|
LOCK(cs_db); |
|
|
|
assert(mapFileUseCount.count(strFile) == 0); |
|
|
|
assert(mapFileUseCount.count(strFile) == 0); |
|
|
@ -158,10 +159,134 @@ CDBEnv::VerifyResult CDBEnv::Verify(const std::string& strFile, bool (*recoverFu |
|
|
|
return RECOVER_FAIL; |
|
|
|
return RECOVER_FAIL; |
|
|
|
|
|
|
|
|
|
|
|
// Try to recover:
|
|
|
|
// Try to recover:
|
|
|
|
bool fRecovered = (*recoverFunc)(*this, strFile); |
|
|
|
bool fRecovered = (*recoverFunc)(strFile); |
|
|
|
return (fRecovered ? RECOVER_OK : RECOVER_FAIL); |
|
|
|
return (fRecovered ? RECOVER_OK : RECOVER_FAIL); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue)) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// Recovery procedure:
|
|
|
|
|
|
|
|
// move wallet file to wallet.timestamp.bak
|
|
|
|
|
|
|
|
// Call Salvage with fAggressive=true to
|
|
|
|
|
|
|
|
// get as much data as possible.
|
|
|
|
|
|
|
|
// Rewrite salvaged data to fresh wallet file
|
|
|
|
|
|
|
|
// Set -rescan so any missing transactions will be
|
|
|
|
|
|
|
|
// found.
|
|
|
|
|
|
|
|
int64_t now = GetTime(); |
|
|
|
|
|
|
|
std::string newFilename = strprintf("wallet.%d.bak", now); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int result = bitdb.dbenv->dbrename(NULL, filename.c_str(), NULL, |
|
|
|
|
|
|
|
newFilename.c_str(), DB_AUTO_COMMIT); |
|
|
|
|
|
|
|
if (result == 0) |
|
|
|
|
|
|
|
LogPrintf("Renamed %s to %s\n", filename, newFilename); |
|
|
|
|
|
|
|
else |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
LogPrintf("Failed to rename %s to %s\n", filename, newFilename); |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<CDBEnv::KeyValPair> salvagedData; |
|
|
|
|
|
|
|
bool fSuccess = bitdb.Salvage(newFilename, true, salvagedData); |
|
|
|
|
|
|
|
if (salvagedData.empty()) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
LogPrintf("Salvage(aggressive) found no records in %s.\n", newFilename); |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
LogPrintf("Salvage(aggressive) found %u records\n", salvagedData.size()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::unique_ptr<Db> pdbCopy(new Db(bitdb.dbenv, 0)); |
|
|
|
|
|
|
|
int ret = pdbCopy->open(NULL, // Txn pointer
|
|
|
|
|
|
|
|
filename.c_str(), // Filename
|
|
|
|
|
|
|
|
"main", // Logical db name
|
|
|
|
|
|
|
|
DB_BTREE, // Database type
|
|
|
|
|
|
|
|
DB_CREATE, // Flags
|
|
|
|
|
|
|
|
0); |
|
|
|
|
|
|
|
if (ret > 0) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
LogPrintf("Cannot create database file %s\n", filename); |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DbTxn* ptxn = bitdb.TxnBegin(); |
|
|
|
|
|
|
|
BOOST_FOREACH(CDBEnv::KeyValPair& row, salvagedData) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (recoverKVcallback) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION); |
|
|
|
|
|
|
|
CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION); |
|
|
|
|
|
|
|
string strType, strErr; |
|
|
|
|
|
|
|
if (!(*recoverKVcallback)(callbackDataIn, ssKey, ssValue)) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
Dbt datKey(&row.first[0], row.first.size()); |
|
|
|
|
|
|
|
Dbt datValue(&row.second[0], row.second.size()); |
|
|
|
|
|
|
|
int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE); |
|
|
|
|
|
|
|
if (ret2 > 0) |
|
|
|
|
|
|
|
fSuccess = false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
ptxn->commit(0); |
|
|
|
|
|
|
|
pdbCopy->close(0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return fSuccess; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool CDB::VerifyEnvironment(const std::string& walletFile, const boost::filesystem::path& dataDir, std::string& errorStr) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0)); |
|
|
|
|
|
|
|
LogPrintf("Using wallet %s\n", walletFile); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Wallet file must be a plain filename without a directory
|
|
|
|
|
|
|
|
if (walletFile != boost::filesystem::basename(walletFile) + boost::filesystem::extension(walletFile)) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
errorStr = strprintf(_("Wallet %s resides outside data directory %s"), walletFile, dataDir.string()); |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!bitdb.Open(dataDir)) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// try moving the database env out of the way
|
|
|
|
|
|
|
|
boost::filesystem::path pathDatabase = dataDir / "database"; |
|
|
|
|
|
|
|
boost::filesystem::path pathDatabaseBak = dataDir / strprintf("database.%d.bak", GetTime()); |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
boost::filesystem::rename(pathDatabase, pathDatabaseBak); |
|
|
|
|
|
|
|
LogPrintf("Moved old %s to %s. Retrying.\n", pathDatabase.string(), pathDatabaseBak.string()); |
|
|
|
|
|
|
|
} catch (const boost::filesystem::filesystem_error&) { |
|
|
|
|
|
|
|
// failure is ok (well, not really, but it's not worse than what we started with)
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// try again
|
|
|
|
|
|
|
|
if (!bitdb.Open(dataDir)) { |
|
|
|
|
|
|
|
// if it still fails, it probably means we can't even create the database env
|
|
|
|
|
|
|
|
errorStr = strprintf(_("Error initializing wallet database environment %s!"), GetDataDir()); |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool CDB::VerifyDatabaseFile(const std::string& walletFile, const boost::filesystem::path& dataDir, std::string& warningStr, std::string& errorStr, bool (*recoverFunc)(const std::string& strFile)) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (boost::filesystem::exists(dataDir / walletFile)) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
CDBEnv::VerifyResult r = bitdb.Verify(walletFile, recoverFunc); |
|
|
|
|
|
|
|
if (r == CDBEnv::RECOVER_OK) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
warningStr = strprintf(_("Warning: Wallet file corrupt, data salvaged!" |
|
|
|
|
|
|
|
" Original %s saved as %s in %s; if" |
|
|
|
|
|
|
|
" your balance or transactions are incorrect you should" |
|
|
|
|
|
|
|
" restore from a backup."), |
|
|
|
|
|
|
|
walletFile, "wallet.{timestamp}.bak", dataDir); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (r == CDBEnv::RECOVER_FAIL) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
errorStr = strprintf(_("%s corrupt, salvage failed"), walletFile); |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// also return true if files does not exists
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* End of headers, beginning of key/value data */ |
|
|
|
/* End of headers, beginning of key/value data */ |
|
|
|
static const char *HEADER_END = "HEADER=END"; |
|
|
|
static const char *HEADER_END = "HEADER=END"; |
|
|
|
/* End of key/value data */ |
|
|
|
/* End of key/value data */ |
|
|
@ -473,3 +598,41 @@ void CDBEnv::Flush(bool fShutdown) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool CDB::PeriodicFlush(std::string strFile) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
bool ret = false; |
|
|
|
|
|
|
|
TRY_LOCK(bitdb.cs_db,lockDb); |
|
|
|
|
|
|
|
if (lockDb) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// Don't do this if any databases are in use
|
|
|
|
|
|
|
|
int nRefCount = 0; |
|
|
|
|
|
|
|
map<string, int>::iterator mi = bitdb.mapFileUseCount.begin(); |
|
|
|
|
|
|
|
while (mi != bitdb.mapFileUseCount.end()) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
nRefCount += (*mi).second; |
|
|
|
|
|
|
|
mi++; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (nRefCount == 0) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
boost::this_thread::interruption_point(); |
|
|
|
|
|
|
|
map<string, int>::iterator mi = bitdb.mapFileUseCount.find(strFile); |
|
|
|
|
|
|
|
if (mi != bitdb.mapFileUseCount.end()) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
LogPrint("db", "Flushing %s\n", strFile); |
|
|
|
|
|
|
|
int64_t nStart = GetTimeMillis(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Flush wallet file so it's self contained
|
|
|
|
|
|
|
|
bitdb.CloseDb(strFile); |
|
|
|
|
|
|
|
bitdb.CheckpointLSN(strFile); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bitdb.mapFileUseCount.erase(mi++); |
|
|
|
|
|
|
|
LogPrint("db", "Flushed %s %dms\n", strFile, GetTimeMillis() - nStart); |
|
|
|
|
|
|
|
ret = true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return ret; |
|
|
|
|
|
|
|
} |
|
|
|