Compare commits

...

14 Commits

Author SHA1 Message Date
Caleb Gardner 76649fde7f Implemented WriteTo which halves decompress times.
Added a drag race benchmark (for the fun of it)
2021-01-10 03:33:33 -06:00
Caleb Gardner ee9406513c Added a new test for fun to compare vs unsquashfs 2021-01-09 10:15:56 -06:00
Caleb Gardner 18092c63aa Some cleanup, no change in functionality 2021-01-09 09:53:58 -06:00
Caleb Gardner b2d6ff56f6 Added uid/guid support.
Added permission support.
2021-01-09 02:30:04 -06:00
Caleb Gardner 4b3d5d12f8 Setup for differnet types of compression for Writer
Added some TODOs
2021-01-08 14:35:32 -06:00
Caleb Gardner 3f71404a2a Re-Write of the Writer to make it simpler 2021-01-08 05:09:38 -06:00
Caleb Gardner 35d22b4bd0 Removed close function from squashfs.File
This seems to cause issues in ver specific circumstances and ultimately, isn't needed.
2021-01-06 12:56:09 -06:00
Caleb Gardner 97b12090c6 Trying to figure out the best way to convert files 2021-01-05 05:53:16 -06:00
Caleb Gardner 43fe4f91a2 More work on Writer
Adding files to the Writer should work properly now (except symlinks)
Threaded the adding of files.
Added ability to ignore errors when adding files.
2021-01-03 04:39:39 -06:00
Caleb Gardner 9524a2c192 More work on Writer 2021-01-02 17:10:21 -06:00
Caleb Gardner 162b228881 Some beginning work on squashfs.Writer
Just laying out some of the functions and what they do.
2021-01-01 10:54:09 -06:00
Caleb Gardner 7f87999a8f Merge branch 'main' of https://github.com/CalebQ42/squashfs into main 2020-12-28 11:51:08 -06:00
Caleb Gardner 47c28baf87 Improved data structure for structs.
Thanks gopls with VS Code
2020-12-28 11:50:56 -06:00
Caleb Gardner a298a3d7b5 Fixed first byte of data blocks being cut off 2020-12-27 02:10:51 -06:00
22 changed files with 784 additions and 304 deletions
+6 -2
View File
@@ -8,7 +8,11 @@ Currently has support for reading squashfs files and extracting files and folder
The only major thing missing from squashfs reading is Xattr parsing.
Special thanks to https://dr-emann.github.io/squashfs/ for some VERY important information in an easy to understand format.
Special thanks to <https://dr-emann.github.io/squashfs/> for some VERY important information in an easy to understand format.
Thanks also to [distri's squashfs library](https://github.com/distr1/distri/tree/master/internal/squashfs) as I referenced it to figure some things out (and double check others).
# [TODO](https://github.com/CalebQ42/squashfs/projects/1?fullscreen=true)
## Performane
This library, decompressing the firefox AppImage and using go tests, takes about twice as long as `unsquashfs` on my quad core laptop. (~1 second with the libarary and about half a second with `unsquashfs`)
## [TODO](https://github.com/CalebQ42/squashfs/projects/1?fullscreen=true)
+17
View File
@@ -0,0 +1,17 @@
package squashfs
import "reflect"
func (w *Writer) compressData(data []byte) ([]byte, error) {
if reflect.DeepEqual(data, make([]byte, len(data))) {
return nil, nil
}
compressedData, err := w.compressor.Compress(data)
if err != nil {
return nil, err
}
if len(data) <= len(compressedData) {
return data, nil
}
return compressedData, nil
}
+120 -63
View File
@@ -18,29 +18,11 @@ var (
//DataReader reads data from data blocks.
type dataReader struct {
r *Reader
offset int64 //offset relative to the beginning of the squash file
blocks []dataBlock
curBlock int //Which block in sizes is currently cached
curData []byte
curReadOffset int //offset relative to the currently cached data
}
//DataBlock holds info about a given data block from it's size
type dataBlock struct {
begOffset int64 //The offset relative to the beginning of the squash file. Makes it easier to seek to it.
size uint32
compressed bool
uncompressedSize uint32
}
//NewDataBlock creates a new squashfs.datablock from a given size.
func newDataBlock(raw uint32) (dbs dataBlock) {
dbs.compressed = raw&(1<<24) != (1 << 24)
dbs.size = raw &^ (1 << 24)
if !dbs.compressed {
dbs.uncompressedSize = dbs.size
}
return
sizes []uint32
offset int64 //offset relative to the beginning of the squash file
curBlock int //Which block in sizes is currently cached
curReadOffset int //offset relative to the currently cached data
}
//NewDataReader creates a new data reader at the given offset, with the blocks defined by sizes
@@ -48,9 +30,7 @@ func (r *Reader) newDataReader(offset int64, sizes []uint32) (*dataReader, error
var dr dataReader
dr.r = r
dr.offset = offset
for _, size := range sizes {
dr.blocks = append(dr.blocks, newDataBlock(size))
}
dr.sizes = sizes
err := dr.readCurBlock()
if err != nil {
return nil, err
@@ -63,29 +43,29 @@ func (r *Reader) newDataReaderFromInode(i *inode.Inode) (*dataReader, error) {
var rdr dataReader
rdr.r = r
switch i.Type {
case inode.BasicFileType:
fil := i.Info.(inode.BasicFile)
if fil.Init.BlockStart == 0 {
case inode.FileType:
fil := i.Info.(inode.File)
if fil.BlockStart == 0 {
return nil, errInodeOnlyFragment
}
rdr.offset = int64(fil.Init.BlockStart)
rdr.offset = int64(fil.BlockStart)
for _, sizes := range fil.BlockSizes {
rdr.blocks = append(rdr.blocks, newDataBlock(sizes))
rdr.sizes = append(rdr.sizes, sizes)
}
if fil.Fragmented {
rdr.blocks = rdr.blocks[:len(rdr.blocks)-1]
rdr.sizes = rdr.sizes[:len(rdr.sizes)-1]
}
case inode.ExtFileType:
fil := i.Info.(inode.ExtendedFile)
if fil.Init.BlockStart == 0 {
fil := i.Info.(inode.ExtFile)
if fil.BlockStart == 0 {
return nil, errInodeOnlyFragment
}
rdr.offset = int64(fil.Init.BlockStart)
rdr.offset = int64(fil.BlockStart)
for _, sizes := range fil.BlockSizes {
rdr.blocks = append(rdr.blocks, newDataBlock(sizes))
rdr.sizes = append(rdr.sizes, sizes)
}
if fil.Fragmented {
rdr.blocks = rdr.blocks[:len(rdr.blocks)-1]
rdr.sizes = rdr.sizes[:len(rdr.sizes)-1]
}
default:
return nil, errInodeNotFile
@@ -97,9 +77,14 @@ func (r *Reader) newDataReaderFromInode(i *inode.Inode) (*dataReader, error) {
return &rdr, nil
}
//removed the compression bit from a data block size
func actualDataSize(size uint32) uint32 {
return size &^ (1 << 24)
}
func (d *dataReader) readNextBlock() error {
d.curBlock++
if d.curBlock >= len(d.blocks) {
if d.curBlock >= len(d.sizes) {
d.curBlock--
return io.EOF
}
@@ -112,42 +97,46 @@ func (d *dataReader) readNextBlock() error {
return nil
}
func (d *dataReader) readCurBlock() error {
if d.curBlock >= len(d.blocks) {
return io.EOF
func (d *dataReader) readBlockAt(offset int64, size uint32) ([]byte, error) {
compressed := size&(1<<24) != (1 << 24)
size = size &^ (1 << 24)
if d.sizes[d.curBlock] == 0 {
return make([]byte, d.r.super.BlockSize), nil
}
if d.blocks[d.curBlock].size == 0 {
d.curData = make([]byte, d.r.super.BlockSize)
d.blocks[d.curBlock].uncompressedSize = d.r.super.BlockSize
d.blocks[d.curBlock].begOffset = d.offset
return nil
}
sec := io.NewSectionReader(d.r.r, d.offset, int64(d.blocks[d.curBlock].size))
if d.blocks[d.curBlock].compressed {
sec := io.NewSectionReader(d.r.r, offset, int64(size))
if compressed {
btys, err := d.r.decompressor.Decompress(sec)
if err != nil {
return err
return nil, err
}
d.blocks[d.curBlock].uncompressedSize = uint32(len(btys))
d.curData = btys
d.blocks[d.curBlock].begOffset = d.offset
d.offset += int64(d.blocks[d.curBlock].size)
return nil
return btys, nil
}
var buf bytes.Buffer
_, err := io.Copy(&buf, sec)
if err != nil {
return err
return nil, err
}
d.curData = buf.Bytes()
d.blocks[d.curBlock].begOffset = d.offset
d.offset += int64(d.blocks[d.curBlock].size)
return err
return buf.Bytes(), nil
}
//Close frees up the curData from memory
func (d *dataReader) Close() error {
d.curData = nil
func (d *dataReader) offsetForBlock(index int) int64 {
out := d.offset
for i := 0; i < index; i++ {
out += int64(actualDataSize(d.sizes[i]))
}
return out
}
func (d *dataReader) readCurBlock() error {
if d.curBlock >= len(d.sizes) {
return io.EOF
}
offset := d.offsetForBlock(d.curBlock)
data, err := d.readBlockAt(offset, d.sizes[d.curBlock])
if err != nil {
return err
}
d.curData = data
return nil
}
@@ -175,12 +164,12 @@ func (d *dataReader) Read(p []byte) (int, error) {
d.curReadOffset = 0
}
for ; read < len(p); read++ {
d.curReadOffset++
if d.curReadOffset < len(d.curData) {
p[read] = d.curData[d.curReadOffset]
} else {
break
}
d.curReadOffset++
}
}
if read != len(p) {
@@ -188,3 +177,71 @@ func (d *dataReader) Read(p []byte) (int, error) {
}
return read, nil
}
// WriteTo writes all the data in the datablock to the writer. MUST BE USED ON A FRESH DATA READER.
func (d *dataReader) WriteTo(w io.Writer) (int64, error) {
type dataCache struct {
err error
data []byte
index int
}
dataChan := make(chan *dataCache)
for i := range d.sizes {
go func(index int, c chan *dataCache) {
var cache dataCache
cache.index = index
defer func() {
c <- &cache
}()
data, err := d.readBlockAt(d.offsetForBlock(index), d.sizes[index])
if err != nil {
cache.err = err
return
}
cache.data = data
return
}(i, dataChan)
}
curIndex := 0
totalWrite := int64(0)
var backlog []*dataCache
mainLoop:
for {
if curIndex == len(d.sizes) {
return totalWrite, nil
}
if len(backlog) > 0 {
for i, cache := range backlog {
if cache.index == curIndex {
writen, err := w.Write(cache.data)
totalWrite += int64(writen)
if err != nil {
return totalWrite, err
}
if len(backlog) > 0 {
backlog[i] = backlog[len(backlog)-1]
backlog = backlog[:len(backlog)-1]
} else {
backlog = nil
}
curIndex++
continue mainLoop
}
}
}
cache := <-dataChan
if cache.err != nil {
return totalWrite, cache.err
}
if cache.index == curIndex {
writen, err := w.Write(cache.data)
totalWrite += int64(writen)
if err != nil {
return totalWrite, err
}
} else {
backlog = append(backlog, cache)
}
curIndex++
}
}
+38 -43
View File
@@ -30,13 +30,14 @@ var (
//
//Implements os.FileInfo and io.ReadCloser
type File struct {
Parent *File //The parent directory. Should ALWAYS be a folder. If it's the root directory, will be nil
Reader io.Reader //Underlying reader. When writing, will probably be an os.File. When reading this is kept nil UNTIL reading to save memory.
name string //The name of the file or folder. Root folder will not have a name ("")
path string //The path to the folder the File is located in.
r *Reader //The squashfs.Reader where this file is contained.
in *inode.Inode //Underlyting inode when reading.
filType int //The file's type, using inode types.
Reader io.Reader
Parent *File
r *Reader //Underlying reader. When writing, will probably be an os.File. When reading this is kept nil UNTIL reading to save memory.
in *inode.Inode
name string
path string
filType int //The file's type, using inode types.
}
//get a File from a directory.entry
@@ -60,10 +61,10 @@ func (f *File) Name() string {
//Size is the complete size of the file. Zero if it's not a file.
func (f *File) Size() int64 {
switch f.filType {
case inode.BasicFileType:
return int64(f.in.Info.(inode.BasicFile).Init.Size)
case inode.FileType:
return int64(f.in.Info.(inode.File).Size)
case inode.ExtFileType:
return int64(f.in.Info.(inode.ExtendedFile).Init.Size)
return int64(f.in.Info.(inode.ExtFile).Size)
default:
return 0
}
@@ -203,27 +204,27 @@ func (f *File) GetFileAtPath(dirPath string) *File {
//IsDir returns if the file is a directory.
func (f *File) IsDir() bool {
return f.filType == inode.BasicDirectoryType || f.filType == inode.ExtDirType
return f.filType == inode.DirType || f.filType == inode.ExtDirType
}
//IsSymlink returns if the file is a symlink.
func (f *File) IsSymlink() bool {
return f.filType == inode.BasicSymlinkType || f.filType == inode.ExtSymlinkType
return f.filType == inode.SymType || f.filType == inode.ExtSymlinkType
}
//IsFile returns if the file is a file.
func (f *File) IsFile() bool {
return f.filType == inode.BasicFileType || f.filType == inode.ExtFileType
return f.filType == inode.FileType || f.filType == inode.ExtFileType
}
//SymlinkPath returns the path the symlink is pointing to. If the file ISN'T a symlink, will return an empty string.
//If a path begins with "/" then the symlink is pointing to an absolute path (starting from root, and not a file inside the archive)
func (f *File) SymlinkPath() string {
switch f.filType {
case inode.BasicSymlinkType:
return f.in.Info.(inode.BasicSymlink).Path
case inode.SymType:
return f.in.Info.(inode.Sym).Path
case inode.ExtSymlinkType:
return f.in.Info.(inode.ExtendedSymlink).Path
return f.in.Info.(inode.Sym).Path
default:
return ""
}
@@ -355,7 +356,6 @@ func (f *File) ExtractWithOptions(path string, dereferenceSymlink, unbreakSymlin
return
}
finishChan := make(chan []error)
defer close(finishChan)
for _, child := range children {
go func(child *File) {
if f.name == "" {
@@ -398,7 +398,18 @@ func (f *File) ExtractWithOptions(path string, dereferenceSymlink, unbreakSymlin
errs = append(errs, err)
return
} //Since we will be reading from the file
_, err = io.Copy(fil, f)
if f.Reader == nil && f.r != nil {
f.Reader, err = f.r.newFileReader(f.in)
if err != nil {
if verbose {
fmt.Println("Error while Copying data to:", path+"/"+f.name)
fmt.Println(err)
}
errs = append(errs, err)
return
}
}
_, err = io.Copy(fil, f.Reader)
if err != nil {
if verbose {
fmt.Println("Error while Copying data to:", path+"/"+f.name)
@@ -407,7 +418,6 @@ func (f *File) ExtractWithOptions(path string, dereferenceSymlink, unbreakSymlin
errs = append(errs, err)
return
}
f.Close()
fil.Chown(int(f.r.idTable[f.in.Header.UID]), int(f.r.idTable[f.in.Header.GID]))
//don't mention anything when it fails. Because it fails often. Probably has something to do about uid & gid 0
// if err != nil {
@@ -479,24 +489,9 @@ func (f *File) ExtractWithOptions(path string, dereferenceSymlink, unbreakSymlin
return
}
//Close frees up the memory held up by the underlying reader. Should NOT be called when writing.
//When reading, Close is safe to use, but any subsequent Read calls resets to the beginning of the file.
func (f *File) Close() error {
if f.IsDir() {
return errNotFile
}
if f.Reader != nil {
if closer, is := f.Reader.(io.Closer); is {
closer.Close()
}
f.Reader = nil
}
return nil
}
//Read from the file. Doesn't do anything fancy, just pases it to the underlying io.Reader. If a directory, return io.EOF.
func (f *File) Read(p []byte) (int, error) {
if f.IsDir() {
if !f.IsFile() {
return 0, io.EOF
}
var err error
@@ -516,14 +511,14 @@ func (r *Reader) readDirFromInode(i *inode.Inode) (*directory.Directory, error)
var metaOffset uint16
var size uint32
switch i.Type {
case inode.BasicDirectoryType:
offset = i.Info.(inode.BasicDirectory).DirectoryIndex
metaOffset = i.Info.(inode.BasicDirectory).DirectoryOffset
size = uint32(i.Info.(inode.BasicDirectory).DirectorySize)
case inode.DirType:
offset = i.Info.(inode.Dir).DirectoryIndex
metaOffset = i.Info.(inode.Dir).DirectoryOffset
size = uint32(i.Info.(inode.Dir).DirectorySize)
case inode.ExtDirType:
offset = i.Info.(inode.ExtendedDirectory).Init.DirectoryIndex
metaOffset = i.Info.(inode.ExtendedDirectory).Init.DirectoryOffset
size = i.Info.(inode.ExtendedDirectory).Init.DirectorySize
offset = i.Info.(inode.ExtDir).DirectoryIndex
metaOffset = i.Info.(inode.ExtDir).DirectoryOffset
size = i.Info.(inode.ExtDir).DirectorySize
default:
return nil, errors.New("Not a directory inode")
}
@@ -548,7 +543,7 @@ func (r *Reader) getInodeFromEntry(en *directory.Entry) (*inode.Inode, error) {
if err != nil {
return nil, err
}
_, err = br.Seek(int64(en.Init.Offset), io.SeekStart)
_, err = br.Seek(int64(en.Offset), io.SeekStart)
if err != nil {
return nil, err
}
+24 -17
View File
@@ -29,20 +29,20 @@ var (
func (r *Reader) newFileReader(in *inode.Inode) (*fileReader, error) {
var rdr fileReader
rdr.in = in
if in.Type != inode.BasicFileType && in.Type != inode.ExtFileType {
if in.Type != inode.FileType && in.Type != inode.ExtFileType {
return nil, errPathIsNotFile
}
switch in.Type {
case inode.BasicFileType:
fil := in.Info.(inode.BasicFile)
case inode.FileType:
fil := in.Info.(inode.File)
rdr.fragged = fil.Fragmented
rdr.fragOnly = fil.Init.BlockStart == 0
rdr.FileSize = int(fil.Init.Size)
rdr.fragOnly = fil.BlockStart == 0
rdr.FileSize = int(fil.Size)
case inode.ExtFileType:
fil := in.Info.(inode.ExtendedFile)
fil := in.Info.(inode.ExtFile)
rdr.fragged = fil.Fragmented
rdr.fragOnly = fil.Init.BlockStart == 0
rdr.FileSize = int(fil.Init.Size)
rdr.fragOnly = fil.BlockStart == 0
rdr.FileSize = int(fil.Size)
}
var err error
if rdr.fragged {
@@ -57,15 +57,6 @@ func (r *Reader) newFileReader(in *inode.Inode) (*fileReader, error) {
return &rdr, nil
}
//Close runs Close on the data reader and frees the fragmentdata
func (f *fileReader) Close() error {
if f.data != nil {
f.data.Close()
}
f.fragmentData = nil
return nil
}
func (f *fileReader) Read(p []byte) (int, error) {
if f.fragOnly {
n, err := bytes.NewBuffer(f.fragmentData[f.read:]).Read(p)
@@ -92,3 +83,19 @@ func (f *fileReader) Read(p []byte) (int, error) {
}
return read, nil
}
func (f *fileReader) WriteTo(w io.Writer) (int64, error) {
if f.fragOnly {
n, err := w.Write(f.fragmentData)
return int64(n), err
}
if !f.fragged {
return f.data.WriteTo(w)
}
n, err := f.data.WriteTo(w)
if err != nil {
return int64(n), err
}
nn, err := w.Write(f.fragmentData)
return int64(nn) + n, err
}
+11 -11
View File
@@ -21,30 +21,30 @@ func (r *Reader) getFragmentDataFromInode(in *inode.Inode) ([]byte, error) {
var size uint32
var fragIndex uint32
var fragOffset uint32
if in.Type == inode.BasicFileType {
bf := in.Info.(inode.BasicFile)
if in.Type == inode.FileType {
bf := in.Info.(inode.File)
if !bf.Fragmented {
return make([]byte, 0), nil
}
if bf.Init.BlockStart == 0 {
size = bf.Init.Size
if bf.BlockStart == 0 {
size = bf.Size
} else {
size = bf.BlockSizes[len(bf.BlockSizes)-1]
}
fragIndex = bf.Init.FragmentIndex
fragOffset = bf.Init.FragmentOffset
fragIndex = bf.FragmentIndex
fragOffset = bf.FragmentOffset
} else if in.Type == inode.ExtFileType {
bf := in.Info.(inode.ExtendedFile)
bf := in.Info.(inode.ExtFile)
if !bf.Fragmented {
return make([]byte, 0), nil
}
if bf.Init.BlockStart == 0 {
size = bf.Init.Size
if bf.BlockStart == 0 {
size = bf.Size
} else {
size = bf.BlockSizes[len(bf.BlockSizes)-1]
}
fragIndex = bf.Init.FragmentIndex
fragOffset = bf.Init.FragmentOffset
fragIndex = bf.FragmentIndex
fragOffset = bf.FragmentOffset
} else {
return nil, errors.New("Inode type not supported")
}
+3 -3
View File
@@ -7,11 +7,11 @@ require (
github.com/adrg/xdg v0.2.3 // indirect
github.com/google/go-cmp v0.5.4 // indirect
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
github.com/klauspost/compress v1.11.3
github.com/klauspost/compress v1.11.4
github.com/kr/text v0.2.0 // indirect
github.com/pierrec/lz4/v4 v4.1.1
github.com/pierrec/lz4/v4 v4.1.2
github.com/smartystreets/assertions v1.2.0 // indirect
github.com/ulikunitz/xz v0.5.8
github.com/ulikunitz/xz v0.5.9
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
+6 -6
View File
@@ -18,8 +18,8 @@ github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/compress v1.11.3 h1:dB4Bn0tN3wdCzQxnS8r06kV74qN/TAfaIS0bVE8h3jc=
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.4 h1:kz40R/YWls3iqT9zX9AHN3WoVsrAWVyui5sxuLqiXqU=
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -29,8 +29,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pierrec/lz4/v4 v4.1.1 h1:cS6aGkNLJr4u+UwaA21yp+gbWN3WJWtKo1axmPDObMA=
github.com/pierrec/lz4/v4 v4.1.1/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM=
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
@@ -42,8 +42,8 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
go.lsp.dev/uri v0.3.0 h1:KcZJmh6nFIBeJzTugn5JTU6OOyG0lDOo3R9KwTxTYbo=
go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
+21 -12
View File
@@ -15,23 +15,22 @@ type gzipInit struct {
//Gzip is a decompressor for gzip type compression. Uses zlib for compression and decompression
type Gzip struct {
CompressionLevel int32
HasCustomWindow bool
HasStrategies bool
wrt *zlib.Writer
gzipInit
HasCustomWindow bool
HasStrategies bool
}
//NewGzipCompressorWithOptions creates a new gzip compressor/decompressor with options read from the given reader.
func NewGzipCompressorWithOptions(r io.Reader) (*Gzip, error) {
var gzip Gzip
var init gzipInit
err := binary.Read(r, binary.LittleEndian, &init)
err := binary.Read(r, binary.LittleEndian, &gzip.gzipInit)
if err != nil {
return nil, err
}
gzip.CompressionLevel = init.CompressionLevel
//TODO: proper support for window size and strategies
gzip.HasCustomWindow = init.WindowSize != 15
gzip.HasStrategies = init.Strategies != 0 && init.Strategies != 1
gzip.HasCustomWindow = gzip.WindowSize != 15
gzip.HasStrategies = gzip.Strategies != 0 && gzip.Strategies != 1
return &gzip, nil
}
@@ -52,15 +51,25 @@ func (g *Gzip) Decompress(r io.Reader) ([]byte, error) {
//Compress compresses the given data (as a byte array) and returns the compressed data.
func (g *Gzip) Compress(data []byte) ([]byte, error) {
var buf bytes.Buffer
wrt := zlib.NewWriter(&buf)
defer wrt.Close()
_, err := wrt.Write(data)
var err error
if g.wrt == nil {
if g.CompressionLevel == 0 {
g.wrt = zlib.NewWriter(&buf)
} else {
g.wrt, err = zlib.NewWriterLevel(&buf, int(g.CompressionLevel))
if err != nil {
return nil, err
}
}
}
wrt, err := zlib.NewWriterLevel(&buf, int(g.CompressionLevel))
if err != nil {
return nil, err
}
err = wrt.Flush()
_, err = wrt.Write(data)
if err != nil {
return nil, err
}
wrt.Close()
return buf.Bytes(), nil
}
+18
View File
@@ -35,3 +35,21 @@ func (l *Lz4) Decompress(r io.Reader) ([]byte, error) {
_, err := io.Copy(&buf, rdr)
return buf.Bytes(), err
}
//Compress implements compression.Compress
func (l *Lz4) Compress(data []byte) ([]byte, error) {
var buf bytes.Buffer
w := lz4.NewWriter(&buf)
if l.HC {
err := w.Apply(lz4.CompressionLevelOption(lz4.Level9))
if err != nil {
return nil, err
}
}
_, err := w.Write(data)
if err != nil {
return nil, err
}
w.Close()
return buf.Bytes(), nil
}
+15
View File
@@ -23,3 +23,18 @@ func (l *Lzma) Decompress(rdr io.Reader) ([]byte, error) {
}
return buf.Bytes(), nil
}
//Compress implements compression.Compress
func (l *Lzma) Compress(data []byte) ([]byte, error) {
var buf bytes.Buffer
w, err := lzma.NewWriter(&buf)
if err != nil {
return nil, err
}
_, err = w.Write(data)
if err != nil {
return nil, err
}
w.Close()
return buf.Bytes(), nil
}
+20
View File
@@ -53,3 +53,23 @@ func (x *Xz) Decompress(rdr io.Reader) ([]byte, error) {
}
return buf.Bytes(), nil
}
//Compress implements compression.Compress
func (x *Xz) Compress(data []byte) ([]byte, error) {
var buf bytes.Buffer
w, err := xz.NewWriter(&buf)
if err != nil {
return nil, err
}
w.DictCap = int(x.DictionarySize)
err = w.Verify()
if err != nil {
return nil, err
}
_, err = w.Write(data)
if err != nil {
return nil, err
}
w.Close()
return buf.Bytes(), nil
}
+15
View File
@@ -34,3 +34,18 @@ func (z *Zstd) Decompress(r io.Reader) ([]byte, error) {
_, err = io.Copy(&buf, rdr)
return buf.Bytes(), err
}
//Compress impelements compression.Compress
func (z *Zstd) Compress(data []byte) ([]byte, error) {
var buf bytes.Buffer
w, err := zstd.NewWriter(&buf, zstd.WithEncoderLevel(zstd.EncoderLevel(z.CompressionLevel)))
if err != nil {
return nil, err
}
_, err = w.Write(data)
if err != nil {
return nil, err
}
w.Close()
return buf.Bytes(), nil
}
+7 -7
View File
@@ -13,8 +13,8 @@ type Header struct {
InodeNumber uint32
}
//EntryInit is the values that can be easily decoded
type EntryInit struct {
//EntryRaw is the values that can be easily decoded
type EntryRaw struct {
Offset uint16
InodeOffset int16
Type uint16
@@ -23,19 +23,19 @@ type EntryInit struct {
//Entry is an entry in a directory.
type Entry struct {
Init EntryInit
Name string
Header *Header
*Header
Name string
EntryRaw
}
//NewEntry creates a new directory entry
func NewEntry(rdr io.Reader) (Entry, error) {
var entry Entry
err := binary.Read(rdr, binary.LittleEndian, &entry.Init)
err := binary.Read(rdr, binary.LittleEndian, &entry.EntryRaw)
if err != nil {
return Entry{}, err
}
tmp := make([]byte, entry.Init.NameSize+1)
tmp := make([]byte, entry.EntryRaw.NameSize+1)
err = binary.Read(rdr, binary.LittleEndian, &tmp)
if err != nil {
return Entry{}, err
+83 -82
View File
@@ -5,14 +5,15 @@ import (
"io"
)
//The different types of inodes as defined by inodetype
const (
BasicDirectoryType = iota + 1
BasicFileType
BasicSymlinkType
BasicBlockDeviceType
BasicCharDeviceType
BasicFifoType
BasicSocketType
DirType = iota + 1
FileType
SymType
BlockDevType
CharDevType
FifoType
SocketType
ExtDirType
ExtFileType
ExtSymlinkType
@@ -32,8 +33,8 @@ type Header struct {
Number uint32
}
//BasicDirectory is self explainatory
type BasicDirectory struct {
//Dir is self explainatory
type Dir struct {
DirectoryIndex uint32
HardLinks uint32
DirectorySize uint16
@@ -41,8 +42,8 @@ type BasicDirectory struct {
ParentInodeNumber uint32
}
//ExtendedDirectoryInit is the information that can be directoy decoded
type ExtendedDirectoryInit struct {
//ExtDirInit is the information that can be directoy decoded
type ExtDirInit struct {
HardLinks uint32
DirectorySize uint32
DirectoryIndex uint32
@@ -52,20 +53,20 @@ type ExtendedDirectoryInit struct {
XattrIndex uint32
}
//ExtendedDirectory is a directory with extra info
type ExtendedDirectory struct {
Init ExtendedDirectoryInit
Indexes []DirectoryIndex
//ExtDir is a directory with extra info
type ExtDir struct {
Indexes []DirIndex
ExtDirInit
}
//NewExtendedDirectory creates a new ExtendedDirectory
func NewExtendedDirectory(rdr io.Reader) (ExtendedDirectory, error) {
var inode ExtendedDirectory
err := binary.Read(rdr, binary.LittleEndian, &inode.Init)
func NewExtendedDirectory(rdr io.Reader) (ExtDir, error) {
var inode ExtDir
err := binary.Read(rdr, binary.LittleEndian, &inode.ExtDirInit)
if err != nil {
return inode, err
}
for i := uint16(0); i < inode.Init.IndexCount; i++ {
for i := uint16(0); i < inode.IndexCount; i++ {
tmp, err := NewDirectoryIndex(rdr)
if err != nil {
return inode, err
@@ -75,27 +76,27 @@ func NewExtendedDirectory(rdr io.Reader) (ExtendedDirectory, error) {
return inode, err
}
//DirectoryIndexInit holds the values that can be easily decoded
type DirectoryIndexInit struct {
//DirIndexInit holds the values that can be easily decoded
type DirIndexInit struct {
Offset uint32
DirTableOffset uint32
NameSize uint32
}
//DirectoryIndex is a quick lookup provided by an ExtendedDirectory
type DirectoryIndex struct {
Init DirectoryIndexInit
//DirIndex is a quick lookup provided by an ExtendedDirectory
type DirIndex struct {
Name string
DirIndexInit
}
//NewDirectoryIndex return a new DirectoryIndex
func NewDirectoryIndex(rdr io.Reader) (DirectoryIndex, error) {
var index DirectoryIndex
err := binary.Read(rdr, binary.LittleEndian, &index.Init)
func NewDirectoryIndex(rdr io.Reader) (DirIndex, error) {
var index DirIndex
err := binary.Read(rdr, binary.LittleEndian, &index.DirIndexInit)
if err != nil {
return index, err
}
tmp := make([]byte, index.Init.NameSize+1, index.Init.NameSize+1)
tmp := make([]byte, index.NameSize+1, index.NameSize+1)
err = binary.Read(rdr, binary.LittleEndian, &tmp)
if err != nil {
return index, err
@@ -104,31 +105,31 @@ func NewDirectoryIndex(rdr io.Reader) (DirectoryIndex, error) {
return index, nil
}
//BasicFileInit is the information that can be directoy decoded
type BasicFileInit struct {
//FileInit is the information that can be directly decoded
type FileInit struct {
BlockStart uint32
FragmentIndex uint32
FragmentOffset uint32
Size uint32
}
//BasicFile is self explainatory
type BasicFile struct {
Init BasicFileInit
//File is self explainatory
type File struct {
BlockSizes []uint32
Fragmented bool
FileInit
}
//NewBasicFile creates a new BasicFile
func NewBasicFile(rdr io.Reader, blockSize uint32) (BasicFile, error) {
var inode BasicFile
err := binary.Read(rdr, binary.LittleEndian, &inode.Init)
//NewFile creates a new File
func NewFile(rdr io.Reader, blockSize uint32) (File, error) {
var inode File
err := binary.Read(rdr, binary.LittleEndian, &inode.FileInit)
if err != nil {
return inode, err
}
inode.Fragmented = inode.Init.FragmentIndex != 0xFFFFFFFF
blocks := inode.Init.Size / blockSize
if inode.Init.Size%blockSize > 0 {
inode.Fragmented = inode.FragmentIndex != 0xFFFFFFFF
blocks := inode.Size / blockSize
if inode.Size%blockSize > 0 {
blocks++
}
inode.BlockSizes = make([]uint32, blocks, blocks)
@@ -136,8 +137,8 @@ func NewBasicFile(rdr io.Reader, blockSize uint32) (BasicFile, error) {
return inode, err
}
//ExtendedFileInit is the information that can be directly decoded
type ExtendedFileInit struct {
//ExtFileInit is the information that can be directly decoded
type ExtFileInit struct {
BlockStart uint32
Size uint32
Sparse uint64
@@ -147,23 +148,23 @@ type ExtendedFileInit struct {
XattrIndex uint32
}
//ExtendedFile is a file with more information
type ExtendedFile struct {
Init ExtendedFileInit
//ExtFile is a file with more information
type ExtFile struct {
BlockSizes []uint32
Fragmented bool
ExtFileInit
}
//NewExtendedFile creates a new ExtendedFile
func NewExtendedFile(rdr io.Reader, blockSize uint32) (ExtendedFile, error) {
var inode ExtendedFile
err := binary.Read(rdr, binary.LittleEndian, &inode.Init)
func NewExtendedFile(rdr io.Reader, blockSize uint32) (ExtFile, error) {
var inode ExtFile
err := binary.Read(rdr, binary.LittleEndian, &inode.ExtFileInit)
if err != nil {
return inode, err
}
inode.Fragmented = inode.Init.FragmentIndex != 0xFFFFFFFF
blocks := inode.Init.Size / blockSize
if inode.Init.Size%blockSize > 0 {
inode.Fragmented = inode.FragmentIndex != 0xFFFFFFFF
blocks := inode.Size / blockSize
if inode.Size%blockSize > 0 {
blocks++
}
inode.BlockSizes = make([]uint32, blocks, blocks)
@@ -171,27 +172,27 @@ func NewExtendedFile(rdr io.Reader, blockSize uint32) (ExtendedFile, error) {
return inode, err
}
//BasicSymlinkInit is all the values that can be directly decoded
type BasicSymlinkInit struct {
//SymInit is all the values that can be directly decoded
type SymInit struct {
HardLinks uint32
TargetPathSize uint32
}
//BasicSymlink is a symlink
type BasicSymlink struct {
Init BasicSymlinkInit
targetPath []byte //len is TargetPathSize
//Sym is a symlink
type Sym struct {
Path string
targetPath []byte //len is TargetPathSize
SymInit
}
//NewBasicSymlink creates a new BasicSymlink
func NewBasicSymlink(rdr io.Reader) (BasicSymlink, error) {
var inode BasicSymlink
err := binary.Read(rdr, binary.LittleEndian, &inode.Init)
//NewSymlink creates a new Symlink
func NewSymlink(rdr io.Reader) (Sym, error) {
var inode Sym
err := binary.Read(rdr, binary.LittleEndian, &inode.SymInit)
if err != nil {
return inode, err
}
inode.targetPath = make([]byte, inode.Init.TargetPathSize, inode.Init.TargetPathSize)
inode.targetPath = make([]byte, inode.TargetPathSize, inode.TargetPathSize)
err = binary.Read(rdr, binary.LittleEndian, &inode.targetPath)
if err != nil {
return inode, err
@@ -200,28 +201,28 @@ func NewBasicSymlink(rdr io.Reader) (BasicSymlink, error) {
return inode, err
}
//ExtendedSymlinkInit is all the values that can be directly decoded
type ExtendedSymlinkInit struct {
//ExtSymInit is all the values that can be directly decoded
type ExtSymInit struct {
HardLinks uint32
TargetPathSize uint32
}
//ExtendedSymlink is a symlink with extra information
type ExtendedSymlink struct {
Init ExtendedSymlinkInit
targetPath []uint8
//ExtSym is a symlink with extra information
type ExtSym struct {
Path string
targetPath []uint8
ExtSymInit
XattrIndex uint32
}
//NewExtendedSymlink creates a new ExtendedSymlink
func NewExtendedSymlink(rdr io.Reader) (ExtendedSymlink, error) {
var inode ExtendedSymlink
err := binary.Read(rdr, binary.LittleEndian, &inode.Init)
func NewExtendedSymlink(rdr io.Reader) (ExtSym, error) {
var inode ExtSym
err := binary.Read(rdr, binary.LittleEndian, &inode.ExtSymInit)
if err != nil {
return inode, err
}
inode.targetPath = make([]uint8, inode.Init.TargetPathSize, inode.Init.TargetPathSize)
inode.targetPath = make([]uint8, inode.TargetPathSize, inode.TargetPathSize)
err = binary.Read(rdr, binary.LittleEndian, &inode.targetPath)
if err != nil {
return inode, err
@@ -231,25 +232,25 @@ func NewExtendedSymlink(rdr io.Reader) (ExtendedSymlink, error) {
return inode, err
}
//BasicDevice is a device
type BasicDevice struct {
//Device is a device
type Device struct {
HardLinks uint32
Device uint32
}
//ExtendedDevice is a device with more info
type ExtendedDevice struct {
BasicDevice
//ExtDevice is a device with more info
type ExtDevice struct {
Device
XattrIndex uint32
}
//BasicIPC is a Fifo or Socket device
type BasicIPC struct {
//IPC is a Fifo or Socket device
type IPC struct {
HardLink uint32
}
//ExtendedIPC is a IPC device with extra info
type ExtendedIPC struct {
BasicIPC
//ExtIPC is a IPC device with extra info
type ExtIPC struct {
IPC
XattrIndex uint32
}
+21 -21
View File
@@ -9,9 +9,9 @@ import (
//
//Info holds the actual Inode. Due to each inode type being a different type, it's store as an interface{}
type Inode struct {
Header Header
Type int //Type the inode type defined in the header. Here so it's easy to access
Info interface{} //Info is the parsed specific data. It's type is defined by Type.
Info interface{} //Info is the parsed specific data. It's type is defined by Type.
Type int //Type the inode type defined in the header. Here so it's easy to access
Header
}
//ProcessInode tries to read an inode from the BlockReader
@@ -23,48 +23,48 @@ func ProcessInode(br io.Reader, blockSize uint32) (*Inode, error) {
}
var info interface{}
switch head.InodeType {
case BasicDirectoryType:
var inode BasicDirectory
case DirType:
var inode Dir
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return nil, err
}
info = inode
case BasicFileType:
inode, err := NewBasicFile(br, blockSize)
case FileType:
inode, err := NewFile(br, blockSize)
if err != nil {
return nil, err
}
info = inode
case BasicSymlinkType:
inode, err := NewBasicSymlink(br)
case SymType:
inode, err := NewSymlink(br)
if err != nil {
return nil, err
}
info = inode
case BasicBlockDeviceType:
var inode BasicDevice
case BlockDevType:
var inode Device
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return nil, err
}
info = inode
case BasicCharDeviceType:
var inode BasicDevice
case CharDevType:
var inode Device
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return nil, err
}
info = inode
case BasicFifoType:
var inode BasicIPC
case FifoType:
var inode IPC
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return nil, err
}
info = inode
case BasicSocketType:
var inode BasicIPC
case SocketType:
var inode IPC
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return nil, err
@@ -89,28 +89,28 @@ func ProcessInode(br io.Reader, blockSize uint32) (*Inode, error) {
}
info = inode
case ExtBlockDeviceType:
var inode ExtendedDevice
var inode ExtDevice
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return nil, err
}
info = inode
case ExtCharDeviceType:
var inode ExtendedDevice
var inode ExtDevice
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return nil, err
}
info = inode
case ExtFifoType:
var inode ExtendedIPC
var inode ExtIPC
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return nil, err
}
info = inode
case ExtSocketType:
var inode ExtendedIPC
var inode ExtIPC
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return nil, err
+1 -1
View File
@@ -16,9 +16,9 @@ type metadata struct {
//MetadataReader is a block reader for metadata. It will automatically read the next block, when it reaches the end of a block.
type metadataReader struct {
s *Reader
offset int64
headers []*metadata
data []byte
offset int64
readOffset int
}
+12 -15
View File
@@ -29,11 +29,11 @@ var (
//Reader processes and reads a squashfs archive.
type Reader struct {
r io.ReaderAt
super superblock
flags superblockFlags
decompressor compression.Decompressor
fragOffsets []uint64
idTable []uint32
super superblock
flags superblockFlags
}
//NewSquashfsReader returns a new squashfs.Reader from an io.ReaderAt
@@ -54,7 +54,7 @@ func NewSquashfsReader(r io.ReaderAt) (*Reader, error) {
rdr.flags = rdr.super.GetFlags()
if rdr.flags.CompressorOptions {
switch rdr.super.CompressionType {
case gzipCompression:
case GzipCompression:
gzip, err := compression.NewGzipCompressorWithOptions(io.NewSectionReader(rdr.r, int64(binary.Size(rdr.super)), 8))
if err != nil {
return nil, err
@@ -63,7 +63,7 @@ func NewSquashfsReader(r io.ReaderAt) (*Reader, error) {
hasUnsupportedOptions = true
}
rdr.decompressor = gzip
case xzCompression:
case XzCompression:
xz, err := compression.NewXzCompressorWithOptions(io.NewSectionReader(rdr.r, int64(binary.Size(rdr.super)), 8))
if err != nil {
return nil, err
@@ -72,35 +72,32 @@ func NewSquashfsReader(r io.ReaderAt) (*Reader, error) {
return nil, errors.New("XZ compression options has filters. These are not yet supported")
}
rdr.decompressor = xz
case lz4Compression:
case Lz4Compression:
lz4, err := compression.NewLz4CompressorWithOptions(io.NewSectionReader(rdr.r, int64(binary.Size(rdr.super)), 8))
if err != nil {
return nil, err
}
if lz4.HC {
hasUnsupportedOptions = true
}
rdr.decompressor = lz4
case zstdCompression:
case ZstdCompression:
zstd, err := compression.NewZstdCompressorWithOptions(io.NewSectionReader(rdr.r, int64(binary.Size(rdr.super)), 4))
if err != nil {
return nil, err
}
rdr.decompressor = zstd
default:
return nil, errCompressorOptions
return nil, errIncompatibleCompression
}
} else {
switch rdr.super.CompressionType {
case gzipCompression:
case GzipCompression:
rdr.decompressor = &compression.Gzip{}
case lzmaCompression:
case LzmaCompression:
rdr.decompressor = &compression.Lzma{}
case xzCompression:
case XzCompression:
rdr.decompressor = &compression.Xz{}
case lz4Compression:
case Lz4Compression:
rdr.decompressor = &compression.Lz4{}
case zstdCompression:
case ZstdCompression:
rdr.decompressor = &compression.Zstd{}
default:
//TODO: all compression types.
+115 -15
View File
@@ -1,21 +1,24 @@
package squashfs
import (
"fmt"
"io"
"net/http"
"os"
"os/exec"
"strconv"
"testing"
"time"
goappimage "github.com/CalebQ42/GoAppImage"
)
const (
downloadURL = "https://github.com/Swordfish90/cool-retro-term/releases/download/1.1.1/Cool-Retro-Term-1.1.1-x86_64.AppImage"
appImageName = "Cool-Retro-Term.AppImage"
squashfsName = "airootfs.sfs" //testing with a ArchLinux root fs from the live img
downloadURL = "https://github.com/srevinsaju/Firefox-Appimage/releases/download/firefox-v84.0.r20201221152838/firefox-84.0.r20201221152838-x86_64.AppImage"
appImageName = "firefox-84.0.r20201221152838-x86_64.AppImage"
squashfsName = "balenaEtcher-1.5.113-x64.AppImage.sfs" //testing with a ArchLinux root fs from the live img
)
//Right now, don't use. Arch linux sfs uses XZ compression with filters, which isn't supported
func TestSquashfs(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
@@ -29,10 +32,20 @@ func TestSquashfs(t *testing.T) {
if err != nil {
t.Fatal(err)
}
os.RemoveAll(wd + "/testing/" + squashfsName + ".d")
root, _ := rdr.GetRootFolder()
errs := root.ExtractWithOptions(wd+"/testing/"+squashfsName+".d", false, false, os.ModePerm, true)
t.Fatal(errs)
fmt.Println("stuff", rdr.super.CompressionType)
fil := rdr.GetFileAtPath("*.desktop")
if fil == nil {
t.Fatal("Can't find desktop fil")
}
errs := fil.ExtractTo(wd + "/testing")
if len(errs) > 0 {
t.Fatal(errs)
}
errs = rdr.ExtractTo(wd + "/testing/" + squashfsName + ".d")
if len(errs) > 0 {
t.Fatal(errs)
}
t.Fatal("No Problems")
}
func TestAppImage(t *testing.T) {
@@ -43,7 +56,10 @@ func TestAppImage(t *testing.T) {
}
aiFil, err := os.Open(wd + "/testing/" + appImageName)
if os.IsNotExist(err) {
downloadTestAppImage(t, wd+"/testing")
err = downloadTestAppImage(wd + "/testing")
if err != nil {
t.Fatal(err)
}
aiFil, err = os.Open(wd + "/testing/" + appImageName)
if err != nil {
t.Fatal(err)
@@ -53,12 +69,14 @@ func TestAppImage(t *testing.T) {
}
defer aiFil.Close()
stat, _ := aiFil.Stat()
os.RemoveAll(wd + "/testing/firefox")
ai := goappimage.NewAppImage(wd + "/testing/" + appImageName)
start := time.Now()
rdr, err := NewSquashfsReader(io.NewSectionReader(aiFil, ai.Offset, stat.Size()-ai.Offset))
if err != nil {
t.Fatal(err)
}
errs := rdr.ExtractTo(wd + "/testing/cool-retro")
errs := rdr.ExtractTo(wd + "/testing/firefox")
if len(errs) > 0 {
t.Fatal(errs)
}
@@ -66,15 +84,93 @@ func TestAppImage(t *testing.T) {
// root, _ := rdr.GetRootFolder()
// errs := root.ExtractWithOptions(wd+"/testing/"+appImageName+".d", true, os.ModePerm, true)
// t.Fatal(errs)
fmt.Println(time.Since(start))
t.Fatal("No problemo!")
}
func downloadTestAppImage(t *testing.T, dir string) {
func TestUnsquashfs(t *testing.T) {
t.Parallel()
wd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
aiFil, err := os.Open(wd + "/testing/" + appImageName)
if os.IsNotExist(err) {
err = downloadTestAppImage(wd + "/testing")
if err != nil {
t.Fatal(err)
}
aiFil, err = os.Open(wd + "/testing/" + appImageName)
if err != nil {
t.Fatal(err)
}
} else if err != nil {
t.Fatal(err)
}
os.RemoveAll(wd + "/testing/unsquashFirefox")
os.RemoveAll(wd + "/testing/firefox")
ai := goappimage.NewAppImage(wd + "/testing/" + appImageName)
fmt.Println("Command:", "unsquashfs", "-d", wd+"/testing/unsquashFirefox", "-o", strconv.Itoa(int(ai.Offset)), aiFil.Name())
cmd := exec.Command("unsquashfs", "-d", wd+"/testing/unsquashFirefox", "-o", strconv.Itoa(int(ai.Offset)), aiFil.Name())
start := time.Now()
err = cmd.Run()
if err != nil {
t.Fatal(err)
}
fmt.Println(time.Since(start))
t.Fatal("HI")
}
func BenchmarkDragRace(b *testing.B) {
wd, err := os.Getwd()
if err != nil {
b.Fatal(err)
}
aiFil, err := os.Open(wd + "/testing/" + appImageName)
if os.IsNotExist(err) {
err = downloadTestAppImage(wd + "/testing")
if err != nil {
b.Fatal(err)
}
aiFil, err = os.Open(wd + "/testing/" + appImageName)
if err != nil {
b.Fatal(err)
}
} else if err != nil {
b.Fatal(err)
}
stat, _ := aiFil.Stat()
ai := goappimage.NewAppImage(wd + "/testing/" + appImageName)
os.RemoveAll(wd + "/testing/unsquashFirefox")
os.RemoveAll(wd + "/testing/firefox")
cmd := exec.Command("unsquashfs", "-d", wd+"/testing/unsquashFirefox", "-o", strconv.Itoa(int(ai.Offset)), aiFil.Name())
start := time.Now()
err = cmd.Run()
if err != nil {
b.Fatal(err)
}
unsquashTime := time.Since(start)
start = time.Now()
rdr, err := NewSquashfsReader(io.NewSectionReader(aiFil, ai.Offset, stat.Size()-ai.Offset))
if err != nil {
b.Fatal(err)
}
errs := rdr.ExtractTo(wd + "/testing/firefox")
if len(errs) > 0 {
b.Fatal(errs)
}
libTime := time.Since(start)
b.Log("Unsqushfs:", unsquashTime.Round(time.Millisecond))
b.Log("Library:", libTime.Round(time.Millisecond))
b.Log("unsquashfs is " + strconv.FormatFloat(float64(libTime.Milliseconds())/float64(unsquashTime.Milliseconds()), 'f', 2, 64) + "x faster")
}
func downloadTestAppImage(dir string) error {
//seems to time out on slow connections. Might fix that at some point... or not. It's just a test...
os.Mkdir(dir, os.ModePerm)
appImage, err := os.Create(dir + "/" + appImageName)
if err != nil {
t.Fatal(err)
return err
}
defer appImage.Close()
check := http.Client{
@@ -85,13 +181,14 @@ func downloadTestAppImage(t *testing.T, dir string) {
}
resp, err := check.Get(downloadURL)
if err != nil {
t.Fatal(err)
return err
}
defer resp.Body.Close()
_, err = io.Copy(appImage, resp.Body)
if err != nil {
t.Fatal(err)
return err
}
return nil
}
func TestCreateSquashFromAppImage(t *testing.T) {
@@ -105,7 +202,10 @@ func TestCreateSquashFromAppImage(t *testing.T) {
}
_, err = os.Open(wd + "/testing/" + appImageName)
if os.IsNotExist(err) {
downloadTestAppImage(t, wd+"/testing")
err = downloadTestAppImage(wd + "/testing")
if err != nil {
t.Fatal(err)
}
_, err = os.Open(wd + "/testing/" + appImageName)
if err != nil {
t.Fatal(err)
+7 -6
View File
@@ -1,12 +1,13 @@
package squashfs
//The types of compression supported by squashfs.
const (
gzipCompression = 1 + iota
lzmaCompression
lzoCompression
xzCompression
lz4Compression
zstdCompression
GzipCompression = 1 + iota
LzmaCompression
LzoCompression
XzCompression
Lz4Compression
ZstdCompression
)
//Superblock contains important information about a squashfs file. Located at the very front of the archive.
+209
View File
@@ -0,0 +1,209 @@
package squashfs
import (
"errors"
"io"
"log"
"os"
"path"
"sort"
"strings"
"syscall"
"github.com/CalebQ42/squashfs/internal/compression"
)
type fileHolder struct {
reader io.Reader
path string
name string
symLocation string
UID int
GUID int
perm int
folder bool
symlink bool
}
//Writer is used to creaste squashfs archives. Currently unusable
//TODO: Make usable
type Writer struct {
compressor compression.Compressor
structure map[string][]*fileHolder
symlinkTable map[string]string //[oldpath]newpath
uidGUIDTable []int
compressionType int
allowErrors bool
}
//NewWriter creates a new with the default options (Gzip compression and allow errors)
func NewWriter() (*Writer, error) {
return NewWriterWithOptions(GzipCompression, true)
}
//NewWriterWithOptions creates a new squashfs.Writer with the given options.
//compressionType can be of any types, except LZO (which this library doesn't have support for yet)
//allowErrors determines if, when adding folders, it allows errors encountered with it's sub-directories and instead logs the errors.
func NewWriterWithOptions(compressionType int, allowErrors bool) (*Writer, error) {
if compressionType < 0 || compressionType > 6 {
return nil, errors.New("Incorrect compression type")
}
if compressionType == 3 {
return nil, errors.New("LZO compression is not (currently) supported")
}
return &Writer{
structure: map[string][]*fileHolder{
"/": make([]*fileHolder, 0),
},
symlinkTable: make(map[string]string),
compressionType: compressionType,
allowErrors: allowErrors,
}, nil
}
//AddFile attempts to add an os.File to the archive at it's root.
func (w *Writer) AddFile(file *os.File) error {
return w.AddFileToFolder("/", file)
}
//AddFileToFolder adds the given file to the squashfs archive, placing it inside the given folder.
func (w *Writer) AddFileToFolder(folder string, file *os.File) error {
name := path.Base(file.Name())
if !strings.HasSuffix(folder, "/") {
folder += "/"
}
return w.AddFileTo(folder+name, file)
}
//AddFileTo adds the given file to the squashfs archive at the given filepath.
func (w *Writer) AddFileTo(filepath string, file *os.File) error {
filepath = path.Clean(filepath)
if !strings.HasPrefix(filepath, "/") {
filepath = "/" + filepath
}
var holder fileHolder
holder.path, holder.name = path.Split(filepath)
holder.reader = file
stat, err := file.Stat()
if err != nil {
return err
}
holder.folder = stat.IsDir()
holder.symlink = (stat.Mode()&os.ModeSymlink == os.ModeSymlink)
holder.perm = int(stat.Mode().Perm())
//Thanks to https://stackoverflow.com/questions/58179647/getting-uid-and-gid-of-a-file for uid and guid getting
if stat, ok := stat.Sys().(*syscall.Stat_t); ok {
holder.UID = int(stat.Uid)
holder.GUID = int(stat.Gid)
}
if sort.SearchInts(w.uidGUIDTable, holder.UID) == len(w.uidGUIDTable) {
w.uidGUIDTable = append(w.uidGUIDTable, holder.UID)
sort.Ints(w.uidGUIDTable)
}
if sort.SearchInts(w.uidGUIDTable, holder.GUID) == len(w.uidGUIDTable) {
w.uidGUIDTable = append(w.uidGUIDTable, holder.GUID)
sort.Ints(w.uidGUIDTable)
}
if holder.symlink {
target, err := os.Readlink(file.Name())
if err != nil {
return err
}
holder.symLocation = target
} else if holder.folder {
subDirNames, err := file.Readdirnames(-1)
if err != nil {
return err
}
dirsAdded := make([]string, 0)
for _, subDir := range subDirNames {
fil, err := os.Open(file.Name() + subDir)
if err != nil {
return err
}
err = w.AddFileToFolder(holder.path+"/"+holder.name, fil)
if err != nil && !w.allowErrors {
for _, dir := range dirsAdded {
w.Remove(dir)
}
return err
} else if err != nil {
log.Println("Error while adding", fil.Name())
log.Println(err)
}
if !w.allowErrors {
dirsAdded = append(dirsAdded, holder.path+"/"+holder.name)
}
}
} else if !stat.Mode().IsRegular() {
return errors.New("Unsupported file type " + file.Name())
}
w.structure[holder.path] = append(w.structure[holder.path], &holder)
return nil
}
//AddReaderTo adds the data from the given reader to the archive as a file located at the given filepath.
//Data from the reader is not read until the squashfs archive is writen.
//If the given reader implements io.Closer, it will be closed after it is fully read.
func (w *Writer) AddReaderTo(filepath string, reader io.Reader) error {
filepath = path.Clean(filepath)
if !strings.HasPrefix(filepath, "/") {
filepath = "/" + filepath
}
var holder fileHolder
holder.path, holder.name = path.Split(filepath)
holder.reader = reader
w.structure[holder.path] = append(w.structure[holder.path], &holder)
return nil
}
//Remove tries to remove the file(s) at the given filepath. If wildcards are used, it will remove all files that match.
//Returns true if one or more files are removed.
func (w *Writer) Remove(filepath string) bool {
var matchFound bool
filepath = path.Clean(filepath)
if !strings.HasPrefix(filepath, "/") {
filepath = "/" + filepath
}
dir, name := path.Split(filepath)
for structDir, files := range w.structure {
if match, _ := path.Match(dir, structDir); match {
for i, fil := range files {
if match, _ = path.Match(name, fil.name); match {
matchFound = true
if i != len(files)-1 {
w.structure[structDir] = append(w.structure[structDir][:i], w.structure[structDir][i+1:]...)
} else {
w.structure[structDir] = w.structure[structDir][:i]
}
}
}
}
}
return matchFound
}
//FixSymlinks will scan through the squashfs archive and try to find broken symlinks and fix them.
//This done by replacing the symlink with the target file and then pointing other symlinks to that file.
//
//If this is not run before writing, you may end up with broken symlinks.
func (w *Writer) FixSymlinks() error {
//TODO
return errors.New("DON'T")
}
//WriteToFilename creates the squashfs archive with the given filepath.
func (w *Writer) WriteToFilename(filepath string) error {
newFil, err := os.Create(filepath)
if err != nil {
return err
}
_, err = w.WriteTo(newFil)
return err
}
//WriteTo attempts to write the archive to the given io.Writer.
func (w *Writer) WriteTo(write io.Writer) (int64, error) {
//TODO
return 0, errors.New("I SAID DON'T")
}
+15
View File
@@ -0,0 +1,15 @@
package squashfs
//TODO: Allow settings the options
// func (w *Writer) SetGzipOptions() error {}
// func (w *Writer) SetLzmaOptions() error {}
// func (w *Writer) SetLzoOptions() error {}
// func (w *Writer) SetXzOptions() error {}
// func (w *Writer) SetLz4Options() error {}
// func (w *Writer) SetZstdOptions() error {}