Compare commits

...

51 Commits

Author SHA1 Message Date
Caleb Gardner e8a8c531a9 Tweaks to make FromReader work 2021-09-27 01:27:58 -05:00
Caleb Gardner 80ff4466ae Added new test 2021-09-26 22:36:30 -05:00
Caleb Gardner 64055a8a63 Improved testing 2021-09-26 19:10:43 -05:00
Caleb Gardner 0402b0a2ee Bringing rawreader from expiremental branch.
Now allows creation of a squashfs.Reader from an io.Reader
2021-09-26 18:30:08 -05:00
Caleb Gardner 305f261d10 Add Lzo decompressor and Xz decompressor with filters 2021-09-12 05:26:47 -05:00
Caleb Gardner 70e3d81427 Some musings on what to do. 2021-04-30 03:32:31 -05:00
Caleb Gardner 6ad6857d8d Renamed files to make them more clear
Trying to figure out how to write. Might have to keep tables uncompressed for now.
2021-04-30 02:52:27 -05:00
Caleb Gardner 7cf15d48c7 Getting back into it. Maybe. 2021-04-04 09:39:28 -05:00
Caleb Gardner 28f39cf315 Updated libraries.
Replaced builtin zlib with faster library.
2021-04-03 10:14:12 -05:00
Caleb Gardner 7f5fa3ba1f Some quick fixes for "correctness" 2021-04-03 01:17:09 -05:00
Caleb Gardner 2a8310a724 Updated README 2021-02-27 01:58:01 -06:00
Caleb Gardner 8ad4d30bc7 Merge branch 'main' of https://github.com/CalebQ42/squashfs 2021-02-27 00:50:40 -06:00
Caleb Gardner b6fbd63ba4 Makes sure folders are created when files are added
Added some comments
2021-02-27 00:49:40 -06:00
Caleb Gardner 9913b848c6 Added some data writing logic. 2021-02-26 01:53:16 -06:00
Caleb Gardner 65bc4a5d78 More work on fragments. 2021-02-25 06:22:57 -06:00
Caleb Gardner c9d451e24c Added fragment calculations (untested). 2021-02-25 03:17:20 -06:00
Caleb Gardner ae5ade0683 Merge pull request #3 from CalebQ42/go1.16_fs3
Added 1.16 io/fs interfaces
2021-02-24 23:40:20 -06:00
Caleb Gardner a7b6801d2b Merge branch 'main' into go1.16_fs3 2021-02-24 23:39:45 -06:00
Caleb Gardner a123935250 Added more necessary parts to compression. 2021-02-24 05:58:18 -06:00
Caleb Gardner 8a91e1a1c1 Getting back into it... 2021-02-23 22:26:15 -06:00
Caleb Gardner 07962426b2 Minor work on the Writer 2021-02-03 14:03:21 -06:00
Caleb Gardner d89153c3e2 Finished io/FS interface 2021-01-30 06:30:00 -06:00
Caleb Gardner 3f1b2a8d1e Restructure for 1.16 io/fs interface 2021-01-29 12:55:57 -06:00
Caleb Gardner 3691e1f486 Merge pull request #2 from srevinsaju/patch-1
fix: typo in readme
2021-01-27 08:35:41 -06:00
Srevin Saju 8ab566521d fix: typo in readme 2021-01-27 12:54:29 +03:00
Caleb Gardner dd08d3516d Reader will now check BlockLog 2021-01-20 14:10:14 -06:00
Caleb Gardner 8dba30e24f Some setup for 1.16 2021-01-20 12:55:39 -06:00
Caleb Gardner d4e2577075 More work on writing 2021-01-17 02:09:13 -06:00
Caleb Gardner 23371163c0 Merge branch 'main' of https://github.com/CalebQ42/squashfs 2021-01-16 15:43:04 -06:00
Caleb Gardner 69f56d6951 Working on how to actually write the archive.
Made SuperblockFlags public so you can set options when writing
2021-01-16 15:42:55 -06:00
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
26 changed files with 2560 additions and 1390 deletions
+19 -5
View File
@@ -4,11 +4,25 @@
A PURE Go library to read and write squashfs. A PURE Go library to read and write squashfs.
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. Currently has support for reading squashfs files and extracting files and folders.
The only major thing missing from squashfs reading is Xattr parsing. Special thanks to <https://dr-emann.github.io/squashfs/> for some VERY important information in an easy to understand format.
Special thanks to https://dr-emann.github.io/squashfs/ for some VERY important information in an easy to understand format.
Thanks also to [distri's squashfs library](https://github.com/distr1/distri/tree/master/internal/squashfs) as I referenced it to figure some things out (and double check others). 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) ## [TODO](https://github.com/CalebQ42/squashfs/projects/1?fullscreen=true)
## Limitations
This library is pure Go (including external libraries) which can cause some issues, which are listed below. Right now this library is also not feature complete, so check out the TODO list above for what I'm still planning on adding.
* No LZO compression. This is purely due to a lack of a good LZO pure golang library. If one is made, it would be a simple job to add it in.
* GZIP Compression
* Strategies might or might not work.
* Custom window sizes might or might not work.
* XZ Compression
* LZMA executable filters are NOT supported.
* No Xattr parsing. This is simply because I haven't done any research on it and how to apply these in a pure go way.
## Performane
This library, decompressing the Firefox AppImage and using go tests, takes about twice as long as `unsquashfs` on my quad core laptop. (~1 second with the library and about half a second with `unsquashfs`)
-190
View File
@@ -1,190 +0,0 @@
package squashfs
import (
"bytes"
"errors"
"io"
"github.com/CalebQ42/squashfs/internal/inode"
)
var (
//ErrInodeNotFile is given when giving an inode, but the function requires a file inode.
errInodeNotFile = errors.New("Given inode is NOT a file type")
//ErrInodeOnlyFragment is given when trying to make a DataReader from an inode, but the inode only had data in a fragment
errInodeOnlyFragment = errors.New("Given inode ONLY has fragment data")
)
//DataReader reads data from data blocks.
type dataReader struct {
r *Reader
offset int64 //offset relative to the beginning of the squash file
blocks []dataBlock
curBlock int //Which block in sizes is currently cached
curData []byte
curReadOffset int //offset relative to the currently cached data
}
//DataBlock holds info about a given data block from it's size
type dataBlock struct {
begOffset int64 //The offset relative to the beginning of the squash file. Makes it easier to seek to it.
size uint32
compressed bool
uncompressedSize uint32
}
//NewDataBlock creates a new squashfs.datablock from a given size.
func newDataBlock(raw uint32) (dbs dataBlock) {
dbs.compressed = raw&(1<<24) != (1 << 24)
dbs.size = raw &^ (1 << 24)
if !dbs.compressed {
dbs.uncompressedSize = dbs.size
}
return
}
//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) {
var dr dataReader
dr.r = r
dr.offset = offset
for _, size := range sizes {
dr.blocks = append(dr.blocks, newDataBlock(size))
}
err := dr.readCurBlock()
if err != nil {
return nil, err
}
return &dr, nil
}
//NewDataReaderFromInode creates a new DataReader from a given inode. Inode must be of BasicFile or ExtendedFile types
func (r *Reader) newDataReaderFromInode(i *inode.Inode) (*dataReader, error) {
var rdr dataReader
rdr.r = r
switch i.Type {
case inode.BasicFileType:
fil := i.Info.(inode.BasicFile)
if fil.Init.BlockStart == 0 {
return nil, errInodeOnlyFragment
}
rdr.offset = int64(fil.Init.BlockStart)
for _, sizes := range fil.BlockSizes {
rdr.blocks = append(rdr.blocks, newDataBlock(sizes))
}
if fil.Fragmented {
rdr.blocks = rdr.blocks[:len(rdr.blocks)-1]
}
case inode.ExtFileType:
fil := i.Info.(inode.ExtendedFile)
if fil.Init.BlockStart == 0 {
return nil, errInodeOnlyFragment
}
rdr.offset = int64(fil.Init.BlockStart)
for _, sizes := range fil.BlockSizes {
rdr.blocks = append(rdr.blocks, newDataBlock(sizes))
}
if fil.Fragmented {
rdr.blocks = rdr.blocks[:len(rdr.blocks)-1]
}
default:
return nil, errInodeNotFile
}
err := rdr.readCurBlock()
if err != nil {
return nil, err
}
return &rdr, nil
}
func (d *dataReader) readNextBlock() error {
d.curBlock++
if d.curBlock >= len(d.blocks) {
d.curBlock--
return io.EOF
}
err := d.readCurBlock()
if err != nil {
d.curBlock--
d.readCurBlock()
return err
}
return nil
}
func (d *dataReader) readCurBlock() error {
if d.curBlock >= len(d.blocks) {
return io.EOF
}
if d.blocks[d.curBlock].size == 0 {
d.curData = make([]byte, d.r.super.BlockSize)
d.blocks[d.curBlock].uncompressedSize = d.r.super.BlockSize
d.blocks[d.curBlock].begOffset = d.offset
return nil
}
sec := io.NewSectionReader(d.r.r, d.offset, int64(d.blocks[d.curBlock].size))
if d.blocks[d.curBlock].compressed {
btys, err := d.r.decompressor.Decompress(sec)
if err != nil {
return err
}
d.blocks[d.curBlock].uncompressedSize = uint32(len(btys))
d.curData = btys
d.blocks[d.curBlock].begOffset = d.offset
d.offset += int64(d.blocks[d.curBlock].size)
return nil
}
var buf bytes.Buffer
_, err := io.Copy(&buf, sec)
if err != nil {
return err
}
d.curData = buf.Bytes()
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) Close() error {
d.curData = nil
return nil
}
func (d *dataReader) Read(p []byte) (int, error) {
if d.curData == nil {
err := d.readCurBlock()
if err != nil {
return 0, err
}
}
if d.curReadOffset+len(p) <= len(d.curData) {
for i := 0; i < len(p); i++ {
p[i] = d.curData[d.curReadOffset+i]
}
d.curReadOffset += len(p)
return len(p), nil
}
read := 0
for read < len(p) {
if d.curReadOffset == len(d.curData) {
err := d.readNextBlock()
if err != nil {
return read, err
}
d.curReadOffset = 0
}
for ; read < len(p); read++ {
if d.curReadOffset < len(d.curData) {
p[read] = d.curData[d.curReadOffset]
} else {
break
}
d.curReadOffset++
}
}
if read != len(p) {
return read, errors.New("Didn't read enough data")
}
return read, nil
}
+137
View File
@@ -0,0 +1,137 @@
package squashfs
import (
"io"
"io/fs"
"time"
"github.com/CalebQ42/squashfs/internal/directory"
"github.com/CalebQ42/squashfs/internal/inode"
)
//DirEntry is a child of a directory.
type DirEntry struct {
en *directory.Entry
parent *FS
r *Reader
}
func (r *Reader) newDirEntry(en *directory.Entry, parent *FS) *DirEntry {
return &DirEntry{
en: en,
parent: parent,
r: r,
}
}
//Name returns the DirEntry's name
func (d DirEntry) Name() string {
return d.en.Name
}
//IsDir Yep.
func (d DirEntry) IsDir() bool {
return d.en.Type == inode.DirType
}
//Type returns the type bits of fs.FileMode of the DirEntry.
func (d DirEntry) Type() fs.FileMode {
switch d.en.Type {
case inode.DirType:
return fs.ModeDir
case inode.SymType:
return fs.ModeSymlink
default:
return 0
}
}
//Info returns the fs.FileInfo for the given DirEntry.
func (d DirEntry) Info() (fs.FileInfo, error) {
in, err := d.r.getInodeFromEntry(d.en)
if err != nil {
return nil, err
}
return &FileInfo{
name: d.en.Name,
i: in,
parent: d.parent,
r: d.r,
}, nil
}
//GetInodeFromEntry returns the inode associated with a given directory.Entry
func (r *Reader) getInodeFromEntry(en *directory.Entry) (*inode.Inode, error) {
br, err := r.newMetadataReader(int64(r.super.InodeTableStart + uint64(en.InodeOffset)))
if err != nil {
return nil, err
}
_, err = br.Seek(int64(en.InodeBlockOffset), io.SeekStart)
if err != nil {
return nil, err
}
i, err := inode.ProcessInode(br, r.super.BlockSize)
if err != nil {
return nil, err
}
return i, nil
}
//FileInfo is a fs.FileInfo for a file.
type FileInfo struct {
i *inode.Inode
parent *FS
r *Reader
name string
}
//Name is the file's name.
func (f FileInfo) Name() string {
return f.name
}
//Size is the file's size if it's a regular file. Otherwise, returns 0.
func (f FileInfo) Size() int64 {
switch f.i.Type {
case inode.FileType:
return int64(f.i.Info.(inode.File).Size)
case inode.ExtFileType:
return int64(f.i.Info.(inode.ExtFile).Size)
}
return 0
}
//Mode returns the fs.FileMode bits of the file.
func (f FileInfo) Mode() fs.FileMode {
mode := fs.FileMode(f.i.Permissions)
switch f.i.Type {
case inode.DirType | inode.ExtDirType:
return mode | fs.ModeDir
case inode.ExtDirType:
return mode | fs.ModeDir
case inode.SymType:
return mode | fs.ModeSymlink
case inode.ExtSymType:
return mode | fs.ModeSymlink
}
return mode
}
//ModTime is the last time the file was modified.
func (f FileInfo) ModTime() time.Time {
return time.Unix(int64(f.i.ModifiedTime), 0)
}
//IsDir yep.
func (f FileInfo) IsDir() bool {
return f.i.Type == inode.DirType || f.i.Type == inode.ExtDirType
}
//Sys returns the File for the FileInfo. If something goes wrong, nil is returned.
func (f FileInfo) Sys() interface{} {
fil, err := f.File()
if err != nil {
return nil
}
return fil
}
-560
View File
@@ -1,560 +0,0 @@
package squashfs
import (
"errors"
"fmt"
"io"
"os"
"path"
"strings"
"time"
"github.com/CalebQ42/squashfs/internal/directory"
"github.com/CalebQ42/squashfs/internal/inode"
)
var (
//ErrNotDirectory is returned when you're trying to do directory things with a non-directory
errNotDirectory = errors.New("File is not a directory")
//ErrNotFile is returned when you're trying to do file things with a directory
errNotFile = errors.New("File is not a file")
//ErrNotReading is returned when running functions that are only meant to be used when reading a squashfs
errNotReading = errors.New("Function only supported when reading a squashfs")
//ErrBrokenSymlink is returned when using ExtractWithOptions with the unbreakSymlink set to true, but the symlink's file cannot be extracted.
ErrBrokenSymlink = errors.New("Extracted symlink is probably broken")
)
//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 {
Parent *File //The parent directory. Should ALWAYS be a folder. If it's the root directory, will be nil
Reader io.Reader //Underlying reader. When writing, will probably be an os.File. When reading this is kept nil UNTIL reading to save memory.
name string //The name of the file or folder. Root folder will not have a name ("")
path string //The path to the folder the File is located in.
r *Reader //The squashfs.Reader where this file is contained.
in *inode.Inode //Underlyting inode when reading.
filType int //The file's type, using inode types.
}
//get a File from a directory.entry
func (r *Reader) newFileFromDirEntry(entry *directory.Entry) (fil *File, err error) {
fil = new(File)
fil.in, err = r.getInodeFromEntry(entry)
if err != nil {
return nil, err
}
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)
if f.r == nil {
return nil, errNotReading
}
if !f.IsDir() {
return nil, errNotDirectory
}
dir, err := f.r.readDirFromInode(f.in)
if err != nil {
return
}
var fil *File
for _, entry := range dir.Entries {
fil, err = f.r.newFileFromDirEntry(&entry)
if err != nil {
return
}
fil.Parent = f
if f.name != "" {
fil.path = f.Path()
}
children = append(children, fil)
}
return
}
//GetChildrenRecursively returns ALL children. Goes down ALL folder paths.
func (f *File) GetChildrenRecursively() (children []*File, err error) {
children = make([]*File, 0)
if f.r == nil {
return nil, errNotReading
}
if !f.IsDir() {
return nil, errNotDirectory
}
children, err = f.GetChildren()
if err != nil {
return
}
var childFolders []*File
for _, child := range children {
if child.IsDir() {
childFolders = append(childFolders, child)
}
}
foldChil := make(chan []*File)
errChan := make(chan error)
for _, folds := range childFolders {
go func(fil *File) {
childs, err := fil.GetChildrenRecursively()
errChan <- err
foldChil <- childs
}(folds)
}
for range childFolders {
err = <-errChan
if err != nil {
return
}
children = append(children, <-foldChil...)
}
return
}
//Path returns the path of the file within the archive.
func (f *File) Path() string {
if f.name == "" {
return f.path
}
return f.path + "/" + f.name
}
//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.
//Allows wildcards supported by path.Match (namely * and ?).
func (f *File) GetFileAtPath(dirPath string) *File {
if dirPath == "" {
return f
}
dirPath = strings.TrimSuffix(strings.TrimPrefix(dirPath, "/"), "/")
if dirPath != "" && !f.IsDir() {
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, "/")
if split[0] == ".." && f.name == "" {
return nil
} else if split[0] == ".." {
if f.Parent != nil {
return f.Parent.GetFileAtPath(strings.Join(split[1:], "/"))
}
return nil
}
children, err := f.GetChildren()
if err != nil {
return nil
}
for _, child := range children {
eq, _ := path.Match(split[0], child.name)
if eq {
return child.GetFileAtPath(strings.Join(split[1:], "/"))
}
}
return nil
}
//IsDir returns if the file is a directory.
func (f *File) IsDir() bool {
return f.filType == inode.BasicDirectoryType || f.filType == inode.ExtDirType
}
//IsSymlink returns if the file is a symlink.
func (f *File) IsSymlink() bool {
return f.filType == inode.BasicSymlinkType || f.filType == inode.ExtSymlinkType
}
//IsFile returns if the file is a file.
func (f *File) IsFile() bool {
return f.filType == inode.BasicFileType || f.filType == inode.ExtFileType
}
//SymlinkPath returns the path the symlink is pointing to. If the file ISN'T a symlink, will return an empty string.
//If a path begins with "/" then the symlink is pointing to an absolute path (starting from root, and not a file inside the archive)
func (f *File) SymlinkPath() string {
switch f.filType {
case inode.BasicSymlinkType:
return f.in.Info.(inode.BasicSymlink).Path
case inode.ExtSymlinkType:
return f.in.Info.(inode.ExtendedSymlink).Path
default:
return ""
}
}
//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
}
if strings.HasSuffix(f.SymlinkPath(), "/") {
return nil
}
return f.Parent.GetFileAtPath(f.SymlinkPath())
}
//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():
mode = mode | os.ModeDir
case f.IsSymlink():
mode = mode | os.ModeSymlink
}
return mode
}
//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, 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, dereferenceSymlink, unbreakSymlink bool, folderPerm os.FileMode, verbose bool) (errs []error) {
errs = make([]error, 0)
err := os.MkdirAll(path, folderPerm)
if err != nil {
return []error{err}
}
switch {
case f.IsDir():
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)
if err != nil {
if verbose {
fmt.Println("Error while making: ", path+"/"+f.name)
fmt.Println(err)
}
errs = append(errs, err)
return
}
fil, err := os.Open(path + "/" + f.name)
if err != nil {
if verbose {
fmt.Println("Error while opening:", path+"/"+f.name)
fmt.Println(err)
}
errs = append(errs, err)
return
}
fil.Chown(int(f.r.idTable[f.in.Header.UID]), int(f.r.idTable[f.in.Header.GID]))
//don't mention anything when it fails. Because it fails often. Probably has something to do about uid & gid 0
// if err != nil {
// if verbose {
// fmt.Println("Error while changing owner:", path+"/"+f.Name)
// fmt.Println(err)
// }
// errs = append(errs, err)
// }
err = fil.Chmod(f.Mode())
if err != nil {
if verbose {
fmt.Println("Error while changing owner:", path+"/"+f.name)
fmt.Println(err)
}
errs = append(errs, err)
}
}
children, err := f.GetChildren()
if err != nil {
if verbose {
fmt.Println("Error getting children for:", f.Path())
fmt.Println(err)
}
errs = append(errs, err)
return
}
finishChan := make(chan []error)
defer close(finishChan)
for _, child := range children {
go func(child *File) {
if f.name == "" {
finishChan <- child.ExtractWithOptions(path, dereferenceSymlink, unbreakSymlink, folderPerm, verbose)
} else {
finishChan <- child.ExtractWithOptions(path+"/"+f.name, dereferenceSymlink, unbreakSymlink, folderPerm, verbose)
}
}(child)
}
for range children {
errs = append(errs, (<-finishChan)...)
}
return
case f.IsFile():
fil, err := os.Create(path + "/" + f.name)
if os.IsExist(err) {
err = os.Remove(path + "/" + f.name)
if err != nil {
if verbose {
fmt.Println("Error while making:", path+"/"+f.name)
fmt.Println(err)
}
errs = append(errs, err)
return
}
fil, err = os.Create(path + "/" + f.name)
if err != nil {
if verbose {
fmt.Println("Error while making:", path+"/"+f.name)
fmt.Println(err)
}
errs = append(errs, err)
return
}
} else if err != nil {
if verbose {
fmt.Println("Error while making:", path+"/"+f.name)
fmt.Println(err)
}
errs = append(errs, err)
return
} //Since we will be reading from the file
_, err = io.Copy(fil, f)
if err != nil {
if verbose {
fmt.Println("Error while Copying data to:", path+"/"+f.name)
fmt.Println(err)
}
errs = append(errs, err)
return
}
f.Close()
fil.Chown(int(f.r.idTable[f.in.Header.UID]), int(f.r.idTable[f.in.Header.GID]))
//don't mention anything when it fails. Because it fails often. Probably has something to do about uid & gid 0
// if err != nil {
// if verbose {
// fmt.Println("Error while changing owner:", path+"/"+f.Name)
// fmt.Println(err)
// }
// errs = append(errs, err)
// return
// }
err = fil.Chmod(f.Mode())
if err != nil {
if verbose {
fmt.Println("Error while setting permissions for:", path+"/"+f.name)
fmt.Println(err)
}
errs = append(errs, err)
}
return
case f.IsSymlink():
symPath := f.SymlinkPath()
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], "/"), 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...)
}
} else {
if verbose {
fmt.Println("Symlink path(", symPath, ") is outside the archive:"+path+"/"+f.name)
}
return
}
}
err = os.Symlink(f.SymlinkPath(), path+"/"+f.name)
if err != nil {
if verbose {
fmt.Println("Error while making symlink:", path+"/"+f.name)
fmt.Println(err)
}
errs = append(errs, err)
}
}
return
}
//Close frees up the memory held up by the underlying reader. Should NOT be called when writing.
//When reading, Close is safe to use, but any subsequent Read calls resets to the beginning of the file.
func (f *File) Close() error {
if f.IsDir() {
return errNotFile
}
if f.Reader != nil {
if closer, is := f.Reader.(io.Closer); is {
closer.Close()
}
f.Reader = nil
}
return nil
}
//Read from the file. Doesn't do anything fancy, just pases it to the underlying io.Reader. If a directory, return io.EOF.
func (f *File) Read(p []byte) (int, error) {
if f.IsDir() {
return 0, io.EOF
}
var err error
if f.Reader == nil && f.r != nil {
f.Reader, err = f.r.newFileReader(f.in)
if err != nil {
return 0, err
}
}
return f.Reader.Read(p)
}
//ReadDirFromInode returns a fully populated Directory from a given Inode.
//If the given inode is not a directory it returns an error.
func (r *Reader) readDirFromInode(i *inode.Inode) (*directory.Directory, error) {
var offset uint32
var metaOffset uint16
var size uint32
switch i.Type {
case inode.BasicDirectoryType:
offset = i.Info.(inode.BasicDirectory).DirectoryIndex
metaOffset = i.Info.(inode.BasicDirectory).DirectoryOffset
size = uint32(i.Info.(inode.BasicDirectory).DirectorySize)
case inode.ExtDirType:
offset = i.Info.(inode.ExtendedDirectory).Init.DirectoryIndex
metaOffset = i.Info.(inode.ExtendedDirectory).Init.DirectoryOffset
size = i.Info.(inode.ExtendedDirectory).Init.DirectorySize
default:
return nil, errors.New("Not a directory inode")
}
br, err := r.newMetadataReader(int64(r.super.DirTableStart + uint64(offset)))
if err != nil {
return nil, err
}
_, err = br.Seek(int64(metaOffset), io.SeekStart)
if err != nil {
return nil, err
}
dir, err := directory.NewDirectory(br, size)
if err != nil {
return dir, err
}
return dir, nil
}
//GetInodeFromEntry returns the inode associated with a given directory.Entry
func (r *Reader) getInodeFromEntry(en *directory.Entry) (*inode.Inode, error) {
br, err := r.newMetadataReader(int64(r.super.InodeTableStart + uint64(en.Header.InodeOffset)))
if err != nil {
return nil, err
}
_, err = br.Seek(int64(en.Init.Offset), io.SeekStart)
if err != nil {
return nil, err
}
i, err := inode.ProcessInode(br, r.super.BlockSize)
if err != nil {
return nil, err
}
return i, nil
}
+18 -18
View File
@@ -10,43 +10,43 @@ import (
//FragmentEntry is an entry in the fragment table //FragmentEntry is an entry in the fragment table
type fragmentEntry struct { type fragmentEntry struct {
Start uint64 Start uint64
Size uint32 Size uint32
Unused uint32 _ uint32 //unused
} }
//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")
} }
//reading the fragment entry first //reading the fragment entry first
fragEntryRdr, err := r.newMetadataReader(int64(r.fragOffsets[int(fragIndex/512)])) fragEntryRdr, err := r.newMetadataReader(int64(r.fragOffsets[int(fragIndex/512)]))
+17 -12
View File
@@ -1,18 +1,23 @@
module github.com/CalebQ42/squashfs module github.com/CalebQ42/squashfs
go 1.15 go 1.17
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.3.3 // indirect
github.com/google/go-cmp v0.5.4 // indirect github.com/gopherjs/gopherjs v0.0.0-20210420193930-a4630ec28c79 // indirect
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect github.com/klauspost/compress v1.12.2
github.com/klauspost/compress v1.11.3 github.com/pierrec/lz4/v4 v4.1.6
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/smartystreets/assertions v1.2.0 // indirect
github.com/ulikunitz/xz v0.5.8 github.com/ulikunitz/xz v0.5.10
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect )
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
require (
github.com/golang/snappy v0.0.3 // indirect
github.com/google/go-cmp v0.5.5 // indirect
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e
github.com/therootcompany/xz v1.0.1
go.lsp.dev/uri v0.3.0 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
) )
+314 -39
View File
@@ -1,73 +1,348 @@
github.com/CalebQ42/GoAppImage v0.4.0 h1:aF+Y/vyo/RGhoyZEW1CMY6WyRWrZZO4ydsRFAtIGnaY= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/CalebQ42/GoAppImage v0.4.0/go.mod h1:qHudJKAn/dlkNWNnH4h1YKXp29EZ7Bppsn7sNP2HuvU= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/adrg/xdg v0.2.2 h1:A7ZHKRz5KGOLJX/bg7IPzStryhvCzAE1wX+KWawPiAo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/CalebQ42/GoAppImage v0.5.0 h1:znoKNXtliH754tS9sYwyOIg/0wFDjFN5Twc7PAh1rSM=
github.com/CalebQ42/GoAppImage v0.5.0/go.mod h1:qHudJKAn/dlkNWNnH4h1YKXp29EZ7Bppsn7sNP2HuvU=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
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.3.3 h1:s/tV7MdqQnzB1nKY8aqHvAMD+uCiuEDzVB5HLRY849U=
github.com/adrg/xdg v0.2.3/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ= github.com/adrg/xdg v0.3.3/go.mod h1:61xAR2VZcggl2St4O9ohF5qCKe08+JDmE4VNzPFQvOQ=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0= github.com/gopherjs/gopherjs v0.0.0-20210420193930-a4630ec28c79 h1:ATVz3rDvK4xX0nHx57zYSHRVIK/+lFwln9KJr8wvuk0=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20210420193930-a4630ec28c79/go.mod h1:Opf9rtYVq0eTyX+aRVmRO9hE8ERAozcdrBxWG9Q6mkQ=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
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.3 h1:dB4Bn0tN3wdCzQxnS8r06kV74qN/TAfaIS0bVE8h3jc= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/klauspost/compress v1.12.2 h1:2KCfW3I9M7nSc5wOqXAlW2v2U6v+w6cbjvbfp+OykW8=
github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
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.1 h1:cS6aGkNLJr4u+UwaA21yp+gbWN3WJWtKo1axmPDObMA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pierrec/lz4/v4 v4.1.1/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pierrec/lz4/v4 v4.1.6 h1:ueMTcBBFrbT8K4uGDNNZPa8Z7LtPV7Cl0TDjaeHxP44=
github.com/pierrec/lz4/v4 v4.1.6/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e h1:dCWirM5F3wMY+cmRda/B1BiPsFtmzXqV9b0hLWtVBMs=
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e/go.mod h1:9leZcVcItj6m9/CfHY5Em/iBrCz7js8LcRQGTKEEv2M=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
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/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
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/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
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/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
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= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 h1:TFlARGu6Czu1z7q93HTxcP1P+/ZFC/IKythI5RzrnRg= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= 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/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
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=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+23 -13
View File
@@ -2,9 +2,10 @@ package compression
import ( import (
"bytes" "bytes"
"compress/zlib"
"encoding/binary" "encoding/binary"
"io" "io"
"github.com/klauspost/compress/zlib"
) )
type gzipInit struct { type gzipInit struct {
@@ -15,23 +16,22 @@ type gzipInit struct {
//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 {
CompressionLevel int32 wrt *zlib.Writer
HasCustomWindow bool gzipInit
HasStrategies bool HasCustomWindow bool
HasStrategies bool
} }
//NewGzipCompressorWithOptions creates a new gzip compressor/decompressor with options read from the given reader. //NewGzipCompressorWithOptions creates a new gzip compressor/decompressor with options read from the given reader.
func NewGzipCompressorWithOptions(r io.Reader) (*Gzip, error) { func NewGzipCompressorWithOptions(r io.Reader) (*Gzip, error) {
var gzip Gzip var gzip Gzip
var init gzipInit err := binary.Read(r, binary.LittleEndian, &gzip.gzipInit)
err := binary.Read(r, binary.LittleEndian, &init)
if err != nil { if err != nil {
return nil, err return nil, err
} }
gzip.CompressionLevel = init.CompressionLevel
//TODO: proper support for window size and strategies //TODO: proper support for window size and strategies
gzip.HasCustomWindow = init.WindowSize != 15 gzip.HasCustomWindow = gzip.WindowSize != 15
gzip.HasStrategies = init.Strategies != 0 && init.Strategies != 1 gzip.HasStrategies = gzip.Strategies != 0 && gzip.Strategies != 1
return &gzip, nil return &gzip, nil
} }
@@ -52,15 +52,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 {
return nil, err
}
}
}
wrt, err := zlib.NewWriterLevel(&buf, int(g.CompressionLevel))
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = wrt.Flush() _, err = wrt.Write(data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
wrt.Close()
return buf.Bytes(), nil return buf.Bytes(), nil
} }
+18
View File
@@ -35,3 +35,21 @@ func (l *Lz4) Decompress(r io.Reader) ([]byte, error) {
_, err := io.Copy(&buf, rdr) _, err := io.Copy(&buf, rdr)
return buf.Bytes(), err 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
}
+30
View File
@@ -0,0 +1,30 @@
package compression
import (
"encoding/binary"
"io"
lzo "github.com/rasky/go-lzo"
)
type Lzo struct {
Algorithm int32
Level int32
}
func NewLzoCompressorWithOptions(rdr io.Reader) (*Lzo, error) {
var lz Lzo
err := binary.Read(rdr, binary.LittleEndian, &lz)
if err != nil {
return nil, err
}
return &lz, nil
}
func (l Lzo) Decompress(rdr io.Reader) ([]byte, error) {
byt, err := lzo.Decompress1X(rdr, 0, 0)
if err != nil {
return nil, err
}
return byt, nil
}
+26 -21
View File
@@ -5,44 +5,29 @@ import (
"encoding/binary" "encoding/binary"
"io" "io"
"github.com/ulikunitz/xz" "github.com/therootcompany/xz"
wrtXz "github.com/ulikunitz/xz"
) )
type xzInit struct {
DictionarySize int32
Filters int32
}
//Xz is a Xz decompressor.
type Xz struct { type Xz struct {
DictionarySize int32 DictionarySize int32
HasFilters bool Filters int32
} }
//NewXzCompressorWithOptions creates a new Xz compressor/decompressor that reads the compressor options from the given reader. //NewXzCompressorWithOptions creates a new Xz compressor/decompressor that reads the compressor options from the given reader.
func NewXzCompressorWithOptions(rdr io.Reader) (*Xz, error) { func NewXzCompressorWithOptions(rdr io.Reader) (*Xz, error) {
var x Xz var x Xz
var init xzInit err := binary.Read(rdr, binary.LittleEndian, &x)
err := binary.Read(rdr, binary.LittleEndian, &init)
if err != nil { if err != nil {
return nil, err return nil, err
} }
x.DictionarySize = init.DictionarySize
//TODO: When I can do filters, parse the filters
if init.Filters != 0 {
x.HasFilters = true
}
return &x, nil return &x, nil
} }
//Decompress decompresses all the data from the rdr and returns the uncompressed bytes. //Decompress decompresses all the data from the rdr and returns the uncompressed bytes.
func (x *Xz) Decompress(rdr io.Reader) ([]byte, error) { func (x *Xz) Decompress(rdr io.Reader) ([]byte, error) {
r, err := xz.NewReader(rdr) r, err := xz.NewReader(rdr, 0)
if err != nil {
return nil, err
}
r.DictCap = int(x.DictionarySize)
err = r.Verify()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -53,3 +38,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 := wrtXz.NewWriter(&buf)
if err != nil {
return nil, err
}
w.DictCap = int(x.DictionarySize)
err = w.Verify()
if err != nil {
return nil, err
}
_, err = w.Write(data)
if err != nil {
return nil, err
}
w.Close()
return buf.Bytes(), nil
}
+15
View File
@@ -34,3 +34,18 @@ func (z *Zstd) Decompress(r io.Reader) ([]byte, error) {
_, err = io.Copy(&buf, rdr) _, err = io.Copy(&buf, rdr)
return buf.Bytes(), err 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
}
+24 -31
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,38 +23,33 @@ 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 Name string
Name string InodeOffset uint32
Header *Header InodeBlockOffset uint16
Type uint16
} }
//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 raw EntryRaw
err := binary.Read(rdr, binary.LittleEndian, &entry.Init) err := binary.Read(rdr, binary.LittleEndian, &raw)
if err != nil { if err != nil {
return Entry{}, err return nil, err
} }
tmp := make([]byte, entry.Init.NameSize+1) tmp := make([]byte, raw.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 nil, err
} }
entry.Name = string(tmp) return &Entry{
return entry, err InodeBlockOffset: raw.Offset,
} Type: raw.Type,
Name: string(tmp),
//Directory is an entry in the directory table of a squashfs. }, nil
//Will only have multiple headers if there are more then 256 entries
type Directory struct {
Headers []Header
Entries []Entry
} }
//NewDirectory reads the directory from rdr //NewDirectory reads the directory from rdr
func NewDirectory(base io.Reader, size uint32) (*Directory, error) { func NewDirectory(base io.Reader, size uint32) (entries []*Entry, err error) {
var dir Directory
var err error
tmp := make([]byte, size) tmp := make([]byte, size)
base.Read(tmp) base.Read(tmp)
rdr := bytes.NewBuffer(tmp) rdr := bytes.NewBuffer(tmp)
@@ -62,6 +57,7 @@ func NewDirectory(base io.Reader, size uint32) (*Directory, error) {
var hdr Header var hdr Header
err = binary.Read(rdr, binary.LittleEndian, &hdr) err = binary.Read(rdr, binary.LittleEndian, &hdr)
if err == io.ErrUnexpectedEOF { if err == io.ErrUnexpectedEOF {
err = nil
break break
} else if err != nil { } else if err != nil {
return nil, err return nil, err
@@ -71,24 +67,21 @@ func NewDirectory(base io.Reader, size uint32) (*Directory, error) {
if hdr.Count%256 > 0 { if hdr.Count%256 > 0 {
headers++ headers++
} }
dir.Headers = append(dir.Headers, hdr)
for i := uint32(0); i < hdr.Count; i++ { for i := uint32(0); i < hdr.Count; i++ {
if i != 0 && i%256 == 0 { if i != 0 && i%256 == 0 {
var newHdr Header err = binary.Read(rdr, binary.LittleEndian, &hdr)
err = binary.Read(rdr, binary.LittleEndian, &newHdr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
dir.Headers = append(dir.Headers, newHdr)
} }
var ent Entry var ent *Entry
ent, err = NewEntry(rdr) ent, err = NewEntry(rdr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ent.Header = &dir.Headers[len(dir.Headers)-1] ent.InodeOffset = hdr.InodeOffset
dir.Entries = append(dir.Entries, ent) entries = append(entries, ent)
} }
} }
return &dir, nil return
} }
+89 -87
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
@@ -24,7 +25,7 @@ const (
//Header is the common header for all inodes //Header is the common header for all inodes
type Header struct { type Header struct {
InodeType uint16 Type uint16
Permissions uint16 Permissions uint16
UID uint16 UID uint16
GID uint16 GID uint16
@@ -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
} }
+51 -48
View File
@@ -2,124 +2,127 @@ package inode
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"io" "io"
"strconv"
) )
//Inode holds an inode. Header is the header that's common for all inodes. //Inode holds an inode. Header is the header that's common for all inodes.
// //
//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 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.
} }
//ProcessInode tries to read an inode from the BlockReader //ProcessInode tries to read an inode from the BlockReader
func ProcessInode(br io.Reader, blockSize uint32) (*Inode, error) { func ProcessInode(br io.Reader, blockSize uint32) (*Inode, error) {
var head Header var in Inode
err := binary.Read(br, binary.LittleEndian, &head) err := binary.Read(br, binary.LittleEndian, &in.Header)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var info interface{} switch in.Type {
switch head.InodeType { case DirType:
case BasicDirectoryType: var inode Dir
var inode BasicDirectory
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 in.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 in.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 in.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 in.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 in.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 in.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 in.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 in.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 in.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 in.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 in.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 in.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 in.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
} }
info = inode in.Info = inode
default:
return nil, errors.New("Unsupported inode type: " + strconv.Itoa(int(in.Type)))
} }
return &Inode{ return &in, nil
Type: int(head.InodeType),
Header: head,
Info: info,
}, nil
} }
+135
View File
@@ -0,0 +1,135 @@
package rawreader
import (
"bytes"
"errors"
"io"
)
func ConvertReader(r io.Reader) (RawReader, error) {
if rr, ok := r.(RawReader); ok {
return rr, nil
}
if rs, is := r.(io.ReadSeeker); is {
return &fromReadSeeker{
ReadSeeker: rs,
}, nil
}
buf := new(bytes.Buffer)
_, err := io.Copy(buf, r)
if err != nil {
return nil, err
}
return &fromReader{
data: buf.Bytes(),
}, nil
}
func ConvertReaderAt(r io.ReaderAt) RawReader {
if rr, ok := r.(RawReader); ok {
return rr
}
return &fromReaderAt{
ReaderAt: r,
}
}
//TODO: Add way to discard data from fromReader
//RawReader implements the needed interfaces for reading a squashfs archive.
type RawReader interface {
io.ReadSeeker
io.ReaderAt
}
type fromReader struct {
data []byte
off int
}
func (r *fromReader) ReadAt(p []byte, off int64) (n int, err error) {
n = len(p)
if int(off)+len(p) > len(r.data) {
n = len(r.data) - int(off)
err = io.EOF
}
if n < 0 {
n = 0
}
for i := 0; i < n; i++ {
p[i] = r.data[int(off)+i]
}
return
}
func (r *fromReader) Seek(off int64, whence int) (n int64, err error) {
switch whence {
case io.SeekEnd:
r.off = len(r.data) - int(off)
if r.off < 0 {
r.off = 0
err = io.EOF
}
case io.SeekCurrent:
r.off += int(off)
case io.SeekStart:
r.off = int(off)
}
if r.off > len(r.data) {
r.off = len(r.data)
return int64(r.off), io.EOF
}
return int64(r.off), err
}
func (r *fromReader) Read(p []byte) (n int, err error) {
n = len(p)
if r.off+len(p) > len(r.data) {
n = len(r.data) - r.off
err = io.EOF
}
if n < 0 {
n = 0
}
for i := 0; i < n; i++ {
p[i] = r.data[r.off+i]
}
return
}
type fromReadSeeker struct {
io.ReadSeeker
}
func (r *fromReadSeeker) ReadAt(p []byte, off int64) (n int, err error) {
tmp, _ := r.Seek(0, io.SeekCurrent)
defer r.Seek(tmp, io.SeekStart)
_, err = r.Seek(off, io.SeekStart)
if err != nil {
return
}
return r.Read(p)
}
type fromReaderAt struct {
io.ReaderAt
off int
}
func (r *fromReaderAt) Read(p []byte) (n int, err error) {
n, err = r.ReadAt(p, int64(r.off))
r.off += n
return
}
func (r *fromReaderAt) Seek(off int64, whence int) (n int64, err error) {
switch whence {
case io.SeekEnd:
return 0, errors.New("cannot SeekEnd RawReader")
case io.SeekCurrent:
r.off += int(off)
case io.SeekStart:
r.off = int(off)
}
return int64(r.off), nil
}
+3 -3
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
} }
@@ -133,7 +133,7 @@ func (br *metadataReader) Read(p []byte) (int, error) {
} }
br.readOffset += read br.readOffset += read
if read != len(p) { if read != len(p) {
return read, errors.New("Didn't read enough data") return read, errors.New("didn't read enough data")
} }
return read, nil return read, nil
} }
@@ -181,7 +181,7 @@ func (br *metadataReader) Seek(offset int64, whence int) (int64, error) {
br.readOffset = len(br.data) - int(offset) br.readOffset = len(br.data) - int(offset)
if br.readOffset < 0 { if br.readOffset < 0 {
br.readOffset = 0 br.readOffset = 0
return int64(br.readOffset), errors.New("Trying to seek to a negative value") return int64(br.readOffset), errors.New("trying to seek to a negative value")
} }
} }
return int64(br.readOffset), nil return int64(br.readOffset), nil
+123 -178
View File
@@ -9,249 +9,194 @@ import (
"github.com/CalebQ42/squashfs/internal/compression" "github.com/CalebQ42/squashfs/internal/compression"
"github.com/CalebQ42/squashfs/internal/inode" "github.com/CalebQ42/squashfs/internal/inode"
"github.com/CalebQ42/squashfs/internal/rawreader"
) )
const ( const (
magic = 0x73717368 magic uint32 = 0x73717368
) )
var ( var (
//ErrNoMagic is returned if the magic number in the superblock isn't correct. //ErrNoMagic is returned if the magic number in the superblock isn't correct.
errNoMagic = errors.New("Magic number doesn't match. Either isn't a squashfs or corrupted") errNoMagic = errors.New("magic number doesn't match. Either isn't a squashfs or corrupted")
//ErrIncompatibleCompression is returned if the compression type in the superblock doesn't work. //ErrIncompatibleCompression is returned if the compression type in the superblock doesn't work.
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 = 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 FS
super superblock r rawreader.RawReader
flags superblockFlags
decompressor compression.Decompressor decompressor compression.Decompressor
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 = rawreader.ConvertReaderAt(r)
err := binary.Read(io.NewSectionReader(rdr.r, 0, int64(binary.Size(rdr.super))), binary.LittleEndian, &rdr.super) err := rdr.Init()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if rdr.super.Magic != magic { return &rdr, nil
return nil, errNoMagic }
//NewSquashfsReaderFromReader returns a new squashfs.Reader from an io.Reader.
//If the io.Reader implements io.Seeker, the seek functions are used.
//It is NOT recommended to use a pure io.Reader as due to how squashfs
//archives are formatted, the ENTIRETY of the io.Reader's data is loaded into
//memory first before it can be used.
func NewSquashfsReaderFromReader(r io.Reader) (*Reader, error) {
var rdr Reader
var err error
rdr.r, err = rawreader.ConvertReader(r)
if err != nil {
return nil, err
} }
// if rdr.super.BlockLog == uint16(math.Log2(float64(rdr.super.BlockSize))) { err = rdr.Init()
// return nil, errors.New("BlockSize and BlockLog doesn't match. The archive is probably corrupt") if err != nil {
// } return nil, err
rdr.flags = rdr.super.GetFlags() }
if rdr.flags.CompressorOptions { return &rdr, nil
switch rdr.super.CompressionType { }
case gzipCompression:
gzip, err := compression.NewGzipCompressorWithOptions(io.NewSectionReader(rdr.r, int64(binary.Size(rdr.super)), 8)) func (r *Reader) Init() error {
err := binary.Read(r.r, binary.LittleEndian, &r.super)
if err != nil {
return err
}
if r.super.Magic != magic {
return errNoMagic
}
if r.super.BlockLog != uint16(math.Log2(float64(r.super.BlockSize))) {
return errors.New("BlockSize and BlockLog doesn't match. The archive is probably corrupt")
}
r.r.Seek(96, io.SeekStart)
r.flags = r.super.GetFlags()
if r.flags.compressorOptions {
switch r.super.CompressionType {
case GzipCompression:
var gzip *compression.Gzip
gzip, err = compression.NewGzipCompressorWithOptions(r.r)
if err != nil { if err != nil {
return nil, err return err
} }
if gzip.HasCustomWindow || gzip.HasStrategies { r.decompressor = gzip
hasUnsupportedOptions = true case XzCompression:
} var xz *compression.Xz
rdr.decompressor = gzip xz, err = compression.NewXzCompressorWithOptions(r.r)
case xzCompression:
xz, err := compression.NewXzCompressorWithOptions(io.NewSectionReader(rdr.r, int64(binary.Size(rdr.super)), 8))
if err != nil { if err != nil {
return nil, err return err
} }
if xz.HasFilters { r.decompressor = xz
return nil, errors.New("XZ compression options has filters. These are not yet supported") case LzoCompression:
} var lz *compression.Lzo
rdr.decompressor = xz lz, err = compression.NewLzoCompressorWithOptions(r.r)
case lz4Compression:
lz4, err := compression.NewLz4CompressorWithOptions(io.NewSectionReader(rdr.r, int64(binary.Size(rdr.super)), 8))
if err != nil { if err != nil {
return nil, err return err
} }
if lz4.HC { r.decompressor = lz
hasUnsupportedOptions = true case Lz4Compression:
} var lz4 *compression.Lz4
rdr.decompressor = lz4 lz4, err = compression.NewLz4CompressorWithOptions(r.r)
case zstdCompression:
zstd, err := compression.NewZstdCompressorWithOptions(io.NewSectionReader(rdr.r, int64(binary.Size(rdr.super)), 4))
if err != nil { if err != nil {
return nil, err return err
} }
rdr.decompressor = zstd r.decompressor = lz4
case ZstdCompression:
var zstd *compression.Zstd
zstd, err = compression.NewZstdCompressorWithOptions(r.r)
if err != nil {
return err
}
r.decompressor = zstd
default: default:
return nil, errCompressorOptions return errIncompatibleCompression
} }
} else { } else {
switch rdr.super.CompressionType { switch r.super.CompressionType {
case gzipCompression: case GzipCompression:
rdr.decompressor = &compression.Gzip{} r.decompressor = &compression.Gzip{}
case lzmaCompression: case LzmaCompression:
rdr.decompressor = &compression.Lzma{} r.decompressor = &compression.Lzma{}
case xzCompression: case LzoCompression:
rdr.decompressor = &compression.Xz{} r.decompressor = &compression.Lzo{}
case lz4Compression: case XzCompression:
rdr.decompressor = &compression.Lz4{} r.decompressor = &compression.Xz{}
case zstdCompression: case Lz4Compression:
rdr.decompressor = &compression.Zstd{} r.decompressor = &compression.Lz4{}
case ZstdCompression:
r.decompressor = &compression.Zstd{}
default: default:
//TODO: all compression types. //TODO: all compression types.
return nil, errIncompatibleCompression return errIncompatibleCompression
} }
} }
fragBlocks := int(math.Ceil(float64(rdr.super.FragCount) / 512)) fragBlocks := int(math.Ceil(float64(r.super.FragCount) / 512))
if fragBlocks > 0 { if fragBlocks > 0 {
offset := int64(rdr.super.FragTableStart) offset := int64(r.super.FragTableStart)
for i := 0; i < fragBlocks; i++ { for i := 0; i < fragBlocks; i++ {
tmp := make([]byte, 8) tmp := make([]byte, 8)
_, err = r.ReadAt(tmp, offset) _, err = r.r.ReadAt(tmp, offset)
if err != nil { if err != nil {
return nil, err return err
} }
rdr.fragOffsets = append(rdr.fragOffsets, binary.LittleEndian.Uint64(tmp)) r.fragOffsets = append(r.fragOffsets, binary.LittleEndian.Uint64(tmp))
offset += 8 offset += 8
} }
} }
unread := rdr.super.IDCount unread := r.super.IDCount
blockOffsets := make([]uint64, int(math.Ceil(float64(rdr.super.IDCount)/2048))) blockOffsets := make([]uint64, int(math.Ceil(float64(r.super.IDCount)/2048)))
_, err = r.r.Seek(int64(r.super.IDTableStart), io.SeekStart)
if err != nil {
return err
}
for i := range blockOffsets { for i := range blockOffsets {
secRdr := io.NewSectionReader(r, int64(rdr.super.IDTableStart)+(8*int64(i)), 8) err = binary.Read(r.r, binary.LittleEndian, &blockOffsets[i])
err = binary.Read(secRdr, binary.LittleEndian, &blockOffsets[i])
if err != nil { if err != nil {
return nil, err return err
} }
idRdr, err := rdr.newMetadataReader(int64(blockOffsets[i])) var idRdr *metadataReader
idRdr, err = r.newMetadataReader(int64(blockOffsets[i]))
if err != nil { if err != nil {
return nil, err return err
} }
read := uint16(math.Min(float64(unread), 2048)) read := uint16(math.Min(float64(unread), 2048))
for i := uint16(0); i < read; i++ { for i := uint16(0); i < read; i++ {
var tmp uint32 var tmp uint32
err = binary.Read(idRdr, binary.LittleEndian, &tmp) err = binary.Read(idRdr, binary.LittleEndian, &tmp)
if err != nil { if err != nil {
return nil, err return err
} }
rdr.idTable = append(rdr.idTable, tmp) r.idTable = append(r.idTable, tmp)
} }
unread -= read unread -= read
} }
if hasUnsupportedOptions { metaRdr, err := r.newMetadataReaderFromInodeRef(r.super.RootInodeRef)
return &rdr, ErrOptions if err != nil {
return err
} }
return &rdr, nil i, err := inode.ProcessInode(metaRdr, r.super.BlockSize)
if err != nil {
return err
}
entries, err := r.readDirFromInode(i)
if err != nil {
return err
}
r.FS = FS{
r: r,
name: "/",
entries: entries,
}
return nil
} }
//ModTime is the last time the file was modified/created. //ModTime is the last time the file was modified/created.
func (r *Reader) ModTime() time.Time { func (r *Reader) ModTime() time.Time {
return time.Unix(int64(r.super.CreationTime), 0) 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)
mr, err := r.newMetadataReaderFromInodeRef(r.super.RootInodeRef)
if err != nil {
return nil, err
}
root.in, err = inode.ProcessInode(mr, r.super.BlockSize)
if err != nil {
return nil, err
}
root.path = "/"
root.filType = root.in.Type
root.r = r
return root, nil
}
//GetAllFiles returns a slice of ALL files and folders contained in the squashfs.
func (r *Reader) GetAllFiles() (fils []*File, err error) {
root, err := r.GetRootFolder()
if err != nil {
return nil, err
}
return 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.
func (r *Reader) FindFile(query func(*File) bool) *File {
root, err := r.GetRootFolder()
if err != nil {
return nil
}
fils, err := root.GetChildren()
if err != nil {
return nil
}
var childrenDirs []*File
for _, fil := range fils {
if query(fil) {
return fil
}
if fil.IsDir() {
childrenDirs = append(childrenDirs, fil)
}
}
for len(childrenDirs) != 0 {
var tmp []*File
for _, dirs := range childrenDirs {
chil, err := dirs.GetChildren()
if err != nil {
return nil
}
for _, child := range chil {
if query(child) {
return child
}
if child.IsDir() {
tmp = append(tmp, child)
}
}
}
childrenDirs = tmp
}
return nil
}
//FindAll returns all files where the given function returns true.
func (r *Reader) FindAll(query func(*File) bool) (all []*File) {
root, err := r.GetRootFolder()
if err != nil {
return nil
}
fils, err := root.GetChildrenRecursively()
if err != nil {
return nil
}
for _, fil := range fils {
if query(fil) {
all = append(all, fil)
}
}
return
}
//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 {
dir, err := r.GetRootFolder()
if err != nil {
return nil
}
return dir.GetFileAtPath(path)
}
+242
View File
@@ -0,0 +1,242 @@
package squashfs
import (
"bytes"
"errors"
"io"
"github.com/CalebQ42/squashfs/internal/inode"
)
var (
//ErrInodeNotFile is given when giving an inode, but the function requires a file inode.
errInodeNotFile = errors.New("given inode is NOT a file type")
//ErrInodeOnlyFragment is given when trying to make a DataReader from an inode, but the inode only had data in a fragment
errInodeOnlyFragment = errors.New("given inode ONLY has fragment data")
)
//DataReader reads data from data blocks.
type dataReader struct {
r *Reader
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
}
//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) {
var dr dataReader
dr.r = r
dr.offset = offset
dr.sizes = sizes
err := dr.readCurBlock()
if err != nil {
return nil, err
}
return &dr, nil
}
//NewDataReaderFromInode creates a new DataReader from a given inode. Inode must be of BasicFile or ExtendedFile types
func (r *Reader) newDataReaderFromInode(i *inode.Inode) (*dataReader, error) {
var rdr dataReader
rdr.r = r
switch i.Type {
case inode.FileType:
fil := i.Info.(inode.File)
if fil.BlockStart == 0 {
return nil, errInodeOnlyFragment
}
rdr.offset = int64(fil.BlockStart)
rdr.sizes = append(rdr.sizes, fil.BlockSizes...)
if fil.Fragmented {
rdr.sizes = rdr.sizes[:len(rdr.sizes)-1]
}
case inode.ExtFileType:
fil := i.Info.(inode.ExtFile)
if fil.BlockStart == 0 {
return nil, errInodeOnlyFragment
}
rdr.offset = int64(fil.BlockStart)
rdr.sizes = append(rdr.sizes, fil.BlockSizes...)
if fil.Fragmented {
rdr.sizes = rdr.sizes[:len(rdr.sizes)-1]
}
default:
return nil, errInodeNotFile
}
err := rdr.readCurBlock()
if err != nil {
return nil, err
}
return &rdr, nil
}
//removed the compression bit from a data block size
func actualDataSize(size uint32) uint32 {
return size &^ (1 << 24)
}
func (d *dataReader) readNextBlock() error {
d.curBlock++
if d.curBlock >= len(d.sizes) {
d.curBlock--
return io.EOF
}
err := d.readCurBlock()
if err != nil {
d.curBlock--
d.readCurBlock()
return err
}
return nil
}
func (d *dataReader) readBlockAt(offset int64, size uint32) ([]byte, error) {
compressed := size&(1<<24) != (1 << 24)
size = size &^ (1 << 24)
if d.sizes[d.curBlock] == 0 {
return make([]byte, d.r.super.BlockSize), nil
}
sec := io.NewSectionReader(d.r.r, offset, int64(size))
if compressed {
btys, err := d.r.decompressor.Decompress(sec)
if err != nil {
return nil, err
}
return btys, nil
}
var buf bytes.Buffer
_, err := io.Copy(&buf, sec)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (d *dataReader) offsetForBlock(index int) int64 {
out := d.offset
for i := 0; i < index; i++ {
out += int64(actualDataSize(d.sizes[i]))
}
return out
}
func (d *dataReader) readCurBlock() error {
if d.curBlock >= len(d.sizes) {
return io.EOF
}
offset := d.offsetForBlock(d.curBlock)
data, err := d.readBlockAt(offset, d.sizes[d.curBlock])
if err != nil {
return err
}
d.curData = data
return nil
}
func (d *dataReader) Read(p []byte) (int, error) {
if d.curData == nil {
err := d.readCurBlock()
if err != nil {
return 0, err
}
}
if d.curReadOffset+len(p) <= len(d.curData) {
for i := 0; i < len(p); i++ {
p[i] = d.curData[d.curReadOffset+i]
}
d.curReadOffset += len(p)
return len(p), nil
}
read := 0
for read < len(p) {
if d.curReadOffset == len(d.curData) {
err := d.readNextBlock()
if err != nil {
return read, err
}
d.curReadOffset = 0
}
for ; read < len(p); read++ {
if d.curReadOffset < len(d.curData) {
p[read] = d.curData[d.curReadOffset]
} else {
break
}
d.curReadOffset++
}
}
if read != len(p) {
return read, errors.New("didn't read enough data")
}
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
}(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)
}
}
}
+381
View File
@@ -0,0 +1,381 @@
package squashfs
import (
"errors"
"io"
"io/fs"
"log"
"os"
"path"
"strconv"
"strings"
"github.com/CalebQ42/squashfs/internal/directory"
"github.com/CalebQ42/squashfs/internal/inode"
)
//File represents a file inside a squashfs archive.
type File struct {
i *inode.Inode
parent *FS
r *Reader
reader *fileReader
name string
dirsRead int
}
//File creates a File from the FileInfo.
//*File satisfies fs.File and fs.ReadDirFile.
func (f FileInfo) File() (file *File, err error) {
file = &File{
name: f.name,
r: f.r,
parent: f.parent,
i: f.i,
}
if file.IsRegular() {
file.reader, err = f.r.newFileReader(f.i)
}
return
}
//File creates a File from the DirEntry.
func (d DirEntry) File() (file *File, err error) {
return d.r.newFileFromDirEntry(d.en, d.parent)
}
func (r Reader) newFileFromDirEntry(en *directory.Entry, parent *FS) (file *File, err error) {
file = &File{
name: en.Name,
r: &r,
parent: parent,
}
file.i, err = r.getInodeFromEntry(en)
if err != nil {
return nil, err
}
if file.IsRegular() {
file.reader, err = r.newFileReader(file.i)
}
return
}
//Stat returns the File's fs.FileInfo
func (f File) Stat() (fs.FileInfo, error) {
return &FileInfo{
i: f.i,
name: f.name,
parent: f.parent,
r: f.r,
}, nil
}
//Read reads the data from the file. Only works if file is a normal file.
func (f File) Read(p []byte) (int, error) {
if f.i.Type == inode.FileType || f.i.Type == inode.ExtFileType {
if f.reader == nil {
return 0, fs.ErrClosed
}
return f.reader.Read(p)
}
return 0, errors.New("can only read files")
}
//WriteTo writes all data from the file to the writer. This is multi-threaded.
func (f File) WriteTo(w io.Writer) (int64, error) {
if f.i.Type == inode.FileType || f.i.Type == inode.ExtFileType {
if f.reader == nil {
return 0, fs.ErrClosed
}
return f.reader.WriteTo(w)
}
return 0, errors.New("can only read files")
}
//Close simply nils the underlying reader. Here mostly to satisfy fs.File
func (f *File) Close() error {
f.reader = nil
return nil
}
//ReadDir returns n fs.DirEntry's that's contained in the File (if it's a directory).
//If n <= 0 all fs.DirEntry's are returned.
func (f File) ReadDir(n int) ([]fs.DirEntry, error) {
if !f.IsDir() {
return nil, errors.New("File is not a directory")
}
ffs, err := f.FS()
if err != nil {
return nil, err
}
var beg, end int
if n <= 0 {
beg, end = 0, len(ffs.entries)
} else {
beg, end = f.dirsRead, f.dirsRead+n
if end > len(ffs.entries) {
end = len(ffs.entries)
err = io.EOF
}
}
out := make([]fs.DirEntry, end-beg)
for i, ent := range ffs.entries[beg:end] {
out[i] = f.r.newDirEntry(ent, ffs)
}
return out, err
}
//FS returns the File as a FS.
func (f File) FS() (*FS, error) {
if !f.IsDir() {
return nil, errors.New("File is not a directory")
}
ents, err := f.r.readDirFromInode(f.i)
if err != nil {
return nil, err
}
return &FS{
entries: ents,
parent: f.parent,
r: f.r,
name: f.name,
}, nil
}
//IsDir Yep.
func (f File) IsDir() bool {
return f.i.Type == inode.DirType || f.i.Type == inode.ExtDirType
}
func (f File) path() string {
if f.name == "/" {
return f.name
}
return f.parent.path() + "/" + f.name
}
//IsRegular yep.
func (f File) IsRegular() bool {
return f.i.Type == inode.FileType || f.i.Type == inode.ExtFileType
}
//IsSymlink yep.
func (f File) IsSymlink() bool {
return f.i.Type == inode.SymType || f.i.Type == inode.ExtSymType
}
//SymlinkPath returns the symlink's target path. Is the File isn't a symlink, returns an empty string.
func (f File) SymlinkPath() string {
switch f.i.Type {
case inode.SymType:
return f.i.Info.(inode.Sym).Path
case inode.ExtSymType:
return f.i.Info.(inode.ExtSym).Path
}
return ""
}
//GetSymlinkFile returns the File the symlink is pointing to.
//If not a symlink, or the target is unobtainable (such as it being outside the archive or it's absolute) returns nil
func (f File) GetSymlinkFile() *File {
if !f.IsSymlink() {
return nil
}
if strings.HasPrefix(f.SymlinkPath(), "/") {
return nil
}
sym, err := f.parent.Open(f.SymlinkPath())
if err != nil {
return nil
}
return sym.(*File)
}
//ExtractionOptions are available options on how to extract.
type ExtractionOptions struct {
notBase bool
DereferenceSymlink bool //Replace symlinks with the target file
UnbreakSymlink bool //Try to make sure symlinks remain unbroken when extracted, without changing the symlink
Verbose bool //Prints extra info to log on an error
FolderPerm fs.FileMode //The permissions used when creating the extraction folder
}
//DefaultOptions is the default ExtractionOptions.
func DefaultOptions() ExtractionOptions {
return ExtractionOptions{
DereferenceSymlink: false,
UnbreakSymlink: false,
Verbose: false,
FolderPerm: fs.ModePerm,
}
}
//ExtractTo extracts the File to the given folder with the default options.
//If the File is a directory, it instead extracts the directory's contents to the folder.
func (f File) ExtractTo(folder string) error {
return f.ExtractWithOptions(folder, DefaultOptions())
}
//ExtractSymlink extracts the File to the folder with the DereferenceSymlink option.
//If the File is a directory, it instead extracts the directory's contents to the folder.
func (f File) ExtractSymlink(folder string) error {
return f.ExtractWithOptions(folder, ExtractionOptions{
DereferenceSymlink: true,
FolderPerm: fs.ModePerm,
})
}
//ExtractWithOptions extracts the File to the given folder with the given ExtrationOptions.
//If the File is a directory, it instead extracts the directory's contents to the folder.
func (f File) ExtractWithOptions(folder string, op ExtractionOptions) error {
folder = path.Clean(folder)
if !op.notBase {
err := os.MkdirAll(folder, op.FolderPerm)
if err != nil {
return err
}
}
stat, err := f.Stat()
if err != nil {
return err
}
if f.IsDir() {
if op.notBase {
err = os.Mkdir(folder+"/"+f.name, stat.Mode())
if err != nil && !os.IsExist(err) {
return err
}
} else {
op.notBase = true
}
var ents []fs.DirEntry
ents, err = f.ReadDir(0)
if err != nil {
if op.Verbose {
log.Println("Error while reading children of", f.path())
}
return err
}
errChan := make(chan error)
for i := 0; i < len(ents); i++ {
go func(ent *DirEntry) {
fil, goErr := ent.File()
if goErr != nil {
errChan <- goErr
fil.Close()
return
}
errChan <- fil.ExtractWithOptions(folder+"/"+f.name, op)
fil.Close()
}(ents[i].(*DirEntry))
}
for i := 0; i < len(ents); i++ {
err = <-errChan
if err != nil {
return err
}
}
return nil
} else if f.IsRegular() {
var fil *os.File
fil, err = os.Create(folder + "/" + f.name)
if os.IsExist(err) {
os.Remove(folder + "/" + f.name)
fil, err = os.Create(folder + "/" + f.name)
if err != nil {
log.Println("Error while creating", folder+"/"+f.name)
return err
}
} else if err != nil {
return err
}
_, err = io.Copy(fil, f)
if err != nil {
log.Println("Error while copying data to", folder+"/"+f.name)
return err
}
return nil
} else if f.IsSymlink() {
symPath := f.SymlinkPath()
if op.DereferenceSymlink {
fil := f.GetSymlinkFile()
if fil == nil {
if op.Verbose {
log.Println("Symlink path(", symPath, ") is unobtainable:", folder+"/"+f.name)
}
return errors.New("cannot get symlink target")
}
fil.name = f.name
err = fil.ExtractWithOptions(folder, op)
if err != nil {
if op.Verbose {
log.Println("Error while extracting the symlink's file:", folder+"/"+f.name)
}
return err
}
return nil
} else if op.UnbreakSymlink {
fil := f.GetSymlinkFile()
if fil == nil {
if op.Verbose {
log.Println("Symlink path(", symPath, ") is unobtainable:", folder+"/"+f.name)
}
return errors.New("cannot get symlink target")
}
extractLoc := path.Clean(folder + "/" + path.Dir(symPath))
err = fil.ExtractWithOptions(extractLoc, op)
if err != nil {
if op.Verbose {
log.Println("Error while extracting ", folder+"/"+f.name)
}
return err
}
}
err = os.Symlink(f.SymlinkPath(), folder+"/"+f.name)
if os.IsExist(err) {
os.Remove(folder + "/" + f.name)
err = os.Symlink(f.SymlinkPath(), folder+"/"+f.name)
}
if err != nil {
if op.Verbose {
log.Println("Error while making symlink:", folder+"/"+f.name)
}
return err
}
return nil
}
return errors.New("Unsupported file type. Inode type: " + strconv.Itoa(int(f.i.Type)))
}
//ReadDirFromInode returns a fully populated Directory from a given Inode.
//If the given inode is not a directory it returns an error.
func (r *Reader) readDirFromInode(i *inode.Inode) ([]*directory.Entry, error) {
var offset uint32
var metaOffset uint16
var size uint32
switch i.Type {
case inode.DirType:
offset = i.Info.(inode.Dir).DirectoryIndex
metaOffset = i.Info.(inode.Dir).DirectoryOffset
size = uint32(i.Info.(inode.Dir).DirectorySize)
case inode.ExtDirType:
offset = i.Info.(inode.ExtDir).DirectoryIndex
metaOffset = i.Info.(inode.ExtDir).DirectoryOffset
size = i.Info.(inode.ExtDir).DirectorySize
default:
return nil, errors.New("not a directory inode")
}
br, err := r.newMetadataReader(int64(r.super.DirTableStart + uint64(offset)))
if err != nil {
return nil, err
}
_, err = br.Seek(int64(metaOffset), io.SeekStart)
if err != nil {
return nil, err
}
ents, err := directory.NewDirectory(br, size)
if err != nil {
return nil, err
}
return ents, nil
}
+31 -18
View File
@@ -22,27 +22,27 @@ type fileReader struct {
var ( var (
//ErrPathIsNotFile returns when trying to read from a file, but the given path is NOT a file. //ErrPathIsNotFile returns when trying to read from a file, but the given path is NOT a file.
errPathIsNotFile = errors.New("The given path is not a file") errPathIsNotFile = errors.New("the given path is not a file")
) )
//ReadFile provides a squashfs.FileReader for the file at the given location. //ReadFile provides a squashfs.FileReader for the file at the given location.
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 {
@@ -53,19 +53,13 @@ func (r *Reader) newFileReader(in *inode.Inode) (*fileReader, error) {
} }
if !rdr.fragOnly { if !rdr.fragOnly {
rdr.data, err = r.newDataReaderFromInode(in) rdr.data, err = r.newDataReaderFromInode(in)
if err != nil {
return nil, err
}
} }
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)
@@ -81,6 +75,9 @@ func (f *fileReader) Read(p []byte) (int, error) {
if f.fragged && err == io.EOF { if f.fragged && err == io.EOF {
if f.fragmentData == nil { if f.fragmentData == nil {
f.fragmentData, err = f.r.getFragmentDataFromInode(f.in) f.fragmentData, err = f.r.getFragmentDataFromInode(f.in)
if err != nil {
return read, err
}
} }
n, err = bytes.NewBuffer(f.fragmentData).Read(p[read:]) n, err = bytes.NewBuffer(f.fragmentData).Read(p[read:])
read += n read += n
@@ -92,3 +89,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
}
+493
View File
@@ -0,0 +1,493 @@
package squashfs
import (
"bytes"
"errors"
"io"
"io/fs"
"os"
"path"
"strings"
"github.com/CalebQ42/squashfs/internal/directory"
)
//FS is a fs.FS representation of a squashfs directory.
//Implements fs.GlobFS, fs.ReadDirFS, fs.ReadFileFS, fs.StatFS, and fs.SubFS
type FS struct {
r *Reader
parent *FS
name string
entries []*directory.Entry
}
//Open opens the file at name. Returns a squashfs.File.
func (f FS) Open(name string) (fs.File, error) {
if !fs.ValidPath(name) {
return nil, &fs.PathError{
Op: "open",
Path: name,
Err: fs.ErrInvalid,
}
}
name = path.Clean(strings.TrimPrefix(name, "/"))
split := strings.Split(name, "/")
if split[0] == ".." {
if f.parent == nil {
//This should only happen on the root FS
return nil, &fs.PathError{
Op: "open",
Path: name,
//TODO: make error clearer
Err: errors.New("trying to get file outside of squashfs"),
}
}
return f.parent.Open(strings.Join(split[1:], "/"))
}
for i := 0; i < len(f.entries); i++ {
if match, _ := path.Match(split[0], f.entries[i].Name); match {
if len(split) == 1 {
return f.r.newFileFromDirEntry(f.entries[i], &f)
}
sub, err := f.Sub(split[0])
if err != nil {
if pathErr, ok := err.(*fs.PathError); ok {
pathErr.Op = "open"
pathErr.Path = name
return nil, err
}
return nil, &fs.PathError{
Op: "open",
Path: name,
Err: err,
}
}
fil, err := sub.Open(strings.Join(split[1:], "/"))
if err != nil {
if pathErr, ok := err.(*fs.PathError); ok {
if pathErr.Err == fs.ErrNotExist {
continue
}
pathErr.Op = "open"
pathErr.Path = name
return nil, err
}
return nil, &fs.PathError{
Op: "open",
Path: name,
Err: err,
}
}
return fil, nil
}
}
return nil, &fs.PathError{
Op: "open",
Path: name,
Err: fs.ErrNotExist,
}
}
//Glob returns the name of the files at the given pattern.
//All paths are relative to the FS.
func (f FS) Glob(pattern string) (out []string, err error) {
if !fs.ValidPath(pattern) {
return nil, &fs.PathError{
Op: "glob",
Path: pattern,
Err: fs.ErrInvalid,
}
}
pattern = path.Clean(strings.TrimPrefix(pattern, "/"))
split := strings.Split(pattern, "/")
if split[0] == ".." {
if f.parent == nil {
//This should only happen on the root FS
return nil, &fs.PathError{
Op: "readdir",
Path: pattern,
//TODO: make error clearer
Err: errors.New("trying to get file outside of squashfs"),
}
}
return f.parent.Glob(strings.Join(split[1:], "/"))
}
for i := 0; i < len(f.entries); i++ {
if match, _ := path.Match(split[0], f.entries[i].Name); match {
if len(split) == 1 {
out = append(out, f.entries[i].Name)
continue
}
sub, err := f.Sub(split[0])
if err != nil {
if pathErr, ok := err.(*fs.PathError); ok {
if pathErr.Err == fs.ErrNotExist {
continue
}
pathErr.Op = "glob"
pathErr.Path = pattern
return nil, pathErr
}
return nil, &fs.PathError{
Op: "glob",
Path: pattern,
Err: err,
}
}
subGlob, err := sub.(FS).Glob(strings.Join(split[1:], "/"))
if err != nil {
if pathErr, ok := err.(*fs.PathError); ok {
if pathErr.Err == fs.ErrNotExist {
continue
}
pathErr.Op = "glob"
pathErr.Path = pattern
return nil, pathErr
}
return nil, &fs.PathError{
Op: "glob",
Path: pattern,
Err: err,
}
}
for i := 0; i < len(subGlob); i++ {
subGlob[i] = f.name + "/" + subGlob[i]
}
out = append(out, subGlob...)
}
}
return
}
//ReadDir returns all the DirEntry returns all DirEntry's for the directory at name.
//If name is not a directory, returns an error.
func (f FS) ReadDir(name string) ([]fs.DirEntry, error) {
if !fs.ValidPath(name) {
return nil, &fs.PathError{
Op: "readdir",
Path: name,
Err: fs.ErrInvalid,
}
}
name = path.Clean(strings.TrimPrefix(name, "/"))
split := strings.Split(name, "/")
if split[0] == ".." {
if f.parent == nil {
//This should only happen on the root FS
return nil, &fs.PathError{
Op: "readdir",
Path: name,
//TODO: make error clearer
Err: errors.New("trying to get file outside of squashfs"),
}
}
return f.parent.ReadDir(strings.Join(split[1:], "/"))
}
for i := 0; i < len(f.entries); i++ {
if match, _ := path.Match(split[0], f.entries[i].Name); match {
if len(split) == 1 {
in, err := f.r.getInodeFromEntry(f.entries[i])
if err != nil {
return nil, &fs.PathError{
Op: "readdir",
Path: name,
Err: err,
}
}
ents, err := f.r.readDirFromInode(in)
if err != nil {
return nil, &fs.PathError{
Op: "readdir",
Path: name,
Err: err,
}
}
out := make([]fs.DirEntry, len(f.entries))
for i, ent := range ents {
out[i] = &DirEntry{
en: ent,
parent: &f,
r: f.r,
}
}
return out, nil
}
sub, err := f.Sub(split[0])
if err != nil {
if pathErr, ok := err.(*fs.PathError); ok {
if pathErr.Err == fs.ErrNotExist {
continue
}
pathErr.Op = "readir"
pathErr.Path = name
return nil, pathErr
}
return nil, &fs.PathError{
Op: "readdir",
Path: name,
Err: err,
}
}
redDir, err := sub.(FS).ReadDir(strings.Join(split[1:], "/"))
if err != nil {
if pathErr, ok := err.(*fs.PathError); ok {
if pathErr.Err == fs.ErrNotExist {
continue
}
pathErr.Op = "readdir"
pathErr.Path = name
return nil, pathErr
}
return nil, &fs.PathError{
Op: "readdir",
Path: name,
Err: err,
}
}
return redDir, nil
}
}
return nil, &fs.PathError{
Op: "readdir",
Path: name,
Err: fs.ErrNotExist,
}
}
//ReadFile returns the data (in []byte) for the file at name.
func (f FS) ReadFile(name string) ([]byte, error) {
fil, err := f.Open(name)
if err != nil {
if pathErr, ok := err.(*fs.PathError); ok {
pathErr.Op = "readfile"
pathErr.Path = name
return nil, pathErr
}
}
var buf bytes.Buffer
_, err = io.Copy(&buf, fil)
if err != nil {
return nil, &fs.PathError{
Op: "readfile",
Path: name,
Err: err,
}
}
return buf.Bytes(), nil
}
//Stat returns the fs.FileInfo for the file at name.
func (f FS) Stat(name string) (fs.FileInfo, error) {
if !fs.ValidPath(name) {
return nil, &fs.PathError{
Op: "stat",
Path: name,
Err: fs.ErrInvalid,
}
}
name = path.Clean(strings.TrimPrefix(name, "/"))
split := strings.Split(name, "/")
if split[0] == ".." {
if f.parent == nil {
//This should only happen on the root FS
return nil, &fs.PathError{
Op: "stat",
Path: name,
//TODO: make error clearer
Err: errors.New("trying to get file outside of squashfs"),
}
}
return f.parent.Stat(strings.Join(split[1:], "/"))
}
for i := 0; i < len(f.entries); i++ {
if match, _ := path.Match(split[0], f.entries[i].Name); match {
if len(split) == 1 {
in, err := f.r.getInodeFromEntry(f.entries[i])
if err != nil {
return nil, &fs.PathError{
Op: "stat",
Path: name,
Err: err,
}
}
return FileInfo{
i: in,
parent: &f,
r: f.r,
name: f.entries[i].Name,
}, nil
}
sub, err := f.Sub(split[0])
if err != nil {
if pathErr, ok := err.(*fs.PathError); ok {
if pathErr.Err == fs.ErrNotExist {
continue
}
pathErr.Op = "stat"
pathErr.Path = name
return nil, pathErr
}
return nil, &fs.PathError{
Op: "stat",
Path: name,
Err: err,
}
}
stat, err := sub.(FS).Stat(strings.Join(split[1:], "/"))
if err != nil {
if pathErr, ok := err.(*fs.PathError); ok {
if pathErr.Err == fs.ErrNotExist {
continue
}
pathErr.Op = "stat"
pathErr.Path = name
return nil, pathErr
}
return nil, &fs.PathError{
Op: "stat",
Path: name,
Err: err,
}
}
return stat, nil
}
}
return nil, &fs.PathError{
Op: "stat",
Path: name,
Err: fs.ErrNotExist,
}
}
//Sub returns the FS at dir
func (f FS) Sub(dir string) (fs.FS, error) {
if !fs.ValidPath(dir) {
return nil, &fs.PathError{
Op: "sub",
Path: dir,
Err: fs.ErrInvalid,
}
}
dir = path.Clean(strings.TrimPrefix(dir, "/"))
split := strings.Split(dir, "/")
if split[0] == ".." {
if f.parent == nil {
//This should only happen on the root FS
return nil, &fs.PathError{
Op: "sub",
Path: dir,
//TODO: make error clearer
Err: errors.New("trying to get file outside of squashfs"),
}
}
return f.parent.Sub(strings.Join(split[1:], "/"))
}
for i := 0; i < len(f.entries); i++ {
if match, _ := path.Match(split[0], f.entries[i].Name); match {
if len(split) == 1 {
in, err := f.r.getInodeFromEntry(f.entries[i])
if err != nil {
return nil, &fs.PathError{
Op: "sub",
Path: dir,
Err: err,
}
}
ents, err := f.r.readDirFromInode(in)
if err != nil {
return nil, &fs.PathError{
Op: "sub",
Path: dir,
Err: err,
}
}
return &FS{
r: f.r,
parent: &f,
name: f.entries[i].Name,
entries: ents,
}, nil
}
sub, err := f.Sub(strings.Join(split[1:], "/"))
if err != nil {
if pathErr, ok := err.(*fs.PathError); ok {
if pathErr.Err == fs.ErrNotExist {
continue
}
pathErr.Op = "sub"
pathErr.Path = dir
return nil, pathErr
}
return nil, &fs.PathError{
Op: "sub",
Path: dir,
Err: err,
}
}
return sub, nil
}
}
return nil, &fs.PathError{
Op: "sub",
Path: dir,
Err: fs.ErrNotExist,
}
}
func (f FS) path() string {
if f.name == "/" {
return f.name
} else if f.parent.name == "/" {
return f.name
}
return f.parent.path() + "/" + f.name
}
//ExtractTo extracts the File to the given folder with the default options.
//It extracts the directory's contents to the folder.
func (f FS) ExtractTo(folder string) error {
return f.ExtractWithOptions(folder, DefaultOptions())
}
//ExtractSymlink extracts the File to the folder with the DereferenceSymlink option.
//It extracts the directory's contents to the folder.
func (f FS) ExtractSymlink(folder string) error {
return f.ExtractWithOptions(folder, ExtractionOptions{
DereferenceSymlink: true,
FolderPerm: fs.ModePerm,
})
}
//ExtractWithOptions extracts the File to the given folder with the given ExtrationOptions.
//It extracts the directory's contents to the folder.
func (f FS) ExtractWithOptions(folder string, op ExtractionOptions) error {
op.notBase = true
folder = path.Clean(folder)
err := os.MkdirAll(folder, op.FolderPerm)
if err != nil {
return err
}
errChan := make(chan error)
for i := 0; i < len(f.entries); i++ {
go func(ent *DirEntry) {
fil, goErr := ent.File()
if goErr != nil {
errChan <- goErr
return
}
errChan <- fil.ExtractWithOptions(folder, op)
fil.Close()
}(&DirEntry{
en: f.entries[i],
parent: &f,
r: f.r,
})
}
for i := 0; i < len(f.entries); i++ {
err := <-errChan
if err != nil {
return err
}
}
return nil
}
+272
View File
@@ -0,0 +1,272 @@
package squashfs
import (
"fmt"
"io"
"net/http"
"os"
"os/exec"
"strconv"
"testing"
"time"
goappimage "github.com/CalebQ42/GoAppImage"
)
const (
appImageURL = "https://github.com/srevinsaju/Firefox-Appimage/releases/download/firefox-v84.0.r20201221152838/firefox-84.0.r20201221152838-x86_64.AppImage"
appImageName = "firefox-84.0.r20201221152838-x86_64.AppImage"
squashfsURL = "https://darkstorm.tech/LinuxPATest.sfs"
squashfsName = "LinuxPATest.sfs"
)
func TestSquashfs(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
squashFil, err := os.Open(wd + "/testing/" + squashfsName)
if os.IsNotExist(err) {
err = downloadTestSquash(wd + "/testing")
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")
op := DefaultOptions()
op.Verbose = true
err = rdr.ExtractWithOptions(wd+"/testing/"+squashfsName+".d", op)
if err != nil {
t.Fatal(err)
}
t.Fatal("No Problems")
}
func TestSquashfsFromReader(t *testing.T) {
resp, err := http.DefaultClient.Get(squashfsURL)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
rdr, err := NewSquashfsReaderFromReader(resp.Body)
if err != nil {
t.Fatal(err)
}
os.RemoveAll("testing/" + squashfsName + ".d")
op := DefaultOptions()
op.Verbose = true
err = rdr.ExtractWithOptions("testing/"+squashfsName+".d", op)
if err != nil {
t.Fatal(err)
}
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()
ai := goappimage.NewAppImage(wd + "/testing/" + appImageName)
rdr, err := NewSquashfsReader(io.NewSectionReader(aiFil, ai.Offset, stat.Size()-ai.Offset))
if err != nil {
t.Fatal(err)
}
os.RemoveAll(wd + "/testing/firefox")
err = rdr.ExtractTo(wd + "/testing/firefox")
t.Fatal(err)
}
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)
}
err = rdr.ExtractTo(wd + "/testing/firefox")
if err != nil {
b.Fatal(err)
}
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")
b.Error("STOP ALREADY!")
}
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(appImageURL)
if err != nil {
return err
}
defer resp.Body.Close()
_, err = io.Copy(appImage, resp.Body)
if err != nil {
return err
}
return nil
}
func downloadTestSquash(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)
sfs, err := os.Create(dir + "/" + squashfsName)
if err != nil {
return err
}
defer sfs.Close()
check := http.Client{
CheckRedirect: func(r *http.Request, _ []*http.Request) error {
r.URL.Opaque = r.URL.Path
return nil
},
}
resp, err := check.Get(squashfsURL)
if err != nil {
return err
}
defer resp.Body.Close()
_, err = io.Copy(sfs, 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)
}
}
-142
View File
@@ -1,142 +0,0 @@
package squashfs
import (
"fmt"
"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 = "balenaEtcher-1.5.113-x64.AppImage.sfs" //testing with a ArchLinux root fs from the live img
)
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) {
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)
}
errs := rdr.ExtractTo(wd + "/testing/cool-retro")
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. 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, _ []*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)
}
}
+84 -25
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.
@@ -32,36 +33,94 @@ type superblock struct {
ExportTableStart uint64 ExportTableStart uint64
} }
//SuperblockFlags is the parsed version of Superblock.Flags //SuperblockFlags is the series of flags describing how a squashfs archive is packed.
type superblockFlags struct { type SuperblockFlags struct {
UncompressedInodes bool //If true, inodes are stored uncompressed.
UncompressedData bool UncompressedInodes bool
Check bool //If true, data is stored uncompressed.
UncompressedData bool
check bool
//If true, fragments are stored uncompressed.
UncompressedFragments bool UncompressedFragments bool
NoFragments bool //If true, ALL data is stored in sequential data blocks instead of utilizing fragments.
AlwaysFragments bool NoFragments bool
Duplicates bool //If true, the last block of data will always be stored as a fragment if it's less then the block size.
Exportable bool AlwaysFragment bool
UncompressedXattr bool //If true, duplicate files are only stored once. (Currently unsupported)
NoXattr bool RemoveDuplicates bool
CompressorOptions bool //If true, the export table is populated. (Currently unsupported)
UncompressedIDs bool Exportable bool
//If true, the xattr table is uncompressed. (Currently unsupported)
UncompressedXattr bool
//If true, the xattr table is not populated. (Currently unsupported)
NoXattr bool
compressorOptions bool
//If true, the UID/GID table is stored uncompressed.
UncompressedIDs bool
}
//DefaultFlags are the default SuperblockFlags that are used.
var DefaultFlags = SuperblockFlags{
RemoveDuplicates: true,
Exportable: true,
} }
//GetFlags returns a SuperblockFlags for a given superblock. //GetFlags returns a SuperblockFlags for a given superblock.
func (s *superblock) GetFlags() superblockFlags { func (s *superblock) GetFlags() SuperblockFlags {
return superblockFlags{ return SuperblockFlags{
UncompressedInodes: s.Flags&0x1 == 0x1, UncompressedInodes: s.Flags&0x1 == 0x1,
UncompressedData: s.Flags&0x2 == 0x2, UncompressedData: s.Flags&0x2 == 0x2,
Check: s.Flags&0x4 == 0x4, check: s.Flags&0x4 == 0x4,
UncompressedFragments: s.Flags&0x8 == 0x8, UncompressedFragments: s.Flags&0x8 == 0x8,
NoFragments: s.Flags&0x10 == 0x10, NoFragments: s.Flags&0x10 == 0x10,
AlwaysFragments: s.Flags&0x20 == 0x20, AlwaysFragment: s.Flags&0x20 == 0x20,
Duplicates: s.Flags&0x40 == 0x40, RemoveDuplicates: s.Flags&0x40 == 0x40,
Exportable: s.Flags&0x80 == 0x80, Exportable: s.Flags&0x80 == 0x80,
UncompressedXattr: s.Flags&0x100 == 0x100, UncompressedXattr: s.Flags&0x100 == 0x100,
NoXattr: s.Flags&0x200 == 0x200, NoXattr: s.Flags&0x200 == 0x200,
CompressorOptions: s.Flags&0x400 == 0x400, compressorOptions: s.Flags&0x400 == 0x400,
UncompressedIDs: s.Flags&0x800 == 0x800, UncompressedIDs: s.Flags&0x800 == 0x800,
} }
} }
//ToUint returns the uint16 representation of the given SuperblockFlags
func (s *SuperblockFlags) ToUint() uint16 {
var out uint16
if s.UncompressedInodes {
out = out | 0x1
}
if s.UncompressedData {
out = out | 0x2
}
if s.check {
out = out | 0x4
}
if s.UncompressedFragments {
out = out | 0x8
}
if s.NoFragments {
out = out | 0x10
}
if s.AlwaysFragment {
out = out | 0x20
}
if s.RemoveDuplicates {
out = out | 0x40
}
if s.Exportable {
out = out | 0x80
}
if s.UncompressedXattr {
out = out | 0x100
}
if s.NoXattr {
out = out | 0x200
}
if s.compressorOptions {
out = out | 0x400
}
if s.UncompressedIDs {
out = out | 0x800
}
return out
}