github.com/google/osv-scalibr@v0.4.1/fs/fs.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 fs provides a virtual filesystem interface for SCALIBR scans and related helper functions. 16 package fs 17 18 import ( 19 "bytes" 20 "fmt" 21 "io" 22 "io/fs" 23 "os" 24 "path/filepath" 25 ) 26 27 // FS is a filesystem interface that allows the opening of files, reading of 28 // directories, and performing stat on files. 29 // 30 // FS implementations may return ErrNotImplemented for `Open`, `ReadDir` and `Stat`. 31 // Extractor implementations must decide whether the error is fatal or can be ignored. 32 // 33 // FS implementations MUST implement io.ReaderAt for opened files to enable random access. 34 type FS interface { 35 fs.FS 36 fs.ReadDirFS 37 fs.StatFS 38 } 39 40 // ScanRoot defines a root directory to start a scan from. 41 // mounted to a local dir. 42 type ScanRoot struct { 43 // A virtual filesystem for file access, rooted at the scan root. 44 FS FS 45 // The path of the scan root. Empty if this is a virtual filesystem and the 46 // scanning environment doesn't support the DirectFS requirement. 47 Path string 48 } 49 50 // IsVirtual returns true if the scan root represents the root of a virtual 51 // filesystem, i.e. one with no real location on the disk of the scanned host. 52 func (r *ScanRoot) IsVirtual() bool { 53 return r.Path == "" 54 } 55 56 // WithAbsolutePath returns a copy of the ScanRoot with the Path 57 // set an absolute path. 58 func (r *ScanRoot) WithAbsolutePath() (*ScanRoot, error) { 59 if r.Path == "" { 60 // Virtual-only filesystem 61 return &ScanRoot{FS: r.FS, Path: r.Path}, nil 62 } 63 absroot, err := filepath.Abs(r.Path) 64 if err != nil { 65 return nil, err 66 } 67 return &ScanRoot{FS: r.FS, Path: absroot}, nil 68 } 69 70 // DirFS returns an FS implementation that accesses the real filesystem at the given root. 71 func DirFS(root string) FS { 72 return os.DirFS(root).(FS) 73 } 74 75 // RealFSScanRoots returns a one-element ScanRoot array representing the given 76 // root path on the real filesystem SCALIBR is running on. 77 func RealFSScanRoots(path string) []*ScanRoot { 78 return []*ScanRoot{RealFSScanRoot(path)} 79 } 80 81 // RealFSScanRoot returns a ScanRoot array the given root path on the real 82 // filesystem SCALIBR is running on. 83 func RealFSScanRoot(path string) *ScanRoot { 84 return &ScanRoot{FS: DirFS(path), Path: path} 85 } 86 87 // NewReaderAt converts an io.Reader into an io.ReaderAt. 88 func NewReaderAt(ioReader io.Reader) (io.ReaderAt, error) { 89 r, ok := ioReader.(io.ReaderAt) 90 if ok { 91 return r, nil 92 } 93 94 // Fallback: In case ioReader does not implement ReadAt, we use a reader on byte buffer 95 // instead, which supports ReadAt. Note that in this case the whole file contents will be 96 // loaded into memory which might be expensive for large files. 97 buff := bytes.NewBuffer([]byte{}) 98 _, err := io.Copy(buff, ioReader) 99 if err != nil { 100 return nil, fmt.Errorf("io.Copy(): %w", err) 101 } 102 103 return bytes.NewReader(buff.Bytes()), nil 104 } 105 106 // GetRealPath returns the real absolute path of the file on the scanning host's filesystem. 107 // If the file is on a virtual filesystem (e.g. a remote container), it is first copied into a 108 // temporary directory on the scanning host's filesystem. It's up to the caller to delete the 109 // directory once they're done using it. 110 // 111 // Sample code to delete the directory: 112 // ``` 113 // realPath := GetRealPath(...) 114 // dir := filepath.Dir(realPath) 115 // 116 // if err := os.RemoveAll(dir); err != nil { 117 // log.Errorf("os.RemoveAll(%q): %w", dir, err) 118 // } 119 func GetRealPath(root *ScanRoot, path string, reader io.Reader) (string, error) { 120 if !root.IsVirtual() { 121 return filepath.Join(root.Path, path), nil 122 } 123 124 // This is a virtual filesystem. 125 // Move the file to the scanning hosts's filesystem. 126 dir, err := os.MkdirTemp("", "scalibr-tmp") 127 if err != nil { 128 return "", err 129 } 130 realPath := filepath.Join(dir, "file") 131 f, err := os.Create(realPath) 132 if err != nil { 133 return "", err 134 } 135 defer f.Close() 136 137 if reader == nil { 138 reader, err = root.FS.Open(path) 139 if err != nil { 140 return "", err 141 } 142 } 143 144 _, err = io.Copy(f, reader) 145 if err != nil { 146 return "", err 147 } 148 149 return realPath, nil 150 }