aboutsummaryrefslogtreecommitdiff
path: root/pack_pack.go
diff options
context:
space:
mode:
Diffstat (limited to 'pack_pack.go')
-rw-r--r--pack_pack.go473
1 files changed, 473 insertions, 0 deletions
diff --git a/pack_pack.go b/pack_pack.go
new file mode 100644
index 00000000..20974669
--- /dev/null
+++ b/pack_pack.go
@@ -0,0 +1,473 @@
+package furgit
+
+import (
+ "bytes"
+ "compress/zlib"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "io"
+ "math"
+ "os"
+ "sync"
+ "syscall"
+)
+
+const (
+ packMagic = 0x5041434b
+ packVersion2 = 2
+)
+
+// PackLocation identifies the path to a pack file and an offset inside it.
+type PackLocation struct {
+ PackPath string
+ Offset uint64
+}
+
+func (repo *Repository) packRead(id Hash) (Object, error) {
+ loc, err := repo.packIndexFind(id)
+ if err != nil {
+ return nil, err
+ }
+ return repo.packReadAt(loc, id)
+}
+
+func (repo *Repository) packIndexFind(id Hash) (PackLocation, error) {
+ idxs, err := repo.packIndexes()
+ if err != nil {
+ return PackLocation{}, err
+ }
+ for _, idx := range idxs {
+ loc, err := idx.lookup(id)
+ if errors.Is(err, ErrNotFound) {
+ continue
+ }
+ if err != nil {
+ return PackLocation{}, err
+ }
+ return loc, nil
+ }
+ return PackLocation{}, ErrNotFound
+}
+
+func (repo *Repository) packReadAt(loc PackLocation, want Hash) (Object, error) {
+ ty, body, err := repo.packBodyResolveAtLocation(loc)
+ if err != nil {
+ return nil, err
+ }
+ data := body.Bytes()
+ if !verifyTypedObject(ty, data, want) {
+ body.Release()
+ return nil, ErrInvalidObject
+ }
+ obj, err := parseObjectBody(ty, want, data)
+ body.Release()
+ return obj, err
+}
+
+func (repo *Repository) packBodyResolveAtLocation(loc PackLocation) (ObjType, borrowedBody, error) {
+ pf, err := repo.packFile(loc.PackPath)
+ if err != nil {
+ return ObjInvalid, borrowedBody{}, err
+ }
+ return repo.packBodyResolveWithin(pf, loc.Offset)
+}
+
+func packHeaderRead(r io.Reader) (ObjType, int, error) {
+ var b [1]byte
+ _, err := io.ReadFull(r, b[:])
+ if err != nil {
+ return ObjInvalid, 0, err
+ }
+ ty := ObjType((b[0] >> 4) & 0x07)
+ size := int(b[0] & 0x0f)
+ shift := 4
+ for (b[0] & 0x80) != 0 {
+ _, err = io.ReadFull(r, b[:])
+ if err != nil {
+ return ObjInvalid, 0, err
+ }
+ size |= int(b[0]&0x7f) << shift
+ shift += 7
+ if (b[0] & 0x80) == 0 {
+ break
+ }
+ }
+ return ty, size, nil
+}
+
+func packSectionInflate(r io.Reader, sizeHint int) (borrowedBody, error) {
+ zr, err := zlib.NewReader(r)
+ if err != nil {
+ return borrowedBody{}, err
+ }
+ defer func() { _ = zr.Close() }()
+
+ if sizeHint > 0 {
+ body := borrowBody(sizeHint)
+ body.Resize(sizeHint)
+ _, err := io.ReadFull(zr, body.Bytes())
+ if err != nil {
+ body.Release()
+ return borrowedBody{}, err
+ }
+ var extra [1]byte
+ _, err = zr.Read(extra[:])
+ if err != io.EOF {
+ body.Release()
+ if err == nil {
+ return borrowedBody{}, ErrInvalidObject
+ }
+ return borrowedBody{}, err
+ }
+ return body, nil
+ }
+
+ body := borrowBody(defaultBodyCap)
+ var scratch [32 * 1024]byte
+ for {
+ n, err := zr.Read(scratch[:])
+ if n > 0 {
+ body.Append(scratch[:n])
+ }
+ if err == io.EOF {
+ return body, nil
+ }
+ if err != nil {
+ body.Release()
+ return borrowedBody{}, err
+ }
+ }
+}
+
+func (repo *Repository) packDeltaResolveOfs(pf *packFile, deltaOffset uint64, r io.Reader) (ObjType, borrowedBody, error) {
+ dist, err := packDeltaReadOfsDistance(r)
+ if err != nil {
+ return ObjInvalid, borrowedBody{}, err
+ }
+ var baseOfs uint64
+ if deltaOffset > dist {
+ baseOfs = deltaOffset - dist
+ }
+ if baseOfs == 0 {
+ return ObjInvalid, borrowedBody{}, ErrInvalidObject
+ }
+ ty, body, err := repo.packBodyResolveWithin(pf, baseOfs)
+ if err != nil {
+ return ObjInvalid, borrowedBody{}, err
+ }
+ delta, err := packSectionInflate(r, 0)
+ if err != nil {
+ body.Release()
+ return ObjInvalid, borrowedBody{}, err
+ }
+ out, err := packDeltaApply(body, delta)
+ delta.Release()
+ body.Release()
+ if err != nil {
+ out.Release()
+ return ObjInvalid, borrowedBody{}, err
+ }
+ return ty, out, nil
+}
+
+func packDeltaReadOfsDistance(r io.Reader) (uint64, error) {
+ var b [1]byte
+ _, err := io.ReadFull(r, b[:])
+ if err != nil {
+ return 0, err
+ }
+ dist := uint64(b[0] & 0x7f)
+ for (b[0] & 0x80) != 0 {
+ _, err = io.ReadFull(r, b[:])
+ if err != nil {
+ return 0, err
+ }
+ dist = ((dist + 1) << 7) + uint64(b[0]&0x7f)
+ }
+ return dist, nil
+}
+
+func (repo *Repository) packBodyResolveByID(id Hash) (ObjType, borrowedBody, error) {
+ loc, err := repo.packIndexFind(id)
+ if err == nil {
+ return repo.packBodyResolveAtLocation(loc)
+ }
+ if !errors.Is(err, ErrNotFound) {
+ return ObjInvalid, borrowedBody{}, err
+ }
+ ty, body, err := repo.looseReadTyped(id)
+ if err != nil {
+ return ObjInvalid, borrowedBody{}, err
+ }
+ return ty, borrowedFromOwned(body), nil
+}
+
+func (repo *Repository) packBodyResolveWithin(pf *packFile, ofs uint64) (ObjType, borrowedBody, error) {
+ r, err := pf.cursor(ofs)
+ if err != nil {
+ return ObjInvalid, borrowedBody{}, err
+ }
+ ty, size, err := packHeaderRead(r)
+ if err != nil {
+ return ObjInvalid, borrowedBody{}, err
+ }
+
+ switch ty {
+ case ObjCommit, ObjTree, ObjBlob, ObjTag:
+ body, err := packSectionInflate(r, size)
+ return ty, body, err
+ case ObjRefDelta:
+ var base Hash
+ _, err := io.ReadFull(r, base[:])
+ if err != nil {
+ return ObjInvalid, borrowedBody{}, err
+ }
+ delta, err := packSectionInflate(r, 0)
+ if err != nil {
+ return ObjInvalid, borrowedBody{}, err
+ }
+ bt, body, err := repo.packBodyResolveByID(base)
+ if err != nil {
+ delta.Release()
+ return ObjInvalid, borrowedBody{}, err
+ }
+ out, err := packDeltaApply(body, delta)
+ delta.Release()
+ body.Release()
+ if err != nil {
+ out.Release()
+ return ObjInvalid, borrowedBody{}, err
+ }
+ return bt, out, nil
+ case ObjOfsDelta:
+ return repo.packDeltaResolveOfs(pf, ofs, r)
+ case ObjInvalid, ObjFuture:
+ return ObjInvalid, borrowedBody{}, ErrInvalidObject
+ default:
+ return ObjInvalid, borrowedBody{}, ErrInvalidObject
+ }
+}
+
+func packDeltaApply(base, delta borrowedBody) (borrowedBody, error) {
+ pos := 0
+ baseBytes := base.Bytes()
+ deltaBytes := delta.Bytes()
+ srcSize, err := packVarintRead(deltaBytes, &pos)
+ if err != nil {
+ return borrowedBody{}, err
+ }
+ dstSize, err := packVarintRead(deltaBytes, &pos)
+ if err != nil {
+ return borrowedBody{}, err
+ }
+ if srcSize != len(baseBytes) {
+ return borrowedBody{}, ErrInvalidObject
+ }
+ out := borrowBody(dstSize)
+ out.Resize(dstSize)
+ outBytes := out.Bytes()
+ outPos := 0
+
+ for pos < len(deltaBytes) {
+ op := deltaBytes[pos]
+ pos++
+ switch {
+ case op&0x80 != 0:
+ off := 0
+ n := 0
+ if op&0x01 != 0 {
+ if pos >= len(deltaBytes) {
+ out.Release()
+ return borrowedBody{}, ErrInvalidObject
+ }
+ off |= int(deltaBytes[pos])
+ pos++
+ }
+ if op&0x02 != 0 {
+ if pos >= len(deltaBytes) {
+ out.Release()
+ return borrowedBody{}, ErrInvalidObject
+ }
+ off |= int(deltaBytes[pos]) << 8
+ pos++
+ }
+ if op&0x04 != 0 {
+ if pos >= len(deltaBytes) {
+ out.Release()
+ return borrowedBody{}, ErrInvalidObject
+ }
+ off |= int(deltaBytes[pos]) << 16
+ pos++
+ }
+ if op&0x08 != 0 {
+ if pos >= len(deltaBytes) {
+ out.Release()
+ return borrowedBody{}, ErrInvalidObject
+ }
+ off |= int(deltaBytes[pos]) << 24
+ pos++
+ }
+ if op&0x10 != 0 {
+ if pos >= len(deltaBytes) {
+ out.Release()
+ return borrowedBody{}, ErrInvalidObject
+ }
+ n |= int(deltaBytes[pos])
+ pos++
+ }
+ if op&0x20 != 0 {
+ if pos >= len(deltaBytes) {
+ out.Release()
+ return borrowedBody{}, ErrInvalidObject
+ }
+ n |= int(deltaBytes[pos]) << 8
+ pos++
+ }
+ if op&0x40 != 0 {
+ if pos >= len(deltaBytes) {
+ out.Release()
+ return borrowedBody{}, ErrInvalidObject
+ }
+ n |= int(deltaBytes[pos]) << 16
+ pos++
+ }
+ if n == 0 {
+ n = 0x10000
+ }
+ if off+n > len(baseBytes) || outPos+n > len(outBytes) {
+ out.Release()
+ return borrowedBody{}, ErrInvalidObject
+ }
+ copy(outBytes[outPos:], baseBytes[off:off+n])
+ outPos += n
+ case op != 0:
+ n := int(op)
+ if pos+n > len(deltaBytes) || outPos+n > len(outBytes) {
+ out.Release()
+ return borrowedBody{}, ErrInvalidObject
+ }
+ copy(outBytes[outPos:], deltaBytes[pos:pos+n])
+ pos += n
+ outPos += n
+ default:
+ out.Release()
+ return borrowedBody{}, ErrInvalidObject
+ }
+ }
+
+ if outPos != len(outBytes) {
+ out.Release()
+ return borrowedBody{}, ErrInvalidObject
+ }
+ return out, nil
+}
+
+func packVarintRead(buf []byte, pos *int) (int, error) {
+ res := 0
+ shift := 0
+ for {
+ if *pos >= len(buf) {
+ return 0, ErrInvalidObject
+ }
+ b := buf[*pos]
+ *pos++
+ res |= int(b&0x7f) << shift
+ if (b & 0x80) == 0 {
+ break
+ }
+ shift += 7
+ }
+ return res, nil
+}
+
+type packFile struct {
+ relPath string
+ size int64
+ data []byte
+ closeMu sync.Once
+}
+
+func openPackFile(absPath, rel string) (*packFile, error) {
+ f, err := os.Open(absPath)
+ if err != nil {
+ return nil, err
+ }
+
+ stat, err := f.Stat()
+ if err != nil {
+ _ = f.Close()
+ return nil, err
+ }
+ if stat.Size() < 12 {
+ _ = f.Close()
+ return nil, ErrInvalidObject
+ }
+
+ header := make([]byte, 12)
+ _, err = io.ReadFull(f, header)
+ if err != nil {
+ _ = f.Close()
+ return nil, err
+ }
+ magic := binary.BigEndian.Uint32(header[:4])
+ ver := binary.BigEndian.Uint32(header[4:8])
+ if magic != packMagic || ver != packVersion2 {
+ _ = f.Close()
+ return nil, ErrInvalidObject
+ }
+
+ region, err := syscall.Mmap(
+ int(f.Fd()),
+ 0,
+ int(stat.Size()),
+ syscall.PROT_READ,
+ syscall.MAP_PRIVATE,
+ )
+ if err != nil {
+ _ = f.Close()
+ return nil, err
+ }
+ err = f.Close()
+ if err != nil {
+ _ = syscall.Munmap(region)
+ return nil, err
+ }
+ return &packFile{
+ relPath: rel,
+ size: stat.Size(),
+ data: region,
+ }, nil
+}
+
+func (pf *packFile) Close() error {
+ if pf == nil {
+ return nil
+ }
+ var closeErr error
+ pf.closeMu.Do(func() {
+ if len(pf.data) > 0 {
+ if err := syscall.Munmap(pf.data); closeErr == nil {
+ closeErr = err
+ }
+ pf.data = nil
+ }
+ })
+ return closeErr
+}
+
+func (pf *packFile) cursor(ofs uint64) (io.Reader, error) {
+ if pf == nil {
+ return nil, ErrInvalidObject
+ }
+ if pf.size < 0 {
+ return nil, ErrInvalidObject
+ }
+ if ofs > uint64(pf.size) {
+ return nil, fmt.Errorf("furgit: pack: offset %d beyond %s", ofs, pf.relPath)
+ }
+ if ofs > uint64(math.MaxInt64) {
+ return nil, fmt.Errorf("furgit: pack: offset %d too large", ofs)
+ }
+ return bytes.NewReader(pf.data[ofs:]), nil
+}