Compare commits

...

27 Commits

Author SHA1 Message Date
Caleb Gardner f242de2710 Better disabling of compression types 2025-03-17 06:53:29 -05:00
Caleb Gardner 88315ee384 Fix build flags 2025-03-17 06:28:31 -05:00
Caleb Gardner 1e2a8f4b75 go mod tidy 2025-03-17 06:22:24 -05:00
Caleb Gardner 863b03fb19 Updated README 2025-03-17 06:21:19 -05:00
Caleb Gardner d3f84344d1 Fix build flags in lzma.go & xz.go 2025-03-17 06:19:45 -05:00
Caleb Gardner ad24995b7b Change no_lzma and no_lzo to no_obsolete and no_gpl
Added build tags section to README
2025-03-17 06:16:25 -05:00
Caleb Gardner 638355ab71 Merge pull request #37 from afbjorklund/comp-none
Allow disabling lzo and lzma
2025-03-17 05:45:26 -05:00
Anders F Björklund 04d914d403 Allow disabling lzo and lzma
By setting the buildtags "no_lzo" and/or "no_lzma",
one can drop the library dependency on lzo and lzma.

The same could be done for xz as well, but there are
still lots of archives using xz compression out there.
2025-03-16 13:56:04 +01:00
Caleb Gardner 7323fe56f6 Merge pull request #35 from afbjorklund/list
Add list option to unsquashfs
2025-03-15 15:48:41 -05:00
Caleb Gardner 6286da31e1 Merge branch 'main' into list 2025-03-15 15:48:06 -05:00
Caleb Gardner 77c87a9653 Merge pull request #34 from afbjorklund/unsquashfs-offset
Allow mounting with an offset
2025-03-15 15:34:04 -05:00
Anders F Björklund e6b0b83dcb Add support for uid/gid 2025-03-15 17:50:02 +01:00
Anders F Björklund cef9090210 Add support for symlinks 2025-03-15 17:46:02 +01:00
Anders F Björklund 24a9457c6b Refactor: export FileInfo 2025-03-15 17:35:40 +01:00
Anders F Björklund e0c1309ed4 Add list option to unsquashfs 2025-03-15 17:32:27 +01:00
Anders F Björklund 8b475b6cc4 Allow mounting with an offset
When mounting squashfs images embedded in apptainer image,
using offset means we don't need to use a temporary copy.
2025-03-15 17:26:59 +01:00
Caleb Gardner 3a48a0bcdc Remove t.Fatal at end of single file test 2025-03-12 00:11:29 -05:00
Caleb Gardner f11416493e Apply FileMode fixes to Inode.Mode() 2025-03-12 00:09:02 -05:00
Caleb Gardner 619bb023b1 Fix missing fileInfo.Mode() types 2025-03-12 00:03:58 -05:00
Caleb Gardner 38e4761d21 Merge pull request #33 from afbjorklund/fileinfo-symlink
Properly show symlinks in Mode
2025-03-11 23:51:01 -05:00
Anders F Björklund 06d2ef3056 Properly show symlinks in Mode
Previously they were extracted OK (as symlinks), but shown
as regular files with length 0 when getting the file info.
2025-03-11 18:46:31 +01:00
Caleb Gardner 446f29df70 Empty io.Reader buffer on EOF 2025-03-04 04:33:47 -06:00
Caleb Gardner d6c8efcfe6 Removed writeToWriteAt
Didn't seem to have any performance advantage
2025-03-04 04:08:13 -06:00
Caleb Gardner d890932d5c Use WriterAt if it's available for FullReader 2025-02-27 07:19:04 -06:00
Caleb Gardner 87b5ac7f5d gopls modernize 2025-02-27 02:46:22 -06:00
Caleb Gardner e9fdd89c67 Merge pull request #31 from willmurphyscode/main
fix: remove stray println
2024-12-10 16:09:45 -06:00
Will Murphy c80d150fdc fix: remove stray println 2024-12-10 17:00:57 -05:00
17 changed files with 341 additions and 105 deletions
+4
View File
@@ -11,6 +11,10 @@ Currently has support for reading squashfs files and extracting files and folder
Special thanks to <https://dr-emann.github.io/squashfs/> for some VERY important information in an easy to understand format.
Thanks also to [distri's squashfs library](https://github.com/distr1/distri/tree/master/internal/squashfs) as I referenced it to figure some things out (and double check others).
## Build tags
As of `v1.1.0` this library has two optional build tags: `no_gpl` and `no_obsolete`. `no_gpl` disables the ability to read archives with lzo compression due to the library's gpl license. `no_obsolete` removes "obsolete" compression types for a reduced compilation size; currently this only disable lzma compression since it's superseded by xz.
## FUSE
As of `v1.0`, FUSE capabilities has been moved to [a separate library](https://github.com/CalebQ42/squashfuse).
+64 -1
View File
@@ -3,14 +3,62 @@ package main
import (
"flag"
"fmt"
"io/fs"
"os"
"os/user"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/CalebQ42/squashfs"
)
func userName(uid int, numeric bool) string {
us := strconv.Itoa(uid)
if numeric {
return us
}
if u, err := user.LookupId(us); err == nil {
return u.Username
}
return us
}
func groupName(gid int, numeric bool) string {
gs := strconv.Itoa(gid)
if numeric {
return gs
}
if g, err := user.LookupGroupId(gs); err == nil {
return g.Name
}
return gs
}
func printEntry(root, path string, d fs.DirEntry, numeric bool) {
fi, _ := d.Info()
sfi := fi.(squashfs.FileInfo)
owner := fmt.Sprintf("%s/%s",
userName(sfi.Uid(), numeric),
groupName(sfi.Gid(), numeric))
link := ""
if sfi.IsSymlink() {
link = " -> " + sfi.SymlinkPath()
}
fmt.Printf("%s %s %*d %s %s%s\n",
strings.ToLower(fi.Mode().String()),
owner, 26-len(owner), fi.Size(),
fi.ModTime().Format("2006-01-02 15:04"),
filepath.Join(root, path), link)
}
func main() {
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")
flag.Parse()
if len(flag.Args()) < 2 {
@@ -21,10 +69,25 @@ func main() {
if err != nil {
panic(err)
}
r, err := squashfs.NewReader(f)
r, err := squashfs.NewReaderAtOffset(f, *offset)
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
})
return
}
op := squashfs.DefaultOptions()
op.Verbose = *verbose
op.IgnorePerm = *ignore
+18 -8
View File
@@ -127,7 +127,7 @@ func (f *File) ReadDir(n int) ([]fs.DirEntry, error) {
}
}
var out []fs.DirEntry
var fi fileInfo
var fi FileInfo
for _, e := range d.Entries[start:end] {
fi, err = f.r.newFileInfo(e)
if err != nil {
@@ -142,7 +142,15 @@ func (f *File) ReadDir(n int) ([]fs.DirEntry, error) {
// Returns the file's fs.FileInfo
func (f *File) Stat() (fs.FileInfo, error) {
return newFileInfo(f.b.Name, &f.b.Inode), nil
uid, err := f.b.Uid(&f.r.Low)
if err != nil {
return nil, err
}
gid, err := f.b.Gid(&f.r.Low)
if err != nil {
return nil, err
}
return newFileInfo(f.b.Name, uid, gid, &f.b.Inode), nil
}
// SymlinkPath returns the symlink's target path. Is the File isn't a symlink, returns an empty string.
@@ -179,9 +187,10 @@ func (f *File) initializeReaders() error {
func (f *File) deviceDevices() (maj uint32, min uint32) {
var dev uint32
if f.b.Inode.Type == inode.Char || f.b.Inode.Type == inode.Block {
switch f.b.Inode.Type {
case inode.Char, inode.Block:
dev = f.b.Inode.Data.(inode.Device).Dev
} else if f.b.Inode.Type == inode.EChar || f.b.Inode.Type == inode.EBlock {
case inode.EChar, inode.EBlock:
dev = f.b.Inode.Data.(inode.EDevice).Dev
}
return dev >> 8, dev & 0x000FF
@@ -266,7 +275,7 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
}(b, path)
}
var errCache []error
for i := 0; i < len(d.Entries); i++ {
for range d.Entries {
err := <-errChan
if err != nil {
errCache = append(errCache, err)
@@ -363,11 +372,12 @@ func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
}
path = filepath.Join(path, f.b.Name)
var typ string
if f.b.Inode.Type == inode.Char || f.b.Inode.Type == inode.EChar {
switch f.b.Inode.Type {
case inode.Char, inode.EChar:
typ = "c"
} else if f.b.Inode.Type == inode.Block || f.b.Inode.Type == inode.EBlock {
case inode.Block, inode.EBlock:
typ = "b"
} else { //Fifo IPC
default: //Fifo IPC
if runtime.GOOS == "darwin" {
if op.Verbose {
log.Println(f.path(), "ignored. A Fifo file and can't be created on Darwin.")
+74 -16
View File
@@ -8,61 +8,119 @@ import (
"github.com/CalebQ42/squashfs/low/inode"
)
type fileInfo struct {
type FileInfo struct {
name string
uid uint32
gid uint32
size int64
target string
perm uint32
modTime uint32
fileType uint16
}
func (r Reader) newFileInfo(e directory.Entry) (fileInfo, error) {
i, err := r.Low.InodeFromEntry(e)
func (r Reader) newFileInfo(e directory.Entry) (FileInfo, error) {
b, err := r.Low.BaseFromEntry(e)
if err != nil {
return fileInfo{}, err
return FileInfo{}, err
}
return newFileInfo(e.Name, &i), nil
uid, err := b.Uid(&r.Low)
if err != nil {
return FileInfo{}, err
}
gid, err := b.Gid(&r.Low)
if err != nil {
return FileInfo{}, err
}
return newFileInfo(e.Name, uid, gid, &b.Inode), nil
}
func newFileInfo(name string, i *inode.Inode) fileInfo {
func newFileInfo(name string, uid, gid uint32, i *inode.Inode) FileInfo {
var size int64
if i.Type == inode.Fil {
var target string
switch i.Type {
case inode.Fil:
size = int64(i.Data.(inode.File).Size)
} else if i.Type == inode.EFil {
case inode.EFil:
size = int64(i.Data.(inode.EFile).Size)
case inode.Sym:
target = string(i.Data.(inode.Symlink).Target)
case inode.ESym:
target = string(i.Data.(inode.ESymlink).Target)
}
return fileInfo{
return FileInfo{
name: name,
uid: uid,
gid: gid,
size: size,
target: target,
perm: uint32(i.Perm),
modTime: i.ModTime,
fileType: i.Type,
}
}
func (f fileInfo) Name() string {
func (f FileInfo) Name() string {
return f.name
}
func (f fileInfo) Size() int64 {
func (f FileInfo) Uid() int {
return int(f.uid)
}
func (f FileInfo) Gid() int {
return int(f.gid)
}
func (f FileInfo) Size() int64 {
return f.size
}
func (f fileInfo) Mode() fs.FileMode {
if f.IsDir() {
func (f FileInfo) SymlinkPath() string {
return f.target
}
func (f FileInfo) Mode() fs.FileMode {
switch f.fileType {
case inode.Dir, inode.EDir:
return fs.FileMode(f.perm | uint32(fs.ModeDir))
case inode.Sym, inode.ESym:
return fs.FileMode(f.perm | uint32(fs.ModeSymlink))
case inode.Char, inode.EChar, inode.Block, inode.EBlock:
return fs.FileMode(f.perm | uint32(fs.ModeDevice))
case inode.Fifo, inode.EFifo:
return fs.FileMode(f.perm | uint32(fs.ModeNamedPipe))
case inode.Sock, inode.ESock:
return fs.FileMode(f.perm | uint32(fs.ModeSocket))
}
return fs.FileMode(f.perm)
}
func (f fileInfo) ModTime() time.Time {
func (f FileInfo) ModTime() time.Time {
return time.Unix(int64(f.modTime), 0)
}
func (f fileInfo) IsDir() bool {
func (f FileInfo) IsDir() bool {
return f.fileType == inode.Dir || f.fileType == inode.EDir
}
func (f fileInfo) Sys() any {
func (f FileInfo) IsSymlink() bool {
return f.fileType == inode.Sym || f.fileType == inode.ESym
}
func (f FileInfo) IsDevice() bool {
return f.fileType == inode.Block || f.fileType == inode.EBlock ||
f.fileType == inode.Char || f.fileType == inode.EChar
}
func (f FileInfo) IsFifo() bool {
return f.fileType == inode.Fifo || f.fileType == inode.EFifo
}
func (f FileInfo) IsSocket() bool {
return f.fileType == inode.Sock || f.fileType == inode.ESock
}
func (f FileInfo) Sys() any {
return nil
}
+2 -2
View File
@@ -42,7 +42,7 @@ func (f *FS) Glob(pattern string) (out []string, err error) {
}
}
split := strings.Split(pattern, "/")
for i := 0; i < len(f.d.Entries); i++ {
for i := range f.d.Entries {
if match, _ := path.Match(split[0], f.d.Entries[i].Name); match {
if len(split) == 1 {
out = append(out, f.d.Entries[i].Name)
@@ -80,7 +80,7 @@ func (f *FS) Glob(pattern string) (out []string, err error) {
Err: err,
}
}
for i := 0; i < len(subGlob); i++ {
for i := range subGlob {
subGlob[i] = f.d.Name + "/" + subGlob[i]
}
out = append(out, subGlob...)
+3 -3
View File
@@ -1,10 +1,10 @@
module github.com/CalebQ42/squashfs
go 1.22.5
go 1.24.0
require (
github.com/klauspost/compress v1.17.9
github.com/pierrec/lz4/v4 v4.1.21
github.com/klauspost/compress v1.18.0
github.com/pierrec/lz4/v4 v4.1.22
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e
github.com/therootcompany/xz v1.0.1
github.com/ulikunitz/xz v0.5.12
+4 -4
View File
@@ -1,7 +1,7 @@
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e h1:dCWirM5F3wMY+cmRda/B1BiPsFtmzXqV9b0hLWtVBMs=
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e/go.mod h1:9leZcVcItj6m9/CfHY5Em/iBrCz7js8LcRQGTKEEv2M=
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
+6
View File
@@ -1,3 +1,5 @@
//go:build !no_obsolete
package decompress
import (
@@ -9,6 +11,10 @@ import (
type Lzma struct{}
func NewLzma() (Lzma, error) {
return Lzma{}, nil
}
func (l Lzma) Decompress(data []byte) ([]byte, error) {
rdr, err := lzma.NewReader(bytes.NewReader(data))
if err != nil {
+17
View File
@@ -0,0 +1,17 @@
//go:build no_obsolete
package decompress
import (
"errors"
)
type Lzma struct{}
func NewLzma() (Lzma, error) {
return Lzma{}, errors.New("lzma compression is disable in this build with no_obsolete")
}
func (l Lzma) Decompress(data []byte) ([]byte, error) {
return nil, errors.New("lzma compression is disable in this build with no_obsolete")
}
+6
View File
@@ -1,3 +1,5 @@
//go:build !no_gpl
package decompress
import (
@@ -8,6 +10,10 @@ import (
type Lzo struct{}
func NewLzo() (Lzo, error) {
return Lzo{}, nil
}
func (l Lzo) Decompress(data []byte) ([]byte, error) {
return lzo.Decompress1X(bytes.NewReader(data), len(data), 0)
}
+15
View File
@@ -0,0 +1,15 @@
//go:build no_gpl
package decompress
import "errors"
type Lzo struct{}
func NewLzo() (Lzo, error) {
return Lzo{}, errors.New("lzo compression is disable in this build with no_gpl")
}
func (l Lzo) Decompress(data []byte) ([]byte, error) {
return nil, errors.New("lzo compression is disable in this build with no_gpl")
}
+1 -4
View File
@@ -50,10 +50,7 @@ func (r *Reader) Read(b []byte) (int, error) {
return curRead, err
}
}
toRead = len(b) - curRead
if toRead > len(r.dat)-int(r.curOffset) {
toRead = len(r.dat) - int(r.curOffset)
}
toRead = min(len(b)-curRead, len(r.dat)-int(r.curOffset))
copy(b[curRead:], r.dat[r.curOffset:int(r.curOffset)+toRead])
r.curOffset += uint16(toRead)
curRead += toRead
+90 -16
View File
@@ -18,7 +18,6 @@ type FullReader struct {
r io.ReaderAt
d decompress.Decompressor
frag FragReaderConstructor
retPool *sync.Pool
sizes []uint32
initialOffset int64
finalBlockSize uint64
@@ -35,11 +34,6 @@ func NewFullReader(r io.ReaderAt, initialOffset int64, d decompress.Decompressor
goroutineLimit: uint16(runtime.NumCPU()),
finalBlockSize: finalBlockSize,
blockSize: blockSize,
retPool: &sync.Pool{
New: func() any {
return &retValue{}
},
},
}
}
@@ -48,6 +42,9 @@ func (r *FullReader) AddFrag(frag FragReaderConstructor) {
}
func (r *FullReader) SetGoroutineLimit(limit uint16) {
if limit <= 0 {
r.goroutineLimit = 1
}
r.goroutineLimit = limit
}
@@ -57,8 +54,8 @@ type retValue struct {
index uint64
}
func (r *FullReader) process(index uint64, fileOffset uint64, retChan chan *retValue) {
ret := r.retPool.Get().(*retValue)
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)
if realSize == 0 {
@@ -79,7 +76,10 @@ func (r *FullReader) process(index uint64, fileOffset uint64, retChan chan *retV
retChan <- ret
}
func (r *FullReader) WriteTo(w io.Writer) (int64, error) {
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
@@ -87,14 +87,16 @@ func (r *FullReader) WriteTo(w io.Writer) (int64, error) {
cache := make(map[uint64]*retValue)
var errCache []error
retChan := make(chan *retValue, r.goroutineLimit)
pool := &sync.Pool{
New: func() any {
return &retValue{}
},
}
for i := uint64(0); i < uint64(math.Ceil(float64(len(r.sizes))/float64(r.goroutineLimit))); i++ {
toProcess = uint16(len(r.sizes)) - (uint16(i) * r.goroutineLimit)
if toProcess > r.goroutineLimit {
toProcess = r.goroutineLimit
}
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, retChan)
go r.process((i*uint64(r.goroutineLimit))+uint64(j), curOffset, pool, retChan)
curOffset += uint64(r.sizes[(i*uint64(r.goroutineLimit))+uint64(j)]) &^ (1 << 24)
}
// Then consume the results on retChan
@@ -128,7 +130,7 @@ func (r *FullReader) WriteTo(w io.Writer) (int64, error) {
}
continue
}
r.retPool.Put(res)
pool.Put(res)
curIndex++
// Now we recursively try to clear the cache
for len(cache) > 0 {
@@ -146,7 +148,7 @@ func (r *FullReader) WriteTo(w io.Writer) (int64, error) {
break
}
delete(cache, curIndex)
r.retPool.Put(res)
pool.Put(res)
curIndex++
}
}
@@ -172,3 +174,75 @@ func (r *FullReader) WriteTo(w io.Writer) (int64, error) {
}
return wrote, nil
}
// func (r FullReader) writeToWriteAt(w io.WriterAt) (out int64, outErr error) {
// wait := &sync.WaitGroup{}
// wait.Add(len(r.sizes))
// mgr := routinemanager.NewManager(r.goroutineLimit)
// curOffset := r.initialOffset
// for i := uint64(0); i < uint64(len(r.sizes)); i++ {
// go func(index uint64, fileOffset int64) {
// lckNum := mgr.Lock()
// defer mgr.Unlock(lckNum)
// defer wait.Done()
// realSize := r.sizes[index] &^ (1 << 24)
// if realSize == 0 {
// if index == uint64(len(r.sizes))-1 && r.frag == nil {
// _, err := w.WriteAt([]byte{0}, int64((uint64(r.blockSize)*index)+r.finalBlockSize)-1)
// if err != nil {
// outErr = errors.Join(outErr, err)
// return
// }
// out = max(out, int64((uint64(r.blockSize)*index)+r.finalBlockSize))
// }
// return
// }
// data := make([]byte, realSize)
// err := binary.Read(toreader.NewReader(r.r, int64(fileOffset)), binary.LittleEndian, &data)
// if err != nil {
// outErr = errors.Join(outErr, err)
// return
// }
// if r.sizes[index] == realSize {
// data, err = r.d.Decompress(data)
// }
// if err != nil {
// outErr = errors.Join(outErr, err)
// return
// }
// _, err = w.WriteAt(data, int64(uint64(r.blockSize)*index))
// if err != nil {
// outErr = errors.Join(outErr, err)
// return
// }
// out = max(out, int64(uint64(r.blockSize)*(index+1)))
// }(i, curOffset)
// curOffset += int64(r.sizes[i]) &^ (1 << 24)
// }
// if r.frag != nil {
// wait.Add(1)
// go func() {
// lckNum := mgr.Lock()
// defer mgr.Unlock(lckNum)
// defer wait.Done()
// rdr, err := r.frag()
// if err != nil {
// outErr = errors.Join(outErr, err)
// return
// }
// dat, err := io.ReadAll(rdr)
// if err != nil {
// outErr = errors.Join(outErr, err)
// return
// }
// _, err = w.WriteAt(dat, int64(int(r.blockSize)*len(r.sizes)))
// if err != nil {
// outErr = errors.Join(outErr, err)
// return
// }
// out = int64(int(r.blockSize)*len(r.sizes)) + int64(r.finalBlockSize)
// }()
// }
// wait.Wait()
// return
// }
+2 -4
View File
@@ -41,6 +41,7 @@ func (r *Reader) advance() error {
r.dat, err = io.ReadAll(r.frag)
return err
} else if r.curIndex >= uint64(len(r.sizes)) {
r.dat = []byte{}
return io.EOF
}
realSize := r.sizes[r.curIndex] &^ (1 << 24)
@@ -73,10 +74,7 @@ func (r *Reader) Read(b []byte) (int, error) {
return curRead, err
}
}
toRead = len(b) - curRead
if toRead > len(r.dat)-r.curOffset {
toRead = len(r.dat) - r.curOffset
}
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
+7 -13
View File
@@ -81,23 +81,17 @@ func Read(r io.Reader, blockSize uint32) (i Inode, err error) {
func (i Inode) Mode() (out fs.FileMode) {
out = fs.FileMode(i.Perm)
switch i.Data.(type) {
case Directory:
switch i.Type {
case Dir, EDir:
out |= fs.ModeDir
case EDirectory:
out |= fs.ModeDir
case Symlink:
case Sym, ESym:
out |= fs.ModeSymlink
case ESymlink:
out |= fs.ModeSymlink
case Device:
case Char, EChar, Block, EBlock:
out |= fs.ModeDevice
case EDevice:
out |= fs.ModeDevice
case IPC:
out |= fs.ModeNamedPipe
case EIPC:
case Fifo, EFifo:
out |= fs.ModeNamedPipe
case Sock, ESock:
out |= fs.ModeSocket
}
return
}
+12 -17
View File
@@ -3,7 +3,6 @@ package squashfslow
import (
"encoding/binary"
"errors"
"fmt"
"io"
"math"
@@ -60,15 +59,21 @@ func NewReader(r io.ReaderAt) (rdr *Reader, err error) {
case ZlibCompression:
rdr.d = decompress.Zlib{}
case LZMACompression:
rdr.d = decompress.Lzma{}
rdr.d, err = decompress.NewLzma()
if err != nil {
return nil, err
}
case LZOCompression:
rdr.d = decompress.Lzo{}
rdr.d, err = decompress.NewLzo()
if err != nil {
return nil, err
}
case XZCompression:
rdr.d = decompress.Xz{}
case LZ4Compression:
rdr.d = decompress.Lz4{}
case ZSTDCompression:
rdr.d = &decompress.Zstd{}
rdr.d = decompress.Zstd{}
default:
return nil, errors.New("invalid compression type. possible corrupted archive")
}
@@ -106,10 +111,7 @@ func (r *Reader) Id(i uint16) (uint32, error) {
if err != nil {
return 0, err
}
idsToRead = r.Superblock.IdCount - uint16(len(r.idTable))
if idsToRead > 2048 {
idsToRead = 2048
}
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)
@@ -136,7 +138,6 @@ func (r *Reader) fragEntry(i uint32) (fragEntry, error) {
} else {
blockNum = 0
}
fmt.Println(blockNum)
blocksRead := len(r.fragTable) / 512
blocksToRead := int(blockNum) - blocksRead + 1
@@ -150,10 +151,7 @@ func (r *Reader) fragEntry(i uint32) (fragEntry, error) {
if err != nil {
return fragEntry{}, err
}
fragsToRead = r.Superblock.FragCount - uint32(len(r.fragTable))
if fragsToRead > 512 {
fragsToRead = 512
}
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)
@@ -196,10 +194,7 @@ func (r *Reader) inodeRef(i uint32) (uint64, error) {
if err != nil {
return 0, err
}
refsToRead = r.Superblock.InodeCount - uint32(len(r.exportTable))
if refsToRead > 1024 {
refsToRead = 1024
}
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)
+16 -17
View File
@@ -1,4 +1,4 @@
package squashfs_test
package squashfs
//Actually proper tests go here.
@@ -13,13 +13,11 @@ import (
"strconv"
"testing"
"time"
"github.com/CalebQ42/squashfs"
)
const (
squashfsURL = "https://darkstorm.tech/files/LinuxPATest.sfs"
squashfsName = "airootfs.sfs"
squashfsName = "LinuxPATest.sfs"
)
func preTest(dir string) (fil *os.File, err error) {
@@ -61,7 +59,7 @@ func TestMisc(t *testing.T) {
if err != nil {
t.Fatal(err)
}
rdr, err := squashfs.NewReader(fil)
rdr, err := NewReader(fil)
if err != nil {
t.Fatal(err)
}
@@ -81,10 +79,10 @@ func BenchmarkRace(b *testing.B) {
os.RemoveAll(libPath)
os.RemoveAll(unsquashPath)
var libTime, unsquashTime time.Duration
op := squashfs.FastOptions()
op := FastOptions()
op.IgnorePerm = true
start := time.Now()
rdr, err := squashfs.NewReader(fil)
rdr, err := NewReader(fil)
if err != nil {
b.Fatal(err)
}
@@ -102,15 +100,15 @@ 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.Fatal("unsquashfs is", strconv.FormatFloat(float64(libTime.Milliseconds())/float64(unsquashTime.Milliseconds()), 'f', 2, 64), "times faster")
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")
}
func TestExtractQuick(t *testing.T) {
//First, setup everything and extract the archive using the library and unsquashfs
// tmpDir := b.TempDir()
// tmpDir := bTempDir()
tmpDir := "testing"
fil, err := preTest(tmpDir)
if err != nil {
@@ -120,13 +118,13 @@ func TestExtractQuick(t *testing.T) {
unsquashPath := filepath.Join(tmpDir, "ExtractSquashfs")
os.RemoveAll(libPath)
os.RemoveAll(unsquashPath)
rdr, err := squashfs.NewReader(fil)
rdr, err := NewReader(fil)
if err != nil {
t.Fatal(err)
}
os.RemoveAll(filepath.Join(tmpDir, "testLog.txt"))
logFil, _ := os.Create(filepath.Join(tmpDir, "testLog.txt"))
op := squashfs.DefaultOptions()
op := FastOptions()
op.Verbose = true
op.IgnorePerm = true
op.LogOutput = logFil
@@ -169,7 +167,7 @@ func TestExtractQuick(t *testing.T) {
}
}
var filePath = "bin"
var filePath = "Start.exe"
func TestSingleFile(t *testing.T) {
tmpDir := "testing"
@@ -178,7 +176,7 @@ func TestSingleFile(t *testing.T) {
t.Fatal(err)
}
os.Remove(filepath.Join(tmpDir, filePath))
rdr, err := squashfs.NewReader(fil)
rdr, err := NewReader(fil)
if err != nil {
t.Fatal(err)
}
@@ -186,9 +184,10 @@ func TestSingleFile(t *testing.T) {
if err != nil {
t.Fatal(err)
}
err = f.(*squashfs.File).ExtractWithOptions("testing", &squashfs.ExtractionOptions{Verbose: true})
op := DefaultOptions()
op.Verbose = true
err = f.(*File).ExtractWithOptions("testing", op)
if err != nil {
t.Fatal(err)
}
t.Fatal("HI")
}