// SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: Copyright (c) 2016 Matthew Holt // Code in this file was derived from // https://github.com/mholt/archiver/blob/v4.0.0-alpha.8/fs.go // // These modifications were necessary to allow us to use an already open file // with archiver.FileFS. package archiverext import ( "io" "io/fs" "github.com/mholt/archiver/v4" ) // FileFS allows accessing a file on disk using a consistent file system interface. // The value should be the path to a regular file, not a directory. This file will // be the only entry in the file system and will be at its root. It can be accessed // within the file system by the name of "." or the filename. // // If the file is compressed, set the Compression field so that reads from the // file will be transparently decompressed. type FileFS struct { // File is the compressed file backing the FileFS. File fs.File // If file is compressed, setting this field will // transparently decompress reads. Compression archiver.Decompressor } // Open opens the named file, which must be the file used to create the file system. func (f FileFS) Open(name string) (fs.File, error) { if err := f.checkName(name, "open"); err != nil { return nil, err } if f.Compression == nil { return f.File, nil } r, err := f.Compression.OpenReader(f.File) if err != nil { return nil, err } return compressedFile{f.File, r}, nil } // ReadDir returns a directory listing with the file as the singular entry. func (f FileFS) ReadDir(name string) ([]fs.DirEntry, error) { if err := f.checkName(name, "stat"); err != nil { return nil, err } info, err := f.Stat(name) if err != nil { return nil, err } return []fs.DirEntry{fs.FileInfoToDirEntry(info)}, nil } // Stat stats the named file, which must be the file used to create the file system. func (f FileFS) Stat(name string) (fs.FileInfo, error) { if err := f.checkName(name, "stat"); err != nil { return nil, err } return f.File.Stat() } func (f FileFS) checkName(name, op string) error { if !fs.ValidPath(name) { return &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid} } // TODO: we may need better name validation. if name != "." { return &fs.PathError{Op: op, Path: name, Err: fs.ErrNotExist} } return nil } // compressedFile is an fs.File that specially reads // from a decompression reader, and which closes both // that reader and the underlying file. type compressedFile struct { fs.File decomp io.ReadCloser } func (cf compressedFile) Read(p []byte) (int, error) { return cf.decomp.Read(p) } func (cf compressedFile) Close() error { err := cf.File.Close() err2 := cf.decomp.Close() if err2 != nil && err == nil { err = err2 } return err }