diff options
| author | 2026-02-21 05:35:12 +0800 | |
|---|---|---|
| committer | 2026-02-21 11:15:18 +0800 | |
| commit | ae879b8cf5a87199802a33d6b15c76afafa8002b (patch) | |
| tree | a93e9486a9610b78823e157c68b75e0724366217 /objectstore/packed/idx_load.go | |
| parent | cache/lru: Add basic LRU (diff) | |
| signature | No signature | |
objectstore/packed: Add initial pack reading support
Diffstat (limited to 'objectstore/packed/idx_load.go')
| -rw-r--r-- | objectstore/packed/idx_load.go | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/objectstore/packed/idx_load.go b/objectstore/packed/idx_load.go new file mode 100644 index 00000000..106701fd --- /dev/null +++ b/objectstore/packed/idx_load.go @@ -0,0 +1,145 @@ +package packed + +import ( + "fmt" + "os" + "slices" + "strings" + "syscall" + + "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) + } + data, err := syscall.Mmap(int(file.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 +} |
