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  }