upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/db.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/db.cpp')
-rw-r--r--src/db.cpp134
1 files changed, 131 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}