github.com/anchore/syft@v1.38.2/syft/internal/unionreader/union_reader.go (about) 1 package unionreader 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "sync" 8 9 "github.com/diskfs/go-diskfs/filesystem/squashfs" 10 11 macho "github.com/anchore/go-macholibre" 12 "github.com/anchore/syft/internal/log" 13 "github.com/anchore/syft/syft/file" 14 ) 15 16 // UnionReader is a single interface with all reading functions needed by multi-arch binary catalogers 17 // cataloger. 18 type UnionReader interface { 19 io.Reader 20 io.ReaderAt 21 io.Seeker 22 io.Closer 23 } 24 25 // GetReaders extracts one or more io.ReaderAt objects representing binaries that can be processed (multiple binaries in the case for multi-architecture binaries). 26 func GetReaders(f UnionReader) ([]io.ReaderAt, error) { 27 if macho.IsUniversalMachoBinary(f) { 28 machoReaders, err := macho.ExtractReaders(f) 29 if err != nil { 30 log.Debugf("extracting readers: %v", err) 31 return nil, err 32 } 33 34 var readers []io.ReaderAt 35 for _, e := range machoReaders { 36 readers = append(readers, e.Reader) 37 } 38 39 return readers, nil 40 } 41 42 return []io.ReaderAt{f}, nil 43 } 44 45 func GetUnionReader(readerCloser io.ReadCloser) (UnionReader, error) { 46 reader, ok := readerCloser.(UnionReader) 47 if ok { 48 return reader, nil 49 } 50 51 // file.LocationReadCloser embeds a ReadCloser, which is likely 52 // to implement UnionReader. Check whether the embedded read closer 53 // implements UnionReader, and just return that if so. 54 55 if r, ok := readerCloser.(file.LocationReadCloser); ok { 56 return GetUnionReader(r.ReadCloser) 57 } 58 59 if r, ok := readerCloser.(*squashfs.File); ok { 60 // seeking is implemented, but not io.ReaderAt. Lets wrap it to prevent from degrading performance 61 // by copying all data. 62 return newReaderAtAdapter(r), nil 63 } 64 65 b, err := io.ReadAll(readerCloser) 66 if err != nil { 67 return nil, fmt.Errorf("unable to read contents from binary: %w", err) 68 } 69 70 bytesReader := bytes.NewReader(b) 71 72 reader = struct { 73 io.ReadCloser 74 io.ReaderAt 75 io.Seeker 76 }{ 77 ReadCloser: io.NopCloser(bytesReader), 78 ReaderAt: bytesReader, 79 Seeker: bytesReader, 80 } 81 82 return reader, nil 83 } 84 85 type readerAtAdapter struct { 86 io.ReadSeekCloser 87 mu *sync.Mutex 88 } 89 90 func newReaderAtAdapter(rs io.ReadSeekCloser) UnionReader { 91 return &readerAtAdapter{ 92 ReadSeekCloser: rs, 93 mu: &sync.Mutex{}, 94 } 95 } 96 97 func (r *readerAtAdapter) Read(p []byte) (n int, err error) { 98 r.mu.Lock() 99 defer r.mu.Unlock() 100 return r.ReadSeekCloser.Read(p) 101 } 102 103 func (r *readerAtAdapter) Seek(offset int64, whence int) (int64, error) { 104 r.mu.Lock() 105 defer r.mu.Unlock() 106 return r.ReadSeekCloser.Seek(offset, whence) 107 } 108 109 func (r *readerAtAdapter) ReadAt(p []byte, off int64) (n int, err error) { 110 r.mu.Lock() 111 defer r.mu.Unlock() 112 113 currentPos, err := r.ReadSeekCloser.Seek(0, io.SeekCurrent) // save current pos 114 if err != nil { 115 return 0, err 116 } 117 118 _, err = r.ReadSeekCloser.Seek(off, io.SeekStart) // seek to absolute position `off` 119 if err != nil { 120 return 0, err 121 } 122 123 n, err = r.ReadSeekCloser.Read(p) // read from that absolute position 124 125 // restore the position for the stateful read/seek operations 126 if restoreErr := r.restorePosition(currentPos); restoreErr != nil { 127 if err == nil { 128 err = restoreErr 129 } 130 } 131 132 return n, err 133 } 134 135 func (r *readerAtAdapter) restorePosition(pos int64) error { 136 _, err := r.ReadSeekCloser.Seek(pos, io.SeekStart) 137 return err 138 }