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