Compare commits

...

8 Commits

Author SHA1 Message Date
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
Caleb Gardner 1da97137a5 Implemented #1
You can dereference a symlink when extracting.
2020-12-08 09:41:10 -06:00
9 changed files with 304 additions and 59 deletions
+7 -7
View File
@@ -1,14 +1,14 @@
# 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.
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.
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).
# [TODO](https://github.com/CalebQ42/squashfs/projects/1?fullscreen=true)
# Where I'm at
* 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)
+133 -40
View File
@@ -7,6 +7,7 @@ import (
"os"
"path"
"strings"
"time"
"github.com/CalebQ42/squashfs/internal/directory"
"github.com/CalebQ42/squashfs/internal/inode"
@@ -26,10 +27,12 @@ var (
//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.
//When writing, this holds the information on WHERE the file will be placed inside the archive.
//
//Implements os.FileInfo and io.ReadCloser
type File struct {
Name string //The name of the file or folder. Root folder will not have a name ("")
Parent *File //The parent directory. Should ALWAYS be a folder. If it's the root directory, will be nil
Reader io.Reader //Underlying reader. When writing, will probably be an os.File. When reading this is kept nil UNTIL reading to save memory.
name string //The name of the file or folder. Root folder will not have a name ("")
path string //The path to the folder the File is located in.
r *Reader //The squashfs.Reader where this file is contained.
in *inode.Inode //Underlyting inode when reading.
@@ -43,12 +46,50 @@ func (r *Reader) newFileFromDirEntry(entry *directory.Entry) (fil *File, err err
if err != nil {
return nil, err
}
fil.Name = entry.Name
fil.name = entry.Name
fil.r = r
fil.filType = fil.in.Type
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.BasicFileType:
return int64(f.in.Info.(inode.BasicFile).Init.Size)
case inode.ExtFileType:
return int64(f.in.Info.(inode.ExtendedFile).Init.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, file.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() {
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
}
return nil
}
//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) {
children = make([]*File, 0)
@@ -69,7 +110,7 @@ func (f *File) GetChildren() (children []*File, err error) {
return
}
fil.Parent = f
if f.Name != "" {
if f.name != "" {
fil.path = f.Path()
}
children = append(children, fil)
@@ -117,10 +158,10 @@ func (f *File) GetChildrenRecursively() (children []*File, err error) {
//Path returns the path of the file within the archive.
func (f *File) Path() string {
if f.Name == "" {
if f.name == "" {
return f.path
}
return f.path + "/" + f.Name
return f.path + "/" + f.name
}
//GetFileAtPath tries to return the File at the given path, relative to the file.
@@ -139,7 +180,7 @@ func (f *File) GetFileAtPath(dirPath string) *File {
dirPath = strings.TrimPrefix(dirPath, "./")
}
split := strings.Split(dirPath, "/")
if split[0] == ".." && f.Name == "" {
if split[0] == ".." && f.name == "" {
return nil
} else if split[0] == ".." {
if f.Parent != nil {
@@ -152,7 +193,7 @@ func (f *File) GetFileAtPath(dirPath string) *File {
return nil
}
for _, child := range children {
eq, _ := path.Match(split[0], child.Name)
eq, _ := path.Match(split[0], child.name)
if eq {
return child.GetFileAtPath(strings.Join(split[1:], "/"))
}
@@ -188,7 +229,8 @@ func (f *File) SymlinkPath() string {
}
}
//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 {
if !f.IsSymlink() {
return nil
@@ -196,11 +238,32 @@ func (f *File) GetSymlinkFile() *File {
if strings.HasSuffix(f.SymlinkPath(), "/") {
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.
func (f *File) Permission() os.FileMode {
//GetSymlinkFileRecursive tries to return the squasfs.File associated with the symlink. It will recursively
//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)
switch {
case f.IsDir():
@@ -211,23 +274,32 @@ func (f *File) Permission() os.FileMode {
return mode
}
//ExtractTo extracts the file to the given path. This is the same as ExtractWithOptions(path, false, os.ModePerm, false).
//ExtractTo extracts the file to the given path. This is the same as ExtractWithOptions(path, false, false, os.ModePerm, false).
//Will NOT try to keep symlinks valid, folders extracted will have the permissions set by the squashfs, but the folder to make path will have full permissions (777).
//
//Will try it's best to extract all files, and if any errors come up, they will be appended to the error slice that's returned.
func (f *File) ExtractTo(path string) []error {
return f.ExtractWithOptions(path, false, os.ModePerm, false)
return f.ExtractWithOptions(path, false, false, os.ModePerm, false)
}
//ExtractSymlink is similar to ExtractTo, but when it extracts a symlink, it instead extracts the file associated with the symlink in it's place.
//This is the same as ExtractWithOptions(path, true, false, os.ModePerm, false)
func (f *File) ExtractSymlink(path string) []error {
return f.ExtractWithOptions(path, true, false, os.ModePerm, false)
}
//ExtractWithOptions will extract the file to the given path, while allowing customization on how it works. ExtractTo is the "default" options.
//Will try it's best to extract all files, and if any errors come up, they will be appended to the error slice that's returned.
//Should only return multiple errors if extracting a folder.
//
//If dereferenceSymlink is set, instead of extracting a symlink, it will extract the file the symlink is pointed to in it's place.
//If both dereferenceSymlink and unbreakSymlink is set, dereferenceSymlink takes precendence.
//
//If unbreakSymlink is set, it will also try to extract the symlink's associated file. WARNING: the symlink's file may have to go up the directory to work.
//If unbreakSymlink is set and the file cannot be extracted, a ErrBrokenSymlink will be appended to the returned error slice.
//
//folderPerm only applies to the folders created to get to path. Folders from the archive are given the correct permissions defined by the archive.
func (f *File) ExtractWithOptions(path string, unbreakSymlink bool, folderPerm os.FileMode, verbose bool) (errs []error) {
func (f *File) ExtractWithOptions(path string, dereferenceSymlink, unbreakSymlink bool, folderPerm os.FileMode, verbose bool) (errs []error) {
errs = make([]error, 0)
err := os.MkdirAll(path, folderPerm)
if err != nil {
@@ -235,21 +307,21 @@ func (f *File) ExtractWithOptions(path string, unbreakSymlink bool, folderPerm o
}
switch {
case f.IsDir():
if f.Name != "" {
if f.name != "" {
//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 verbose {
fmt.Println("Error while making: ", path+"/"+f.Name)
fmt.Println("Error while making: ", path+"/"+f.name)
fmt.Println(err)
}
errs = append(errs, err)
return
}
fil, err := os.Open(path + "/" + f.Name)
fil, err := os.Open(path + "/" + f.name)
if err != nil {
if verbose {
fmt.Println("Error while opening:", path+"/"+f.Name)
fmt.Println("Error while opening:", path+"/"+f.name)
fmt.Println(err)
}
errs = append(errs, err)
@@ -264,10 +336,10 @@ func (f *File) ExtractWithOptions(path string, unbreakSymlink bool, folderPerm o
// }
// errs = append(errs, err)
// }
err = fil.Chmod(f.Permission())
err = fil.Chmod(f.Mode())
if err != nil {
if verbose {
fmt.Println("Error while changing owner:", path+"/"+f.Name)
fmt.Println("Error while changing owner:", path+"/"+f.name)
fmt.Println(err)
}
errs = append(errs, err)
@@ -286,10 +358,10 @@ func (f *File) ExtractWithOptions(path string, unbreakSymlink bool, folderPerm o
defer close(finishChan)
for _, child := range children {
go func(child *File) {
if f.Name == "" {
finishChan <- child.ExtractWithOptions(path, unbreakSymlink, folderPerm, verbose)
if f.name == "" {
finishChan <- child.ExtractWithOptions(path, dereferenceSymlink, unbreakSymlink, folderPerm, verbose)
} else {
finishChan <- child.ExtractWithOptions(path+"/"+f.Name, unbreakSymlink, folderPerm, verbose)
finishChan <- child.ExtractWithOptions(path+"/"+f.name, dereferenceSymlink, unbreakSymlink, folderPerm, verbose)
}
}(child)
}
@@ -298,21 +370,21 @@ func (f *File) ExtractWithOptions(path string, unbreakSymlink bool, folderPerm o
}
return
case f.IsFile():
fil, err := os.Create(path + "/" + f.Name)
fil, err := os.Create(path + "/" + f.name)
if os.IsExist(err) {
err = os.Remove(path + "/" + f.Name)
err = os.Remove(path + "/" + f.name)
if err != nil {
if verbose {
fmt.Println("Error while making:", path+"/"+f.Name)
fmt.Println("Error while making:", path+"/"+f.name)
fmt.Println(err)
}
errs = append(errs, err)
return
}
fil, err = os.Create(path + "/" + f.Name)
fil, err = os.Create(path + "/" + f.name)
if err != nil {
if verbose {
fmt.Println("Error while making:", path+"/"+f.Name)
fmt.Println("Error while making:", path+"/"+f.name)
fmt.Println(err)
}
errs = append(errs, err)
@@ -320,7 +392,7 @@ func (f *File) ExtractWithOptions(path string, unbreakSymlink bool, folderPerm o
}
} else if err != nil {
if verbose {
fmt.Println("Error while making:", path+"/"+f.Name)
fmt.Println("Error while making:", path+"/"+f.name)
fmt.Println(err)
}
errs = append(errs, err)
@@ -329,7 +401,7 @@ func (f *File) ExtractWithOptions(path string, unbreakSymlink bool, folderPerm o
_, err = io.Copy(fil, f)
if err != nil {
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)
}
errs = append(errs, err)
@@ -346,10 +418,10 @@ func (f *File) ExtractWithOptions(path string, unbreakSymlink bool, folderPerm o
// errs = append(errs, err)
// return
// }
err = fil.Chmod(f.Permission())
err = fil.Chmod(f.Mode())
if err != nil {
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)
}
errs = append(errs, err)
@@ -357,27 +429,48 @@ func (f *File) ExtractWithOptions(path string, unbreakSymlink bool, folderPerm o
return
case f.IsSymlink():
symPath := f.SymlinkPath()
if unbreakSymlink {
if dereferenceSymlink {
fil := f.GetSymlinkFile()
if fil == nil {
if verbose {
fmt.Println("Symlink path(", symPath, ") is outside the archive:"+path+"/"+f.name)
}
return
}
fil.name = f.name
extracSymErrs := fil.ExtractWithOptions(path, dereferenceSymlink, unbreakSymlink, folderPerm, verbose)
if len(extracSymErrs) > 0 {
if verbose {
fmt.Println("Error(s) while extracting the symlink's file:", path+"/"+f.name)
fmt.Println(extracSymErrs)
}
errs = append(errs, extracSymErrs...)
}
return
} else if unbreakSymlink {
fil := f.GetSymlinkFile()
if fil != nil {
symPath = path + "/" + symPath
paths := strings.Split(symPath, "/")
extracSymErrs := fil.ExtractWithOptions(strings.Join(paths[:len(paths)-1], "/"), unbreakSymlink, folderPerm, verbose)
extracSymErrs := fil.ExtractWithOptions(strings.Join(paths[:len(paths)-1], "/"), dereferenceSymlink, unbreakSymlink, folderPerm, verbose)
if len(extracSymErrs) > 0 {
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)
}
errs = append(errs, extracSymErrs...)
}
} else if verbose {
fmt.Println("Symlink path(", symPath, ") is outside the archive:"+path+"/"+f.Name)
} else {
if verbose {
fmt.Println("Symlink path(", symPath, ") is outside the archive:"+path+"/"+f.name)
}
return
}
}
err = os.Symlink(f.SymlinkPath(), path+"/"+f.Name)
err = os.Symlink(f.SymlinkPath(), path+"/"+f.name)
if err != nil {
if verbose {
fmt.Println("Error while making symlink:", path+"/"+f.Name)
fmt.Println("Error while making symlink:", path+"/"+f.name)
fmt.Println(err)
}
errs = append(errs, err)
+2
View File
@@ -7,7 +7,9 @@ require (
github.com/adrg/xdg v0.2.3 // indirect
github.com/google/go-cmp v0.5.4 // indirect
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
github.com/klauspost/compress v1.11.3
github.com/kr/text v0.2.0 // indirect
github.com/pierrec/lz4/v4 v4.1.1
github.com/smartystreets/assertions v1.2.0 // indirect
github.com/ulikunitz/xz v0.5.8
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
+4
View File
@@ -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/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/compress v1.11.3 h1:dB4Bn0tN3wdCzQxnS8r06kV74qN/TAfaIS0bVE8h3jc=
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pierrec/lz4/v4 v4.1.1 h1:cS6aGkNLJr4u+UwaA21yp+gbWN3WJWtKo1axmPDObMA=
github.com/pierrec/lz4/v4 v4.1.1/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
+27 -1
View File
@@ -3,11 +3,37 @@ package compression
import (
"bytes"
"compress/zlib"
"encoding/binary"
"io"
)
type gzipInit struct {
CompressionLevel int32
WindowSize int16
Strategies int16
}
//Gzip is a decompressor for gzip type compression. Uses zlib for compression and decompression
type Gzip struct{}
type Gzip struct {
CompressionLevel int32
HasCustomWindow bool
HasStrategies bool
}
//NewGzipCompressorWithOptions creates a new gzip compressor/decompressor with options read from the given reader.
func NewGzipCompressorWithOptions(r io.Reader) (*Gzip, error) {
var gzip Gzip
var init gzipInit
err := binary.Read(r, binary.LittleEndian, &init)
if err != nil {
return nil, err
}
gzip.CompressionLevel = init.CompressionLevel
//TODO: proper support for window size and strategies
gzip.HasCustomWindow = init.WindowSize != 15
gzip.HasStrategies = init.Strategies != 0 && init.Strategies != 1
return &gzip, nil
}
//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) {
+37
View File
@@ -0,0 +1,37 @@
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
}
+36
View File
@@ -0,0 +1,36 @@
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
}
+50 -1
View File
@@ -5,6 +5,7 @@ import (
"errors"
"io"
"math"
"time"
"github.com/CalebQ42/squashfs/internal/compression"
"github.com/CalebQ42/squashfs/internal/inode"
@@ -21,6 +22,8 @@ var (
errIncompatibleCompression = errors.New("Compression type unsupported")
//ErrCompressorOptions is returned if compressor options is present. It's 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.
@@ -35,6 +38,7 @@ type Reader struct {
//NewSquashfsReader returns a new squashfs.Reader from an io.ReaderAt
func NewSquashfsReader(r io.ReaderAt) (*Reader, error) {
hasUnsupportedOptions := false
var rdr Reader
rdr.r = r
err := binary.Read(io.NewSectionReader(rdr.r, 0, int64(binary.Size(rdr.super))), binary.LittleEndian, &rdr.super)
@@ -50,8 +54,17 @@ func NewSquashfsReader(r io.ReaderAt) (*Reader, error) {
rdr.flags = rdr.super.GetFlags()
if rdr.flags.CompressorOptions {
switch rdr.super.CompressionType {
case gzipCompression:
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:
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.
xz, err := compression.NewXzCompressorWithOptions(io.NewSectionReader(rdr.r, int64(binary.Size(rdr.super)), 8))
if err != nil {
return nil, err
}
@@ -59,6 +72,21 @@ func NewSquashfsReader(r io.ReaderAt) (*Reader, error) {
return nil, errors.New("XZ compression options has filters. These are not yet supported")
}
rdr.decompressor = xz
case lz4Compression:
lz4, err := compression.NewLz4CompressorWithOptions(io.NewSectionReader(rdr.r, int64(binary.Size(rdr.super)), 8))
if err != nil {
return nil, err
}
if lz4.HC {
hasUnsupportedOptions = true
}
rdr.decompressor = lz4
case zstdCompression:
zstd, err := compression.NewZstdCompressorWithOptions(io.NewSectionReader(rdr.r, int64(binary.Size(rdr.super)), 4))
if err != nil {
return nil, err
}
rdr.decompressor = zstd
default:
return nil, errCompressorOptions
}
@@ -70,6 +98,10 @@ func NewSquashfsReader(r io.ReaderAt) (*Reader, error) {
rdr.decompressor = &compression.Lzma{}
case xzCompression:
rdr.decompressor = &compression.Xz{}
case lz4Compression:
rdr.decompressor = &compression.Lz4{}
case zstdCompression:
rdr.decompressor = &compression.Zstd{}
default:
//TODO: all compression types.
return nil, errIncompatibleCompression
@@ -111,9 +143,26 @@ func NewSquashfsReader(r io.ReaderAt) (*Reader, error) {
}
unread -= read
}
if hasUnsupportedOptions {
return &rdr, ErrOptions
}
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 {
root, err := r.GetRootFolder()
if err != nil {
return []error{err}
}
return root.ExtractTo(path)
}
//GetRootFolder returns a squashfs.File that references the root directory of the squashfs archive.
func (r *Reader) GetRootFolder() (root *File, err error) {
root = new(File)
+8 -10
View File
@@ -1,7 +1,6 @@
package squashfs
import (
"fmt"
"io"
"net/http"
"os"
@@ -16,7 +15,7 @@ const (
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.
//Right now, don't use. Arch linux sfs uses XZ compression with filters, which isn't supported
func TestSquashfs(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
@@ -32,7 +31,7 @@ func TestSquashfs(t *testing.T) {
}
os.RemoveAll(wd + "/testing/" + squashfsName + ".d")
root, _ := rdr.GetRootFolder()
errs := root.ExtractWithOptions(wd+"/testing/"+squashfsName+".d", false, os.ModePerm, true)
errs := root.ExtractWithOptions(wd+"/testing/"+squashfsName+".d", false, false, os.ModePerm, true)
t.Fatal(errs)
}
@@ -59,11 +58,10 @@ func TestAppImage(t *testing.T) {
if err != nil {
t.Fatal(err)
}
fil := rdr.GetFileAtPath("usr/q*/QtQ*k/Extras/Priv*/q*")
if fil == nil {
t.Fatal("Can't find desktop file")
errs := rdr.ExtractTo(wd + "/testing/cool-retro")
if len(errs) > 0 {
t.Fatal(errs)
}
fmt.Println("Fount:", fil.Path())
// os.RemoveAll(wd + "/testing/" + appImageName + ".d")
// root, _ := rdr.GetRootFolder()
// errs := root.ExtractWithOptions(wd+"/testing/"+appImageName+".d", true, os.ModePerm, true)
@@ -72,15 +70,15 @@ func TestAppImage(t *testing.T) {
}
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)
//seems to time out on slow connections. Might fix that at some point... or not. It's just a test...
os.Mkdir(dir, os.ModePerm)
appImage, err := os.Create(dir + "/" + appImageName)
if err != nil {
t.Fatal(err)
}
defer appImage.Close()
check := http.Client{
CheckRedirect: func(r *http.Request, via []*http.Request) error {
CheckRedirect: func(r *http.Request, _ []*http.Request) error {
r.URL.Opaque = r.URL.Path
return nil
},