Files
squashfs/fs.go
T
Caleb Gardner 1f0868fb21 Added -show-hard-links to go-unsquashfs
Exposed the underlying squashfslow values for File and FS
2025-06-06 11:05:43 -05:00

280 lines
6.0 KiB
Go

package squashfs
import (
"io"
"io/fs"
"path"
"path/filepath"
"slices"
"strings"
squashfslow "github.com/CalebQ42/squashfs/low"
"github.com/CalebQ42/squashfs/low/directory"
)
// FS is a fs.FS representation of a squashfs directory.
// Implements fs.GlobFS, fs.ReadDirFS, fs.ReadFileFS, fs.StatFS, and fs.SubFS
type FS struct {
r *Reader
parent *FS
LowDir squashfslow.Directory
}
// Creates a new *FS from the given squashfs.directory
func (r *Reader) FSFromDirectory(d squashfslow.Directory, parent FS) FS {
return FS{
LowDir: d,
r: r,
parent: &parent,
}
}
// Glob returns the name of the files at the given pattern.
// All paths are relative to the FS.
// Uses filepath.Match to compare names.
func (f *FS) Glob(pattern string) (out []string, err error) {
pattern = filepath.Clean(pattern)
if !fs.ValidPath(pattern) {
return nil, &fs.PathError{
Op: "glob",
Path: pattern,
Err: fs.ErrInvalid,
}
}
split := strings.Split(pattern, "/")
for i := range f.LowDir.Entries {
if match, _ := path.Match(split[0], f.LowDir.Entries[i].Name); match {
if len(split) == 1 {
out = append(out, f.LowDir.Entries[i].Name)
continue
}
sub, err := f.Sub(split[0])
if err != nil {
if pathErr, ok := err.(*fs.PathError); ok {
if pathErr.Err == fs.ErrNotExist {
continue
}
pathErr.Op = "glob"
pathErr.Path = pattern
return nil, pathErr
}
return nil, &fs.PathError{
Op: "glob",
Path: pattern,
Err: err,
}
}
subGlob, err := sub.(fs.GlobFS).Glob(strings.Join(split[1:], "/"))
if err != nil {
if pathErr, ok := err.(*fs.PathError); ok {
if pathErr.Err == fs.ErrNotExist {
continue
}
pathErr.Op = "glob"
pathErr.Path = pattern
return nil, pathErr
}
return nil, &fs.PathError{
Op: "glob",
Path: pattern,
Err: err,
}
}
for i := range subGlob {
subGlob[i] = f.LowDir.Name + "/" + subGlob[i]
}
out = append(out, subGlob...)
}
}
return
}
// Opens the file at name. Returns a *File as an fs.File.
func (f FS) Open(name string) (fs.File, error) {
return f.OpenFile(name)
}
func (f FS) OpenFile(name string) (*File, error) {
name = filepath.Clean(name)
if !fs.ValidPath(name) {
return nil, &fs.PathError{
Op: "open",
Path: name,
Err: fs.ErrInvalid,
}
}
if name == "." || name == "" {
return f.File(), nil
}
split := strings.Split(name, "/")
if split[0] == ".." {
if f.parent == nil { // root directory
return nil, &fs.PathError{
Op: "open",
Path: name,
Err: fs.ErrNotExist,
}
} else {
return f.parent.OpenFile(strings.Join(split[1:], "/"))
}
}
i, found := slices.BinarySearchFunc(f.LowDir.Entries, split[0], func(e directory.Entry, name string) int {
return strings.Compare(e.Name, name)
})
if !found {
return nil, &fs.PathError{
Op: "open",
Path: name,
Err: fs.ErrNotExist,
}
}
b, err := f.r.Low.BaseFromEntry(f.LowDir.Entries[i])
if err != nil {
return nil, err
}
if len(split) == 1 {
return &File{
Low: b,
r: f.r,
parent: f,
}, nil
}
if !b.IsDir() {
return nil, &fs.PathError{
Op: "open",
Path: name,
Err: fs.ErrNotExist,
}
}
d, err := b.ToDir(f.r.Low)
if err != nil {
return nil, err
}
return f.r.FSFromDirectory(d, f).OpenFile(strings.Join(split[1:], "/"))
}
// Returns all DirEntry's for the directory at name.
// If name is not a directory, returns an error.
func (f FS) ReadDir(name string) ([]fs.DirEntry, error) {
name = filepath.Clean(name)
if !fs.ValidPath(name) {
return nil, &fs.PathError{
Op: "readdir",
Path: name,
Err: fs.ErrInvalid,
}
}
if name == "." || name == "" {
return f.File().ReadDir(-1)
}
fil, err := f.Open(name)
if err != nil {
return nil, err
}
return fil.(*File).ReadDir(-1)
}
// Returns the contents of the file at name.
func (f FS) ReadFile(name string) (out []byte, err error) {
name = filepath.Clean(name)
if !fs.ValidPath(name) {
return nil, &fs.PathError{
Op: "readfile",
Path: name,
Err: fs.ErrInvalid,
}
}
if name == "." || name == "" {
return nil, fs.ErrInvalid
}
fil, err := f.Open(name)
if err != nil {
return nil, err
}
if !fil.(*File).IsRegular() {
return nil, fs.ErrInvalid
}
return io.ReadAll(fil)
}
// Returns the fs.FileInfo for the file at name.
func (f FS) Stat(name string) (fs.FileInfo, error) {
name = filepath.Clean(name)
if !fs.ValidPath(name) {
return nil, &fs.PathError{
Op: "stat",
Path: name,
Err: fs.ErrInvalid,
}
}
if name == "." || name == "" {
return f.File().Stat()
}
fil, err := f.Open(name)
if err != nil {
return nil, err
}
return fil.(*File).Stat()
}
// Returns the FS at dir
func (f FS) Sub(dir string) (fs.FS, error) {
dir = filepath.Clean(dir)
if !fs.ValidPath(dir) {
return nil, &fs.PathError{
Op: "dir",
Path: dir,
Err: fs.ErrInvalid,
}
}
if dir == "." || dir == "" {
return f, nil
}
fil, err := f.Open(dir)
if err != nil {
return nil, err
}
if !fil.(*File).IsDir() {
return nil, &fs.PathError{
Op: "dir",
Path: dir,
Err: fs.ErrInvalid,
}
}
return fil.(*File).FS()
}
// Extract the FS 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 FS) Extract(folder string) error {
return f.File().Extract(folder)
}
// Extract the FS 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 FS) ExtractWithOptions(folder string, op *ExtractionOptions) error {
return f.File().ExtractWithOptions(folder, op)
}
// Returns the FS as a *File
func (f FS) File() *File {
if f.parent != nil {
return &File{
Low: f.LowDir.FileBase,
parent: *f.parent,
r: f.r,
}
}
return &File{
Low: f.LowDir.FileBase,
r: f.r,
}
}
func (f FS) path() string {
if f.parent == nil {
return f.LowDir.Name
}
return filepath.Join(f.parent.path(), f.LowDir.Name)
}