Compare commits

..

42 Commits

Author SHA1 Message Date
Caleb Gardner ebbbc9e87e Merge pull request #40 from CalebQ42/go-unsquashfs-hardlinks
go-unsquashfs hardlinks & squashfslow access
2025-05-25 13:53:38 -05:00
Caleb Gardner 155999a8e3 Fixed always showing hardlink info 2025-05-25 13:45:51 -05:00
Caleb Gardner 7930f4402b Added -show-hard-links to go-unsquashfs
Exposed the underlying squashfslow values for File and FS
2025-05-25 13:35:40 -05:00
Caleb Gardner ada61a391c Added OpenFile to get a *squashfs.File instead of fs.File
Added -e to extract only specific files/folders
Only require the filename for -l, -ll, and -lln
2025-05-21 01:11:24 -05:00
Caleb Gardner f32cb520dc Zstd re-use 2025-04-16 18:02:19 -05:00
Caleb Gardner f991ddb1d4 Don't ignore permissions in benchmark tests 2025-04-16 16:22:32 -05:00
Caleb Gardner 4c8c9f0b47 Re-use zstd and zlib readers 2025-04-16 05:49:42 -05:00
Caleb Gardner 8a2556ea0d Remove test file 2025-04-16 05:16:58 -05:00
Caleb Gardner 33156751ca Merge pull request #39 from CalebQ42/perfExp
Performance
2025-04-10 11:26:24 -05:00
Caleb Gardner 6224c4be41 Further performance improvements
Further removed multiple pointer instances
Re-use decompression readers (except zstd due to bugs)
2025-04-10 11:20:55 -05:00
Caleb Gardner 6b0e9ef2c6 Reduce use of binary.Read and, by extention, reflection 2025-04-10 06:26:41 -05:00
Caleb Gardner 4490fc3873 Removed all the pointers 2025-04-10 02:15:24 -05:00
Caleb Gardner f242de2710 Better disabling of compression types 2025-03-17 06:53:29 -05:00
Caleb Gardner 88315ee384 Fix build flags 2025-03-17 06:28:31 -05:00
Caleb Gardner 1e2a8f4b75 go mod tidy 2025-03-17 06:22:24 -05:00
Caleb Gardner 863b03fb19 Updated README 2025-03-17 06:21:19 -05:00
Caleb Gardner d3f84344d1 Fix build flags in lzma.go & xz.go 2025-03-17 06:19:45 -05:00
Caleb Gardner ad24995b7b Change no_lzma and no_lzo to no_obsolete and no_gpl
Added build tags section to README
2025-03-17 06:16:25 -05:00
Caleb Gardner 638355ab71 Merge pull request #37 from afbjorklund/comp-none
Allow disabling lzo and lzma
2025-03-17 05:45:26 -05:00
Anders F Björklund 04d914d403 Allow disabling lzo and lzma
By setting the buildtags "no_lzo" and/or "no_lzma",
one can drop the library dependency on lzo and lzma.

The same could be done for xz as well, but there are
still lots of archives using xz compression out there.
2025-03-16 13:56:04 +01:00
Caleb Gardner 7323fe56f6 Merge pull request #35 from afbjorklund/list
Add list option to unsquashfs
2025-03-15 15:48:41 -05:00
Caleb Gardner 6286da31e1 Merge branch 'main' into list 2025-03-15 15:48:06 -05:00
Caleb Gardner 77c87a9653 Merge pull request #34 from afbjorklund/unsquashfs-offset
Allow mounting with an offset
2025-03-15 15:34:04 -05:00
Anders F Björklund e6b0b83dcb Add support for uid/gid 2025-03-15 17:50:02 +01:00
Anders F Björklund cef9090210 Add support for symlinks 2025-03-15 17:46:02 +01:00
Anders F Björklund 24a9457c6b Refactor: export FileInfo 2025-03-15 17:35:40 +01:00
Anders F Björklund e0c1309ed4 Add list option to unsquashfs 2025-03-15 17:32:27 +01:00
Anders F Björklund 8b475b6cc4 Allow mounting with an offset
When mounting squashfs images embedded in apptainer image,
using offset means we don't need to use a temporary copy.
2025-03-15 17:26:59 +01:00
Caleb Gardner 3a48a0bcdc Remove t.Fatal at end of single file test 2025-03-12 00:11:29 -05:00
Caleb Gardner f11416493e Apply FileMode fixes to Inode.Mode() 2025-03-12 00:09:02 -05:00
Caleb Gardner 619bb023b1 Fix missing fileInfo.Mode() types 2025-03-12 00:03:58 -05:00
Caleb Gardner 38e4761d21 Merge pull request #33 from afbjorklund/fileinfo-symlink
Properly show symlinks in Mode
2025-03-11 23:51:01 -05:00
Anders F Björklund 06d2ef3056 Properly show symlinks in Mode
Previously they were extracted OK (as symlinks), but shown
as regular files with length 0 when getting the file info.
2025-03-11 18:46:31 +01:00
Caleb Gardner 446f29df70 Empty io.Reader buffer on EOF 2025-03-04 04:33:47 -06:00
Caleb Gardner d6c8efcfe6 Removed writeToWriteAt
Didn't seem to have any performance advantage
2025-03-04 04:08:13 -06:00
Caleb Gardner d890932d5c Use WriterAt if it's available for FullReader 2025-02-27 07:19:04 -06:00
Caleb Gardner 87b5ac7f5d gopls modernize 2025-02-27 02:46:22 -06:00
Caleb Gardner e9fdd89c67 Merge pull request #31 from willmurphyscode/main
fix: remove stray println
2024-12-10 16:09:45 -06:00
Will Murphy c80d150fdc fix: remove stray println 2024-12-10 17:00:57 -05:00
Caleb Gardner 03266d0560 Fix frag, id, inode table values on block boundries
Fixes bug mention in #30
2024-11-26 17:09:39 -06:00
Caleb Gardner 0f8a4e0027 Re-added NewReaderAtOffset 2024-09-20 20:10:33 -05:00
Caleb Gardner 2a33cad709 PERFORMANCE
Changed some struct values from pointers to normal values for improved performance.
2024-07-17 09:30:16 -05:00
29 changed files with 797 additions and 353 deletions
+9 -5
View File
@@ -11,6 +11,10 @@ Currently has support for reading squashfs files and extracting files and folder
Special thanks to <https://dr-emann.github.io/squashfs/> for some VERY important information in an easy to understand format. Special thanks to <https://dr-emann.github.io/squashfs/> for some VERY important information in an easy to understand format.
Thanks also to [distri's squashfs library](https://github.com/distr1/distri/tree/master/internal/squashfs) as I referenced it to figure some things out (and double check others). Thanks also to [distri's squashfs library](https://github.com/distr1/distri/tree/master/internal/squashfs) as I referenced it to figure some things out (and double check others).
## Build tags
As of `v1.1.0` this library has two optional build tags: `no_gpl` and `no_obsolete`. `no_gpl` disables the ability to read archives with lzo compression due to the library's gpl license. `no_obsolete` removes "obsolete" compression types for a reduced compilation size; currently this only disable lzma compression since it's superseded by xz.
## FUSE ## FUSE
As of `v1.0`, FUSE capabilities has been moved to [a separate library](https://github.com/CalebQ42/squashfuse). As of `v1.0`, FUSE capabilities has been moved to [a separate library](https://github.com/CalebQ42/squashfuse).
@@ -24,14 +28,14 @@ As of `v1.0`, FUSE capabilities has been moved to [a separate library](https://g
## Issues ## Issues
* Significantly slower then `unsquashfs` when extracting folders * Noticably slower then `unsquashfs` for extraction, especially on larger images.
* This seems to be related to above along with the general optimization of `unsquashfs` and it's compression libraries. * This seems to be related to above along with the general optimization of `unsquashfs` and it's compression libraries.
* Times seem to be largely dependent on file tree size and compression type. * Times seem to be largely dependent on file tree size and compression type.
* My main testing image (~100MB) using Zstd takes about 6x longer. * My main testing image (~100MB) using Zstd takes ~2x longer.
* An Arch Linux airootfs image (~780MB) using XZ compression with LZMA filters takes about 32x longer. * An Arch Linux airootfs image (~780MB) using XZ compression with LZMA filters takes ~28x longer.
* A Tensorflow docker image (~3.3GB) using Zstd takes about 12x longer. * A Tensorflow docker image (~3.3GB) using Zstd takes ~3x longer.
Note: These numbers are using `FastOptions()`. `DefaultOptions()` takes about 2x longer. Note: These numbers are using `FastOptions()`. `DefaultOptions()` takes ~2x longer.
## Recommendations on Usage ## Recommendations on Usage
+116 -5
View File
@@ -4,16 +4,116 @@ import (
"flag" "flag"
"fmt" "fmt"
"os" "os"
"os/user"
"path/filepath"
"strconv"
"strings"
"time" "time"
"github.com/CalebQ42/squashfs" "github.com/CalebQ42/squashfs"
squashfslow "github.com/CalebQ42/squashfs/low"
)
func userName(uid int, numeric bool) string {
us := strconv.Itoa(uid)
if numeric {
return us
}
if u, err := user.LookupId(us); err == nil {
return u.Username
}
return us
}
func groupName(gid int, numeric bool) string {
gs := strconv.Itoa(gid)
if numeric {
return gs
}
if g, err := user.LookupGroupId(gs); err == nil {
return g.Name
}
return gs
}
var hardLinks = make(map[uint32]string)
func printFile(rdr *squashfs.Reader, path string, f *squashfs.File) {
path = filepath.Join(path, f.Low.Name)
fi, _ := f.Stat()
sfi := fi.(squashfs.FileInfo)
owner := fmt.Sprintf("%s/%s",
userName(sfi.Uid(), *numeric),
groupName(sfi.Gid(), *numeric))
var link string
var isHardLink bool
if *showHardLinks {
link, isHardLink = hardLinks[f.Low.Inode.Num]
if !isHardLink {
hardLinks[f.Low.Inode.Num] = path
}
}
var size int64
if isHardLink {
size = 0
} else {
size = fi.Size()
}
if sfi.IsSymlink() {
link = " -> " + sfi.SymlinkPath()
} else if isHardLink {
link = " link to " + link
}
fmt.Printf("%s %s %*d %s %s%s\n",
strings.ToLower(fi.Mode().String()),
owner, 26-len(owner), size,
fi.ModTime().Format("2006-01-02 15:04"),
path, link)
if f.IsDir() {
fs, _ := f.FS()
printDir(rdr, path, fs)
}
}
func printDir(rdr *squashfs.Reader, path string, f squashfs.FS) {
var base squashfslow.FileBase
var fil squashfs.File
var err error
for _, e := range f.LowDir.Entries {
base, err = rdr.Low.BaseFromEntry(e)
if err != nil {
panic(err)
}
fil = rdr.FileFromBase(base, f)
printFile(rdr, path, &fil)
}
}
var (
verbose *bool
list *bool
long *bool
numeric *bool
offset *int64
ignore *bool
file *string
showHardLinks *bool
) )
func main() { func main() {
verbose := flag.Bool("v", false, "Verbose") verbose = flag.Bool("v", false, "Verbose")
ignore := flag.Bool("ip", false, "Ignore Permissions and extract all files/folders with 0755") list = flag.Bool("l", false, "List")
long = flag.Bool("ll", false, "List with attributes")
numeric = flag.Bool("lln", false, "List with attributes and numeric ids")
showHardLinks = flag.Bool("show-hard-links", false, "When used with ll or lln, shows hard links")
offset = flag.Int64("o", 0, "Offset")
ignore = flag.Bool("ip", false, "Ignore Permissions and extract all files/folders with 0755")
file = flag.String("e", "", "File or folder to extract")
flag.Parse() flag.Parse()
if len(flag.Args()) < 2 { if (*list || *long || *numeric) && flag.NArg() < 1 {
fmt.Println("Please provide a file name")
os.Exit(0)
} else if (!*list && !*long && !*numeric) && flag.NArg() < 2 {
fmt.Println("Please provide a file name and extraction path") fmt.Println("Please provide a file name and extraction path")
os.Exit(0) os.Exit(0)
} }
@@ -21,15 +121,26 @@ func main() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
r, err := squashfs.NewReader(f) r, err := squashfs.NewReaderAtOffset(f, *offset)
if err != nil { if err != nil {
panic(err) panic(err)
} }
extractFil := r.File()
if *file != "" {
extractFil, err = r.OpenFile(*file)
if err != nil {
panic(err)
}
}
if *list || *long || *numeric {
printFile(&r, "", extractFil)
return
}
op := squashfs.DefaultOptions() op := squashfs.DefaultOptions()
op.Verbose = *verbose op.Verbose = *verbose
op.IgnorePerm = *ignore op.IgnorePerm = *ignore
n := time.Now() n := time.Now()
err = r.ExtractWithOptions(flag.Arg(1), op) err = extractFil.ExtractWithOptions(flag.Arg(1), op)
if err != nil { if err != nil {
panic(err) panic(err)
} }
+78 -64
View File
@@ -19,49 +19,47 @@ import (
// File represents a file inside a squashfs archive. // File represents a file inside a squashfs archive.
type File struct { type File struct {
b *squashfslow.FileBase full data.FullReader
full *data.FullReader rdr data.Reader
rdr *data.Reader rdrInit bool
parent *FS parent FS
r *Reader r *Reader
Low squashfslow.FileBase
dirsRead int dirsRead int
} }
// Creates a new *File from the given *squashfs.Base // Creates a new *File from the given *squashfs.Base
func (r *Reader) FileFromBase(b *squashfslow.FileBase, parent *FS) *File { func (r *Reader) FileFromBase(b squashfslow.FileBase, parent FS) File {
return &File{ return File{
b: b, Low: b,
parent: parent, parent: parent,
r: r, r: r,
} }
} }
func (f *File) FS() (*FS, error) { func (f File) FS() (FS, error) {
if !f.IsDir() { if !f.IsDir() {
return nil, errors.New("not a directory") return FS{}, errors.New("not a directory")
} }
d, err := f.b.ToDir(f.r.Low) d, err := f.Low.ToDir(f.r.Low)
if err != nil { if err != nil {
return nil, err return FS{}, err
} }
return &FS{d: d, parent: f.parent, r: f.r}, nil return FS{LowDir: d, parent: &f.parent, r: f.r}, nil
} }
// Closes the underlying readers. // Closes the underlying readers.
// Further calls to Read and WriteTo will re-create the readers. // Further calls to Read and WriteTo will re-create the readers.
// Never returns an error. // Never returns an error.
func (f *File) Close() error { func (f *File) Close() error {
if f.rdr != nil { f.rdr.Close()
return f.rdr.Close() f.full.Close()
}
f.rdr = nil
f.full = nil
return nil return nil
} }
// Returns the file the symlink points to. // Returns the file the symlink points to.
// If the file isn't a symlink, or points to a file outside the archive, returns nil. // If the file isn't a symlink, or points to a file outside the archive, returns nil.
func (f *File) GetSymlinkFile() fs.File { func (f File) GetSymlinkFile() fs.File {
if !f.IsSymlink() { if !f.IsSymlink() {
return nil return nil
} }
@@ -76,22 +74,22 @@ func (f *File) GetSymlinkFile() fs.File {
} }
// Returns whether the file is a directory. // Returns whether the file is a directory.
func (f *File) IsDir() bool { func (f File) IsDir() bool {
return f.b.IsDir() return f.Low.IsDir()
} }
// Returns whether the file is a regular file. // Returns whether the file is a regular file.
func (f *File) IsRegular() bool { func (f File) IsRegular() bool {
return f.b.IsRegular() return f.Low.IsRegular()
} }
// Returns whether the file is a symlink. // Returns whether the file is a symlink.
func (f *File) IsSymlink() bool { func (f File) IsSymlink() bool {
return f.b.Inode.Type == inode.Sym || f.b.Inode.Type == inode.ESym return f.Low.Inode.Type == inode.Sym || f.Low.Inode.Type == inode.ESym
} }
func (f *File) Mode() fs.FileMode { func (f File) Mode() fs.FileMode {
return f.b.Inode.Mode() return f.Low.Inode.Mode()
} }
// Read reads the data from the file. Only works if file is a normal file. // Read reads the data from the file. Only works if file is a normal file.
@@ -99,7 +97,7 @@ func (f *File) Read(b []byte) (int, error) {
if !f.IsRegular() { if !f.IsRegular() {
return 0, errors.New("file is not a regular file") return 0, errors.New("file is not a regular file")
} }
if f.rdr == nil { if !f.rdrInit {
err := f.initializeReaders() err := f.initializeReaders()
if err != nil { if err != nil {
return 0, err return 0, err
@@ -114,7 +112,7 @@ func (f *File) ReadDir(n int) ([]fs.DirEntry, error) {
if !f.IsDir() { if !f.IsDir() {
return nil, errors.New("file is not a directory") return nil, errors.New("file is not a directory")
} }
d, err := f.b.ToDir(f.r.Low) d, err := f.Low.ToDir(f.r.Low)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -127,7 +125,7 @@ func (f *File) ReadDir(n int) ([]fs.DirEntry, error) {
} }
} }
var out []fs.DirEntry var out []fs.DirEntry
var fi fileInfo var fi FileInfo
for _, e := range d.Entries[start:end] { for _, e := range d.Entries[start:end] {
fi, err = f.r.newFileInfo(e) fi, err = f.r.newFileInfo(e)
if err != nil { if err != nil {
@@ -141,17 +139,25 @@ func (f *File) ReadDir(n int) ([]fs.DirEntry, error) {
} }
// Returns the file's fs.FileInfo // Returns the file's fs.FileInfo
func (f *File) Stat() (fs.FileInfo, error) { func (f File) Stat() (fs.FileInfo, error) {
return newFileInfo(f.b.Name, f.b.Inode), nil uid, err := f.Low.Uid(&f.r.Low)
if err != nil {
return nil, err
}
gid, err := f.Low.Gid(&f.r.Low)
if err != nil {
return nil, err
}
return newFileInfo(f.Low.Name, uid, gid, &f.Low.Inode), nil
} }
// SymlinkPath returns the symlink's target path. Is the File isn't a symlink, returns an empty string. // SymlinkPath returns the symlink's target path. Is the File isn't a symlink, returns an empty string.
func (f *File) SymlinkPath() string { func (f File) SymlinkPath() string {
switch f.b.Inode.Type { switch f.Low.Inode.Type {
case inode.Sym: case inode.Sym:
return string(f.b.Inode.Data.(inode.Symlink).Target) return string(f.Low.Inode.Data.(inode.Symlink).Target)
case inode.ESym: case inode.ESym:
return string(f.b.Inode.Data.(inode.ESymlink).Target) return string(f.Low.Inode.Data.(inode.ESymlink).Target)
} }
return "" return ""
} }
@@ -162,7 +168,7 @@ func (f *File) WriteTo(w io.Writer) (int64, error) {
if !f.IsRegular() { if !f.IsRegular() {
return 0, errors.New("file is not a regular file") return 0, errors.New("file is not a regular file")
} }
if f.full == nil { if !f.rdrInit {
err := f.initializeReaders() err := f.initializeReaders()
if err != nil { if err != nil {
return 0, err return 0, err
@@ -173,36 +179,43 @@ func (f *File) WriteTo(w io.Writer) (int64, error) {
func (f *File) initializeReaders() error { func (f *File) initializeReaders() error {
var err error var err error
f.rdr, f.full, err = f.b.GetRegFileReaders(f.r.Low) f.rdr, f.full, err = f.Low.GetRegFileReaders(f.r.Low)
if err == nil {
f.rdrInit = true
} else {
f.rdr.Close()
f.full.Close()
}
return err return err
} }
func (f *File) deviceDevices() (maj uint32, min uint32) { func (f File) deviceDevices() (maj uint32, min uint32) {
var dev uint32 var dev uint32
if f.b.Inode.Type == inode.Char || f.b.Inode.Type == inode.Block { switch f.Low.Inode.Type {
dev = f.b.Inode.Data.(inode.Device).Dev case inode.Char, inode.Block:
} else if f.b.Inode.Type == inode.EChar || f.b.Inode.Type == inode.EBlock { dev = f.Low.Inode.Data.(inode.Device).Dev
dev = f.b.Inode.Data.(inode.EDevice).Dev case inode.EChar, inode.EBlock:
dev = f.Low.Inode.Data.(inode.EDevice).Dev
} }
return dev >> 8, dev & 0x000FF return dev >> 8, dev & 0x000FF
} }
func (f *File) path() string { func (f File) path() string {
if f.parent == nil { if f.parent.LowDir.Name == "" {
return f.b.Name return f.Low.Name
} }
return filepath.Join(f.parent.path(), f.b.Name) return filepath.Join(f.parent.path(), f.Low.Name)
} }
// Extract the file to the given folder. If the file is a folder, the folder's contents will be extracted to the folder. // 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. // Uses default extraction options.
func (f *File) Extract(folder string) error { func (f File) Extract(folder string) error {
return f.ExtractWithOptions(folder, DefaultOptions()) 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. // 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. // Allows setting various extraction options via ExtractionOptions.
func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error { func (f File) ExtractWithOptions(path string, op *ExtractionOptions) error {
if op.manager == nil { if op.manager == nil {
op.manager = routinemanager.NewManager(op.SimultaneousFiles) op.manager = routinemanager.NewManager(op.SimultaneousFiles)
if op.LogOutput != nil { if op.LogOutput != nil {
@@ -216,9 +229,9 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
return err return err
} }
} }
switch f.b.Inode.Type { switch f.Low.Inode.Type {
case inode.Dir, inode.EDir: case inode.Dir, inode.EDir:
d, err := f.b.ToDir(f.r.Low) d, err := f.Low.ToDir(f.r.Low)
if err != nil { if err != nil {
if op.Verbose { if op.Verbose {
log.Println("Failed to create squashfs.Directory for", path) log.Println("Failed to create squashfs.Directory for", path)
@@ -234,7 +247,7 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
} }
return errors.Join(errors.New("failed to get base from entry: "+path), err) return errors.Join(errors.New("failed to get base from entry: "+path), err)
} }
go func(b *squashfslow.FileBase, path string) { go func(b squashfslow.FileBase, path string) {
i := op.manager.Lock() i := op.manager.Lock()
if b.IsDir() { if b.IsDir() {
extDir := filepath.Join(path, b.Name) extDir := filepath.Join(path, b.Name)
@@ -266,7 +279,7 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
}(b, path) }(b, path)
} }
var errCache []error var errCache []error
for i := 0; i < len(d.Entries); i++ { for range d.Entries {
err := <-errChan err := <-errChan
if err != nil { if err != nil {
errCache = append(errCache, err) errCache = append(errCache, err)
@@ -276,7 +289,7 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
return errors.Join(errors.New("failed to extract folder: "+path), errors.Join(errCache...)) return errors.Join(errors.New("failed to extract folder: "+path), errors.Join(errCache...))
} }
case inode.Fil, inode.EFil: case inode.Fil, inode.EFil:
path = filepath.Join(path, f.b.Name) path = filepath.Join(path, f.Low.Name)
outFil, err := os.Create(path) outFil, err := os.Create(path)
if err != nil { if err != nil {
if op.Verbose { if op.Verbose {
@@ -285,7 +298,7 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
return errors.Join(errors.New("failed to create file: "+path), err) return errors.Join(errors.New("failed to create file: "+path), err)
} }
defer outFil.Close() defer outFil.Close()
full, err := f.b.GetFullReader(f.r.Low) full, err := f.Low.GetFullReader(&f.r.Low)
if err != nil { if err != nil {
if op.Verbose { if op.Verbose {
log.Println("Failed to create full reader for", path) log.Println("Failed to create full reader for", path)
@@ -311,11 +324,11 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
return errors.New("failed to get symlink's file") return errors.New("failed to get symlink's file")
} }
fil := filTmp.(*File) fil := filTmp.(*File)
fil.b.Name = f.b.Name fil.Low.Name = f.Low.Name
err := fil.ExtractWithOptions(path, op) err := fil.ExtractWithOptions(path, op)
if err != nil { if err != nil {
if op.Verbose { if op.Verbose {
log.Println("Failed to extract symlink's file:", filepath.Join(path, f.b.Name)) log.Println("Failed to extract symlink's file:", filepath.Join(path, f.Low.Name))
} }
return errors.Join(errors.New("failed to extract symlink's file: "+path), err) return errors.Join(errors.New("failed to extract symlink's file: "+path), err)
} }
@@ -338,7 +351,7 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
return errors.Join(errors.New("failed to extract symlink's file: "+extractLoc), err) return errors.Join(errors.New("failed to extract symlink's file: "+extractLoc), err)
} }
} }
path = filepath.Join(path, f.b.Name) path = filepath.Join(path, f.Low.Name)
err := os.Symlink(f.SymlinkPath(), path) err := os.Symlink(f.SymlinkPath(), path)
if err != nil { if err != nil {
if op.Verbose { if op.Verbose {
@@ -361,13 +374,14 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
} }
return errors.Join(errors.New("mknot command not found"), err) return errors.Join(errors.New("mknot command not found"), err)
} }
path = filepath.Join(path, f.b.Name) path = filepath.Join(path, f.Low.Name)
var typ string var typ string
if f.b.Inode.Type == inode.Char || f.b.Inode.Type == inode.EChar { switch f.Low.Inode.Type {
case inode.Char, inode.EChar:
typ = "c" typ = "c"
} else if f.b.Inode.Type == inode.Block || f.b.Inode.Type == inode.EBlock { case inode.Block, inode.EBlock:
typ = "b" typ = "b"
} else { //Fifo IPC default: //Fifo IPC
if runtime.GOOS == "darwin" { if runtime.GOOS == "darwin" {
if op.Verbose { if op.Verbose {
log.Println(f.path(), "ignored. A Fifo file and can't be created on Darwin.") log.Println(f.path(), "ignored. A Fifo file and can't be created on Darwin.")
@@ -398,7 +412,7 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
} }
return nil return nil
default: default:
return errors.New("Unsupported file type. Inode type: " + strconv.Itoa(int(f.b.Inode.Type))) return errors.New("Unsupported file type. Inode type: " + strconv.Itoa(int(f.Low.Inode.Type)))
} }
if op.Verbose { if op.Verbose {
log.Println(f.path(), "extracted to", path) log.Println(f.path(), "extracted to", path)
@@ -406,7 +420,7 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
if op.IgnorePerm { if op.IgnorePerm {
return nil return nil
} }
uid, err := f.b.Uid(f.r.Low) uid, err := f.Low.Uid(&f.r.Low)
if err != nil { if err != nil {
if op.Verbose { if op.Verbose {
log.Println("Failed to get uid for", path) log.Println("Failed to get uid for", path)
@@ -414,7 +428,7 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
} }
return nil return nil
} }
gid, err := f.b.Gid(f.r.Low) gid, err := f.Low.Gid(&f.r.Low)
if err != nil { if err != nil {
if op.Verbose { if op.Verbose {
log.Println("Failed to get gid for", path) log.Println("Failed to get gid for", path)
+74 -16
View File
@@ -8,61 +8,119 @@ import (
"github.com/CalebQ42/squashfs/low/inode" "github.com/CalebQ42/squashfs/low/inode"
) )
type fileInfo struct { type FileInfo struct {
name string name string
uid uint32
gid uint32
size int64 size int64
target string
perm uint32 perm uint32
modTime uint32 modTime uint32
fileType uint16 fileType uint16
} }
func (r Reader) newFileInfo(e directory.Entry) (fileInfo, error) { func (r Reader) newFileInfo(e directory.Entry) (FileInfo, error) {
i, err := r.Low.InodeFromEntry(e) b, err := r.Low.BaseFromEntry(e)
if err != nil { if err != nil {
return fileInfo{}, err return FileInfo{}, err
} }
return newFileInfo(e.Name, i), nil uid, err := b.Uid(&r.Low)
if err != nil {
return FileInfo{}, err
}
gid, err := b.Gid(&r.Low)
if err != nil {
return FileInfo{}, err
}
return newFileInfo(e.Name, uid, gid, &b.Inode), nil
} }
func newFileInfo(name string, i *inode.Inode) fileInfo { func newFileInfo(name string, uid, gid uint32, i *inode.Inode) FileInfo {
var size int64 var size int64
if i.Type == inode.Fil { var target string
switch i.Type {
case inode.Fil:
size = int64(i.Data.(inode.File).Size) size = int64(i.Data.(inode.File).Size)
} else if i.Type == inode.EFil { case inode.EFil:
size = int64(i.Data.(inode.EFile).Size) size = int64(i.Data.(inode.EFile).Size)
case inode.Sym:
target = string(i.Data.(inode.Symlink).Target)
case inode.ESym:
target = string(i.Data.(inode.ESymlink).Target)
} }
return fileInfo{ return FileInfo{
name: name, name: name,
uid: uid,
gid: gid,
size: size, size: size,
target: target,
perm: uint32(i.Perm), perm: uint32(i.Perm),
modTime: i.ModTime, modTime: i.ModTime,
fileType: i.Type, fileType: i.Type,
} }
} }
func (f fileInfo) Name() string { func (f FileInfo) Name() string {
return f.name return f.name
} }
func (f fileInfo) Size() int64 { func (f FileInfo) Uid() int {
return int(f.uid)
}
func (f FileInfo) Gid() int {
return int(f.gid)
}
func (f FileInfo) Size() int64 {
return f.size return f.size
} }
func (f fileInfo) Mode() fs.FileMode { func (f FileInfo) SymlinkPath() string {
if f.IsDir() { return f.target
}
func (f FileInfo) Mode() fs.FileMode {
switch f.fileType {
case inode.Dir, inode.EDir:
return fs.FileMode(f.perm | uint32(fs.ModeDir)) return fs.FileMode(f.perm | uint32(fs.ModeDir))
case inode.Sym, inode.ESym:
return fs.FileMode(f.perm | uint32(fs.ModeSymlink))
case inode.Char, inode.EChar, inode.Block, inode.EBlock:
return fs.FileMode(f.perm | uint32(fs.ModeDevice))
case inode.Fifo, inode.EFifo:
return fs.FileMode(f.perm | uint32(fs.ModeNamedPipe))
case inode.Sock, inode.ESock:
return fs.FileMode(f.perm | uint32(fs.ModeSocket))
} }
return fs.FileMode(f.perm) return fs.FileMode(f.perm)
} }
func (f fileInfo) ModTime() time.Time { func (f FileInfo) ModTime() time.Time {
return time.Unix(int64(f.modTime), 0) return time.Unix(int64(f.modTime), 0)
} }
func (f fileInfo) IsDir() bool { func (f FileInfo) IsDir() bool {
return f.fileType == inode.Dir || f.fileType == inode.EDir return f.fileType == inode.Dir || f.fileType == inode.EDir
} }
func (f fileInfo) Sys() any { func (f FileInfo) IsSymlink() bool {
return f.fileType == inode.Sym || f.fileType == inode.ESym
}
func (f FileInfo) IsDevice() bool {
return f.fileType == inode.Block || f.fileType == inode.EBlock ||
f.fileType == inode.Char || f.fileType == inode.EChar
}
func (f FileInfo) IsFifo() bool {
return f.fileType == inode.Fifo || f.fileType == inode.EFifo
}
func (f FileInfo) IsSocket() bool {
return f.fileType == inode.Sock || f.fileType == inode.ESock
}
func (f FileInfo) Sys() any {
return nil return nil
} }
+38 -28
View File
@@ -15,17 +15,17 @@ import (
// FS is a fs.FS representation of a squashfs directory. // FS is a fs.FS representation of a squashfs directory.
// Implements fs.GlobFS, fs.ReadDirFS, fs.ReadFileFS, fs.StatFS, and fs.SubFS // Implements fs.GlobFS, fs.ReadDirFS, fs.ReadFileFS, fs.StatFS, and fs.SubFS
type FS struct { type FS struct {
d *squashfslow.Directory
r *Reader r *Reader
parent *FS parent *FS
LowDir squashfslow.Directory
} }
// Creates a new *FS from the given squashfs.directory // Creates a new *FS from the given squashfs.directory
func (r *Reader) FSFromDirectory(d *squashfslow.Directory, parent *FS) *FS { func (r *Reader) FSFromDirectory(d squashfslow.Directory, parent FS) FS {
return &FS{ return FS{
d: d, LowDir: d,
r: r, r: r,
parent: parent, parent: &parent,
} }
} }
@@ -42,10 +42,10 @@ func (f *FS) Glob(pattern string) (out []string, err error) {
} }
} }
split := strings.Split(pattern, "/") split := strings.Split(pattern, "/")
for i := 0; i < len(f.d.Entries); i++ { for i := range f.LowDir.Entries {
if match, _ := path.Match(split[0], f.d.Entries[i].Name); match { if match, _ := path.Match(split[0], f.LowDir.Entries[i].Name); match {
if len(split) == 1 { if len(split) == 1 {
out = append(out, f.d.Entries[i].Name) out = append(out, f.LowDir.Entries[i].Name)
continue continue
} }
sub, err := f.Sub(split[0]) sub, err := f.Sub(split[0])
@@ -80,8 +80,8 @@ func (f *FS) Glob(pattern string) (out []string, err error) {
Err: err, Err: err,
} }
} }
for i := 0; i < len(subGlob); i++ { for i := range subGlob {
subGlob[i] = f.d.Name + "/" + subGlob[i] subGlob[i] = f.LowDir.Name + "/" + subGlob[i]
} }
out = append(out, subGlob...) out = append(out, subGlob...)
} }
@@ -90,7 +90,11 @@ func (f *FS) Glob(pattern string) (out []string, err error) {
} }
// Opens the file at name. Returns a *File as an fs.File. // Opens the file at name. Returns a *File as an fs.File.
func (f *FS) Open(name string) (fs.File, error) { 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) name = filepath.Clean(name)
if !fs.ValidPath(name) { if !fs.ValidPath(name) {
return nil, &fs.PathError{ return nil, &fs.PathError{
@@ -111,10 +115,10 @@ func (f *FS) Open(name string) (fs.File, error) {
Err: fs.ErrNotExist, Err: fs.ErrNotExist,
} }
} else { } else {
return f.parent.Open(strings.Join(split[1:], "/")) return f.parent.OpenFile(strings.Join(split[1:], "/"))
} }
} }
i, found := slices.BinarySearchFunc(f.d.Entries, split[0], func(e directory.Entry, name string) int { i, found := slices.BinarySearchFunc(f.LowDir.Entries, split[0], func(e directory.Entry, name string) int {
return strings.Compare(e.Name, name) return strings.Compare(e.Name, name)
}) })
if !found { if !found {
@@ -124,13 +128,13 @@ func (f *FS) Open(name string) (fs.File, error) {
Err: fs.ErrNotExist, Err: fs.ErrNotExist,
} }
} }
b, err := f.r.Low.BaseFromEntry(f.d.Entries[i]) b, err := f.r.Low.BaseFromEntry(f.LowDir.Entries[i])
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(split) == 1 { if len(split) == 1 {
return &File{ return &File{
b: b, Low: b,
r: f.r, r: f.r,
parent: f, parent: f,
}, nil }, nil
@@ -146,12 +150,12 @@ func (f *FS) Open(name string) (fs.File, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return f.r.FSFromDirectory(d, f).Open(strings.Join(split[1:], "/")) return f.r.FSFromDirectory(d, f).OpenFile(strings.Join(split[1:], "/"))
} }
// Returns all DirEntry's for the directory at name. // Returns all DirEntry's for the directory at name.
// If name is not a directory, returns an error. // If name is not a directory, returns an error.
func (f *FS) ReadDir(name string) ([]fs.DirEntry, error) { func (f FS) ReadDir(name string) ([]fs.DirEntry, error) {
name = filepath.Clean(name) name = filepath.Clean(name)
if !fs.ValidPath(name) { if !fs.ValidPath(name) {
return nil, &fs.PathError{ return nil, &fs.PathError{
@@ -171,7 +175,7 @@ func (f *FS) ReadDir(name string) ([]fs.DirEntry, error) {
} }
// Returns the contents of the file at name. // Returns the contents of the file at name.
func (f *FS) ReadFile(name string) (out []byte, err error) { func (f FS) ReadFile(name string) (out []byte, err error) {
name = filepath.Clean(name) name = filepath.Clean(name)
if !fs.ValidPath(name) { if !fs.ValidPath(name) {
return nil, &fs.PathError{ return nil, &fs.PathError{
@@ -194,7 +198,7 @@ func (f *FS) ReadFile(name string) (out []byte, err error) {
} }
// Returns the fs.FileInfo for the file at name. // Returns the fs.FileInfo for the file at name.
func (f *FS) Stat(name string) (fs.FileInfo, error) { func (f FS) Stat(name string) (fs.FileInfo, error) {
name = filepath.Clean(name) name = filepath.Clean(name)
if !fs.ValidPath(name) { if !fs.ValidPath(name) {
return nil, &fs.PathError{ return nil, &fs.PathError{
@@ -214,7 +218,7 @@ func (f *FS) Stat(name string) (fs.FileInfo, error) {
} }
// Returns the FS at dir // Returns the FS at dir
func (f *FS) Sub(dir string) (fs.FS, error) { func (f FS) Sub(dir string) (fs.FS, error) {
dir = filepath.Clean(dir) dir = filepath.Clean(dir)
if !fs.ValidPath(dir) { if !fs.ValidPath(dir) {
return nil, &fs.PathError{ return nil, &fs.PathError{
@@ -242,28 +246,34 @@ func (f *FS) Sub(dir string) (fs.FS, error) {
// Extract the FS to the given folder. If the file is a folder, the folder's contents will be extracted to the folder. // 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. // Uses default extraction options.
func (f *FS) Extract(folder string) error { func (f FS) Extract(folder string) error {
return f.File().Extract(folder) 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. // 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. // Allows setting various extraction options via ExtractionOptions.
func (f *FS) ExtractWithOptions(folder string, op *ExtractionOptions) error { func (f FS) ExtractWithOptions(folder string, op *ExtractionOptions) error {
return f.File().ExtractWithOptions(folder, op) return f.File().ExtractWithOptions(folder, op)
} }
// Returns the FS as a *File // Returns the FS as a *File
func (f *FS) File() *File { func (f FS) File() *File {
if f.parent != nil {
return &File{ return &File{
b: &f.d.FileBase, Low: f.LowDir.FileBase,
parent: f.parent, parent: *f.parent,
r: f.r,
}
}
return &File{
Low: f.LowDir.FileBase,
r: f.r, r: f.r,
} }
} }
func (f *FS) path() string { func (f FS) path() string {
if f.parent == nil { if f.parent == nil {
return f.d.Name return f.LowDir.Name
} }
return filepath.Join(f.parent.path(), f.d.Name) return filepath.Join(f.parent.path(), f.LowDir.Name)
} }
+4 -4
View File
@@ -1,11 +1,11 @@
module github.com/CalebQ42/squashfs module github.com/CalebQ42/squashfs
go 1.21.5 go 1.24.0
require ( require (
github.com/pierrec/lz4/v4 v4.1.19 github.com/klauspost/compress v1.18.0
github.com/ulikunitz/xz v0.5.11 github.com/pierrec/lz4/v4 v4.1.22
github.com/klauspost/compress v1.17.4
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e
github.com/therootcompany/xz v1.0.1 github.com/therootcompany/xz v1.0.1
github.com/ulikunitz/xz v0.5.12
) )
+6 -6
View File
@@ -1,10 +1,10 @@
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/pierrec/lz4/v4 v4.1.19 h1:tYLzDnjDXh9qIxSTKHwXwOYmm9d887Y7Y1ZkyXYHAN4= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
github.com/pierrec/lz4/v4 v4.1.19/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e h1:dCWirM5F3wMY+cmRda/B1BiPsFtmzXqV9b0hLWtVBMs= github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e h1:dCWirM5F3wMY+cmRda/B1BiPsFtmzXqV9b0hLWtVBMs=
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e/go.mod h1:9leZcVcItj6m9/CfHY5Em/iBrCz7js8LcRQGTKEEv2M= github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e/go.mod h1:9leZcVcItj6m9/CfHY5Em/iBrCz7js8LcRQGTKEEv2M=
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
+18 -3
View File
@@ -3,13 +3,28 @@ package decompress
import ( import (
"bytes" "bytes"
"io" "io"
"sync"
"github.com/pierrec/lz4/v4" "github.com/pierrec/lz4/v4"
) )
type Lz4 struct{} type Lz4 struct {
pool sync.Pool
}
func (l Lz4) Decompress(data []byte) ([]byte, error) { func NewLz4() *Lz4 {
rdr := lz4.NewReader(bytes.NewReader(data)) return &Lz4{
pool: sync.Pool{
New: func() any {
return lz4.NewReader(nil)
},
},
}
}
func (l *Lz4) Decompress(data []byte) ([]byte, error) {
rdr := l.pool.Get().(*lz4.Reader)
defer l.pool.Put(rdr)
rdr.Reset(bytes.NewReader(data))
return io.ReadAll(rdr) return io.ReadAll(rdr)
} }
+6
View File
@@ -1,3 +1,5 @@
//go:build !no_obsolete
package decompress package decompress
import ( import (
@@ -9,6 +11,10 @@ import (
type Lzma struct{} type Lzma struct{}
func NewLzma() (Lzma, error) {
return Lzma{}, nil
}
func (l Lzma) Decompress(data []byte) ([]byte, error) { func (l Lzma) Decompress(data []byte) ([]byte, error) {
rdr, err := lzma.NewReader(bytes.NewReader(data)) rdr, err := lzma.NewReader(bytes.NewReader(data))
if err != nil { if err != nil {
+17
View File
@@ -0,0 +1,17 @@
//go:build no_obsolete
package decompress
import (
"errors"
)
type Lzma struct{}
func NewLzma() (Lzma, error) {
return Lzma{}, errors.New("lzma compression is disable in this build with no_obsolete")
}
func (l Lzma) Decompress(data []byte) ([]byte, error) {
return nil, errors.New("lzma compression is disable in this build with no_obsolete")
}
+6
View File
@@ -1,3 +1,5 @@
//go:build !no_gpl
package decompress package decompress
import ( import (
@@ -8,6 +10,10 @@ import (
type Lzo struct{} type Lzo struct{}
func NewLzo() (Lzo, error) {
return Lzo{}, nil
}
func (l Lzo) Decompress(data []byte) ([]byte, error) { func (l Lzo) Decompress(data []byte) ([]byte, error) {
return lzo.Decompress1X(bytes.NewReader(data), len(data), 0) return lzo.Decompress1X(bytes.NewReader(data), len(data), 0)
} }
+15
View File
@@ -0,0 +1,15 @@
//go:build no_gpl
package decompress
import "errors"
type Lzo struct{}
func NewLzo() (Lzo, error) {
return Lzo{}, errors.New("lzo compression is disable in this build with no_gpl")
}
func (l Lzo) Decompress(data []byte) ([]byte, error) {
return nil, errors.New("lzo compression is disable in this build with no_gpl")
}
+19 -3
View File
@@ -3,14 +3,30 @@ package decompress
import ( import (
"bytes" "bytes"
"io" "io"
"sync"
"github.com/therootcompany/xz" "github.com/therootcompany/xz"
) )
type Xz struct{} type Xz struct {
pool sync.Pool
}
func (x Xz) Decompress(data []byte) ([]byte, error) { func NewXz() *Xz {
rdr, err := xz.NewReader(bytes.NewReader(data), 0) return &Xz{
pool: sync.Pool{
New: func() any {
rdr, _ := xz.NewReader(nil, 0)
return rdr
},
},
}
}
func (x *Xz) Decompress(data []byte) ([]byte, error) {
rdr := x.pool.Get().(*xz.Reader)
defer x.pool.Put(rdr)
err := rdr.Reset(bytes.NewReader(data))
if err != nil { if err != nil {
return nil, err return nil, err
} }
+20 -6
View File
@@ -2,17 +2,31 @@ package decompress
import ( import (
"bytes" "bytes"
"compress/zlib"
"io" "io"
"sync"
"github.com/klauspost/compress/zlib"
) )
type Zlib struct{} type Zlib struct {
pool sync.Pool
}
func (z Zlib) Decompress(data []byte) ([]byte, error) { func NewZlib() *Zlib {
rdr, err := zlib.NewReader(bytes.NewReader(data)) return &Zlib{}
}
func (z *Zlib) Decompress(data []byte) ([]byte, error) {
rdr := z.pool.Get()
defer z.pool.Put(rdr)
var err error
if rdr == nil {
rdr, err = zlib.NewReader(bytes.NewReader(data))
} else {
err = rdr.(zlib.Resetter).Reset(bytes.NewReader(data), nil)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer rdr.Close() return io.ReadAll(rdr.(io.ReadCloser))
return io.ReadAll(rdr)
} }
+11 -10
View File
@@ -1,19 +1,20 @@
package decompress package decompress
import ( import (
"bytes"
"io"
"github.com/klauspost/compress/zstd" "github.com/klauspost/compress/zstd"
) )
type Zstd struct{} type Zstd struct {
rdr *zstd.Decoder
}
func NewZstd() Zstd {
rdr, _ := zstd.NewReader(nil, zstd.WithDecoderLowmem(true))
return Zstd{
rdr: rdr,
}
}
func (z Zstd) Decompress(data []byte) ([]byte, error) { func (z Zstd) Decompress(data []byte) ([]byte, error) {
rdr, err := zstd.NewReader(bytes.NewReader(data)) return z.rdr.DecodeAll(data, nil)
if err != nil {
return nil, err
}
defer rdr.Close()
return io.ReadAll(rdr)
} }
+7 -9
View File
@@ -14,8 +14,8 @@ type Reader struct {
curOffset uint16 curOffset uint16
} }
func NewReader(r io.Reader, d decompress.Decompressor) *Reader { func NewReader(r io.Reader, d decompress.Decompressor) Reader {
return &Reader{ return Reader{
r: r, r: r,
d: d, d: d,
} }
@@ -23,14 +23,15 @@ func NewReader(r io.Reader, d decompress.Decompressor) *Reader {
func (r *Reader) advance() error { func (r *Reader) advance() error {
r.curOffset = 0 r.curOffset = 0
var size uint16 dat := make([]byte, 2)
err := binary.Read(r.r, binary.LittleEndian, &size) _, err := r.r.Read(dat)
if err != nil { if err != nil {
return err return err
} }
size := binary.LittleEndian.Uint16(dat)
realSize := size &^ 0x8000 realSize := size &^ 0x8000
r.dat = make([]byte, realSize) r.dat = make([]byte, realSize)
err = binary.Read(r.r, binary.LittleEndian, &r.dat) _, err = r.r.Read(r.dat)
if err != nil { if err != nil {
return err return err
} }
@@ -50,10 +51,7 @@ func (r *Reader) Read(b []byte) (int, error) {
return curRead, err return curRead, err
} }
} }
toRead = len(b) - curRead toRead = min(len(b)-curRead, len(r.dat)-int(r.curOffset))
if toRead > len(r.dat)-int(r.curOffset) {
toRead = len(r.dat) - int(r.curOffset)
}
copy(b[curRead:], r.dat[r.curOffset:int(r.curOffset)+toRead]) copy(b[curRead:], r.dat[r.curOffset:int(r.curOffset)+toRead])
r.curOffset += uint16(toRead) r.curOffset += uint16(toRead)
curRead += toRead curRead += toRead
+19
View File
@@ -0,0 +1,19 @@
package toreader
import "io"
type OffsetReader struct {
r io.ReaderAt
off int64
}
func NewOffsetReader(r io.ReaderAt, off int64) *OffsetReader {
return &OffsetReader{
r: r,
off: off,
}
}
func (r OffsetReader) ReadAt(p []byte, off int64) (n int, e error) {
return r.r.ReadAt(p, off+r.off)
}
+107 -21
View File
@@ -1,15 +1,14 @@
package data package data
import ( import (
"encoding/binary"
"errors" "errors"
"io" "io"
"io/fs"
"math" "math"
"runtime" "runtime"
"sync" "sync"
"github.com/CalebQ42/squashfs/internal/decompress" "github.com/CalebQ42/squashfs/internal/decompress"
"github.com/CalebQ42/squashfs/internal/toreader"
) )
type FragReaderConstructor func() (io.Reader, error) type FragReaderConstructor func() (io.Reader, error)
@@ -18,16 +17,16 @@ type FullReader struct {
r io.ReaderAt r io.ReaderAt
d decompress.Decompressor d decompress.Decompressor
frag FragReaderConstructor frag FragReaderConstructor
retPool *sync.Pool
sizes []uint32 sizes []uint32
initialOffset int64 initialOffset int64
finalBlockSize uint64 finalBlockSize uint64
blockSize uint32 blockSize uint32
goroutineLimit uint16 goroutineLimit uint16
closed bool
} }
func NewFullReader(r io.ReaderAt, initialOffset int64, d decompress.Decompressor, sizes []uint32, finalBlockSize uint64, blockSize uint32) *FullReader { func NewFullReader(r io.ReaderAt, initialOffset int64, d decompress.Decompressor, sizes []uint32, finalBlockSize uint64, blockSize uint32) FullReader {
return &FullReader{ return FullReader{
r: r, r: r,
d: d, d: d,
sizes: sizes, sizes: sizes,
@@ -35,19 +34,26 @@ func NewFullReader(r io.ReaderAt, initialOffset int64, d decompress.Decompressor
goroutineLimit: uint16(runtime.NumCPU()), goroutineLimit: uint16(runtime.NumCPU()),
finalBlockSize: finalBlockSize, finalBlockSize: finalBlockSize,
blockSize: blockSize, blockSize: blockSize,
retPool: &sync.Pool{
New: func() any {
return &retValue{}
},
},
} }
} }
func (r *FullReader) Close() error {
r.closed = true
r.r = nil
r.d = nil
r.frag = nil
r.sizes = nil
return nil
}
func (r *FullReader) AddFrag(frag FragReaderConstructor) { func (r *FullReader) AddFrag(frag FragReaderConstructor) {
r.frag = frag r.frag = frag
} }
func (r *FullReader) SetGoroutineLimit(limit uint16) { func (r *FullReader) SetGoroutineLimit(limit uint16) {
if limit <= 0 {
r.goroutineLimit = 1
}
r.goroutineLimit = limit r.goroutineLimit = limit
} }
@@ -57,8 +63,8 @@ type retValue struct {
index uint64 index uint64
} }
func (r *FullReader) process(index uint64, fileOffset uint64, retChan chan *retValue) { func (r FullReader) process(index uint64, fileOffset uint64, pool *sync.Pool, retChan chan *retValue) {
ret := r.retPool.Get().(*retValue) ret := pool.Get().(*retValue)
ret.index = index ret.index = index
realSize := r.sizes[index] &^ (1 << 24) realSize := r.sizes[index] &^ (1 << 24)
if realSize == 0 { if realSize == 0 {
@@ -72,14 +78,20 @@ func (r *FullReader) process(index uint64, fileOffset uint64, retChan chan *retV
return return
} }
ret.data = make([]byte, realSize) ret.data = make([]byte, realSize)
ret.err = binary.Read(toreader.NewReader(r.r, int64(r.initialOffset)+int64(fileOffset)), binary.LittleEndian, &ret.data) _, ret.err = r.r.ReadAt(ret.data, r.initialOffset+int64(fileOffset))
if r.sizes[index] == realSize { if r.sizes[index] == realSize {
ret.data, ret.err = r.d.Decompress(ret.data) ret.data, ret.err = r.d.Decompress(ret.data)
} }
retChan <- ret retChan <- ret
} }
func (r *FullReader) WriteTo(w io.Writer) (int64, error) { func (r FullReader) WriteTo(w io.Writer) (int64, error) {
if r.closed {
return 0, fs.ErrClosed
}
// if wa, is := w.(io.WriterAt); is {
// return r.writeToWriteAt(wa)
// }
var curIndex uint64 var curIndex uint64
var curOffset uint64 var curOffset uint64
var toProcess uint16 var toProcess uint16
@@ -87,14 +99,16 @@ func (r *FullReader) WriteTo(w io.Writer) (int64, error) {
cache := make(map[uint64]*retValue) cache := make(map[uint64]*retValue)
var errCache []error var errCache []error
retChan := make(chan *retValue, r.goroutineLimit) retChan := make(chan *retValue, r.goroutineLimit)
for i := uint64(0); i < uint64(math.Ceil(float64(len(r.sizes))/float64(r.goroutineLimit))); i++ { pool := &sync.Pool{
toProcess = uint16(len(r.sizes)) - (uint16(i) * r.goroutineLimit) New: func() any {
if toProcess > r.goroutineLimit { return &retValue{}
toProcess = r.goroutineLimit },
} }
for i := uint64(0); i < uint64(math.Ceil(float64(len(r.sizes))/float64(r.goroutineLimit))); i++ {
toProcess = min(uint16(len(r.sizes))-(uint16(i)*r.goroutineLimit), r.goroutineLimit)
// Start all the goroutines // Start all the goroutines
for j := uint16(0); j < toProcess; j++ { for j := uint16(0); j < toProcess; j++ {
go r.process((i*uint64(r.goroutineLimit))+uint64(j), curOffset, retChan) go r.process((i*uint64(r.goroutineLimit))+uint64(j), curOffset, pool, retChan)
curOffset += uint64(r.sizes[(i*uint64(r.goroutineLimit))+uint64(j)]) &^ (1 << 24) curOffset += uint64(r.sizes[(i*uint64(r.goroutineLimit))+uint64(j)]) &^ (1 << 24)
} }
// Then consume the results on retChan // Then consume the results on retChan
@@ -128,7 +142,7 @@ func (r *FullReader) WriteTo(w io.Writer) (int64, error) {
} }
continue continue
} }
r.retPool.Put(res) pool.Put(res)
curIndex++ curIndex++
// Now we recursively try to clear the cache // Now we recursively try to clear the cache
for len(cache) > 0 { for len(cache) > 0 {
@@ -146,7 +160,7 @@ func (r *FullReader) WriteTo(w io.Writer) (int64, error) {
break break
} }
delete(cache, curIndex) delete(cache, curIndex)
r.retPool.Put(res) pool.Put(res)
curIndex++ curIndex++
} }
} }
@@ -172,3 +186,75 @@ func (r *FullReader) WriteTo(w io.Writer) (int64, error) {
} }
return wrote, nil return wrote, nil
} }
// func (r FullReader) writeToWriteAt(w io.WriterAt) (out int64, outErr error) {
// wait := &sync.WaitGroup{}
// wait.Add(len(r.sizes))
// mgr := routinemanager.NewManager(r.goroutineLimit)
// curOffset := r.initialOffset
// for i := uint64(0); i < uint64(len(r.sizes)); i++ {
// go func(index uint64, fileOffset int64) {
// lckNum := mgr.Lock()
// defer mgr.Unlock(lckNum)
// defer wait.Done()
// realSize := r.sizes[index] &^ (1 << 24)
// if realSize == 0 {
// if index == uint64(len(r.sizes))-1 && r.frag == nil {
// _, err := w.WriteAt([]byte{0}, int64((uint64(r.blockSize)*index)+r.finalBlockSize)-1)
// if err != nil {
// outErr = errors.Join(outErr, err)
// return
// }
// out = max(out, int64((uint64(r.blockSize)*index)+r.finalBlockSize))
// }
// return
// }
// data := make([]byte, realSize)
// err := binary.Read(toreader.NewReader(r.r, int64(fileOffset)), binary.LittleEndian, &data)
// if err != nil {
// outErr = errors.Join(outErr, err)
// return
// }
// if r.sizes[index] == realSize {
// data, err = r.d.Decompress(data)
// }
// if err != nil {
// outErr = errors.Join(outErr, err)
// return
// }
// _, err = w.WriteAt(data, int64(uint64(r.blockSize)*index))
// if err != nil {
// outErr = errors.Join(outErr, err)
// return
// }
// out = max(out, int64(uint64(r.blockSize)*(index+1)))
// }(i, curOffset)
// curOffset += int64(r.sizes[i]) &^ (1 << 24)
// }
// if r.frag != nil {
// wait.Add(1)
// go func() {
// lckNum := mgr.Lock()
// defer mgr.Unlock(lckNum)
// defer wait.Done()
// rdr, err := r.frag()
// if err != nil {
// outErr = errors.Join(outErr, err)
// return
// }
// dat, err := io.ReadAll(rdr)
// if err != nil {
// outErr = errors.Join(outErr, err)
// return
// }
// _, err = w.WriteAt(dat, int64(int(r.blockSize)*len(r.sizes)))
// if err != nil {
// outErr = errors.Join(outErr, err)
// return
// }
// out = int64(int(r.blockSize)*len(r.sizes)) + int64(r.finalBlockSize)
// }()
// }
// wait.Wait()
// return
// }
+15 -8
View File
@@ -1,8 +1,8 @@
package data package data
import ( import (
"encoding/binary"
"io" "io"
"io/fs"
"github.com/CalebQ42/squashfs/internal/decompress" "github.com/CalebQ42/squashfs/internal/decompress"
) )
@@ -17,10 +17,11 @@ type Reader struct {
curIndex uint64 curIndex uint64
finalBlockSize uint64 finalBlockSize uint64
blockSize uint32 blockSize uint32
closed bool
} }
func NewReader(r io.Reader, d decompress.Decompressor, sizes []uint32, finalBlockSize uint64, blockSize uint32) *Reader { func NewReader(r io.Reader, d decompress.Decompressor, sizes []uint32, finalBlockSize uint64, blockSize uint32) Reader {
return &Reader{ return Reader{
r: r, r: r,
d: d, d: d,
sizes: sizes, sizes: sizes,
@@ -41,6 +42,7 @@ func (r *Reader) advance() error {
r.dat, err = io.ReadAll(r.frag) r.dat, err = io.ReadAll(r.frag)
return err return err
} else if r.curIndex >= uint64(len(r.sizes)) { } else if r.curIndex >= uint64(len(r.sizes)) {
r.dat = []byte{}
return io.EOF return io.EOF
} }
realSize := r.sizes[r.curIndex] &^ (1 << 24) realSize := r.sizes[r.curIndex] &^ (1 << 24)
@@ -53,7 +55,7 @@ func (r *Reader) advance() error {
return nil return nil
} }
r.dat = make([]byte, realSize) r.dat = make([]byte, realSize)
err = binary.Read(r.r, binary.LittleEndian, &r.dat) _, err = r.r.Read(r.dat)
if err != nil { if err != nil {
return err return err
} }
@@ -65,6 +67,9 @@ func (r *Reader) advance() error {
} }
func (r *Reader) Read(b []byte) (int, error) { func (r *Reader) Read(b []byte) (int, error) {
if r.closed {
return 0, fs.ErrClosed
}
curRead := 0 curRead := 0
var toRead int var toRead int
for curRead < len(b) { for curRead < len(b) {
@@ -73,10 +78,7 @@ func (r *Reader) Read(b []byte) (int, error) {
return curRead, err return curRead, err
} }
} }
toRead = len(b) - curRead toRead = min(len(b)-curRead, len(r.dat)-r.curOffset)
if toRead > len(r.dat)-r.curOffset {
toRead = len(r.dat) - r.curOffset
}
toRead = copy(b[curRead:], r.dat[r.curOffset:r.curOffset+toRead]) toRead = copy(b[curRead:], r.dat[r.curOffset:r.curOffset+toRead])
r.curOffset += toRead r.curOffset += toRead
curRead += toRead curRead += toRead
@@ -85,6 +87,9 @@ func (r *Reader) Read(b []byte) (int, error) {
} }
func (r *Reader) Close() error { func (r *Reader) Close() error {
r.closed = true
r.r = nil
r.d = nil
if r.frag != nil { if r.frag != nil {
if l, ok := r.frag.(*io.LimitedReader); ok { if l, ok := r.frag.(*io.LimitedReader); ok {
if cl, ok := l.R.(io.Closer); ok { if cl, ok := l.R.(io.Closer); ok {
@@ -92,6 +97,8 @@ func (r *Reader) Close() error {
} }
} }
} }
r.frag = nil
r.sizes = nil
r.dat = nil r.dat = nil
return nil return nil
} }
+14 -14
View File
@@ -18,10 +18,10 @@ type Directory struct {
Entries []directory.Entry Entries []directory.Entry
} }
func (r *Reader) directoryFromRef(ref uint64, name string) (*Directory, error) { func (r Reader) directoryFromRef(ref uint64, name string) (Directory, error) {
i, err := r.InodeFromRef(ref) i, err := r.InodeFromRef(ref)
if err != nil { if err != nil {
return nil, err return Directory{}, err
} }
var blockStart uint32 var blockStart uint32
var size uint32 var size uint32
@@ -36,48 +36,48 @@ func (r *Reader) directoryFromRef(ref uint64, name string) (*Directory, error) {
size = i.Data.(inode.EDirectory).Size size = i.Data.(inode.EDirectory).Size
offset = i.Data.(inode.EDirectory).Offset offset = i.Data.(inode.EDirectory).Offset
default: default:
return nil, errors.New("not a directory") return Directory{}, errors.New("not a directory")
} }
dirRdr := metadata.NewReader(toreader.NewReader(r.r, int64(r.Superblock.DirTableStart)+int64(blockStart)), r.d) dirRdr := metadata.NewReader(toreader.NewReader(r.r, int64(r.Superblock.DirTableStart)+int64(blockStart)), r.d)
defer dirRdr.Close() defer dirRdr.Close()
_, err = dirRdr.Read(make([]byte, offset)) _, err = dirRdr.Read(make([]byte, offset))
if err != nil { if err != nil {
return nil, err return Directory{}, err
} }
entries, err := directory.ReadDirectory(dirRdr, size) entries, err := directory.ReadDirectory(&dirRdr, size)
if err != nil { if err != nil {
return nil, err return Directory{}, err
} }
return &Directory{ return Directory{
FileBase: *r.BaseFromInode(i, name), FileBase: r.BaseFromInode(i, name),
Entries: entries, Entries: entries,
}, nil }, nil
} }
func (d *Directory) Open(r *Reader, path string) (*FileBase, error) { func (d Directory) Open(r Reader, path string) (FileBase, error) {
path = filepath.Clean(path) path = filepath.Clean(path)
if path == "." || path == "" { if path == "." || path == "" {
return &d.FileBase, nil return d.FileBase, nil
} }
split := strings.Split(path, "/") split := strings.Split(path, "/")
i, found := slices.BinarySearchFunc(d.Entries, split[0], func(e directory.Entry, name string) int { i, found := slices.BinarySearchFunc(d.Entries, split[0], func(e directory.Entry, name string) int {
return strings.Compare(e.Name, name) return strings.Compare(e.Name, name)
}) })
if !found { if !found {
return nil, fs.ErrNotExist return FileBase{}, fs.ErrNotExist
} }
b, err := r.BaseFromEntry(d.Entries[i]) b, err := r.BaseFromEntry(d.Entries[i])
if err != nil { if err != nil {
return nil, err return FileBase{}, err
} }
if len(split) == 1 { if len(split) == 1 {
return b, nil return b, nil
} else if !b.IsDir() { } else if !b.IsDir() {
return nil, fs.ErrNotExist return FileBase{}, fs.ErrNotExist
} }
dir, err := b.ToDir(r) dir, err := b.ToDir(r)
if err != nil { if err != nil {
return nil, err return FileBase{}, err
} }
return dir.Open(r, strings.Join(split[1:], "/")) return dir.Open(r, strings.Join(split[1:], "/"))
} }
+31 -31
View File
@@ -12,43 +12,43 @@ import (
) )
type FileBase struct { type FileBase struct {
Inode *inode.Inode Inode inode.Inode
Name string Name string
} }
func (r *Reader) BaseFromInode(i *inode.Inode, name string) *FileBase { func (r Reader) BaseFromInode(i inode.Inode, name string) FileBase {
return &FileBase{Inode: i, Name: name} return FileBase{Inode: i, Name: name}
} }
func (r *Reader) BaseFromEntry(e directory.Entry) (*FileBase, error) { func (r Reader) BaseFromEntry(e directory.Entry) (FileBase, error) {
in, err := r.InodeFromEntry(e) in, err := r.InodeFromEntry(e)
if err != nil { if err != nil {
return nil, err return FileBase{}, err
} }
return &FileBase{Inode: in, Name: e.Name}, nil return FileBase{Inode: in, Name: e.Name}, nil
} }
func (r *Reader) BaseFromRef(ref uint64, name string) (*FileBase, error) { func (r Reader) BaseFromRef(ref uint64, name string) (FileBase, error) {
in, err := r.InodeFromRef(ref) in, err := r.InodeFromRef(ref)
if err != nil { if err != nil {
return nil, err return FileBase{}, err
} }
return &FileBase{Inode: in, Name: name}, nil return FileBase{Inode: in, Name: name}, nil
} }
func (b *FileBase) Uid(r *Reader) (uint32, error) { func (b FileBase) Uid(r *Reader) (uint32, error) {
return r.Id(b.Inode.UidInd) return r.Id(b.Inode.UidInd)
} }
func (b *FileBase) Gid(r *Reader) (uint32, error) { func (b FileBase) Gid(r *Reader) (uint32, error) {
return r.Id(b.Inode.GidInd) return r.Id(b.Inode.GidInd)
} }
func (b *FileBase) IsDir() bool { func (b FileBase) IsDir() bool {
return b.Inode.Type == inode.Dir || b.Inode.Type == inode.EDir return b.Inode.Type == inode.Dir || b.Inode.Type == inode.EDir
} }
func (b *FileBase) ToDir(r *Reader) (*Directory, error) { func (b FileBase) ToDir(r Reader) (Directory, error) {
var blockStart uint32 var blockStart uint32
var size uint32 var size uint32
var offset uint16 var offset uint16
@@ -62,31 +62,31 @@ func (b *FileBase) ToDir(r *Reader) (*Directory, error) {
size = b.Inode.Data.(inode.EDirectory).Size size = b.Inode.Data.(inode.EDirectory).Size
offset = b.Inode.Data.(inode.EDirectory).Offset offset = b.Inode.Data.(inode.EDirectory).Offset
default: default:
return nil, errors.New("not a directory") return Directory{}, errors.New("not a directory")
} }
dirRdr := metadata.NewReader(toreader.NewReader(r.r, int64(r.Superblock.DirTableStart)+int64(blockStart)), r.d) dirRdr := metadata.NewReader(toreader.NewReader(r.r, int64(r.Superblock.DirTableStart)+int64(blockStart)), r.d)
defer dirRdr.Close() defer dirRdr.Close()
_, err := dirRdr.Read(make([]byte, offset)) _, err := dirRdr.Read(make([]byte, offset))
if err != nil { if err != nil {
return nil, err return Directory{}, err
} }
entries, err := directory.ReadDirectory(dirRdr, size) entries, err := directory.ReadDirectory(&dirRdr, size)
if err != nil { if err != nil {
return nil, err return Directory{}, err
} }
return &Directory{ return Directory{
FileBase: *b, FileBase: b,
Entries: entries, Entries: entries,
}, nil }, nil
} }
func (b *FileBase) IsRegular() bool { func (b FileBase) IsRegular() bool {
return b.Inode.Type == inode.Fil || b.Inode.Type == inode.EFil return b.Inode.Type == inode.Fil || b.Inode.Type == inode.EFil
} }
func (b *FileBase) GetRegFileReaders(r *Reader) (*data.Reader, *data.FullReader, error) { func (b FileBase) GetRegFileReaders(r Reader) (data.Reader, data.FullReader, error) {
if !b.IsRegular() { if !b.IsRegular() {
return nil, nil, errors.New("not a regular file") return data.Reader{}, data.FullReader{}, errors.New("not a regular file")
} }
var blockStart uint64 var blockStart uint64
var fragIndex uint32 var fragIndex uint32
@@ -113,13 +113,13 @@ func (b *FileBase) GetRegFileReaders(r *Reader) (*data.Reader, *data.FullReader,
} }
frag := data.NewReader(toreader.NewReader(r.r, int64(ent.Start)), r.d, []uint32{ent.Size}, uint64(r.Superblock.BlockSize), r.Superblock.BlockSize) frag := data.NewReader(toreader.NewReader(r.r, int64(ent.Start)), r.d, []uint32{ent.Size}, uint64(r.Superblock.BlockSize), r.Superblock.BlockSize)
frag.Read(make([]byte, fragOffset)) frag.Read(make([]byte, fragOffset))
return io.LimitReader(frag, int64(fragSize)), nil return io.LimitReader(&frag, int64(fragSize)), nil
} }
outRdr := data.NewReader(toreader.NewReader(r.r, int64(blockStart)), r.d, sizes, fragSize, r.Superblock.BlockSize) outRdr := data.NewReader(toreader.NewReader(r.r, int64(blockStart)), r.d, sizes, fragSize, r.Superblock.BlockSize)
if fragIndex != 0xffffffff { if fragIndex != 0xffffffff {
f, err := frag() f, err := frag()
if err != nil { if err != nil {
return nil, nil, err return data.Reader{}, data.FullReader{}, err
} }
outRdr.AddFrag(f) outRdr.AddFrag(f)
} }
@@ -130,9 +130,9 @@ func (b *FileBase) GetRegFileReaders(r *Reader) (*data.Reader, *data.FullReader,
return outRdr, outFull, nil return outRdr, outFull, nil
} }
func (b *FileBase) GetFullReader(r *Reader) (*data.FullReader, error) { func (b FileBase) GetFullReader(r *Reader) (data.FullReader, error) {
if !b.IsRegular() { if !b.IsRegular() {
return nil, errors.New("not a regular file") return data.FullReader{}, errors.New("not a regular file")
} }
var blockStart uint64 var blockStart uint64
var fragIndex uint32 var fragIndex uint32
@@ -161,15 +161,15 @@ func (b *FileBase) GetFullReader(r *Reader) (*data.FullReader, error) {
} }
frag := data.NewReader(toreader.NewReader(r.r, int64(ent.Start)), r.d, []uint32{ent.Size}, uint64(r.Superblock.BlockSize), r.Superblock.BlockSize) frag := data.NewReader(toreader.NewReader(r.r, int64(ent.Start)), r.d, []uint32{ent.Size}, uint64(r.Superblock.BlockSize), r.Superblock.BlockSize)
frag.Read(make([]byte, fragOffset)) frag.Read(make([]byte, fragOffset))
return io.LimitReader(frag, int64(fragSize)), nil return io.LimitReader(&frag, int64(fragSize)), nil
}) })
} }
return outFull, nil return outFull, nil
} }
func (b *FileBase) GetReader(r *Reader) (*data.Reader, error) { func (b FileBase) GetReader(r *Reader) (data.Reader, error) {
if !b.IsRegular() { if !b.IsRegular() {
return nil, errors.New("not a regular file") return data.Reader{}, errors.New("not a regular file")
} }
var blockStart uint64 var blockStart uint64
var fragIndex uint32 var fragIndex uint32
@@ -193,11 +193,11 @@ func (b *FileBase) GetReader(r *Reader) (*data.Reader, error) {
if fragIndex != 0xffffffff { if fragIndex != 0xffffffff {
ent, err := r.fragEntry(fragIndex) ent, err := r.fragEntry(fragIndex)
if err != nil { if err != nil {
return nil, err return data.Reader{}, err
} }
frag := data.NewReader(toreader.NewReader(r.r, int64(ent.Start)), r.d, []uint32{ent.Size}, uint64(r.Superblock.BlockSize), r.Superblock.BlockSize) frag := data.NewReader(toreader.NewReader(r.r, int64(ent.Start)), r.d, []uint32{ent.Size}, uint64(r.Superblock.BlockSize), r.Superblock.BlockSize)
frag.Read(make([]byte, fragOffset)) frag.Read(make([]byte, fragOffset))
outRdr.AddFrag(io.LimitReader(frag, int64(fragSize))) outRdr.AddFrag(io.LimitReader(&frag, int64(fragSize)))
} }
return outRdr, nil return outRdr, nil
} }
+5 -5
View File
@@ -7,20 +7,20 @@ import (
"github.com/CalebQ42/squashfs/low/inode" "github.com/CalebQ42/squashfs/low/inode"
) )
func (r *Reader) InodeFromRef(ref uint64) (*inode.Inode, error) { func (r Reader) InodeFromRef(ref uint64) (inode.Inode, error) {
offset, meta := (ref>>16)+r.Superblock.InodeTableStart, ref&0xFFFF offset, meta := (ref>>16)+r.Superblock.InodeTableStart, ref&0xFFFF
rdr := metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d) rdr := metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d)
defer rdr.Close() defer rdr.Close()
_, err := rdr.Read(make([]byte, meta)) _, err := rdr.Read(make([]byte, meta))
if err != nil { if err != nil {
return nil, err return inode.Inode{}, err
} }
return inode.Read(rdr, r.Superblock.BlockSize) return inode.Read(&rdr, r.Superblock.BlockSize)
} }
func (r *Reader) InodeFromEntry(e directory.Entry) (*inode.Inode, error) { func (r Reader) InodeFromEntry(e directory.Entry) (inode.Inode, error) {
rdr := metadata.NewReader(toreader.NewReader(r.r, int64(r.Superblock.InodeTableStart)+int64(e.BlockStart)), r.d) rdr := metadata.NewReader(toreader.NewReader(r.r, int64(r.Superblock.InodeTableStart)+int64(e.BlockStart)), r.d)
defer rdr.Close() defer rdr.Close()
rdr.Read(make([]byte, e.Offset)) rdr.Read(make([]byte, e.Offset))
return inode.Read(rdr, r.Superblock.BlockSize) return inode.Read(&rdr, r.Superblock.BlockSize)
} }
+28 -15
View File
@@ -13,7 +13,7 @@ type Directory struct {
ParentNum uint32 ParentNum uint32
} }
type eDirectoryInit struct { type EDirectory struct {
LinkCount uint32 LinkCount uint32
Size uint32 Size uint32
BlockStart uint32 BlockStart uint32
@@ -21,42 +21,55 @@ type eDirectoryInit struct {
IndCount uint16 IndCount uint16
Offset uint16 Offset uint16
XattrInd uint32 XattrInd uint32
}
type EDirectory struct {
eDirectoryInit
Indexes []DirectoryIndex Indexes []DirectoryIndex
} }
type directoryIndexInit struct { type DirectoryIndex struct {
Ind uint32 Ind uint32
Start uint32 Start uint32
NameSize uint32 NameSize uint32
}
type DirectoryIndex struct {
directoryIndexInit
Name []byte Name []byte
} }
func ReadDir(r io.Reader) (d Directory, err error) { func ReadDir(r io.Reader) (d Directory, err error) {
err = binary.Read(r, binary.LittleEndian, &d) dat := make([]byte, 16)
_, err = r.Read(dat)
if err != nil {
return
}
d.BlockStart = binary.LittleEndian.Uint32(dat)
d.LinkCount = binary.LittleEndian.Uint32(dat[4:])
d.Size = binary.LittleEndian.Uint16(dat[8:])
d.Offset = binary.LittleEndian.Uint16(dat[10:])
d.ParentNum = binary.LittleEndian.Uint32(dat[12:])
return return
} }
func ReadEDir(r io.Reader) (d EDirectory, err error) { func ReadEDir(r io.Reader) (d EDirectory, err error) {
err = binary.Read(r, binary.LittleEndian, &d.eDirectoryInit) dat := make([]byte, 24)
_, err = r.Read(dat)
if err != nil { if err != nil {
return return
} }
d.LinkCount = binary.LittleEndian.Uint32(dat)
d.Size = binary.LittleEndian.Uint32(dat[4:])
d.BlockStart = binary.LittleEndian.Uint32(dat[8:])
d.ParentNum = binary.LittleEndian.Uint32(dat[12:])
d.IndCount = binary.LittleEndian.Uint16(dat[16:])
d.Offset = binary.LittleEndian.Uint16(dat[18:])
d.XattrInd = binary.LittleEndian.Uint32(dat[20:])
d.Indexes = make([]DirectoryIndex, d.IndCount) d.Indexes = make([]DirectoryIndex, d.IndCount)
for i := range d.Indexes { for i := range d.IndCount {
err = binary.Read(r, binary.LittleEndian, &d.Indexes[i].directoryIndexInit) dat = make([]byte, 12)
_, err = r.Read(dat)
if err != nil { if err != nil {
return return
} }
d.Indexes[i].Ind = binary.LittleEndian.Uint32(dat)
d.Indexes[i].Start = binary.LittleEndian.Uint32(dat[4:])
d.Indexes[i].NameSize = binary.LittleEndian.Uint32(dat[8:])
d.Indexes[i].Name = make([]byte, d.Indexes[i].NameSize+1) d.Indexes[i].Name = make([]byte, d.Indexes[i].NameSize+1)
err = binary.Read(r, binary.LittleEndian, &d.Indexes[i].Name) _, err = r.Read(d.Indexes[i].Name)
if err != nil { if err != nil {
return return
} }
+15 -7
View File
@@ -6,15 +6,11 @@ import (
"math" "math"
) )
type fileInit struct { type File struct {
BlockStart uint32 BlockStart uint32
FragInd uint32 FragInd uint32
FragOffset uint32 FragOffset uint32
Size uint32 Size uint32
}
type File struct {
fileInit
BlockSizes []uint32 BlockSizes []uint32
} }
@@ -34,16 +30,28 @@ type EFile struct {
} }
func ReadFile(r io.Reader, blockSize uint32) (f File, err error) { func ReadFile(r io.Reader, blockSize uint32) (f File, err error) {
err = binary.Read(r, binary.LittleEndian, &f.fileInit) dat := make([]byte, 16)
_, err = r.Read(dat)
if err != nil { if err != nil {
return return
} }
f.BlockStart = binary.LittleEndian.Uint32(dat)
f.FragInd = binary.LittleEndian.Uint32(dat[4:])
f.FragOffset = binary.LittleEndian.Uint32(dat[8:])
f.Size = binary.LittleEndian.Uint32(dat[12:])
toRead := int(math.Floor(float64(f.Size) / float64(blockSize))) toRead := int(math.Floor(float64(f.Size) / float64(blockSize)))
if f.FragInd == 0xFFFFFFFF && f.Size%blockSize > 0 { if f.FragInd == 0xFFFFFFFF && f.Size%blockSize > 0 {
toRead++ toRead++
} }
dat = make([]byte, toRead*4)
_, err = r.Read(dat)
if err != nil {
return
}
f.BlockSizes = make([]uint32, toRead) f.BlockSizes = make([]uint32, toRead)
err = binary.Read(r, binary.LittleEndian, &f.BlockSizes) for i := range toRead {
f.BlockSizes[i] = binary.LittleEndian.Uint32(dat[i*4:])
}
return return
} }
+8 -15
View File
@@ -39,8 +39,7 @@ type Inode struct {
Data any Data any
} }
func Read(r io.Reader, blockSize uint32) (i *Inode, err error) { func Read(r io.Reader, blockSize uint32) (i Inode, err error) {
i = new(Inode)
err = binary.Read(r, binary.LittleEndian, &i.Header) err = binary.Read(r, binary.LittleEndian, &i.Header)
if err != nil { if err != nil {
return return
@@ -82,23 +81,17 @@ func Read(r io.Reader, blockSize uint32) (i *Inode, err error) {
func (i Inode) Mode() (out fs.FileMode) { func (i Inode) Mode() (out fs.FileMode) {
out = fs.FileMode(i.Perm) out = fs.FileMode(i.Perm)
switch i.Data.(type) { switch i.Type {
case Directory: case Dir, EDir:
out |= fs.ModeDir out |= fs.ModeDir
case EDirectory: case Sym, ESym:
out |= fs.ModeDir
case Symlink:
out |= fs.ModeSymlink out |= fs.ModeSymlink
case ESymlink: case Char, EChar, Block, EBlock:
out |= fs.ModeSymlink
case Device:
out |= fs.ModeDevice out |= fs.ModeDevice
case EDevice: case Fifo, EFifo:
out |= fs.ModeDevice
case IPC:
out |= fs.ModeNamedPipe
case EIPC:
out |= fs.ModeNamedPipe out |= fs.ModeNamedPipe
case Sock, ESock:
out |= fs.ModeSocket
} }
return return
} }
+37 -38
View File
@@ -32,48 +32,53 @@ var (
type Reader struct { type Reader struct {
r io.ReaderAt r io.ReaderAt
d decompress.Decompressor d decompress.Decompressor
Root *Directory Root Directory
fragTable []fragEntry fragTable []fragEntry
idTable []uint32 idTable []uint32
exportTable []uint64 exportTable []uint64
Superblock superblock Superblock superblock
} }
func NewReader(r io.ReaderAt) (rdr *Reader, err error) { func NewReader(r io.ReaderAt) (rdr Reader, err error) {
rdr = new(Reader)
rdr.r = r rdr.r = r
err = binary.Read(toreader.NewReader(r, 0), binary.LittleEndian, &rdr.Superblock) err = binary.Read(toreader.NewReader(r, 0), binary.LittleEndian, &rdr.Superblock)
if err != nil { if err != nil {
return nil, errors.Join(errors.New("failed to read superblock"), err) return rdr, errors.Join(errors.New("failed to read superblock"), err)
} }
if !rdr.Superblock.ValidMagic() { if !rdr.Superblock.ValidMagic() {
return nil, ErrorMagic return rdr, ErrorMagic
} }
if !rdr.Superblock.ValidBlockLog() { if !rdr.Superblock.ValidBlockLog() {
return nil, ErrorLog return rdr, ErrorLog
} }
if !rdr.Superblock.ValidVersion() { if !rdr.Superblock.ValidVersion() {
return nil, ErrorVersion return rdr, ErrorVersion
} }
switch rdr.Superblock.CompType { switch rdr.Superblock.CompType {
case ZlibCompression: case ZlibCompression:
rdr.d = decompress.Zlib{} rdr.d = decompress.NewZlib()
case LZMACompression: case LZMACompression:
rdr.d = decompress.Lzma{} rdr.d, err = decompress.NewLzma()
if err != nil {
return rdr, err
}
case LZOCompression: case LZOCompression:
rdr.d = decompress.Lzo{} rdr.d, err = decompress.NewLzo()
if err != nil {
return rdr, err
}
case XZCompression: case XZCompression:
rdr.d = decompress.Xz{} rdr.d = decompress.NewXz()
case LZ4Compression: case LZ4Compression:
rdr.d = decompress.Lz4{} rdr.d = decompress.NewLz4()
case ZSTDCompression: case ZSTDCompression:
rdr.d = &decompress.Zstd{} rdr.d = decompress.NewZstd()
default: default:
return nil, errors.New("invalid compression type. possible corrupted archive") return rdr, errors.New("invalid compression type. possible corrupted archive")
} }
rdr.Root, err = rdr.directoryFromRef(rdr.Superblock.RootInodeRef, "") rdr.Root, err = rdr.directoryFromRef(rdr.Superblock.RootInodeRef, "")
if err != nil { if err != nil {
return nil, errors.Join(errors.New("failed to read root directory"), err) return rdr, errors.Join(errors.New("failed to read root directory"), err)
} }
return return
} }
@@ -88,7 +93,7 @@ func (r *Reader) Id(i uint16) (uint32, error) {
// Populate the id table as needed // Populate the id table as needed
var blockNum uint32 var blockNum uint32
if i != 0 { // If i == 0, we go negatives causing issues with uint32s if i != 0 { // If i == 0, we go negatives causing issues with uint32s
blockNum = uint32(math.Ceil(float64(i)/2048)) - 1 blockNum = uint32(math.Ceil(float64(i+1)/2048)) - 1
} else { } else {
blockNum = 0 blockNum = 0
} }
@@ -99,19 +104,17 @@ func (r *Reader) Id(i uint16) (uint32, error) {
var idsToRead uint16 var idsToRead uint16
var idsTmp []uint32 var idsTmp []uint32
var err error var err error
var rdr *metadata.Reader var rdr metadata.Reader
// We can *maybe* have a slight speed increase by manually decoding instead of using reflection via binary.Read
for i := blocksRead; i < int(blocksRead)+blocksToRead; i++ { for i := blocksRead; i < int(blocksRead)+blocksToRead; i++ {
err = binary.Read(toreader.NewReader(r.r, int64(r.Superblock.IdTableStart)+int64(8*i)), binary.LittleEndian, &offset) err = binary.Read(toreader.NewReader(r.r, int64(r.Superblock.IdTableStart)+int64(8*i)), binary.LittleEndian, &offset)
if err != nil { if err != nil {
return 0, err return 0, err
} }
idsToRead = r.Superblock.IdCount - uint16(len(r.idTable)) idsToRead = min(r.Superblock.IdCount-uint16(len(r.idTable)), 2048)
if idsToRead > 2048 {
idsToRead = 2048
}
idsTmp = make([]uint32, idsToRead) idsTmp = make([]uint32, idsToRead)
rdr = metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d) rdr = metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d)
err = binary.Read(rdr, binary.LittleEndian, &idsTmp) err = binary.Read(&rdr, binary.LittleEndian, &idsTmp)
rdr.Close() rdr.Close()
if err != nil { if err != nil {
return 0, err return 0, err
@@ -131,7 +134,7 @@ func (r *Reader) fragEntry(i uint32) (fragEntry, error) {
// Populate the fragment table as needed // Populate the fragment table as needed
var blockNum uint32 var blockNum uint32
if i != 0 { // If i == 0, we go negatives causing issues with uint32s if i != 0 { // If i == 0, we go negatives causing issues with uint32s
blockNum = uint32(math.Ceil(float64(i)/512)) - 1 blockNum = uint32(math.Ceil(float64(i+1)/512)) - 1
} else { } else {
blockNum = 0 blockNum = 0
} }
@@ -142,19 +145,17 @@ func (r *Reader) fragEntry(i uint32) (fragEntry, error) {
var fragsToRead uint32 var fragsToRead uint32
var fragsTmp []fragEntry var fragsTmp []fragEntry
var err error var err error
var rdr *metadata.Reader var rdr metadata.Reader
// We can *maybe* have a slight speed increase by manually decoding instead of using reflection via binary.Read
for i := blocksRead; i < int(blocksRead)+blocksToRead; i++ { for i := blocksRead; i < int(blocksRead)+blocksToRead; i++ {
err = binary.Read(toreader.NewReader(r.r, int64(r.Superblock.FragTableStart)+int64(8*i)), binary.LittleEndian, &offset) err = binary.Read(toreader.NewReader(r.r, int64(r.Superblock.FragTableStart)+int64(8*i)), binary.LittleEndian, &offset)
if err != nil { if err != nil {
return fragEntry{}, err return fragEntry{}, err
} }
fragsToRead = r.Superblock.FragCount - uint32(len(r.fragTable)) fragsToRead = min(r.Superblock.FragCount-uint32(len(r.fragTable)), 512)
if fragsToRead > 512 {
fragsToRead = 512
}
fragsTmp = make([]fragEntry, fragsToRead) fragsTmp = make([]fragEntry, fragsToRead)
rdr = metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d) rdr = metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d)
err = binary.Read(rdr, binary.LittleEndian, &fragsTmp) err = binary.Read(&rdr, binary.LittleEndian, &fragsTmp)
rdr.Close() rdr.Close()
if err != nil { if err != nil {
return fragEntry{}, err return fragEntry{}, err
@@ -177,7 +178,7 @@ func (r *Reader) inodeRef(i uint32) (uint64, error) {
// Populate the export table as needed // Populate the export table as needed
var blockNum uint32 var blockNum uint32
if i != 0 { // If i == 0, we go negatives causing issues with uint32s if i != 0 { // If i == 0, we go negatives causing issues with uint32s
blockNum = uint32(math.Ceil(float64(i)/1024)) - 1 blockNum = uint32(math.Ceil(float64(i+1)/1024)) - 1
} else { } else {
blockNum = 0 blockNum = 0
} }
@@ -188,19 +189,17 @@ func (r *Reader) inodeRef(i uint32) (uint64, error) {
var refsToRead uint32 var refsToRead uint32
var refsTmp []uint64 var refsTmp []uint64
var err error var err error
var rdr *metadata.Reader var rdr metadata.Reader
// We can *maybe* have a slight speed increase by manually decoding instead of using reflection via binary.Read
for i := blocksRead; i < int(blocksRead)+blocksToRead; i++ { for i := blocksRead; i < int(blocksRead)+blocksToRead; i++ {
err = binary.Read(toreader.NewReader(r.r, int64(r.Superblock.ExportTableStart)+int64(8*i)), binary.LittleEndian, &offset) err = binary.Read(toreader.NewReader(r.r, int64(r.Superblock.ExportTableStart)+int64(8*i)), binary.LittleEndian, &offset)
if err != nil { if err != nil {
return 0, err return 0, err
} }
refsToRead = r.Superblock.InodeCount - uint32(len(r.exportTable)) refsToRead = min(r.Superblock.InodeCount-uint32(len(r.exportTable)), 1024)
if refsToRead > 1024 {
refsToRead = 1024
}
refsTmp = make([]uint64, refsToRead) refsTmp = make([]uint64, refsToRead)
rdr = metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d) rdr = metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d)
err = binary.Read(rdr, binary.LittleEndian, &refsTmp) err = binary.Read(&rdr, binary.LittleEndian, &refsTmp)
rdr.Close() rdr.Close()
if err != nil { if err != nil {
return 0, err return 0, err
@@ -210,10 +209,10 @@ func (r *Reader) inodeRef(i uint32) (uint64, error) {
return r.exportTable[i], nil return r.exportTable[i], nil
} }
func (r *Reader) Inode(i uint32) (*inode.Inode, error) { func (r Reader) Inode(i uint32) (inode.Inode, error) {
ref, err := r.inodeRef(i) ref, err := r.inodeRef(i)
if err != nil { if err != nil {
return nil, err return inode.Inode{}, err
} }
return r.InodeFromRef(ref) return r.InodeFromRef(ref)
} }
+22 -9
View File
@@ -1,4 +1,4 @@
package squashfslow_test package squashfslow
import ( import (
"fmt" "fmt"
@@ -8,13 +8,11 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"testing" "testing"
squashfslow "github.com/CalebQ42/squashfs/low"
) )
const ( const (
squashfsURL = "https://darkstorm.tech/files/LinuxPATest.sfs" squashfsURL = "https://darkstorm.tech/files/LinuxPATest.sfs"
squashfsName = "LinuxPATest.sfs" squashfsName = "airootfs.sfs"
) )
func preTest(dir string) (fil *os.File, err error) { func preTest(dir string) (fil *os.File, err error) {
@@ -50,6 +48,21 @@ func preTest(dir string) (fil *os.File, err error) {
return return
} }
func TestMisc(t *testing.T) {
tmpDir := "../testing"
fil, err := preTest(tmpDir)
if err != nil {
t.Fatal(err)
}
defer fil.Close()
rdr, err := NewReader(fil)
if err != nil {
t.Fatal(err)
}
t.Log(rdr.Superblock.FragCount)
t.Fatal(rdr.fragEntry(1233))
}
func TestReader(t *testing.T) { func TestReader(t *testing.T) {
tmpDir := "../testing" tmpDir := "../testing"
fil, err := preTest(tmpDir) fil, err := preTest(tmpDir)
@@ -57,14 +70,14 @@ func TestReader(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
defer fil.Close() defer fil.Close()
rdr, err := squashfslow.NewReader(fil) rdr, err := NewReader(fil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
path := filepath.Join(tmpDir, "extractTest") path := filepath.Join(tmpDir, "extractTest")
os.RemoveAll(path) os.RemoveAll(path)
os.MkdirAll(path, 0777) os.MkdirAll(path, 0777)
err = extractToDir(rdr, &rdr.Root.FileBase, path) err = extractToDir(rdr, rdr.Root.FileBase, path)
t.Fatal(err) t.Fatal(err)
} }
@@ -77,7 +90,7 @@ func TestSingleFile(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
defer fil.Close() defer fil.Close()
rdr, err := squashfslow.NewReader(fil) rdr, err := NewReader(fil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -92,7 +105,7 @@ func TestSingleFile(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
func extractToDir(rdr *squashfslow.Reader, b *squashfslow.FileBase, folder string) error { func extractToDir(rdr Reader, b FileBase, folder string) error {
path := filepath.Join(folder, b.Name) path := filepath.Join(folder, b.Name)
if b.IsDir() { if b.IsDir() {
d, err := b.ToDir(rdr) d, err := b.ToDir(rdr)
@@ -103,7 +116,7 @@ func extractToDir(rdr *squashfslow.Reader, b *squashfslow.FileBase, folder strin
if err != nil { if err != nil {
return err return err
} }
var nestBast *squashfslow.FileBase var nestBast FileBase
for _, e := range d.Entries { for _, e := range d.Entries {
nestBast, err = rdr.BaseFromEntry(e) nestBast, err = rdr.BaseFromEntry(e)
if err != nil { if err != nil {
+13 -8
View File
@@ -4,29 +4,34 @@ import (
"io" "io"
"time" "time"
"github.com/CalebQ42/squashfs/internal/toreader"
squashfslow "github.com/CalebQ42/squashfs/low" squashfslow "github.com/CalebQ42/squashfs/low"
) )
type Reader struct { type Reader struct {
*FS FS
Low *squashfslow.Reader Low squashfslow.Reader
} }
func NewReader(r io.ReaderAt) (*Reader, error) { func NewReader(r io.ReaderAt) (Reader, error) {
rdr, err := squashfslow.NewReader(r) rdr, err := squashfslow.NewReader(r)
if err != nil { if err != nil {
return nil, err return Reader{}, err
} }
out := &Reader{ out := Reader{
Low: rdr, Low: rdr,
} }
out.FS = &FS{ out.FS = FS{
d: rdr.Root, LowDir: rdr.Root,
r: out, r: &out,
} }
return out, nil return out, nil
} }
func NewReaderAtOffset(r io.ReaderAt, offset int64) (Reader, error) {
return NewReader(toreader.NewOffsetReader(r, offset))
}
func (r *Reader) ModTime() time.Time { func (r *Reader) ModTime() time.Time {
return time.Unix(int64(r.Low.Superblock.ModTime), 0) return time.Unix(int64(r.Low.Superblock.ModTime), 0)
} }
+34 -18
View File
@@ -1,4 +1,4 @@
package squashfs_test package squashfs
//Actually proper tests go here. //Actually proper tests go here.
@@ -13,13 +13,11 @@ import (
"strconv" "strconv"
"testing" "testing"
"time" "time"
"github.com/CalebQ42/squashfs"
) )
const ( const (
squashfsURL = "https://darkstorm.tech/files/LinuxPATest.sfs" squashfsURL = "https://darkstorm.tech/files/LinuxPATest.sfs"
squashfsName = "airootfs.sfs" squashfsName = "tensorflow.sqfs"
) )
func preTest(dir string) (fil *os.File, err error) { func preTest(dir string) (fil *os.File, err error) {
@@ -61,13 +59,31 @@ func TestMisc(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
rdr, err := squashfs.NewReader(fil) rdr, err := NewReader(fil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_ = rdr _ = rdr
// Put testing here // Put testing here
t.Fatal("UM") // t.Fatal("UM")
}
func BenchmarkExtract(b *testing.B) {
tmpDir := "testing"
fil, err := preTest(tmpDir)
if err != nil {
b.Fatal(err)
}
libPath := filepath.Join(tmpDir, "ExtractLib")
os.RemoveAll(libPath)
rdr, err := NewReader(fil)
if err != nil {
b.Fatal(err)
}
err = rdr.ExtractWithOptions(libPath, FastOptions())
if err != nil {
b.Fatal(err)
}
} }
func BenchmarkRace(b *testing.B) { func BenchmarkRace(b *testing.B) {
@@ -81,18 +97,17 @@ func BenchmarkRace(b *testing.B) {
os.RemoveAll(libPath) os.RemoveAll(libPath)
os.RemoveAll(unsquashPath) os.RemoveAll(unsquashPath)
var libTime, unsquashTime time.Duration var libTime, unsquashTime time.Duration
op := squashfs.FastOptions()
start := time.Now() start := time.Now()
rdr, err := squashfs.NewReader(fil) rdr, err := NewReader(fil)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
err = rdr.ExtractWithOptions(libPath, op) err = rdr.ExtractWithOptions(libPath, FastOptions())
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
libTime = time.Since(start) libTime = time.Since(start)
cmd := exec.Command("unsquashfs", "-d", unsquashPath, fil.Name()) cmd := exec.Command("unsquashfs", "-q", "-d", unsquashPath, fil.Name())
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
start = time.Now() start = time.Now()
@@ -109,7 +124,7 @@ func BenchmarkRace(b *testing.B) {
func TestExtractQuick(t *testing.T) { func TestExtractQuick(t *testing.T) {
//First, setup everything and extract the archive using the library and unsquashfs //First, setup everything and extract the archive using the library and unsquashfs
// tmpDir := b.TempDir() // tmpDir := bTempDir()
tmpDir := "testing" tmpDir := "testing"
fil, err := preTest(tmpDir) fil, err := preTest(tmpDir)
if err != nil { if err != nil {
@@ -119,13 +134,13 @@ func TestExtractQuick(t *testing.T) {
unsquashPath := filepath.Join(tmpDir, "ExtractSquashfs") unsquashPath := filepath.Join(tmpDir, "ExtractSquashfs")
os.RemoveAll(libPath) os.RemoveAll(libPath)
os.RemoveAll(unsquashPath) os.RemoveAll(unsquashPath)
rdr, err := squashfs.NewReader(fil) rdr, err := NewReader(fil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
os.RemoveAll(filepath.Join(tmpDir, "testLog.txt")) os.RemoveAll(filepath.Join(tmpDir, "testLog.txt"))
logFil, _ := os.Create(filepath.Join(tmpDir, "testLog.txt")) logFil, _ := os.Create(filepath.Join(tmpDir, "testLog.txt"))
op := squashfs.DefaultOptions() op := FastOptions()
op.Verbose = true op.Verbose = true
op.IgnorePerm = true op.IgnorePerm = true
op.LogOutput = logFil op.LogOutput = logFil
@@ -168,7 +183,7 @@ func TestExtractQuick(t *testing.T) {
} }
} }
var filePath = "bin" var filePath = "usr/sbin/add-shell"
func TestSingleFile(t *testing.T) { func TestSingleFile(t *testing.T) {
tmpDir := "testing" tmpDir := "testing"
@@ -176,8 +191,8 @@ func TestSingleFile(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
os.Remove(filepath.Join(tmpDir, filePath)) os.RemoveAll("testing/stuff")
rdr, err := squashfs.NewReader(fil) rdr, err := NewReader(fil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -185,9 +200,10 @@ func TestSingleFile(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = f.(*squashfs.File).ExtractWithOptions("testing", &squashfs.ExtractionOptions{Verbose: true}) op := DefaultOptions()
op.Verbose = true
err = f.(*File).ExtractWithOptions("testing/stuff", op)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Fatal("HI")
} }