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  }