github.com/quay/claircore@v1.5.28/rpm/ndb/ndb.go (about)

     1  package ndb
     2  
     3  import (
     4  	"context"
     5  	"encoding/binary"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  
    10  	"github.com/quay/claircore/rpm/internal/rpm"
    11  )
    12  
    13  var le = binary.LittleEndian
    14  
    15  // Used throughout the various DBs.
    16  const (
    17  	slotSize  = 4 * 4
    18  	slotStart = 2
    19  )
    20  
    21  // CheckMagic reports whether the Reader starts with a magic header for
    22  // a file format supported by this package.
    23  func CheckMagic(_ context.Context, r io.Reader) bool {
    24  	const (
    25  		xdb = 'R' | 'p'<<8 | 'm'<<16 | 'X'<<24
    26  		pkg = 'R' | 'p'<<8 | 'm'<<16 | 'P'<<24
    27  	)
    28  	b := make([]byte, 4)
    29  	if _, err := io.ReadFull(r, b); err != nil {
    30  		return false
    31  	}
    32  	m := le.Uint32(b)
    33  	return m == xdb || m == pkg
    34  }
    35  
    36  // XDB is the "xdb" a.k.a. "Index.db", the ndb mechanism for creating indexes.
    37  type XDB struct {
    38  	r      io.ReaderAt
    39  	lookup map[rpm.Tag]*xdbSlot
    40  	slot   []xdbSlot
    41  	xdbHeader
    42  }
    43  
    44  // Parse closes over the passed [io.ReaderAt] and populates the XDB.
    45  func (db *XDB) Parse(r io.ReaderAt) error {
    46  	const headerSize = 32
    47  	h := make([]byte, headerSize)
    48  	if _, err := r.ReadAt(h, 0); err != nil {
    49  		return fmt.Errorf("xdb: unable to read header: %w", err)
    50  	}
    51  	if err := db.xdbHeader.UnmarshalBinary(h); err != nil {
    52  		return fmt.Errorf("xdb: bad header: %w", err)
    53  	}
    54  	pg := make([]byte, db.PageSize*db.SlotNPages)
    55  	if _, err := r.ReadAt(pg, 0); err != nil {
    56  		return fmt.Errorf("xdb: unable to read slots: %w", err)
    57  	}
    58  
    59  	// Size for full pages of slots.
    60  	max := (len(pg) / slotSize) - slotStart
    61  	db.lookup = make(map[rpm.Tag]*xdbSlot, max)
    62  	db.slot = make([]xdbSlot, max)
    63  	n := 0
    64  	var x *xdbSlot
    65  	for off := slotStart * slotSize; n < max; n, off = n+1, off+slotSize {
    66  		x = &db.slot[n]
    67  		if err := x.UnmarshalBinary(pg[off:]); err != nil {
    68  			return err
    69  		}
    70  		if x.Tag == 0 || x.Tag == rpm.TagInvalid {
    71  			break
    72  		}
    73  		db.lookup[x.Tag] = x
    74  	}
    75  	db.slot = db.slot[:n]
    76  	db.r = r
    77  	return nil
    78  }
    79  
    80  // Index reports the index for the specifed tag.
    81  func (db *XDB) Index(tag rpm.Tag) (*Index, error) {
    82  	slot, ok := db.lookup[tag]
    83  	if !ok {
    84  		return nil, fmt.Errorf("ndb: no such tag %d", tag)
    85  	}
    86  	off, ct := int64(slot.StartPage*db.PageSize), int64(slot.PageCount*db.PageSize)
    87  	r := io.NewSectionReader(db.r, off, ct)
    88  	var idx Index
    89  	if err := idx.Parse(r); err != nil {
    90  		return nil, err
    91  	}
    92  	return &idx, nil
    93  }
    94  
    95  type xdbHeader struct {
    96  	Version        uint32
    97  	Generation     uint32
    98  	SlotNPages     uint32
    99  	PageSize       uint32
   100  	UserGeneration uint32
   101  }
   102  
   103  // UnmarshalBinary implements encoding.BinaryUnmarshaler for the xdb header.
   104  func (h *xdbHeader) UnmarshalBinary(b []byte) error {
   105  	const (
   106  		headerSz = 32
   107  		magic    = 'R' | 'p'<<8 | 'm'<<16 | 'X'<<24
   108  		version  = 0
   109  
   110  		offsetMagic          = 0
   111  		offsetVersion        = 4
   112  		offsetGeneration     = 8
   113  		offsetSlotNPages     = 12
   114  		offsetPageSize       = 16
   115  		offsetUserGeneration = 20
   116  	)
   117  
   118  	if len(b) < headerSz {
   119  		return io.ErrShortBuffer
   120  	}
   121  	if le.Uint32(b[offsetMagic:]) != magic {
   122  		return errors.New("xdb: bad magic")
   123  	}
   124  	h.Version = le.Uint32(b[offsetVersion:])
   125  	if h.Version != version {
   126  		return errors.New("bad version")
   127  	}
   128  	h.Generation = le.Uint32(b[offsetGeneration:])
   129  	h.SlotNPages = le.Uint32(b[offsetSlotNPages:])
   130  	h.PageSize = le.Uint32(b[offsetPageSize:])
   131  	h.UserGeneration = le.Uint32(b[offsetUserGeneration:])
   132  	return nil
   133  }
   134  
   135  type xdbSlot struct {
   136  	Subtag    uint8
   137  	Tag       rpm.Tag
   138  	StartPage uint32
   139  	PageCount uint32
   140  }
   141  
   142  func (s *xdbSlot) UnmarshalBinary(b []byte) error {
   143  	const (
   144  		magic     = ('S' | 'l'<<8 | 'o'<<16 | 0x00<<24)
   145  		magicMask = ^uint32(0xFF << 24)
   146  
   147  		magicOffset  = 0
   148  		subtagOffset = 3
   149  		tagOffset    = 4
   150  		startOffset  = 8
   151  		countOffset  = 12
   152  	)
   153  	if len(b) < slotSize {
   154  		return io.ErrShortBuffer
   155  	}
   156  	if le.Uint32(b[magicOffset:])&magicMask != magic {
   157  		return fmt.Errorf("slot: bad magic")
   158  	}
   159  	s.Subtag = b[subtagOffset]
   160  	s.Tag = rpm.Tag(le.Uint32(b[tagOffset:]))
   161  	s.StartPage = le.Uint32(b[startOffset:])
   162  	s.PageCount = le.Uint32(b[countOffset:])
   163  	return nil
   164  }