Compare commits

...

29 Commits

Author SHA1 Message Date
Caleb Gardner 17e1d65488 Fixed Extended Files 2021-01-16 03:09:48 -06:00
Caleb Gardner 80946f58e7 Fixed issue with Extended Symlinks
Removed some shadowed err's
2021-01-16 01:32:00 -06:00
Caleb Gardner 4187598783 A couple of fixes.
GetChildrenRecursively is no longer threaded so it's more consistent
Fixed GetFileAtPath, specifically when getting the root dir
2021-01-15 10:57:03 -06:00
Caleb Gardner 9cf92c4916 Removed some shadowed values (thanks gopls) 2021-01-13 11:45:10 -06:00
Caleb Gardner 407d649b3d Finished FixSymlinks (in theory) 2021-01-13 06:02:15 -06:00
Caleb Gardner dcf59a4261 The root inode is now only initialized once.
Privated File.Reader because it really shouldn't be public.
2021-01-12 01:43:03 -06:00
Caleb Gardner 1506ca0ac3 updated dependencies 2021-01-10 04:39:19 -06:00
Caleb Gardner fe9344b633 Fixed issue with not all data being extracted with ExtractTo 2021-01-10 04:25:09 -06:00
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
Caleb Gardner 89ec7eb0fb Fixed GetSymlinkFile
Added GetSymlinkFileRecursive
2020-12-16 02:39:35 -06:00
Caleb Gardner d63ba47818 Added Reader.ModTime 2020-12-11 01:39:08 -06:00
Caleb Gardner 495d2345a4 File now implements os.FileInfo 2020-12-11 01:38:53 -06:00
Caleb Gardner 6dfcb1cf80 Merge branch 'main' of https://github.com/CalebQ42/squashfs into main 2020-12-10 08:52:58 -06:00
Caleb Gardner c7593eaff3 Added Reader.ExtractTo for ease of use 2020-12-10 08:52:49 -06:00
Caleb Gardner 135403032f Updated README 2020-12-09 01:45:58 -06:00
Caleb Gardner 5c3bf8d528 Implemented the rest of the compression types
Haven't implemented LZO due to limited libraries
2020-12-09 01:40:30 -06:00
23 changed files with 1330 additions and 508 deletions
+10 -6
View File
@@ -1,14 +1,18 @@
# squashfs [![PkgGoDev](https://pkg.go.dev/badge/github.com/CalebQ42/squashfs)](https://pkg.go.dev/github.com/CalebQ42/squashfs) # squashfs (WIP)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/CalebQ42/squashfs)](https://pkg.go.dev/github.com/CalebQ42/squashfs) [![Go Report Card](https://goreportcard.com/badge/github.com/CalebQ42/squashfs)](https://goreportcard.com/report/github.com/CalebQ42/squashfs)
A PURE Go library to read and write squashfs. A PURE Go library to read and write squashfs.
Currently, you can read a squashfs and extract files (folder extraction not supported. Yet). Currently has support for reading squashfs files and extracting files and folders. Supports all compression types except LZO, but additional compression options are hit or miss.
Special thanks to https://dr-emann.github.io/squashfs/ for some VERY important information in an easy to understand format. 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.
Thanks also to [distri's squashfs library](https://github.com/distr1/distri/tree/master/internal/squashfs) as I referenced it to figure some things out (and double check others). Thanks also to [distri's squashfs library](https://github.com/distr1/distri/tree/master/internal/squashfs) as I referenced it to figure some things out (and double check others).
# [TODO](https://github.com/CalebQ42/squashfs/projects/1?fullscreen=true) ## Performane
# Where I'm at 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`)
* Working on the File interface that should make it easier to deal with squashfs files. I'm also trying to make them capable for when I get squashing working. ## [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
}
+119 -62
View File
@@ -18,39 +18,19 @@ var (
//DataReader reads data from data blocks. //DataReader reads data from data blocks.
type dataReader struct { type dataReader struct {
r *Reader 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 curData []byte
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 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
}
//NewDataReader creates a new data reader at the given offset, with the blocks defined by sizes //NewDataReader creates a new data reader at the given offset, with the blocks defined by sizes
func (r *Reader) newDataReader(offset int64, sizes []uint32) (*dataReader, error) { func (r *Reader) newDataReader(offset int64, sizes []uint32) (*dataReader, error) {
var dr dataReader var dr dataReader
dr.r = r dr.r = r
dr.offset = offset dr.offset = offset
for _, size := range sizes { dr.sizes = sizes
dr.blocks = append(dr.blocks, newDataBlock(size))
}
err := dr.readCurBlock() err := dr.readCurBlock()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -63,29 +43,29 @@ func (r *Reader) newDataReaderFromInode(i *inode.Inode) (*dataReader, error) {
var rdr dataReader var rdr dataReader
rdr.r = r rdr.r = r
switch i.Type { switch i.Type {
case inode.BasicFileType: case inode.FileType:
fil := i.Info.(inode.BasicFile) fil := i.Info.(inode.File)
if fil.Init.BlockStart == 0 { if fil.BlockStart == 0 {
return nil, errInodeOnlyFragment return nil, errInodeOnlyFragment
} }
rdr.offset = int64(fil.Init.BlockStart) rdr.offset = int64(fil.BlockStart)
for _, sizes := range fil.BlockSizes { for _, sizes := range fil.BlockSizes {
rdr.blocks = append(rdr.blocks, newDataBlock(sizes)) rdr.sizes = append(rdr.sizes, sizes)
} }
if fil.Fragmented { if fil.Fragmented {
rdr.blocks = rdr.blocks[:len(rdr.blocks)-1] rdr.sizes = rdr.sizes[:len(rdr.sizes)-1]
} }
case inode.ExtFileType: case inode.ExtFileType:
fil := i.Info.(inode.ExtendedFile) fil := i.Info.(inode.ExtFile)
if fil.Init.BlockStart == 0 { if fil.BlockStart == 0 {
return nil, errInodeOnlyFragment return nil, errInodeOnlyFragment
} }
rdr.offset = int64(fil.Init.BlockStart) rdr.offset = int64(fil.BlockStart)
for _, sizes := range fil.BlockSizes { for _, sizes := range fil.BlockSizes {
rdr.blocks = append(rdr.blocks, newDataBlock(sizes)) rdr.sizes = append(rdr.sizes, sizes)
} }
if fil.Fragmented { if fil.Fragmented {
rdr.blocks = rdr.blocks[:len(rdr.blocks)-1] rdr.sizes = rdr.sizes[:len(rdr.sizes)-1]
} }
default: default:
return nil, errInodeNotFile return nil, errInodeNotFile
@@ -97,9 +77,14 @@ func (r *Reader) newDataReaderFromInode(i *inode.Inode) (*dataReader, error) {
return &rdr, nil 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 { func (d *dataReader) readNextBlock() error {
d.curBlock++ d.curBlock++
if d.curBlock >= len(d.blocks) { if d.curBlock >= len(d.sizes) {
d.curBlock-- d.curBlock--
return io.EOF return io.EOF
} }
@@ -112,42 +97,46 @@ func (d *dataReader) readNextBlock() error {
return nil return nil
} }
func (d *dataReader) readCurBlock() error { func (d *dataReader) readBlockAt(offset int64, size uint32) ([]byte, error) {
if d.curBlock >= len(d.blocks) { compressed := size&(1<<24) != (1 << 24)
return io.EOF 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 { sec := io.NewSectionReader(d.r.r, offset, int64(size))
d.curData = make([]byte, d.r.super.BlockSize) if compressed {
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 {
btys, err := d.r.decompressor.Decompress(sec) btys, err := d.r.decompressor.Decompress(sec)
if err != nil { if err != nil {
return err return nil, err
} }
d.blocks[d.curBlock].uncompressedSize = uint32(len(btys)) return btys, nil
d.curData = btys
d.blocks[d.curBlock].begOffset = d.offset
d.offset += int64(d.blocks[d.curBlock].size)
return nil
} }
var buf bytes.Buffer var buf bytes.Buffer
_, err := io.Copy(&buf, sec) _, err := io.Copy(&buf, sec)
if err != nil { if err != nil {
return err return nil, err
} }
d.curData = buf.Bytes() return buf.Bytes(), nil
d.blocks[d.curBlock].begOffset = d.offset
d.offset += int64(d.blocks[d.curBlock].size)
return err
} }
//Close frees up the curData from memory func (d *dataReader) offsetForBlock(index int) int64 {
func (d *dataReader) Close() error { out := d.offset
d.curData = nil 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 return nil
} }
@@ -175,12 +164,12 @@ func (d *dataReader) Read(p []byte) (int, error) {
d.curReadOffset = 0 d.curReadOffset = 0
} }
for ; read < len(p); read++ { for ; read < len(p); read++ {
d.curReadOffset++
if d.curReadOffset < len(d.curData) { if d.curReadOffset < len(d.curData) {
p[read] = d.curData[d.curReadOffset] p[read] = d.curData[d.curReadOffset]
} else { } else {
break break
} }
d.curReadOffset++
} }
} }
if read != len(p) { if read != len(p) {
@@ -188,3 +177,71 @@ func (d *dataReader) Read(p []byte) (int, error) {
} }
return read, nil 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
}
curIndex++
} else {
backlog = append(backlog, cache)
}
}
}
+144 -100
View File
@@ -7,6 +7,7 @@ import (
"os" "os"
"path" "path"
"strings" "strings"
"time"
"github.com/CalebQ42/squashfs/internal/directory" "github.com/CalebQ42/squashfs/internal/directory"
"github.com/CalebQ42/squashfs/internal/inode" "github.com/CalebQ42/squashfs/internal/inode"
@@ -26,14 +27,21 @@ var (
//File is the main way to interact with files within squashfs, or when putting files into a squashfs. //File is the main way to interact with files within squashfs, or when putting files into a squashfs.
//File can be either a file or folder. When reading from a squashfs, it reads from the datablocks. //File can be either a file or folder. When reading from a squashfs, it reads from the datablocks.
//When writing, this holds the information on WHERE the file will be placed inside the archive. //When writing, this holds the information on WHERE the file will be placed inside the archive.
//
//If copying data from a squashfs, the returned reader from io.Sys() implements io.WriterTo which
//will be significantly faster then calling Read directly.
//Ex: use io.Sys().(io.Reader) for io.Copy instead of using the File directly.
//
//Implements os.FileInfo and io.Reader
type File struct { type File struct {
Name string //The name of the file or folder. Root folder will not have a name ("") reader io.Reader
Parent *File //The parent directory. Should ALWAYS be a folder. If it's the root directory, will be nil Parent *File
Reader io.Reader //Underlying reader. When writing, will probably be an os.File. When reading this is kept nil UNTIL reading to save memory. r *Reader //Underlying reader. When writing, will probably be an os.File. When reading this is kept nil UNTIL reading to save memory.
path string //The path to the folder the File is located in. in *inode.Inode
r *Reader //The squashfs.Reader where this file is contained. name string
in *inode.Inode //Underlyting inode when reading. dir string
filType int //The file's type, using inode types. filType int //The file's type, using inode types.
} }
//get a File from a directory.entry //get a File from a directory.entry
@@ -43,12 +51,50 @@ func (r *Reader) newFileFromDirEntry(entry *directory.Entry) (fil *File, err err
if err != nil { if err != nil {
return nil, err return nil, err
} }
fil.Name = entry.Name fil.name = entry.Name
fil.r = r fil.r = r
fil.filType = fil.in.Type fil.filType = fil.in.Type
return return
} }
//Name is the file's name
func (f *File) Name() string {
return f.name
}
//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.FileType:
return int64(f.in.Info.(inode.File).Size)
case inode.ExtFileType:
return int64(f.in.Info.(inode.ExtFile).Size)
default:
return 0
}
}
//ModTime is the time of last modification.
func (f *File) ModTime() time.Time {
return time.Unix(int64(f.in.Header.ModifiedTime), 0)
}
//Sys returns the underlying reader. If the reader isn't initialized, it will initialize it.
//If called on something other then a file, returns nil.
func (f *File) Sys() interface{} {
if !f.IsFile() {
return nil
}
if f.reader == nil && f.r != nil {
var err error
f.reader, err = f.r.newFileReader(f.in)
if err != nil {
return nil
}
}
return f.reader
}
//GetChildren returns a *squashfs.File slice of every direct child of the directory. If the File is not a directory, will return ErrNotDirectory //GetChildren returns a *squashfs.File slice of every direct child of the directory. If the File is not a directory, will return ErrNotDirectory
func (f *File) GetChildren() (children []*File, err error) { func (f *File) GetChildren() (children []*File, err error) {
children = make([]*File, 0) children = make([]*File, 0)
@@ -69,8 +115,8 @@ func (f *File) GetChildren() (children []*File, err error) {
return return
} }
fil.Parent = f fil.Parent = f
if f.Name != "" { if f.name != "" {
fil.path = f.Path() fil.dir = f.Path()
} }
children = append(children, fil) children = append(children, fil)
} }
@@ -96,50 +142,40 @@ func (f *File) GetChildrenRecursively() (children []*File, err error) {
childFolders = append(childFolders, child) childFolders = append(childFolders, child)
} }
} }
foldChil := make(chan []*File)
errChan := make(chan error)
for _, folds := range childFolders { for _, folds := range childFolders {
go func(fil *File) { var childs []*File
childs, err := fil.GetChildrenRecursively() childs, err = folds.GetChildrenRecursively()
errChan <- err
foldChil <- childs
}(folds)
}
for range childFolders {
err = <-errChan
if err != nil { if err != nil {
fmt.Println(err)
return return
} }
children = append(children, <-foldChil...) children = append(children, childs...)
} }
return return
} }
//Path returns the path of the file within the archive. //Path returns the path of the file within the archive.
func (f *File) Path() string { func (f *File) Path() string {
if f.Name == "" { if f.name == "" {
return f.path return f.dir
} }
return f.path + "/" + f.Name return f.dir + "/" + f.name
} }
//GetFileAtPath tries to return the File at the given path, relative to the file. //GetFileAtPath tries to return the File at the given path, relative to the file.
//Returns nil if called on something other then a folder, OR if the path goes oustide the archive. //Returns nil if called on something other then a folder, OR if the path goes oustide the archive.
//Allows wildcards supported by path.Match (namely * and ?). //Allows wildcards supported by path.Match (namely * and ?) and will return the FIRST file that matches.
func (f *File) GetFileAtPath(dirPath string) *File { func (f *File) GetFileAtPath(dirPath string) *File {
if dirPath == "" { dirPath = path.Clean(dirPath)
dirPath = strings.TrimPrefix(dirPath, "/")
if dirPath == "" || dirPath == "." {
return f return f
} }
dirPath = strings.TrimSuffix(strings.TrimPrefix(dirPath, "/"), "/") if dirPath != "." && !f.IsDir() {
if dirPath != "" && !f.IsDir() {
return nil return nil
} }
for strings.HasSuffix(dirPath, "./") {
//since you can TECHNICALLY have an infinite amount of ./ and it would still be valid.
dirPath = strings.TrimPrefix(dirPath, "./")
}
split := strings.Split(dirPath, "/") split := strings.Split(dirPath, "/")
if split[0] == ".." && f.Name == "" { if split[0] == ".." && f.name == "" {
return nil return nil
} else if split[0] == ".." { } else if split[0] == ".." {
if f.Parent != nil { if f.Parent != nil {
@@ -152,7 +188,7 @@ func (f *File) GetFileAtPath(dirPath string) *File {
return nil return nil
} }
for _, child := range children { for _, child := range children {
eq, _ := path.Match(split[0], child.Name) eq, _ := path.Match(split[0], child.name)
if eq { if eq {
return child.GetFileAtPath(strings.Join(split[1:], "/")) return child.GetFileAtPath(strings.Join(split[1:], "/"))
} }
@@ -162,33 +198,34 @@ func (f *File) GetFileAtPath(dirPath string) *File {
//IsDir returns if the file is a directory. //IsDir returns if the file is a directory.
func (f *File) IsDir() bool { 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. //IsSymlink returns if the file is a symlink.
func (f *File) IsSymlink() bool { func (f *File) IsSymlink() bool {
return f.filType == inode.BasicSymlinkType || f.filType == inode.ExtSymlinkType return f.filType == inode.SymType || f.filType == inode.ExtSymType
} }
//IsFile returns if the file is a file. //IsFile returns if the file is a file.
func (f *File) IsFile() bool { 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. //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) //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 { func (f *File) SymlinkPath() string {
switch f.filType { switch f.filType {
case inode.BasicSymlinkType: case inode.SymType:
return f.in.Info.(inode.BasicSymlink).Path return f.in.Info.(inode.Sym).Path
case inode.ExtSymlinkType: case inode.ExtSymType:
return f.in.Info.(inode.ExtendedSymlink).Path return f.in.Info.(inode.ExtSym).Path
default: default:
return "" return ""
} }
} }
//GetSymlinkFile tries to return the squashfs.File associated with the symlink //GetSymlinkFile tries to return the squashfs.File associated with the symlink. If the file isn't a symlink
//or the symlink points to a location outside the archive, nil is returned.
func (f *File) GetSymlinkFile() *File { func (f *File) GetSymlinkFile() *File {
if !f.IsSymlink() { if !f.IsSymlink() {
return nil return nil
@@ -196,11 +233,32 @@ func (f *File) GetSymlinkFile() *File {
if strings.HasSuffix(f.SymlinkPath(), "/") { if strings.HasSuffix(f.SymlinkPath(), "/") {
return nil return nil
} }
return f.r.GetFileAtPath(f.SymlinkPath()) return f.Parent.GetFileAtPath(f.SymlinkPath())
} }
//Permission returns the os.FileMode of the File. Sets mode bits for directories and symlinks. //GetSymlinkFileRecursive tries to return the squasfs.File associated with the symlink. It will recursively
func (f *File) Permission() os.FileMode { //try to get the symlink's file. This will return either a non-symlink File, or nil.
func (f *File) GetSymlinkFileRecursive() *File {
if !f.IsSymlink() {
return nil
}
if strings.HasSuffix(f.SymlinkPath(), "/") {
return nil
}
sym := f
for {
sym = sym.GetSymlinkFile()
if sym == nil {
return nil
}
if !sym.IsSymlink() {
return sym
}
}
}
//Mode returns the os.FileMode of the File. Sets mode bits for directories and symlinks.
func (f *File) Mode() os.FileMode {
mode := os.FileMode(f.in.Header.Permissions) mode := os.FileMode(f.in.Header.Permissions)
switch { switch {
case f.IsDir(): case f.IsDir():
@@ -244,21 +302,22 @@ func (f *File) ExtractWithOptions(path string, dereferenceSymlink, unbreakSymlin
} }
switch { switch {
case f.IsDir(): case f.IsDir():
if f.Name != "" { if f.name != "" {
//TODO: check if folder is present, and if so, try to set it's permission //TODO: check if folder is present, and if so, try to set it's permission
err = os.Mkdir(path+"/"+f.Name, os.ModePerm) err = os.Mkdir(path+"/"+f.name, os.ModePerm)
if err != nil { if err != nil {
if verbose { if verbose {
fmt.Println("Error while making: ", path+"/"+f.Name) fmt.Println("Error while making: ", path+"/"+f.name)
fmt.Println(err) fmt.Println(err)
} }
errs = append(errs, err) errs = append(errs, err)
return return
} }
fil, err := os.Open(path + "/" + f.Name) var fil *os.File
fil, err = os.Open(path + "/" + f.name)
if err != nil { if err != nil {
if verbose { if verbose {
fmt.Println("Error while opening:", path+"/"+f.Name) fmt.Println("Error while opening:", path+"/"+f.name)
fmt.Println(err) fmt.Println(err)
} }
errs = append(errs, err) errs = append(errs, err)
@@ -273,16 +332,17 @@ func (f *File) ExtractWithOptions(path string, dereferenceSymlink, unbreakSymlin
// } // }
// errs = append(errs, err) // errs = append(errs, err)
// } // }
err = fil.Chmod(f.Permission()) err = fil.Chmod(f.Mode())
if err != nil { if err != nil {
if verbose { if verbose {
fmt.Println("Error while changing owner:", path+"/"+f.Name) fmt.Println("Error while changing owner:", path+"/"+f.name)
fmt.Println(err) fmt.Println(err)
} }
errs = append(errs, err) errs = append(errs, err)
} }
} }
children, err := f.GetChildren() var children []*File
children, err = f.GetChildren()
if err != nil { if err != nil {
if verbose { if verbose {
fmt.Println("Error getting children for:", f.Path()) fmt.Println("Error getting children for:", f.Path())
@@ -292,13 +352,12 @@ func (f *File) ExtractWithOptions(path string, dereferenceSymlink, unbreakSymlin
return return
} }
finishChan := make(chan []error) finishChan := make(chan []error)
defer close(finishChan)
for _, child := range children { for _, child := range children {
go func(child *File) { go func(child *File) {
if f.Name == "" { if f.name == "" {
finishChan <- child.ExtractWithOptions(path, dereferenceSymlink, unbreakSymlink, folderPerm, verbose) finishChan <- child.ExtractWithOptions(path, dereferenceSymlink, unbreakSymlink, folderPerm, verbose)
} else { } else {
finishChan <- child.ExtractWithOptions(path+"/"+f.Name, dereferenceSymlink, unbreakSymlink, folderPerm, verbose) finishChan <- child.ExtractWithOptions(path+"/"+f.name, dereferenceSymlink, unbreakSymlink, folderPerm, verbose)
} }
}(child) }(child)
} }
@@ -307,21 +366,22 @@ func (f *File) ExtractWithOptions(path string, dereferenceSymlink, unbreakSymlin
} }
return return
case f.IsFile(): case f.IsFile():
fil, err := os.Create(path + "/" + f.Name) var fil *os.File
fil, err = os.Create(path + "/" + f.name)
if os.IsExist(err) { if os.IsExist(err) {
err = os.Remove(path + "/" + f.Name) err = os.Remove(path + "/" + f.name)
if err != nil { if err != nil {
if verbose { if verbose {
fmt.Println("Error while making:", path+"/"+f.Name) fmt.Println("Error while making:", path+"/"+f.name)
fmt.Println(err) fmt.Println(err)
} }
errs = append(errs, err) errs = append(errs, err)
return return
} }
fil, err = os.Create(path + "/" + f.Name) fil, err = os.Create(path + "/" + f.name)
if err != nil { if err != nil {
if verbose { if verbose {
fmt.Println("Error while making:", path+"/"+f.Name) fmt.Println("Error while making:", path+"/"+f.name)
fmt.Println(err) fmt.Println(err)
} }
errs = append(errs, err) errs = append(errs, err)
@@ -329,22 +389,21 @@ func (f *File) ExtractWithOptions(path string, dereferenceSymlink, unbreakSymlin
} }
} else if err != nil { } else if err != nil {
if verbose { if verbose {
fmt.Println("Error while making:", path+"/"+f.Name) fmt.Println("Error while making:", path+"/"+f.name)
fmt.Println(err) fmt.Println(err)
} }
errs = append(errs, err) errs = append(errs, err)
return return
} //Since we will be reading from the file } //Since we will be reading from the file
_, err = io.Copy(fil, f) _, err = io.Copy(fil, f.Sys().(io.Reader))
if err != nil { if err != nil {
if verbose { if verbose {
fmt.Println("Error while Copying data to:", path+"/"+f.Name) fmt.Println("Error while Copying data to:", path+"/"+f.name)
fmt.Println(err) fmt.Println(err)
} }
errs = append(errs, err) errs = append(errs, err)
return return
} }
f.Close()
fil.Chown(int(f.r.idTable[f.in.Header.UID]), int(f.r.idTable[f.in.Header.GID])) 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 //don't mention anything when it fails. Because it fails often. Probably has something to do about uid & gid 0
// if err != nil { // if err != nil {
@@ -355,10 +414,10 @@ func (f *File) ExtractWithOptions(path string, dereferenceSymlink, unbreakSymlin
// errs = append(errs, err) // errs = append(errs, err)
// return // return
// } // }
err = fil.Chmod(f.Permission()) err = fil.Chmod(f.Mode())
if err != nil { if err != nil {
if verbose { if verbose {
fmt.Println("Error while setting permissions for:", path+"/"+f.Name) fmt.Println("Error while setting permissions for:", path+"/"+f.name)
fmt.Println(err) fmt.Println(err)
} }
errs = append(errs, err) errs = append(errs, err)
@@ -370,15 +429,15 @@ func (f *File) ExtractWithOptions(path string, dereferenceSymlink, unbreakSymlin
fil := f.GetSymlinkFile() fil := f.GetSymlinkFile()
if fil == nil { if fil == nil {
if verbose { if verbose {
fmt.Println("Symlink path(", symPath, ") is outside the archive:"+path+"/"+f.Name) fmt.Println("Symlink path(", symPath, ") is outside the archive:"+path+"/"+f.name)
} }
return return
} }
fil.Name = f.Name fil.name = f.name
extracSymErrs := fil.ExtractWithOptions(path, dereferenceSymlink, unbreakSymlink, folderPerm, verbose) extracSymErrs := fil.ExtractWithOptions(path, dereferenceSymlink, unbreakSymlink, folderPerm, verbose)
if len(extracSymErrs) > 0 { if len(extracSymErrs) > 0 {
if verbose { if verbose {
fmt.Println("Error(s) while extracting the symlink's file:", path+"/"+f.Name) fmt.Println("Error(s) while extracting the symlink's file:", path+"/"+f.name)
fmt.Println(extracSymErrs) fmt.Println(extracSymErrs)
} }
errs = append(errs, extracSymErrs...) errs = append(errs, extracSymErrs...)
@@ -392,22 +451,22 @@ func (f *File) ExtractWithOptions(path string, dereferenceSymlink, unbreakSymlin
extracSymErrs := fil.ExtractWithOptions(strings.Join(paths[:len(paths)-1], "/"), dereferenceSymlink, unbreakSymlink, folderPerm, verbose) extracSymErrs := fil.ExtractWithOptions(strings.Join(paths[:len(paths)-1], "/"), dereferenceSymlink, unbreakSymlink, folderPerm, verbose)
if len(extracSymErrs) > 0 { if len(extracSymErrs) > 0 {
if verbose { if verbose {
fmt.Println("Error(s) while extracting the symlink's file:", path+"/"+f.Name) fmt.Println("Error(s) while extracting the symlink's file:", path+"/"+f.name)
fmt.Println(extracSymErrs) fmt.Println(extracSymErrs)
} }
errs = append(errs, extracSymErrs...) errs = append(errs, extracSymErrs...)
} }
} else { } else {
if verbose { if verbose {
fmt.Println("Symlink path(", symPath, ") is outside the archive:"+path+"/"+f.Name) fmt.Println("Symlink path(", symPath, ") is outside the archive:"+path+"/"+f.name)
} }
return return
} }
} }
err = os.Symlink(f.SymlinkPath(), path+"/"+f.Name) err = os.Symlink(f.SymlinkPath(), path+"/"+f.name)
if err != nil { if err != nil {
if verbose { if verbose {
fmt.Println("Error while making symlink:", path+"/"+f.Name) fmt.Println("Error while making symlink:", path+"/"+f.name)
fmt.Println(err) fmt.Println(err)
} }
errs = append(errs, err) errs = append(errs, err)
@@ -416,34 +475,19 @@ func (f *File) ExtractWithOptions(path string, dereferenceSymlink, unbreakSymlin
return 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. //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) { func (f *File) Read(p []byte) (int, error) {
if f.IsDir() { if !f.IsFile() {
return 0, io.EOF return 0, io.EOF
} }
var err error var err error
if f.Reader == nil && f.r != nil { if f.reader == nil && f.r != nil {
f.Reader, err = f.r.newFileReader(f.in) f.reader, err = f.r.newFileReader(f.in)
if err != nil { if err != nil {
return 0, err return 0, err
} }
} }
return f.Reader.Read(p) return f.reader.Read(p)
} }
//ReadDirFromInode returns a fully populated Directory from a given Inode. //ReadDirFromInode returns a fully populated Directory from a given Inode.
@@ -453,14 +497,14 @@ func (r *Reader) readDirFromInode(i *inode.Inode) (*directory.Directory, error)
var metaOffset uint16 var metaOffset uint16
var size uint32 var size uint32
switch i.Type { switch i.Type {
case inode.BasicDirectoryType: case inode.DirType:
offset = i.Info.(inode.BasicDirectory).DirectoryIndex offset = i.Info.(inode.Dir).DirectoryIndex
metaOffset = i.Info.(inode.BasicDirectory).DirectoryOffset metaOffset = i.Info.(inode.Dir).DirectoryOffset
size = uint32(i.Info.(inode.BasicDirectory).DirectorySize) size = uint32(i.Info.(inode.Dir).DirectorySize)
case inode.ExtDirType: case inode.ExtDirType:
offset = i.Info.(inode.ExtendedDirectory).Init.DirectoryIndex offset = i.Info.(inode.ExtDir).DirectoryIndex
metaOffset = i.Info.(inode.ExtendedDirectory).Init.DirectoryOffset metaOffset = i.Info.(inode.ExtDir).DirectoryOffset
size = i.Info.(inode.ExtendedDirectory).Init.DirectorySize size = i.Info.(inode.ExtDir).DirectorySize
default: default:
return nil, errors.New("Not a directory inode") return nil, errors.New("Not a directory inode")
} }
@@ -485,7 +529,7 @@ func (r *Reader) getInodeFromEntry(en *directory.Entry) (*inode.Inode, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, err = br.Seek(int64(en.Init.Offset), io.SeekStart) _, err = br.Seek(int64(en.Offset), io.SeekStart)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+24 -17
View File
@@ -29,20 +29,20 @@ var (
func (r *Reader) newFileReader(in *inode.Inode) (*fileReader, error) { func (r *Reader) newFileReader(in *inode.Inode) (*fileReader, error) {
var rdr fileReader var rdr fileReader
rdr.in = in rdr.in = in
if in.Type != inode.BasicFileType && in.Type != inode.ExtFileType { if in.Type != inode.FileType && in.Type != inode.ExtFileType {
return nil, errPathIsNotFile return nil, errPathIsNotFile
} }
switch in.Type { switch in.Type {
case inode.BasicFileType: case inode.FileType:
fil := in.Info.(inode.BasicFile) fil := in.Info.(inode.File)
rdr.fragged = fil.Fragmented rdr.fragged = fil.Fragmented
rdr.fragOnly = fil.Init.BlockStart == 0 rdr.fragOnly = fil.BlockStart == 0
rdr.FileSize = int(fil.Init.Size) rdr.FileSize = int(fil.Size)
case inode.ExtFileType: case inode.ExtFileType:
fil := in.Info.(inode.ExtendedFile) fil := in.Info.(inode.ExtFile)
rdr.fragged = fil.Fragmented rdr.fragged = fil.Fragmented
rdr.fragOnly = fil.Init.BlockStart == 0 rdr.fragOnly = fil.BlockStart == 0
rdr.FileSize = int(fil.Init.Size) rdr.FileSize = int(fil.Size)
} }
var err error var err error
if rdr.fragged { if rdr.fragged {
@@ -57,15 +57,6 @@ func (r *Reader) newFileReader(in *inode.Inode) (*fileReader, error) {
return &rdr, nil 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) { func (f *fileReader) Read(p []byte) (int, error) {
if f.fragOnly { if f.fragOnly {
n, err := bytes.NewBuffer(f.fragmentData[f.read:]).Read(p) 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 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
}
+15 -15
View File
@@ -12,39 +12,39 @@ import (
type fragmentEntry struct { type fragmentEntry struct {
Start uint64 Start uint64
Size uint32 Size uint32
Unused uint32 // Unused uint32
} }
//GetFragmentDataFromInode returns the fragment data for a given inode. //GetFragmentDataFromInode returns the fragment data for a given inode.
//If the inode does not have a fragment, harmlessly returns an empty slice without an error. //If the inode does not have a fragment, harmlessly returns an empty slice without an error.
func (r *Reader) getFragmentDataFromInode(in *inode.Inode) ([]byte, error) { func (r *Reader) getFragmentDataFromInode(in *inode.Inode) ([]byte, error) {
var size uint32 var size uint64
var fragIndex uint32 var fragIndex uint32
var fragOffset uint32 var fragOffset uint32
if in.Type == inode.BasicFileType { if in.Type == inode.FileType {
bf := in.Info.(inode.BasicFile) bf := in.Info.(inode.File)
if !bf.Fragmented { if !bf.Fragmented {
return make([]byte, 0), nil return make([]byte, 0), nil
} }
if bf.Init.BlockStart == 0 { if bf.BlockStart == 0 {
size = bf.Init.Size size = uint64(bf.Size)
} else { } else {
size = bf.BlockSizes[len(bf.BlockSizes)-1] size = uint64(bf.BlockSizes[len(bf.BlockSizes)-1])
} }
fragIndex = bf.Init.FragmentIndex fragIndex = bf.FragmentIndex
fragOffset = bf.Init.FragmentOffset fragOffset = bf.FragmentOffset
} else if in.Type == inode.ExtFileType { } else if in.Type == inode.ExtFileType {
bf := in.Info.(inode.ExtendedFile) bf := in.Info.(inode.ExtFile)
if !bf.Fragmented { if !bf.Fragmented {
return make([]byte, 0), nil return make([]byte, 0), nil
} }
if bf.Init.BlockStart == 0 { if bf.BlockStart == 0 {
size = bf.Init.Size size = bf.Size
} else { } else {
size = bf.BlockSizes[len(bf.BlockSizes)-1] size = uint64(bf.BlockSizes[len(bf.BlockSizes)-1])
} }
fragIndex = bf.Init.FragmentIndex fragIndex = bf.FragmentIndex
fragOffset = bf.Init.FragmentOffset fragOffset = bf.FragmentOffset
} else { } else {
return nil, errors.New("Inode type not supported") return nil, errors.New("Inode type not supported")
} }
+6 -3
View File
@@ -3,14 +3,17 @@ module github.com/CalebQ42/squashfs
go 1.15 go 1.15
require ( require (
github.com/CalebQ42/GoAppImage v0.4.0 github.com/CalebQ42/GoAppImage v0.5.0
github.com/adrg/xdg v0.2.3 // indirect github.com/adrg/xdg v0.2.3 // indirect
github.com/google/go-cmp v0.5.4 // indirect github.com/google/go-cmp v0.5.4 // indirect
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
github.com/klauspost/compress v1.11.6
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/pierrec/lz4/v4 v4.1.3
github.com/smartystreets/assertions v1.2.0 // indirect github.com/smartystreets/assertions v1.2.0 // indirect
github.com/ulikunitz/xz v0.5.8 github.com/stretchr/testify v1.7.0 // indirect
github.com/ulikunitz/xz v0.5.9
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
) )
+13 -6
View File
@@ -1,5 +1,5 @@
github.com/CalebQ42/GoAppImage v0.4.0 h1:aF+Y/vyo/RGhoyZEW1CMY6WyRWrZZO4ydsRFAtIGnaY= github.com/CalebQ42/GoAppImage v0.5.0 h1:znoKNXtliH754tS9sYwyOIg/0wFDjFN5Twc7PAh1rSM=
github.com/CalebQ42/GoAppImage v0.4.0/go.mod h1:qHudJKAn/dlkNWNnH4h1YKXp29EZ7Bppsn7sNP2HuvU= github.com/CalebQ42/GoAppImage v0.5.0/go.mod h1:qHudJKAn/dlkNWNnH4h1YKXp29EZ7Bppsn7sNP2HuvU=
github.com/adrg/xdg v0.2.2 h1:A7ZHKRz5KGOLJX/bg7IPzStryhvCzAE1wX+KWawPiAo= github.com/adrg/xdg v0.2.2 h1:A7ZHKRz5KGOLJX/bg7IPzStryhvCzAE1wX+KWawPiAo=
github.com/adrg/xdg v0.2.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ= github.com/adrg/xdg v0.2.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ=
github.com/adrg/xdg v0.2.3 h1:GxXngdYxNDkoUvZXjNJGwqZxWXi43MKbOOlA/00qZi4= github.com/adrg/xdg v0.2.3 h1:GxXngdYxNDkoUvZXjNJGwqZxWXi43MKbOOlA/00qZi4=
@@ -18,6 +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/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 h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/compress v1.11.6 h1:EgWPCW6O3n1D5n99Zq3xXBt9uCwRGvpwGOusOLNBRSQ=
github.com/klauspost/compress v1.11.6/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 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/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -27,6 +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/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 h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pierrec/lz4/v4 v4.1.3 h1:/dvQpkb0o1pVlSgKNQqfkavlnXaIK+hJ0LXsKRUN9D4=
github.com/pierrec/lz4/v4 v4.1.3/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
@@ -35,11 +39,14 @@ github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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 h1:KcZJmh6nFIBeJzTugn5JTU6OOyG0lDOo3R9KwTxTYbo=
go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I= go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
@@ -65,5 +72,5 @@ gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+40 -5
View File
@@ -3,11 +3,36 @@ package compression
import ( import (
"bytes" "bytes"
"compress/zlib" "compress/zlib"
"encoding/binary"
"io" "io"
) )
type gzipInit struct {
CompressionLevel int32
WindowSize int16
Strategies int16
}
//Gzip is a decompressor for gzip type compression. Uses zlib for compression and decompression //Gzip is a decompressor for gzip type compression. Uses zlib for compression and decompression
type Gzip struct{} type Gzip struct {
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
err := binary.Read(r, binary.LittleEndian, &gzip.gzipInit)
if err != nil {
return nil, err
}
//TODO: proper support for window size and strategies
gzip.HasCustomWindow = gzip.WindowSize != 15
gzip.HasStrategies = gzip.Strategies != 0 && gzip.Strategies != 1
return &gzip, nil
}
//Decompress reads the entirety of the given reader and returns it uncompressed as a byte slice. //Decompress reads the entirety of the given reader and returns it uncompressed as a byte slice.
func (g *Gzip) Decompress(r io.Reader) ([]byte, error) { func (g *Gzip) Decompress(r io.Reader) ([]byte, error) {
@@ -26,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. //Compress compresses the given data (as a byte array) and returns the compressed data.
func (g *Gzip) Compress(data []byte) ([]byte, error) { func (g *Gzip) Compress(data []byte) ([]byte, error) {
var buf bytes.Buffer var buf bytes.Buffer
wrt := zlib.NewWriter(&buf) var err error
defer wrt.Close() if g.wrt == nil {
_, err := wrt.Write(data) if g.CompressionLevel == 0 {
g.wrt = zlib.NewWriter(&buf)
} else {
g.wrt, err = zlib.NewWriterLevel(&buf, int(g.CompressionLevel))
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = wrt.Flush() }
}
wrt, err := zlib.NewWriterLevel(&buf, int(g.CompressionLevel))
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, err = wrt.Write(data)
if err != nil {
return nil, err
}
wrt.Close()
return buf.Bytes(), nil return buf.Bytes(), nil
} }
+55
View File
@@ -0,0 +1,55 @@
package compression
import (
"bytes"
"encoding/binary"
"io"
"github.com/pierrec/lz4/v4"
)
//Lz4 is a Lz4 Compressor/Decompressor
type Lz4 struct {
HC bool
}
//NewLz4CompressorWithOptions creates a new lz4 compressor/decompressor with options read from the given reader.
func NewLz4CompressorWithOptions(r io.Reader) (*Lz4, error) {
var lz4 Lz4
var init struct {
Version int32
Flags int32
}
err := binary.Read(r, binary.LittleEndian, &init)
if err != nil {
return nil, err
}
lz4.HC = init.Flags == 1
return &lz4, nil
}
//Decompress decompresses all data from r and returns the uncompressed bytes
func (l *Lz4) Decompress(r io.Reader) ([]byte, error) {
rdr := lz4.NewReader(r)
var buf bytes.Buffer
_, 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 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 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
}
+51
View File
@@ -0,0 +1,51 @@
package compression
import (
"bytes"
"encoding/binary"
"io"
"github.com/klauspost/compress/zstd"
)
//Zstd is a zstd compressor/decompressor
type Zstd struct {
CompressionLevel int32
}
//NewZstdCompressorWithOptions creates a new Zstd with options read from the given reader
func NewZstdCompressorWithOptions(r io.Reader) (*Zstd, error) {
var zstd Zstd
err := binary.Read(r, binary.LittleEndian, &zstd)
if err != nil {
return nil, err
}
return &zstd, nil
}
//Decompress decompresses all data from the reader and returns the uncompressed data
func (z *Zstd) Decompress(r io.Reader) ([]byte, error) {
rdr, err := zstd.NewReader(r)
if err != nil {
return nil, err
}
defer rdr.Close()
var buf bytes.Buffer
_, 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
}
+6 -6
View File
@@ -13,8 +13,8 @@ type Header struct {
InodeNumber uint32 InodeNumber uint32
} }
//EntryInit is the values that can be easily decoded //EntryRaw is the values that can be easily decoded
type EntryInit struct { type EntryRaw struct {
Offset uint16 Offset uint16
InodeOffset int16 InodeOffset int16
Type uint16 Type uint16
@@ -23,19 +23,19 @@ type EntryInit struct {
//Entry is an entry in a directory. //Entry is an entry in a directory.
type Entry struct { type Entry struct {
Init EntryInit *Header
Name string Name string
Header *Header EntryRaw
} }
//NewEntry creates a new directory entry //NewEntry creates a new directory entry
func NewEntry(rdr io.Reader) (Entry, error) { func NewEntry(rdr io.Reader) (Entry, error) {
var entry Entry var entry Entry
err := binary.Read(rdr, binary.LittleEndian, &entry.Init) err := binary.Read(rdr, binary.LittleEndian, &entry.EntryRaw)
if err != nil { if err != nil {
return Entry{}, err return Entry{}, err
} }
tmp := make([]byte, entry.Init.NameSize+1) tmp := make([]byte, entry.EntryRaw.NameSize+1)
err = binary.Read(rdr, binary.LittleEndian, &tmp) err = binary.Read(rdr, binary.LittleEndian, &tmp)
if err != nil { if err != nil {
return Entry{}, err return Entry{}, err
+88 -86
View File
@@ -5,17 +5,18 @@ import (
"io" "io"
) )
//The different types of inodes as defined by inodetype
const ( const (
BasicDirectoryType = iota + 1 DirType = iota + 1
BasicFileType FileType
BasicSymlinkType SymType
BasicBlockDeviceType BlockDevType
BasicCharDeviceType CharDevType
BasicFifoType FifoType
BasicSocketType SocketType
ExtDirType ExtDirType
ExtFileType ExtFileType
ExtSymlinkType ExtSymType
ExtBlockDeviceType ExtBlockDeviceType
ExtCharDeviceType ExtCharDeviceType
ExtFifoType ExtFifoType
@@ -32,8 +33,8 @@ type Header struct {
Number uint32 Number uint32
} }
//BasicDirectory is self explainatory //Dir is self explainatory
type BasicDirectory struct { type Dir struct {
DirectoryIndex uint32 DirectoryIndex uint32
HardLinks uint32 HardLinks uint32
DirectorySize uint16 DirectorySize uint16
@@ -41,8 +42,8 @@ type BasicDirectory struct {
ParentInodeNumber uint32 ParentInodeNumber uint32
} }
//ExtendedDirectoryInit is the information that can be directoy decoded //ExtDirInit is the information that can be directoy decoded
type ExtendedDirectoryInit struct { type ExtDirInit struct {
HardLinks uint32 HardLinks uint32
DirectorySize uint32 DirectorySize uint32
DirectoryIndex uint32 DirectoryIndex uint32
@@ -52,21 +53,22 @@ type ExtendedDirectoryInit struct {
XattrIndex uint32 XattrIndex uint32
} }
//ExtendedDirectory is a directory with extra info //ExtDir is a directory with extra info
type ExtendedDirectory struct { type ExtDir struct {
Init ExtendedDirectoryInit Indexes []DirIndex
Indexes []DirectoryIndex ExtDirInit
} }
//NewExtendedDirectory creates a new ExtendedDirectory //NewExtendedDirectory creates a new ExtendedDirectory
func NewExtendedDirectory(rdr io.Reader) (ExtendedDirectory, error) { func NewExtendedDirectory(rdr io.Reader) (ExtDir, error) {
var inode ExtendedDirectory var inode ExtDir
err := binary.Read(rdr, binary.LittleEndian, &inode.Init) err := binary.Read(rdr, binary.LittleEndian, &inode.ExtDirInit)
if err != nil { if err != nil {
return inode, err return inode, err
} }
for i := uint16(0); i < inode.Init.IndexCount; i++ { for i := uint16(0); i < inode.IndexCount; i++ {
tmp, err := NewDirectoryIndex(rdr) var tmp DirIndex
tmp, err = NewDirectoryIndex(rdr)
if err != nil { if err != nil {
return inode, err return inode, err
} }
@@ -75,27 +77,27 @@ func NewExtendedDirectory(rdr io.Reader) (ExtendedDirectory, error) {
return inode, err return inode, err
} }
//DirectoryIndexInit holds the values that can be easily decoded //DirIndexInit holds the values that can be easily decoded
type DirectoryIndexInit struct { type DirIndexInit struct {
Offset uint32 Offset uint32
DirTableOffset uint32 DirTableOffset uint32
NameSize uint32 NameSize uint32
} }
//DirectoryIndex is a quick lookup provided by an ExtendedDirectory //DirIndex is a quick lookup provided by an ExtendedDirectory
type DirectoryIndex struct { type DirIndex struct {
Init DirectoryIndexInit
Name string Name string
DirIndexInit
} }
//NewDirectoryIndex return a new DirectoryIndex //NewDirectoryIndex return a new DirectoryIndex
func NewDirectoryIndex(rdr io.Reader) (DirectoryIndex, error) { func NewDirectoryIndex(rdr io.Reader) (DirIndex, error) {
var index DirectoryIndex var index DirIndex
err := binary.Read(rdr, binary.LittleEndian, &index.Init) err := binary.Read(rdr, binary.LittleEndian, &index.DirIndexInit)
if err != nil { if err != nil {
return index, err 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) err = binary.Read(rdr, binary.LittleEndian, &tmp)
if err != nil { if err != nil {
return index, err return index, err
@@ -104,31 +106,31 @@ func NewDirectoryIndex(rdr io.Reader) (DirectoryIndex, error) {
return index, nil return index, nil
} }
//BasicFileInit is the information that can be directoy decoded //FileInit is the information that can be directly decoded
type BasicFileInit struct { type FileInit struct {
BlockStart uint32 BlockStart uint32
FragmentIndex uint32 FragmentIndex uint32
FragmentOffset uint32 FragmentOffset uint32
Size uint32 Size uint32
} }
//BasicFile is self explainatory //File is self explainatory
type BasicFile struct { type File struct {
Init BasicFileInit
BlockSizes []uint32 BlockSizes []uint32
Fragmented bool Fragmented bool
FileInit
} }
//NewBasicFile creates a new BasicFile //NewFile creates a new File
func NewBasicFile(rdr io.Reader, blockSize uint32) (BasicFile, error) { func NewFile(rdr io.Reader, blockSize uint32) (File, error) {
var inode BasicFile var inode File
err := binary.Read(rdr, binary.LittleEndian, &inode.Init) err := binary.Read(rdr, binary.LittleEndian, &inode.FileInit)
if err != nil { if err != nil {
return inode, err return inode, err
} }
inode.Fragmented = inode.Init.FragmentIndex != 0xFFFFFFFF inode.Fragmented = inode.FragmentIndex != 0xFFFFFFFF
blocks := inode.Init.Size / blockSize blocks := inode.Size / blockSize
if inode.Init.Size%blockSize > 0 { if inode.Size%blockSize > 0 {
blocks++ blocks++
} }
inode.BlockSizes = make([]uint32, blocks, blocks) inode.BlockSizes = make([]uint32, blocks, blocks)
@@ -136,10 +138,10 @@ func NewBasicFile(rdr io.Reader, blockSize uint32) (BasicFile, error) {
return inode, err return inode, err
} }
//ExtendedFileInit is the information that can be directly decoded //ExtFileInit is the information that can be directly decoded
type ExtendedFileInit struct { type ExtFileInit struct {
BlockStart uint32 BlockStart uint64
Size uint32 Size uint64
Sparse uint64 Sparse uint64
HardLinks uint32 HardLinks uint32
FragmentIndex uint32 FragmentIndex uint32
@@ -147,23 +149,23 @@ type ExtendedFileInit struct {
XattrIndex uint32 XattrIndex uint32
} }
//ExtendedFile is a file with more information //ExtFile is a file with more information
type ExtendedFile struct { type ExtFile struct {
Init ExtendedFileInit
BlockSizes []uint32 BlockSizes []uint32
Fragmented bool Fragmented bool
ExtFileInit
} }
//NewExtendedFile creates a new ExtendedFile //NewExtendedFile creates a new ExtendedFile
func NewExtendedFile(rdr io.Reader, blockSize uint32) (ExtendedFile, error) { func NewExtendedFile(rdr io.Reader, blockSize uint32) (ExtFile, error) {
var inode ExtendedFile var inode ExtFile
err := binary.Read(rdr, binary.LittleEndian, &inode.Init) err := binary.Read(rdr, binary.LittleEndian, &inode.ExtFileInit)
if err != nil { if err != nil {
return inode, err return inode, err
} }
inode.Fragmented = inode.Init.FragmentIndex != 0xFFFFFFFF inode.Fragmented = inode.FragmentIndex != 0xFFFFFFFF
blocks := inode.Init.Size / blockSize blocks := inode.Size / uint64(blockSize)
if inode.Init.Size%blockSize > 0 { if inode.Size%uint64(blockSize) > 0 {
blocks++ blocks++
} }
inode.BlockSizes = make([]uint32, blocks, blocks) inode.BlockSizes = make([]uint32, blocks, blocks)
@@ -171,27 +173,27 @@ func NewExtendedFile(rdr io.Reader, blockSize uint32) (ExtendedFile, error) {
return inode, err return inode, err
} }
//BasicSymlinkInit is all the values that can be directly decoded //SymInit is all the values that can be directly decoded
type BasicSymlinkInit struct { type SymInit struct {
HardLinks uint32 HardLinks uint32
TargetPathSize uint32 TargetPathSize uint32
} }
//BasicSymlink is a symlink //Sym is a symlink
type BasicSymlink struct { type Sym struct {
Init BasicSymlinkInit
targetPath []byte //len is TargetPathSize
Path string Path string
targetPath []byte //len is TargetPathSize
SymInit
} }
//NewBasicSymlink creates a new BasicSymlink //NewSymlink creates a new Symlink
func NewBasicSymlink(rdr io.Reader) (BasicSymlink, error) { func NewSymlink(rdr io.Reader) (Sym, error) {
var inode BasicSymlink var inode Sym
err := binary.Read(rdr, binary.LittleEndian, &inode.Init) err := binary.Read(rdr, binary.LittleEndian, &inode.SymInit)
if err != nil { if err != nil {
return inode, err 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) err = binary.Read(rdr, binary.LittleEndian, &inode.targetPath)
if err != nil { if err != nil {
return inode, err return inode, err
@@ -200,28 +202,28 @@ func NewBasicSymlink(rdr io.Reader) (BasicSymlink, error) {
return inode, err return inode, err
} }
//ExtendedSymlinkInit is all the values that can be directly decoded //ExtSymInit is all the values that can be directly decoded
type ExtendedSymlinkInit struct { type ExtSymInit struct {
HardLinks uint32 HardLinks uint32
TargetPathSize uint32 TargetPathSize uint32
} }
//ExtendedSymlink is a symlink with extra information //ExtSym is a symlink with extra information
type ExtendedSymlink struct { type ExtSym struct {
Init ExtendedSymlinkInit
targetPath []uint8
Path string Path string
targetPath []uint8
ExtSymInit
XattrIndex uint32 XattrIndex uint32
} }
//NewExtendedSymlink creates a new ExtendedSymlink //NewExtendedSymlink creates a new ExtendedSymlink
func NewExtendedSymlink(rdr io.Reader) (ExtendedSymlink, error) { func NewExtendedSymlink(rdr io.Reader) (ExtSym, error) {
var inode ExtendedSymlink var inode ExtSym
err := binary.Read(rdr, binary.LittleEndian, &inode.Init) err := binary.Read(rdr, binary.LittleEndian, &inode.ExtSymInit)
if err != nil { if err != nil {
return inode, err 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) err = binary.Read(rdr, binary.LittleEndian, &inode.targetPath)
if err != nil { if err != nil {
return inode, err return inode, err
@@ -231,25 +233,25 @@ func NewExtendedSymlink(rdr io.Reader) (ExtendedSymlink, error) {
return inode, err return inode, err
} }
//BasicDevice is a device //Device is a device
type BasicDevice struct { type Device struct {
HardLinks uint32 HardLinks uint32
Device uint32 Device uint32
} }
//ExtendedDevice is a device with more info //ExtDevice is a device with more info
type ExtendedDevice struct { type ExtDevice struct {
BasicDevice Device
XattrIndex uint32 XattrIndex uint32
} }
//BasicIPC is a Fifo or Socket device //IPC is a Fifo or Socket device
type BasicIPC struct { type IPC struct {
HardLink uint32 HardLink uint32
} }
//ExtendedIPC is a IPC device with extra info //ExtIPC is a IPC device with extra info
type ExtendedIPC struct { type ExtIPC struct {
BasicIPC IPC
XattrIndex uint32 XattrIndex uint32
} }
+29 -24
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{} //Info holds the actual Inode. Due to each inode type being a different type, it's store as an interface{}
type Inode struct { 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 //ProcessInode tries to read an inode from the BlockReader
@@ -23,94 +23,99 @@ func ProcessInode(br io.Reader, blockSize uint32) (*Inode, error) {
} }
var info interface{} var info interface{}
switch head.InodeType { switch head.InodeType {
case BasicDirectoryType: case DirType:
var inode BasicDirectory var inode Dir
err = binary.Read(br, binary.LittleEndian, &inode) err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil { if err != nil {
return nil, err return nil, err
} }
info = inode info = inode
case BasicFileType: case FileType:
inode, err := NewBasicFile(br, blockSize) var inode File
inode, err = NewFile(br, blockSize)
if err != nil { if err != nil {
return nil, err return nil, err
} }
info = inode info = inode
case BasicSymlinkType: case SymType:
inode, err := NewBasicSymlink(br) var inode Sym
inode, err = NewSymlink(br)
if err != nil { if err != nil {
return nil, err return nil, err
} }
info = inode info = inode
case BasicBlockDeviceType: case BlockDevType:
var inode BasicDevice var inode Device
err = binary.Read(br, binary.LittleEndian, &inode) err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil { if err != nil {
return nil, err return nil, err
} }
info = inode info = inode
case BasicCharDeviceType: case CharDevType:
var inode BasicDevice var inode Device
err = binary.Read(br, binary.LittleEndian, &inode) err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil { if err != nil {
return nil, err return nil, err
} }
info = inode info = inode
case BasicFifoType: case FifoType:
var inode BasicIPC var inode IPC
err = binary.Read(br, binary.LittleEndian, &inode) err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil { if err != nil {
return nil, err return nil, err
} }
info = inode info = inode
case BasicSocketType: case SocketType:
var inode BasicIPC var inode IPC
err = binary.Read(br, binary.LittleEndian, &inode) err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil { if err != nil {
return nil, err return nil, err
} }
info = inode info = inode
case ExtDirType: case ExtDirType:
inode, err := NewExtendedDirectory(br) var inode ExtDir
inode, err = NewExtendedDirectory(br)
if err != nil { if err != nil {
return nil, err return nil, err
} }
info = inode info = inode
case ExtFileType: case ExtFileType:
inode, err := NewExtendedFile(br, blockSize) var inode ExtFile
inode, err = NewExtendedFile(br, blockSize)
if err != nil { if err != nil {
return nil, err return nil, err
} }
info = inode info = inode
case ExtSymlinkType: case ExtSymType:
inode, err := NewExtendedSymlink(br) var inode ExtSym
inode, err = NewExtendedSymlink(br)
if err != nil { if err != nil {
return nil, err return nil, err
} }
info = inode info = inode
case ExtBlockDeviceType: case ExtBlockDeviceType:
var inode ExtendedDevice var inode ExtDevice
err = binary.Read(br, binary.LittleEndian, &inode) err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil { if err != nil {
return nil, err return nil, err
} }
info = inode info = inode
case ExtCharDeviceType: case ExtCharDeviceType:
var inode ExtendedDevice var inode ExtDevice
err = binary.Read(br, binary.LittleEndian, &inode) err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil { if err != nil {
return nil, err return nil, err
} }
info = inode info = inode
case ExtFifoType: case ExtFifoType:
var inode ExtendedIPC var inode ExtIPC
err = binary.Read(br, binary.LittleEndian, &inode) err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil { if err != nil {
return nil, err return nil, err
} }
info = inode info = inode
case ExtSocketType: case ExtSocketType:
var inode ExtendedIPC var inode ExtIPC
err = binary.Read(br, binary.LittleEndian, &inode) err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil { if err != nil {
return nil, err 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. //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 { type metadataReader struct {
s *Reader s *Reader
offset int64
headers []*metadata headers []*metadata
data []byte data []byte
offset int64
readOffset int readOffset int
} }
+86 -21
View File
@@ -5,6 +5,7 @@ import (
"errors" "errors"
"io" "io"
"math" "math"
"time"
"github.com/CalebQ42/squashfs/internal/compression" "github.com/CalebQ42/squashfs/internal/compression"
"github.com/CalebQ42/squashfs/internal/inode" "github.com/CalebQ42/squashfs/internal/inode"
@@ -21,20 +22,24 @@ var (
errIncompatibleCompression = errors.New("Compression type unsupported") errIncompatibleCompression = errors.New("Compression type unsupported")
//ErrCompressorOptions is returned if compressor options is present. It's not currently supported. //ErrCompressorOptions is returned if compressor options is present. It's not currently supported.
errCompressorOptions = errors.New("Compressor options is not currently supported") errCompressorOptions = errors.New("Compressor options is not currently supported")
//ErrOptions is returned when compression options that I haven't tested is set. When this is returned, the Reader is also returned.
ErrOptions = errors.New("Possibly incompatible compressor options")
) )
//Reader processes and reads a squashfs archive. //Reader processes and reads a squashfs archive.
type Reader struct { type Reader struct {
r io.ReaderAt r io.ReaderAt
super superblock
flags superblockFlags
decompressor compression.Decompressor decompressor compression.Decompressor
root *File
fragOffsets []uint64 fragOffsets []uint64
idTable []uint32 idTable []uint32
super superblock
flags superblockFlags
} }
//NewSquashfsReader returns a new squashfs.Reader from an io.ReaderAt //NewSquashfsReader returns a new squashfs.Reader from an io.ReaderAt
func NewSquashfsReader(r io.ReaderAt) (*Reader, error) { func NewSquashfsReader(r io.ReaderAt) (*Reader, error) {
hasUnsupportedOptions := false
var rdr Reader var rdr Reader
rdr.r = r rdr.r = r
err := binary.Read(io.NewSectionReader(rdr.r, 0, int64(binary.Size(rdr.super))), binary.LittleEndian, &rdr.super) err := binary.Read(io.NewSectionReader(rdr.r, 0, int64(binary.Size(rdr.super))), binary.LittleEndian, &rdr.super)
@@ -50,8 +55,19 @@ func NewSquashfsReader(r io.ReaderAt) (*Reader, error) {
rdr.flags = rdr.super.GetFlags() rdr.flags = rdr.super.GetFlags()
if rdr.flags.CompressorOptions { if rdr.flags.CompressorOptions {
switch rdr.super.CompressionType { switch rdr.super.CompressionType {
case xzCompression: case GzipCompression:
xz, err := compression.NewXzCompressorWithOptions(io.NewSectionReader(rdr.r, int64(binary.Size(rdr.super)), 1000)) //1000 is technically too much, but it's just an easy way to do it. var gzip *compression.Gzip
gzip, err = compression.NewGzipCompressorWithOptions(io.NewSectionReader(rdr.r, int64(binary.Size(rdr.super)), 8))
if err != nil {
return nil, err
}
if gzip.HasCustomWindow || gzip.HasStrategies {
hasUnsupportedOptions = true
}
rdr.decompressor = gzip
case XzCompression:
var xz *compression.Xz
xz, err = compression.NewXzCompressorWithOptions(io.NewSectionReader(rdr.r, int64(binary.Size(rdr.super)), 8))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -59,17 +75,35 @@ func NewSquashfsReader(r io.ReaderAt) (*Reader, error) {
return nil, errors.New("XZ compression options has filters. These are not yet supported") return nil, errors.New("XZ compression options has filters. These are not yet supported")
} }
rdr.decompressor = xz rdr.decompressor = xz
case Lz4Compression:
var lz4 *compression.Lz4
lz4, err = compression.NewLz4CompressorWithOptions(io.NewSectionReader(rdr.r, int64(binary.Size(rdr.super)), 8))
if err != nil {
return nil, err
}
rdr.decompressor = lz4
case ZstdCompression:
var zstd *compression.Zstd
zstd, err = compression.NewZstdCompressorWithOptions(io.NewSectionReader(rdr.r, int64(binary.Size(rdr.super)), 4))
if err != nil {
return nil, err
}
rdr.decompressor = zstd
default: default:
return nil, errCompressorOptions return nil, errIncompatibleCompression
} }
} else { } else {
switch rdr.super.CompressionType { switch rdr.super.CompressionType {
case gzipCompression: case GzipCompression:
rdr.decompressor = &compression.Gzip{} rdr.decompressor = &compression.Gzip{}
case lzmaCompression: case LzmaCompression:
rdr.decompressor = &compression.Lzma{} rdr.decompressor = &compression.Lzma{}
case xzCompression: case XzCompression:
rdr.decompressor = &compression.Xz{} rdr.decompressor = &compression.Xz{}
case Lz4Compression:
rdr.decompressor = &compression.Lz4{}
case ZstdCompression:
rdr.decompressor = &compression.Zstd{}
default: default:
//TODO: all compression types. //TODO: all compression types.
return nil, errIncompatibleCompression return nil, errIncompatibleCompression
@@ -111,42 +145,69 @@ func NewSquashfsReader(r io.ReaderAt) (*Reader, error) {
} }
unread -= read unread -= read
} }
if hasUnsupportedOptions {
return &rdr, ErrOptions
}
return &rdr, nil return &rdr, nil
} }
//ModTime is the last time the file was modified/created.
func (r *Reader) ModTime() time.Time {
return time.Unix(int64(r.super.CreationTime), 0)
}
//ExtractTo tries to extract ALL files to the given path. This is the same as getting the root folder and extracting that.
func (r *Reader) ExtractTo(path string) []error {
if r.root == nil {
_, err := r.GetRootFolder()
if err != nil {
return []error{err}
}
}
return r.root.ExtractTo(path)
}
//GetRootFolder returns a squashfs.File that references the root directory of the squashfs archive. //GetRootFolder returns a squashfs.File that references the root directory of the squashfs archive.
func (r *Reader) GetRootFolder() (root *File, err error) { func (r *Reader) GetRootFolder() (*File, error) {
root = new(File) if r.root != nil {
return r.root, nil
}
mr, err := r.newMetadataReaderFromInodeRef(r.super.RootInodeRef) mr, err := r.newMetadataReaderFromInodeRef(r.super.RootInodeRef)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var root File
root.in, err = inode.ProcessInode(mr, r.super.BlockSize) root.in, err = inode.ProcessInode(mr, r.super.BlockSize)
if err != nil { if err != nil {
return nil, err return nil, err
} }
root.path = "/" root.dir = "/"
root.filType = root.in.Type root.filType = root.in.Type
root.r = r root.r = r
return root, nil r.root = &root
return r.root, nil
} }
//GetAllFiles returns a slice of ALL files and folders contained in the squashfs. //GetAllFiles returns a slice of ALL files and folders contained in the squashfs.
func (r *Reader) GetAllFiles() (fils []*File, err error) { func (r *Reader) GetAllFiles() (fils []*File, err error) {
root, err := r.GetRootFolder() if r.root == nil {
_, err := r.GetRootFolder()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return root.GetChildrenRecursively() }
return r.root.GetChildrenRecursively()
} }
//FindFile returns the first file (in the same order as Reader.GetAllFiles) that the given function returns true for. Returns nil if nothing is found. //FindFile returns the first file (in the same order as Reader.GetAllFiles) that the given function returns true for. Returns nil if nothing is found.
func (r *Reader) FindFile(query func(*File) bool) *File { func (r *Reader) FindFile(query func(*File) bool) *File {
root, err := r.GetRootFolder() if r.root == nil {
_, err := r.GetRootFolder()
if err != nil { if err != nil {
return nil return nil
} }
fils, err := root.GetChildren() }
fils, err := r.root.GetChildren()
if err != nil { if err != nil {
return nil return nil
} }
@@ -182,11 +243,13 @@ func (r *Reader) FindFile(query func(*File) bool) *File {
//FindAll returns all files where the given function returns true. //FindAll returns all files where the given function returns true.
func (r *Reader) FindAll(query func(*File) bool) (all []*File) { func (r *Reader) FindAll(query func(*File) bool) (all []*File) {
root, err := r.GetRootFolder() if r.root == nil {
_, err := r.GetRootFolder()
if err != nil { if err != nil {
return nil return nil
} }
fils, err := root.GetChildrenRecursively() }
fils, err := r.root.GetChildrenRecursively()
if err != nil { if err != nil {
return nil return nil
} }
@@ -199,10 +262,12 @@ func (r *Reader) FindAll(query func(*File) bool) (all []*File) {
} }
//GetFileAtPath will return the file at the given path. If the file cannot be found, will return nil. //GetFileAtPath will return the file at the given path. If the file cannot be found, will return nil.
func (r *Reader) GetFileAtPath(path string) *File { func (r *Reader) GetFileAtPath(filepath string) *File {
dir, err := r.GetRootFolder() if r.root == nil {
_, err := r.GetRootFolder()
if err != nil { if err != nil {
return nil return nil
} }
return dir.GetFileAtPath(path) }
return r.root.GetFileAtPath(filepath)
} }
+229
View File
@@ -0,0 +1,229 @@
package squashfs
import (
"fmt"
"io"
"net/http"
"os"
"os/exec"
"strconv"
"testing"
"time"
goappimage "github.com/CalebQ42/GoAppImage"
)
const (
downloadURL = "https://github.com/srevinsaju/Firefox-Appimage/releases/download/firefox-v84.0.r20201221152838/firefox-84.0.r20201221152838-x86_64.AppImage"
appImageName = "Ultimaker_Cura-4.8.0.AppImage"
squashfsName = "balenaEtcher-1.5.113-x64.AppImage.sfs"
)
func TestSquashfs(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
squashFil, err := os.Open(wd + "/testing/" + squashfsName)
if err != nil {
t.Fatal(err)
}
rdr, err := NewSquashfsReader(squashFil)
if err != nil {
t.Fatal(err)
}
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) {
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)
}
defer aiFil.Close()
stat, _ := aiFil.Stat()
os.RemoveAll(wd + "/testing/firefox")
ai := goappimage.NewAppImage(wd + "/testing/" + appImageName)
rdr, err := NewSquashfsReader(io.NewSectionReader(aiFil, ai.Offset, stat.Size()-ai.Offset))
if err != nil {
t.Fatal(err)
}
desktop := rdr.GetFileAtPath("*.desktop")
if desktop == nil {
t.Fatal("Problema!")
}
os.Remove(wd + "/testing/cura.desktop")
deskFil, _ := os.Create(wd + "/testing/cura.desktop")
io.Copy(deskFil, desktop.Sys().(io.Reader))
t.Fatal("No problemo!")
}
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 {
return err
}
defer appImage.Close()
check := http.Client{
CheckRedirect: func(r *http.Request, _ []*http.Request) error {
r.URL.Opaque = r.URL.Path
return nil
},
}
resp, err := check.Get(downloadURL)
if err != nil {
return err
}
defer resp.Body.Close()
_, err = io.Copy(appImage, resp.Body)
if err != nil {
return err
}
return nil
}
func TestCreateSquashFromAppImage(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.Mkdir(wd+"/testing", 0777)
if err != nil && !os.IsExist(err) {
t.Fatal(err)
}
_, err = os.Open(wd + "/testing/" + appImageName)
if os.IsNotExist(err) {
err = downloadTestAppImage(wd + "/testing")
if err != nil {
t.Fatal(err)
}
_, err = os.Open(wd + "/testing/" + appImageName)
if err != nil {
t.Fatal(err)
}
} else if err != nil {
t.Fatal(err)
}
ai := goappimage.NewAppImage(wd + "/testing/" + appImageName)
aiFil, err := os.Open(wd + "/testing/" + appImageName)
if err != nil {
t.Fatal(err)
}
defer aiFil.Close()
aiFil.Seek(ai.Offset, 0)
os.Remove(wd + "/testing/" + appImageName + ".squashfs")
aiSquash, err := os.Create(wd + "/testing/" + appImageName + ".squashfs")
if err != nil {
t.Fatal(err)
}
_, err = io.Copy(aiSquash, aiFil)
if err != nil {
t.Fatal(err)
}
}
-136
View File
@@ -1,136 +0,0 @@
package squashfs
import (
"io"
"net/http"
"os"
"testing"
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
)
//Right now, don't use. Arch linux sfs uses XZ compression and when tested, most files just completely fail to extract.
func TestSquashfs(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
squashFil, err := os.Open(wd + "/testing/" + squashfsName)
if err != nil {
t.Fatal(err)
}
rdr, err := NewSquashfsReader(squashFil)
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)
}
func TestAppImage(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) {
downloadTestAppImage(t, wd+"/testing")
aiFil, err = os.Open(wd + "/testing/" + appImageName)
if err != nil {
t.Fatal(err)
}
} else if err != nil {
t.Fatal(err)
}
defer aiFil.Close()
stat, _ := aiFil.Stat()
ai := goappimage.NewAppImage(wd + "/testing/" + appImageName)
rdr, err := NewSquashfsReader(io.NewSectionReader(aiFil, ai.Offset, stat.Size()-ai.Offset))
if err != nil {
t.Fatal(err)
}
fil := rdr.GetFileAtPath(".DirIcon")
if fil == nil {
t.Fatal("Can't find desktop file")
}
errs := fil.ExtractSymlink(wd + "/testing/")
if len(errs) > 0 {
t.Fatal(errs)
}
// os.RemoveAll(wd + "/testing/" + appImageName + ".d")
// root, _ := rdr.GetRootFolder()
// errs := root.ExtractWithOptions(wd+"/testing/"+appImageName+".d", true, os.ModePerm, true)
// t.Fatal(errs)
t.Fatal("No problemo!")
}
func downloadTestAppImage(t *testing.T, dir string) {
//seems to time out on slow connections. Might fix that at some point... or not
os.Mkdir(dir, 0777)
appImage, err := os.Create(dir + "/" + appImageName)
if err != nil {
t.Fatal(err)
}
defer appImage.Close()
check := http.Client{
CheckRedirect: func(r *http.Request, via []*http.Request) error {
r.URL.Opaque = r.URL.Path
return nil
},
}
resp, err := check.Get(downloadURL)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
_, err = io.Copy(appImage, resp.Body)
if err != nil {
t.Fatal(err)
}
}
func TestCreateSquashFromAppImage(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.Mkdir(wd+"/testing", 0777)
if err != nil && !os.IsExist(err) {
t.Fatal(err)
}
_, err = os.Open(wd + "/testing/" + appImageName)
if os.IsNotExist(err) {
downloadTestAppImage(t, wd+"/testing")
_, err = os.Open(wd + "/testing/" + appImageName)
if err != nil {
t.Fatal(err)
}
} else if err != nil {
t.Fatal(err)
}
ai := goappimage.NewAppImage(wd + "/testing/" + appImageName)
aiFil, err := os.Open(wd + "/testing/" + appImageName)
if err != nil {
t.Fatal(err)
}
defer aiFil.Close()
aiFil.Seek(ai.Offset, 0)
os.Remove(wd + "/testing/" + appImageName + ".squashfs")
aiSquash, err := os.Create(wd + "/testing/" + appImageName + ".squashfs")
if err != nil {
t.Fatal(err)
}
_, err = io.Copy(aiSquash, aiFil)
if err != nil {
t.Fatal(err)
}
}
+7 -6
View File
@@ -1,12 +1,13 @@
package squashfs package squashfs
//The types of compression supported by squashfs.
const ( const (
gzipCompression = 1 + iota GzipCompression = 1 + iota
lzmaCompression LzmaCompression
lzoCompression LzoCompression
xzCompression XzCompression
lz4Compression Lz4Compression
zstdCompression ZstdCompression
) )
//Superblock contains important information about a squashfs file. Located at the very front of the archive. //Superblock contains important information about a squashfs file. Located at the very front of the archive.
+326
View File
@@ -0,0 +1,326 @@
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
Flags superblockFlags
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
}
if w.Contains(filepath) {
return errors.New("File already exists at " + 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
}
if w.Contains(filepath) {
return errors.New("File already exists at " + 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 len(w.structure[structDir]) > 1 {
w.structure[structDir][i] = w.structure[structDir][len(w.structure[structDir])-1]
w.structure[structDir] = w.structure[structDir][:len(w.structure[structDir])-1]
} else {
w.structure[structDir] = nil
}
}
}
}
}
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 all symlinks can be resolved, the error slice will be nil, and the bool false, otherwise all errors occured will be in the slice.
func (w *Writer) FixSymlinks() (errs []error, problems bool) {
for dir, holderSlice := range w.structure {
for i, holder := range holderSlice {
if !holder.symlink {
continue
}
sym := holder.symLocation
if !path.IsAbs(holder.symLocation) {
sym = path.Join(dir, holder.symLocation)
}
if path, ok := w.symlinkTable[sym]; ok {
w.structure[dir][i].symLocation = path
continue
}
if path.IsAbs(sym) || strings.HasPrefix(sym, "../") {
var symFil *os.File
var err error
if strings.HasPrefix(sym, "../") {
holderFil, ok := holder.reader.(*os.File)
if !ok {
problems = true
errs = append(errs, errors.New("Cannot resolve symlink at "+dir+holder.name))
continue
}
symFilPath := path.Dir(holderFil.Name())
symFilPath = path.Join(symFilPath, holder.symLocation)
symFil, err = os.Open(symFilPath)
} else {
symFil, err = os.Open(sym)
}
if err != nil {
problems = true
errs = append(errs, err)
continue
}
suc := w.Remove(dir + holder.name)
if !suc {
problems = true
errs = append(errs, errors.New("Cannot resolve symlink at "+dir+holder.name))
continue
}
err = w.AddFileTo(dir+holder.name, symFil)
if err != nil {
w.structure[dir] = append(w.structure[dir], holder)
problems = true
errs = append(errs, err)
continue
}
w.symlinkTable[sym] = dir + holder.name
} else {
symHolder := w.holderAt(sym)
if symHolder != nil {
w.symlinkTable[sym] = sym
continue
}
holderFil, ok := holder.reader.(*os.File)
if !ok {
problems = true
errs = append(errs, errors.New("Cannot resolve symlink at "+dir+holder.name))
continue
}
symFilPath := path.Dir(holderFil.Name())
symFilPath = path.Join(symFilPath, holder.symLocation)
symFil, err := os.Open(symFilPath)
if err != nil {
problems = true
errs = append(errs, err)
continue
}
err = w.AddFileTo(sym, symFil)
if err != nil {
problems = true
errs = append(errs, err)
continue
}
w.symlinkTable[sym] = sym
}
}
}
return
}
func (w *Writer) holderAt(filepath string) *fileHolder {
filepath = path.Clean(filepath)
if !strings.HasPrefix(filepath, "/") {
filepath = "/" + filepath
}
dir, name := path.Split(filepath)
if holderSlice, ok := w.structure[dir]; ok {
for _, holder := range holderSlice {
if holder.name == name {
return holder
}
}
}
return nil
}
//Contains returns whether a file is present at the given filepath
func (w *Writer) Contains(filepath string) bool {
filepath = path.Clean(filepath)
if !strings.HasPrefix(filepath, "/") {
filepath = "/" + filepath
}
dir, name := path.Split(filepath)
if holderSlice, ok := w.structure[dir]; ok {
for _, holder := range holderSlice {
if holder.name == name {
return true
}
}
}
return false
}
//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 {}