diff options
Diffstat (limited to 'pack_pack.go')
| -rw-r--r-- | pack_pack.go | 473 |
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 +} |
