go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/connection/tar/fs.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package tar 5 6 import ( 7 "archive/tar" 8 "bufio" 9 "errors" 10 "io" 11 "os" 12 "path/filepath" 13 "regexp" 14 "strings" 15 "time" 16 17 "github.com/rs/zerolog/log" 18 "github.com/spf13/afero" 19 "go.mondoo.com/cnquery/providers/os/fsutil" 20 ) 21 22 func NewFs(source string) *FS { 23 return &FS{ 24 Source: source, 25 FileMap: make(map[string]*tar.Header), 26 } 27 } 28 29 type FS struct { 30 Source string 31 FileMap map[string]*tar.Header 32 } 33 34 func (fs *FS) Name() string { 35 return "tarfs" 36 } 37 38 func (fs *FS) Create(name string) (afero.File, error) { 39 return nil, errors.New("create not implemented") 40 } 41 42 func (fs *FS) Mkdir(name string, perm os.FileMode) error { 43 return errors.New("mkdir not implemented") 44 } 45 46 func (fs *FS) MkdirAll(path string, perm os.FileMode) error { 47 return errors.New("mkdirall not implemented") 48 } 49 50 func (fs *FS) Open(path string) (afero.File, error) { 51 h, ok := fs.FileMap[path] 52 if !ok { 53 return nil, os.ErrNotExist 54 } 55 56 if h.Typeflag == tar.TypeSymlink { 57 resolvedPath := fs.resolveSymlink(h) 58 log.Debug().Str("path", path).Str("resolved", Abs(resolvedPath)).Msg("file is a symlink, resolved it") 59 h, ok = fs.FileMap[Abs(resolvedPath)] 60 if !ok { 61 return nil, os.ErrNotExist 62 } 63 } 64 65 reader, err := fs.open(h) 66 if err != nil { 67 return nil, err 68 } 69 70 return &File{ 71 path: path, 72 header: h, 73 Fs: fs, 74 reader: reader, 75 }, nil 76 } 77 78 func (fs *FS) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) { 79 return nil, errors.New("openfile not implemented") 80 } 81 82 func (fs *FS) Remove(name string) error { 83 return errors.New("remove not implemented") 84 } 85 86 func (fs *FS) RemoveAll(path string) error { 87 return errors.New("removeall not implemented") 88 } 89 90 func (fs *FS) Rename(oldname, newname string) error { 91 return errors.New("rename not implemented") 92 } 93 94 func (fs *FS) Stat(name string) (os.FileInfo, error) { 95 h, ok := fs.FileMap[name] 96 if !ok { 97 return nil, os.ErrNotExist 98 } 99 return fs.stat(h) 100 } 101 102 func (fs *FS) Chmod(name string, mode os.FileMode) error { 103 return errors.New("chmod not implemented") 104 } 105 106 func (fs *FS) Chtimes(name string, atime time.Time, mtime time.Time) error { 107 return errors.New("chtimes not implemented") 108 } 109 110 func (fs *FS) Chown(name string, uid, gid int) error { 111 return errors.New("chown not implemented") 112 } 113 114 func (fs *FS) stat(header *tar.Header) (os.FileInfo, error) { 115 statHeader := header 116 if header.Typeflag == tar.TypeSymlink { 117 path := fs.resolveSymlink(header) 118 h, ok := fs.FileMap[Abs(path)] 119 if !ok { 120 return nil, errors.New("could not find " + path) 121 } 122 statHeader = h 123 } 124 return statHeader.FileInfo(), nil 125 } 126 127 // resolve symlink file 128 func (fs *FS) resolveSymlink(header *tar.Header) string { 129 dest := header.Name 130 link := header.Linkname 131 132 var path string 133 if filepath.IsAbs(link) { 134 var err error 135 // we need to remove the root / then 136 path, err = filepath.Rel("/", link) 137 if err != nil { 138 log.Error().Str("link", link).Msg("could not determine the relative root path") 139 } 140 141 } else { 142 path = Clean(join(dest, "..", link)) 143 } 144 log.Debug().Str("link", link).Str("file", dest).Str("path", path).Msg("tar> is symlink") 145 return path 146 } 147 148 func (fs *FS) open(header *tar.Header) (*bufio.Reader, error) { 149 log.Debug().Str("file", header.Name).Msg("tar> load file content") 150 151 // open tar file 152 f, err := os.Open(fs.Source) 153 if err != nil { 154 return nil, err 155 } 156 defer f.Close() 157 158 path := header.Name 159 if header.Typeflag == tar.TypeSymlink { 160 path = fs.resolveSymlink(header) 161 } 162 163 // extract file from tar stream 164 reader, err := fsutil.ExtractFileFromTarStream(path, f) 165 if err != nil { 166 return nil, err 167 } 168 return reader, nil 169 } 170 171 func (fs *FS) tar(path string, header *tar.Header) (io.ReadCloser, error) { 172 fReader, err := fs.open(header) 173 if err != nil { 174 return nil, err 175 } 176 177 // create a pipe 178 tarReader, tarWriter := io.Pipe() 179 180 // get file info, header my just include symlink fileinfo 181 fi, err := fs.stat(header) 182 if err != nil { 183 return nil, err 184 } 185 186 // convert raw stream to tar stream 187 go fsutil.StreamFileAsTar(header.Name, fi, io.NopCloser(fReader), tarWriter) 188 189 // return the reader 190 return tarReader, nil 191 } 192 193 // searches for files and returns the file info 194 // regex can be nil 195 func (fs *FS) Find(from string, r *regexp.Regexp, typ string) ([]string, error) { 196 list := []string{} 197 for k := range fs.FileMap { 198 p := strings.HasPrefix(k, from) 199 m := true 200 if r != nil { 201 m = r.MatchString(k) 202 } 203 log.Trace().Str("path", k).Str("from", from).Str("prefix", from).Bool("prefix", p).Bool("m", m).Msg("check if matches") 204 if p && m { 205 entry := fs.FileMap[k] 206 if (typ == "directory" && entry.Typeflag == tar.TypeDir) || (typ == "file" && entry.Typeflag == tar.TypeReg) { 207 list = append(list, k) 208 log.Debug().Msg("matches") 209 continue 210 } 211 } 212 } 213 return list, nil 214 }