// Copyright 2019 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "sql/recover_module/payload.h" #include #include #include #include #include "base/check_op.h" #include "sql/recover_module/btree.h" #include "sql/recover_module/integers.h" #include "sql/recover_module/pager.h" #include "third_party/sqlite/sqlite3.h" namespace sql { namespace recover { namespace { // The size of page IDs pointing to overflow pages. constexpr int kPageIdSize = sizeof(int32_t); // The largest page header size. Inner B-tree pages use this size. constexpr int kMaxPageOverhead = 12; // Maximum number of bytes in a cell used by header and trailer. // // The maximum overhead is incurred by cells in leaf table B-tree pages. // * 2-byte cell pointer, in the cell pointer array // * 9-byte row ID, for the maximum varint size // * 8-byte payload size, (varint size for maximum payload size of 2 ** 48) // * 4-byte first overflow page ID constexpr int kMaxCellOverhead = 23; // Knob used to trade off between having more rows in a tree page and // avoiding the use of overflow pages for large values. The knob value is // stored in the database header. // // In SQLite 3.6 and above, the knob was fixed to the value below. static constexpr int kLeafPayloadFraction = 32; // Denominator used by all load fractions in SQLite's B-tree logic. static constexpr int kPayloadFractionDenominator = 255; // The maximum size of a payload on a leaf page. // // Records whose size exceeds this limit spill over to overflow pages. // // The return value is guaranteed to be at least // LeafPayloadReader::kMinInlineSize, and at most the database page size. int MaxInlinePayloadSize(int page_size) { DCHECK_GE(page_size, DatabasePageReader::kMinUsablePageSize); DCHECK_LE(page_size, DatabasePageReader::kMaxPageSize); const int max_inline_payload_size = page_size - kMaxPageOverhead - kMaxCellOverhead; DCHECK_GE(max_inline_payload_size, LeafPayloadReader::kMinInlineSize); static_assert( DatabasePageReader::kMinPageSize - kMaxPageOverhead - kMaxCellOverhead > LeafPayloadReader::kMinInlineSize, "The DCHECK above may fail"); return max_inline_payload_size; } // The minimum size of a payload on a B-tree page. // // Records that spill over to overflow pages are guaranteed to have at least // these many bytes stored in the B-tree page. // // The return value is guaranteed to be at least // LeafPayloadReader::kMinInlineSize, and at most // MaxInlinePayloadSize(page_size). int MinInlinePayloadSize(int page_size) { DCHECK_GE(page_size, DatabasePageReader::kMinUsablePageSize); DCHECK_LE(page_size, DatabasePageReader::kMaxPageSize); const int min_inline_payload_size = ((page_size - kMaxPageOverhead) * kLeafPayloadFraction) / kPayloadFractionDenominator - kMaxCellOverhead; static_assert((DatabasePageReader::kMaxPageSize - kMaxPageOverhead) * kLeafPayloadFraction <= std::numeric_limits::max(), "The |min_inline_payload_size| computation above may overflow"); DCHECK_GE(min_inline_payload_size, LeafPayloadReader::kMinInlineSize); static_assert(((DatabasePageReader::kMinUsablePageSize - kMaxPageOverhead) * kLeafPayloadFraction) / kPayloadFractionDenominator - kMaxCellOverhead >= LeafPayloadReader::kMinInlineSize, "The DCHECK above may fail"); // The minimum inline payload size is ((P - 12) * 32) / 255 - 23. This is // smaller or equal to ((P - 12) * 255) / 255 - 23, which is P - 35. This is // the maximum payload size. DCHECK_LE(min_inline_payload_size, MaxInlinePayloadSize(page_size)); return min_inline_payload_size; } // The maximum size of a payload on an overflow page. // // The return value is guaranteed to be positive, and at most equal to the page // size. int MaxOverflowPayloadSize(int page_size) { DCHECK_GE(page_size, DatabasePageReader::kMinUsablePageSize); DCHECK_LE(page_size, DatabasePageReader::kMaxPageSize); // Each overflow page starts with a 32-bit integer pointing to the next // overflow page. The rest of the page stores payload bytes. const int max_overflow_payload_size = page_size - 4; DCHECK_GT(max_overflow_payload_size, 0); static_assert(DatabasePageReader::kMinUsablePageSize > 4, "The DCHECK above may fail"); DCHECK_LE(max_overflow_payload_size, DatabasePageReader::kMaxPageSize); return max_overflow_payload_size; } } // namespace LeafPayloadReader::LeafPayloadReader(DatabasePageReader* db_reader) : db_reader_(db_reader), page_id_(DatabasePageReader::kInvalidPageId) {} LeafPayloadReader::~LeafPayloadReader() = default; bool LeafPayloadReader::Initialize(int64_t payload_size, int payload_offset) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); payload_size_ = payload_size; inline_payload_offset_ = payload_offset; page_id_ = db_reader_->page_id(); const int page_size = db_reader_->page_size(); const int max_inline_payload_size = MaxInlinePayloadSize(page_size); if (payload_size <= max_inline_payload_size) { // The payload fits inside the page. inline_payload_size_ = static_cast(payload_size); overflow_page_count_ = 0; } else { const int min_inline_payload_size = MinInlinePayloadSize(page_size); // The payload size is bigger than the maximum inline payload size, so it // must be bigger than the minimum payload size. This check verifies that // the subtractions below have non-negative results. DCHECK_GT(payload_size, min_inline_payload_size); // Payload sizes are upper-bounded by the page size. static_assert( DatabasePageReader::kMaxPageSize * 2 <= std::numeric_limits::max(), "The additions below may overflow"); // Ideally, all bytes in the overflow pages would be used by the payload. // Check if this can be accomplished within the other payload constraints. max_overflow_payload_size_ = MaxOverflowPayloadSize(page_size); const int64_t efficient_overflow_page_count = (payload_size - min_inline_payload_size) / max_overflow_payload_size_; const int efficient_overflow_spill = (payload_size - min_inline_payload_size) % max_overflow_payload_size_; const int efficient_inline_payload_size = min_inline_payload_size + efficient_overflow_spill; if (efficient_inline_payload_size <= max_inline_payload_size) { inline_payload_size_ = efficient_inline_payload_size; overflow_page_count_ = efficient_overflow_page_count; DCHECK_EQ( 0, (payload_size - inline_payload_size_) % max_overflow_payload_size_) << "Overflow pages not fully packed"; } else { inline_payload_size_ = min_inline_payload_size; overflow_page_count_ = efficient_overflow_page_count + 1; } DCHECK_LE(inline_payload_size_, max_inline_payload_size); DCHECK_EQ(overflow_page_count_, (payload_size - inline_payload_size_ + (max_overflow_payload_size_ - 1)) / max_overflow_payload_size_) << "Incorect overflow page count calculation"; } DCHECK_LE(inline_payload_size_, payload_size); DCHECK_LE(inline_payload_size_, page_size); const int first_overflow_page_id_size = (overflow_page_count_ == 0) ? 0 : kPageIdSize; if (inline_payload_offset_ + inline_payload_size_ + first_overflow_page_id_size > page_size) { // Corruption can result in overly large payload sizes. Reject the obvious // case where the in-page payload extends past the end of the page. page_id_ = DatabasePageReader::kInvalidPageId; return false; } overflow_page_ids_.clear(); overflow_page_ids_.reserve(overflow_page_count_); return true; } bool LeafPayloadReader::ReadPayload(int64_t offset, int64_t size, uint8_t* buffer) { DCHECK_GE(offset, 0); DCHECK_LT(offset, payload_size_); DCHECK_GT(size, 0); DCHECK_LE(offset + size, payload_size_); DCHECK(buffer != nullptr); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(page_id_ != DatabasePageReader::kInvalidPageId) << "Initialize() not called, or last call did not succeed"; if (offset < inline_payload_size_) { // The read overlaps the payload bytes stored in the B-tree page. if (db_reader_->ReadPage(page_id_) != SQLITE_OK) return false; const int page_size = db_reader_->page_size(); // The static_cast is safe because inline_payload_size_ is smaller than a // SQLite page size, which is a 32-bit integer. const int read_offset = inline_payload_offset_ + static_cast(offset); DCHECK_LE(read_offset, page_size); const int read_size = (static_cast(offset) + size <= inline_payload_size_) ? static_cast(size) : inline_payload_size_ - static_cast(offset); DCHECK_LE(read_offset + read_size, page_size); std::copy(db_reader_->page_data() + read_offset, db_reader_->page_data() + read_offset + read_size, buffer); if (read_size == size) { // The read is entirely inside the B-tree page. return true; } offset += read_size; DCHECK_EQ(offset, inline_payload_size_); DCHECK_GT(size, read_size); size -= read_size; buffer += read_size; } // The read is entirely in overflow pages. DCHECK_GE(offset, inline_payload_size_); while (size > 0) { const int overflow_page_index = (offset - inline_payload_size_) / max_overflow_payload_size_; DCHECK_LT(overflow_page_index, overflow_page_count_); const int overflow_page_offset = (offset - inline_payload_size_) % max_overflow_payload_size_; while (overflow_page_ids_.size() <= static_cast(overflow_page_index)) { if (!PopulateNextOverflowPageId()) return false; } const int page_id = overflow_page_ids_[overflow_page_index]; if (db_reader_->ReadPage(page_id) != SQLITE_OK) return false; const int page_size = db_reader_->page_size(); const int read_offset = kPageIdSize + overflow_page_offset; DCHECK_LE(read_offset, page_size); const int read_size = std::min(page_size - read_offset, size); DCHECK_LE(read_offset + read_size, page_size); std::copy(db_reader_->page_data() + read_offset, db_reader_->page_data() + read_offset + read_size, buffer); offset += read_size; DCHECK_GE(size, read_size); size -= read_size; buffer += read_size; } return true; } const uint8_t* LeafPayloadReader::ReadInlinePayload() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(page_id_ != DatabasePageReader::kInvalidPageId) << "Initialize() not called, or last call did not succeed"; if (db_reader_->ReadPage(page_id_) != SQLITE_OK) return nullptr; return db_reader_->page_data() + inline_payload_offset_; } bool LeafPayloadReader::PopulateNextOverflowPageId() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_LT(overflow_page_ids_.size(), static_cast(overflow_page_count_)); int page_id_offset; if (overflow_page_ids_.empty()) { // The first overflow page ID is right after the payload's inline bytes. page_id_offset = inline_payload_offset_ + inline_payload_size_; if (db_reader_->ReadPage(page_id_) != SQLITE_OK) return false; } else { // Overflow pages start with the ID of the next overflow page. page_id_offset = 0; if (db_reader_->ReadPage(overflow_page_ids_.back()) != SQLITE_OK) return false; } DCHECK_LE(page_id_offset + kPageIdSize, db_reader_->page_size()); const int next_page_id = LoadBigEndianInt32(db_reader_->page_data() + page_id_offset); if (!DatabasePageReader::IsValidPageId(next_page_id)) { // The overflow page is corrupted. return false; } overflow_page_ids_.push_back(next_page_id); return true; } } // namespace recover } // namespace sql