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