Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1// Copyright (c) 2011 The LevelDB Authors. All rights reserved.2// Use of this source code is governed by a BSD-style license that can be3// found in the LICENSE file. See the AUTHORS file for names of contributors.5#include "db/log_reader.h"7#include <stdio.h>9#include "leveldb/env.h"10#include "util/coding.h"11#include "util/crc32c.h"13namespace leveldb {14namespace log {16Reader::Reporter::~Reporter() = default;18Reader::Reader(SequentialFile* file, Reporter* reporter, bool checksum,19uint64_t initial_offset)20: file_(file),21reporter_(reporter),22checksum_(checksum),23backing_store_(new char[kBlockSize]),24buffer_(),25eof_(false),26last_record_offset_(0),27end_of_buffer_offset_(0),28initial_offset_(initial_offset),29resyncing_(initial_offset > 0) {}31Reader::~Reader() { delete[] backing_store_; }33bool Reader::SkipToInitialBlock() {34const size_t offset_in_block = initial_offset_ % kBlockSize;35uint64_t block_start_location = initial_offset_ - offset_in_block;37// Don't search a block if we'd be in the trailer38if (offset_in_block > kBlockSize - 6) {39block_start_location += kBlockSize;40}42end_of_buffer_offset_ = block_start_location;44// Skip to start of first block that can contain the initial record45if (block_start_location > 0) {46Status skip_status = file_->Skip(block_start_location);47if (!skip_status.ok()) {48ReportDrop(block_start_location, skip_status);49return false;50}51}53return true;54}56bool Reader::ReadRecord(Slice* record, std::string* scratch) {57if (last_record_offset_ < initial_offset_) {58if (!SkipToInitialBlock()) {59return false;60}61}63scratch->clear();64record->clear();65bool in_fragmented_record = false;66// Record offset of the logical record that we're reading67// 0 is a dummy value to make compilers happy68uint64_t prospective_record_offset = 0;70Slice fragment;71while (true) {72const unsigned int record_type = ReadPhysicalRecord(&fragment);74// ReadPhysicalRecord may have only had an empty trailer remaining in its75// internal buffer. Calculate the offset of the next physical record now76// that it has returned, properly accounting for its header size.77uint64_t physical_record_offset =78end_of_buffer_offset_ - buffer_.size() - kHeaderSize - fragment.size();80if (resyncing_) {81if (record_type == kMiddleType) {82continue;83} else if (record_type == kLastType) {84resyncing_ = false;85continue;86} else {87resyncing_ = false;88}89}91switch (record_type) {92case kFullType:93if (in_fragmented_record) {94// Handle bug in earlier versions of log::Writer where95// it could emit an empty kFirstType record at the tail end96// of a block followed by a kFullType or kFirstType record97// at the beginning of the next block.98if (!scratch->empty()) {99ReportCorruption(scratch->size(), "partial record without end(1)");100}101}102prospective_record_offset = physical_record_offset;103scratch->clear();104*record = fragment;105last_record_offset_ = prospective_record_offset;106return true;108case kFirstType:109if (in_fragmented_record) {110// Handle bug in earlier versions of log::Writer where111// it could emit an empty kFirstType record at the tail end112// of a block followed by a kFullType or kFirstType record113// at the beginning of the next block.114if (!scratch->empty()) {115ReportCorruption(scratch->size(), "partial record without end(2)");116}117}118prospective_record_offset = physical_record_offset;119scratch->assign(fragment.data(), fragment.size());120in_fragmented_record = true;121break;123case kMiddleType:124if (!in_fragmented_record) {125ReportCorruption(fragment.size(),126"missing start of fragmented record(1)");127} else {128scratch->append(fragment.data(), fragment.size());129}130break;132case kLastType:133if (!in_fragmented_record) {134ReportCorruption(fragment.size(),135"missing start of fragmented record(2)");136} else {137scratch->append(fragment.data(), fragment.size());138*record = Slice(*scratch);139last_record_offset_ = prospective_record_offset;140return true;141}142break;144case kEof:145if (in_fragmented_record) {146// This can be caused by the writer dying immediately after147// writing a physical record but before completing the next; don't148// treat it as a corruption, just ignore the entire logical record.149scratch->clear();150}151return false;153case kBadRecord:154if (in_fragmented_record) {155ReportCorruption(scratch->size(), "error in middle of record");156in_fragmented_record = false;157scratch->clear();158}159break;161default: {162char buf[40];163snprintf(buf, sizeof(buf), "unknown record type %u", record_type);164ReportCorruption(165(fragment.size() + (in_fragmented_record ? scratch->size() : 0)),166buf);167in_fragmented_record = false;168scratch->clear();169break;170}171}172}173return false;174}176uint64_t Reader::LastRecordOffset() { return last_record_offset_; }178void Reader::ReportCorruption(uint64_t bytes, const char* reason) {179ReportDrop(bytes, Status::Corruption(reason, file_->GetName()));180}182void Reader::ReportDrop(uint64_t bytes, const Status& reason) {183if (reporter_ != nullptr &&184end_of_buffer_offset_ - buffer_.size() - bytes >= initial_offset_) {185reporter_->Corruption(static_cast<size_t>(bytes), reason);186}187}189unsigned int Reader::ReadPhysicalRecord(Slice* result) {190while (true) {191if (buffer_.size() < kHeaderSize) {192if (!eof_) {193// Last read was a full read, so this is a trailer to skip194buffer_.clear();195Status status = file_->Read(kBlockSize, &buffer_, backing_store_);196end_of_buffer_offset_ += buffer_.size();197if (!status.ok()) {198buffer_.clear();199ReportDrop(kBlockSize, status);200eof_ = true;201return kEof;202} else if (buffer_.size() < kBlockSize) {203eof_ = true;204}205continue;206} else {207// Note that if buffer_ is non-empty, we have a truncated header at the208// end of the file, which can be caused by the writer crashing in the209// middle of writing the header. Instead of considering this an error,210// just report EOF.211buffer_.clear();212return kEof;213}214}216// Parse the header217const char* header = buffer_.data();218const uint32_t a = static_cast<uint32_t>(header[4]) & 0xff;219const uint32_t b = static_cast<uint32_t>(header[5]) & 0xff;220const unsigned int type = header[6];221const uint32_t length = a | (b << 8);222if (kHeaderSize + length > buffer_.size()) {223size_t drop_size = buffer_.size();224buffer_.clear();225if (!eof_) {226ReportCorruption(drop_size, "bad record length");227return kBadRecord;228}229// If the end of the file has been reached without reading |length| bytes230// of payload, assume the writer died in the middle of writing the record.231// Don't report a corruption.232return kEof;233}235if (type == kZeroType && length == 0) {236// Skip zero length record without reporting any drops since237// such records are produced by the mmap based writing code in238// env_posix.cc that preallocates file regions.239buffer_.clear();240return kBadRecord;241}243// Check crc244if (checksum_) {245uint32_t expected_crc = crc32c::Unmask(DecodeFixed32(header));246uint32_t actual_crc = crc32c::Value(header + 6, 1 + length);247if (actual_crc != expected_crc) {248// Drop the rest of the buffer since "length" itself may have249// been corrupted and if we trust it, we could find some250// fragment of a real log record that just happens to look
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.