db.cpp 14.2 KB
Newer Older
s_nakamoto's avatar
s_nakamoto committed
1
// Copyright (c) 2009-2010 Satoshi Nakamoto
2
// Copyright (c) 2009-2014 The Bitcoin developers
s_nakamoto's avatar
s_nakamoto committed
3
// Distributed under the MIT/X11 software license, see the accompanying
Fordy's avatar
Fordy committed
4
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
s_nakamoto's avatar
s_nakamoto committed
5

6
#include "db.h"
7

8
#include "addrman.h"
9
10
11
#include "hash.h"
#include "protocol.h"
#include "util.h"
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
12
#include "utilstrencodings.h"
13
14

#include <stdint.h>
s_nakamoto's avatar
s_nakamoto committed
15

Pieter Wuille's avatar
Pieter Wuille committed
16
#ifndef WIN32
17
#include <sys/stat.h>
Pieter Wuille's avatar
Pieter Wuille committed
18
19
#endif

20
#include <boost/filesystem.hpp>
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
21
#include <boost/thread.hpp>
22
#include <boost/version.hpp>
Philip Kaufmann's avatar
Philip Kaufmann committed
23

24
25
26
using namespace std;
using namespace boost;

s_nakamoto's avatar
s_nakamoto committed
27
28
29
30
31
32
33
34

unsigned int nWalletDBUpdated;


//
// CDB
//

35
CDBEnv bitdb;
s_nakamoto's avatar
s_nakamoto committed
36

37
void CDBEnv::EnvShutdown()
38
39
40
41
42
{
    if (!fDbEnvInit)
        return;

    fDbEnvInit = false;
43
44
    int ret = dbenv.close(0);
    if (ret != 0)
45
        LogPrintf("CDBEnv::EnvShutdown : Error %d shutting down database environment: %s\n", ret, DbEnv::strerror(ret));
46
    if (!fMockDb)
47
        DbEnv(0).remove(strPath.c_str(), 0);
48
49
}

50
CDBEnv::CDBEnv() : dbenv(DB_CXX_NO_EXCEPTIONS)
s_nakamoto's avatar
s_nakamoto committed
51
{
52
53
    fDbEnvInit = false;
    fMockDb = false;
s_nakamoto's avatar
s_nakamoto committed
54
55
}

56
57
58
59
60
61
62
63
64
CDBEnv::~CDBEnv()
{
    EnvShutdown();
}

void CDBEnv::Close()
{
    EnvShutdown();
}
s_nakamoto's avatar
s_nakamoto committed
65

66
bool CDBEnv::Open(const boost::filesystem::path& pathIn)
67
68
69
70
{
    if (fDbEnvInit)
        return true;

Gavin Andresen's avatar
Gavin Andresen committed
71
    boost::this_thread::interruption_point();
72

73
74
    strPath = pathIn.string();
    boost::filesystem::path pathLogDir = pathIn / "database";
75
    TryCreateDirectory(pathLogDir);
76
77
    boost::filesystem::path pathErrorFile = pathIn / "db.log";
    LogPrintf("CDBEnv::Open: LogDir=%s ErrorFile=%s\n", pathLogDir.string(), pathErrorFile.string());
78

Jeff Garzik's avatar
Jeff Garzik committed
79
80
81
82
    unsigned int nEnvFlags = 0;
    if (GetBoolArg("-privdb", true))
        nEnvFlags |= DB_PRIVATE;

83
    dbenv.set_lg_dir(pathLogDir.string().c_str());
84
85
86
    dbenv.set_cachesize(0, 0x100000, 1); // 1 MiB should be enough for just the wallet
    dbenv.set_lg_bsize(0x10000);
    dbenv.set_lg_max(1048576);
87
88
    dbenv.set_lk_max_locks(40000);
    dbenv.set_lk_max_objects(40000);
89
90
91
92
    dbenv.set_errfile(fopen(pathErrorFile.string().c_str(), "a")); /// debug
    dbenv.set_flags(DB_AUTO_COMMIT, 1);
    dbenv.set_flags(DB_TXN_WRITE_NOSYNC, 1);
    dbenv.log_set_config(DB_LOG_AUTO_REMOVE, 1);
93
    int ret = dbenv.open(strPath.c_str(),
94
95
96
97
98
99
100
101
102
                         DB_CREATE |
                             DB_INIT_LOCK |
                             DB_INIT_LOG |
                             DB_INIT_MPOOL |
                             DB_INIT_TXN |
                             DB_THREAD |
                             DB_RECOVER |
                             nEnvFlags,
                         S_IRUSR | S_IWUSR);
103
    if (ret != 0)
104
        return error("CDBEnv::Open : Error %d opening database environment: %s\n", ret, DbEnv::strerror(ret));
105
106

    fDbEnvInit = true;
107
    fMockDb = false;
108
109
110
    return true;
}

111
112
113
void CDBEnv::MakeMock()
{
    if (fDbEnvInit)
114
        throw runtime_error("CDBEnv::MakeMock : Already initialized");
115

Gavin Andresen's avatar
Gavin Andresen committed
116
    boost::this_thread::interruption_point();
117

118
    LogPrint("db", "CDBEnv::MakeMock\n");
119
120

    dbenv.set_cachesize(1, 0, 1);
121
    dbenv.set_lg_bsize(10485760 * 4);
122
123
124
125
126
127
    dbenv.set_lg_max(10485760);
    dbenv.set_lk_max_locks(10000);
    dbenv.set_lk_max_objects(10000);
    dbenv.set_flags(DB_AUTO_COMMIT, 1);
    dbenv.log_set_config(DB_LOG_IN_MEMORY, 1);
    int ret = dbenv.open(NULL,
128
129
130
131
132
133
134
135
                         DB_CREATE |
                             DB_INIT_LOCK |
                             DB_INIT_LOG |
                             DB_INIT_MPOOL |
                             DB_INIT_TXN |
                             DB_THREAD |
                             DB_PRIVATE,
                         S_IRUSR | S_IWUSR);
136
    if (ret > 0)
137
        throw runtime_error(strprintf("CDBEnv::MakeMock : Error %d opening database environment.", ret));
138
139
140
141
142

    fDbEnvInit = true;
    fMockDb = true;
}

143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
CDBEnv::VerifyResult CDBEnv::Verify(std::string strFile, bool (*recoverFunc)(CDBEnv& dbenv, std::string strFile))
{
    LOCK(cs_db);
    assert(mapFileUseCount.count(strFile) == 0);

    Db db(&dbenv, 0);
    int result = db.verify(strFile.c_str(), NULL, NULL, 0);
    if (result == 0)
        return VERIFY_OK;
    else if (recoverFunc == NULL)
        return RECOVER_FAIL;

    // Try to recover:
    bool fRecovered = (*recoverFunc)(*this, strFile);
    return (fRecovered ? RECOVER_OK : RECOVER_FAIL);
}

160
bool CDBEnv::Salvage(std::string strFile, bool fAggressive, std::vector<CDBEnv::KeyValPair>& vResult)
161
162
163
164
165
{
    LOCK(cs_db);
    assert(mapFileUseCount.count(strFile) == 0);

    u_int32_t flags = DB_SALVAGE;
166
167
    if (fAggressive)
        flags |= DB_AGGRESSIVE;
168
169
170
171
172

    stringstream strDump;

    Db db(&dbenv, 0);
    int result = db.verify(strFile.c_str(), NULL, &strDump, flags);
173
    if (result == DB_VERIFY_BAD) {
174
        LogPrintf("CDBEnv::Salvage : Database salvage found errors, all data may not be recoverable.\n");
175
        if (!fAggressive) {
176
            LogPrintf("CDBEnv::Salvage : Rerun with aggressive mode to ignore errors and continue.\n");
177
178
179
            return false;
        }
    }
180
    if (result != 0 && result != DB_VERIFY_BAD) {
181
        LogPrintf("CDBEnv::Salvage : Database salvage failed with result %d.\n", result);
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
        return false;
    }

    // Format of bdb dump is ascii lines:
    // header lines...
    // HEADER=END
    // hexadecimal key
    // hexadecimal value
    // ... repeated
    // DATA=END

    string strLine;
    while (!strDump.eof() && strLine != "HEADER=END")
        getline(strDump, strLine); // Skip past header

    std::string keyHex, valueHex;
198
    while (!strDump.eof() && keyHex != "DATA=END") {
199
        getline(strDump, keyHex);
crowning-'s avatar
crowning- committed
200
        if (keyHex != "DATA=END") {
201
            getline(strDump, valueHex);
202
            vResult.push_back(make_pair(ParseHex(keyHex), ParseHex(valueHex)));
203
204
205
206
207
208
209
        }
    }

    return (result == 0);
}


210
void CDBEnv::CheckpointLSN(const std::string& strFile)
211
212
{
    dbenv.txn_checkpoint(0, 0, 0);
213
214
    if (fMockDb)
        return;
215
216
    dbenv.lsn_reset(strFile.c_str(), 0);
}
s_nakamoto's avatar
s_nakamoto committed
217
218


219
CDB::CDB(const std::string& strFilename, const char* pszMode) : pdb(NULL), activeTxn(NULL)
s_nakamoto's avatar
s_nakamoto committed
220
221
{
    int ret;
222
    fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));
223
    if (strFilename.empty())
s_nakamoto's avatar
s_nakamoto committed
224
225
        return;

226
    bool fCreate = strchr(pszMode, 'c') != NULL;
s_nakamoto's avatar
s_nakamoto committed
227
228
229
230
231
    unsigned int nFlags = DB_THREAD;
    if (fCreate)
        nFlags |= DB_CREATE;

    {
232
233
        LOCK(bitdb.cs_db);
        if (!bitdb.Open(GetDataDir()))
234
            throw runtime_error("CDB : Failed to open database environment.");
s_nakamoto's avatar
s_nakamoto committed
235

236
        strFile = strFilename;
237
238
        ++bitdb.mapFileUseCount[strFile];
        pdb = bitdb.mapDb[strFile];
239
        if (pdb == NULL) {
240
            pdb = new Db(&bitdb.dbenv, 0);
s_nakamoto's avatar
s_nakamoto committed
241

242
            bool fMockDb = bitdb.IsMock();
243
244
            if (fMockDb) {
                DbMpoolFile* mpf = pdb->get_mpf();
245
246
                ret = mpf->set_flags(DB_MPOOL_NOFILE, 1);
                if (ret != 0)
247
                    throw runtime_error(strprintf("CDB : Failed to configure for no temp file backing for database %s", strFile));
248
249
            }

250
251
            ret = pdb->open(NULL,                               // Txn pointer
                            fMockDb ? NULL : strFile.c_str(),   // Filename
252
                            fMockDb ? strFile.c_str() : "main", // Logical db name
253
254
                            DB_BTREE,                           // Database type
                            nFlags,                             // Flags
s_nakamoto's avatar
s_nakamoto committed
255
256
                            0);

257
            if (ret != 0) {
s_nakamoto's avatar
s_nakamoto committed
258
259
                delete pdb;
                pdb = NULL;
xanatos's avatar
xanatos committed
260
                --bitdb.mapFileUseCount[strFile];
s_nakamoto's avatar
s_nakamoto committed
261
                strFile = "";
262
                throw runtime_error(strprintf("CDB : Error %d, can't open database %s", ret, strFile));
s_nakamoto's avatar
s_nakamoto committed
263
264
            }

265
            if (fCreate && !Exists(string("version"))) {
s_nakamoto's avatar
s_nakamoto committed
266
267
                bool fTmp = fReadOnly;
                fReadOnly = false;
268
                WriteVersion(CLIENT_VERSION);
s_nakamoto's avatar
s_nakamoto committed
269
270
271
                fReadOnly = fTmp;
            }

272
            bitdb.mapDb[strFile] = pdb;
s_nakamoto's avatar
s_nakamoto committed
273
274
275
276
        }
    }
}

277
void CDB::Flush()
s_nakamoto's avatar
s_nakamoto committed
278
{
279
    if (activeTxn)
280
        return;
s_nakamoto's avatar
s_nakamoto committed
281
282
283

    // Flush database activity from memory pool to disk log
    unsigned int nMinutes = 0;
284
285
    if (fReadOnly)
        nMinutes = 1;
286

287
    bitdb.dbenv.txn_checkpoint(nMinutes ? GetArg("-dblogsize", 100) * 1024 : 0, nMinutes, 0);
288
289
290
291
292
293
294
295
296
297
298
299
}

void CDB::Close()
{
    if (!pdb)
        return;
    if (activeTxn)
        activeTxn->abort();
    activeTxn = NULL;
    pdb = NULL;

    Flush();
s_nakamoto's avatar
s_nakamoto committed
300

301
    {
302
        LOCK(bitdb.cs_db);
303
        --bitdb.mapFileUseCount[strFile];
304
    }
s_nakamoto's avatar
s_nakamoto committed
305
306
}

307
void CDBEnv::CloseDb(const string& strFile)
s_nakamoto's avatar
s_nakamoto committed
308
309
{
    {
310
        LOCK(cs_db);
311
        if (mapDb[strFile] != NULL) {
s_nakamoto's avatar
s_nakamoto committed
312
313
314
315
316
317
318
319
320
            // Close the database handle
            Db* pdb = mapDb[strFile];
            pdb->close(0);
            delete pdb;
            mapDb[strFile] = NULL;
        }
    }
}

321
322
323
324
325
326
327
328
329
bool CDBEnv::RemoveDb(const string& strFile)
{
    this->CloseDb(strFile);

    LOCK(cs_db);
    int rc = dbenv.dbremove(NULL, strFile.c_str(), NULL, DB_AUTO_COMMIT);
    return (rc == 0);
}

330
bool CDB::Rewrite(const string& strFile, const char* pszSkip)
Pieter Wuille's avatar
Pieter Wuille committed
331
{
332
    while (true) {
Pieter Wuille's avatar
Pieter Wuille committed
333
        {
334
            LOCK(bitdb.cs_db);
335
            if (!bitdb.mapFileUseCount.count(strFile) || bitdb.mapFileUseCount[strFile] == 0) {
Pieter Wuille's avatar
Pieter Wuille committed
336
                // Flush log data to the dat file
337
                bitdb.CloseDb(strFile);
338
                bitdb.CheckpointLSN(strFile);
339
                bitdb.mapFileUseCount.erase(strFile);
Pieter Wuille's avatar
Pieter Wuille committed
340
341

                bool fSuccess = true;
342
                LogPrintf("CDB::Rewrite : Rewriting %s...\n", strFile);
343
                string strFileRes = strFile + ".rewrite";
Pieter Wuille's avatar
Pieter Wuille committed
344
345
                { // surround usage of db with extra {}
                    CDB db(strFile.c_str(), "r");
346
                    Db* pdbCopy = new Db(&bitdb.dbenv, 0);
347

348
349
350
351
352
                    int ret = pdbCopy->open(NULL,               // Txn pointer
                                            strFileRes.c_str(), // Filename
                                            "main",             // Logical db name
                                            DB_BTREE,           // Database type
                                            DB_CREATE,          // Flags
Pieter Wuille's avatar
Pieter Wuille committed
353
                                            0);
354
                    if (ret > 0) {
355
                        LogPrintf("CDB::Rewrite : Can't create database file %s\n", strFileRes);
Pieter Wuille's avatar
Pieter Wuille committed
356
357
                        fSuccess = false;
                    }
358

Pieter Wuille's avatar
Pieter Wuille committed
359
360
                    Dbc* pcursor = db.GetCursor();
                    if (pcursor)
361
                        while (fSuccess) {
362
363
                            CDataStream ssKey(SER_DISK, CLIENT_VERSION);
                            CDataStream ssValue(SER_DISK, CLIENT_VERSION);
Pieter Wuille's avatar
Pieter Wuille committed
364
                            int ret = db.ReadAtCursor(pcursor, ssKey, ssValue, DB_NEXT);
365
                            if (ret == DB_NOTFOUND) {
Pieter Wuille's avatar
Pieter Wuille committed
366
367
                                pcursor->close();
                                break;
368
                            } else if (ret != 0) {
Pieter Wuille's avatar
Pieter Wuille committed
369
370
371
372
373
374
375
                                pcursor->close();
                                fSuccess = false;
                                break;
                            }
                            if (pszSkip &&
                                strncmp(&ssKey[0], pszSkip, std::min(ssKey.size(), strlen(pszSkip))) == 0)
                                continue;
376
                            if (strncmp(&ssKey[0], "\x07version", 8) == 0) {
Pieter Wuille's avatar
Pieter Wuille committed
377
378
                                // Update version:
                                ssValue.clear();
379
                                ssValue << CLIENT_VERSION;
Pieter Wuille's avatar
Pieter Wuille committed
380
381
382
383
384
385
                            }
                            Dbt datKey(&ssKey[0], ssKey.size());
                            Dbt datValue(&ssValue[0], ssValue.size());
                            int ret2 = pdbCopy->put(NULL, &datKey, &datValue, DB_NOOVERWRITE);
                            if (ret2 > 0)
                                fSuccess = false;
386
                        }
387
                    if (fSuccess) {
Pieter Wuille's avatar
Pieter Wuille committed
388
                        db.Close();
389
                        bitdb.CloseDb(strFile);
Pieter Wuille's avatar
Pieter Wuille committed
390
                        if (pdbCopy->close(0))
Pieter Wuille's avatar
Pieter Wuille committed
391
                            fSuccess = false;
Pieter Wuille's avatar
Pieter Wuille committed
392
                        delete pdbCopy;
Pieter Wuille's avatar
Pieter Wuille committed
393
394
                    }
                }
395
                if (fSuccess) {
396
                    Db dbA(&bitdb.dbenv, 0);
Pieter Wuille's avatar
Pieter Wuille committed
397
398
                    if (dbA.remove(strFile.c_str(), NULL, 0))
                        fSuccess = false;
399
                    Db dbB(&bitdb.dbenv, 0);
Pieter Wuille's avatar
Pieter Wuille committed
400
401
402
403
                    if (dbB.rename(strFileRes.c_str(), NULL, strFile.c_str(), 0))
                        fSuccess = false;
                }
                if (!fSuccess)
404
                    LogPrintf("CDB::Rewrite : Failed to rewrite database file %s\n", strFileRes);
Pieter Wuille's avatar
Pieter Wuille committed
405
406
407
                return fSuccess;
            }
        }
408
        MilliSleep(100);
Pieter Wuille's avatar
Pieter Wuille committed
409
410
411
412
413
    }
    return false;
}


414
void CDBEnv::Flush(bool fShutdown)
s_nakamoto's avatar
s_nakamoto committed
415
{
416
    int64_t nStart = GetTimeMillis();
417
418
    // Flush log data to the actual data file on all files that are not in use
    LogPrint("db", "CDBEnv::Flush : Flush(%s)%s\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started");
s_nakamoto's avatar
s_nakamoto committed
419
420
421
    if (!fDbEnvInit)
        return;
    {
422
        LOCK(cs_db);
s_nakamoto's avatar
s_nakamoto committed
423
        map<string, int>::iterator mi = mapFileUseCount.begin();
424
        while (mi != mapFileUseCount.end()) {
s_nakamoto's avatar
s_nakamoto committed
425
426
            string strFile = (*mi).first;
            int nRefCount = (*mi).second;
427
            LogPrint("db", "CDBEnv::Flush : Flushing %s (refcount = %d)...\n", strFile, nRefCount);
428
            if (nRefCount == 0) {
s_nakamoto's avatar
s_nakamoto committed
429
430
                // Move log data to the dat file
                CloseDb(strFile);
431
                LogPrint("db", "CDBEnv::Flush : %s checkpoint\n", strFile);
s_nakamoto's avatar
s_nakamoto committed
432
                dbenv.txn_checkpoint(0, 0, 0);
433
                LogPrint("db", "CDBEnv::Flush : %s detach\n", strFile);
434
435
                if (!fMockDb)
                    dbenv.lsn_reset(strFile.c_str(), 0);
436
                LogPrint("db", "CDBEnv::Flush : %s closed\n", strFile);
s_nakamoto's avatar
s_nakamoto committed
437
                mapFileUseCount.erase(mi++);
438
            } else
s_nakamoto's avatar
s_nakamoto committed
439
440
                mi++;
        }
441
        LogPrint("db", "CDBEnv::Flush : Flush(%s)%s took %15dms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started", GetTimeMillis() - nStart);
442
        if (fShutdown) {
s_nakamoto's avatar
s_nakamoto committed
443
            char** listp;
444
            if (mapFileUseCount.empty()) {
s_nakamoto's avatar
s_nakamoto committed
445
                dbenv.log_archive(&listp, DB_ARCH_REMOVE);
446
                Close();
447
                if (!fMockDb)
448
                    boost::filesystem::remove_all(boost::filesystem::path(strPath) / "database");
449
            }
s_nakamoto's avatar
s_nakamoto committed
450
451
452
        }
    }
}