pkg/sdk/fsutil/targzfs/fs.go (135 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package targzfs import ( "bytes" "fmt" "io/fs" "sort" "strings" "github.com/gobwas/glob" ) var ( // Block decompression if the TAR archive is larger than 25MB. maxDecompressedSize = int64(25 * 1024 * 1024) // Block decompression if the archive has more than 10k files. maxFileCount = 10000 ) type tarGzFs struct { files map[string]*tarEntry rootEntries []fs.DirEntry rootEntry *tarEntry } var _ fs.FS = (*tarGzFs)(nil) // Open opens the named file. func (gzfs *tarGzFs) Open(name string) (fs.File, error) { // Shortcut if the file is '.' if name == "." { if gzfs.rootEntries == nil { return &rootFile{}, nil } return &tarFile{ tarEntry: *gzfs.rootEntry, r: bytes.NewReader(gzfs.rootEntry.b), readDirPos: 0, }, nil } // Lookup file. f, err := gzfs.get(name, "open") if err != nil { return nil, err } // Wrapped file content return &tarFile{ tarEntry: *f, r: bytes.NewReader(f.b), readDirPos: 0, }, nil } var _ fs.ReadDirFS = (*tarGzFs)(nil) // ReadDir is used to enumerate all files from a directory. func (gzfs *tarGzFs) ReadDir(name string) ([]fs.DirEntry, error) { // Shortcut if the file is '.' if name == "." { return gzfs.rootEntries, nil } // Lookup file. e, err := gzfs.get(name, "readdir") if err != nil { return nil, err } // Only directory should be used. if !e.IsDir() { return nil, &fs.PathError{Op: "readdir", Path: name, Err: fs.ErrInvalid} } // Sort results by name. sort.Slice(e.entries, func(i, j int) bool { return e.entries[i].Name() < e.entries[j].Name() }) // Return file entries. return e.entries, nil } var _ fs.ReadFileFS = (*tarGzFs)(nil) // ReadFile is used to retrieve directly the file content. func (gzfs *tarGzFs) ReadFile(name string) ([]byte, error) { // Shortcut if the file is '.' if name == "." { return nil, &fs.PathError{Op: "readfile", Path: name, Err: fs.ErrInvalid} } // Lookup file. e, err := gzfs.get(name, "readfile") if err != nil { return nil, err } // Entry must be a file if e.IsDir() { return nil, &fs.PathError{Op: "readfile", Path: name, Err: fs.ErrInvalid} } // Copy content buf := make([]byte, len(e.b)) copy(buf, e.b) // No error return buf, nil } var _ fs.StatFS = (*tarGzFs)(nil) // Stat query the in-memory file system to get file info. func (gzfs *tarGzFs) Stat(name string) (fs.FileInfo, error) { // Shortcut if the file is '.' if name == "." { if gzfs.rootEntry == nil { return &rootFile{}, nil } // Return root fileinfo return gzfs.rootEntry.Info() } // Lookup file. e, err := gzfs.get(name, "stat") if err != nil { return nil, err } // Return fileinfo return e.h.FileInfo(), nil } var _ fs.GlobFS = (*tarGzFs)(nil) func (gzfs *tarGzFs) Glob(pattern string) (matches []string, _ error) { // Compile pattern g, err := glob.Compile(pattern) if err != nil { return nil, fmt.Errorf("unable to compile glob pattern: %w", err) } // Iterate over file names for name := range gzfs.files { // Check if pattern match the file name if g.Match(name) { matches = append(matches, name) } } // Return results return } var _ fs.SubFS = (*tarGzFs)(nil) func (gzfs *tarGzFs) Sub(dir string) (fs.FS, error) { if dir == "." { return gzfs, nil } // Lookup directory e, err := gzfs.get(dir, "sub") if err != nil { return nil, err } // Must be a directory if !e.IsDir() { return nil, &fs.PathError{Op: "sub", Path: dir, Err: fs.ErrInvalid} } // Create a sub-filesystem subfs := &tarGzFs{ files: make(map[string]*tarEntry), rootEntries: e.entries, rootEntry: e, } // Copy files and remove directory prefix. prefix := dir + "/" for name, file := range gzfs.files { if strings.HasPrefix(name, prefix) { subfs.files[strings.TrimPrefix(name, prefix)] = file } } // No error return subfs, nil } // ----------------------------------------------------------------------------- func (gzfs *tarGzFs) get(name, op string) (*tarEntry, error) { if !fs.ValidPath(name) { return nil, &fs.PathError{Op: op, Path: name, Err: fs.ErrInvalid} } // Lookup file e, ok := gzfs.files[name] if !ok { return nil, &fs.PathError{Op: op, Path: name, Err: fs.ErrNotExist} } return e, nil }