| 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 |
nothing calls this directly
no test coverage detected