ref: 6e28d6a7ec210349b8c336249ba130422310fecb
dir: /refstore/reftable/table.go/
package reftable
import (
"encoding/binary"
"errors"
"fmt"
"hash/crc32"
"os"
"syscall"
"codeberg.org/lindenii/furgit/internal/intconv"
"codeberg.org/lindenii/furgit/objectid"
"codeberg.org/lindenii/furgit/ref"
)
const (
reftableMagic = "REFT"
version1 = 1
version2 = 2
blockTypeRef = byte('r')
blockTypeIndex = byte('i')
)
var (
hashIDSHA1 = binary.BigEndian.Uint32([]byte("sha1"))
hashIDSHA256 = binary.BigEndian.Uint32([]byte("s256"))
)
// tableFile is one opened and mapped reftable file.
type tableFile struct {
// name is the table filename from tables.list.
name string
// algo is the expected object ID algorithm.
algo objectid.Algorithm
// file is the opened table file.
file *os.File
// data is the mapped table bytes.
data []byte
// headerLen is 24 for v1 or 28 for v2.
headerLen int
// blockSize is configured alignment; 0 means unaligned.
blockSize int
// refEnd is the exclusive end of ref blocks section.
refEnd int
// refIndexPos is the root ref-index block position, or 0 when absent.
refIndexPos uint64
}
// recordValue is one decoded reference record value.
type recordValue struct {
// deleted marks a tombstone record.
deleted bool
// detachedID stores a direct object ID for detached refs.
detachedID objectid.ObjectID
// hasDetached reports whether detachedID is valid.
hasDetached bool
// peeled stores an optional peeled ID for annotated tags.
peeled *objectid.ObjectID
// symbolicTarget stores symref target for symbolic refs.
symbolicTarget string
}
// openTableFile maps and validates one reftable file.
func openTableFile(root *os.Root, name string, algo objectid.Algorithm) (*tableFile, error) {
file, err := root.Open(name)
if err != nil {
return nil, err
}
info, err := file.Stat()
if err != nil {
_ = file.Close()
return nil, err
}
size := info.Size()
if size < 0 || size > int64(int(^uint(0)>>1)) {
_ = file.Close()
return nil, fmt.Errorf("refstore/reftable: table %q has unsupported size", name)
}
fd, err := intconv.UintptrToInt(file.Fd())
if err != nil {
_ = file.Close()
return nil, err
}
data, err := syscall.Mmap(fd, 0, int(size), syscall.PROT_READ, syscall.MAP_PRIVATE)
if err != nil {
_ = file.Close()
return nil, err
}
out := &tableFile{name: name, algo: algo, file: file, data: data}
if err := out.parseMeta(); err != nil {
_ = out.close()
return nil, err
}
return out, nil
}
// close unmaps and closes one table file.
func (table *tableFile) close() error {
var closeErr error
if table.data != nil {
if err := syscall.Munmap(table.data); err != nil && closeErr == nil {
closeErr = err
}
table.data = nil
}
if table.file != nil {
if err := table.file.Close(); err != nil && closeErr == nil {
closeErr = err
}
table.file = nil
}
return closeErr
}
// parseMeta validates header/footer and section boundaries.
func (table *tableFile) parseMeta() error {
if len(table.data) < 24 {
return fmt.Errorf("refstore/reftable: table %q: file too short", table.name)
}
if string(table.data[:4]) != reftableMagic {
return fmt.Errorf("refstore/reftable: table %q: bad magic", table.name)
}
version := table.data[4]
switch version {
case version1:
table.headerLen = 24
if table.algo != objectid.AlgorithmSHA1 {
return fmt.Errorf("refstore/reftable: table %q: version 1 requires sha1", table.name)
}
case version2:
table.headerLen = 28
if len(table.data) < table.headerLen {
return fmt.Errorf("refstore/reftable: table %q: truncated header", table.name)
}
hashID := binary.BigEndian.Uint32(table.data[24:28])
if err := validateHashID(hashID, table.algo); err != nil {
return fmt.Errorf("refstore/reftable: table %q: %w", table.name, err)
}
default:
return fmt.Errorf("refstore/reftable: table %q: unsupported version %d", table.name, version)
}
table.blockSize = int(readUint24(table.data[5:8]))
footerLen := 68
if version == version2 {
footerLen = 72
}
if len(table.data) < footerLen {
return fmt.Errorf("refstore/reftable: table %q: missing footer", table.name)
}
footerStart := len(table.data) - footerLen
footer := table.data[footerStart:]
if string(footer[:4]) != reftableMagic || footer[4] != version {
return fmt.Errorf("refstore/reftable: table %q: invalid footer header", table.name)
}
wantCRC := binary.BigEndian.Uint32(footer[footerLen-4:])
haveCRC := crc32.ChecksumIEEE(footer[:footerLen-4])
if wantCRC != haveCRC {
return fmt.Errorf("refstore/reftable: table %q: footer crc mismatch", table.name)
}
if version == version2 {
hashID := binary.BigEndian.Uint32(footer[24:28])
if err := validateHashID(hashID, table.algo); err != nil {
return fmt.Errorf("refstore/reftable: table %q: %w", table.name, err)
}
}
off := table.headerLen
table.refIndexPos = binary.BigEndian.Uint64(footer[off : off+8])
off += 8
objPosAndLen := binary.BigEndian.Uint64(footer[off : off+8])
off += 8
objPos := objPosAndLen >> 5
objIndexPos := binary.BigEndian.Uint64(footer[off : off+8])
off += 8
logPos := binary.BigEndian.Uint64(footer[off : off+8])
off += 8
logIndexPos := binary.BigEndian.Uint64(footer[off : off+8])
_ = objIndexPos
_ = logIndexPos
refEnd, err := intconv.IntToUint64(footerStart)
if err != nil {
return fmt.Errorf("refstore/reftable: table %q: invalid footer offset: %w", table.name, err)
}
if table.refIndexPos != 0 && table.refIndexPos < refEnd {
refEnd = table.refIndexPos
}
if objPos != 0 && objPos < refEnd {
refEnd = objPos
}
if logPos != 0 && logPos < refEnd {
refEnd = logPos
}
headerLenU64, err := intconv.IntToUint64(table.headerLen)
if err != nil {
return fmt.Errorf("refstore/reftable: table %q: invalid header length: %w", table.name, err)
}
dataLenU64, err := intconv.IntToUint64(len(table.data))
if err != nil {
return fmt.Errorf("refstore/reftable: table %q: invalid data length: %w", table.name, err)
}
if refEnd < headerLenU64 || refEnd > dataLenU64 {
return fmt.Errorf("refstore/reftable: table %q: invalid ref section", table.name)
}
if table.refIndexPos > dataLenU64 {
return fmt.Errorf("refstore/reftable: table %q: invalid ref index position", table.name)
}
refEndInt, err := intconv.Uint64ToInt(refEnd)
if err != nil {
return fmt.Errorf("refstore/reftable: table %q: invalid ref section end: %w", table.name, err)
}
table.refEnd = refEndInt
return nil
}
// validateHashID validates a reftable v2 hash identifier.
func validateHashID(hashID uint32, algo objectid.Algorithm) error {
switch hashID {
case hashIDSHA1:
if algo != objectid.AlgorithmSHA1 {
return errors.New("hash id sha1 mismatch")
}
return nil
case hashIDSHA256:
if algo != objectid.AlgorithmSHA256 {
return errors.New("hash id s256 mismatch")
}
return nil
default:
return fmt.Errorf("unknown hash id 0x%08x", hashID)
}
}
// toRef converts a decoded record value into a public ref value.
func (record recordValue) toRef(name string) (ref.Ref, error) {
if record.deleted {
return nil, errors.New("refstore/reftable: cannot materialize deleted record")
}
if record.symbolicTarget != "" {
return ref.Symbolic{RefName: name, Target: record.symbolicTarget}, nil
}
if !record.hasDetached {
return nil, errors.New("refstore/reftable: malformed detached record")
}
return ref.Detached{RefName: name, ID: record.detachedID, Peeled: record.peeled}, nil
}