1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
package packed
import (
"bytes"
"errors"
"fmt"
"os"
"lindenii.org/go/furgit/internal/format/packfile"
"lindenii.org/go/furgit/internal/format/packidx"
"lindenii.org/go/furgit/internal/format/packidx/bloom"
"lindenii.org/go/furgit/internal/mmap"
"lindenii.org/go/furgit/object/id"
"lindenii.org/go/lgo/intconv"
)
var (
errPackTruncated = errors.New("truncated")
errPackMalformedHeader = errors.New("malformed header")
errPackCountMismatch = errors.New("object count differs from index")
errPackTrailerMismatch = errors.New("trailer hash differs from index")
)
// pack is one discovered pack:
// its base name, its parsed index, and its mapped data.
// All fields are immutable after openPack.
type pack struct {
// name is the pack base name, like "pack-<hash>".
name string
// idxMapping owns the mapped pack index bytes,
// and idx is the parsed index view over them.
idxMapping *mmap.Mmap
idx packidx.Packidx
// dataMapping owns the mapped pack data bytes,
// and data aliases them.
dataMapping *mmap.Mmap
data []byte
bloomMapping *mmap.Mmap
filter *bloom.Bloom
}
// openPack opens, maps, and validates
// one pack index and its pack data
// by pack base name.
func openPack(root *os.Root, name string, objectFormat id.ObjectFormat) (*pack, error) {
idxMapping, err := mapFile(root, name+".idx")
if err != nil {
return nil, err
}
idx, err := packidx.Parse(idxMapping.Data(), objectFormat.Size())
if err != nil {
_ = idxMapping.Close()
return nil, fmt.Errorf("%w: index %q: %w", ErrMalformedPackedStore, name, err)
}
dataMapping, err := mapFile(root, name+".pack")
if err != nil {
_ = idxMapping.Close()
return nil, err
}
err = validatePackData(dataMapping.Data(), &idx, objectFormat.Size())
if err != nil {
_ = idxMapping.Close()
_ = dataMapping.Close()
return nil, fmt.Errorf("%w: pack %q: %w", ErrMalformedPackedStore, name, err)
}
bloomMapping, filter := openBloom(root, name, objectFormat, idx.PackHash())
return &pack{
name: name,
idxMapping: idxMapping,
idx: idx,
dataMapping: dataMapping,
data: dataMapping.Data(),
bloomMapping: bloomMapping,
filter: filter,
}, nil
}
func openBloom(root *os.Root, name string, objectFormat id.ObjectFormat, packHash []byte) (*mmap.Mmap, *bloom.Bloom) {
mapping, err := mapFile(root, name+".bloom")
if err != nil {
return nil, nil
}
filter, err := bloom.Parse(mapping.Data(), objectFormat)
if err != nil {
_ = mapping.Close()
return nil, nil
}
if !bytes.Equal(filter.PackHash(), packHash) {
_ = mapping.Close()
return nil, nil
}
return mapping, &filter
}
// mapFile opens and maps one file under root.
func mapFile(root *os.Root, name string) (*mmap.Mmap, error) {
file, err := root.Open(name)
if err != nil {
return nil, fmt.Errorf("object/store/packed: %w", err)
}
defer func() { _ = file.Close() }()
mapping, err := mmap.Open(file)
if err != nil {
return nil, fmt.Errorf("object/store/packed: %q: %w", name, err)
}
return mapping, nil
}
// validatePackData checks one mapped pack
// against the pack format and its index.
func validatePackData(data []byte, idx *packidx.Packidx, hashSize int) error {
if len(data) < packfile.HeaderLen+hashSize {
return errPackTruncated
}
header, err := packfile.ParseHeader(data)
if err != nil {
return fmt.Errorf("%w: %w", errPackMalformedHeader, err)
}
count := uint64(header.ObjectCount)
numObjects, err := intconv.IntToUint64(idx.NumObjects())
if err != nil {
return fmt.Errorf("object count: %w", err)
}
if count != numObjects {
return errPackCountMismatch
}
if !bytes.Equal(data[len(data)-hashSize:], idx.PackHash()) {
return errPackTrailerMismatch
}
return nil
}
// close releases the pack data, index, and filter mappings.
func (pack *pack) close() error {
errs := []error{
pack.dataMapping.Close(),
pack.idxMapping.Close(),
}
if pack.bloomMapping != nil {
errs = append(errs, pack.bloomMapping.Close())
}
return errors.Join(errs...)
}
|