diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/db.cpp | 134 | ||||
| -rw-r--r-- | src/db.h | 6 | ||||
| -rw-r--r-- | src/main_gui.cpp | 32 | ||||
| -rw-r--r-- | src/util.h | 32 |
4 files changed, 201 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 | } | ||
| @@ -403,6 +403,12 @@ public: | |||
| 403 | 403 | ||
| 404 | bool LoadWallet(); | 404 | bool LoadWallet(); |
| 405 | 405 | ||
| 406 | // Writes a wallet.dat that opens on its own, anywhere -- see the definition in | ||
| 407 | // db.cpp for why a plain file copy does not. Still a point-in-time snapshot: | ||
| 408 | // keys are made as needed rather than derived from a seed, so coins paid to an | ||
| 409 | // address created after this call are not spendable from the file it writes. | ||
| 410 | bool BackupWallet(const string& strDest); | ||
| 411 | |||
| 406 | inline bool SetAddressBookName(const string& strAddress, const string& strName) | 412 | inline bool SetAddressBookName(const string& strAddress, const string& strName) |
| 407 | { | 413 | { |
| 408 | return CWalletDB().WriteName(strAddress, strName); | 414 | return CWalletDB().WriteName(strAddress, strName); |
diff --git a/src/main_gui.cpp b/src/main_gui.cpp index c64dca9..976cd42 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp | |||
| @@ -67,6 +67,10 @@ static void PrintUsage() | |||
| 67 | printf(" /port=N (P2P listen port, default 8433)\n"); | 67 | printf(" /port=N (P2P listen port, default 8433)\n"); |
| 68 | printf(" /btfseed=ADDRESS:ENCHEX (extra bootstrap peer, repeatable)\n"); | 68 | printf(" /btfseed=ADDRESS:ENCHEX (extra bootstrap peer, repeatable)\n"); |
| 69 | printf("\n"); | 69 | printf("\n"); |
| 70 | printf("Wallet:\n"); | ||
| 71 | printf(" /backupwallet=FILE (write a wallet.dat that opens on its own,\n"); | ||
| 72 | printf(" then exit; run it again after new addresses)\n"); | ||
| 73 | printf("\n"); | ||
| 70 | printf("Each option also accepts '-' instead of '/'.\n"); | 74 | printf("Each option also accepts '-' instead of '/'.\n"); |
| 71 | } | 75 | } |
| 72 | 76 | ||
| @@ -219,6 +223,28 @@ int main(int argc, char* argv[]) | |||
| 219 | return 1; | 223 | return 1; |
| 220 | } | 224 | } |
| 221 | printf("Height=%d\n", nBestHeight); | 225 | printf("Height=%d\n", nBestHeight); |
| 226 | |||
| 227 | // Before the node opens sockets or touches anything: the wallet is loaded, | ||
| 228 | // which is all a backup needs, and finishing here means the copy is taken | ||
| 229 | // from a quiet directory rather than from under a running node. | ||
| 230 | string strBackup = argval2(argc, argv, "/backupwallet", "-backupwallet"); | ||
| 231 | if (!strBackup.empty()) | ||
| 232 | { | ||
| 233 | bool fOk = BackupWallet(strBackup); | ||
| 234 | if (fOk) | ||
| 235 | { | ||
| 236 | printf("Backed up to %s\n", strBackup.c_str()); | ||
| 237 | printf("This file opens on its own -- it does not need the database/ " | ||
| 238 | "directory beside it.\n"); | ||
| 239 | printf("It is a snapshot: coins paid to addresses created after now are " | ||
| 240 | "not in it, so back up again whenever you receive to a new address.\n"); | ||
| 241 | } | ||
| 242 | else | ||
| 243 | fprintf(stderr, "Backup failed -- nothing was written.\n"); | ||
| 244 | DBFlush(true); | ||
| 245 | return fOk ? 0 : 1; | ||
| 246 | } | ||
| 247 | |||
| 222 | ReacceptWalletTransactions(); | 248 | ReacceptWalletTransactions(); |
| 223 | 249 | ||
| 224 | if (!StartNode(strErrors)) { fprintf(stderr,"StartNode: %s\n",strErrors.c_str()); return 1; } | 250 | if (!StartNode(strErrors)) { fprintf(stderr,"StartNode: %s\n",strErrors.c_str()); return 1; } |
| @@ -249,6 +275,11 @@ int main(int argc, char* argv[]) | |||
| 249 | printf("Running headless. Ctrl-C to stop.\n"); | 275 | printf("Running headless. Ctrl-C to stop.\n"); |
| 250 | while (!fShutdown) Sleep(500); | 276 | while (!fShutdown) Sleep(500); |
| 251 | StopNode(); | 277 | StopNode(); |
| 278 | // Checkpoints, releases wallet.dat from this directory's Berkeley DB | ||
| 279 | // environment, and closes it. Declared since 2009 and never once | ||
| 280 | // called here, which is why a wallet.dat copied elsewhere would not | ||
| 281 | // open -- issue #40. Not optional. | ||
| 282 | DBFlush(true); | ||
| 252 | return 0; | 283 | return 0; |
| 253 | } | 284 | } |
| 254 | 285 | ||
| @@ -258,6 +289,7 @@ int main(int argc, char* argv[]) | |||
| 258 | int ret = RunGUI(argc, argv); | 289 | int ret = RunGUI(argc, argv); |
| 259 | fShutdown = true; | 290 | fShutdown = true; |
| 260 | StopNode(); | 291 | StopNode(); |
| 292 | DBFlush(true); | ||
| 261 | return ret; | 293 | return ret; |
| 262 | #endif | 294 | #endif |
| 263 | } | 295 | } |
| @@ -3,6 +3,11 @@ | |||
| 3 | // file license.txt or http://www.opensource.org/licenses/mit-license.php. | 3 | // file license.txt or http://www.opensource.org/licenses/mit-license.php. |
| 4 | 4 | ||
| 5 | 5 | ||
| 6 | // For abi::__forced_unwind, which CATCH_PRINT_EXCEPTION below must let past. | ||
| 7 | #ifndef _WIN32 | ||
| 8 | #include <cxxabi.h> | ||
| 9 | #endif | ||
| 10 | |||
| 6 | #if defined(_MSC_VER) || defined(__BORLANDC__) | 11 | #if defined(_MSC_VER) || defined(__BORLANDC__) |
| 7 | typedef __int64 int64; | 12 | typedef __int64 int64; |
| 8 | typedef unsigned __int64 uint64; | 13 | typedef unsigned __int64 uint64; |
| @@ -419,7 +424,34 @@ inline void heapchk() | |||
| 419 | } \ | 424 | } \ |
| 420 | } | 425 | } |
| 421 | 426 | ||
| 427 | // The forced-unwind arm has to come first, and it has to rethrow. | ||
| 428 | // | ||
| 429 | // On POSIX, _endthread() is pthread_exit(), and glibc implements that by | ||
| 430 | // throwing abi::__forced_unwind through the stack. It is not a real exception | ||
| 431 | // and catching it without rethrowing is a fatal error by contract: glibc | ||
| 432 | // notices and calls abort(). | ||
| 433 | // | ||
| 434 | // The bare catch(...) below did exactly that. Every shutdown on Linux died with | ||
| 435 | // | ||
| 436 | // UNKNOWN EXCEPTION bitflash-node in ThreadSocketHandler() | ||
| 437 | // FATAL: exception not rethrown | ||
| 438 | // | ||
| 439 | // which looked like nothing worse than an ugly exit, and was not: the process | ||
| 440 | // aborted partway through shutting down, so nothing after StopNode() ran. See | ||
| 441 | // main_gui.cpp, where DBFlush(true) is now called -- it could never have | ||
| 442 | // worked while this abort stood in front of it. Windows is unaffected; there | ||
| 443 | // _endthread() is the CRT's and unwinds nothing. | ||
| 444 | #ifdef _WIN32 | ||
| 445 | #define CATCH_FORCED_UNWIND | ||
| 446 | #else | ||
| 447 | #define CATCH_FORCED_UNWIND \ | ||
| 448 | catch (abi::__forced_unwind&) { \ | ||
| 449 | throw; \ | ||
| 450 | } | ||
| 451 | #endif | ||
| 452 | |||
| 422 | #define CATCH_PRINT_EXCEPTION(pszFn) \ | 453 | #define CATCH_PRINT_EXCEPTION(pszFn) \ |
| 454 | CATCH_FORCED_UNWIND \ | ||
| 423 | catch (std::exception& e) { \ | 455 | catch (std::exception& e) { \ |
| 424 | PrintException(&e, (pszFn)); \ | 456 | PrintException(&e, (pszFn)); \ |
| 425 | } catch (...) { \ | 457 | } catch (...) { \ |