Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a2ced9072 | |||
| a908d69987 | |||
| 6ada4f3b49 | |||
| 89f28cec6e | |||
| c988309edc | |||
| e8a8c531a9 | |||
| 80ff4466ae | |||
| 64055a8a63 | |||
| 0402b0a2ee | |||
| 305f261d10 | |||
| 70e3d81427 | |||
| 6ad6857d8d | |||
| 7cf15d48c7 | |||
| 28f39cf315 | |||
| 7f5fa3ba1f | |||
| 2a8310a724 | |||
| 8ad4d30bc7 | |||
| b6fbd63ba4 | |||
| 9913b848c6 | |||
| 65bc4a5d78 | |||
| c9d451e24c | |||
| ae5ade0683 | |||
| a7b6801d2b | |||
| a123935250 | |||
| 8a91e1a1c1 | |||
| 07962426b2 | |||
| d89153c3e2 | |||
| 3f1b2a8d1e | |||
| 3691e1f486 | |||
| 8ab566521d | |||
| dd08d3516d | |||
| 8dba30e24f | |||
| d4e2577075 | |||
| 23371163c0 | |||
| 69f56d6951 | |||
| 17e1d65488 | |||
| 80946f58e7 | |||
| 4187598783 | |||
| 9cf92c4916 | |||
| 407d649b3d | |||
| dcf59a4261 | |||
| 1506ca0ac3 | |||
| fe9344b633 | |||
| 76649fde7f | |||
| ee9406513c | |||
| 18092c63aa | |||
| b2d6ff56f6 | |||
| 4b3d5d12f8 | |||
| 3f71404a2a | |||
| 35d22b4bd0 | |||
| 97b12090c6 | |||
| 43fe4f91a2 | |||
| 9524a2c192 | |||
| 162b228881 | |||
| 7f87999a8f | |||
| 47c28baf87 | |||
| a298a3d7b5 | |||
| 89ec7eb0fb | |||
| d63ba47818 | |||
| 495d2345a4 | |||
| 6dfcb1cf80 | |||
| c7593eaff3 | |||
| 135403032f | |||
| 5c3bf8d528 | |||
| 1da97137a5 | |||
| e5d4d0902f | |||
| c0f3695cca | |||
| 89b0a41ab9 | |||
| a894e2efb9 | |||
| fef7bec20d | |||
| 28bb0f873a | |||
| e302d98665 | |||
| 1254df2861 | |||
| c5c6291643 | |||
| 508a33b323 | |||
| c5f1962e72 | |||
| faf9153323 | |||
| e20213c3f7 | |||
| edd63a422b | |||
| 938fd30d73 | |||
| cea69188d4 |
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: CalebQ42
|
||||
@@ -1,44 +1,28 @@
|
||||
# GoSquashfs
|
||||
# squashfs (WIP)
|
||||
|
||||
[](https://pkg.go.dev/github.com/CalebQ42/GoSquashfs)
|
||||
[](https://pkg.go.dev/github.com/CalebQ42/squashfs) [](https://goreportcard.com/report/github.com/CalebQ42/squashfs)
|
||||
|
||||
A PURE Go library to read and write squashfs.
|
||||
|
||||
Currently, you can read a squashfs and extract files (only files at the moment). Many things are public that shouldn't be, but you can use it by using NewSquashfsReader and subsequent ReadFile.
|
||||
Currently has support for reading squashfs files and extracting files and folders.
|
||||
|
||||
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).
|
||||
|
||||
# Working
|
||||
## [TODO](https://github.com/CalebQ42/squashfs/projects/1?fullscreen=true)
|
||||
|
||||
* Extracting files from string paths
|
||||
* Reading the header
|
||||
* Reading metadata blocks (whether encrypted or not)
|
||||
* Reading inodes
|
||||
* Reading directories
|
||||
* Basic gzip compression (Shouldn't be too hard to implement other, but for right now, this works)
|
||||
* Listing all files via a string slice
|
||||
## Limitations
|
||||
|
||||
# Not Working (Yet). Not necessarily in order.
|
||||
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.
|
||||
|
||||
* Provide an easy interface to find and list files and their properties
|
||||
* Maybe squashfs.File
|
||||
* Make device, socket, symlink, and all extended types of inode work properly. (I need to find an archive that uses it first.)
|
||||
* Extracting files
|
||||
* from inodes.
|
||||
* from file info.
|
||||
* Give a list of files
|
||||
* In io.FileStat (?) form
|
||||
* Reading the UID, GUID, Xatt, Compression Options, and Export tables.
|
||||
* Implement other compression types (Should be relatively easy)
|
||||
* Squashing
|
||||
* Threading processes to speed them up
|
||||
* Reasonable tests
|
||||
* 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.
|
||||
|
||||
# TODO
|
||||
## Performane
|
||||
|
||||
* Go over all documentation again (especially for exported structs and functions) to make sure it's easy to understand.
|
||||
|
||||
# Where I'm at
|
||||
|
||||
* Working on the File interface that should make it easier to deal with squashfs files. I'm also trying to make them capable for when I get squashing working.
|
||||
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`)
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
package squashfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"io"
|
||||
)
|
||||
|
||||
//Decompressor is a squashfs decompressor interface. Allows for easy decompression no matter the type of compression.
|
||||
type decompressor interface {
|
||||
Decompress(io.Reader) ([]byte, error)
|
||||
}
|
||||
|
||||
type compressor interface {
|
||||
Compress(io.Reader) ([]byte, error)
|
||||
}
|
||||
|
||||
//ZlibDecompressor is a decompressor for gzip type compression
|
||||
type zlibDecompressor struct{}
|
||||
|
||||
//Decompress reads the entirety of the given reader and returns it uncompressed as a byte slice.
|
||||
func (z *zlibDecompressor) Decompress(r io.Reader) ([]byte, error) {
|
||||
rdr, err := zlib.NewReader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data bytes.Buffer
|
||||
_, err = io.Copy(&data, rdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data.Bytes(), nil
|
||||
}
|
||||
-187
@@ -1,187 +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 {
|
||||
d.readCurBlock()
|
||||
}
|
||||
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++ {
|
||||
d.curReadOffset++
|
||||
if d.curReadOffset < len(d.curData) {
|
||||
p[read] = d.curData[d.curReadOffset]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if read != len(p) {
|
||||
return read, errors.New("Didn't read enough data")
|
||||
}
|
||||
return read, nil
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
package squashfs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"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")
|
||||
)
|
||||
|
||||
//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.
|
||||
type File struct {
|
||||
Name string //The name of the file or folder. Root folder will not have a name ("")
|
||||
Parent *File //The parent directory. 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.
|
||||
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
|
||||
}
|
||||
|
||||
//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 + "/" + f.Name
|
||||
}
|
||||
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
|
||||
}
|
||||
chil, err := f.GetChildren()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var childFolders []*File
|
||||
for _, child := range chil {
|
||||
children = append(children, child)
|
||||
if child.IsDir() {
|
||||
childFolders = append(childFolders, child)
|
||||
}
|
||||
}
|
||||
for _, folds := range childFolders {
|
||||
var childs []*File
|
||||
childs, err = folds.GetChildrenRecursively()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
children = append(children, childs...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//IsDir returns if the file is a directory.
|
||||
func (f *File) IsDir() bool {
|
||||
return f.filType == inode.BasicDirectoryType || f.filType == inode.ExtDirType
|
||||
}
|
||||
|
||||
//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 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)
|
||||
}
|
||||
+18
-18
@@ -10,43 +10,43 @@ import (
|
||||
|
||||
//FragmentEntry is an entry in the fragment table
|
||||
type fragmentEntry struct {
|
||||
Start uint64
|
||||
Size uint32
|
||||
Unused uint32
|
||||
Start uint64
|
||||
Size uint32
|
||||
_ uint32 //unused
|
||||
}
|
||||
|
||||
//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.
|
||||
func (r *Reader) getFragmentDataFromInode(in *inode.Inode) ([]byte, error) {
|
||||
var size uint32
|
||||
var size uint64
|
||||
var fragIndex uint32
|
||||
var fragOffset uint32
|
||||
if in.Type == inode.BasicFileType {
|
||||
bf := in.Info.(inode.BasicFile)
|
||||
if in.Type == inode.FileType {
|
||||
bf := in.Info.(inode.File)
|
||||
if !bf.Fragmented {
|
||||
return make([]byte, 0), nil
|
||||
}
|
||||
if bf.Init.BlockStart == 0 {
|
||||
size = bf.Init.Size
|
||||
if bf.BlockStart == 0 {
|
||||
size = uint64(bf.Size)
|
||||
} else {
|
||||
size = bf.BlockSizes[len(bf.BlockSizes)-1]
|
||||
size = uint64(bf.BlockSizes[len(bf.BlockSizes)-1])
|
||||
}
|
||||
fragIndex = bf.Init.FragmentIndex
|
||||
fragOffset = bf.Init.FragmentOffset
|
||||
fragIndex = bf.FragmentIndex
|
||||
fragOffset = bf.FragmentOffset
|
||||
} else if in.Type == inode.ExtFileType {
|
||||
bf := in.Info.(inode.ExtendedFile)
|
||||
bf := in.Info.(inode.ExtFile)
|
||||
if !bf.Fragmented {
|
||||
return make([]byte, 0), nil
|
||||
}
|
||||
if bf.Init.BlockStart == 0 {
|
||||
size = bf.Init.Size
|
||||
if bf.BlockStart == 0 {
|
||||
size = bf.Size
|
||||
} else {
|
||||
size = bf.BlockSizes[len(bf.BlockSizes)-1]
|
||||
size = uint64(bf.BlockSizes[len(bf.BlockSizes)-1])
|
||||
}
|
||||
fragIndex = bf.Init.FragmentIndex
|
||||
fragOffset = bf.Init.FragmentOffset
|
||||
fragIndex = bf.FragmentIndex
|
||||
fragOffset = bf.FragmentOffset
|
||||
} else {
|
||||
return nil, errors.New("Inode type not supported")
|
||||
return nil, errors.New("inode type not supported")
|
||||
}
|
||||
//reading the fragment entry first
|
||||
fragEntryRdr, err := r.newMetadataReader(int64(r.fragOffsets[int(fragIndex/512)]))
|
||||
|
||||
@@ -1,7 +1,23 @@
|
||||
module github.com/CalebQ42/squashfs
|
||||
|
||||
go 1.15
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/CalebQ42/GoAppImage v0.4.0
|
||||
github.com/CalebQ42/GoAppImage v0.5.0
|
||||
github.com/adrg/xdg v0.3.3 // indirect
|
||||
github.com/gopherjs/gopherjs v0.0.0-20210420193930-a4630ec28c79 // indirect
|
||||
github.com/klauspost/compress v1.12.2
|
||||
github.com/pierrec/lz4/v4 v4.1.6
|
||||
github.com/smartystreets/assertions v1.2.0 // indirect
|
||||
github.com/ulikunitz/xz v0.5.10
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // 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
|
||||
)
|
||||
|
||||
@@ -1,54 +1,348 @@
|
||||
github.com/CalebQ42/GoAppImage v0.4.0 h1:aF+Y/vyo/RGhoyZEW1CMY6WyRWrZZO4ydsRFAtIGnaY=
|
||||
github.com/CalebQ42/GoAppImage v0.4.0/go.mod h1:qHudJKAn/dlkNWNnH4h1YKXp29EZ7Bppsn7sNP2HuvU=
|
||||
github.com/CalebQ42/GoSquashfs v0.1.0 h1:1E6oeZLxGwjFgB0M5BcDD/IpKOQq1aO0gGsN0llCFoE=
|
||||
github.com/CalebQ42/GoSquashfs v0.1.0/go.mod h1:NzAR1YC1SVKOKhRao5IiWY3GhOMI+IxBy1xeZJeVKlQ=
|
||||
github.com/adrg/xdg v0.2.2 h1:A7ZHKRz5KGOLJX/bg7IPzStryhvCzAE1wX+KWawPiAo=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
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.3.3 h1:s/tV7MdqQnzB1nKY8aqHvAMD+uCiuEDzVB5HLRY849U=
|
||||
github.com/adrg/xdg v0.3.3/go.mod h1:61xAR2VZcggl2St4O9ohF5qCKe08+JDmE4VNzPFQvOQ=
|
||||
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
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-20210420193930-a4630ec28c79 h1:ATVz3rDvK4xX0nHx57zYSHRVIK/+lFwln9KJr8wvuk0=
|
||||
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/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
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/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
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/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
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/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 v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
|
||||
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/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/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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
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/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/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/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/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/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/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-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
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 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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
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/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-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
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=
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package compression
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/klauspost/compress/zlib"
|
||||
)
|
||||
|
||||
type gzipInit struct {
|
||||
CompressionLevel int32
|
||||
WindowSize int16
|
||||
Strategies int16
|
||||
}
|
||||
|
||||
//Gzip is a decompressor for gzip type compression. Uses zlib for compression and decompression
|
||||
type Gzip struct {
|
||||
wrt *zlib.Writer
|
||||
gzipInit
|
||||
HasCustomWindow bool
|
||||
HasStrategies bool
|
||||
}
|
||||
|
||||
//NewGzipCompressorWithOptions creates a new gzip compressor/decompressor with options read from the given reader.
|
||||
func NewGzipCompressorWithOptions(r io.Reader) (*Gzip, error) {
|
||||
var gzip Gzip
|
||||
err := binary.Read(r, binary.LittleEndian, &gzip.gzipInit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//TODO: proper support for window size and strategies
|
||||
gzip.HasCustomWindow = gzip.WindowSize != 15
|
||||
gzip.HasStrategies = gzip.Strategies != 0 && gzip.Strategies != 1
|
||||
return &gzip, nil
|
||||
}
|
||||
|
||||
//Decompress reads the entirety of the given reader and returns it uncompressed as a byte slice.
|
||||
func (g *Gzip) Decompress(r io.Reader) ([]byte, error) {
|
||||
rdr, err := zlib.NewReader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data bytes.Buffer
|
||||
_, err = io.Copy(&data, rdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data.Bytes(), nil
|
||||
}
|
||||
|
||||
//Compress compresses the given data (as a byte array) and returns the compressed data.
|
||||
func (g *Gzip) Compress(data []byte) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
var err error
|
||||
if g.wrt == nil {
|
||||
if g.CompressionLevel == 0 {
|
||||
g.wrt = zlib.NewWriter(&buf)
|
||||
} else {
|
||||
g.wrt, err = zlib.NewWriterLevel(&buf, int(g.CompressionLevel))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
wrt, err := zlib.NewWriterLevel(&buf, int(g.CompressionLevel))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = wrt.Write(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wrt.Close()
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package compression
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/pierrec/lz4/v4"
|
||||
)
|
||||
|
||||
//Lz4 is a Lz4 Compressor/Decompressor
|
||||
type Lz4 struct {
|
||||
HC bool
|
||||
}
|
||||
|
||||
//NewLz4CompressorWithOptions creates a new lz4 compressor/decompressor with options read from the given reader.
|
||||
func NewLz4CompressorWithOptions(r io.Reader) (*Lz4, error) {
|
||||
var lz4 Lz4
|
||||
var init struct {
|
||||
Version int32
|
||||
Flags int32
|
||||
}
|
||||
err := binary.Read(r, binary.LittleEndian, &init)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lz4.HC = init.Flags == 1
|
||||
return &lz4, nil
|
||||
}
|
||||
|
||||
//Decompress decompresses all data from r and returns the uncompressed bytes
|
||||
func (l *Lz4) Decompress(r io.Reader) ([]byte, error) {
|
||||
rdr := lz4.NewReader(r)
|
||||
var buf bytes.Buffer
|
||||
_, err := io.Copy(&buf, rdr)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
//Compress implements compression.Compress
|
||||
func (l *Lz4) Compress(data []byte) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
w := lz4.NewWriter(&buf)
|
||||
if l.HC {
|
||||
err := w.Apply(lz4.CompressionLevelOption(lz4.Level9))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
_, err := w.Write(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w.Close()
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package compression
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"github.com/ulikunitz/xz/lzma"
|
||||
)
|
||||
|
||||
//Lzma is a lzma decompressor
|
||||
type Lzma struct{}
|
||||
|
||||
//Decompress decompresses all the data in the given reader and returns the uncompressed bytes.
|
||||
func (l *Lzma) Decompress(rdr io.Reader) ([]byte, error) {
|
||||
r, err := lzma.NewReader(rdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
_, err = io.Copy(&buf, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package compression
|
||||
|
||||
import "io"
|
||||
|
||||
//Compressor is a squashfs decompressor interface. Allows for easy compression.
|
||||
type Compressor interface {
|
||||
Compress([]byte) ([]byte, error)
|
||||
}
|
||||
|
||||
//Decompressor is a squashfs decompressor interface. Allows for easy decompression no matter the type of compression.
|
||||
type Decompressor interface {
|
||||
Decompress(io.Reader) ([]byte, error)
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package compression
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/therootcompany/xz"
|
||||
|
||||
wrtXz "github.com/ulikunitz/xz"
|
||||
)
|
||||
|
||||
type Xz struct {
|
||||
DictionarySize int32
|
||||
Filters int32
|
||||
}
|
||||
|
||||
//NewXzCompressorWithOptions creates a new Xz compressor/decompressor that reads the compressor options from the given reader.
|
||||
func NewXzCompressorWithOptions(rdr io.Reader) (*Xz, error) {
|
||||
var x Xz
|
||||
err := binary.Read(rdr, binary.LittleEndian, &x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &x, nil
|
||||
}
|
||||
|
||||
//Decompress decompresses all the data from the rdr and returns the uncompressed bytes.
|
||||
func (x *Xz) Decompress(rdr io.Reader) ([]byte, error) {
|
||||
r, err := xz.NewReader(rdr, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
_, err = io.Copy(&buf, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package compression
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/klauspost/compress/zstd"
|
||||
)
|
||||
|
||||
//Zstd is a zstd compressor/decompressor
|
||||
type Zstd struct {
|
||||
CompressionLevel int32
|
||||
}
|
||||
|
||||
//NewZstdCompressorWithOptions creates a new Zstd with options read from the given reader
|
||||
func NewZstdCompressorWithOptions(r io.Reader) (*Zstd, error) {
|
||||
var zstd Zstd
|
||||
err := binary.Read(r, binary.LittleEndian, &zstd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &zstd, nil
|
||||
}
|
||||
|
||||
//Decompress decompresses all data from the reader and returns the uncompressed data
|
||||
func (z *Zstd) Decompress(r io.Reader) ([]byte, error) {
|
||||
rdr, err := zstd.NewReader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rdr.Close()
|
||||
var buf bytes.Buffer
|
||||
_, err = io.Copy(&buf, rdr)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
//Compress impelements compression.Compress
|
||||
func (z *Zstd) Compress(data []byte) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
w, err := zstd.NewWriter(&buf, zstd.WithEncoderLevel(zstd.EncoderLevel(z.CompressionLevel)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = w.Write(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w.Close()
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
@@ -13,8 +13,8 @@ type Header struct {
|
||||
InodeNumber uint32
|
||||
}
|
||||
|
||||
//EntryInit is the values that can be easily decoded
|
||||
type EntryInit struct {
|
||||
//EntryRaw is the values that can be easily decoded
|
||||
type EntryRaw struct {
|
||||
Offset uint16
|
||||
InodeOffset int16
|
||||
Type uint16
|
||||
@@ -23,38 +23,33 @@ type EntryInit struct {
|
||||
|
||||
//Entry is an entry in a directory.
|
||||
type Entry struct {
|
||||
Init EntryInit
|
||||
Name string
|
||||
Header *Header
|
||||
Name string
|
||||
InodeOffset uint32
|
||||
InodeBlockOffset uint16
|
||||
Type uint16
|
||||
}
|
||||
|
||||
//NewEntry creates a new directory entry
|
||||
func NewEntry(rdr io.Reader) (Entry, error) {
|
||||
var entry Entry
|
||||
err := binary.Read(rdr, binary.LittleEndian, &entry.Init)
|
||||
func NewEntry(rdr io.Reader) (*Entry, error) {
|
||||
var raw EntryRaw
|
||||
err := binary.Read(rdr, binary.LittleEndian, &raw)
|
||||
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)
|
||||
if err != nil {
|
||||
return Entry{}, err
|
||||
return nil, err
|
||||
}
|
||||
entry.Name = string(tmp)
|
||||
return entry, err
|
||||
}
|
||||
|
||||
//Directory is an entry in the directory table of a squashfs.
|
||||
//Will only have multiple headers if there are more then 256 entries
|
||||
type Directory struct {
|
||||
Headers []Header
|
||||
Entries []Entry
|
||||
return &Entry{
|
||||
InodeBlockOffset: raw.Offset,
|
||||
Type: raw.Type,
|
||||
Name: string(tmp),
|
||||
}, nil
|
||||
}
|
||||
|
||||
//NewDirectory reads the directory from rdr
|
||||
func NewDirectory(base io.Reader, size uint16) (*Directory, error) {
|
||||
var dir Directory
|
||||
var err error
|
||||
func NewDirectory(base io.Reader, size uint32) (entries []*Entry, err error) {
|
||||
tmp := make([]byte, size)
|
||||
base.Read(tmp)
|
||||
rdr := bytes.NewBuffer(tmp)
|
||||
@@ -62,6 +57,7 @@ func NewDirectory(base io.Reader, size uint16) (*Directory, error) {
|
||||
var hdr Header
|
||||
err = binary.Read(rdr, binary.LittleEndian, &hdr)
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
err = nil
|
||||
break
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
@@ -71,24 +67,21 @@ func NewDirectory(base io.Reader, size uint16) (*Directory, error) {
|
||||
if hdr.Count%256 > 0 {
|
||||
headers++
|
||||
}
|
||||
dir.Headers = append(dir.Headers, hdr)
|
||||
for i := uint32(0); i < hdr.Count; i++ {
|
||||
if i != 0 && i%256 == 0 {
|
||||
var newHdr Header
|
||||
err = binary.Read(rdr, binary.LittleEndian, &newHdr)
|
||||
err = binary.Read(rdr, binary.LittleEndian, &hdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dir.Headers = append(dir.Headers, newHdr)
|
||||
}
|
||||
var ent Entry
|
||||
var ent *Entry
|
||||
ent, err = NewEntry(rdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ent.Header = &dir.Headers[len(dir.Headers)-1]
|
||||
dir.Entries = append(dir.Entries, ent)
|
||||
ent.InodeOffset = hdr.InodeOffset
|
||||
entries = append(entries, ent)
|
||||
}
|
||||
}
|
||||
return &dir, nil
|
||||
return
|
||||
}
|
||||
|
||||
+109
-96
@@ -2,21 +2,21 @@ package inode
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
//The different types of inodes as defined by inodetype
|
||||
const (
|
||||
BasicDirectoryType = iota + 1
|
||||
BasicFileType
|
||||
BasicSymlinkType
|
||||
BasicBlockDeviceType
|
||||
BasicCharDeviceType
|
||||
BasicFifoType
|
||||
BasicSocketType
|
||||
DirType = iota + 1
|
||||
FileType
|
||||
SymType
|
||||
BlockDevType
|
||||
CharDevType
|
||||
FifoType
|
||||
SocketType
|
||||
ExtDirType
|
||||
ExtFileType
|
||||
ExtSymlinkType
|
||||
ExtSymType
|
||||
ExtBlockDeviceType
|
||||
ExtCharDeviceType
|
||||
ExtFifoType
|
||||
@@ -25,7 +25,7 @@ const (
|
||||
|
||||
//Header is the common header for all inodes
|
||||
type Header struct {
|
||||
InodeType uint16
|
||||
Type uint16
|
||||
Permissions uint16
|
||||
UID uint16
|
||||
GID uint16
|
||||
@@ -33,8 +33,8 @@ type Header struct {
|
||||
Number uint32
|
||||
}
|
||||
|
||||
//BasicDirectory is self explainatory
|
||||
type BasicDirectory struct {
|
||||
//Dir is self explainatory
|
||||
type Dir struct {
|
||||
DirectoryIndex uint32
|
||||
HardLinks uint32
|
||||
DirectorySize uint16
|
||||
@@ -42,8 +42,8 @@ type BasicDirectory struct {
|
||||
ParentInodeNumber uint32
|
||||
}
|
||||
|
||||
//ExtendedDirectoryInit is the information that can be directoy decoded
|
||||
type ExtendedDirectoryInit struct {
|
||||
//ExtDirInit is the information that can be directoy decoded
|
||||
type ExtDirInit struct {
|
||||
HardLinks uint32
|
||||
DirectorySize uint32
|
||||
DirectoryIndex uint32
|
||||
@@ -53,82 +53,84 @@ type ExtendedDirectoryInit struct {
|
||||
XattrIndex uint32
|
||||
}
|
||||
|
||||
//ExtendedDirectory is a directory with extra info
|
||||
type ExtendedDirectory struct {
|
||||
Init ExtendedDirectoryInit
|
||||
Indexes []DirectoryIndex
|
||||
//ExtDir is a directory with extra info
|
||||
type ExtDir struct {
|
||||
Indexes []DirIndex
|
||||
ExtDirInit
|
||||
}
|
||||
|
||||
//NewExtendedDirectory creates a new ExtendedDirectory
|
||||
func NewExtendedDirectory(rdr io.Reader) (ExtendedDirectory, error) {
|
||||
var inode ExtendedDirectory
|
||||
err := binary.Read(rdr, binary.LittleEndian, &inode.Init)
|
||||
func NewExtendedDirectory(rdr io.Reader) (ExtDir, error) {
|
||||
var inode ExtDir
|
||||
err := binary.Read(rdr, binary.LittleEndian, &inode.ExtDirInit)
|
||||
if err != nil {
|
||||
return inode, err
|
||||
}
|
||||
if inode.Init.IndexCount > 0 {
|
||||
inode.Indexes = make([]DirectoryIndex, inode.Init.IndexCount)
|
||||
for i := uint16(0); i < inode.Init.IndexCount; i++ {
|
||||
inode.Indexes[i], err = NewDirectoryIndex(rdr)
|
||||
if err != nil {
|
||||
fmt.Println("Error while reading Directory Index ", i)
|
||||
return inode, err
|
||||
}
|
||||
for i := uint16(0); i < inode.IndexCount; i++ {
|
||||
var tmp DirIndex
|
||||
tmp, err = NewDirectoryIndex(rdr)
|
||||
if err != nil {
|
||||
return inode, err
|
||||
}
|
||||
inode.Indexes = append(inode.Indexes, tmp)
|
||||
}
|
||||
return inode, err
|
||||
}
|
||||
|
||||
//DirectoryIndexInit holds the values that can be easily decoded
|
||||
type DirectoryIndexInit struct {
|
||||
//DirIndexInit holds the values that can be easily decoded
|
||||
type DirIndexInit struct {
|
||||
Offset uint32
|
||||
DirTableOffset uint32
|
||||
NameSize uint32
|
||||
}
|
||||
|
||||
//DirectoryIndex is a quick lookup provided by an ExtendedDirectory
|
||||
type DirectoryIndex struct {
|
||||
Init DirectoryIndexInit
|
||||
Name []byte
|
||||
//DirIndex is a quick lookup provided by an ExtendedDirectory
|
||||
type DirIndex struct {
|
||||
Name string
|
||||
DirIndexInit
|
||||
}
|
||||
|
||||
//NewDirectoryIndex return a new DirectoryIndex
|
||||
func NewDirectoryIndex(rdr io.Reader) (DirectoryIndex, error) {
|
||||
var index DirectoryIndex
|
||||
err := binary.Read(rdr, binary.LittleEndian, &index.Init)
|
||||
func NewDirectoryIndex(rdr io.Reader) (DirIndex, error) {
|
||||
var index DirIndex
|
||||
err := binary.Read(rdr, binary.LittleEndian, &index.DirIndexInit)
|
||||
if err != nil {
|
||||
return index, err
|
||||
}
|
||||
index.Name = make([]byte, index.Init.NameSize, index.Init.NameSize)
|
||||
err = binary.Read(rdr, binary.LittleEndian, &index.Name)
|
||||
return index, err
|
||||
tmp := make([]byte, index.NameSize+1, index.NameSize+1)
|
||||
err = binary.Read(rdr, binary.LittleEndian, &tmp)
|
||||
if err != nil {
|
||||
return index, err
|
||||
}
|
||||
index.Name = string(tmp)
|
||||
return index, nil
|
||||
}
|
||||
|
||||
//BasicFileInit is the information that can be directoy decoded
|
||||
type BasicFileInit struct {
|
||||
//FileInit is the information that can be directly decoded
|
||||
type FileInit struct {
|
||||
BlockStart uint32
|
||||
FragmentIndex uint32
|
||||
FragmentOffset uint32
|
||||
Size uint32
|
||||
}
|
||||
|
||||
//BasicFile is self explainatory
|
||||
type BasicFile struct {
|
||||
Init BasicFileInit
|
||||
//File is self explainatory
|
||||
type File struct {
|
||||
BlockSizes []uint32
|
||||
Fragmented bool
|
||||
FileInit
|
||||
}
|
||||
|
||||
//NewBasicFile creates a new BasicFile
|
||||
func NewBasicFile(rdr io.Reader, blockSize uint32) (BasicFile, error) {
|
||||
var inode BasicFile
|
||||
err := binary.Read(rdr, binary.LittleEndian, &inode.Init)
|
||||
//NewFile creates a new File
|
||||
func NewFile(rdr io.Reader, blockSize uint32) (File, error) {
|
||||
var inode File
|
||||
err := binary.Read(rdr, binary.LittleEndian, &inode.FileInit)
|
||||
if err != nil {
|
||||
return inode, err
|
||||
}
|
||||
inode.Fragmented = inode.Init.FragmentIndex != 0xFFFFFFFF
|
||||
blocks := inode.Init.Size / blockSize
|
||||
if inode.Init.Size%blockSize > 0 {
|
||||
inode.Fragmented = inode.FragmentIndex != 0xFFFFFFFF
|
||||
blocks := inode.Size / blockSize
|
||||
if inode.Size%blockSize > 0 {
|
||||
blocks++
|
||||
}
|
||||
inode.BlockSizes = make([]uint32, blocks, blocks)
|
||||
@@ -136,10 +138,10 @@ func NewBasicFile(rdr io.Reader, blockSize uint32) (BasicFile, error) {
|
||||
return inode, err
|
||||
}
|
||||
|
||||
//ExtendedFileInit is the information that can be directly decoded
|
||||
type ExtendedFileInit struct {
|
||||
BlockStart uint32
|
||||
Size uint32
|
||||
//ExtFileInit is the information that can be directly decoded
|
||||
type ExtFileInit struct {
|
||||
BlockStart uint64
|
||||
Size uint64
|
||||
Sparse uint64
|
||||
HardLinks uint32
|
||||
FragmentIndex uint32
|
||||
@@ -147,23 +149,23 @@ type ExtendedFileInit struct {
|
||||
XattrIndex uint32
|
||||
}
|
||||
|
||||
//ExtendedFile is a file with more information
|
||||
type ExtendedFile struct {
|
||||
Init ExtendedFileInit
|
||||
//ExtFile is a file with more information
|
||||
type ExtFile struct {
|
||||
BlockSizes []uint32
|
||||
Fragmented bool
|
||||
ExtFileInit
|
||||
}
|
||||
|
||||
//NewExtendedFile creates a new ExtendedFile
|
||||
func NewExtendedFile(rdr io.Reader, blockSize uint32) (ExtendedFile, error) {
|
||||
var inode ExtendedFile
|
||||
err := binary.Read(rdr, binary.LittleEndian, &inode.Init)
|
||||
func NewExtendedFile(rdr io.Reader, blockSize uint32) (ExtFile, error) {
|
||||
var inode ExtFile
|
||||
err := binary.Read(rdr, binary.LittleEndian, &inode.ExtFileInit)
|
||||
if err != nil {
|
||||
return inode, err
|
||||
}
|
||||
inode.Fragmented = inode.Init.FragmentIndex != 0xFFFFFFFF
|
||||
blocks := inode.Init.Size / blockSize
|
||||
if inode.Init.Size%blockSize > 0 {
|
||||
inode.Fragmented = inode.FragmentIndex != 0xFFFFFFFF
|
||||
blocks := inode.Size / uint64(blockSize)
|
||||
if inode.Size%uint64(blockSize) > 0 {
|
||||
blocks++
|
||||
}
|
||||
inode.BlockSizes = make([]uint32, blocks, blocks)
|
||||
@@ -171,74 +173,85 @@ func NewExtendedFile(rdr io.Reader, blockSize uint32) (ExtendedFile, error) {
|
||||
return inode, err
|
||||
}
|
||||
|
||||
//BasicSymlinkInit is all the values that can be directly decoded
|
||||
type BasicSymlinkInit struct {
|
||||
//SymInit is all the values that can be directly decoded
|
||||
type SymInit struct {
|
||||
HardLinks uint32
|
||||
TargetPathSize uint32
|
||||
}
|
||||
|
||||
//BasicSymlink is a symlink
|
||||
type BasicSymlink struct {
|
||||
Init BasicSymlinkInit
|
||||
//Sym is a symlink
|
||||
type Sym struct {
|
||||
Path string
|
||||
targetPath []byte //len is TargetPathSize
|
||||
SymInit
|
||||
}
|
||||
|
||||
//NewBasicSymlink creates a new BasicSymlink
|
||||
func NewBasicSymlink(rdr io.Reader) (BasicSymlink, error) {
|
||||
var inode BasicSymlink
|
||||
err := binary.Read(rdr, binary.LittleEndian, &inode.Init)
|
||||
//NewSymlink creates a new Symlink
|
||||
func NewSymlink(rdr io.Reader) (Sym, error) {
|
||||
var inode Sym
|
||||
err := binary.Read(rdr, binary.LittleEndian, &inode.SymInit)
|
||||
if err != nil {
|
||||
return inode, err
|
||||
}
|
||||
inode.targetPath = make([]byte, inode.Init.TargetPathSize, inode.Init.TargetPathSize)
|
||||
inode.targetPath = make([]byte, inode.TargetPathSize, inode.TargetPathSize)
|
||||
err = binary.Read(rdr, binary.LittleEndian, &inode.targetPath)
|
||||
if err != nil {
|
||||
return inode, err
|
||||
}
|
||||
inode.Path = string(inode.targetPath)
|
||||
return inode, err
|
||||
}
|
||||
|
||||
//ExtendedSymlinkInit is all the values that can be directly decoded
|
||||
type ExtendedSymlinkInit struct {
|
||||
//ExtSymInit is all the values that can be directly decoded
|
||||
type ExtSymInit struct {
|
||||
HardLinks uint32
|
||||
TargetPathSize uint32
|
||||
}
|
||||
|
||||
//ExtendedSymlink is a symlink with extra information
|
||||
type ExtendedSymlink struct {
|
||||
Init ExtendedSymlinkInit
|
||||
TargetPath []uint8
|
||||
//ExtSym is a symlink with extra information
|
||||
type ExtSym struct {
|
||||
Path string
|
||||
targetPath []uint8
|
||||
ExtSymInit
|
||||
XattrIndex uint32
|
||||
}
|
||||
|
||||
//NewExtendedSymlink creates a new ExtendedSymlink
|
||||
func NewExtendedSymlink(rdr io.Reader) (ExtendedSymlink, error) {
|
||||
var inode ExtendedSymlink
|
||||
err := binary.Read(rdr, binary.LittleEndian, &inode.Init)
|
||||
func NewExtendedSymlink(rdr io.Reader) (ExtSym, error) {
|
||||
var inode ExtSym
|
||||
err := binary.Read(rdr, binary.LittleEndian, &inode.ExtSymInit)
|
||||
if err != nil {
|
||||
return inode, err
|
||||
}
|
||||
inode.TargetPath = make([]uint8, inode.Init.TargetPathSize, inode.Init.TargetPathSize)
|
||||
inode.targetPath = make([]uint8, inode.TargetPathSize, inode.TargetPathSize)
|
||||
err = binary.Read(rdr, binary.LittleEndian, &inode.targetPath)
|
||||
if err != nil {
|
||||
return inode, err
|
||||
}
|
||||
inode.Path = string(inode.targetPath)
|
||||
err = binary.Read(rdr, binary.LittleEndian, &inode.XattrIndex)
|
||||
return inode, err
|
||||
}
|
||||
|
||||
//BasicDevice is a device
|
||||
type BasicDevice struct {
|
||||
//Device is a device
|
||||
type Device struct {
|
||||
HardLinks uint32
|
||||
Device uint32
|
||||
}
|
||||
|
||||
//ExtendedDevice is a device with more info
|
||||
type ExtendedDevice struct {
|
||||
BasicDevice
|
||||
//ExtDevice is a device with more info
|
||||
type ExtDevice struct {
|
||||
Device
|
||||
XattrIndex uint32
|
||||
}
|
||||
|
||||
//BasicIPC is a Fifo or Socket device
|
||||
type BasicIPC struct {
|
||||
//IPC is a Fifo or Socket device
|
||||
type IPC struct {
|
||||
HardLink uint32
|
||||
}
|
||||
|
||||
//ExtendedIPC is a IPC device with extra info
|
||||
type ExtendedIPC struct {
|
||||
BasicIPC
|
||||
//ExtIPC is a IPC device with extra info
|
||||
type ExtIPC struct {
|
||||
IPC
|
||||
XattrIndex uint32
|
||||
}
|
||||
|
||||
+51
-48
@@ -2,124 +2,127 @@ package inode
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
//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{}
|
||||
type Inode struct {
|
||||
Header Header
|
||||
Type int //Type the inode type defined in the header. Here so it's easy to access
|
||||
Info interface{} //Info is the parsed specific data. It's type is defined by Type.
|
||||
Header
|
||||
Info interface{} //Info is the parsed specific data. It's type is defined by Type.
|
||||
}
|
||||
|
||||
//ProcessInode tries to read an inode from the BlockReader
|
||||
func ProcessInode(br io.Reader, blockSize uint32) (*Inode, error) {
|
||||
var head Header
|
||||
err := binary.Read(br, binary.LittleEndian, &head)
|
||||
var in Inode
|
||||
err := binary.Read(br, binary.LittleEndian, &in.Header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var info interface{}
|
||||
switch head.InodeType {
|
||||
case BasicDirectoryType:
|
||||
var inode BasicDirectory
|
||||
switch in.Type {
|
||||
case DirType:
|
||||
var inode Dir
|
||||
err = binary.Read(br, binary.LittleEndian, &inode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info = inode
|
||||
case BasicFileType:
|
||||
inode, err := NewBasicFile(br, blockSize)
|
||||
in.Info = inode
|
||||
case FileType:
|
||||
var inode File
|
||||
inode, err = NewFile(br, blockSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info = inode
|
||||
case BasicSymlinkType:
|
||||
inode, err := NewBasicSymlink(br)
|
||||
in.Info = inode
|
||||
case SymType:
|
||||
var inode Sym
|
||||
inode, err = NewSymlink(br)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info = inode
|
||||
case BasicBlockDeviceType:
|
||||
var inode BasicDevice
|
||||
in.Info = inode
|
||||
case BlockDevType:
|
||||
var inode Device
|
||||
err = binary.Read(br, binary.LittleEndian, &inode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info = inode
|
||||
case BasicCharDeviceType:
|
||||
var inode BasicDevice
|
||||
in.Info = inode
|
||||
case CharDevType:
|
||||
var inode Device
|
||||
err = binary.Read(br, binary.LittleEndian, &inode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info = inode
|
||||
case BasicFifoType:
|
||||
var inode BasicIPC
|
||||
in.Info = inode
|
||||
case FifoType:
|
||||
var inode IPC
|
||||
err = binary.Read(br, binary.LittleEndian, &inode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info = inode
|
||||
case BasicSocketType:
|
||||
var inode BasicIPC
|
||||
in.Info = inode
|
||||
case SocketType:
|
||||
var inode IPC
|
||||
err = binary.Read(br, binary.LittleEndian, &inode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info = inode
|
||||
in.Info = inode
|
||||
case ExtDirType:
|
||||
inode, err := NewExtendedDirectory(br)
|
||||
var inode ExtDir
|
||||
inode, err = NewExtendedDirectory(br)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info = inode
|
||||
in.Info = inode
|
||||
case ExtFileType:
|
||||
inode, err := NewExtendedFile(br, blockSize)
|
||||
var inode ExtFile
|
||||
inode, err = NewExtendedFile(br, blockSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info = inode
|
||||
case ExtSymlinkType:
|
||||
inode, err := NewExtendedSymlink(br)
|
||||
in.Info = inode
|
||||
case ExtSymType:
|
||||
var inode ExtSym
|
||||
inode, err = NewExtendedSymlink(br)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info = inode
|
||||
in.Info = inode
|
||||
case ExtBlockDeviceType:
|
||||
var inode ExtendedDevice
|
||||
var inode ExtDevice
|
||||
err = binary.Read(br, binary.LittleEndian, &inode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info = inode
|
||||
in.Info = inode
|
||||
case ExtCharDeviceType:
|
||||
var inode ExtendedDevice
|
||||
var inode ExtDevice
|
||||
err = binary.Read(br, binary.LittleEndian, &inode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info = inode
|
||||
in.Info = inode
|
||||
case ExtFifoType:
|
||||
var inode ExtendedIPC
|
||||
var inode ExtIPC
|
||||
err = binary.Read(br, binary.LittleEndian, &inode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info = inode
|
||||
in.Info = inode
|
||||
case ExtSocketType:
|
||||
var inode ExtendedIPC
|
||||
var inode ExtIPC
|
||||
err = binary.Read(br, binary.LittleEndian, &inode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info = inode
|
||||
in.Info = inode
|
||||
default:
|
||||
return nil, errors.New("Unsupported inode type: " + strconv.Itoa(int(in.Type)))
|
||||
}
|
||||
return &Inode{
|
||||
Type: int(head.InodeType),
|
||||
Header: head,
|
||||
Info: info,
|
||||
}, nil
|
||||
return &in, nil
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -16,9 +16,9 @@ type metadata struct {
|
||||
//MetadataReader is a block reader for metadata. It will automatically read the next block, when it reaches the end of a block.
|
||||
type metadataReader struct {
|
||||
s *Reader
|
||||
offset int64
|
||||
headers []*metadata
|
||||
data []byte
|
||||
offset int64
|
||||
readOffset int
|
||||
}
|
||||
|
||||
@@ -52,6 +52,17 @@ func (s *Reader) newMetadataReaderFromInodeRef(ref uint64) (*metadataReader, err
|
||||
return br, nil
|
||||
}
|
||||
|
||||
//ProcessInodeRef processes an inode reference and returns two values
|
||||
//
|
||||
//The first value is the inode table offset. AKA, it's where the metadata block of the inode STARTS relative to the inode table.
|
||||
//
|
||||
//The second value is the offset of the inode, INSIDE of the metadata.
|
||||
func processInodeRef(inodeRef uint64) (tableOffset uint64, metaOffset uint64) {
|
||||
tableOffset = inodeRef >> 16
|
||||
metaOffset = inodeRef &^ 0xFFFFFFFF0000
|
||||
return
|
||||
}
|
||||
|
||||
func (br *metadataReader) parseMetadata() error {
|
||||
var raw uint16
|
||||
err := binary.Read(io.NewSectionReader(br.s.r, br.offset, 2), binary.LittleEndian, &raw)
|
||||
@@ -59,7 +70,7 @@ func (br *metadataReader) parseMetadata() error {
|
||||
return err
|
||||
}
|
||||
br.offset += 2
|
||||
compressed := !(raw&0x8000 == 0x8000)
|
||||
compressed := raw&0x8000 != 0x8000
|
||||
size := raw &^ 0x8000
|
||||
br.headers = append(br.headers, &metadata{
|
||||
raw: raw,
|
||||
@@ -93,7 +104,7 @@ func (br *metadataReader) readNextDataBlock() error {
|
||||
|
||||
//Read reads bytes into the given byte slice. Returns the amount of data read.
|
||||
func (br *metadataReader) Read(p []byte) (int, error) {
|
||||
if br.readOffset+len(p) < len(br.data) {
|
||||
if br.readOffset+len(p) <= len(br.data) {
|
||||
for i := 0; i < len(p); i++ {
|
||||
p[i] = br.data[br.readOffset+i]
|
||||
}
|
||||
@@ -122,7 +133,7 @@ func (br *metadataReader) Read(p []byte) (int, error) {
|
||||
}
|
||||
br.readOffset += read
|
||||
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
|
||||
}
|
||||
@@ -170,7 +181,7 @@ func (br *metadataReader) Seek(offset int64, whence int) (int64, error) {
|
||||
br.readOffset = len(br.data) - int(offset)
|
||||
if 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
|
||||
@@ -5,189 +5,199 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/CalebQ42/squashfs/internal/compression"
|
||||
"github.com/CalebQ42/squashfs/internal/inode"
|
||||
"github.com/CalebQ42/squashfs/internal/rawreader"
|
||||
)
|
||||
|
||||
const (
|
||||
magic = 0x73717368
|
||||
magic uint32 = 0x73717368
|
||||
)
|
||||
|
||||
var (
|
||||
//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 = 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")
|
||||
//ErrFragmentTableIssues is returned if there's trouble reading the fragment table when creating a reader.
|
||||
//When this is returned, the reader is still returned.
|
||||
errFragmentTableIssues = errors.New("Trouble while reading the fragment table")
|
||||
errIncompatibleCompression = errors.New("compression type unsupported")
|
||||
)
|
||||
|
||||
//Reader processes and reads a squashfs archive.
|
||||
//TODO: Give a way to actually read files :P
|
||||
type Reader struct {
|
||||
r io.ReaderAt
|
||||
super superblock
|
||||
flags superblockFlags
|
||||
decompressor decompressor
|
||||
FS
|
||||
r rawreader.RawReader
|
||||
decompressor compression.Decompressor
|
||||
fragOffsets []uint64
|
||||
idTable []uint32
|
||||
super superblock
|
||||
flags SuperblockFlags
|
||||
}
|
||||
|
||||
//NewSquashfsReader returns a new squashfs.Reader from an io.ReaderAt
|
||||
func NewSquashfsReader(r io.ReaderAt) (*Reader, error) {
|
||||
var rdr Reader
|
||||
rdr.r = r
|
||||
err := binary.Read(io.NewSectionReader(rdr.r, 0, int64(binary.Size(rdr.super))), binary.LittleEndian, &rdr.super)
|
||||
rdr.r = rawreader.ConvertReaderAt(r)
|
||||
err := rdr.Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rdr.super.Magic != magic {
|
||||
return nil, ErrNoMagic
|
||||
}
|
||||
rdr.flags = rdr.super.GetFlags()
|
||||
switch rdr.super.CompressionType {
|
||||
case gzipCompression:
|
||||
rdr.decompressor = &zlibDecompressor{}
|
||||
default:
|
||||
return nil, ErrIncompatibleCompression
|
||||
}
|
||||
if rdr.flags.CompressorOptions {
|
||||
//TODO: parse compressor options
|
||||
return nil, ErrCompressorOptions
|
||||
}
|
||||
fragBlocks := int(math.Ceil(float64(rdr.super.FragCount) / 512.0))
|
||||
if fragBlocks > 0 {
|
||||
offset := int64(rdr.super.FragTableStart)
|
||||
for i := 0; i < fragBlocks; i++ {
|
||||
tmp := make([]byte, 8)
|
||||
_, err = r.ReadAt(tmp, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rdr.fragOffsets = append(rdr.fragOffsets, binary.LittleEndian.Uint64(tmp))
|
||||
offset += 8
|
||||
}
|
||||
}
|
||||
return &rdr, nil
|
||||
}
|
||||
|
||||
//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)
|
||||
//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
|
||||
}
|
||||
root.in, err = inode.ProcessInode(mr, r.super.BlockSize)
|
||||
err = rdr.Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
root.Path = "/"
|
||||
root.filType = root.in.Type
|
||||
root.r = r
|
||||
return root, nil
|
||||
return &rdr, 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()
|
||||
func (r *Reader) Init() error {
|
||||
err := binary.Read(r.r, binary.LittleEndian, &r.super)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return 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
|
||||
if r.super.Magic != magic {
|
||||
return errNoMagic
|
||||
}
|
||||
fils, err := root.GetChildren()
|
||||
if err != nil {
|
||||
return nil
|
||||
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")
|
||||
}
|
||||
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()
|
||||
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 {
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
for _, child := range chil {
|
||||
if query(child) {
|
||||
return child
|
||||
}
|
||||
if child.IsDir() {
|
||||
tmp = append(tmp, child)
|
||||
}
|
||||
r.decompressor = gzip
|
||||
case XzCompression:
|
||||
var xz *compression.Xz
|
||||
xz, err = compression.NewXzCompressorWithOptions(r.r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.decompressor = xz
|
||||
case LzoCompression:
|
||||
var lz *compression.Lzo
|
||||
lz, err = compression.NewLzoCompressorWithOptions(r.r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.decompressor = lz
|
||||
case Lz4Compression:
|
||||
var lz4 *compression.Lz4
|
||||
lz4, err = compression.NewLz4CompressorWithOptions(r.r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.decompressor = lz4
|
||||
case ZstdCompression:
|
||||
var zstd *compression.Zstd
|
||||
zstd, err = compression.NewZstdCompressorWithOptions(r.r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.decompressor = zstd
|
||||
default:
|
||||
return errIncompatibleCompression
|
||||
}
|
||||
childrenDirs = tmp
|
||||
} else {
|
||||
switch r.super.CompressionType {
|
||||
case GzipCompression:
|
||||
r.decompressor = &compression.Gzip{}
|
||||
case LzmaCompression:
|
||||
r.decompressor = &compression.Lzma{}
|
||||
case LzoCompression:
|
||||
r.decompressor = &compression.Lzo{}
|
||||
case XzCompression:
|
||||
r.decompressor = &compression.Xz{}
|
||||
case Lz4Compression:
|
||||
r.decompressor = &compression.Lz4{}
|
||||
case ZstdCompression:
|
||||
r.decompressor = &compression.Zstd{}
|
||||
default:
|
||||
//TODO: all compression types.
|
||||
return errIncompatibleCompression
|
||||
}
|
||||
}
|
||||
fragBlocks := int(math.Ceil(float64(r.super.FragCount) / 512))
|
||||
if fragBlocks > 0 {
|
||||
offset := int64(r.super.FragTableStart)
|
||||
for i := 0; i < fragBlocks; i++ {
|
||||
tmp := make([]byte, 8)
|
||||
_, err = r.r.ReadAt(tmp, offset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.fragOffsets = append(r.fragOffsets, binary.LittleEndian.Uint64(tmp))
|
||||
offset += 8
|
||||
}
|
||||
}
|
||||
unread := r.super.IDCount
|
||||
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 {
|
||||
err = binary.Read(r.r, binary.LittleEndian, &blockOffsets[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var idRdr *metadataReader
|
||||
idRdr, err = r.newMetadataReader(int64(blockOffsets[i]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
read := uint16(math.Min(float64(unread), 2048))
|
||||
for i := uint16(0); i < read; i++ {
|
||||
var tmp uint32
|
||||
err = binary.Read(idRdr, binary.LittleEndian, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.idTable = append(r.idTable, tmp)
|
||||
}
|
||||
unread -= read
|
||||
}
|
||||
metaRdr, err := r.newMetadataReaderFromInodeRef(r.super.RootInodeRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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{
|
||||
i: i,
|
||||
r: r,
|
||||
name: "/",
|
||||
entries: entries,
|
||||
}
|
||||
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 {
|
||||
path = strings.TrimSuffix(strings.TrimPrefix(path, "/"), "/")
|
||||
pathDirs := strings.Split(path, "/")
|
||||
dir, err := r.GetRootFolder()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
children, err := dir.GetChildren()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
for _, folder := range pathDirs {
|
||||
for _, child := range children {
|
||||
if child.Name == folder {
|
||||
dir = child
|
||||
if dir.IsDir() {
|
||||
children, err = dir.GetChildren()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
children = make([]*File, 0)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if dir.Path+"/"+dir.Name == "/"+path {
|
||||
return dir
|
||||
}
|
||||
return nil
|
||||
//ModTime is the last time the file was modified/created.
|
||||
func (r *Reader) ModTime() time.Time {
|
||||
return time.Unix(int64(r.super.CreationTime), 0)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+382
@@ -0,0 +1,382 @@
|
||||
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{
|
||||
i: f.i,
|
||||
r: f.r,
|
||||
parent: f.parent,
|
||||
name: f.name,
|
||||
entries: ents,
|
||||
}, 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
|
||||
}
|
||||
@@ -22,27 +22,27 @@ type fileReader struct {
|
||||
|
||||
var (
|
||||
//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.
|
||||
func (r *Reader) newFileReader(in *inode.Inode) (*fileReader, error) {
|
||||
var rdr fileReader
|
||||
rdr.in = in
|
||||
if in.Type != inode.BasicFileType && in.Type != inode.ExtFileType {
|
||||
return nil, ErrPathIsNotFile
|
||||
if in.Type != inode.FileType && in.Type != inode.ExtFileType {
|
||||
return nil, errPathIsNotFile
|
||||
}
|
||||
switch in.Type {
|
||||
case inode.BasicFileType:
|
||||
fil := in.Info.(inode.BasicFile)
|
||||
case inode.FileType:
|
||||
fil := in.Info.(inode.File)
|
||||
rdr.fragged = fil.Fragmented
|
||||
rdr.fragOnly = fil.Init.BlockStart == 0
|
||||
rdr.FileSize = int(fil.Init.Size)
|
||||
rdr.fragOnly = fil.BlockStart == 0
|
||||
rdr.FileSize = int(fil.Size)
|
||||
case inode.ExtFileType:
|
||||
fil := in.Info.(inode.ExtendedFile)
|
||||
fil := in.Info.(inode.ExtFile)
|
||||
rdr.fragged = fil.Fragmented
|
||||
rdr.fragOnly = fil.Init.BlockStart == 0
|
||||
rdr.FileSize = int(fil.Init.Size)
|
||||
rdr.fragOnly = fil.BlockStart == 0
|
||||
rdr.FileSize = int(fil.Size)
|
||||
}
|
||||
var err error
|
||||
if rdr.fragged {
|
||||
@@ -53,19 +53,13 @@ func (r *Reader) newFileReader(in *inode.Inode) (*fileReader, error) {
|
||||
}
|
||||
if !rdr.fragOnly {
|
||||
rdr.data, err = r.newDataReaderFromInode(in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &rdr, nil
|
||||
}
|
||||
|
||||
//Close runs Close on the data reader and frees the fragmentdata
|
||||
func (f *fileReader) Close() error {
|
||||
if f.data != nil {
|
||||
f.data.Close()
|
||||
}
|
||||
f.fragmentData = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fileReader) Read(p []byte) (int, error) {
|
||||
if f.fragOnly {
|
||||
n, err := bytes.NewBuffer(f.fragmentData[f.read:]).Read(p)
|
||||
@@ -81,6 +75,9 @@ func (f *fileReader) Read(p []byte) (int, error) {
|
||||
if f.fragged && err == io.EOF {
|
||||
if f.fragmentData == nil {
|
||||
f.fragmentData, err = f.r.getFragmentDataFromInode(f.in)
|
||||
if err != nil {
|
||||
return read, err
|
||||
}
|
||||
}
|
||||
n, err = bytes.NewBuffer(f.fragmentData).Read(p[read:])
|
||||
read += n
|
||||
@@ -92,3 +89,19 @@ func (f *fileReader) Read(p []byte) (int, error) {
|
||||
}
|
||||
return read, nil
|
||||
}
|
||||
|
||||
func (f *fileReader) WriteTo(w io.Writer) (int64, error) {
|
||||
if f.fragOnly {
|
||||
n, err := w.Write(f.fragmentData)
|
||||
return int64(n), err
|
||||
}
|
||||
if !f.fragged {
|
||||
return f.data.WriteTo(w)
|
||||
}
|
||||
n, err := f.data.WriteTo(w)
|
||||
if err != nil {
|
||||
return int64(n), err
|
||||
}
|
||||
nn, err := w.Write(f.fragmentData)
|
||||
return int64(nn) + n, err
|
||||
}
|
||||
+510
@@ -0,0 +1,510 @@
|
||||
package squashfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/CalebQ42/squashfs/internal/directory"
|
||||
"github.com/CalebQ42/squashfs/internal/inode"
|
||||
)
|
||||
|
||||
//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 {
|
||||
i *inode.Inode
|
||||
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:], "/"))
|
||||
}
|
||||
if split[0] == "." {
|
||||
return &File{i: f.i, r: f.r, parent: f.parent, name: f.name}, nil
|
||||
}
|
||||
for i := 0; i < len(f.entries); i++ {
|
||||
if split[0] == f.entries[i].Name {
|
||||
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:], "/"))
|
||||
}
|
||||
if split[0] == "." {
|
||||
f := &File{i: f.i, r: f.r, parent: f.parent, name: f.name}
|
||||
return f.ReadDir(-1)
|
||||
}
|
||||
for i := 0; i < len(f.entries); i++ {
|
||||
if split[0] == f.entries[i].Name {
|
||||
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(ents))
|
||||
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:], "/"))
|
||||
}
|
||||
if split[0] == "." {
|
||||
f := &File{i: f.i, r: f.r, parent: f.parent, name: f.name}
|
||||
return f.Stat()
|
||||
}
|
||||
for i := 0; i < len(f.entries); i++ {
|
||||
if split[0] == f.entries[i].Name {
|
||||
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:], "/"))
|
||||
}
|
||||
if split[0] == "." {
|
||||
return f, nil
|
||||
}
|
||||
for i := 0; i < len(f.entries); i++ {
|
||||
if split[0] == f.entries[i].Name {
|
||||
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{
|
||||
i: in,
|
||||
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
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package squashfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
goappimage "github.com/CalebQ42/GoAppImage"
|
||||
)
|
||||
|
||||
const (
|
||||
downloadURL = "https://github.com/zilti/code-oss.AppImage/releases/download/continuous/Code_OSS-x86_64.AppImage"
|
||||
appImageName = "Code_OSS.AppImage"
|
||||
)
|
||||
|
||||
func TestMain(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)
|
||||
}
|
||||
rdr.FindAll(func(fil *File) bool {
|
||||
return strings.HasSuffix(fil.Name, ".desktop")
|
||||
})
|
||||
fils, err := rdr.GetAllFiles()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, fil := range fils {
|
||||
fmt.Println(fil.Path + "/" + fil.Name)
|
||||
}
|
||||
// extractionFil := "code-oss.desktop"
|
||||
// os.Remove(wd + "/testing/" + extractionFil)
|
||||
// desk, err := os.Create(wd + "/testing/" + extractionFil)
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// ext := rdr.GetFileAtPath(extractionFil)
|
||||
// if ext == nil {
|
||||
// t.Fatal("Cannot find file")
|
||||
// }
|
||||
// defer ext.Close()
|
||||
// _, err = io.Copy(desk, ext)
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
t.Fatal("No problems here!")
|
||||
}
|
||||
|
||||
func downloadTestAppImage(t *testing.T, dir string) {
|
||||
//seems to time out on slow connections. Might fix that at some point... or not
|
||||
os.Mkdir(dir, 0777)
|
||||
appImage, err := os.Create(dir + "/" + appImageName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer appImage.Close()
|
||||
check := http.Client{
|
||||
CheckRedirect: func(r *http.Request, via []*http.Request) error {
|
||||
r.URL.Opaque = r.URL.Path
|
||||
return nil
|
||||
},
|
||||
}
|
||||
resp, err := check.Get(downloadURL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
_, err = io.Copy(appImage, resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
+84
-25
@@ -1,12 +1,13 @@
|
||||
package squashfs
|
||||
|
||||
//The types of compression supported by squashfs.
|
||||
const (
|
||||
gzipCompression = 1 + iota
|
||||
lzmaCompression
|
||||
lzoCompression
|
||||
xzCompression
|
||||
lz4Compression
|
||||
zstdCompression
|
||||
GzipCompression = 1 + iota
|
||||
LzmaCompression
|
||||
LzoCompression
|
||||
XzCompression
|
||||
Lz4Compression
|
||||
ZstdCompression
|
||||
)
|
||||
|
||||
//Superblock contains important information about a squashfs file. Located at the very front of the archive.
|
||||
@@ -32,36 +33,94 @@ type superblock struct {
|
||||
ExportTableStart uint64
|
||||
}
|
||||
|
||||
//SuperblockFlags is the parsed version of Superblock.Flags
|
||||
type superblockFlags struct {
|
||||
UncompressedInodes bool
|
||||
UncompressedData bool
|
||||
Check bool
|
||||
//SuperblockFlags is the series of flags describing how a squashfs archive is packed.
|
||||
type SuperblockFlags struct {
|
||||
//If true, inodes are stored uncompressed.
|
||||
UncompressedInodes bool
|
||||
//If true, data is stored uncompressed.
|
||||
UncompressedData bool
|
||||
check bool
|
||||
//If true, fragments are stored uncompressed.
|
||||
UncompressedFragments bool
|
||||
NoFragments bool
|
||||
AlwaysFragments bool
|
||||
Duplicates bool
|
||||
Exportable bool
|
||||
UncompressedXattr bool
|
||||
NoXattr bool
|
||||
CompressorOptions bool
|
||||
UncompressedIDs bool
|
||||
//If true, ALL data is stored in sequential data blocks instead of utilizing fragments.
|
||||
NoFragments bool
|
||||
//If true, the last block of data will always be stored as a fragment if it's less then the block size.
|
||||
AlwaysFragment bool
|
||||
//If true, duplicate files are only stored once. (Currently unsupported)
|
||||
RemoveDuplicates bool
|
||||
//If true, the export table is populated. (Currently unsupported)
|
||||
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.
|
||||
func (s *superblock) GetFlags() superblockFlags {
|
||||
return superblockFlags{
|
||||
func (s *superblock) GetFlags() SuperblockFlags {
|
||||
return SuperblockFlags{
|
||||
UncompressedInodes: s.Flags&0x1 == 0x1,
|
||||
UncompressedData: s.Flags&0x2 == 0x2,
|
||||
Check: s.Flags&0x4 == 0x4,
|
||||
check: s.Flags&0x4 == 0x4,
|
||||
UncompressedFragments: s.Flags&0x8 == 0x8,
|
||||
NoFragments: s.Flags&0x10 == 0x10,
|
||||
AlwaysFragments: s.Flags&0x20 == 0x20,
|
||||
Duplicates: s.Flags&0x40 == 0x40,
|
||||
AlwaysFragment: s.Flags&0x20 == 0x20,
|
||||
RemoveDuplicates: s.Flags&0x40 == 0x40,
|
||||
Exportable: s.Flags&0x80 == 0x80,
|
||||
UncompressedXattr: s.Flags&0x100 == 0x100,
|
||||
NoXattr: s.Flags&0x200 == 0x200,
|
||||
CompressorOptions: s.Flags&0x400 == 0x400,
|
||||
compressorOptions: s.Flags&0x400 == 0x400,
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
package squashfs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/CalebQ42/squashfs/internal/directory"
|
||||
"github.com/CalebQ42/squashfs/internal/inode"
|
||||
)
|
||||
|
||||
var (
|
||||
//ErrNotFound means that the given path is NOT present in the archive
|
||||
ErrNotFound = errors.New("Path not found")
|
||||
)
|
||||
|
||||
//ProcessInodeRef processes an inode reference and returns two values
|
||||
//
|
||||
//The first value is the inode table offset. AKA, it's where the metadata block of the inode STARTS relative to the inode table.
|
||||
//
|
||||
//The second value is the offset of the inode, INSIDE of the metadata.
|
||||
func processInodeRef(inodeRef uint64) (tableOffset uint64, metaOffset uint64) {
|
||||
tableOffset = inodeRef >> 16
|
||||
metaOffset = inodeRef &^ 0xFFFFFFFF0000
|
||||
return
|
||||
}
|
||||
|
||||
//ReadDirFromInode returns a fully populated directory.Directory from a given inode.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 uint16
|
||||
switch i.Type {
|
||||
case inode.BasicDirectoryType:
|
||||
offset = i.Info.(inode.BasicDirectory).DirectoryIndex
|
||||
metaOffset = i.Info.(inode.BasicDirectory).DirectoryOffset
|
||||
size = i.Info.(inode.BasicDirectory).DirectorySize
|
||||
case inode.ExtDirType:
|
||||
offset = i.Info.(inode.ExtendedDirectory).Init.DirectoryIndex
|
||||
metaOffset = i.Info.(inode.ExtendedDirectory).Init.DirectoryOffset
|
||||
size = uint16(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
|
||||
}
|
||||
|
||||
//GetInodeFromPath returns the inode at the given path, relative to root.
|
||||
//The given path can start with or without "/"
|
||||
func (r *Reader) getInodeFromPath(path string) (*inode.Inode, error) {
|
||||
path = strings.TrimSuffix(strings.TrimPrefix(path, "/"), "/")
|
||||
pathDirs := strings.Split(path, "/")
|
||||
rdr, err := r.newMetadataReaderFromInodeRef(r.super.RootInodeRef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
curInodeDir, err := inode.ProcessInode(rdr, r.super.BlockSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if path == "" {
|
||||
return curInodeDir, nil
|
||||
}
|
||||
for depth := 0; depth < len(pathDirs); depth++ {
|
||||
if curInodeDir.Type != inode.BasicDirectoryType && curInodeDir.Type != inode.ExtDirType {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
dir, err := r.readDirFromInode(curInodeDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, entry := range dir.Entries {
|
||||
if entry.Name == pathDirs[depth] {
|
||||
if depth == len(pathDirs)-1 {
|
||||
in, err := r.getInodeFromEntry(&entry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return in, nil
|
||||
}
|
||||
curInodeDir, err = r.getInodeFromEntry(&entry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
Reference in New Issue
Block a user