Compare commits

...

32 Commits

Author SHA1 Message Date
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
28 changed files with 630 additions and 277 deletions
+9 -6
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,15 +28,14 @@ As of `v1.0`, FUSE capabilities has been moved to [a separate library](https://g
## Issues ## Issues
* Significantly slower then `unsquashfs` when nested images * 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.
* Not to mention it's written in C
* 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 5x 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 30x 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
+64 -1
View File
@@ -3,14 +3,62 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"io/fs"
"os" "os"
"os/user"
"path/filepath"
"strconv"
"strings"
"time" "time"
"github.com/CalebQ42/squashfs" "github.com/CalebQ42/squashfs"
) )
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
}
func printEntry(root, path string, d fs.DirEntry, numeric bool) {
fi, _ := d.Info()
sfi := fi.(squashfs.FileInfo)
owner := fmt.Sprintf("%s/%s",
userName(sfi.Uid(), numeric),
groupName(sfi.Gid(), numeric))
link := ""
if sfi.IsSymlink() {
link = " -> " + sfi.SymlinkPath()
}
fmt.Printf("%s %s %*d %s %s%s\n",
strings.ToLower(fi.Mode().String()),
owner, 26-len(owner), fi.Size(),
fi.ModTime().Format("2006-01-02 15:04"),
filepath.Join(root, path), link)
}
func main() { func main() {
verbose := flag.Bool("v", false, "Verbose") verbose := flag.Bool("v", false, "Verbose")
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")
offset := flag.Int64("o", 0, "Offset")
ignore := flag.Bool("ip", false, "Ignore Permissions and extract all files/folders with 0755") ignore := flag.Bool("ip", false, "Ignore Permissions and extract all files/folders with 0755")
flag.Parse() flag.Parse()
if len(flag.Args()) < 2 { if len(flag.Args()) < 2 {
@@ -21,10 +69,25 @@ 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)
} }
if *list || *long || *numeric {
root := flag.Arg(1)
fs.WalkDir(r, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
panic(err)
}
if *long || *numeric {
printEntry(root, path, d, *numeric)
} else {
fmt.Println(filepath.Join(root, path))
}
return nil
})
return
}
op := squashfs.DefaultOptions() op := squashfs.DefaultOptions()
op.Verbose = *verbose op.Verbose = *verbose
op.IgnorePerm = *ignore op.IgnorePerm = *ignore
+54 -40
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 {
full *data.FullReader full data.FullReader
rdr *data.Reader rdr data.Reader
parent *FS rdrInit bool
parent FS
r *Reader r *Reader
b squashfslow.FileBase b 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, b: 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.b.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{d: 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,21 +74,21 @@ 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.b.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.b.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.b.Inode.Type == inode.Sym || f.b.Inode.Type == inode.ESym
} }
func (f *File) Mode() fs.FileMode { func (f File) Mode() fs.FileMode {
return f.b.Inode.Mode() return f.b.Inode.Mode()
} }
@@ -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.b.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,12 +139,20 @@ 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.b.Uid(&f.r.Low)
if err != nil {
return nil, err
}
gid, err := f.b.Gid(&f.r.Low)
if err != nil {
return nil, err
}
return newFileInfo(f.b.Name, uid, gid, &f.b.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.b.Inode.Type {
case inode.Sym: case inode.Sym:
return string(f.b.Inode.Data.(inode.Symlink).Target) return string(f.b.Inode.Data.(inode.Symlink).Target)
@@ -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,22 +179,29 @@ 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.b.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.b.Inode.Type {
case inode.Char, inode.Block:
dev = f.b.Inode.Data.(inode.Device).Dev dev = f.b.Inode.Data.(inode.Device).Dev
} else if f.b.Inode.Type == inode.EChar || f.b.Inode.Type == inode.EBlock { case inode.EChar, inode.EBlock:
dev = f.b.Inode.Data.(inode.EDevice).Dev dev = f.b.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.d.Name == "" {
return f.b.Name return f.b.Name
} }
return filepath.Join(f.parent.path(), f.b.Name) return filepath.Join(f.parent.path(), f.b.Name)
@@ -196,13 +209,13 @@ func (f *File) path() string {
// 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 {
@@ -218,7 +231,7 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
} }
switch f.b.Inode.Type { switch f.b.Inode.Type {
case inode.Dir, inode.EDir: case inode.Dir, inode.EDir:
d, err := f.b.ToDir(&f.r.Low) d, err := f.b.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)
@@ -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)
@@ -363,11 +376,12 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
} }
path = filepath.Join(path, f.b.Name) path = filepath.Join(path, f.b.Name)
var typ string var typ string
if f.b.Inode.Type == inode.Char || f.b.Inode.Type == inode.EChar { switch f.b.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.")
+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
} }
+24 -18
View File
@@ -21,11 +21,11 @@ type FS struct {
} }
// 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, d: d,
r: r, r: r,
parent: parent, parent: &parent,
} }
} }
@@ -42,7 +42,7 @@ 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.d.Entries {
if match, _ := path.Match(split[0], f.d.Entries[i].Name); match { if match, _ := path.Match(split[0], f.d.Entries[i].Name); match {
if len(split) == 1 { if len(split) == 1 {
out = append(out, f.d.Entries[i].Name) out = append(out, f.d.Entries[i].Name)
@@ -80,7 +80,7 @@ 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.d.Name + "/" + subGlob[i]
} }
out = append(out, subGlob...) out = append(out, subGlob...)
@@ -90,7 +90,7 @@ 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) {
name = filepath.Clean(name) name = filepath.Clean(name)
if !fs.ValidPath(name) { if !fs.ValidPath(name) {
return nil, &fs.PathError{ return nil, &fs.PathError{
@@ -142,7 +142,7 @@ func (f *FS) Open(name string) (fs.File, error) {
Err: fs.ErrNotExist, Err: fs.ErrNotExist,
} }
} }
d, err := b.ToDir(&f.r.Low) d, err := b.ToDir(f.r.Low)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -151,7 +151,7 @@ func (f *FS) Open(name string) (fs.File, error) {
// 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 +171,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 +194,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 +214,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,26 +242,32 @@ 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{
b: f.d.FileBase,
parent: *f.parent,
r: f.r,
}
}
return &File{ return &File{
b: f.d.FileBase, b: f.d.FileBase,
parent: f.parent, 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.d.Name
} }
+3 -3
View File
@@ -1,10 +1,10 @@
module github.com/CalebQ42/squashfs module github.com/CalebQ42/squashfs
go 1.22.5 go 1.24.0
require ( require (
github.com/klauspost/compress v1.17.9 github.com/klauspost/compress v1.18.0
github.com/pierrec/lz4/v4 v4.1.21 github.com/pierrec/lz4/v4 v4.1.22
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 github.com/ulikunitz/xz v0.5.12
+4 -4
View File
@@ -1,7 +1,7 @@
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
github.com/pierrec/lz4/v4 v4.1.21/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=
+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
} }
+2 -5
View File
@@ -1,19 +1,16 @@
package decompress package decompress
import ( import (
"bytes"
"io"
"github.com/klauspost/compress/zstd" "github.com/klauspost/compress/zstd"
) )
type Zstd struct{} type Zstd struct{}
func (z Zstd) Decompress(data []byte) ([]byte, error) { func (z Zstd) Decompress(data []byte) ([]byte, error) {
rdr, err := zstd.NewReader(bytes.NewReader(data)) rdr, err := zstd.NewReader(nil, zstd.WithDecoderLowmem(true), zstd.WithDecoderConcurrency(1))
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer rdr.Close() defer rdr.Close()
return io.ReadAll(rdr) return rdr.DecodeAll(data, nil)
} }
+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
+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)
pool := &sync.Pool{
New: func() any {
return &retValue{}
},
}
for i := uint64(0); i < uint64(math.Ceil(float64(len(r.sizes))/float64(r.goroutineLimit))); i++ { for i := uint64(0); i < uint64(math.Ceil(float64(len(r.sizes))/float64(r.goroutineLimit))); i++ {
toProcess = uint16(len(r.sizes)) - (uint16(i) * r.goroutineLimit) toProcess = min(uint16(len(r.sizes))-(uint16(i)*r.goroutineLimit), r.goroutineLimit)
if toProcess > r.goroutineLimit {
toProcess = 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
} }
+3 -3
View File
@@ -18,7 +18,7 @@ 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 Directory{}, err return Directory{}, err
@@ -44,7 +44,7 @@ func (r *Reader) directoryFromRef(ref uint64, name string) (Directory, error) {
if err != nil { if err != nil {
return Directory{}, err return Directory{}, err
} }
entries, err := directory.ReadDirectory(dirRdr, size) entries, err := directory.ReadDirectory(&dirRdr, size)
if err != nil { if err != nil {
return Directory{}, err return Directory{}, err
} }
@@ -54,7 +54,7 @@ func (r *Reader) directoryFromRef(ref uint64, name string) (Directory, error) {
}, 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
+21 -21
View File
@@ -16,11 +16,11 @@ type FileBase struct {
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 FileBase{}, err return FileBase{}, err
@@ -28,7 +28,7 @@ func (r *Reader) BaseFromEntry(e directory.Entry) (FileBase, error) {
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 FileBase{}, err return FileBase{}, err
@@ -36,19 +36,19 @@ func (r *Reader) BaseFromRef(ref uint64, name string) (FileBase, error) {
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
@@ -70,23 +70,23 @@ func (b *FileBase) ToDir(r *Reader) (Directory, error) {
if err != nil { if err != nil {
return Directory{}, err return Directory{}, err
} }
entries, err := directory.ReadDirectory(dirRdr, size) entries, err := directory.ReadDirectory(&dirRdr, size)
if err != nil { if err != nil {
return Directory{}, 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
} }
+4 -4
View File
@@ -7,7 +7,7 @@ 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()
@@ -15,12 +15,12 @@ func (r *Reader) InodeFromRef(ref uint64) (inode.Inode, error) {
if err != nil { if err != nil {
return inode.Inode{}, 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)
} }
+32 -19
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
} Indexes []DirectoryIndex
type EDirectory struct {
eDirectoryInit
Indexes []DirectoryIndex
}
type directoryIndexInit struct {
Ind uint32
Start uint32
NameSize uint32
} }
type DirectoryIndex struct { type DirectoryIndex struct {
directoryIndexInit Ind uint32
Name []byte Start uint32
NameSize uint32
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
} }
+7 -13
View File
@@ -81,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
} }
+34 -35
View File
@@ -39,41 +39,46 @@ type Reader struct {
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.Zlib{}
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.Zstd{}
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,7 +209,7 @@ 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 inode.Inode{}, err return inode.Inode{}, err
+24 -11
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)
} }
@@ -88,11 +101,11 @@ func TestSingleFile(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = extractToDir(rdr, &b, path) err = extractToDir(rdr, b, path)
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,13 +116,13 @@ 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 {
return err return err
} }
err = extractToDir(rdr, &nestBast, path) err = extractToDir(rdr, nestBast, path)
if err != nil { if err != nil {
return err return err
} }
+8 -8
View File
@@ -9,26 +9,26 @@ import (
) )
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, d: rdr.Root,
r: out, r: &out,
} }
return out, nil return out, nil
} }
func NewReaderAtOffset(r io.ReaderAt, offset int64) (*Reader, error) { func NewReaderAtOffset(r io.ReaderAt, offset int64) (Reader, error) {
return NewReader(toreader.NewOffsetReader(r, offset)) return NewReader(toreader.NewOffsetReader(r, offset))
} }
Executable
BIN
View File
Binary file not shown.
+38 -19
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,7 +59,7 @@ 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)
} }
@@ -70,6 +68,26 @@ func TestMisc(t *testing.T) {
// 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)
op := FastOptions()
op.IgnorePerm = true
rdr, err := NewReader(fil)
if err != nil {
b.Fatal(err)
}
err = rdr.ExtractWithOptions(libPath, op)
if err != nil {
b.Fatal(err)
}
}
func BenchmarkRace(b *testing.B) { func BenchmarkRace(b *testing.B) {
tmpDir := "testing" tmpDir := "testing"
fil, err := preTest(tmpDir) fil, err := preTest(tmpDir)
@@ -81,10 +99,10 @@ 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() op := FastOptions()
op.IgnorePerm = true op.IgnorePerm = true
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)
} }
@@ -93,7 +111,7 @@ func BenchmarkRace(b *testing.B) {
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()
@@ -102,15 +120,15 @@ func BenchmarkRace(b *testing.B) {
b.Log("Unsquashfs error:", err) b.Log("Unsquashfs error:", err)
} }
unsquashTime = time.Since(start) unsquashTime = time.Since(start)
// b.Log("Library took:", libTime.Round(time.Millisecond)) b.Log("Library took:", libTime.Round(time.Millisecond))
// b.Log("unsquashfs took:", unsquashTime.Round(time.Millisecond)) b.Log("unsquashfs took:", unsquashTime.Round(time.Millisecond))
b.Fatal("unsquashfs is", strconv.FormatFloat(float64(libTime.Milliseconds())/float64(unsquashTime.Milliseconds()), 'f', 2, 64), "times faster") b.Log("unsquashfs is", strconv.FormatFloat(float64(libTime.Milliseconds())/float64(unsquashTime.Milliseconds()), 'f', 2, 64), "times faster")
} }
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 {
@@ -120,13 +138,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
@@ -169,7 +187,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"
@@ -177,8 +195,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)
} }
@@ -186,9 +204,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")
} }