Branch data Line data Source code
1 : : // Distributed under the MIT software license, see the accompanying
2 : : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
3 : :
4 : : #include <compat/byteswap.h>
5 : : #include <crypto/common.h> // For ReadBE32
6 : : #include <logging.h>
7 : : #include <streams.h>
8 : : #include <util/translation.h>
9 : : #include <wallet/migrate.h>
10 : :
11 : : #include <optional>
12 : : #include <variant>
13 : :
14 : : namespace wallet {
15 : : // Magic bytes in both endianness's
16 : : constexpr uint32_t BTREE_MAGIC = 0x00053162; // If the file endianness matches our system, we see this magic
17 : : constexpr uint32_t BTREE_MAGIC_OE = 0x62310500; // If the file endianness is the other one, we will see this magic
18 : :
19 : : // Subdatabase name
20 : : static const std::vector<std::byte> SUBDATABASE_NAME = {std::byte{'m'}, std::byte{'a'}, std::byte{'i'}, std::byte{'n'}};
21 : :
22 : : enum class PageType : uint8_t {
23 : : /*
24 : : * BDB has several page types, most of which we do not use
25 : : * They are listed here for completeness, but commented out
26 : : * to avoid opening something unintended.
27 : : INVALID = 0, // Invalid page type
28 : : DUPLICATE = 1, // Duplicate. Deprecated and no longer used
29 : : HASH_UNSORTED = 2, // Hash pages. Deprecated.
30 : : RECNO_INTERNAL = 4, // Recno internal
31 : : RECNO_LEAF = 6, // Recno leaf
32 : : HASH_META = 8, // Hash metadata
33 : : QUEUE_META = 10, // Queue Metadata
34 : : QUEUE_DATA = 11, // Queue Data
35 : : DUPLICATE_LEAF = 12, // Off-page duplicate leaf
36 : : HASH_SORTED = 13, // Sorted hash page
37 : : */
38 : : BTREE_INTERNAL = 3, // BTree internal
39 : : BTREE_LEAF = 5, // BTree leaf
40 : : OVERFLOW_DATA = 7, // Overflow
41 : : BTREE_META = 9, // BTree metadata
42 : : };
43 : :
44 : : enum class RecordType : uint8_t {
45 : : KEYDATA = 1,
46 : : // DUPLICATE = 2, Unused as our databases do not support duplicate records
47 : : OVERFLOW_DATA = 3,
48 : : DELETE = 0x80, // Indicate this record is deleted. This is OR'd with the real type.
49 : : };
50 : :
51 : : enum class BTreeFlags : uint32_t {
52 : : /*
53 : : * BTree databases have feature flags, but we do not use them except for
54 : : * subdatabases. The unused flags are included for completeness, but commented out
55 : : * to avoid accidental use.
56 : : DUP = 1, // Duplicates
57 : : RECNO = 2, // Recno tree
58 : : RECNUM = 4, // BTree: Maintain record counts
59 : : FIXEDLEN = 8, // Recno: fixed length records
60 : : RENUMBER = 0x10, // Recno: renumber on insert/delete
61 : : DUPSORT = 0x40, // Duplicates are sorted
62 : : COMPRESS = 0x80, // Compressed
63 : : */
64 : : SUBDB = 0x20, // Subdatabases
65 : : };
66 : :
67 : : /** Berkeley DB BTree metadata page layout */
68 : : class MetaPage
69 : : {
70 : : public:
71 : : uint32_t lsn_file; // Log Sequence Number file
72 : : uint32_t lsn_offset; // Log Sequence Number offset
73 : : uint32_t page_num; // Current page number
74 : : uint32_t magic; // Magic number
75 : : uint32_t version; // Version
76 : : uint32_t pagesize; // Page size
77 : : uint8_t encrypt_algo; // Encryption algorithm
78 : : PageType type; // Page type
79 : : uint8_t metaflags; // Meta-only flags
80 : : uint8_t unused1; // Unused
81 : : uint32_t free_list; // Free list page number
82 : : uint32_t last_page; // Page number of last page in db
83 : : uint32_t partitions; // Number of partitions
84 : : uint32_t key_count; // Cached key count
85 : : uint32_t record_count; // Cached record count
86 : : BTreeFlags flags; // Flags
87 : : std::array<std::byte, 20> uid; // 20 byte unique file ID
88 : : uint32_t unused2; // Unused
89 : : uint32_t minkey; // Minimum key
90 : : uint32_t re_len; // Recno: fixed length record length
91 : : uint32_t re_pad; // Recno: fixed length record pad
92 : : uint32_t root; // Root page number
93 : : char unused3[368]; // 92 * 4 bytes of unused space
94 : : uint32_t crypto_magic; // Crypto magic number
95 : : char trash[12]; // 3 * 4 bytes of trash space
96 : : unsigned char iv[20]; // Crypto IV
97 : : unsigned char chksum[16]; // Checksum
98 : :
99 : : bool other_endian;
100 : : uint32_t expected_page_num;
101 : :
102 : 85 : MetaPage(uint32_t expected_page_num) : expected_page_num(expected_page_num) {}
103 : : MetaPage() = delete;
104 : :
105 : : template <typename Stream>
106 : 85 : void Unserialize(Stream& s)
107 : : {
108 : 85 : s >> lsn_file;
109 : 83 : s >> lsn_offset;
110 : 81 : s >> page_num;
111 : 79 : s >> magic;
112 : 77 : s >> version;
113 : 77 : s >> pagesize;
114 : 77 : s >> encrypt_algo;
115 : :
116 : 76 : other_endian = magic == BTREE_MAGIC_OE;
117 : :
118 : : uint8_t uint8_type;
119 : 75 : s >> uint8_type;
120 : 75 : type = static_cast<PageType>(uint8_type);
121 : :
122 : 75 : s >> metaflags;
123 : 73 : s >> unused1;
124 : 73 : s >> free_list;
125 : 73 : s >> last_page;
126 : 73 : s >> partitions;
127 : 73 : s >> key_count;
128 : 73 : s >> record_count;
129 : :
130 : : uint32_t uint32_flags;
131 : 73 : s >> uint32_flags;
132 [ + + ]: 73 : if (other_endian) {
133 : 70 : uint32_flags = internal_bswap_32(uint32_flags);
134 : : }
135 : 73 : flags = static_cast<BTreeFlags>(uint32_flags);
136 : :
137 : 73 : s >> uid;
138 : 73 : s >> unused2;
139 : 73 : s >> minkey;
140 : 73 : s >> re_len;
141 : 73 : s >> re_pad;
142 : 73 : s >> root;
143 : 73 : s >> unused3;
144 : 73 : s >> crypto_magic;
145 : 73 : s >> trash;
146 : 73 : s >> iv;
147 : 73 : s >> chksum;
148 : :
149 [ + + ]: 73 : if (other_endian) {
150 : 70 : lsn_file = internal_bswap_32(lsn_file);
151 : 70 : lsn_offset = internal_bswap_32(lsn_offset);
152 : 70 : page_num = internal_bswap_32(page_num);
153 : 70 : magic = internal_bswap_32(magic);
154 : 70 : version = internal_bswap_32(version);
155 : 70 : pagesize = internal_bswap_32(pagesize);
156 : 70 : free_list = internal_bswap_32(free_list);
157 : 70 : last_page = internal_bswap_32(last_page);
158 : 70 : partitions = internal_bswap_32(partitions);
159 : 70 : key_count = internal_bswap_32(key_count);
160 : 70 : record_count = internal_bswap_32(record_count);
161 : 70 : unused2 = internal_bswap_32(unused2);
162 : 70 : minkey = internal_bswap_32(minkey);
163 : 70 : re_len = internal_bswap_32(re_len);
164 : 70 : re_pad = internal_bswap_32(re_pad);
165 : 70 : root = internal_bswap_32(root);
166 : 70 : crypto_magic = internal_bswap_32(crypto_magic);
167 : : }
168 : :
169 : : // Page number must match
170 [ + + ]: 73 : if (page_num != expected_page_num) {
171 [ + - ]: 2 : throw std::runtime_error("Meta page number mismatch");
172 : : }
173 : :
174 : : // Check magic
175 [ + + ]: 71 : if (magic != BTREE_MAGIC) {
176 [ + - ]: 2 : throw std::runtime_error("Not a BDB file");
177 : : }
178 : :
179 : : // Only version 9 is supported
180 [ + + ]: 69 : if (version != 9) {
181 [ + - ]: 2 : throw std::runtime_error("Unsupported BDB data file version number");
182 : : }
183 : :
184 : : // Page size must be 512 <= pagesize <= 64k, and be a power of 2
185 [ + + + + ]: 67 : if (pagesize < 512 || pagesize > 65536 || (pagesize & (pagesize - 1)) != 0) {
186 [ + - ]: 6 : throw std::runtime_error("Bad page size");
187 : : }
188 : :
189 : : // Page type must be the btree type
190 [ + + ]: 61 : if (type != PageType::BTREE_META) {
191 [ + - ]: 2 : throw std::runtime_error("Unexpected page type, should be 9 (BTree Metadata)");
192 : : }
193 : :
194 : : // Only supported meta-flag is subdatabase
195 [ + + ]: 59 : if (flags != BTreeFlags::SUBDB) {
196 [ + - ]: 2 : throw std::runtime_error("Unexpected database flags, should only be 0x20 (subdatabases)");
197 : : }
198 : 57 : }
199 : : };
200 : :
201 : : /** General class for records in a BDB BTree database. Contains common fields. */
202 : : class RecordHeader
203 : : {
204 : : public:
205 : : uint16_t len; // Key/data item length
206 : : RecordType type; // Page type (BDB has this include a DELETE FLAG that we track separately)
207 : : bool deleted; // Whether the DELETE flag was set on type
208 : :
209 : : static constexpr size_t SIZE = 3; // The record header is 3 bytes
210 : :
211 : : bool other_endian;
212 : :
213 : 75 : RecordHeader(bool other_endian) : other_endian(other_endian) {}
214 : : RecordHeader() = delete;
215 : :
216 : : template <typename Stream>
217 : 75 : void Unserialize(Stream& s)
218 : : {
219 : 75 : s >> len;
220 : :
221 : : uint8_t uint8_type;
222 : 74 : s >> uint8_type;
223 : 74 : type = static_cast<RecordType>(uint8_type & ~static_cast<uint8_t>(RecordType::DELETE));
224 : 74 : deleted = uint8_type & static_cast<uint8_t>(RecordType::DELETE);
225 : :
226 [ + - ]: 74 : if (other_endian) {
227 : 74 : len = internal_bswap_16(len);
228 : : }
229 : 74 : }
230 : : };
231 : :
232 : : /** Class for data in the record directly */
233 [ + - ]: 194 : class DataRecord
234 : : {
235 : : public:
236 : 66 : DataRecord(const RecordHeader& header) : m_header(header) {}
237 : : DataRecord() = delete;
238 : :
239 : : RecordHeader m_header;
240 : :
241 : : std::vector<std::byte> data; // Variable length key/data item
242 : :
243 : : template <typename Stream>
244 : 66 : void Unserialize(Stream& s)
245 : : {
246 : 66 : data.resize(m_header.len);
247 : 66 : s.read(AsWritableBytes(Span(data.data(), data.size())));
248 : 64 : }
249 : : };
250 : :
251 : : /** Class for records representing internal nodes of the BTree. */
252 : 0 : class InternalRecord
253 : : {
254 : : public:
255 [ # # ]: 0 : InternalRecord(const RecordHeader& header) : m_header(header) {}
256 : : InternalRecord() = delete;
257 : :
258 : : RecordHeader m_header;
259 : :
260 : : uint8_t unused; // Padding, unused
261 : : uint32_t page_num; // Page number of referenced page
262 : : uint32_t records; // Subtree record count
263 : : std::vector<std::byte> data; // Variable length key item
264 : :
265 : : static constexpr size_t FIXED_SIZE = 9; // Size of fixed data is 9 bytes
266 : :
267 : : template <typename Stream>
268 : 0 : void Unserialize(Stream& s)
269 : : {
270 : 0 : s >> unused;
271 : 0 : s >> page_num;
272 : 0 : s >> records;
273 : :
274 : 0 : data.resize(m_header.len);
275 : 0 : s.read(AsWritableBytes(Span(data.data(), data.size())));
276 : :
277 [ # # ]: 0 : if (m_header.other_endian) {
278 : 0 : page_num = internal_bswap_32(page_num);
279 : 0 : records = internal_bswap_32(records);
280 : : }
281 : 0 : }
282 : : };
283 : :
284 : : /** Class for records representing overflow records of the BTree.
285 : : * Overflow records point to a page which contains the data in the record.
286 : : * Those pages may point to further pages with the rest of the data if it does not fit
287 : : * in one page */
288 : : class OverflowRecord
289 : : {
290 : : public:
291 : 6 : OverflowRecord(const RecordHeader& header) : m_header(header) {}
292 : : OverflowRecord() = delete;
293 : :
294 : : RecordHeader m_header;
295 : :
296 : : uint8_t unused2; // Padding, unused
297 : : uint32_t page_number; // Page number where data begins
298 : : uint32_t item_len; // Total length of item
299 : :
300 : : static constexpr size_t SIZE = 9; // Overflow record is always 9 bytes
301 : :
302 : : template <typename Stream>
303 : 6 : void Unserialize(Stream& s)
304 : : {
305 : 6 : s >> unused2;
306 : 6 : s >> page_number;
307 : 6 : s >> item_len;
308 : :
309 [ + - ]: 6 : if (m_header.other_endian) {
310 : 6 : page_number = internal_bswap_32(page_number);
311 : 6 : item_len = internal_bswap_32(item_len);
312 : : }
313 : 6 : }
314 : : };
315 : :
316 : : /** A generic data page in the database. Contains fields common to all data pages. */
317 : : class PageHeader
318 : : {
319 : : public:
320 : : uint32_t lsn_file; // Log Sequence Number file
321 : : uint32_t lsn_offset; // Log Sequence Number offset
322 : : uint32_t page_num; // Current page number
323 : : uint32_t prev_page; // Previous page number
324 : : uint32_t next_page; // Next page number
325 : : uint16_t entries; // Number of items on the page
326 : : uint16_t hf_offset; // High free byte page offset
327 : : uint8_t level; // Btree page level
328 : : PageType type; // Page type
329 : :
330 : : static constexpr int64_t SIZE = 26; // The header is 26 bytes
331 : :
332 : : uint32_t expected_page_num;
333 : : bool other_endian;
334 : :
335 : 49 : PageHeader(uint32_t page_num, bool other_endian) : expected_page_num(page_num), other_endian(other_endian) {}
336 : : PageHeader() = delete;
337 : :
338 : : template <typename Stream>
339 : 49 : void Unserialize(Stream& s)
340 : : {
341 : 49 : s >> lsn_file;
342 : 49 : s >> lsn_offset;
343 : 49 : s >> page_num;
344 : 49 : s >> prev_page;
345 : 49 : s >> next_page;
346 : 49 : s >> entries;
347 : 49 : s >> hf_offset;
348 : 49 : s >> level;
349 : :
350 : : uint8_t uint8_type;
351 : 49 : s >> uint8_type;
352 : 49 : type = static_cast<PageType>(uint8_type);
353 : :
354 [ + - ]: 49 : if (other_endian) {
355 : 49 : lsn_file = internal_bswap_32(lsn_file);
356 : 49 : lsn_offset = internal_bswap_32(lsn_offset);
357 : 49 : page_num = internal_bswap_32(page_num);
358 : 49 : prev_page = internal_bswap_32(prev_page);
359 : 49 : next_page = internal_bswap_32(next_page);
360 : 49 : entries = internal_bswap_16(entries);
361 : 49 : hf_offset = internal_bswap_16(hf_offset);
362 : : }
363 : :
364 [ + + ]: 49 : if (expected_page_num != page_num) {
365 [ + - ]: 2 : throw std::runtime_error("Page number mismatch");
366 : : }
367 [ + + + + : 47 : if ((type != PageType::OVERFLOW_DATA && level < 1) || (type == PageType::OVERFLOW_DATA && level != 0)) {
+ + + + ]
368 [ + - ]: 2 : throw std::runtime_error("Bad btree level");
369 : : }
370 : 45 : }
371 : : };
372 : :
373 : : /** A page of records in the database */
374 : 43 : class RecordsPage
375 : : {
376 : : public:
377 : 43 : RecordsPage(const PageHeader& header) : m_header(header) {}
378 : : RecordsPage() = delete;
379 : :
380 : : PageHeader m_header;
381 : :
382 : : std::vector<uint16_t> indexes;
383 : : std::vector<std::variant<DataRecord, OverflowRecord>> records;
384 : :
385 : : template <typename Stream>
386 : 43 : void Unserialize(Stream& s)
387 : : {
388 : : // Current position within the page
389 : 43 : int64_t pos = PageHeader::SIZE;
390 : :
391 : : // Get the items
392 [ + + ]: 113 : for (uint32_t i = 0; i < m_header.entries; ++i) {
393 : : // Get the index
394 : : uint16_t index;
395 : 78 : s >> index;
396 [ + - ]: 78 : if (m_header.other_endian) {
397 : 78 : index = internal_bswap_16(index);
398 : : }
399 : 78 : indexes.push_back(index);
400 : 78 : pos += sizeof(uint16_t);
401 : :
402 : : // Go to the offset from the index
403 : 78 : int64_t to_jump = index - pos;
404 [ + + ]: 78 : if (to_jump < 0) {
405 [ + - ]: 1 : throw std::runtime_error("Data record position not in page");
406 : : }
407 : 77 : s.ignore(to_jump);
408 : :
409 : : // Read the record
410 : 75 : RecordHeader rec_hdr(m_header.other_endian);
411 : 74 : s >> rec_hdr;
412 : 74 : to_jump += RecordHeader::SIZE;
413 : :
414 [ + + + ]: 74 : switch (rec_hdr.type) {
415 [ + + ]: 66 : case RecordType::KEYDATA: {
416 [ + + ]: 66 : DataRecord record(rec_hdr);
417 : 64 : s >> record;
418 [ + - ]: 64 : records.emplace_back(record);
419 : 64 : to_jump += rec_hdr.len;
420 : : break;
421 : 66 : }
422 : 6 : case RecordType::OVERFLOW_DATA: {
423 : 6 : OverflowRecord record(rec_hdr);
424 : 6 : s >> record;
425 : 6 : records.emplace_back(record);
426 : 6 : to_jump += OverflowRecord::SIZE;
427 : 6 : break;
428 : : }
429 : 2 : default:
430 [ + - ]: 2 : throw std::runtime_error("Unknown record type in records page");
431 : : }
432 : :
433 : : // Go back to the indexes
434 : 70 : s.seek(-to_jump, SEEK_CUR);
435 : : }
436 : 34 : }
437 : : };
438 : :
439 : : /** A page containing overflow data */
440 : 0 : class OverflowPage
441 : : {
442 : : public:
443 : 0 : OverflowPage(const PageHeader& header) : m_header(header) {}
444 : : OverflowPage() = delete;
445 : :
446 : : PageHeader m_header;
447 : :
448 : : // BDB overloads some page fields to store overflow page data
449 : : // hf_offset contains the length of the overflow data stored on this page
450 : : // entries contains a reference count for references to this item
451 : :
452 : : // The overflow data itself. Begins immediately following header
453 : : std::vector<std::byte> data;
454 : :
455 : : template <typename Stream>
456 : 0 : void Unserialize(Stream& s)
457 : : {
458 : 0 : data.resize(m_header.hf_offset);
459 : 0 : s.read(AsWritableBytes(Span(data.data(), data.size())));
460 : 0 : }
461 : : };
462 : :
463 : : /** A page of records in the database */
464 : 0 : class InternalPage
465 : : {
466 : : public:
467 : 0 : InternalPage(const PageHeader& header) : m_header(header) {}
468 : : InternalPage() = delete;
469 : :
470 : : PageHeader m_header;
471 : :
472 : : std::vector<uint16_t> indexes;
473 : : std::vector<InternalRecord> records;
474 : :
475 : : template <typename Stream>
476 : 0 : void Unserialize(Stream& s)
477 : : {
478 : : // Current position within the page
479 : 0 : int64_t pos = PageHeader::SIZE;
480 : :
481 : : // Get the items
482 [ # # ]: 0 : for (uint32_t i = 0; i < m_header.entries; ++i) {
483 : : // Get the index
484 : : uint16_t index;
485 : 0 : s >> index;
486 [ # # ]: 0 : if (m_header.other_endian) {
487 : 0 : index = internal_bswap_16(index);
488 : : }
489 : 0 : indexes.push_back(index);
490 : 0 : pos += sizeof(uint16_t);
491 : :
492 : : // Go to the offset from the index
493 : 0 : int64_t to_jump = index - pos;
494 [ # # ]: 0 : if (to_jump < 0) {
495 [ # # ]: 0 : throw std::runtime_error("Internal record position not in page");
496 : : }
497 : 0 : s.ignore(to_jump);
498 : :
499 : : // Read the record
500 : 0 : RecordHeader rec_hdr(m_header.other_endian);
501 : 0 : s >> rec_hdr;
502 : 0 : to_jump += RecordHeader::SIZE;
503 : :
504 [ # # ]: 0 : if (rec_hdr.type != RecordType::KEYDATA) {
505 [ # # ]: 0 : throw std::runtime_error("Unknown record type in internal page");
506 : : }
507 [ # # ]: 0 : InternalRecord record(rec_hdr);
508 : 0 : s >> record;
509 [ # # ]: 0 : records.emplace_back(record);
510 : 0 : to_jump += InternalRecord::FIXED_SIZE + rec_hdr.len;
511 : :
512 : : // Go back to the indexes
513 [ # # ]: 0 : s.seek(-to_jump, SEEK_CUR);
514 : : }
515 : 0 : }
516 : : };
517 : :
518 : 63 : static void SeekToPage(AutoFile& s, uint32_t page_num, uint32_t page_size)
519 : : {
520 : 63 : int64_t pos = int64_t{page_num} * page_size;
521 : 63 : s.seek(pos, SEEK_SET);
522 : 63 : }
523 : :
524 : 78 : void BerkeleyRODatabase::Open()
525 : : {
526 : : // Open the file
527 : 78 : FILE* file = fsbridge::fopen(m_filepath, "rb");
528 [ + - ]: 78 : AutoFile db_file(file);
529 [ - + ]: 78 : if (db_file.IsNull()) {
530 [ # # ]: 0 : throw std::runtime_error("BerkeleyRODatabase: Failed to open database file");
531 : : }
532 : :
533 : 78 : uint32_t page_size = 4096; // Default page size
534 : :
535 : : // Read the outer metapage
536 : : // Expected page number is 0
537 : 78 : MetaPage outer_meta(0);
538 [ + + ]: 78 : db_file >> outer_meta;
539 : 50 : page_size = outer_meta.pagesize;
540 : :
541 : : // Verify the size of the file is a multiple of the page size
542 [ + - ]: 50 : db_file.seek(0, SEEK_END);
543 [ + - ]: 50 : int64_t size = db_file.tell();
544 : :
545 : : // Since BDB stores everything in a page, the file size should be a multiple of the page size;
546 : : // However, BDB doesn't actually check that this is the case, and enforcing this check results
547 : : // in us rejecting a database that BDB would not, so this check needs to be excluded.
548 : : // This is left commented out as a reminder to not accidentally implement this in the future.
549 : : // if (size % page_size != 0) {
550 : : // throw std::runtime_error("File size is not a multiple of page size");
551 : : // }
552 : :
553 : : // Check the last page number
554 : 50 : uint32_t expected_last_page{uint32_t((size / page_size) - 1)};
555 [ + + ]: 50 : if (outer_meta.last_page != expected_last_page) {
556 [ + - ]: 2 : throw std::runtime_error("Last page number could not fit in file");
557 : : }
558 : :
559 : : // Make sure encryption is disabled
560 [ + + ]: 48 : if (outer_meta.encrypt_algo != 0) {
561 [ + - ]: 1 : throw std::runtime_error("BDB builtin encryption is not supported");
562 : : }
563 : :
564 : : // Check all Log Sequence Numbers (LSN) point to file 0 and offset 1 which indicates that the LSNs were
565 : : // reset and that the log files are not necessary to get all of the data in the database.
566 [ + + ]: 49 : for (uint32_t i = 0; i < outer_meta.last_page; ++i) {
567 : : // The LSN is composed of 2 32-bit ints, the first is a file id, the second an offset
568 : : // It will always be the first 8 bytes of a page, so we deserialize it directly for every page
569 : 7 : uint32_t file;
570 : 7 : uint32_t offset;
571 [ + - ]: 7 : SeekToPage(db_file, i, page_size);
572 [ + + + - ]: 7 : db_file >> file >> offset;
573 [ + - ]: 6 : if (outer_meta.other_endian) {
574 : 6 : file = internal_bswap_32(file);
575 : 6 : offset = internal_bswap_32(offset);
576 : : }
577 [ + + + + ]: 6 : if (file != 0 || offset != 1) {
578 [ + - ]: 4 : throw std::runtime_error("LSNs are not reset, this database is not completely flushed. Please reopen then close the database with a version that has BDB support");
579 : : }
580 : : }
581 : :
582 : : // Read the root page
583 [ + - ]: 42 : SeekToPage(db_file, outer_meta.root, page_size);
584 : 42 : PageHeader header(outer_meta.root, outer_meta.other_endian);
585 [ + + ]: 42 : db_file >> header;
586 [ + + ]: 38 : if (header.type != PageType::BTREE_LEAF) {
587 [ + - ]: 1 : throw std::runtime_error("Unexpected outer database root page type");
588 : : }
589 [ + + ]: 37 : if (header.entries != 2) {
590 [ + - ]: 1 : throw std::runtime_error("Unexpected number of entries in outer database root page");
591 : : }
592 [ + + ]: 36 : RecordsPage page(header);
593 [ + + ]: 36 : db_file >> page;
594 : :
595 : : // First record should be the string "main"
596 [ + - + + : 52 : if (!std::holds_alternative<DataRecord>(page.records.at(0)) || std::get<DataRecord>(page.records.at(0)).data != SUBDATABASE_NAME) {
- + + + ]
597 [ + - ]: 14 : throw std::runtime_error("Subdatabase has an unexpected name");
598 : : }
599 : : // Check length of page number for subdatabase location
600 [ + - + + : 24 : if (!std::holds_alternative<DataRecord>(page.records.at(1)) || std::get<DataRecord>(page.records.at(1)).m_header.len != 4) {
- + + + ]
601 [ + - ]: 4 : throw std::runtime_error("Subdatabase page number has unexpected length");
602 : : }
603 : :
604 : : // Read subdatabase page number
605 : : // It is written as a big endian 32 bit number
606 [ + - - + : 9 : uint32_t main_db_page = ReadBE32(UCharCast(std::get<DataRecord>(page.records.at(1)).data.data()));
+ + ]
607 : :
608 : : // The main database is in a page that doesn't exist
609 [ + + ]: 9 : if (main_db_page > outer_meta.last_page) {
610 [ + - ]: 2 : throw std::runtime_error("Page number is greater than database last page");
611 : : }
612 : :
613 : : // Read the inner metapage
614 [ + - ]: 7 : SeekToPage(db_file, main_db_page, page_size);
615 : 7 : MetaPage inner_meta(main_db_page);
616 [ + - ]: 7 : db_file >> inner_meta;
617 : :
618 [ - + ]: 7 : if (inner_meta.pagesize != page_size) {
619 [ # # ]: 0 : throw std::runtime_error("Unexpected page size");
620 : : }
621 : :
622 [ - + ]: 7 : if (inner_meta.last_page > outer_meta.last_page) {
623 [ # # ]: 0 : throw std::runtime_error("Subdatabase last page is greater than database last page");
624 : : }
625 : :
626 : : // Make sure encryption is disabled
627 [ - + ]: 7 : if (inner_meta.encrypt_algo != 0) {
628 [ # # ]: 0 : throw std::runtime_error("BDB builtin encryption is not supported");
629 : : }
630 : :
631 : : // Do a DFS through the BTree, starting at root
632 [ + - ]: 7 : std::vector<uint32_t> pages{inner_meta.root};
633 [ + + ]: 14 : while (pages.size() > 0) {
634 : 7 : uint32_t curr_page = pages.back();
635 : : // It turns out BDB completely ignores this last_page field and doesn't actually update it to the correct
636 : : // last page. While we should be checking this, we can't.
637 : : // This is left commented out as a reminder to not accidentally implement this in the future.
638 : : // if (curr_page > inner_meta.last_page) {
639 : : // throw std::runtime_error("Page number is greater than subdatabase last page");
640 : : // }
641 [ + - ]: 7 : pages.pop_back();
642 [ + - ]: 7 : SeekToPage(db_file, curr_page, page_size);
643 : 7 : PageHeader header(curr_page, inner_meta.other_endian);
644 [ + - ]: 7 : db_file >> header;
645 [ - + - ]: 7 : switch (header.type) {
646 : 0 : case PageType::BTREE_INTERNAL: {
647 [ # # ]: 0 : InternalPage int_page(header);
648 [ # # ]: 0 : db_file >> int_page;
649 [ # # ]: 0 : for (const InternalRecord& rec : int_page.records) {
650 [ # # ]: 0 : if (rec.m_header.deleted) continue;
651 [ # # ]: 0 : pages.push_back(rec.page_num);
652 : : }
653 : 0 : break;
654 : 0 : }
655 : 7 : case PageType::BTREE_LEAF: {
656 [ + - ]: 7 : RecordsPage rec_page(header);
657 [ + - ]: 7 : db_file >> rec_page;
658 [ - + ]: 7 : if (rec_page.records.size() % 2 != 0) {
659 : : // BDB stores key value pairs in consecutive records, thus an odd number of records is unexpected
660 [ # # ]: 0 : throw std::runtime_error("Records page has odd number of records");
661 : : }
662 : 7 : bool is_key = true;
663 : 7 : std::vector<std::byte> key;
664 [ + + ]: 21 : for (const std::variant<DataRecord, OverflowRecord>& rec : rec_page.records) {
665 : 14 : std::vector<std::byte> data;
666 [ + - ]: 14 : if (const DataRecord* drec = std::get_if<DataRecord>(&rec)) {
667 [ + + ]: 14 : if (drec->m_header.deleted) continue;
668 [ + - ]: 8 : data = drec->data;
669 [ # # ]: 0 : } else if (const OverflowRecord* orec = std::get_if<OverflowRecord>(&rec)) {
670 [ # # ]: 0 : if (orec->m_header.deleted) continue;
671 : 0 : uint32_t next_page = orec->page_number;
672 [ # # ]: 0 : while (next_page != 0) {
673 [ # # ]: 0 : SeekToPage(db_file, next_page, page_size);
674 : 0 : PageHeader opage_header(next_page, inner_meta.other_endian);
675 [ # # ]: 0 : db_file >> opage_header;
676 [ # # ]: 0 : if (opage_header.type != PageType::OVERFLOW_DATA) {
677 [ # # ]: 0 : throw std::runtime_error("Bad overflow record page type");
678 : : }
679 [ # # ]: 0 : OverflowPage opage(opage_header);
680 [ # # ]: 0 : db_file >> opage;
681 [ # # ]: 0 : data.insert(data.end(), opage.data.begin(), opage.data.end());
682 : 0 : next_page = opage_header.next_page;
683 : 0 : }
684 : : }
685 : :
686 [ + + ]: 8 : if (is_key) {
687 [ + - ]: 5 : key = data;
688 : : } else {
689 [ + - + - : 3 : m_records.emplace(SerializeData{key.begin(), key.end()}, SerializeData{data.begin(), data.end()});
+ - ]
690 [ + - ]: 3 : key.clear();
691 : : }
692 : 8 : is_key = !is_key;
693 : 14 : }
694 : 7 : break;
695 : 14 : }
696 : 0 : default:
697 [ # # ]: 0 : throw std::runtime_error("Unexpected page type");
698 : : }
699 : : }
700 : 50 : }
701 : :
702 : 7 : std::unique_ptr<DatabaseBatch> BerkeleyRODatabase::MakeBatch(bool flush_on_close)
703 : : {
704 : 7 : return std::make_unique<BerkeleyROBatch>(*this);
705 : : }
706 : :
707 : 0 : bool BerkeleyRODatabase::Backup(const std::string& dest) const
708 : : {
709 : 0 : fs::path src(m_filepath);
710 [ # # ]: 0 : fs::path dst(fs::PathFromString(dest));
711 : :
712 [ # # # # ]: 0 : if (fs::is_directory(dst)) {
713 [ # # ]: 0 : dst = BDBDataFile(dst);
714 : : }
715 : 0 : try {
716 [ # # # # : 0 : if (fs::exists(dst) && fs::equivalent(src, dst)) {
# # # # ]
717 [ # # # # ]: 0 : LogPrintf("cannot backup to wallet source file %s\n", fs::PathToString(dst));
718 : 0 : return false;
719 : : }
720 : :
721 [ # # ]: 0 : fs::copy_file(src, dst, fs::copy_options::overwrite_existing);
722 [ # # # # : 0 : LogPrintf("copied %s to %s\n", fs::PathToString(m_filepath), fs::PathToString(dst));
# # ]
723 : 0 : return true;
724 [ - - ]: 0 : } catch (const fs::filesystem_error& e) {
725 [ - - - - : 0 : LogPrintf("error copying %s to %s - %s\n", fs::PathToString(m_filepath), fs::PathToString(dst), fsbridge::get_filesystem_error_message(e));
- - ]
726 : 0 : return false;
727 [ - - ]: 0 : }
728 : 0 : }
729 : :
730 : 0 : bool BerkeleyROBatch::ReadKey(DataStream&& key, DataStream& value)
731 : : {
732 : 0 : SerializeData key_data{key.begin(), key.end()};
733 : 0 : const auto it{m_database.m_records.find(key_data)};
734 [ # # ]: 0 : if (it == m_database.m_records.end()) {
735 : : return false;
736 : : }
737 [ # # ]: 0 : auto val = it->second;
738 [ # # ]: 0 : value.clear();
739 [ # # ]: 0 : value.write(Span(val));
740 : 0 : return true;
741 : 0 : }
742 : :
743 : 0 : bool BerkeleyROBatch::HasKey(DataStream&& key)
744 : : {
745 : 0 : SerializeData key_data{key.begin(), key.end()};
746 : 0 : return m_database.m_records.count(key_data) > 0;
747 : 0 : }
748 : :
749 : 7 : BerkeleyROCursor::BerkeleyROCursor(const BerkeleyRODatabase& database, Span<const std::byte> prefix)
750 [ + - ]: 7 : : m_database(database)
751 : : {
752 [ + - ]: 7 : std::tie(m_cursor, m_cursor_end) = m_database.m_records.equal_range(BytePrefix{prefix});
753 : 7 : }
754 : :
755 : 10 : DatabaseCursor::Status BerkeleyROCursor::Next(DataStream& ssKey, DataStream& ssValue)
756 : : {
757 [ + + ]: 10 : if (m_cursor == m_cursor_end) {
758 : : return DatabaseCursor::Status::DONE;
759 : : }
760 : 3 : ssKey.write(Span(m_cursor->first));
761 : 3 : ssValue.write(Span(m_cursor->second));
762 : 3 : m_cursor++;
763 : 3 : return DatabaseCursor::Status::MORE;
764 : : }
765 : :
766 : 0 : std::unique_ptr<DatabaseCursor> BerkeleyROBatch::GetNewPrefixCursor(Span<const std::byte> prefix)
767 : : {
768 : 0 : return std::make_unique<BerkeleyROCursor>(m_database, prefix);
769 : : }
770 : :
771 : 78 : std::unique_ptr<BerkeleyRODatabase> MakeBerkeleyRODatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error)
772 : : {
773 : 78 : fs::path data_file = BDBDataFile(path);
774 : 78 : try {
775 [ + + ]: 78 : std::unique_ptr<BerkeleyRODatabase> db = std::make_unique<BerkeleyRODatabase>(data_file);
776 : 7 : status = DatabaseStatus::SUCCESS;
777 : 7 : return db;
778 [ - + ]: 71 : } catch (const std::runtime_error& e) {
779 [ + - ]: 71 : error.original = e.what();
780 : 71 : status = DatabaseStatus::FAILED_LOAD;
781 : 71 : return nullptr;
782 : 71 : }
783 : 78 : }
784 : : } // namespace wallet
|