shithub: furgit

ref: c9eefd50557a5436da84e0a38ee96c812d453336
dir: /objectstore/packed/idx_load.go/

View raw version
package packed

import (
	"fmt"
	"os"
	"slices"
	"strings"
	"syscall"

	"codeberg.org/lindenii/furgit/internal/intconv"
	"codeberg.org/lindenii/furgit/objectid"
)

// location identifies one object entry in a specific pack file.
type location struct {
	packName string
	offset   uint64
}

// idxFile stores one mapped and validated idx v2 file.
type idxFile struct {
	// idxName is the basename of this .idx file.
	idxName string
	// packName is the matching .pack basename.
	packName string
	// algo is the hash algorithm encoded by the index.
	algo objectid.Algorithm

	// file is the opened index file descriptor.
	file *os.File
	// data is the mapped index bytes.
	data []byte

	// fanout stores fanout table values.
	fanout [256]uint32
	// numObjects equals fanout[255].
	numObjects int

	// namesOffset starts the sorted object-id table.
	namesOffset int
	// offset32Offset starts the 32-bit offset table.
	offset32Offset int
	// offset64Offset starts the 64-bit offset table.
	offset64Offset int
	// offset64Count is the number of 64-bit offset entries.
	offset64Count int
}

// loadIndexes loads and validates all .idx files under objects/pack.
func (store *Store) loadIndexes() ([]*idxFile, error) {
	dir, err := store.root.Open(".")
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}
	defer func() { _ = dir.Close() }()
	entries, err := dir.ReadDir(-1)
	if err != nil {
		return nil, err
	}

	idxNames := make([]string, 0, len(entries))
	for _, entry := range entries {
		if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".idx") {
			continue
		}
		idxNames = append(idxNames, entry.Name())
	}
	slices.Sort(idxNames)

	out := make([]*idxFile, 0, len(idxNames))
	for _, idxName := range idxNames {
		packName := strings.TrimSuffix(idxName, ".idx") + ".pack"
		if _, err := store.root.Stat(packName); err != nil {
			if os.IsNotExist(err) {
				return nil, fmt.Errorf("objectstore/packed: missing pack file for index %q", idxName)
			}
			return nil, err
		}
		index, err := openIdxFile(store.root, idxName, packName, store.algo)
		if err != nil {
			for _, loaded := range out {
				_ = loaded.close()
			}
			return nil, err
		}
		out = append(out, index)
	}
	return out, nil
}

// openIdxFile maps and validates one idx v2 file.
func openIdxFile(root *os.Root, idxName, packName string, algo objectid.Algorithm) (*idxFile, error) {
	file, err := root.Open(idxName)
	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("objectstore/packed: idx %q has unsupported size", idxName)
	}
	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
	}

	index := &idxFile{
		idxName:  idxName,
		packName: packName,
		algo:     algo,
		file:     file,
		data:     data,
	}
	if err := index.parse(); err != nil {
		_ = index.close()
		return nil, err
	}
	return index, nil
}

// close unmaps and closes one idx handle.
func (index *idxFile) close() error {
	var closeErr error
	if index.data != nil {
		if err := syscall.Munmap(index.data); err != nil && closeErr == nil {
			closeErr = err
		}
		index.data = nil
	}
	if index.file != nil {
		if err := index.file.Close(); err != nil && closeErr == nil {
			closeErr = err
		}
		index.file = nil
	}
	return closeErr
}