github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/walker/cached_file.go (about)

     1  package walker
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"os"
     7  	"sync"
     8  
     9  	"golang.org/x/xerrors"
    10  
    11  	dio "github.com/aquasecurity/go-dep-parser/pkg/io"
    12  )
    13  
    14  // cachedFile represents a file cached in memory or storage according to the file size.
    15  type cachedFile struct {
    16  	once sync.Once
    17  	err  error
    18  
    19  	size   int64
    20  	reader io.Reader
    21  
    22  	threshold int64 // Files larger than this threshold are written to file without being read into memory.
    23  
    24  	content  []byte // It will be populated if this file is small
    25  	filePath string // It will be populated if this file is large
    26  }
    27  
    28  func newCachedFile(size int64, r io.Reader, threshold int64) *cachedFile {
    29  	return &cachedFile{
    30  		size:      size,
    31  		reader:    r,
    32  		threshold: threshold,
    33  	}
    34  }
    35  
    36  // Open opens a file and cache the file.
    37  // If the file size is greater than or equal to threshold, it copies the content to a temp file and opens it next time.
    38  // If the file size is less than threshold, it opens the file once and the content will be shared so that others analyzers can use the same data.
    39  func (o *cachedFile) Open() (dio.ReadSeekCloserAt, error) {
    40  	o.once.Do(func() {
    41  		// When the file is large, it will be written down to a temp file.
    42  		if o.size >= o.threshold {
    43  			f, err := os.CreateTemp("", "fanal-*")
    44  			if err != nil {
    45  				o.err = xerrors.Errorf("failed to create the temp file: %w", err)
    46  				return
    47  			}
    48  
    49  			if _, err = io.Copy(f, o.reader); err != nil {
    50  				o.err = xerrors.Errorf("failed to copy: %w", err)
    51  				return
    52  			}
    53  
    54  			o.filePath = f.Name()
    55  		} else {
    56  			b, err := io.ReadAll(o.reader)
    57  			if err != nil {
    58  				o.err = xerrors.Errorf("unable to read the file: %w", err)
    59  				return
    60  			}
    61  			o.content = b
    62  		}
    63  	})
    64  	if o.err != nil {
    65  		return nil, xerrors.Errorf("failed to open: %w", o.err)
    66  	}
    67  
    68  	return o.open()
    69  }
    70  
    71  func (o *cachedFile) open() (dio.ReadSeekCloserAt, error) {
    72  	if o.filePath != "" {
    73  		f, err := os.Open(o.filePath)
    74  		if err != nil {
    75  			return nil, xerrors.Errorf("failed to open the temp file: %w", err)
    76  		}
    77  		return f, nil
    78  	}
    79  
    80  	return dio.NopCloser(bytes.NewReader(o.content)), nil
    81  }
    82  
    83  func (o *cachedFile) Clean() error {
    84  	return os.Remove(o.filePath)
    85  }