Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a829f0df9f | |||
| ea1b9f20cb | |||
| 74d9239c25 | |||
| 07e9d5f123 | |||
| 9fd87fe38a | |||
| de1b18fd1c | |||
| 7d1458b3a4 | |||
| b057df9ada | |||
| e1449da2f0 | |||
| ddb81aade0 | |||
| b2c8084f41 | |||
| 0905141013 | |||
| 66042eab27 | |||
| 02d98b610c | |||
| 81b663b48a | |||
| 97214ca6ca | |||
| 968dff82cb | |||
| 1f0868fb21 | |||
| f378136299 | |||
| 3378651686 | |||
| ebbbc9e87e | |||
| 155999a8e3 | |||
| 7930f4402b | |||
| ada61a391c | |||
| f32cb520dc | |||
| f991ddb1d4 | |||
| 4c8c9f0b47 | |||
| 8a2556ea0d | |||
| 33156751ca | |||
| 6224c4be41 | |||
| 6b0e9ef2c6 | |||
| 4490fc3873 | |||
| 0253a76dbe |
@@ -1,2 +1,7 @@
|
||||
testing
|
||||
/go-unsquashfs
|
||||
squashfs.test
|
||||
|
||||
# Memory and CPU pprof profiles
|
||||
mem.out
|
||||
cpu.out
|
||||
|
||||
@@ -28,15 +28,14 @@ As of `v1.0`, FUSE capabilities has been moved to [a separate library](https://g
|
||||
|
||||
## 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.
|
||||
* Not to mention it's written in C
|
||||
* Times seem to be largely dependent on file tree size and compression type.
|
||||
* My main testing image (~100MB) using Zstd takes about 5x longer.
|
||||
* An Arch Linux airootfs image (~780MB) using XZ compression with LZMA filters takes about 30x longer.
|
||||
* A Tensorflow docker image (~3.3GB) using Zstd takes about 12x longer.
|
||||
* My main testing image (~100MB) using Zstd takes ~2x 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 ~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
|
||||
|
||||
|
||||
+76
-28
@@ -3,7 +3,6 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
@@ -12,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/CalebQ42/squashfs"
|
||||
squashfslow "github.com/CalebQ42/squashfs/low"
|
||||
)
|
||||
|
||||
func userName(uid int, numeric bool) string {
|
||||
@@ -36,32 +36,84 @@ func groupName(gid int, numeric bool) string {
|
||||
return gs
|
||||
}
|
||||
|
||||
func printEntry(root, path string, d fs.DirEntry, numeric bool) {
|
||||
fi, _ := d.Info()
|
||||
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))
|
||||
link := ""
|
||||
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), fi.Size(),
|
||||
owner, 26-len(owner), size,
|
||||
fi.ModTime().Format("2006-01-02 15:04"),
|
||||
filepath.Join(root, path), link)
|
||||
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() {
|
||||
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")
|
||||
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")
|
||||
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()
|
||||
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")
|
||||
os.Exit(0)
|
||||
}
|
||||
@@ -73,26 +125,22 @@ func main() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
extractFil := r.File()
|
||||
if *file != "" {
|
||||
extractFil, err = r.OpenFile(*file)
|
||||
if err != nil {
|
||||
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
|
||||
})
|
||||
printFile(&r, "", extractFil)
|
||||
return
|
||||
}
|
||||
op := squashfs.DefaultOptions()
|
||||
op.Verbose = *verbose
|
||||
op.IgnorePerm = *ignore
|
||||
n := time.Now()
|
||||
err = r.ExtractWithOptions(flag.Arg(1), op)
|
||||
err = extractFil.ExtractWithOptions(flag.Arg(1), op)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
+14
-26
@@ -4,46 +4,34 @@ import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"runtime"
|
||||
|
||||
"github.com/CalebQ42/squashfs/internal/routinemanager"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type ExtractionOptions struct {
|
||||
manager *routinemanager.Manager
|
||||
LogOutput io.Writer //Where the verbose log should write.
|
||||
DereferenceSymlink bool //Replace symlinks with the target file.
|
||||
UnbreakSymlink bool //Try to make sure symlinks remain unbroken when extracted, without changing the symlink.
|
||||
Verbose bool //Prints extra info to log on an error.
|
||||
IgnorePerm bool //Ignore file's permissions and instead use Perm.
|
||||
Perm fs.FileMode //Permission to use when IgnorePerm. Defaults to 0777.
|
||||
SimultaneousFiles uint16 //Number of files to process in parallel. Default set based on runtime.NumCPU().
|
||||
ExtractionRoutines uint16 //Number of goroutines to use for each file's extraction. Only applies to regular files. Default set based on runtime.NumCPU().
|
||||
dispatcher chan struct{} // Limits the amount of work being done simultaneously.
|
||||
fullRdrPool sync.Pool // Pool for data.FullReader results.
|
||||
LogOutput io.Writer //Where the verbose log should write.
|
||||
DereferenceSymlink bool //Replace symlinks with the target file.
|
||||
UnbreakSymlink bool //Try to make sure symlinks remain unbroken when extracted, without changing the symlink.
|
||||
Verbose bool //Prints extra info to log on an error.
|
||||
IgnorePerm bool //Ignore file's permissions and instead use Perm.
|
||||
Perm fs.FileMode //Permission to use when IgnorePerm. Defaults to 0777.
|
||||
ExtractionRoutines uint16 //The number of threads to use during extraction. Defaults to a number based on runtime.NumCPU().
|
||||
SimultaneousFiles uint16 //Depreciated: Only use ExtractionRoutines
|
||||
}
|
||||
|
||||
// The default extraction options.
|
||||
// The default extraction options. Uses half of your CPU cores.
|
||||
func DefaultOptions() *ExtractionOptions {
|
||||
cores := uint16(runtime.NumCPU() / 2)
|
||||
var files, routines uint16
|
||||
if cores <= 4 {
|
||||
files = 1
|
||||
routines = cores
|
||||
} else {
|
||||
files = cores - 4
|
||||
routines = 4
|
||||
}
|
||||
return &ExtractionOptions{
|
||||
Perm: 0777,
|
||||
SimultaneousFiles: files,
|
||||
ExtractionRoutines: routines,
|
||||
ExtractionRoutines: uint16(runtime.NumCPU() / 2),
|
||||
}
|
||||
}
|
||||
|
||||
// Less limited default options. Can run up 2x faster than DefaultOptions.
|
||||
// Tends to use all available CPU resources.
|
||||
// Faster extraction option. Uses all CPU cores.
|
||||
func FastOptions() *ExtractionOptions {
|
||||
return &ExtractionOptions{
|
||||
Perm: 0777,
|
||||
SimultaneousFiles: uint16(runtime.NumCPU()),
|
||||
ExtractionRoutines: uint16(runtime.NumCPU()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/CalebQ42/squashfs/internal/routinemanager"
|
||||
squashfslow "github.com/CalebQ42/squashfs/low"
|
||||
"github.com/CalebQ42/squashfs/low/data"
|
||||
"github.com/CalebQ42/squashfs/low/inode"
|
||||
@@ -19,49 +19,48 @@ import (
|
||||
|
||||
// File represents a file inside a squashfs archive.
|
||||
type File struct {
|
||||
full *data.FullReader
|
||||
rdr *data.Reader
|
||||
parent *FS
|
||||
full data.FullReader
|
||||
rdr data.Reader
|
||||
rdrInit bool
|
||||
parent FS
|
||||
r *Reader
|
||||
b squashfslow.FileBase
|
||||
Low squashfslow.FileBase
|
||||
dirsRead int
|
||||
}
|
||||
|
||||
// Creates a new *File from the given *squashfs.Base
|
||||
func (r *Reader) FileFromBase(b squashfslow.FileBase, parent *FS) *File {
|
||||
return &File{
|
||||
b: b,
|
||||
func (r *Reader) FileFromBase(b squashfslow.FileBase, parent FS) File {
|
||||
return File{
|
||||
Low: b,
|
||||
parent: parent,
|
||||
r: r,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *File) FS() (*FS, error) {
|
||||
func (f File) FS() (FS, error) {
|
||||
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 {
|
||||
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.
|
||||
// Further calls to Read and WriteTo will re-create the readers.
|
||||
// Never returns an error.
|
||||
func (f *File) Close() error {
|
||||
if f.rdr != nil {
|
||||
return f.rdr.Close()
|
||||
}
|
||||
f.rdr = nil
|
||||
f.full = nil
|
||||
f.rdr.Close()
|
||||
f.full.Close()
|
||||
f.rdrInit = false
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns the file the symlink points to.
|
||||
// If the file isn't a symlink, or points to a file outside the archive, returns nil.
|
||||
func (f *File) GetSymlinkFile() fs.File {
|
||||
func (f File) GetSymlinkFile() fs.File {
|
||||
if !f.IsSymlink() {
|
||||
return nil
|
||||
}
|
||||
@@ -76,22 +75,22 @@ func (f *File) GetSymlinkFile() fs.File {
|
||||
}
|
||||
|
||||
// Returns whether the file is a directory.
|
||||
func (f *File) IsDir() bool {
|
||||
return f.b.IsDir()
|
||||
func (f File) IsDir() bool {
|
||||
return f.Low.IsDir()
|
||||
}
|
||||
|
||||
// Returns whether the file is a regular file.
|
||||
func (f *File) IsRegular() bool {
|
||||
return f.b.IsRegular()
|
||||
func (f File) IsRegular() bool {
|
||||
return f.Low.IsRegular()
|
||||
}
|
||||
|
||||
// Returns whether the file is a symlink.
|
||||
func (f *File) IsSymlink() bool {
|
||||
return f.b.Inode.Type == inode.Sym || f.b.Inode.Type == inode.ESym
|
||||
func (f File) IsSymlink() bool {
|
||||
return f.Low.Inode.Type == inode.Sym || f.Low.Inode.Type == inode.ESym
|
||||
}
|
||||
|
||||
func (f *File) Mode() fs.FileMode {
|
||||
return f.b.Inode.Mode()
|
||||
func (f File) Mode() fs.FileMode {
|
||||
return f.Low.Inode.Mode()
|
||||
}
|
||||
|
||||
// Read reads the data from the file. Only works if file is a normal file.
|
||||
@@ -99,7 +98,7 @@ func (f *File) Read(b []byte) (int, error) {
|
||||
if !f.IsRegular() {
|
||||
return 0, errors.New("file is not a regular file")
|
||||
}
|
||||
if f.rdr == nil {
|
||||
if !f.rdrInit {
|
||||
err := f.initializeReaders()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -114,7 +113,7 @@ func (f *File) ReadDir(n int) ([]fs.DirEntry, error) {
|
||||
if !f.IsDir() {
|
||||
return nil, errors.New("file is not a directory")
|
||||
}
|
||||
d, err := f.b.ToDir(&f.r.Low)
|
||||
d, err := f.Low.ToDir(f.r.Low)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -141,25 +140,25 @@ func (f *File) ReadDir(n int) ([]fs.DirEntry, error) {
|
||||
}
|
||||
|
||||
// Returns the file's fs.FileInfo
|
||||
func (f *File) Stat() (fs.FileInfo, error) {
|
||||
uid, err := f.b.Uid(&f.r.Low)
|
||||
func (f File) Stat() (fs.FileInfo, error) {
|
||||
uid, err := f.Low.Uid(&f.r.Low)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gid, err := f.b.Gid(&f.r.Low)
|
||||
gid, err := f.Low.Gid(&f.r.Low)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newFileInfo(f.b.Name, uid, gid, &f.b.Inode), nil
|
||||
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.
|
||||
func (f *File) SymlinkPath() string {
|
||||
switch f.b.Inode.Type {
|
||||
func (f File) SymlinkPath() string {
|
||||
switch f.Low.Inode.Type {
|
||||
case inode.Sym:
|
||||
return string(f.b.Inode.Data.(inode.Symlink).Target)
|
||||
return string(f.Low.Inode.Data.(inode.Symlink).Target)
|
||||
case inode.ESym:
|
||||
return string(f.b.Inode.Data.(inode.ESymlink).Target)
|
||||
return string(f.Low.Inode.Data.(inode.ESymlink).Target)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -170,7 +169,7 @@ func (f *File) WriteTo(w io.Writer) (int64, error) {
|
||||
if !f.IsRegular() {
|
||||
return 0, errors.New("file is not a regular file")
|
||||
}
|
||||
if f.full == nil {
|
||||
if !f.rdrInit {
|
||||
err := f.initializeReaders()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -181,39 +180,53 @@ func (f *File) WriteTo(w io.Writer) (int64, error) {
|
||||
|
||||
func (f *File) initializeReaders() 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
|
||||
}
|
||||
|
||||
func (f *File) deviceDevices() (maj uint32, min uint32) {
|
||||
func (f File) deviceDevices() (maj uint32, min uint32) {
|
||||
var dev uint32
|
||||
switch f.b.Inode.Type {
|
||||
switch f.Low.Inode.Type {
|
||||
case inode.Char, inode.Block:
|
||||
dev = f.b.Inode.Data.(inode.Device).Dev
|
||||
dev = f.Low.Inode.Data.(inode.Device).Dev
|
||||
case inode.EChar, inode.EBlock:
|
||||
dev = f.b.Inode.Data.(inode.EDevice).Dev
|
||||
dev = f.Low.Inode.Data.(inode.EDevice).Dev
|
||||
}
|
||||
return dev >> 8, dev & 0x000FF
|
||||
}
|
||||
|
||||
func (f *File) path() string {
|
||||
if f.parent == nil {
|
||||
return f.b.Name
|
||||
func (f File) path() string {
|
||||
if f.parent.LowDir.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.
|
||||
// Uses default extraction options.
|
||||
func (f *File) Extract(folder string) error {
|
||||
func (f File) Extract(folder string) error {
|
||||
return f.ExtractWithOptions(folder, DefaultOptions())
|
||||
}
|
||||
|
||||
// Extract the file to the given folder. If the file is a folder, the folder's contents will be extracted to the folder.
|
||||
// Allows setting various extraction options via ExtractionOptions.
|
||||
func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
|
||||
if op.manager == nil {
|
||||
op.manager = routinemanager.NewManager(op.SimultaneousFiles)
|
||||
func (f File) ExtractWithOptions(path string, op *ExtractionOptions) error {
|
||||
if op.dispatcher == nil {
|
||||
op.fullRdrPool = sync.Pool{
|
||||
New: func() any {
|
||||
return &data.BlockResults{}
|
||||
},
|
||||
}
|
||||
op.dispatcher = make(chan struct{}, op.ExtractionRoutines)
|
||||
for range op.ExtractionRoutines {
|
||||
op.dispatcher <- struct{}{}
|
||||
}
|
||||
if op.LogOutput != nil {
|
||||
log.SetOutput(op.LogOutput)
|
||||
}
|
||||
@@ -225,13 +238,15 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
switch f.b.Inode.Type {
|
||||
switch f.Low.Inode.Type {
|
||||
case inode.Dir, inode.EDir:
|
||||
d, err := f.b.ToDir(&f.r.Low)
|
||||
<-op.dispatcher
|
||||
d, err := f.Low.ToDir(f.r.Low)
|
||||
if err != nil {
|
||||
if op.Verbose {
|
||||
log.Println("Failed to create squashfs.Directory for", path)
|
||||
}
|
||||
op.dispatcher <- struct{}{}
|
||||
return errors.Join(errors.New("failed to create squashfs.Directory: "+path), err)
|
||||
}
|
||||
errChan := make(chan error, len(d.Entries))
|
||||
@@ -244,19 +259,21 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
|
||||
return errors.Join(errors.New("failed to get base from entry: "+path), err)
|
||||
}
|
||||
go func(b squashfslow.FileBase, path string) {
|
||||
i := op.manager.Lock()
|
||||
if b.IsDir() {
|
||||
<-op.dispatcher
|
||||
extDir := filepath.Join(path, b.Name)
|
||||
err = os.Mkdir(extDir, 0777)
|
||||
op.manager.Unlock(i)
|
||||
if err != nil {
|
||||
if op.Verbose {
|
||||
log.Println("Failed to create directory", path)
|
||||
}
|
||||
op.dispatcher <- struct{}{}
|
||||
errChan <- errors.Join(errors.New("failed to create directory: "+path), err)
|
||||
return
|
||||
}
|
||||
err = f.r.FileFromBase(b, f.r.FSFromDirectory(d, f.parent)).ExtractWithOptions(extDir, op)
|
||||
fil := f.r.FileFromBase(b, f.r.FSFromDirectory(d, f.parent))
|
||||
op.dispatcher <- struct{}{}
|
||||
err = fil.ExtractWithOptions(extDir, op)
|
||||
if err != nil {
|
||||
if op.Verbose {
|
||||
log.Println("Failed to extract directory", path)
|
||||
@@ -268,12 +285,12 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
|
||||
} else {
|
||||
fil := f.r.FileFromBase(b, f.r.FSFromDirectory(d, f.parent))
|
||||
err = fil.ExtractWithOptions(path, op)
|
||||
op.manager.Unlock(i)
|
||||
fil.Close()
|
||||
errChan <- err
|
||||
}
|
||||
}(b, path)
|
||||
}
|
||||
op.dispatcher <- struct{}{}
|
||||
var errCache []error
|
||||
for range d.Entries {
|
||||
err := <-errChan
|
||||
@@ -285,23 +302,28 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
|
||||
return errors.Join(errors.New("failed to extract folder: "+path), errors.Join(errCache...))
|
||||
}
|
||||
case inode.Fil, inode.EFil:
|
||||
path = filepath.Join(path, f.b.Name)
|
||||
<-op.dispatcher
|
||||
path = filepath.Join(path, f.Low.Name)
|
||||
outFil, err := os.Create(path)
|
||||
if err != nil {
|
||||
if op.Verbose {
|
||||
log.Println("Failed to create file", path)
|
||||
}
|
||||
op.dispatcher <- struct{}{}
|
||||
return errors.Join(errors.New("failed to create file: "+path), err)
|
||||
}
|
||||
defer outFil.Close()
|
||||
full, err := f.b.GetFullReader(&f.r.Low)
|
||||
full, err := f.Low.GetFullReader(&f.r.Low)
|
||||
defer full.Close()
|
||||
if err != nil {
|
||||
if op.Verbose {
|
||||
log.Println("Failed to create full reader for", path)
|
||||
}
|
||||
op.dispatcher <- struct{}{}
|
||||
return errors.Join(errors.New("failed to create full reader: "+path), err)
|
||||
}
|
||||
full.SetGoroutineLimit(op.ExtractionRoutines)
|
||||
full.SetDispatcherPool(op.dispatcher, &op.fullRdrPool)
|
||||
op.dispatcher <- struct{}{}
|
||||
_, err = full.WriteTo(outFil)
|
||||
if err != nil {
|
||||
if op.Verbose {
|
||||
@@ -310,6 +332,8 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
|
||||
return errors.Join(errors.New("failed to write file: "+path), err)
|
||||
}
|
||||
case inode.Sym, inode.ESym:
|
||||
<-op.dispatcher
|
||||
defer func() { op.dispatcher <- struct{}{} }()
|
||||
symPath := f.SymlinkPath()
|
||||
if op.DereferenceSymlink {
|
||||
filTmp := f.GetSymlinkFile()
|
||||
@@ -320,11 +344,11 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
|
||||
return errors.New("failed to get symlink's file")
|
||||
}
|
||||
fil := filTmp.(*File)
|
||||
fil.b.Name = f.b.Name
|
||||
fil.Low.Name = f.Low.Name
|
||||
err := fil.ExtractWithOptions(path, op)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
@@ -347,7 +371,7 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
|
||||
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)
|
||||
if err != nil {
|
||||
if op.Verbose {
|
||||
@@ -357,6 +381,8 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
|
||||
}
|
||||
}
|
||||
case inode.Char, inode.EChar, inode.Block, inode.EBlock, inode.Fifo, inode.EFifo:
|
||||
<-op.dispatcher
|
||||
defer func() { op.dispatcher <- struct{}{} }()
|
||||
if runtime.GOOS == "windows" {
|
||||
if op.Verbose {
|
||||
log.Println(f.path(), "ignored. A device link and can't be created on Windows.")
|
||||
@@ -370,9 +396,9 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
|
||||
}
|
||||
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
|
||||
switch f.b.Inode.Type {
|
||||
switch f.Low.Inode.Type {
|
||||
case inode.Char, inode.EChar:
|
||||
typ = "c"
|
||||
case inode.Block, inode.EBlock:
|
||||
@@ -408,7 +434,7 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
|
||||
}
|
||||
return nil
|
||||
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 {
|
||||
log.Println(f.path(), "extracted to", path)
|
||||
@@ -416,7 +442,7 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
|
||||
if op.IgnorePerm {
|
||||
return nil
|
||||
}
|
||||
uid, err := f.b.Uid(&f.r.Low)
|
||||
uid, err := f.Low.Uid(&f.r.Low)
|
||||
if err != nil {
|
||||
if op.Verbose {
|
||||
log.Println("Failed to get uid for", path)
|
||||
@@ -424,7 +450,7 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
gid, err := f.b.Gid(&f.r.Low)
|
||||
gid, err := f.Low.Gid(&f.r.Low)
|
||||
if err != nil {
|
||||
if op.Verbose {
|
||||
log.Println("Failed to get gid for", path)
|
||||
|
||||
@@ -17,15 +17,15 @@ import (
|
||||
type FS struct {
|
||||
r *Reader
|
||||
parent *FS
|
||||
d squashfslow.Directory
|
||||
LowDir squashfslow.Directory
|
||||
}
|
||||
|
||||
// Creates a new *FS from the given squashfs.directory
|
||||
func (r *Reader) FSFromDirectory(d squashfslow.Directory, parent *FS) *FS {
|
||||
return &FS{
|
||||
d: d,
|
||||
func (r *Reader) FSFromDirectory(d squashfslow.Directory, parent FS) FS {
|
||||
return FS{
|
||||
LowDir: d,
|
||||
r: r,
|
||||
parent: parent,
|
||||
parent: &parent,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,10 @@ func (f *FS) Glob(pattern string) (out []string, err error) {
|
||||
}
|
||||
}
|
||||
split := strings.Split(pattern, "/")
|
||||
for i := range f.d.Entries {
|
||||
if match, _ := path.Match(split[0], f.d.Entries[i].Name); match {
|
||||
for i := range f.LowDir.Entries {
|
||||
if match, _ := path.Match(split[0], f.LowDir.Entries[i].Name); match {
|
||||
if len(split) == 1 {
|
||||
out = append(out, f.d.Entries[i].Name)
|
||||
out = append(out, f.LowDir.Entries[i].Name)
|
||||
continue
|
||||
}
|
||||
sub, err := f.Sub(split[0])
|
||||
@@ -81,7 +81,7 @@ func (f *FS) Glob(pattern string) (out []string, err error) {
|
||||
}
|
||||
}
|
||||
for i := range subGlob {
|
||||
subGlob[i] = f.d.Name + "/" + subGlob[i]
|
||||
subGlob[i] = f.LowDir.Name + "/" + subGlob[i]
|
||||
}
|
||||
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.
|
||||
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)
|
||||
if !fs.ValidPath(name) {
|
||||
return nil, &fs.PathError{
|
||||
@@ -111,10 +115,10 @@ func (f *FS) Open(name string) (fs.File, error) {
|
||||
Err: fs.ErrNotExist,
|
||||
}
|
||||
} 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)
|
||||
})
|
||||
if !found {
|
||||
@@ -124,13 +128,13 @@ func (f *FS) Open(name string) (fs.File, error) {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
if len(split) == 1 {
|
||||
return &File{
|
||||
b: b,
|
||||
Low: b,
|
||||
r: f.r,
|
||||
parent: f,
|
||||
}, nil
|
||||
@@ -142,16 +146,16 @@ func (f *FS) Open(name string) (fs.File, error) {
|
||||
Err: fs.ErrNotExist,
|
||||
}
|
||||
}
|
||||
d, err := b.ToDir(&f.r.Low)
|
||||
d, err := b.ToDir(f.r.Low)
|
||||
if err != nil {
|
||||
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.
|
||||
// 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)
|
||||
if !fs.ValidPath(name) {
|
||||
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.
|
||||
func (f *FS) ReadFile(name string) (out []byte, err error) {
|
||||
func (f FS) ReadFile(name string) (out []byte, err error) {
|
||||
name = filepath.Clean(name)
|
||||
if !fs.ValidPath(name) {
|
||||
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.
|
||||
func (f *FS) Stat(name string) (fs.FileInfo, error) {
|
||||
func (f FS) Stat(name string) (fs.FileInfo, error) {
|
||||
name = filepath.Clean(name)
|
||||
if !fs.ValidPath(name) {
|
||||
return nil, &fs.PathError{
|
||||
@@ -214,7 +218,7 @@ func (f *FS) Stat(name string) (fs.FileInfo, error) {
|
||||
}
|
||||
|
||||
// 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)
|
||||
if !fs.ValidPath(dir) {
|
||||
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.
|
||||
// Uses default extraction options.
|
||||
func (f *FS) Extract(folder string) error {
|
||||
func (f FS) Extract(folder string) error {
|
||||
return f.File().Extract(folder)
|
||||
}
|
||||
|
||||
// Extract the FS to the given folder. If the file is a folder, the folder's contents will be extracted to the folder.
|
||||
// Allows setting various extraction options via ExtractionOptions.
|
||||
func (f *FS) ExtractWithOptions(folder string, op *ExtractionOptions) error {
|
||||
func (f FS) ExtractWithOptions(folder string, op *ExtractionOptions) error {
|
||||
return f.File().ExtractWithOptions(folder, op)
|
||||
}
|
||||
|
||||
// Returns the FS as a *File
|
||||
func (f *FS) File() *File {
|
||||
func (f FS) File() *File {
|
||||
if f.parent != nil {
|
||||
return &File{
|
||||
Low: f.LowDir.FileBase,
|
||||
parent: *f.parent,
|
||||
r: f.r,
|
||||
}
|
||||
}
|
||||
return &File{
|
||||
b: f.d.FileBase,
|
||||
parent: f.parent,
|
||||
r: f.r,
|
||||
Low: f.LowDir.FileBase,
|
||||
r: f.r,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FS) path() string {
|
||||
func (f FS) path() string {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -3,13 +3,28 @@ package decompress
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/pierrec/lz4/v4"
|
||||
)
|
||||
|
||||
type Lz4 struct{}
|
||||
type Lz4 struct {
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
func (l Lz4) Decompress(data []byte) ([]byte, error) {
|
||||
rdr := lz4.NewReader(bytes.NewReader(data))
|
||||
func NewLz4() *Lz4 {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -3,14 +3,30 @@ package decompress
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/therootcompany/xz"
|
||||
)
|
||||
|
||||
type Xz struct{}
|
||||
type Xz struct {
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
func (x Xz) Decompress(data []byte) ([]byte, error) {
|
||||
rdr, err := xz.NewReader(bytes.NewReader(data), 0)
|
||||
func NewXz() *Xz {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -2,17 +2,31 @@ package decompress
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/klauspost/compress/zlib"
|
||||
)
|
||||
|
||||
type Zlib struct{}
|
||||
type Zlib struct {
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
func (z Zlib) Decompress(data []byte) ([]byte, error) {
|
||||
rdr, err := zlib.NewReader(bytes.NewReader(data))
|
||||
func NewZlib() *Zlib {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
defer rdr.Close()
|
||||
return io.ReadAll(rdr)
|
||||
return io.ReadAll(rdr.(io.ReadCloser))
|
||||
}
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
package decompress
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"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) {
|
||||
rdr, err := zstd.NewReader(bytes.NewReader(data))
|
||||
dat, err := z.rdr.DecodeAll(data, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rdr.Close()
|
||||
return io.ReadAll(rdr)
|
||||
return dat, err
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ type Reader struct {
|
||||
curOffset uint16
|
||||
}
|
||||
|
||||
func NewReader(r io.Reader, d decompress.Decompressor) *Reader {
|
||||
return &Reader{
|
||||
func NewReader(r io.Reader, d decompress.Decompressor) Reader {
|
||||
return Reader{
|
||||
r: r,
|
||||
d: d,
|
||||
}
|
||||
@@ -23,14 +23,15 @@ func NewReader(r io.Reader, d decompress.Decompressor) *Reader {
|
||||
|
||||
func (r *Reader) advance() error {
|
||||
r.curOffset = 0
|
||||
var size uint16
|
||||
err := binary.Read(r.r, binary.LittleEndian, &size)
|
||||
dat := make([]byte, 2)
|
||||
_, err := r.r.Read(dat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
size := binary.LittleEndian.Uint16(dat)
|
||||
realSize := size &^ 0x8000
|
||||
r.dat = make([]byte, realSize)
|
||||
err = binary.Read(r.r, binary.LittleEndian, &r.dat)
|
||||
_, err = r.r.Read(r.dat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Lower-Level Squashfs
|
||||
|
||||
This library is a lower level version of the main [squashfs](https://github.com/CalebQ42/squashfs) library that doesn't try to be easy to use and exposes a lot of information that is not necesary for must use cases.
|
||||
|
||||
I will try to keep the API stable, but it is not guarenteed.
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
package squashfslow
|
||||
|
||||
// TODO: Make work
|
||||
// func requireNoError(t *testing.T, err error) {
|
||||
// t.Helper()
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// }
|
||||
|
||||
// func assertEqual(t *testing.T, want int, got int) {
|
||||
// t.Helper()
|
||||
// if want != got {
|
||||
// t.Errorf("want %d, got %d", want, got)
|
||||
// }
|
||||
// }
|
||||
|
||||
// func assertLength(t *testing.T, want int, slice []int) {
|
||||
// t.Helper()
|
||||
// if len(slice) != want {
|
||||
// t.Errorf("want len %d, got %d", want, len(slice))
|
||||
// }
|
||||
// }
|
||||
|
||||
// func assertErrorIs(t *testing.T, err error, wantErr error) {
|
||||
// t.Helper()
|
||||
// if err == nil {
|
||||
// t.Errorf("want %s, got nil", wantErr)
|
||||
// return
|
||||
// }
|
||||
// if !errors.Is(err, wantErr) {
|
||||
// t.Errorf("want %s, got %v", wantErr, err)
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestCachingPagedReader(t *testing.T) {
|
||||
// // Mock readBlocks function
|
||||
// mockReadNMore := func(startBlock, numItems int) ([]int, error) {
|
||||
// if startBlock < 0 {
|
||||
// return nil, errors.New("invalid block start")
|
||||
// }
|
||||
// var result []int
|
||||
// for i := 0; i < numItems; i++ {
|
||||
// result = append(result, startBlock*512+i)
|
||||
// }
|
||||
// return result, nil
|
||||
// }
|
||||
|
||||
// t.Run("ValidRequestWithinFirstBlock", func(t *testing.T) {
|
||||
// tab := NewTable[int]()
|
||||
// currentItems := make([]int, 0)
|
||||
// item, err := readPagedItems(300, 512, ¤tItems, 2048, mockReadNMore)
|
||||
// requireNoError(t, err)
|
||||
// assertEqual(t, 300, item)
|
||||
// assertLength(t, 512, currentItems) // Ensure one block is read
|
||||
// })
|
||||
|
||||
// t.Run("ValidRequestAcrossMultipleBlocks", func(t *testing.T) {
|
||||
// currentItems := make([]int, 0)
|
||||
// item, err := readPagedItems(600, 512, ¤tItems, 2048, mockReadNMore)
|
||||
// requireNoError(t, err)
|
||||
// assertEqual(t, 600, item)
|
||||
// assertLength(t, 1024, currentItems)
|
||||
// })
|
||||
|
||||
// t.Run("SequentialRequestsWithinBlocks", func(t *testing.T) {
|
||||
// currentItems := make([]int, 0)
|
||||
// // First request
|
||||
// item, err := readPagedItems(300, 512, ¤tItems, 2048, mockReadNMore)
|
||||
// requireNoError(t, err)
|
||||
// assertEqual(t, 300, item)
|
||||
|
||||
// // Second request in the same block
|
||||
// item, err = readPagedItems(400, 512, ¤tItems, 2048, mockReadNMore)
|
||||
// requireNoError(t, err)
|
||||
// assertEqual(t, 400, item)
|
||||
// assertLength(t, 512, currentItems)
|
||||
// })
|
||||
|
||||
// t.Run("RequestExactBlockBoundary", func(t *testing.T) {
|
||||
// currentItems := make([]int, 0)
|
||||
// item, err := readPagedItems(511, 512, ¤tItems, 2048, mockReadNMore)
|
||||
// requireNoError(t, err)
|
||||
// assertEqual(t, 511, item)
|
||||
// assertLength(t, 512, currentItems)
|
||||
|
||||
// // Request the next block's first item
|
||||
// item, err = readPagedItems(512, 512, ¤tItems, 2048, mockReadNMore)
|
||||
// requireNoError(t, err)
|
||||
// assertEqual(t, 512, item)
|
||||
// assertLength(t, 1024, currentItems)
|
||||
// })
|
||||
|
||||
// t.Run("OutOfBoundsRequest", func(t *testing.T) {
|
||||
// currentItems := make([]int, 0)
|
||||
// _, err := readPagedItems(2048, 512, ¤tItems, 2048, mockReadNMore)
|
||||
// assertErrorIs(t, err, errOutOfBounds)
|
||||
// })
|
||||
|
||||
// t.Run("RequestBeyondReadBlocks", func(t *testing.T) {
|
||||
// readFail := errors.New("failed to read block")
|
||||
// failingReadBlocks := func(startBlock, numBlocks int) ([]int, error) {
|
||||
// if startBlock > 1 {
|
||||
// return nil, readFail
|
||||
// }
|
||||
// var result []int
|
||||
// for i := 0; i < numBlocks*512; i++ {
|
||||
// result = append(result, startBlock*512+i)
|
||||
// }
|
||||
// return result, nil
|
||||
// }
|
||||
|
||||
// currentItems := make([]int, 0)
|
||||
// _, err := readPagedItems(1024, 512, ¤tItems, 2048, failingReadBlocks)
|
||||
// assertErrorIs(t, err, readFail)
|
||||
// })
|
||||
|
||||
// t.Run("partial last page", func(t *testing.T) {
|
||||
// currentItems := make([]int, 0)
|
||||
|
||||
// // Request the next block's first item
|
||||
// item, err := readPagedItems(512, 512, ¤tItems, 612, mockReadNMore)
|
||||
// requireNoError(t, err)
|
||||
// assertEqual(t, 512, item)
|
||||
// assertLength(t, 612, currentItems)
|
||||
// })
|
||||
// }
|
||||
+194
-210
@@ -1,248 +1,232 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"math"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/CalebQ42/squashfs/internal/decompress"
|
||||
"github.com/CalebQ42/squashfs/internal/toreader"
|
||||
)
|
||||
|
||||
type FragReaderConstructor func() (io.Reader, error)
|
||||
|
||||
type FullReader struct {
|
||||
r io.ReaderAt
|
||||
d decompress.Decompressor
|
||||
frag FragReaderConstructor
|
||||
sizes []uint32
|
||||
initialOffset int64
|
||||
finalBlockSize uint64
|
||||
blockSize uint32
|
||||
goroutineLimit uint16
|
||||
fileSize uint64
|
||||
blockSize uint32
|
||||
dispatcher chan struct{}
|
||||
pool *sync.Pool
|
||||
rdr io.ReaderAt
|
||||
decomp decompress.Decompressor
|
||||
sizes []uint32
|
||||
blockOffsets []uint64
|
||||
fragDat []byte
|
||||
}
|
||||
|
||||
func NewFullReader(r io.ReaderAt, initialOffset int64, d decompress.Decompressor, sizes []uint32, finalBlockSize uint64, blockSize uint32) *FullReader {
|
||||
return &FullReader{
|
||||
r: r,
|
||||
d: d,
|
||||
sizes: sizes,
|
||||
initialOffset: initialOffset,
|
||||
goroutineLimit: uint16(runtime.NumCPU()),
|
||||
finalBlockSize: finalBlockSize,
|
||||
blockSize: blockSize,
|
||||
func NewFullReader(rdr io.ReaderAt, decomp decompress.Decompressor, blockSize uint32, size uint64, start uint64, sizes []uint32) FullReader {
|
||||
out := FullReader{
|
||||
fileSize: size,
|
||||
blockSize: blockSize,
|
||||
rdr: rdr,
|
||||
decomp: decomp,
|
||||
sizes: sizes,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *FullReader) AddFrag(frag FragReaderConstructor) {
|
||||
r.frag = frag
|
||||
}
|
||||
|
||||
func (r *FullReader) SetGoroutineLimit(limit uint16) {
|
||||
if limit <= 0 {
|
||||
r.goroutineLimit = 1
|
||||
out.blockOffsets = make([]uint64, len(sizes))
|
||||
curOffset := start
|
||||
for i := range sizes {
|
||||
out.blockOffsets[i] = curOffset
|
||||
curOffset += uint64(sizes[i]) &^ (1 << 24)
|
||||
}
|
||||
r.goroutineLimit = limit
|
||||
return out
|
||||
}
|
||||
|
||||
type retValue struct {
|
||||
err error
|
||||
data []byte
|
||||
index uint64
|
||||
func (f *FullReader) Close() error {
|
||||
f.fragDat = nil
|
||||
f.sizes = nil
|
||||
f.blockOffsets = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r FullReader) process(index uint64, fileOffset uint64, pool *sync.Pool, retChan chan *retValue) {
|
||||
ret := pool.Get().(*retValue)
|
||||
ret.index = index
|
||||
realSize := r.sizes[index] &^ (1 << 24)
|
||||
func (f *FullReader) AddFragData(blockStart uint64, blockSize uint32, offset uint32) error {
|
||||
realSize := blockSize &^ (1 << 24)
|
||||
dat := make([]byte, realSize)
|
||||
_, err := f.rdr.ReadAt(dat, int64(blockStart))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if blockSize == realSize {
|
||||
dat, err = f.decomp.Decompress(dat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
f.fragDat = make([]byte, f.fileSize%uint64(f.blockSize))
|
||||
copy(f.fragDat, dat[offset:])
|
||||
dat = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FullReader) SetDispatcherPool(dispatcher chan struct{}, pool *sync.Pool) {
|
||||
f.dispatcher = dispatcher
|
||||
f.pool = pool
|
||||
}
|
||||
|
||||
// The number of blocks, including the fragment block if present
|
||||
func (f FullReader) BlockNum() uint32 {
|
||||
out := len(f.sizes)
|
||||
if f.fragDat != nil {
|
||||
out++
|
||||
}
|
||||
return uint32(out)
|
||||
}
|
||||
|
||||
// Returns the data block at the given index
|
||||
func (f FullReader) Block(i uint32) ([]byte, error) {
|
||||
if i == uint32(len(f.sizes)) && f.fragDat != nil {
|
||||
return f.fragDat, nil
|
||||
}
|
||||
if i >= uint32(len(f.sizes)) {
|
||||
return nil, errors.New("invalid block index")
|
||||
}
|
||||
realSize := f.sizes[i] &^ (1 << 24)
|
||||
if realSize == 0 {
|
||||
if index == uint64(len(r.sizes))-1 && r.frag == nil {
|
||||
ret.data = make([]byte, r.finalBlockSize)
|
||||
} else {
|
||||
ret.data = make([]byte, r.blockSize)
|
||||
if i == uint32(len(f.sizes)-1) && f.fragDat == nil {
|
||||
return make([]byte, f.fileSize%uint64(f.blockSize)), nil
|
||||
}
|
||||
ret.err = nil
|
||||
retChan <- ret
|
||||
return
|
||||
return make([]byte, f.blockSize), nil
|
||||
}
|
||||
ret.data = make([]byte, realSize)
|
||||
ret.err = binary.Read(toreader.NewReader(r.r, int64(r.initialOffset)+int64(fileOffset)), binary.LittleEndian, &ret.data)
|
||||
if r.sizes[index] == realSize {
|
||||
ret.data, ret.err = r.d.Decompress(ret.data)
|
||||
dat := make([]byte, realSize)
|
||||
_, err := f.rdr.ReadAt(dat, int64(f.blockOffsets[i]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
retChan <- ret
|
||||
if realSize == f.sizes[i] {
|
||||
dat, err = f.decomp.Decompress(dat)
|
||||
}
|
||||
return dat, err
|
||||
}
|
||||
|
||||
func (r FullReader) WriteTo(w io.Writer) (int64, error) {
|
||||
// if wa, is := w.(io.WriterAt); is {
|
||||
// return r.writeToWriteAt(wa)
|
||||
// }
|
||||
var curIndex uint64
|
||||
var curOffset uint64
|
||||
var toProcess uint16
|
||||
var wrote int64
|
||||
cache := make(map[uint64]*retValue)
|
||||
var errCache []error
|
||||
retChan := make(chan *retValue, r.goroutineLimit)
|
||||
pool := &sync.Pool{
|
||||
New: func() any {
|
||||
return &retValue{}
|
||||
},
|
||||
func (f FullReader) blockFromPool(i uint32) *BlockResults {
|
||||
out := f.pool.Get().(*BlockResults)
|
||||
out.idx = i
|
||||
out.err = nil
|
||||
if i == uint32(len(f.sizes)) && f.fragDat != nil {
|
||||
out.block = f.fragDat
|
||||
return out
|
||||
}
|
||||
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
|
||||
for j := uint16(0); j < toProcess; j++ {
|
||||
go r.process((i*uint64(r.goroutineLimit))+uint64(j), curOffset, pool, retChan)
|
||||
curOffset += uint64(r.sizes[(i*uint64(r.goroutineLimit))+uint64(j)]) &^ (1 << 24)
|
||||
if i >= uint32(len(f.sizes)) {
|
||||
out.err = errors.New("invalid block index")
|
||||
return out
|
||||
}
|
||||
realSize := f.sizes[i] &^ (1 << 24)
|
||||
if realSize == 0 {
|
||||
if i == uint32(len(f.sizes)-1) && f.fragDat == nil {
|
||||
out.block = make([]byte, f.fileSize%uint64(f.blockSize))
|
||||
return out
|
||||
}
|
||||
// Then consume the results on retChan
|
||||
for j := uint16(0); j < toProcess; j++ {
|
||||
res := <-retChan
|
||||
// If there's an error, we don't care about the results.
|
||||
if res.err != nil {
|
||||
errCache = append(errCache, res.err)
|
||||
if len(cache) > 0 {
|
||||
clear(cache)
|
||||
}
|
||||
continue
|
||||
out.block = make([]byte, f.blockSize)
|
||||
}
|
||||
out.block = make([]byte, realSize)
|
||||
_, out.err = f.rdr.ReadAt(out.block, int64(f.blockOffsets[i]))
|
||||
if out.err != nil {
|
||||
return out
|
||||
}
|
||||
if realSize == f.sizes[i] {
|
||||
out.block, out.err = f.decomp.Decompress(out.block)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type BlockResults struct {
|
||||
idx uint32
|
||||
block []byte
|
||||
err error
|
||||
}
|
||||
|
||||
func (f FullReader) WriteTo(w io.Writer) (wrote int64, err error) {
|
||||
if f.dispatcher == nil {
|
||||
f.dispatcher = make(chan struct{}, runtime.NumCPU())
|
||||
for range runtime.NumCPU() {
|
||||
f.dispatcher <- struct{}{}
|
||||
}
|
||||
}
|
||||
if f.pool == nil {
|
||||
f.pool = &sync.Pool{
|
||||
New: func() any {
|
||||
return &BlockResults{}
|
||||
},
|
||||
}
|
||||
}
|
||||
open := true
|
||||
resChan := make(chan *BlockResults, len(f.dispatcher))
|
||||
var results map[uint32]*BlockResults
|
||||
if _, is := w.(io.WriterAt); !is {
|
||||
results = make(map[uint32]*BlockResults)
|
||||
}
|
||||
for i := range f.BlockNum() {
|
||||
go func(idx uint32) {
|
||||
<-f.dispatcher
|
||||
defer func() { f.dispatcher <- struct{}{} }()
|
||||
if !open {
|
||||
resChan <- f.pool.Get().(*BlockResults)
|
||||
return
|
||||
}
|
||||
// If there has been an error previously, we don't care about the results.
|
||||
// We still want to wait for all the goroutines to prevent resources being wasted.
|
||||
if len(errCache) > 0 {
|
||||
continue
|
||||
}
|
||||
// If we don't need the data yet, we cache it and move on
|
||||
if res.index != curIndex {
|
||||
cache[res.index] = res
|
||||
continue
|
||||
}
|
||||
// If we do need the data, we write it
|
||||
wr, err := w.Write(res.data)
|
||||
wrote += int64(wr)
|
||||
resChan <- f.blockFromPool(idx)
|
||||
}(i)
|
||||
}
|
||||
out := int64(0)
|
||||
errOut := make([]error, 0)
|
||||
for i := uint32(0); i < f.BlockNum(); {
|
||||
res := <-resChan
|
||||
defer f.pool.Put(res)
|
||||
if res.err != nil {
|
||||
open = false
|
||||
errOut = append(errOut, res.err)
|
||||
}
|
||||
if len(errOut) > 0 {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if wa, is := w.(io.WriterAt); is {
|
||||
_, err := wa.WriteAt(res.block, int64(res.idx)*int64(f.blockSize))
|
||||
if err != nil {
|
||||
errCache = append(errCache, err)
|
||||
if len(cache) > 0 {
|
||||
clear(cache)
|
||||
}
|
||||
continue
|
||||
errOut = append(errOut, err)
|
||||
} else {
|
||||
out = max(out, int64(res.idx)*int64(f.blockSize)+int64(len(res.block)))
|
||||
}
|
||||
pool.Put(res)
|
||||
curIndex++
|
||||
// Now we recursively try to clear the cache
|
||||
for len(cache) > 0 {
|
||||
res, ok := cache[curIndex]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
wr, err := w.Write(res.data)
|
||||
wrote += int64(wr)
|
||||
i++
|
||||
continue
|
||||
}
|
||||
var err error
|
||||
if res.idx == i {
|
||||
_, err = w.Write(res.block)
|
||||
if err != nil {
|
||||
errOut = append(errOut, err)
|
||||
} else {
|
||||
out = max(out, int64(res.idx)*int64(f.blockSize)+int64(len(res.block)))
|
||||
}
|
||||
i++
|
||||
} else {
|
||||
results[res.idx] = res
|
||||
}
|
||||
var has bool
|
||||
for {
|
||||
res, has = results[i]
|
||||
if has {
|
||||
_, err = w.Write(res.block)
|
||||
if err != nil {
|
||||
errCache = append(errCache, err)
|
||||
if len(cache) > 0 {
|
||||
clear(cache)
|
||||
}
|
||||
break
|
||||
errOut = append(errOut, err)
|
||||
} else {
|
||||
out = max(out, int64(res.idx)*int64(f.blockSize)+int64(len(res.block)))
|
||||
}
|
||||
delete(cache, curIndex)
|
||||
pool.Put(res)
|
||||
curIndex++
|
||||
i++
|
||||
delete(results, i)
|
||||
f.pool.Put(res)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(errCache) > 0 {
|
||||
return wrote, errors.Join(errCache...)
|
||||
}
|
||||
}
|
||||
if r.frag != nil {
|
||||
rdr, err := r.frag()
|
||||
if err != nil {
|
||||
return wrote, err
|
||||
}
|
||||
wr, err := io.Copy(w, rdr)
|
||||
wrote += wr
|
||||
if l, ok := rdr.(*io.LimitedReader); ok {
|
||||
if cl, ok := l.R.(io.Closer); ok {
|
||||
cl.Close()
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return wrote, err
|
||||
}
|
||||
if len(errOut) > 0 {
|
||||
return out, errors.Join(errOut...)
|
||||
}
|
||||
return wrote, nil
|
||||
return out, 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
|
||||
// }
|
||||
|
||||
+42
-77
@@ -1,95 +1,60 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/CalebQ42/squashfs/internal/decompress"
|
||||
)
|
||||
import "io"
|
||||
|
||||
type Reader struct {
|
||||
r io.Reader
|
||||
d decompress.Decompressor
|
||||
frag io.Reader
|
||||
sizes []uint32
|
||||
dat []byte
|
||||
curOffset int
|
||||
curIndex uint64
|
||||
finalBlockSize uint64
|
||||
blockSize uint32
|
||||
f *FullReader
|
||||
curBlock []byte
|
||||
nextIdx uint32
|
||||
curOffset uint32
|
||||
}
|
||||
|
||||
func NewReader(r io.Reader, d decompress.Decompressor, sizes []uint32, finalBlockSize uint64, blockSize uint32) *Reader {
|
||||
return &Reader{
|
||||
r: r,
|
||||
d: d,
|
||||
sizes: sizes,
|
||||
finalBlockSize: finalBlockSize,
|
||||
blockSize: blockSize,
|
||||
func NewReader(f *FullReader) (Reader, error) {
|
||||
dat, err := f.Block(0)
|
||||
if err != nil {
|
||||
return Reader{}, err
|
||||
}
|
||||
return Reader{
|
||||
f: f,
|
||||
curBlock: dat,
|
||||
nextIdx: 1,
|
||||
curOffset: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Reader) AddFrag(fragRdr io.Reader) {
|
||||
r.frag = fragRdr
|
||||
func (d *Reader) Close() error {
|
||||
d.curBlock = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reader) advance() error {
|
||||
r.curOffset = 0
|
||||
defer func() { r.curIndex++ }()
|
||||
var err error
|
||||
if r.curIndex == uint64(len(r.sizes)) && r.frag != nil {
|
||||
r.dat, err = io.ReadAll(r.frag)
|
||||
return err
|
||||
} else if r.curIndex >= uint64(len(r.sizes)) {
|
||||
r.dat = []byte{}
|
||||
func (d *Reader) advanceBlock() error {
|
||||
if d.nextIdx >= d.f.BlockNum() {
|
||||
d.curBlock = nil
|
||||
return io.EOF
|
||||
}
|
||||
realSize := r.sizes[r.curIndex] &^ (1 << 24)
|
||||
if realSize == 0 {
|
||||
if r.curIndex == uint64(len(r.sizes))-1 && r.frag == nil {
|
||||
r.dat = make([]byte, r.finalBlockSize)
|
||||
} else {
|
||||
r.dat = make([]byte, r.blockSize)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
r.dat = make([]byte, realSize)
|
||||
err = binary.Read(r.r, binary.LittleEndian, &r.dat)
|
||||
var err error
|
||||
d.curBlock, err = d.f.Block(d.nextIdx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.sizes[r.curIndex] != realSize {
|
||||
return nil
|
||||
}
|
||||
r.dat, err = r.d.Decompress(r.dat)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Reader) Read(b []byte) (int, error) {
|
||||
curRead := 0
|
||||
var toRead int
|
||||
for curRead < len(b) {
|
||||
if r.curOffset >= len(r.dat) {
|
||||
if err := r.advance(); err != nil {
|
||||
return curRead, err
|
||||
}
|
||||
}
|
||||
toRead = min(len(b)-curRead, len(r.dat)-r.curOffset)
|
||||
toRead = copy(b[curRead:], r.dat[r.curOffset:r.curOffset+toRead])
|
||||
r.curOffset += toRead
|
||||
curRead += toRead
|
||||
}
|
||||
return curRead, nil
|
||||
}
|
||||
|
||||
func (r *Reader) Close() error {
|
||||
if r.frag != nil {
|
||||
if l, ok := r.frag.(*io.LimitedReader); ok {
|
||||
if cl, ok := l.R.(io.Closer); ok {
|
||||
cl.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
r.dat = nil
|
||||
d.nextIdx++
|
||||
d.curOffset = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Reader) Read(buf []byte) (int, error) {
|
||||
totRed := 0
|
||||
toRead := 0
|
||||
var err error
|
||||
for totRed < len(buf) {
|
||||
if int(d.curOffset) >= len(d.curBlock) {
|
||||
err = d.advanceBlock()
|
||||
if err != nil {
|
||||
return totRed, err
|
||||
}
|
||||
}
|
||||
toRead = min(len(d.curBlock)-int(d.curOffset), len(buf)-totRed)
|
||||
copy(buf[totRed:], d.curBlock[d.curOffset:d.curOffset+uint32(toRead)])
|
||||
}
|
||||
return totRed, nil
|
||||
}
|
||||
|
||||
+3
-3
@@ -18,7 +18,7 @@ type Directory struct {
|
||||
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)
|
||||
if err != nil {
|
||||
return Directory{}, err
|
||||
@@ -44,7 +44,7 @@ func (r *Reader) directoryFromRef(ref uint64, name string) (Directory, error) {
|
||||
if err != nil {
|
||||
return Directory{}, err
|
||||
}
|
||||
entries, err := directory.ReadDirectory(dirRdr, size)
|
||||
entries, err := directory.ReadDirectory(&dirRdr, size)
|
||||
if err != nil {
|
||||
return Directory{}, err
|
||||
}
|
||||
@@ -54,7 +54,7 @@ func (r *Reader) directoryFromRef(ref uint64, name string) (Directory, error) {
|
||||
}, 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)
|
||||
if path == "." || path == "" {
|
||||
return d.FileBase, nil
|
||||
|
||||
+39
-11
@@ -11,12 +11,45 @@ type header struct {
|
||||
Num uint32
|
||||
}
|
||||
|
||||
type decEntry struct {
|
||||
func readHeader(r io.Reader) (h header, err error) {
|
||||
dat := make([]byte, 12)
|
||||
_, err = r.Read(dat)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
h.Count = binary.LittleEndian.Uint32(dat)
|
||||
h.BlockStart = binary.LittleEndian.Uint32(dat[4:])
|
||||
h.Num = binary.LittleEndian.Uint32(dat[8:])
|
||||
return
|
||||
}
|
||||
|
||||
type dirEntry struct {
|
||||
Offset uint16
|
||||
NumOffset int16
|
||||
InodeType uint16
|
||||
NameSize uint16
|
||||
// Name []byte (not decoded along with decEntry)
|
||||
Name []byte
|
||||
}
|
||||
|
||||
func readEntry(r io.Reader) (e dirEntry, err error) {
|
||||
dat := make([]byte, 8)
|
||||
_, err = r.Read(dat)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
e.Offset = binary.LittleEndian.Uint16(dat)
|
||||
_, err = binary.Decode(dat[2:], binary.LittleEndian, &e.NumOffset)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
e.InodeType = binary.LittleEndian.Uint16(dat[4:])
|
||||
e.NameSize = binary.LittleEndian.Uint16(dat[6:])
|
||||
e.Name = make([]byte, e.NameSize+1)
|
||||
_, err = r.Read(e.Name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Entry struct {
|
||||
@@ -31,20 +64,15 @@ func ReadDirectory(r io.Reader, size uint32) (out []Entry, err error) {
|
||||
size -= 3
|
||||
var curRead uint32
|
||||
var h header
|
||||
var de decEntry
|
||||
var de dirEntry
|
||||
for curRead < size {
|
||||
err = binary.Read(r, binary.LittleEndian, &h)
|
||||
h, err = readHeader(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
curRead += 12
|
||||
for i := uint32(0); i < h.Count+1 && curRead < size; i++ {
|
||||
err = binary.Read(r, binary.LittleEndian, &de)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
nameTmp := make([]byte, de.NameSize+1)
|
||||
err = binary.Read(r, binary.LittleEndian, &nameTmp)
|
||||
de, err = readEntry(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -52,7 +80,7 @@ func ReadDirectory(r io.Reader, size uint32) (out []Entry, err error) {
|
||||
out = append(out, Entry{
|
||||
BlockStart: h.BlockStart,
|
||||
Offset: de.Offset,
|
||||
Name: string(nameTmp),
|
||||
Name: string(de.Name),
|
||||
InodeType: de.InodeType,
|
||||
Num: h.Num + uint32(de.NumOffset),
|
||||
})
|
||||
|
||||
+36
-83
@@ -2,7 +2,6 @@ package squashfslow
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/CalebQ42/squashfs/internal/metadata"
|
||||
"github.com/CalebQ42/squashfs/internal/toreader"
|
||||
@@ -16,11 +15,11 @@ type FileBase struct {
|
||||
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}
|
||||
}
|
||||
|
||||
func (r *Reader) BaseFromEntry(e directory.Entry) (FileBase, error) {
|
||||
func (r Reader) BaseFromEntry(e directory.Entry) (FileBase, error) {
|
||||
in, err := r.InodeFromEntry(e)
|
||||
if err != nil {
|
||||
return FileBase{}, err
|
||||
@@ -28,7 +27,7 @@ func (r *Reader) BaseFromEntry(e directory.Entry) (FileBase, error) {
|
||||
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)
|
||||
if err != nil {
|
||||
return FileBase{}, err
|
||||
@@ -36,19 +35,19 @@ func (r *Reader) BaseFromRef(ref uint64, name string) (FileBase, error) {
|
||||
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)
|
||||
}
|
||||
|
||||
func (b *FileBase) Gid(r *Reader) (uint32, error) {
|
||||
func (b FileBase) Gid(r *Reader) (uint32, error) {
|
||||
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
|
||||
}
|
||||
|
||||
func (b *FileBase) ToDir(r *Reader) (Directory, error) {
|
||||
func (b FileBase) ToDir(r Reader) (Directory, error) {
|
||||
var blockStart uint32
|
||||
var size uint32
|
||||
var offset uint16
|
||||
@@ -70,134 +69,88 @@ func (b *FileBase) ToDir(r *Reader) (Directory, error) {
|
||||
if err != nil {
|
||||
return Directory{}, err
|
||||
}
|
||||
entries, err := directory.ReadDirectory(dirRdr, size)
|
||||
entries, err := directory.ReadDirectory(&dirRdr, size)
|
||||
if err != nil {
|
||||
return Directory{}, err
|
||||
}
|
||||
return Directory{
|
||||
FileBase: *b,
|
||||
FileBase: b,
|
||||
Entries: entries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *FileBase) IsRegular() bool {
|
||||
func (b FileBase) IsRegular() bool {
|
||||
return b.Inode.Type == inode.Fil || b.Inode.Type == inode.EFil
|
||||
}
|
||||
|
||||
func (b *FileBase) GetRegFileReaders(r *Reader) (*data.Reader, *data.FullReader, error) {
|
||||
// Returns a regular file's readers. They are linked, so the data.Reader calls to the data.FullReader.
|
||||
// Aka: closing the FullReader breaks the Reader
|
||||
func (b FileBase) GetRegFileReaders(r Reader) (data.Reader, data.FullReader, error) {
|
||||
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 fragIndex uint32
|
||||
var fragOffset uint32
|
||||
var fragSize uint64
|
||||
var sizes []uint32
|
||||
var fileSize uint64
|
||||
if b.Inode.Type == inode.Fil {
|
||||
blockStart = uint64(b.Inode.Data.(inode.File).BlockStart)
|
||||
fragIndex = b.Inode.Data.(inode.File).FragInd
|
||||
fragOffset = b.Inode.Data.(inode.File).FragOffset
|
||||
sizes = b.Inode.Data.(inode.File).BlockSizes
|
||||
fragSize = uint64(b.Inode.Data.(inode.File).Size % r.Superblock.BlockSize)
|
||||
fileSize = uint64(b.Inode.Data.(inode.File).Size)
|
||||
} else {
|
||||
blockStart = b.Inode.Data.(inode.EFile).BlockStart
|
||||
fragIndex = b.Inode.Data.(inode.EFile).FragInd
|
||||
fragOffset = b.Inode.Data.(inode.EFile).FragOffset
|
||||
sizes = b.Inode.Data.(inode.EFile).BlockSizes
|
||||
fragSize = b.Inode.Data.(inode.EFile).Size % uint64(r.Superblock.BlockSize)
|
||||
fileSize = b.Inode.Data.(inode.EFile).Size
|
||||
}
|
||||
frag := func() (io.Reader, error) {
|
||||
outFull := data.NewFullReader(r.r, r.d, r.Superblock.BlockSize, fileSize, blockStart, sizes)
|
||||
if fragIndex != 0xFFFFFFFF {
|
||||
ent, err := r.fragEntry(fragIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return data.Reader{}, data.FullReader{}, err
|
||||
}
|
||||
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))
|
||||
return io.LimitReader(frag, int64(fragSize)), nil
|
||||
outFull.AddFragData(ent.Start, ent.Size, fragOffset)
|
||||
}
|
||||
outRdr := data.NewReader(toreader.NewReader(r.r, int64(blockStart)), r.d, sizes, fragSize, r.Superblock.BlockSize)
|
||||
if fragIndex != 0xffffffff {
|
||||
f, err := frag()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
outRdr.AddFrag(f)
|
||||
}
|
||||
outFull := data.NewFullReader(r.r, int64(blockStart), r.d, sizes, fragSize, r.Superblock.BlockSize)
|
||||
if fragIndex != 0xffffffff {
|
||||
outFull.AddFrag(frag)
|
||||
outRdr, err := data.NewReader(&outFull)
|
||||
if err != nil {
|
||||
return data.Reader{}, data.FullReader{}, err
|
||||
}
|
||||
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() {
|
||||
return nil, errors.New("not a regular file")
|
||||
return data.FullReader{}, errors.New("not a regular file")
|
||||
}
|
||||
var blockStart uint64
|
||||
var fragIndex uint32
|
||||
var fragOffset uint32
|
||||
var fragSize uint64
|
||||
var sizes []uint32
|
||||
var fileSize uint64
|
||||
if b.Inode.Type == inode.Fil {
|
||||
blockStart = uint64(b.Inode.Data.(inode.File).BlockStart)
|
||||
fragIndex = b.Inode.Data.(inode.File).FragInd
|
||||
fragOffset = b.Inode.Data.(inode.File).FragOffset
|
||||
sizes = b.Inode.Data.(inode.File).BlockSizes
|
||||
fragSize = uint64(b.Inode.Data.(inode.File).Size % r.Superblock.BlockSize)
|
||||
fileSize = uint64(b.Inode.Data.(inode.File).Size)
|
||||
} else {
|
||||
blockStart = b.Inode.Data.(inode.EFile).BlockStart
|
||||
fragIndex = b.Inode.Data.(inode.EFile).FragInd
|
||||
fragOffset = b.Inode.Data.(inode.EFile).FragOffset
|
||||
sizes = b.Inode.Data.(inode.EFile).BlockSizes
|
||||
fragSize = b.Inode.Data.(inode.EFile).Size % uint64(r.Superblock.BlockSize)
|
||||
fileSize = b.Inode.Data.(inode.EFile).Size
|
||||
}
|
||||
outFull := data.NewFullReader(r.r, int64(blockStart), r.d, sizes, fragSize, r.Superblock.BlockSize)
|
||||
if fragIndex != 0xffffffff {
|
||||
outFull.AddFrag(func() (io.Reader, error) {
|
||||
ent, err := r.fragEntry(fragIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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))
|
||||
return io.LimitReader(frag, int64(fragSize)), nil
|
||||
})
|
||||
outFull := data.NewFullReader(r.r, r.d, r.Superblock.BlockSize, fileSize, blockStart, sizes)
|
||||
if fragIndex != 0xFFFFFFFF {
|
||||
ent, err := r.fragEntry(fragIndex)
|
||||
if err != nil {
|
||||
return data.FullReader{}, err
|
||||
}
|
||||
outFull.AddFragData(ent.Start, ent.Size, fragOffset)
|
||||
}
|
||||
return outFull, nil
|
||||
}
|
||||
|
||||
func (b *FileBase) GetReader(r *Reader) (*data.Reader, error) {
|
||||
if !b.IsRegular() {
|
||||
return nil, errors.New("not a regular file")
|
||||
}
|
||||
var blockStart uint64
|
||||
var fragIndex uint32
|
||||
var fragOffset uint32
|
||||
var fragSize uint64
|
||||
var sizes []uint32
|
||||
if b.Inode.Type == inode.Fil {
|
||||
blockStart = uint64(b.Inode.Data.(inode.File).BlockStart)
|
||||
fragIndex = b.Inode.Data.(inode.File).FragInd
|
||||
fragOffset = b.Inode.Data.(inode.File).FragOffset
|
||||
sizes = b.Inode.Data.(inode.File).BlockSizes
|
||||
fragSize = uint64(b.Inode.Data.(inode.File).Size % r.Superblock.BlockSize)
|
||||
} else {
|
||||
blockStart = b.Inode.Data.(inode.EFile).BlockStart
|
||||
fragIndex = b.Inode.Data.(inode.EFile).FragInd
|
||||
fragOffset = b.Inode.Data.(inode.EFile).FragOffset
|
||||
sizes = b.Inode.Data.(inode.EFile).BlockSizes
|
||||
fragSize = b.Inode.Data.(inode.EFile).Size % uint64(r.Superblock.BlockSize)
|
||||
}
|
||||
outRdr := data.NewReader(toreader.NewReader(r.r, int64(blockStart)), r.d, sizes, fragSize, r.Superblock.BlockSize)
|
||||
if fragIndex != 0xffffffff {
|
||||
ent, err := r.fragEntry(fragIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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))
|
||||
outRdr.AddFrag(io.LimitReader(frag, int64(fragSize)))
|
||||
}
|
||||
return outRdr, nil
|
||||
}
|
||||
|
||||
+6
-4
@@ -7,7 +7,9 @@ import (
|
||||
"github.com/CalebQ42/squashfs/low/inode"
|
||||
)
|
||||
|
||||
func (r *Reader) InodeFromRef(ref uint64) (inode.Inode, error) {
|
||||
type InodeRef = uint64
|
||||
|
||||
func (r Reader) InodeFromRef(ref InodeRef) (inode.Inode, error) {
|
||||
offset, meta := (ref>>16)+r.Superblock.InodeTableStart, ref&0xFFFF
|
||||
rdr := metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d)
|
||||
defer rdr.Close()
|
||||
@@ -15,12 +17,12 @@ func (r *Reader) InodeFromRef(ref uint64) (inode.Inode, error) {
|
||||
if err != nil {
|
||||
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)
|
||||
defer rdr.Close()
|
||||
rdr.Read(make([]byte, e.Offset))
|
||||
return inode.Read(rdr, r.Superblock.BlockSize)
|
||||
return inode.Read(&rdr, r.Superblock.BlockSize)
|
||||
}
|
||||
|
||||
+36
-23
@@ -13,7 +13,21 @@ type Directory struct {
|
||||
ParentNum uint32
|
||||
}
|
||||
|
||||
type eDirectoryInit struct {
|
||||
func ReadDir(r io.Reader) (d Directory, err error) {
|
||||
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
|
||||
}
|
||||
|
||||
type EDirectory struct {
|
||||
LinkCount uint32
|
||||
Size uint32
|
||||
BlockStart uint32
|
||||
@@ -21,42 +35,41 @@ type eDirectoryInit struct {
|
||||
IndCount uint16
|
||||
Offset uint16
|
||||
XattrInd uint32
|
||||
}
|
||||
|
||||
type EDirectory struct {
|
||||
eDirectoryInit
|
||||
Indexes []DirectoryIndex
|
||||
}
|
||||
|
||||
type directoryIndexInit struct {
|
||||
Ind uint32
|
||||
Start uint32
|
||||
NameSize uint32
|
||||
Indexes []DirectoryIndex
|
||||
}
|
||||
|
||||
type DirectoryIndex struct {
|
||||
directoryIndexInit
|
||||
Name []byte
|
||||
}
|
||||
|
||||
func ReadDir(r io.Reader) (d Directory, err error) {
|
||||
err = binary.Read(r, binary.LittleEndian, &d)
|
||||
return
|
||||
Ind uint32
|
||||
Start uint32
|
||||
NameSize uint32
|
||||
Name []byte
|
||||
}
|
||||
|
||||
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 {
|
||||
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)
|
||||
for i := range d.Indexes {
|
||||
err = binary.Read(r, binary.LittleEndian, &d.Indexes[i].directoryIndexInit)
|
||||
for i := range d.IndCount {
|
||||
dat = make([]byte, 12)
|
||||
_, err = r.Read(dat)
|
||||
if err != nil {
|
||||
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)
|
||||
err = binary.Read(r, binary.LittleEndian, &d.Indexes[i].Name)
|
||||
_, err = r.Read(d.Indexes[i].Name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
+46
-27
@@ -6,19 +6,41 @@ import (
|
||||
"math"
|
||||
)
|
||||
|
||||
type fileInit struct {
|
||||
type File struct {
|
||||
BlockStart uint32
|
||||
FragInd uint32
|
||||
FragOffset uint32
|
||||
Size uint32
|
||||
}
|
||||
|
||||
type File struct {
|
||||
fileInit
|
||||
BlockSizes []uint32
|
||||
}
|
||||
|
||||
type eFileInit struct {
|
||||
func ReadFile(r io.Reader, blockSize uint32) (f File, err error) {
|
||||
dat := make([]byte, 16)
|
||||
_, err = r.Read(dat)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
f.BlockStart = binary.LittleEndian.Uint32(dat[0:4])
|
||||
f.FragInd = binary.LittleEndian.Uint32(dat[4:8])
|
||||
f.FragOffset = binary.LittleEndian.Uint32(dat[8:12])
|
||||
f.Size = binary.LittleEndian.Uint32(dat[12:16])
|
||||
toRead := int(math.Floor(float64(f.Size) / float64(blockSize)))
|
||||
if f.FragInd == 0xFFFFFFFF && f.Size%blockSize > 0 {
|
||||
toRead++
|
||||
}
|
||||
dat = make([]byte, toRead*4)
|
||||
_, err = r.Read(dat)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
f.BlockSizes = make([]uint32, toRead)
|
||||
for i := range toRead {
|
||||
f.BlockSizes[i] = binary.LittleEndian.Uint32(dat[i*4:])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type EFile struct {
|
||||
BlockStart uint64
|
||||
Size uint64
|
||||
Sparse uint64
|
||||
@@ -26,37 +48,34 @@ type eFileInit struct {
|
||||
FragInd uint32
|
||||
FragOffset uint32
|
||||
XattrInd uint32
|
||||
}
|
||||
|
||||
type EFile struct {
|
||||
eFileInit
|
||||
BlockSizes []uint32
|
||||
}
|
||||
|
||||
func ReadFile(r io.Reader, blockSize uint32) (f File, err error) {
|
||||
err = binary.Read(r, binary.LittleEndian, &f.fileInit)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
toRead := int(math.Floor(float64(f.Size) / float64(blockSize)))
|
||||
if f.FragInd == 0xFFFFFFFF && f.Size%blockSize > 0 {
|
||||
toRead++
|
||||
}
|
||||
f.BlockSizes = make([]uint32, toRead)
|
||||
err = binary.Read(r, binary.LittleEndian, &f.BlockSizes)
|
||||
return
|
||||
}
|
||||
|
||||
func ReadEFile(r io.Reader, blockSize uint32) (f EFile, err error) {
|
||||
err = binary.Read(r, binary.LittleEndian, &f.eFileInit)
|
||||
dat := make([]byte, 40)
|
||||
_, err = r.Read(dat)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
toRead := int(math.Floor(float64(f.Size) / float64(blockSize)))
|
||||
f.BlockStart = binary.LittleEndian.Uint64(dat[0:8])
|
||||
f.Size = binary.LittleEndian.Uint64(dat[8:16])
|
||||
f.Sparse = binary.LittleEndian.Uint64(dat[16:24])
|
||||
f.LinkCount = binary.LittleEndian.Uint32(dat[24:28])
|
||||
f.FragInd = binary.LittleEndian.Uint32(dat[28:32])
|
||||
f.FragOffset = binary.LittleEndian.Uint32(dat[32:36])
|
||||
f.XattrInd = binary.LittleEndian.Uint32(dat[36:40])
|
||||
toRead := f.Size / uint64(blockSize)
|
||||
if f.FragInd == 0xFFFFFFFF && f.Size%uint64(blockSize) > 0 {
|
||||
toRead++
|
||||
}
|
||||
dat = make([]byte, toRead*4)
|
||||
_, err = r.Read(dat)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
+8
-1
@@ -40,10 +40,17 @@ type Inode struct {
|
||||
}
|
||||
|
||||
func Read(r io.Reader, blockSize uint32) (i Inode, err error) {
|
||||
err = binary.Read(r, binary.LittleEndian, &i.Header)
|
||||
dat := make([]byte, 16)
|
||||
_, err = r.Read(dat)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
i.Type = binary.LittleEndian.Uint16(dat[0:2])
|
||||
i.Perm = binary.LittleEndian.Uint16(dat[2:4])
|
||||
i.UidInd = binary.LittleEndian.Uint16(dat[4:6])
|
||||
i.GidInd = binary.LittleEndian.Uint16(dat[6:8])
|
||||
i.ModTime = binary.LittleEndian.Uint32(dat[8:12])
|
||||
i.Num = binary.LittleEndian.Uint32(dat[12:16])
|
||||
switch i.Type {
|
||||
case Dir:
|
||||
i.Data, err = ReadDir(r)
|
||||
|
||||
+36
-12
@@ -10,18 +10,31 @@ type Device struct {
|
||||
Dev uint32
|
||||
}
|
||||
|
||||
func ReadDevice(r io.Reader) (d Device, err error) {
|
||||
dat := make([]byte, 8)
|
||||
_, err = r.Read(dat)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
d.LinkCount = binary.LittleEndian.Uint32(dat)
|
||||
d.Dev = binary.LittleEndian.Uint32(dat[4:])
|
||||
return
|
||||
}
|
||||
|
||||
type EDevice struct {
|
||||
Device
|
||||
XattrInd uint32
|
||||
}
|
||||
|
||||
func ReadDevice(r io.Reader) (d Device, err error) {
|
||||
err = binary.Read(r, binary.LittleEndian, &d)
|
||||
return
|
||||
}
|
||||
|
||||
func ReadEDevice(r io.Reader) (d EDevice, err error) {
|
||||
err = binary.Read(r, binary.LittleEndian, &d)
|
||||
dat := make([]byte, 12)
|
||||
_, err = r.Read(dat)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
d.LinkCount = binary.LittleEndian.Uint32(dat)
|
||||
d.Dev = binary.LittleEndian.Uint32(dat[4:])
|
||||
d.XattrInd = binary.LittleEndian.Uint32(dat[8:])
|
||||
return
|
||||
}
|
||||
|
||||
@@ -29,17 +42,28 @@ type IPC struct {
|
||||
LinkCount uint32
|
||||
}
|
||||
|
||||
func ReadIPC(r io.Reader) (i IPC, err error) {
|
||||
dat := make([]byte, 4)
|
||||
_, err = r.Read(dat)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
i.LinkCount = binary.LittleEndian.Uint32(dat)
|
||||
return
|
||||
}
|
||||
|
||||
type EIPC struct {
|
||||
IPC
|
||||
XattrInd uint32
|
||||
}
|
||||
|
||||
func ReadIPC(r io.Reader) (i IPC, err error) {
|
||||
err = binary.Read(r, binary.LittleEndian, &i)
|
||||
return
|
||||
}
|
||||
|
||||
func ReadEIPC(r io.Reader) (i EIPC, err error) {
|
||||
err = binary.Read(r, binary.LittleEndian, &i)
|
||||
dat := make([]byte, 8)
|
||||
_, err = r.Read(dat)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
i.LinkCount = binary.LittleEndian.Uint32(dat)
|
||||
i.XattrInd = binary.LittleEndian.Uint32(dat[4:])
|
||||
return
|
||||
}
|
||||
|
||||
+25
-17
@@ -5,42 +5,50 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type symlinkInit struct {
|
||||
type Symlink struct {
|
||||
LinkCount uint32
|
||||
TargetSize uint32
|
||||
}
|
||||
|
||||
type Symlink struct {
|
||||
symlinkInit
|
||||
Target []byte
|
||||
}
|
||||
|
||||
type ESymlink struct {
|
||||
symlinkInit
|
||||
Target []byte
|
||||
XattrInd uint32
|
||||
Target []byte
|
||||
}
|
||||
|
||||
func ReadSym(r io.Reader) (s Symlink, err error) {
|
||||
err = binary.Read(r, binary.LittleEndian, &s.symlinkInit)
|
||||
dat := make([]byte, 8)
|
||||
_, err = r.Read(dat)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.LinkCount = binary.LittleEndian.Uint32(dat)
|
||||
s.TargetSize = binary.LittleEndian.Uint32(dat[4:])
|
||||
s.Target = make([]byte, s.TargetSize)
|
||||
err = binary.Read(r, binary.LittleEndian, &s.Target)
|
||||
_, err = r.Read(s.Target)
|
||||
return
|
||||
}
|
||||
|
||||
type ESymlink struct {
|
||||
LinkCount uint32
|
||||
TargetSize uint32
|
||||
Target []byte
|
||||
XattrInd uint32
|
||||
}
|
||||
|
||||
func ReadESym(r io.Reader) (s ESymlink, err error) {
|
||||
err = binary.Read(r, binary.LittleEndian, &s.symlinkInit)
|
||||
dat := make([]byte, 8)
|
||||
_, err = r.Read(dat)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.LinkCount = binary.LittleEndian.Uint32(dat)
|
||||
s.TargetSize = binary.LittleEndian.Uint32(dat[4:])
|
||||
s.Target = make([]byte, s.TargetSize)
|
||||
err = binary.Read(r, binary.LittleEndian, &s.Target)
|
||||
_, err = r.Read(s.Target)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = binary.Read(r, binary.LittleEndian, &s.XattrInd)
|
||||
dat = make([]byte, 4)
|
||||
_, err = r.Read(dat)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.XattrInd = binary.LittleEndian.Uint32(dat)
|
||||
return
|
||||
}
|
||||
|
||||
+57
-135
@@ -4,10 +4,8 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/CalebQ42/squashfs/internal/decompress"
|
||||
"github.com/CalebQ42/squashfs/internal/metadata"
|
||||
"github.com/CalebQ42/squashfs/internal/toreader"
|
||||
"github.com/CalebQ42/squashfs/low/inode"
|
||||
)
|
||||
@@ -30,185 +28,109 @@ var (
|
||||
)
|
||||
|
||||
type Reader struct {
|
||||
Root Directory
|
||||
Superblock superblock
|
||||
r io.ReaderAt
|
||||
d decompress.Decompressor
|
||||
Root Directory
|
||||
fragTable []fragEntry
|
||||
idTable []uint32
|
||||
exportTable []uint64
|
||||
Superblock superblock
|
||||
fragTable *Table[fragEntry]
|
||||
idTable *Table[uint32]
|
||||
exportTable *Table[InodeRef]
|
||||
}
|
||||
|
||||
func NewReader(r io.ReaderAt) (rdr *Reader, err error) {
|
||||
rdr = new(Reader)
|
||||
func NewReader(r io.ReaderAt) (rdr Reader, err error) {
|
||||
rdr.r = r
|
||||
err = binary.Read(toreader.NewReader(r, 0), binary.LittleEndian, &rdr.Superblock)
|
||||
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() {
|
||||
return nil, ErrorMagic
|
||||
return rdr, ErrorMagic
|
||||
}
|
||||
if !rdr.Superblock.ValidBlockLog() {
|
||||
return nil, ErrorLog
|
||||
return rdr, ErrorLog
|
||||
}
|
||||
if !rdr.Superblock.ValidVersion() {
|
||||
return nil, ErrorVersion
|
||||
return rdr, ErrorVersion
|
||||
}
|
||||
switch rdr.Superblock.CompType {
|
||||
case ZlibCompression:
|
||||
rdr.d = decompress.Zlib{}
|
||||
rdr.d = decompress.NewZlib()
|
||||
case LZMACompression:
|
||||
rdr.d, err = decompress.NewLzma()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return rdr, err
|
||||
}
|
||||
case LZOCompression:
|
||||
rdr.d, err = decompress.NewLzo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return rdr, err
|
||||
}
|
||||
case XZCompression:
|
||||
rdr.d = decompress.Xz{}
|
||||
rdr.d = decompress.NewXz()
|
||||
case LZ4Compression:
|
||||
rdr.d = decompress.Lz4{}
|
||||
rdr.d = decompress.NewLz4()
|
||||
case ZSTDCompression:
|
||||
rdr.d = decompress.Zstd{}
|
||||
rdr.d = decompress.NewZstd()
|
||||
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, "")
|
||||
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)
|
||||
}
|
||||
rdr.fragTable = NewTable(&rdr, rdr.Superblock.FragTableStart, rdr.Superblock.FragCount, readFrag)
|
||||
rdr.idTable = NewTable(&rdr, rdr.Superblock.IdTableStart, uint32(rdr.Superblock.IdCount), readId)
|
||||
rdr.exportTable = NewTable(&rdr, rdr.Superblock.ExportTableStart, rdr.Superblock.InodeCount, readRef)
|
||||
return
|
||||
}
|
||||
|
||||
func readFrag(r io.Reader) (fragEntry, error) {
|
||||
dat := make([]byte, 16)
|
||||
_, err := r.Read(dat)
|
||||
if err != nil {
|
||||
return fragEntry{}, err
|
||||
}
|
||||
return fragEntry{
|
||||
Start: binary.LittleEndian.Uint64(dat[0:8]),
|
||||
Size: binary.LittleEndian.Uint32(dat[8:12]),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func readId(r io.Reader) (uint32, error) {
|
||||
dat := make([]byte, 4)
|
||||
_, err := r.Read(dat)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return binary.LittleEndian.Uint32(dat), nil
|
||||
}
|
||||
|
||||
func readRef(r io.Reader) (InodeRef, error) {
|
||||
dat := make([]byte, 8)
|
||||
_, err := r.Read(dat)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return binary.LittleEndian.Uint64(dat), nil
|
||||
}
|
||||
|
||||
// Get a uid/gid at the given index. Lazily populates the reader's Id table as necessary.
|
||||
func (r *Reader) Id(i uint16) (uint32, error) {
|
||||
if len(r.idTable) > int(i) {
|
||||
return r.idTable[i], nil
|
||||
} else if i >= r.Superblock.IdCount {
|
||||
return 0, errors.New("id out of bounds")
|
||||
}
|
||||
// Populate the id table as needed
|
||||
var blockNum uint32
|
||||
if i != 0 { // If i == 0, we go negatives causing issues with uint32s
|
||||
blockNum = uint32(math.Ceil(float64(i+1)/2048)) - 1
|
||||
} else {
|
||||
blockNum = 0
|
||||
}
|
||||
blocksRead := len(r.idTable) / 2048
|
||||
blocksToRead := int(blockNum) - blocksRead + 1
|
||||
|
||||
var offset uint64
|
||||
var idsToRead uint16
|
||||
var idsTmp []uint32
|
||||
var err error
|
||||
var rdr *metadata.Reader
|
||||
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)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
idsToRead = min(r.Superblock.IdCount-uint16(len(r.idTable)), 2048)
|
||||
idsTmp = make([]uint32, idsToRead)
|
||||
rdr = metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d)
|
||||
err = binary.Read(rdr, binary.LittleEndian, &idsTmp)
|
||||
rdr.Close()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
r.idTable = append(r.idTable, idsTmp...)
|
||||
}
|
||||
return r.idTable[i], nil
|
||||
return r.idTable.Get(uint32(i))
|
||||
}
|
||||
|
||||
// Get a fragment entry at the given index. Lazily populates the reader's fragment table as necessary.
|
||||
func (r *Reader) fragEntry(i uint32) (fragEntry, error) {
|
||||
if len(r.fragTable) > int(i) {
|
||||
return r.fragTable[i], nil
|
||||
} else if i >= r.Superblock.FragCount {
|
||||
return fragEntry{}, errors.New("fragment out of bounds")
|
||||
}
|
||||
// Populate the fragment table as needed
|
||||
var blockNum uint32
|
||||
if i != 0 { // If i == 0, we go negatives causing issues with uint32s
|
||||
blockNum = uint32(math.Ceil(float64(i+1)/512)) - 1
|
||||
} else {
|
||||
blockNum = 0
|
||||
}
|
||||
blocksRead := len(r.fragTable) / 512
|
||||
blocksToRead := int(blockNum) - blocksRead + 1
|
||||
|
||||
var offset uint64
|
||||
var fragsToRead uint32
|
||||
var fragsTmp []fragEntry
|
||||
var err error
|
||||
var rdr *metadata.Reader
|
||||
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)
|
||||
if err != nil {
|
||||
return fragEntry{}, err
|
||||
}
|
||||
fragsToRead = min(r.Superblock.FragCount-uint32(len(r.fragTable)), 512)
|
||||
fragsTmp = make([]fragEntry, fragsToRead)
|
||||
rdr = metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d)
|
||||
err = binary.Read(rdr, binary.LittleEndian, &fragsTmp)
|
||||
rdr.Close()
|
||||
if err != nil {
|
||||
return fragEntry{}, err
|
||||
}
|
||||
r.fragTable = append(r.fragTable, fragsTmp...)
|
||||
}
|
||||
return r.fragTable[i], nil
|
||||
return r.fragTable.Get(i)
|
||||
}
|
||||
|
||||
// Get an inode reference at the given index. Lazily populates the reader's export table as necessary.
|
||||
func (r *Reader) inodeRef(i uint32) (uint64, error) {
|
||||
if !r.Superblock.Exportable() {
|
||||
return 0, ErrorNotExportable
|
||||
}
|
||||
if len(r.exportTable) > int(i) {
|
||||
return r.exportTable[i], nil
|
||||
} else if i >= r.Superblock.InodeCount {
|
||||
return 0, errors.New("inode out of bounds")
|
||||
}
|
||||
// Populate the export table as needed
|
||||
var blockNum uint32
|
||||
if i != 0 { // If i == 0, we go negatives causing issues with uint32s
|
||||
blockNum = uint32(math.Ceil(float64(i+1)/1024)) - 1
|
||||
} else {
|
||||
blockNum = 0
|
||||
}
|
||||
blocksRead := len(r.exportTable) / 1024
|
||||
blocksToRead := int(blockNum) - blocksRead + 1
|
||||
|
||||
var offset uint64
|
||||
var refsToRead uint32
|
||||
var refsTmp []uint64
|
||||
var err error
|
||||
var rdr *metadata.Reader
|
||||
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)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
refsToRead = min(r.Superblock.InodeCount-uint32(len(r.exportTable)), 1024)
|
||||
refsTmp = make([]uint64, refsToRead)
|
||||
rdr = metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d)
|
||||
err = binary.Read(rdr, binary.LittleEndian, &refsTmp)
|
||||
rdr.Close()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
r.exportTable = append(r.exportTable, refsTmp...)
|
||||
}
|
||||
return r.exportTable[i], nil
|
||||
func (r *Reader) inodeRef(i uint32) (InodeRef, error) {
|
||||
return r.exportTable.Get(i)
|
||||
}
|
||||
|
||||
func (r *Reader) Inode(i uint32) (inode.Inode, error) {
|
||||
ref, err := r.inodeRef(i)
|
||||
func (r Reader) Inode(i uint32) (inode.Inode, error) {
|
||||
ref, err := r.inodeRef(i - 1) // Inode table is 1 indexed
|
||||
if err != nil {
|
||||
return inode.Inode{}, err
|
||||
}
|
||||
|
||||
+10
-6
@@ -77,8 +77,10 @@ func TestReader(t *testing.T) {
|
||||
path := filepath.Join(tmpDir, "extractTest")
|
||||
os.RemoveAll(path)
|
||||
os.MkdirAll(path, 0777)
|
||||
err = extractToDir(rdr, &rdr.Root.FileBase, path)
|
||||
t.Fatal(err)
|
||||
err = extractToDir(rdr, rdr.Root.FileBase, path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
var singleFile = "PortableApps/CPU-X/CPU-X-v4.2.0-x86_64.AppImage"
|
||||
@@ -101,11 +103,13 @@ func TestSingleFile(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = extractToDir(rdr, &b, path)
|
||||
t.Fatal(err)
|
||||
err = extractToDir(rdr, b, path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func extractToDir(rdr *Reader, b *FileBase, folder string) error {
|
||||
func extractToDir(rdr Reader, b FileBase, folder string) error {
|
||||
path := filepath.Join(folder, b.Name)
|
||||
if b.IsDir() {
|
||||
d, err := b.ToDir(rdr)
|
||||
@@ -122,7 +126,7 @@ func extractToDir(rdr *Reader, b *FileBase, folder string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = extractToDir(rdr, &nestBast, path)
|
||||
err = extractToDir(rdr, nestBast, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ type superblock struct {
|
||||
IdCount uint16
|
||||
VerMaj uint16
|
||||
VerMin uint16
|
||||
RootInodeRef uint64
|
||||
RootInodeRef InodeRef
|
||||
Size uint64
|
||||
IdTableStart uint64
|
||||
XattrTableStart uint64
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package squashfslow
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/CalebQ42/squashfs/internal/metadata"
|
||||
"github.com/CalebQ42/squashfs/internal/toreader"
|
||||
)
|
||||
|
||||
var errOutOfBounds = errors.New("out of bounds")
|
||||
var errUnexpectedOutOfBounds = errors.New("unexpected out of bounds")
|
||||
var errNilCollection = errors.New("nil collection")
|
||||
|
||||
type CreateFunction[T any] = func(io.Reader) (T, error)
|
||||
|
||||
type Table[T any] struct {
|
||||
totalItems uint32
|
||||
itemsPerBlock uint32
|
||||
offset uint64
|
||||
mut sync.RWMutex
|
||||
currentItems []T
|
||||
rdr *Reader
|
||||
createFunc CreateFunction[T]
|
||||
}
|
||||
|
||||
func NewTable[T any](rdr *Reader, start uint64, totalItems uint32, createFunc CreateFunction[T]) *Table[T] {
|
||||
var zero T
|
||||
return &Table[T]{
|
||||
totalItems: totalItems,
|
||||
itemsPerBlock: 8192 / uint32(binary.Size(zero)),
|
||||
offset: start,
|
||||
mut: sync.RWMutex{},
|
||||
rdr: rdr,
|
||||
createFunc: createFunc,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Table[T]) Get(requestedItemIndex uint32) (T, error) {
|
||||
t.mut.RLock()
|
||||
if requestedItemIndex >= t.totalItems {
|
||||
t.mut.RUnlock()
|
||||
var zero T
|
||||
return zero, errOutOfBounds
|
||||
}
|
||||
if uint32(len(t.currentItems)) > requestedItemIndex {
|
||||
t.mut.RUnlock()
|
||||
return t.currentItems[requestedItemIndex], nil
|
||||
}
|
||||
t.mut.RUnlock()
|
||||
return t.fillAndGet(requestedItemIndex)
|
||||
}
|
||||
|
||||
func (t *Table[T]) fillAndGet(requestedItemIndex uint32) (T, error) {
|
||||
t.mut.Lock()
|
||||
defer t.mut.Unlock()
|
||||
var offset uint64
|
||||
var toRead uint32
|
||||
var rdr *toreader.Reader
|
||||
var metaRdr metadata.Reader
|
||||
var err error
|
||||
for uint32(len(t.currentItems)) <= requestedItemIndex {
|
||||
rdr = toreader.NewReader(t.rdr.r, int64(t.offset))
|
||||
err = binary.Read(rdr, binary.LittleEndian, &offset)
|
||||
if err != nil {
|
||||
var zero T
|
||||
return zero, err
|
||||
}
|
||||
t.offset += 8
|
||||
toRead = min(t.itemsPerBlock, t.totalItems-uint32(len(t.currentItems)))
|
||||
oldLen := uint32(len(t.currentItems))
|
||||
t.currentItems = append(t.currentItems, make([]T, toRead)...)
|
||||
metaRdr = metadata.NewReader(toreader.NewReader(t.rdr.r, int64(offset)), t.rdr.d)
|
||||
for i := range toRead {
|
||||
t.currentItems[oldLen+i], err = t.createFunc(&metaRdr)
|
||||
if err != nil {
|
||||
var zero T
|
||||
return zero, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return t.currentItems[requestedItemIndex], nil
|
||||
}
|
||||
@@ -9,26 +9,26 @@ import (
|
||||
)
|
||||
|
||||
type Reader struct {
|
||||
*FS
|
||||
FS
|
||||
Low squashfslow.Reader
|
||||
}
|
||||
|
||||
func NewReader(r io.ReaderAt) (*Reader, error) {
|
||||
func NewReader(r io.ReaderAt) (Reader, error) {
|
||||
rdr, err := squashfslow.NewReader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return Reader{}, err
|
||||
}
|
||||
out := &Reader{
|
||||
Low: *rdr,
|
||||
out := Reader{
|
||||
Low: rdr,
|
||||
}
|
||||
out.FS = &FS{
|
||||
d: rdr.Root,
|
||||
r: out,
|
||||
out.FS = FS{
|
||||
LowDir: rdr.Root,
|
||||
r: &out,
|
||||
}
|
||||
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))
|
||||
}
|
||||
|
||||
|
||||
+26
-10
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
const (
|
||||
squashfsURL = "https://darkstorm.tech/files/LinuxPATest.sfs"
|
||||
squashfsName = "LinuxPATest.sfs"
|
||||
squashfsName = "tensorflow.sqfs"
|
||||
)
|
||||
|
||||
func preTest(dir string) (fil *os.File, err error) {
|
||||
@@ -68,6 +68,24 @@ func TestMisc(t *testing.T) {
|
||||
// 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) {
|
||||
tmpDir := "testing"
|
||||
fil, err := preTest(tmpDir)
|
||||
@@ -79,19 +97,17 @@ func BenchmarkRace(b *testing.B) {
|
||||
os.RemoveAll(libPath)
|
||||
os.RemoveAll(unsquashPath)
|
||||
var libTime, unsquashTime time.Duration
|
||||
op := FastOptions()
|
||||
op.IgnorePerm = true
|
||||
start := time.Now()
|
||||
rdr, err := NewReader(fil)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
err = rdr.ExtractWithOptions(libPath, op)
|
||||
err = rdr.ExtractWithOptions(libPath, FastOptions())
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
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.Stderr = os.Stderr
|
||||
start = time.Now()
|
||||
@@ -100,8 +116,8 @@ func BenchmarkRace(b *testing.B) {
|
||||
b.Log("Unsquashfs error:", err)
|
||||
}
|
||||
unsquashTime = time.Since(start)
|
||||
b.Log("Library took:", libTime.Round(time.Millisecond))
|
||||
b.Log("unsquashfs took:", unsquashTime.Round(time.Millisecond))
|
||||
// b.Log("Library took:", libTime.Round(time.Millisecond))
|
||||
// b.Log("unsquashfs took:", unsquashTime.Round(time.Millisecond))
|
||||
b.Log("unsquashfs is", strconv.FormatFloat(float64(libTime.Milliseconds())/float64(unsquashTime.Milliseconds()), 'f', 2, 64), "times faster")
|
||||
}
|
||||
|
||||
@@ -167,7 +183,7 @@ func TestExtractQuick(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var filePath = "Start.exe"
|
||||
var filePath = "usr/sbin/add-shell"
|
||||
|
||||
func TestSingleFile(t *testing.T) {
|
||||
tmpDir := "testing"
|
||||
@@ -175,7 +191,7 @@ func TestSingleFile(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
os.Remove(filepath.Join(tmpDir, filePath))
|
||||
os.RemoveAll("testing/stuff")
|
||||
rdr, err := NewReader(fil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -186,7 +202,7 @@ func TestSingleFile(t *testing.T) {
|
||||
}
|
||||
op := DefaultOptions()
|
||||
op.Verbose = true
|
||||
err = f.(*File).ExtractWithOptions("testing", op)
|
||||
err = f.(*File).ExtractWithOptions("testing/stuff", op)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user