// Copyright (c) HashiCorp, Inc// SPDX-License-Identifier: MPL-2.0package segmentimport ()const (// MaxEntrySize is the largest we allow any single raft log entry to be. This // is larger than our raft implementation ever allows so seems safe to encode // statically for now. We could make this configurable. It's main purpose it // to limit allocation when reading entries back if their lengths are // corrupted.MaxEntrySize = 64 * 1024 * 1024// 64 MiB// minBufSize is the size we allocate read and write buffers. Setting it // larger wastes more memory but increases the chances that we'll read the // whole frame in a single shot and not need a second allocation and trip to // the disk. minBufSize = 64 * 1024 fileHeaderLen = 32 version = 0 magic = 0x58eb6b0d// Note that this must remain a power of 2 to ensure aligning to this also // aligns to sector boundaries. frameHeaderLen = 8)const ( // Start iota from 0FrameInvaliduint8 = iotaFrameEntryFrameIndexFrameCommit)var (// ErrTooBig indicates that the caller tried to write a logEntry with a // payload that's larger than we are prepared to support.ErrTooBig = errors.New("entries larger than 64MiB are not supported"))/* File Header functions 0 1 2 3 4 5 6 7 8 +------+------+------+------+------+------+------+------+ | Magic | Reserved | Vsn | +------+------+------+------+------+------+------+------+ | BaseIndex | +------+------+------+------+------+------+------+------+ | SegmentID | +------+------+------+------+------+------+------+------+ | Codec (unused) | +------+------+------+------+------+------+------+------+*/// writeFileHeader writes a file header into buf for the given file metadata.func writeFileHeader( []byte, types.SegmentInfo) error {iflen() < fileHeaderLen {returnio.ErrShortBuffer }binary.LittleEndian.PutUint32([0:4], magic)// Explicitly zero Reserved bytes just in case [4] = 0 [5] = 0 [6] = 0 [7] = versionbinary.LittleEndian.PutUint64([8:16], .BaseIndex)binary.LittleEndian.PutUint64([16:24], .ID)// I removed the codec option from this library since we let the caller // define how they want to encode/decode bytes. Keep a placeholder so that // nothing breaks.binary.LittleEndian.PutUint64([24:32], 1)returnnil}// readFileHeader reads a file header from buf.func readFileHeader( []byte) (*types.SegmentInfo, error) {iflen() < fileHeaderLen {returnnil, io.ErrShortBuffer }vartypes.SegmentInfo := binary.LittleEndian.Uint64([0:8])if != magic {returnnil, types.ErrCorrupt }if [7] != version {returnnil, types.ErrCorrupt } .BaseIndex = binary.LittleEndian.Uint64([8:16]) .ID = binary.LittleEndian.Uint64([16:24])return &, nil}func validateFileHeader(, types.SegmentInfo) error {if .ID != .ID {returnfmt.Errorf("%w: segment header ID %x doesn't match metadata %x",types.ErrCorrupt, .ID, .ID) }if .BaseIndex != .BaseIndex {returnfmt.Errorf("%w: segment header BaseIndex %d doesn't match metadata %d",types.ErrCorrupt, .BaseIndex, .BaseIndex) }returnnil}/* Frame Functions 0 1 2 3 4 5 6 7 8 +------+------+------+------+------+------+------+------+ | Type | Reserved | Length/CRC | +------+------+------+------+------+------+------+------+*/type frameHeader struct { typ uint8 len uint32 crc uint32}func writeFrame( []byte, frameHeader, []byte) error {iflen() < encodedFrameSize(int(.len)) {returnio.ErrShortBuffer }if := writeFrameHeader(, ); != nil {return }copy([frameHeaderLen:], [:.len])// Explicitly write null bytes for padding := padLen(int(.len))for := 0; < ; ++ { [frameHeaderLen+int(.len)+] = 0x0 }returnnil}func writeFrameHeader( []byte, frameHeader) error {iflen() < frameHeaderLen {returnio.ErrShortBuffer } [0] = .typ [1] = 0 [2] = 0 [3] = 0 := .lenif .typ == FrameCommit { = .crc }binary.LittleEndian.PutUint32([4:8], )returnnil}var zeroHeader [frameHeaderLen]bytefunc readFrameHeader( []byte) (frameHeader, error) {varframeHeaderiflen() < frameHeaderLen {return , io.ErrShortBuffer }switch [0] {default:return , fmt.Errorf("%w: corrupt frame header with unknown type %d", types.ErrCorrupt, [0])caseFrameInvalid:// Check if the whole header is zero and return a zero frame as this could // just indicate we've read right off the end of the written data during // recovery.ifbytes.Equal([:frameHeaderLen], zeroHeader[:]) {return , nil }return , fmt.Errorf("%w: corrupt frame header with type 0 but non-zero other fields", types.ErrCorrupt)caseFrameEntry, FrameIndex: .typ = [0] .len = binary.LittleEndian.Uint32([4:8])caseFrameCommit: .typ = [0] .crc = binary.LittleEndian.Uint32([4:8]) }return , nil}// padLen returns how many bytes of padding should be added to a frame of length// n to ensure it is a multiple of headerLen. We ensure frameHeaderLen is a// power of two so that it's always a multiple of a typical sector size (e.g.// 512 bytes) to reduce the risk that headers are torn by being written across// sector boundaries. It will return an int in the range [0, 7].func padLen( int) int {// This looks a bit awful but it's just doing (n % 8) and subtracting that // from 8 to get the number of bytes extra needed to get up to the next 8-byte // boundary. The extra & 7 is to handle the case where n is a multiple of 8 // already and so n%8 is 0 and 8-0 is 8. By &ing 8 (0b1000) with 7 (0b111) we // effectively wrap it back around to 0. This only works as long as // frameHeaderLen is a power of 2 but that's necessary per comment above.return (frameHeaderLen - ( % frameHeaderLen)) & (frameHeaderLen - 1)}func encodedFrameSize( int) int {returnframeHeaderLen + + padLen()}func indexFrameSize( int) int {// Index frames are completely unnecessary if the whole block is a // continuation with no new entries.if == 0 {return0 }returnencodedFrameSize( * 4)}func writeIndexFrame( []byte, []uint32) error {iflen() < indexFrameSize(len()) {returnio.ErrShortBuffer } := frameHeader{typ: FrameIndex,len: uint32(len() * 4), }if := writeFrameHeader(, ); != nil {return } := frameHeaderLenfor , := range {binary.LittleEndian.PutUint32([:], ) += 4 }if (len() % 2) == 1 {// Odd number of entries, zero pad to keep it 8-byte alignedbinary.LittleEndian.PutUint32([:], 0) }returnnil}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.