github.com/quay/claircore@v1.5.28/rpm/ndb/package.go (about) 1 package ndb 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "hash/adler32" 8 "io" 9 ) 10 11 // Pages are hard-coded to 4096 bytes for the Package database; this 12 // is different from the Index database, which could have variable page 13 // sizes. 14 15 // PackageDB is the "pkgdb" a.k.a. "Packages.db", the raw package data. 16 type PackageDB struct { 17 r io.ReaderAt 18 lookup map[uint32]*pkgSlot 19 slot []pkgSlot 20 pkgHeader 21 } 22 23 // Parse closes over the provided [io.ReaderAt] and populates the provided PackageDB. 24 func (db *PackageDB) Parse(r io.ReaderAt) error { 25 const ( 26 headerSz = 4 * 8 27 pageSz = 4096 28 ) 29 30 // Read and verify the header. 31 b := make([]byte, headerSz) 32 if _, err := r.ReadAt(b, 0); err != nil { 33 return fmt.Errorf("ndb: package: unable to read header: %w", err) 34 } 35 if err := db.pkgHeader.UnmarshalBinary(b); err != nil { 36 return fmt.Errorf("ndb: package: unable to unmarshal header: %w", err) 37 } 38 39 // Package count should be contiguous. 40 lastIndex := db.NextPkgIdx - 1 41 db.lookup = make(map[uint32]*pkgSlot, lastIndex) 42 db.slot = make([]pkgSlot, 0, lastIndex) 43 b = b[:slotSize] 44 // Read every populated slot (these should be contiguous) and populate the lookup table. 45 for off := int64(slotStart * slotSize); off < int64(db.NPages*pageSz); off += slotSize { 46 if _, err := r.ReadAt(b, off); err != nil { 47 return fmt.Errorf("ndb: package: unable to read slot @%d: %w", off, err) 48 } 49 var s pkgSlot 50 err := s.UnmarshalBinary(b) 51 switch { 52 case errors.Is(err, nil): 53 case errors.Is(err, errSkipSlot): 54 continue 55 default: 56 return fmt.Errorf("ndb: package: slot @%d: unexpected error: %w", off, err) 57 } 58 i := len(db.slot) 59 db.slot = append(db.slot, s) 60 db.lookup[s.Index] = &db.slot[i] 61 if s.Index == lastIndex { 62 break 63 } 64 } 65 db.r = r 66 67 return nil 68 } 69 70 // AllHeaders returns ReaderAts for all RPM headers in the PackageDB. 71 func (db *PackageDB) AllHeaders(_ context.Context) ([]io.ReaderAt, error) { 72 r := make([]io.ReaderAt, len(db.slot)) 73 var err error 74 for i, s := range db.slot { 75 r[i], err = db.GetHeader(s.Index) 76 if err != nil { 77 return nil, err 78 } 79 } 80 return r, nil 81 } 82 83 // Validate currently here to fulfil an interface. 84 func (db *PackageDB) Validate(_ context.Context) error { 85 return nil 86 } 87 88 // GetHeader returns an [io.ReaderAt] populated with [rpm.Header] data or 89 // reports an error. 90 func (db *PackageDB) GetHeader(pkgID uint32) (io.ReaderAt, error) { 91 const ( 92 headerSize = 4 * 4 93 trailerSize = 4 * 3 94 ) 95 // Lookup offset and count. 96 blob, ok := db.lookup[pkgID] 97 if !ok { 98 return nil, fmt.Errorf("ndb: package: package id %d does not exist", pkgID) 99 } 100 101 // Read and verify header. 102 b := make([]byte, headerSize) 103 if _, err := db.r.ReadAt(b, blob.Offset()); err != nil { 104 return nil, fmt.Errorf("ndb: package: error reading header: %w", err) 105 } 106 var bh blobHeader 107 if err := bh.UnmarshalBinary(b); err != nil { 108 return nil, fmt.Errorf("ndb: package: bad header: %w", err) 109 } 110 if bh.Package != pkgID { 111 return nil, fmt.Errorf("ndb: package: martian blob") 112 } 113 114 // Read and verify trailer. 115 if _, err := db.r.ReadAt(b[:trailerSize], blob.Offset()+blob.Count()-trailerSize); err != nil { 116 return nil, fmt.Errorf("ndb: package: error reading trailer: %w", err) 117 } 118 var bt blobTrailer 119 if err := bt.UnmarshalBinary(b); err != nil { 120 return nil, fmt.Errorf("ndb: package: bad trailer: %w", err) 121 } 122 if bt.Len != bh.Len { 123 return nil, fmt.Errorf("ndb: package: header/trailer length mismatch") 124 } 125 // This is slightly different from the ultimate reader -- the checksum includes any padding. 126 h := adler32.New() 127 rd := io.NewSectionReader(db.r, blob.Offset(), blob.Count()-trailerSize) 128 if _, err := io.Copy(h, rd); err != nil { 129 panic(err) 130 } 131 if got, want := h.Sum32(), bt.Checksum; got != want { 132 return nil, fmt.Errorf("ndb: package: checksum mismatch; got: 0x%08x, want: 0x%08x", got, want) 133 } 134 135 return io.NewSectionReader(db.r, blob.Offset()+headerSize, int64(bh.Len)), nil 136 } 137 138 // PkgHeader is the header for the PackageDB. It's meant to be embedded. 139 type pkgHeader struct { 140 Generation uint32 141 NPages uint32 142 NextPkgIdx uint32 143 } 144 145 // UnmarshalBinary implements encoding.BinaryUnmarshaler for a PackageDB header. 146 func (h *pkgHeader) UnmarshalBinary(b []byte) error { 147 const ( 148 magic = 'R' | 'p'<<8 | 'm'<<16 | 'P'<<24 149 version = 0 150 151 magicOffset = 0 152 versionOffset = 4 153 generationOffset = 8 154 nPagesOffset = 12 155 nextPkgIndexOffset = 16 156 ) 157 if len(b) < 32 { 158 return io.ErrShortBuffer 159 } 160 if le.Uint32(b[magicOffset:]) != magic { 161 return fmt.Errorf("ndb: package: bad header: bad magic") 162 } 163 if le.Uint32(b[versionOffset:]) != version { 164 return fmt.Errorf("ndb: package: bad header: bad version") 165 } 166 167 h.Generation = le.Uint32(b[generationOffset:]) 168 h.NPages = le.Uint32(b[nPagesOffset:]) 169 h.NextPkgIdx = le.Uint32(b[nextPkgIndexOffset:]) 170 171 return nil 172 } 173 174 // PkgSlot is a decoded package slot. 175 type pkgSlot struct { 176 Index uint32 177 blkOffset uint32 178 blkCount uint32 179 } 180 181 // BlockSize is the size of a blob block. 182 // 183 // Blobs are denominated and allocated in blocks. 184 const blockSize = 16 185 186 // ErrSkipSlot is returned by pkgSlot.UnmarshalBinary when the slot is empty. 187 var errSkipSlot = errors.New("skip slot") 188 189 func (s *pkgSlot) GoString() string { 190 return fmt.Sprintf("blob@%08x[%08x]", s.blkOffset*blockSize, s.blkCount*blockSize) 191 } 192 193 // Offset reports the byte offset indicated by the slot. 194 func (s *pkgSlot) Offset() int64 { return int64(s.blkOffset) * blockSize } 195 196 // Count reports the length in bytes of the data in the slot. 197 func (s *pkgSlot) Count() int64 { return int64(s.blkCount) * blockSize } 198 199 // UnmarshalBinary implements encoding.BinaryUnmarshaler. 200 func (s *pkgSlot) UnmarshalBinary(b []byte) error { 201 const ( 202 magic = ('S' | 'l'<<8 | 'o'<<16 | 't'<<24) 203 204 magicOffset = 0 205 slotIdxOffset = 4 206 slotOffsetOffset = 8 207 slotCountOffset = 12 208 209 headerSize = 4 * 4 210 trailerSize = 3 * 4 211 ) 212 if len(b) < slotSize { 213 return io.ErrShortBuffer 214 } 215 if le.Uint32(b[magicOffset:]) != magic { 216 return fmt.Errorf("slot: bad magic") 217 } 218 s.blkOffset = le.Uint32(b[slotOffsetOffset:]) 219 if s.blkOffset == 0 { 220 return errSkipSlot 221 } 222 s.Index = le.Uint32(b[slotIdxOffset:]) 223 s.blkCount = le.Uint32(b[slotCountOffset:]) 224 // Double-check the blob size. 225 if s.blkCount < ((headerSize + trailerSize + blockSize - 1) / blockSize) { 226 return fmt.Errorf("slot: nonsense block count (%d)", s.blkCount) 227 } 228 229 return nil 230 } 231 232 // BlobHeader is the header for a blob. 233 type blobHeader struct { 234 Package uint32 235 Generation uint32 236 Len uint32 237 } 238 239 // UnmarshalBinary implements encoding.BinaryUnmarshaler. 240 func (h *blobHeader) UnmarshalBinary(b []byte) error { 241 const ( 242 magic = ('B' | 'l'<<8 | 'b'<<16 | 'S'<<24) 243 minSize = 4 * 4 244 245 offsetMagic = 0 246 offsetPackage = 4 247 offsetGeneration = 8 248 offsetLength = 12 249 ) 250 251 if len(b) < minSize { 252 return io.ErrShortBuffer 253 } 254 if le.Uint32(b[offsetMagic:]) != magic { 255 return errors.New("blob: header: bad magic") 256 } 257 h.Package = le.Uint32(b[offsetPackage:]) 258 h.Generation = le.Uint32(b[offsetGeneration:]) 259 h.Len = le.Uint32(b[offsetLength:]) 260 261 return nil 262 } 263 264 // BlockCount reports the number of 16-byte blocks this blob occupies. 265 func (h *blobHeader) BlockCount() uint32 { 266 const ( 267 headerSize = 4 * 4 268 trailerSize = 3 * 4 269 ) 270 return ((headerSize + h.Len + trailerSize + blockSize) - 1) / blockSize 271 } 272 273 // BlobTrailer is the trailer (a.k.a. "tail") of a blob. 274 type blobTrailer struct { 275 Checksum uint32 276 Len uint32 277 } 278 279 // UnmarshalBinary implements encoding.BinaryUnmarshaler. 280 func (t *blobTrailer) UnmarshalBinary(b []byte) error { 281 const ( 282 magic = ('B' | 'l'<<8 | 'b'<<16 | 'E'<<24) 283 minSize = 3 * 4 284 285 offsetChecksum = 0 286 offsetLength = 4 287 offsetMagic = 8 288 ) 289 290 if len(b) < minSize { 291 return io.ErrShortBuffer 292 } 293 if le.Uint32(b[offsetMagic:]) != magic { 294 return errors.New("blob: trailer: bad magic") 295 } 296 t.Checksum = le.Uint32(b[offsetChecksum:]) 297 t.Len = le.Uint32(b[offsetLength:]) 298 return nil 299 }