@@ -52,20 +52,44 @@ void CheckUniqueFileid(const CDBEnv& env, const std::string& filename, Db& db)
52
52
}
53
53
}
54
54
}
55
+
56
+ CCriticalSection cs_db;
57
+ std::map<std::string, CDBEnv> g_dbenvs; // !< Map from directory name to open db environment.
55
58
} // namespace
56
59
60
+ CDBEnv* GetWalletEnv (const fs::path& wallet_path, std::string& database_filename)
61
+ {
62
+ fs::path env_directory = wallet_path.parent_path ();
63
+ database_filename = wallet_path.filename ().string ();
64
+ LOCK (cs_db);
65
+ // Note: An ununsed temporary CDBEnv object may be created inside the
66
+ // emplace function if the key already exists. This is a little inefficient,
67
+ // but not a big concern since the map will be changed in the future to hold
68
+ // pointers instead of objects, anyway.
69
+ return &g_dbenvs.emplace (std::piecewise_construct, std::forward_as_tuple (env_directory.string ()), std::forward_as_tuple (env_directory)).first ->second ;
70
+ }
71
+
57
72
//
58
73
// CDB
59
74
//
60
75
61
- CDBEnv bitdb;
62
-
63
- void CDBEnv::EnvShutdown ()
76
+ void CDBEnv::Close ()
64
77
{
65
78
if (!fDbEnvInit )
66
79
return ;
67
80
68
81
fDbEnvInit = false ;
82
+
83
+ for (auto & db : mapDb) {
84
+ auto count = mapFileUseCount.find (db.first );
85
+ assert (count == mapFileUseCount.end () || count->second == 0 );
86
+ if (db.second ) {
87
+ db.second ->close (0 );
88
+ delete db.second ;
89
+ db.second = nullptr ;
90
+ }
91
+ }
92
+
69
93
int ret = dbenv->close (0 );
70
94
if (ret != 0 )
71
95
LogPrintf (" CDBEnv::EnvShutdown: Error %d shutting down database environment: %s\n " , ret, DbEnv::strerror (ret));
@@ -80,29 +104,24 @@ void CDBEnv::Reset()
80
104
fMockDb = false ;
81
105
}
82
106
83
- CDBEnv::CDBEnv ()
107
+ CDBEnv::CDBEnv (const fs::path& dir_path) : strPath(dir_path.string() )
84
108
{
85
109
Reset ();
86
110
}
87
111
88
112
CDBEnv::~CDBEnv ()
89
113
{
90
- EnvShutdown ();
114
+ Close ();
91
115
}
92
116
93
- void CDBEnv::Close ()
94
- {
95
- EnvShutdown ();
96
- }
97
-
98
- bool CDBEnv::Open (const fs::path& pathIn, bool retry)
117
+ bool CDBEnv::Open (bool retry)
99
118
{
100
119
if (fDbEnvInit )
101
120
return true ;
102
121
103
122
boost::this_thread::interruption_point ();
104
123
105
- strPath = pathIn. string () ;
124
+ fs::path pathIn = strPath ;
106
125
if (!LockDirectory (pathIn, " .walletlock" )) {
107
126
LogPrintf (" Cannot obtain a lock on wallet directory %s. Another instance of bitcoin may be using it.\n " , strPath);
108
127
return false ;
@@ -150,7 +169,7 @@ bool CDBEnv::Open(const fs::path& pathIn, bool retry)
150
169
// failure is ok (well, not really, but it's not worse than what we started with)
151
170
}
152
171
// try opening it again one more time
153
- if (!Open (pathIn, false )) {
172
+ if (!Open (false /* retry */ )) {
154
173
// if it still fails, it probably means we can't even create the database env
155
174
return false ;
156
175
}
@@ -209,12 +228,15 @@ CDBEnv::VerifyResult CDBEnv::Verify(const std::string& strFile, recoverFunc_type
209
228
return RECOVER_FAIL;
210
229
211
230
// Try to recover:
212
- bool fRecovered = (*recoverFunc)(strFile, out_backup_filename);
231
+ bool fRecovered = (*recoverFunc)(fs::path (strPath) / strFile, out_backup_filename);
213
232
return (fRecovered ? RECOVER_OK : RECOVER_FAIL);
214
233
}
215
234
216
- bool CDB::Recover (const std::string& filename , void *callbackDataIn, bool (*recoverKVcallback)(void * callbackData, CDataStream ssKey, CDataStream ssValue), std::string& newFilename)
235
+ bool CDB::Recover (const fs::path& file_path , void *callbackDataIn, bool (*recoverKVcallback)(void * callbackData, CDataStream ssKey, CDataStream ssValue), std::string& newFilename)
217
236
{
237
+ std::string filename;
238
+ CDBEnv* env = GetWalletEnv (file_path, filename);
239
+
218
240
// Recovery procedure:
219
241
// move wallet file to walletfilename.timestamp.bak
220
242
// Call Salvage with fAggressive=true to
@@ -225,7 +247,7 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco
225
247
int64_t now = GetTime ();
226
248
newFilename = strprintf (" %s.%d.bak" , filename, now);
227
249
228
- int result = bitdb. dbenv ->dbrename (nullptr , filename.c_str (), nullptr ,
250
+ int result = env-> dbenv ->dbrename (nullptr , filename.c_str (), nullptr ,
229
251
newFilename.c_str (), DB_AUTO_COMMIT);
230
252
if (result == 0 )
231
253
LogPrintf (" Renamed %s to %s\n " , filename, newFilename);
@@ -236,15 +258,15 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco
236
258
}
237
259
238
260
std::vector<CDBEnv::KeyValPair> salvagedData;
239
- bool fSuccess = bitdb. Salvage (newFilename, true , salvagedData);
261
+ bool fSuccess = env-> Salvage (newFilename, true , salvagedData);
240
262
if (salvagedData.empty ())
241
263
{
242
264
LogPrintf (" Salvage(aggressive) found no records in %s.\n " , newFilename);
243
265
return false ;
244
266
}
245
267
LogPrintf (" Salvage(aggressive) found %u records\n " , salvagedData.size ());
246
268
247
- std::unique_ptr<Db> pdbCopy = MakeUnique<Db>(bitdb. dbenv .get (), 0 );
269
+ std::unique_ptr<Db> pdbCopy = MakeUnique<Db>(env-> dbenv .get (), 0 );
248
270
int ret = pdbCopy->open (nullptr , // Txn pointer
249
271
filename.c_str (), // Filename
250
272
" main" , // Logical db name
@@ -257,7 +279,7 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco
257
279
return false ;
258
280
}
259
281
260
- DbTxn* ptxn = bitdb. TxnBegin ();
282
+ DbTxn* ptxn = env-> TxnBegin ();
261
283
for (CDBEnv::KeyValPair& row : salvagedData)
262
284
{
263
285
if (recoverKVcallback)
@@ -279,8 +301,12 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco
279
301
return fSuccess ;
280
302
}
281
303
282
- bool CDB::VerifyEnvironment (const std::string& walletFile, const fs::path& walletDir , std::string& errorStr)
304
+ bool CDB::VerifyEnvironment (const fs::path& file_path , std::string& errorStr)
283
305
{
306
+ std::string walletFile;
307
+ CDBEnv* env = GetWalletEnv (file_path, walletFile);
308
+ fs::path walletDir = env->Directory ();
309
+
284
310
LogPrintf (" Using BerkeleyDB version %s\n " , DbEnv::version (0 , 0 , 0 ));
285
311
LogPrintf (" Using wallet %s\n " , walletFile);
286
312
@@ -291,20 +317,24 @@ bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& walle
291
317
return false ;
292
318
}
293
319
294
- if (!bitdb. Open (walletDir, true )) {
320
+ if (!env-> Open (true /* retry */ )) {
295
321
errorStr = strprintf (_ (" Error initializing wallet database environment %s!" ), walletDir);
296
322
return false ;
297
323
}
298
324
299
325
return true ;
300
326
}
301
327
302
- bool CDB::VerifyDatabaseFile (const std::string& walletFile, const fs::path& walletDir , std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc)
328
+ bool CDB::VerifyDatabaseFile (const fs::path& file_path , std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc)
303
329
{
330
+ std::string walletFile;
331
+ CDBEnv* env = GetWalletEnv (file_path, walletFile);
332
+ fs::path walletDir = env->Directory ();
333
+
304
334
if (fs::exists (walletDir / walletFile))
305
335
{
306
336
std::string backup_filename;
307
- CDBEnv::VerifyResult r = bitdb. Verify (walletFile, recoverFunc, backup_filename);
337
+ CDBEnv::VerifyResult r = env-> Verify (walletFile, recoverFunc, backup_filename);
308
338
if (r == CDBEnv::RECOVER_OK)
309
339
{
310
340
warningStr = strprintf (_ (" Warning: Wallet file corrupt, data salvaged!"
@@ -414,8 +444,8 @@ CDB::CDB(CWalletDBWrapper& dbw, const char* pszMode, bool fFlushOnCloseIn) : pdb
414
444
nFlags |= DB_CREATE;
415
445
416
446
{
417
- LOCK (env-> cs_db );
418
- if (!env->Open (GetWalletDir () ))
447
+ LOCK (cs_db);
448
+ if (!env->Open (false /* retry */ ))
419
449
throw std::runtime_error (" CDB: Failed to open database environment." );
420
450
421
451
pdb = env->mapDb [strFilename];
@@ -442,7 +472,25 @@ CDB::CDB(CWalletDBWrapper& dbw, const char* pszMode, bool fFlushOnCloseIn) : pdb
442
472
if (ret != 0 ) {
443
473
throw std::runtime_error (strprintf (" CDB: Error %d, can't open database %s" , ret, strFilename));
444
474
}
445
- CheckUniqueFileid (*env, strFilename, *pdb_temp);
475
+
476
+ // Call CheckUniqueFileid on the containing BDB environment to
477
+ // avoid BDB data consistency bugs that happen when different data
478
+ // files in the same environment have the same fileid.
479
+ //
480
+ // Also call CheckUniqueFileid on all the other g_dbenvs to prevent
481
+ // bitcoin from opening the same data file through another
482
+ // environment when the file is referenced through equivalent but
483
+ // not obviously identical symlinked or hard linked or bind mounted
484
+ // paths. In the future a more relaxed check for equal inode and
485
+ // device ids could be done instead, which would allow opening
486
+ // different backup copies of a wallet at the same time. Maybe even
487
+ // more ideally, an exclusive lock for accessing the database could
488
+ // be implemented, so no equality checks are needed at all. (Newer
489
+ // versions of BDB have an set_lk_exclusive method for this
490
+ // purpose, but the older version we use does not.)
491
+ for (auto & env : g_dbenvs) {
492
+ CheckUniqueFileid (env.second , strFilename, *pdb_temp);
493
+ }
446
494
447
495
pdb = pdb_temp.release ();
448
496
env->mapDb [strFilename] = pdb;
@@ -490,7 +538,7 @@ void CDB::Close()
490
538
Flush ();
491
539
492
540
{
493
- LOCK (env-> cs_db );
541
+ LOCK (cs_db);
494
542
--env->mapFileUseCount [strFile];
495
543
}
496
544
}
@@ -518,7 +566,7 @@ bool CDB::Rewrite(CWalletDBWrapper& dbw, const char* pszSkip)
518
566
const std::string& strFile = dbw.strFile ;
519
567
while (true ) {
520
568
{
521
- LOCK (env-> cs_db );
569
+ LOCK (cs_db);
522
570
if (!env->mapFileUseCount .count (strFile) || env->mapFileUseCount [strFile] == 0 ) {
523
571
// Flush log data to the dat file
524
572
env->CloseDb (strFile);
@@ -646,7 +694,7 @@ bool CDB::PeriodicFlush(CWalletDBWrapper& dbw)
646
694
bool ret = false ;
647
695
CDBEnv *env = dbw.env ;
648
696
const std::string& strFile = dbw.strFile ;
649
- TRY_LOCK (bitdb. cs_db ,lockDb);
697
+ TRY_LOCK (cs_db, lockDb);
650
698
if (lockDb)
651
699
{
652
700
// Don't do this if any databases are in use
@@ -694,7 +742,7 @@ bool CWalletDBWrapper::Backup(const std::string& strDest)
694
742
while (true )
695
743
{
696
744
{
697
- LOCK (env-> cs_db );
745
+ LOCK (cs_db);
698
746
if (!env->mapFileUseCount .count (strFile) || env->mapFileUseCount [strFile] == 0 )
699
747
{
700
748
// Flush log data to the dat file
0 commit comments