github.com/quay/claircore@v1.5.28/apk/scanner.go (about) 1 package apk 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "io/fs" 9 "runtime/trace" 10 11 "github.com/quay/zlog" 12 13 "github.com/quay/claircore" 14 "github.com/quay/claircore/indexer" 15 ) 16 17 const ( 18 name = "apk" 19 version = "v0.0.1" 20 kind = "package" 21 ) 22 23 var ( 24 _ indexer.VersionedScanner = (*Scanner)(nil) 25 _ indexer.PackageScanner = (*Scanner)(nil) 26 ) 27 28 // Scanner scans for packages in an apk database. 29 // 30 // The zero value is ready to use. 31 type Scanner struct{} 32 33 // Name implements indexer.VersionedScanner. 34 func (*Scanner) Name() string { return name } 35 36 // Version implements indexer.VersionedScanner. 37 func (*Scanner) Version() string { return version } 38 39 // Kind implements indexer.VersionedScanner. 40 func (*Scanner) Kind() string { return kind } 41 42 const installedFile = "lib/apk/db/installed" 43 44 // Scan examines a layer for an apk installation database, and extracts 45 // the packages listed there. 46 // 47 // A return of (nil, nil) is expected if there's no apk database. 48 func (*Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*claircore.Package, error) { 49 if err := ctx.Err(); err != nil { 50 return nil, err 51 } 52 defer trace.StartRegion(ctx, "Scanner.Scan").End() 53 trace.Log(ctx, "layer", layer.Hash.String()) 54 ctx = zlog.ContextWithValues(ctx, 55 "component", "apk/Scanner.Scan", 56 "version", version, 57 "layer", layer.Hash.String()) 58 59 zlog.Debug(ctx).Msg("start") 60 defer zlog.Debug(ctx).Msg("done") 61 62 sys, err := layer.FS() 63 if err != nil { 64 return nil, fmt.Errorf("apk: unable to open layer: %w", err) 65 } 66 b, err := fs.ReadFile(sys, installedFile) 67 switch { 68 case err == nil: 69 case errors.Is(err, fs.ErrNotExist): 70 return nil, nil 71 default: 72 return nil, err 73 } 74 zlog.Debug(ctx).Msg("found database") 75 76 pkgs := []*claircore.Package{} 77 srcs := make(map[string]*claircore.Package) 78 79 // It'd be great if we could just use the textproto package here, but we 80 // can't because the database "keys" are case sensitive, unlike MIME 81 // headers. So, roll our own entry and header splitting. 82 delim := []byte("\n\n") 83 entries := bytes.Split(b, delim) 84 for _, entry := range entries { 85 if len(entry) == 0 { 86 continue 87 } 88 p := claircore.Package{ 89 Kind: claircore.BINARY, 90 PackageDB: installedFile, 91 } 92 r := bytes.NewBuffer(entry) 93 for line, err := r.ReadBytes('\n'); err == nil; line, err = r.ReadBytes('\n') { 94 l := string(bytes.TrimSpace(line[2:])) 95 switch line[0] { 96 case 'P': 97 p.Name = l 98 case 'V': 99 p.Version = l 100 case 'c': 101 p.RepositoryHint = l 102 case 'A': 103 p.Arch = l 104 case 'o': 105 if src, ok := srcs[l]; ok { 106 p.Source = src 107 } else { 108 p.Source = &claircore.Package{ 109 Name: l, 110 Kind: claircore.SOURCE, 111 } 112 if p.Version != "" { 113 p.Source.Version = p.Version 114 } 115 srcs[l] = p.Source 116 } 117 } 118 } 119 pkgs = append(pkgs, &p) 120 } 121 zlog.Debug(ctx).Int("count", len(pkgs)).Msg("found packages") 122 123 return pkgs, nil 124 }