176 lines
4.1 KiB
Go
176 lines
4.1 KiB
Go
package squashfs
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"io/fs"
|
|
"path/filepath"
|
|
|
|
"github.com/CalebQ42/squashfs/squashfs"
|
|
"github.com/CalebQ42/squashfs/squashfs/data"
|
|
"github.com/CalebQ42/squashfs/squashfs/inode"
|
|
)
|
|
|
|
// File represents a file inside a squashfs archive.
|
|
type File struct {
|
|
b *squashfs.Base
|
|
full *data.FullReader
|
|
rdr *data.Reader
|
|
parent *FS
|
|
r *Reader
|
|
dirsRead int
|
|
}
|
|
|
|
func (f *File) FS() (*FS, error) {
|
|
if !f.IsDir() {
|
|
return nil, errors.New("not a directory")
|
|
}
|
|
d, err := f.b.ToDir(f.r.r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &FS{d: d, parent: f.parent, r: f.r}, nil
|
|
}
|
|
|
|
// Closes the underlying readers.
|
|
// Further calls to Read and WriteTo will re-create the readers.
|
|
// Never returns an error.
|
|
func (f *File) Close() error {
|
|
if f.rdr != nil {
|
|
return f.rdr.Close()
|
|
}
|
|
f.rdr = nil
|
|
f.full = nil
|
|
return nil
|
|
}
|
|
|
|
// Returns the file the symlink points to.
|
|
// If the file isn't a symlink, or points to a file outside the archive, returns nil.
|
|
func (f *File) GetSymlinkFile() fs.File {
|
|
if !f.IsSymlink() {
|
|
return nil
|
|
}
|
|
if filepath.IsAbs(f.SymlinkPath()) {
|
|
return nil
|
|
}
|
|
fil, err := f.parent.Open(f.SymlinkPath())
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return fil
|
|
}
|
|
|
|
// Returns whether the file is a directory.
|
|
func (f *File) IsDir() bool {
|
|
return f.b.IsDir()
|
|
}
|
|
|
|
// Returns whether the file is a regular file.
|
|
func (f *File) IsRegular() bool {
|
|
return f.b.IsRegular()
|
|
}
|
|
|
|
// Returns whether the file is a symlink.
|
|
func (f *File) IsSymlink() bool {
|
|
return f.b.Inode.Type == inode.Sym || f.b.Inode.Type == inode.ESym
|
|
}
|
|
|
|
func (f *File) Mode() fs.FileMode {
|
|
return f.b.Inode.Mode()
|
|
}
|
|
|
|
// Read reads the data from the file. Only works if file is a normal file.
|
|
func (f *File) Read(b []byte) (int, error) {
|
|
if !f.IsRegular() {
|
|
return 0, errors.New("file is not a regular file")
|
|
}
|
|
if f.rdr == nil {
|
|
err := f.initializeReaders()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
return f.rdr.Read(b)
|
|
}
|
|
|
|
// ReadDir returns n fs.DirEntry's that's contained in the File (if it's a directory).
|
|
// If n <= 0 all fs.DirEntry's are returned.
|
|
func (f *File) ReadDir(n int) ([]fs.DirEntry, error) {
|
|
if !f.IsDir() {
|
|
return nil, errors.New("file is not a directory")
|
|
}
|
|
d, err := f.b.ToDir(f.r.r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
start, end := 0, len(d.Entries)
|
|
if n > 0 {
|
|
start, end = f.dirsRead, f.dirsRead+n
|
|
if end > len(d.Entries) {
|
|
end = len(d.Entries)
|
|
err = io.EOF
|
|
}
|
|
}
|
|
var out []fs.DirEntry
|
|
var fi fileInfo
|
|
for _, e := range d.Entries[start:end] {
|
|
fi, err = f.r.newFileInfo(e)
|
|
if err != nil {
|
|
f.dirsRead += len(out)
|
|
return out, err
|
|
}
|
|
out = append(out, fs.FileInfoToDirEntry(fi))
|
|
}
|
|
f.dirsRead += len(out)
|
|
return out, err
|
|
}
|
|
|
|
// Returns the file's fs.FileInfo
|
|
func (f *File) Stat() (fs.FileInfo, error) {
|
|
return newFileInfo(f.b.Name, f.b.Inode), nil
|
|
}
|
|
|
|
// SymlinkPath returns the symlink's target path. Is the File isn't a symlink, returns an empty string.
|
|
func (f *File) SymlinkPath() string {
|
|
switch f.b.Inode.Type {
|
|
case inode.Sym:
|
|
return string(f.b.Inode.Data.(inode.Symlink).Target)
|
|
case inode.ESym:
|
|
return string(f.b.Inode.Data.(inode.ESymlink).Target)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Writes all data from the file to the given writer in a multi-threaded manner.
|
|
// The underlying reader is separate
|
|
func (f *File) WriteTo(w io.Writer) (int64, error) {
|
|
if !f.IsRegular() {
|
|
return 0, errors.New("file is not a regular file")
|
|
}
|
|
if f.full == nil {
|
|
err := f.initializeReaders()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
return f.full.WriteTo(w)
|
|
}
|
|
|
|
func (f *File) initializeReaders() error {
|
|
var err error
|
|
f.rdr, f.full, err = f.b.GetRegFileReaders(f.r.r)
|
|
return err
|
|
}
|
|
|
|
// Extract the file to the given folder. If the file is a folder, the folder's contents will be extracted to the folder.
|
|
// Uses default extraction options.
|
|
func (f *File) Extract(folder string) error {
|
|
return f.ExtractWithOptions(folder, DefaultOptions())
|
|
}
|
|
|
|
// Extract the file to the given folder. If the file is a folder, the folder's contents will be extracted to the folder.
|
|
// Allows setting various extraction options via ExtractionOptions.
|
|
func (f *File) ExtractWithOptions(folder string, op *ExtractionOptions) error {
|
|
//TODO
|
|
}
|