upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/db.cpp134
-rw-r--r--src/db.h6
-rw-r--r--src/main_gui.cpp32
-rw-r--r--src/util.h32
4 files changed, 201 insertions, 3 deletions
diff --git a/src/db.cpp b/src/db.cpp
index d43e4ef..fc5f180 100644
--- a/src/db.cpp
+++ b/src/db.cpp
@@ -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.
650bool 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}
diff --git a/src/db.h b/src/db.h
index 6dc3a4f..987786c 100644
--- a/src/db.h
+++ b/src/db.h
@@ -403,6 +403,12 @@ public:
403 403
404bool LoadWallet(); 404bool 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.
410bool BackupWallet(const string& strDest);
411
406inline bool SetAddressBookName(const string& strAddress, const string& strName) 412inline 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}
diff --git a/src/util.h b/src/util.h
index 797ed27..25ef118 100644
--- a/src/util.h
+++ b/src/util.h
@@ -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__)
7typedef __int64 int64; 12typedef __int64 int64;
8typedef unsigned __int64 uint64; 13typedef 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 (...) { \