github.com/google/osv-scalibr@v0.4.1/common/linux/dpkg/dpkg.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package dpkg provides utilities for interacting with the dpkg package manager database. 16 package dpkg 17 18 import ( 19 "bufio" 20 "context" 21 "errors" 22 "fmt" 23 "io" 24 "io/fs" 25 "path" 26 27 scalibrfs "github.com/google/osv-scalibr/fs" 28 "github.com/google/osv-scalibr/fs/diriterate" 29 ) 30 31 const ( 32 infoDirPath = "var/lib/dpkg/info" 33 ) 34 35 // filePathIterator is an iterator over all paths found in dpkg files with a specific extension. 36 type filePathIterator struct { 37 rootFs scalibrfs.FS 38 dirs *diriterate.DirIterator 39 currentFileReader io.ReadCloser 40 currentScanner *bufio.Scanner 41 fileExt string 42 } 43 44 func newFilePathIterator(rootFs scalibrfs.FS, fileExt string) (*filePathIterator, error) { 45 if _, err := rootFs.Stat(infoDirPath); err != nil { 46 if errors.Is(err, fs.ErrNotExist) { 47 // If info dir doesn't exist, dpkg is not installed or has no package info. 48 // Return an iterator that doesn't iterate over any files. 49 return &filePathIterator{rootFs: rootFs, fileExt: fileExt}, nil 50 } 51 return nil, err 52 } 53 dirs, err := diriterate.ReadDir(rootFs, infoDirPath) 54 if err != nil { 55 return nil, fmt.Errorf("failed to read dpkg info dir at %s: %w", infoDirPath, err) 56 } 57 return &filePathIterator{rootFs: rootFs, dirs: dirs, fileExt: fileExt}, nil 58 } 59 60 // Next returns the path of the next installed file. 61 // It returns io.EOF when there are no more files. 62 func (it *filePathIterator) Next(ctx context.Context) (string, error) { 63 if err := ctx.Err(); err != nil { 64 return "", fmt.Errorf("dpkg.filePathIterator.Next halted because of context error: %w", err) 65 } 66 67 for { 68 if it.currentScanner != nil && it.currentScanner.Scan() { 69 return it.currentScanner.Text(), nil 70 } 71 if it.currentFileReader != nil { 72 it.currentFileReader.Close() 73 it.currentFileReader = nil 74 it.currentScanner = nil 75 } 76 77 listPath, err := it.nextFileWithExt() 78 if err != nil { 79 return "", err 80 } 81 82 reader, err := it.rootFs.Open(listPath) 83 if err != nil { 84 return "", err 85 } 86 it.currentFileReader = reader 87 it.currentScanner = bufio.NewScanner(reader) 88 } 89 } 90 91 func (it *filePathIterator) nextFileWithExt() (string, error) { 92 if it.dirs == nil { 93 return "", io.EOF 94 } 95 for { 96 f, err := it.dirs.Next() 97 if err != nil { 98 return "", err 99 } 100 101 if !f.IsDir() && path.Ext(f.Name()) == it.fileExt { 102 return path.Join(infoDirPath, f.Name()), nil 103 } 104 } 105 } 106 107 // Close closes the iterator and releases any resources. 108 func (it *filePathIterator) Close() error { 109 var errs []error 110 if it.currentFileReader != nil { 111 if err := it.currentFileReader.Close(); err != nil { 112 errs = append(errs, err) 113 } 114 } 115 if it.dirs != nil { 116 if err := it.dirs.Close(); err != nil { 117 errs = append(errs, err) 118 } 119 } 120 return errors.Join(errs...) 121 } 122 123 // ListFilePathIterator is an iterator over all paths found in dpkg .list files. 124 type ListFilePathIterator struct { 125 *filePathIterator 126 } 127 128 // NewListFilePathIterator creates a new iterator over files installed by dpkg. 129 // The caller is responsible for calling Close() on the returned iterator. 130 func NewListFilePathIterator(rootFs scalibrfs.FS) (*ListFilePathIterator, error) { 131 it, err := newFilePathIterator(rootFs, ".list") 132 if err != nil { 133 return nil, err 134 } 135 return &ListFilePathIterator{it}, nil 136 } 137 138 // ConffilePathIterator is an iterator over all paths found in dpkg .conffiles files. 139 type ConffilePathIterator struct { 140 *filePathIterator 141 } 142 143 // NewConffilePathIterator creates a new iterator over conffiles managed by dpkg. 144 // The caller is responsible for calling Close() on the returned iterator. 145 func NewConffilePathIterator(rootFs scalibrfs.FS) (*ConffilePathIterator, error) { 146 it, err := newFilePathIterator(rootFs, ".conffiles") 147 if err != nil { 148 return nil, err 149 } 150 return &ConffilePathIterator{it}, nil 151 }