diff options
Diffstat (limited to 'src/db.cpp')
| -rw-r--r-- | src/db.cpp | 134 |
1 files changed, 131 insertions, 3 deletions
| @@ -59,7 +59,12 @@ CDB::CDB(const char* pszFile, const char* pszMode, bool fTxn) : pdb(NULL) | |||
| 59 | { | 59 | { |
| 60 | string strAppDir = GetAppDir(); | 60 | string strAppDir = GetAppDir(); |
| 61 | string strLogDir = strAppDir + "/database"; | 61 | string strLogDir = strAppDir + "/database"; |
| 62 | _mkdir(strLogDir.c_str()); | 62 | |
| 63 | // _mkdir returns 0 only when it actually created the directory, so | ||
| 64 | // this is also the test for "this environment did not exist until | ||
| 65 | // now". See the recovery block after dbenv.open() for why we care. | ||
| 66 | bool fFreshEnv = (_mkdir(strLogDir.c_str()) == 0); | ||
| 67 | |||
| 63 | printf("dbenv.open strAppDir=%s\n", strAppDir.c_str()); | 68 | printf("dbenv.open strAppDir=%s\n", strAppDir.c_str()); |
| 64 | 69 | ||
| 65 | dbenv.set_lg_dir(strLogDir.c_str()); | 70 | dbenv.set_lg_dir(strLogDir.c_str()); |
| @@ -81,6 +86,41 @@ CDB::CDB(const char* pszFile, const char* pszMode, bool fTxn) : pdb(NULL) | |||
| 81 | if (ret > 0) | 86 | if (ret > 0) |
| 82 | throw runtime_error(strprintf("CDB() : error %d opening database environment\n", ret)); | 87 | throw runtime_error(strprintf("CDB() : error %d opening database environment\n", ret)); |
| 83 | fDbEnvInit = true; | 88 | fDbEnvInit = true; |
| 89 | |||
| 90 | // A Berkeley DB file records, in every page header, a log sequence | ||
| 91 | // number belonging to the environment that wrote it. Carried into a | ||
| 92 | // different environment those numbers refer to logs that do not | ||
| 93 | // exist, and BDB refuses to write: "Db::put: Invalid argument", | ||
| 94 | // thrown from a path that used to abort the process. | ||
| 95 | // | ||
| 96 | // That is what happens when someone backs up wallet.dat on its own. | ||
| 97 | // It is the obviously important file, it is the only one people | ||
| 98 | // copy, and the environment it is welded to lives in a database/ | ||
| 99 | // subdirectory nobody has reason to suspect. The file is intact -- | ||
| 100 | // same bytes, same keys -- and the wallet still will not open. | ||
| 101 | // Issue #40 reports exactly this, after the fact. | ||
| 102 | // | ||
| 103 | // lsn_reset() exists for precisely this migration, so do it for | ||
| 104 | // them. A fresh environment beside pre-existing .dat files means | ||
| 105 | // those files came from somewhere else; in the normal case the | ||
| 106 | // environment is already there and none of this runs. | ||
| 107 | if (fFreshEnv) | ||
| 108 | { | ||
| 109 | static const char* pszBdbFiles[] = { "wallet.dat", "blkindex.dat" }; | ||
| 110 | for (int i = 0; i < (int)(sizeof(pszBdbFiles)/sizeof(pszBdbFiles[0])); i++) | ||
| 111 | { | ||
| 112 | const char* pszName = pszBdbFiles[i]; | ||
| 113 | if (!FileExists((strAppDir + "/" + pszName).c_str())) | ||
| 114 | continue; | ||
| 115 | try | ||
| 116 | { | ||
| 117 | if (dbenv.lsn_reset(pszName, 0) == 0) | ||
| 118 | printf("CDB() : adopted %s from another environment " | ||
| 119 | "(log sequence numbers reset)\n", pszName); | ||
| 120 | } | ||
| 121 | catch (...) { } // best effort; the open below reports real trouble | ||
| 122 | } | ||
| 123 | } | ||
| 84 | } | 124 | } |
| 85 | 125 | ||
| 86 | strFile = pszFile; | 126 | strFile = pszFile; |
| @@ -137,7 +177,14 @@ void DBFlush(bool fShutdown) | |||
| 137 | printf("DBFlush(%s)\n", fShutdown ? "true" : "false"); | 177 | printf("DBFlush(%s)\n", fShutdown ? "true" : "false"); |
| 138 | CRITICAL_BLOCK(cs_db) | 178 | CRITICAL_BLOCK(cs_db) |
| 139 | { | 179 | { |
| 140 | dbenv.txn_checkpoint(0, 0, 0); | 180 | // This runs on the way out. Anything that throws here and is not caught |
| 181 | // takes the process down before the wallet has been put down cleanly, | ||
| 182 | // which is the difference between a portable wallet.dat and one welded | ||
| 183 | // to this directory -- the lsn_reset just below is what frees it. | ||
| 184 | try { dbenv.txn_checkpoint(0, 0, 0); } | ||
| 185 | catch (const std::exception& e) | ||
| 186 | { printf("DBFlush() : checkpoint failed: %s\n", e.what()); } | ||
| 187 | |||
| 141 | map<string, int>::iterator mi = mapFileUseCount.begin(); | 188 | map<string, int>::iterator mi = mapFileUseCount.begin(); |
| 142 | while (mi != mapFileUseCount.end()) | 189 | while (mi != mapFileUseCount.end()) |
| 143 | { | 190 | { |
| @@ -145,7 +192,9 @@ void DBFlush(bool fShutdown) | |||
| 145 | int nRefCount = (*mi).second; | 192 | int nRefCount = (*mi).second; |
| 146 | if (nRefCount == 0) | 193 | if (nRefCount == 0) |
| 147 | { | 194 | { |
| 148 | dbenv.lsn_reset(strFile.c_str(), 0); | 195 | try { dbenv.lsn_reset(strFile.c_str(), 0); } |
| 196 | catch (const std::exception& e) | ||
| 197 | { printf("DBFlush() : lsn_reset(%s) failed: %s\n", strFile.c_str(), e.what()); } | ||
| 149 | mapFileUseCount.erase(mi++); | 198 | mapFileUseCount.erase(mi++); |
| 150 | } | 199 | } |
| 151 | else | 200 | else |
| @@ -580,3 +629,82 @@ bool LoadWallet() | |||
| 580 | 629 | ||
| 581 | return true; | 630 | return true; |
| 582 | } | 631 | } |
| 632 | |||
| 633 | |||
| 634 | // Write a copy of wallet.dat that can be opened anywhere. | ||
| 635 | // | ||
| 636 | // A plain file copy is not a backup here. Berkeley DB stamps every page with a | ||
| 637 | // log sequence number tied to the environment that wrote it, so the copy only | ||
| 638 | // works beside the database/ directory it grew up with -- which is why backing | ||
| 639 | // up "just wallet.dat", the one file anybody would think to save, produces | ||
| 640 | // something that will not open. See the recovery block in CDB::CDB and #40. | ||
| 641 | // | ||
| 642 | // So: flush outstanding writes, copy, then clear the copy's log sequence | ||
| 643 | // numbers. What lands on disk is a wallet.dat that stands on its own. | ||
| 644 | // | ||
| 645 | // This is still a point-in-time snapshot. Keys are generated as they are | ||
| 646 | // needed, not derived from a seed, so coins paid to an address created after | ||
| 647 | // this file was written are not spendable from it. Backing up once is not | ||
| 648 | // enough, and that is a property of the wallet format rather than of this | ||
| 649 | // function. | ||
| 650 | bool BackupWallet(const string& strDest) | ||
| 651 | { | ||
| 652 | if (strDest.empty()) | ||
| 653 | return error("BackupWallet() : no destination given\n"); | ||
| 654 | |||
| 655 | string strSrc = GetAppDir() + "/wallet.dat"; | ||
| 656 | if (!FileExists(strSrc.c_str())) | ||
| 657 | return error("BackupWallet() : %s does not exist\n", strSrc.c_str()); | ||
| 658 | |||
| 659 | CRITICAL_BLOCK(cs_db) | ||
| 660 | { | ||
| 661 | // Committing first, so the copy is not missing the newest records. | ||
| 662 | // Both calls can throw, and a failed backup must not take the node | ||
| 663 | // down with it. | ||
| 664 | try { dbenv.txn_checkpoint(0, 0, 0); } | ||
| 665 | catch (const std::exception& e) | ||
| 666 | { return error("BackupWallet() : checkpoint failed: %s\n", e.what()); } | ||
| 667 | |||
| 668 | try | ||
| 669 | { | ||
| 670 | FILE* pfIn = fopen(strSrc.c_str(), "rb"); | ||
| 671 | if (!pfIn) | ||
| 672 | return error("BackupWallet() : cannot read %s\n", strSrc.c_str()); | ||
| 673 | FILE* pfOut = fopen(strDest.c_str(), "wb"); | ||
| 674 | if (!pfOut) | ||
| 675 | { | ||
| 676 | fclose(pfIn); | ||
| 677 | return error("BackupWallet() : cannot write %s\n", strDest.c_str()); | ||
| 678 | } | ||
| 679 | |||
| 680 | char buf[65536]; | ||
| 681 | size_t n; | ||
| 682 | bool fOk = true; | ||
| 683 | while ((n = fread(buf, 1, sizeof(buf), pfIn)) > 0) | ||
| 684 | if (fwrite(buf, 1, n, pfOut) != n) { fOk = false; break; } | ||
| 685 | if (ferror(pfIn)) | ||
| 686 | fOk = false; | ||
| 687 | fclose(pfIn); | ||
| 688 | // Flushed and closed before lsn_reset touches it. | ||
| 689 | if (fclose(pfOut) != 0) | ||
| 690 | fOk = false; | ||
| 691 | |||
| 692 | if (!fOk) | ||
| 693 | { | ||
| 694 | remove(strDest.c_str()); // half a wallet is worse than none | ||
| 695 | return error("BackupWallet() : copy to %s failed\n", strDest.c_str()); | ||
| 696 | } | ||
| 697 | |||
| 698 | // Without this the copy is welded to this node's database/ dir. | ||
| 699 | int ret = dbenv.lsn_reset(strDest.c_str(), 0); | ||
| 700 | if (ret != 0) | ||
| 701 | printf("BackupWallet() : warning -- lsn_reset returned %d; the copy " | ||
| 702 | "may only open beside this node's database/ directory\n", ret); | ||
| 703 | } | ||
| 704 | catch (const std::exception& e) | ||
| 705 | { return error("BackupWallet() : %s\n", e.what()); } | ||
| 706 | } | ||
| 707 | |||
| 708 | printf("BackupWallet() : wrote %s\n", strDest.c_str()); | ||
| 709 | return true; | ||
| 710 | } | ||