migrate.cpp raw
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 MetaPage(uint32_t expected_page_num) : expected_page_num(expected_page_num) {}
103 MetaPage() = delete;
104
105 template <typename Stream>
106 void Unserialize(Stream& s)
107 {
108 s >> lsn_file;
109 s >> lsn_offset;
110 s >> page_num;
111 s >> magic;
112 s >> version;
113 s >> pagesize;
114 s >> encrypt_algo;
115
116 other_endian = magic == BTREE_MAGIC_OE;
117
118 uint8_t uint8_type;
119 s >> uint8_type;
120 type = static_cast<PageType>(uint8_type);
121
122 s >> metaflags;
123 s >> unused1;
124 s >> free_list;
125 s >> last_page;
126 s >> partitions;
127 s >> key_count;
128 s >> record_count;
129
130 uint32_t uint32_flags;
131 s >> uint32_flags;
132 if (other_endian) {
133 uint32_flags = internal_bswap_32(uint32_flags);
134 }
135 flags = static_cast<BTreeFlags>(uint32_flags);
136
137 s >> uid;
138 s >> unused2;
139 s >> minkey;
140 s >> re_len;
141 s >> re_pad;
142 s >> root;
143 s >> unused3;
144 s >> crypto_magic;
145 s >> trash;
146 s >> iv;
147 s >> chksum;
148
149 if (other_endian) {
150 lsn_file = internal_bswap_32(lsn_file);
151 lsn_offset = internal_bswap_32(lsn_offset);
152 page_num = internal_bswap_32(page_num);
153 magic = internal_bswap_32(magic);
154 version = internal_bswap_32(version);
155 pagesize = internal_bswap_32(pagesize);
156 free_list = internal_bswap_32(free_list);
157 last_page = internal_bswap_32(last_page);
158 partitions = internal_bswap_32(partitions);
159 key_count = internal_bswap_32(key_count);
160 record_count = internal_bswap_32(record_count);
161 unused2 = internal_bswap_32(unused2);
162 minkey = internal_bswap_32(minkey);
163 re_len = internal_bswap_32(re_len);
164 re_pad = internal_bswap_32(re_pad);
165 root = internal_bswap_32(root);
166 crypto_magic = internal_bswap_32(crypto_magic);
167 }
168
169 // Page number must match
170 if (page_num != expected_page_num) {
171 throw std::runtime_error("Meta page number mismatch");
172 }
173
174 // Check magic
175 if (magic != BTREE_MAGIC) {
176 throw std::runtime_error("Not a BDB file");
177 }
178
179 // Only version 9 is supported
180 if (version != 9) {
181 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 if (pagesize < 512 || pagesize > 65536 || (pagesize & (pagesize - 1)) != 0) {
186 throw std::runtime_error("Bad page size");
187 }
188
189 // Page type must be the btree type
190 if (type != PageType::BTREE_META) {
191 throw std::runtime_error("Unexpected page type, should be 9 (BTree Metadata)");
192 }
193
194 // Only supported meta-flag is subdatabase
195 if (flags != BTreeFlags::SUBDB) {
196 throw std::runtime_error("Unexpected database flags, should only be 0x20 (subdatabases)");
197 }
198 }
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 RecordHeader(bool other_endian) : other_endian(other_endian) {}
214 RecordHeader() = delete;
215
216 template <typename Stream>
217 void Unserialize(Stream& s)
218 {
219 s >> len;
220
221 uint8_t uint8_type;
222 s >> uint8_type;
223 type = static_cast<RecordType>(uint8_type & ~static_cast<uint8_t>(RecordType::DELETE));
224 deleted = uint8_type & static_cast<uint8_t>(RecordType::DELETE);
225
226 if (other_endian) {
227 len = internal_bswap_16(len);
228 }
229 }
230 };
231
232 /** Class for data in the record directly */
233 class DataRecord
234 {
235 public:
236 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 void Unserialize(Stream& s)
245 {
246 data.resize(m_header.len);
247 s.read(AsWritableBytes(Span(data.data(), data.size())));
248 }
249 };
250
251 /** Class for records representing internal nodes of the BTree. */
252 class InternalRecord
253 {
254 public:
255 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 void Unserialize(Stream& s)
269 {
270 s >> unused;
271 s >> page_num;
272 s >> records;
273
274 data.resize(m_header.len);
275 s.read(AsWritableBytes(Span(data.data(), data.size())));
276
277 if (m_header.other_endian) {
278 page_num = internal_bswap_32(page_num);
279 records = internal_bswap_32(records);
280 }
281 }
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 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 void Unserialize(Stream& s)
304 {
305 s >> unused2;
306 s >> page_number;
307 s >> item_len;
308
309 if (m_header.other_endian) {
310 page_number = internal_bswap_32(page_number);
311 item_len = internal_bswap_32(item_len);
312 }
313 }
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 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 void Unserialize(Stream& s)
340 {
341 s >> lsn_file;
342 s >> lsn_offset;
343 s >> page_num;
344 s >> prev_page;
345 s >> next_page;
346 s >> entries;
347 s >> hf_offset;
348 s >> level;
349
350 uint8_t uint8_type;
351 s >> uint8_type;
352 type = static_cast<PageType>(uint8_type);
353
354 if (other_endian) {
355 lsn_file = internal_bswap_32(lsn_file);
356 lsn_offset = internal_bswap_32(lsn_offset);
357 page_num = internal_bswap_32(page_num);
358 prev_page = internal_bswap_32(prev_page);
359 next_page = internal_bswap_32(next_page);
360 entries = internal_bswap_16(entries);
361 hf_offset = internal_bswap_16(hf_offset);
362 }
363
364 if (expected_page_num != page_num) {
365 throw std::runtime_error("Page number mismatch");
366 }
367 if ((type != PageType::OVERFLOW_DATA && level < 1) || (type == PageType::OVERFLOW_DATA && level != 0)) {
368 throw std::runtime_error("Bad btree level");
369 }
370 }
371 };
372
373 /** A page of records in the database */
374 class RecordsPage
375 {
376 public:
377 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 void Unserialize(Stream& s)
387 {
388 // Current position within the page
389 int64_t pos = PageHeader::SIZE;
390
391 // Get the items
392 for (uint32_t i = 0; i < m_header.entries; ++i) {
393 // Get the index
394 uint16_t index;
395 s >> index;
396 if (m_header.other_endian) {
397 index = internal_bswap_16(index);
398 }
399 indexes.push_back(index);
400 pos += sizeof(uint16_t);
401
402 // Go to the offset from the index
403 int64_t to_jump = index - pos;
404 if (to_jump < 0) {
405 throw std::runtime_error("Data record position not in page");
406 }
407 s.ignore(to_jump);
408
409 // Read the record
410 RecordHeader rec_hdr(m_header.other_endian);
411 s >> rec_hdr;
412 to_jump += RecordHeader::SIZE;
413
414 switch (rec_hdr.type) {
415 case RecordType::KEYDATA: {
416 DataRecord record(rec_hdr);
417 s >> record;
418 records.emplace_back(record);
419 to_jump += rec_hdr.len;
420 break;
421 }
422 case RecordType::OVERFLOW_DATA: {
423 OverflowRecord record(rec_hdr);
424 s >> record;
425 records.emplace_back(record);
426 to_jump += OverflowRecord::SIZE;
427 break;
428 }
429 default:
430 throw std::runtime_error("Unknown record type in records page");
431 }
432
433 // Go back to the indexes
434 s.seek(-to_jump, SEEK_CUR);
435 }
436 }
437 };
438
439 /** A page containing overflow data */
440 class OverflowPage
441 {
442 public:
443 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 void Unserialize(Stream& s)
457 {
458 data.resize(m_header.hf_offset);
459 s.read(AsWritableBytes(Span(data.data(), data.size())));
460 }
461 };
462
463 /** A page of records in the database */
464 class InternalPage
465 {
466 public:
467 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 void Unserialize(Stream& s)
477 {
478 // Current position within the page
479 int64_t pos = PageHeader::SIZE;
480
481 // Get the items
482 for (uint32_t i = 0; i < m_header.entries; ++i) {
483 // Get the index
484 uint16_t index;
485 s >> index;
486 if (m_header.other_endian) {
487 index = internal_bswap_16(index);
488 }
489 indexes.push_back(index);
490 pos += sizeof(uint16_t);
491
492 // Go to the offset from the index
493 int64_t to_jump = index - pos;
494 if (to_jump < 0) {
495 throw std::runtime_error("Internal record position not in page");
496 }
497 s.ignore(to_jump);
498
499 // Read the record
500 RecordHeader rec_hdr(m_header.other_endian);
501 s >> rec_hdr;
502 to_jump += RecordHeader::SIZE;
503
504 if (rec_hdr.type != RecordType::KEYDATA) {
505 throw std::runtime_error("Unknown record type in internal page");
506 }
507 InternalRecord record(rec_hdr);
508 s >> record;
509 records.emplace_back(record);
510 to_jump += InternalRecord::FIXED_SIZE + rec_hdr.len;
511
512 // Go back to the indexes
513 s.seek(-to_jump, SEEK_CUR);
514 }
515 }
516 };
517
518 static void SeekToPage(AutoFile& s, uint32_t page_num, uint32_t page_size)
519 {
520 int64_t pos = int64_t{page_num} * page_size;
521 s.seek(pos, SEEK_SET);
522 }
523
524 void BerkeleyRODatabase::Open()
525 {
526 // Open the file
527 FILE* file = fsbridge::fopen(m_filepath, "rb");
528 AutoFile db_file(file);
529 if (db_file.IsNull()) {
530 throw std::runtime_error("BerkeleyRODatabase: Failed to open database file");
531 }
532
533 uint32_t page_size = 4096; // Default page size
534
535 // Read the outer metapage
536 // Expected page number is 0
537 MetaPage outer_meta(0);
538 db_file >> outer_meta;
539 page_size = outer_meta.pagesize;
540
541 // Verify the size of the file is a multiple of the page size
542 db_file.seek(0, SEEK_END);
543 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 uint32_t expected_last_page{uint32_t((size / page_size) - 1)};
555 if (outer_meta.last_page != expected_last_page) {
556 throw std::runtime_error("Last page number could not fit in file");
557 }
558
559 // Make sure encryption is disabled
560 if (outer_meta.encrypt_algo != 0) {
561 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 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 uint32_t file;
570 uint32_t offset;
571 SeekToPage(db_file, i, page_size);
572 db_file >> file >> offset;
573 if (outer_meta.other_endian) {
574 file = internal_bswap_32(file);
575 offset = internal_bswap_32(offset);
576 }
577 if (file != 0 || offset != 1) {
578 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 SeekToPage(db_file, outer_meta.root, page_size);
584 PageHeader header(outer_meta.root, outer_meta.other_endian);
585 db_file >> header;
586 if (header.type != PageType::BTREE_LEAF) {
587 throw std::runtime_error("Unexpected outer database root page type");
588 }
589 if (header.entries != 2) {
590 throw std::runtime_error("Unexpected number of entries in outer database root page");
591 }
592 RecordsPage page(header);
593 db_file >> page;
594
595 // First record should be the string "main"
596 if (!std::holds_alternative<DataRecord>(page.records.at(0)) || std::get<DataRecord>(page.records.at(0)).data != SUBDATABASE_NAME) {
597 throw std::runtime_error("Subdatabase has an unexpected name");
598 }
599 // Check length of page number for subdatabase location
600 if (!std::holds_alternative<DataRecord>(page.records.at(1)) || std::get<DataRecord>(page.records.at(1)).m_header.len != 4) {
601 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 uint32_t main_db_page = ReadBE32(std::get<DataRecord>(page.records.at(1)).data.data());
607
608 // The main database is in a page that doesn't exist
609 if (main_db_page > outer_meta.last_page) {
610 throw std::runtime_error("Page number is greater than database last page");
611 }
612
613 // Read the inner metapage
614 SeekToPage(db_file, main_db_page, page_size);
615 MetaPage inner_meta(main_db_page);
616 db_file >> inner_meta;
617
618 if (inner_meta.pagesize != page_size) {
619 throw std::runtime_error("Unexpected page size");
620 }
621
622 if (inner_meta.last_page > outer_meta.last_page) {
623 throw std::runtime_error("Subdatabase last page is greater than database last page");
624 }
625 uint64_t max_data_size = static_cast<uint64_t>(outer_meta.last_page) * page_size;
626
627 // Make sure encryption is disabled
628 if (inner_meta.encrypt_algo != 0) {
629 throw std::runtime_error("BDB builtin encryption is not supported");
630 }
631
632 // Read the root's level from its header
633 // Note that we will read the root page twice in order to process it.
634 SeekToPage(db_file, inner_meta.root, page_size);
635 PageHeader root_header(inner_meta.root, inner_meta.other_endian);
636 db_file >> root_header;
637
638 // Do a DFS through the BTree, starting at root
639 // We track the expected level of each page in order to avoid loops
640 std::vector<std::pair<uint32_t, uint32_t>> pages{{inner_meta.root, root_header.level}};
641 while (pages.size() > 0) {
642 auto [curr_page, expected_level] = pages.back();
643 // It turns out BDB completely ignores this last_page field and doesn't actually update it to the correct
644 // last page. While we should be checking this, we can't.
645 // This is left commented out as a reminder to not accidentally implement this in the future.
646 // if (curr_page > inner_meta.last_page) {
647 // throw std::runtime_error("Page number is greater than subdatabase last page");
648 // }
649 pages.pop_back();
650 SeekToPage(db_file, curr_page, page_size);
651 PageHeader header(curr_page, inner_meta.other_endian);
652 db_file >> header;
653 if (header.level != expected_level) {
654 throw std::runtime_error("BTree page has an unexpected level");
655 }
656 switch (header.type) {
657 case PageType::BTREE_INTERNAL: {
658 InternalPage int_page(header);
659 db_file >> int_page;
660 for (const InternalRecord& rec : int_page.records) {
661 if (rec.m_header.deleted) continue;
662 pages.emplace_back(rec.page_num, header.level - 1);
663 }
664 break;
665 }
666 case PageType::BTREE_LEAF: {
667 if (header.level != 1) {
668 throw std::runtime_error("BTree Leaf page is not at level 1");
669 }
670 RecordsPage rec_page(header);
671 db_file >> rec_page;
672 if (rec_page.records.size() % 2 != 0) {
673 // BDB stores key value pairs in consecutive records, thus an odd number of records is unexpected
674 throw std::runtime_error("Records page has odd number of records");
675 }
676 bool is_key = true;
677 std::vector<std::byte> key;
678 for (const std::variant<DataRecord, OverflowRecord>& rec : rec_page.records) {
679 std::vector<std::byte> data;
680 if (const DataRecord* drec = std::get_if<DataRecord>(&rec)) {
681 if (drec->m_header.deleted) continue;
682 data = drec->data;
683 } else if (const OverflowRecord* orec = std::get_if<OverflowRecord>(&rec)) {
684 if (orec->m_header.deleted) continue;
685 uint32_t next_page = orec->page_number;
686 if (orec->item_len > max_data_size) {
687 throw std::runtime_error("Overflow record has an impossible length");
688 }
689 while (next_page != 0) {
690 SeekToPage(db_file, next_page, page_size);
691 PageHeader opage_header(next_page, inner_meta.other_endian);
692 db_file >> opage_header;
693 if (opage_header.type != PageType::OVERFLOW_DATA) {
694 throw std::runtime_error("Bad overflow record page type");
695 }
696 OverflowPage opage(opage_header);
697 db_file >> opage;
698 data.insert(data.end(), opage.data.begin(), opage.data.end());
699 if (data.size() > orec->item_len) {
700 throw std::runtime_error("Overflow record data is larger than stated size");
701 }
702 next_page = opage_header.next_page;
703 }
704 }
705
706 if (is_key) {
707 key = data;
708 } else {
709 m_records.emplace(SerializeData{key.begin(), key.end()}, SerializeData{data.begin(), data.end()});
710 key.clear();
711 }
712 is_key = !is_key;
713 }
714 break;
715 }
716 default:
717 throw std::runtime_error("Unexpected page type");
718 }
719 }
720 }
721
722 std::unique_ptr<DatabaseBatch> BerkeleyRODatabase::MakeBatch(bool flush_on_close)
723 {
724 return std::make_unique<BerkeleyROBatch>(*this);
725 }
726
727 bool BerkeleyRODatabase::Backup(const std::string& dest) const
728 {
729 fs::path src(m_filepath);
730 fs::path dst(fs::PathFromString(dest));
731
732 if (fs::is_directory(dst)) {
733 dst = BDBDataFile(dst);
734 }
735 try {
736 if (fs::exists(dst) && fs::equivalent(src, dst)) {
737 LogWarning("cannot backup to wallet source file %s", fs::PathToString(dst));
738 return false;
739 }
740
741 fs::copy_file(src, dst, fs::copy_options::overwrite_existing);
742 LogPrintf("copied %s to %s\n", fs::PathToString(m_filepath), fs::PathToString(dst));
743 return true;
744 } catch (const fs::filesystem_error& e) {
745 LogPrintf("error copying %s to %s - %s\n", fs::PathToString(m_filepath), fs::PathToString(dst), fsbridge::get_filesystem_error_message(e));
746 return false;
747 }
748 }
749
750 bool BerkeleyROBatch::ReadKey(DataStream&& key, DataStream& value)
751 {
752 SerializeData key_data{key.begin(), key.end()};
753 const auto it{m_database.m_records.find(key_data)};
754 if (it == m_database.m_records.end()) {
755 return false;
756 }
757 auto val = it->second;
758 value.clear();
759 value.write(Span(val));
760 return true;
761 }
762
763 bool BerkeleyROBatch::HasKey(DataStream&& key)
764 {
765 SerializeData key_data{key.begin(), key.end()};
766 return m_database.m_records.count(key_data) > 0;
767 }
768
769 BerkeleyROCursor::BerkeleyROCursor(const BerkeleyRODatabase& database, Span<const std::byte> prefix)
770 : m_database(database)
771 {
772 std::tie(m_cursor, m_cursor_end) = m_database.m_records.equal_range(BytePrefix{prefix});
773 }
774
775 DatabaseCursor::Status BerkeleyROCursor::Next(DataStream& ssKey, DataStream& ssValue)
776 {
777 if (m_cursor == m_cursor_end) {
778 return DatabaseCursor::Status::DONE;
779 }
780 ssKey.write(Span(m_cursor->first));
781 ssValue.write(Span(m_cursor->second));
782 m_cursor++;
783 return DatabaseCursor::Status::MORE;
784 }
785
786 std::unique_ptr<DatabaseCursor> BerkeleyROBatch::GetNewPrefixCursor(Span<const std::byte> prefix)
787 {
788 return std::make_unique<BerkeleyROCursor>(m_database, prefix);
789 }
790
791 std::unique_ptr<BerkeleyRODatabase> MakeBerkeleyRODatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error)
792 {
793 fs::path data_file = BDBDataFile(path);
794 try {
795 std::unique_ptr<BerkeleyRODatabase> db = std::make_unique<BerkeleyRODatabase>(data_file);
796 status = DatabaseStatus::SUCCESS;
797 return db;
798 } catch (const std::runtime_error& e) {
799 error.original = e.what();
800 status = DatabaseStatus::FAILED_LOAD;
801 return nullptr;
802 }
803 }
804 } // namespace wallet
805