Move changes from exp2 to main

This is largely a move to simplify a lot of the readers
Also further breaks out functions.
This commit is contained in:
Caleb Gardner
2022-05-10 01:12:13 -05:00
parent 0a2ced9072
commit 16ef5838c3
41 changed files with 1377 additions and 2293 deletions
-137
View File
@@ -1,137 +0,0 @@
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
}
+67
View File
@@ -0,0 +1,67 @@
package squashfs
import (
"io/fs"
"time"
"github.com/CalebQ42/squashfs/internal/directory"
"github.com/CalebQ42/squashfs/internal/inode"
)
type FileInfo struct {
e directory.Entry
size int64
perm uint32
modTime uint32
}
func (r Reader) newFileInfo(e directory.Entry) (FileInfo, error) {
i, err := r.inodeFromDir(e)
if err != nil {
return FileInfo{}, err
}
return newFileInfo(e, i), nil
}
func newFileInfo(e directory.Entry, i inode.Inode) FileInfo {
var size int64
if i.Type == inode.Fil {
size = int64(i.Data.(inode.File).Size)
} else if i.Type == inode.EFil {
size = int64(i.Data.(inode.EFile).Size)
}
return FileInfo{
e: e,
size: size,
perm: uint32(i.Perm),
modTime: i.ModTime,
}
}
func (f FileInfo) Name() string {
return f.e.Name
}
func (f FileInfo) Size() int64 {
return f.size
}
func (f FileInfo) Mode() fs.FileMode {
if f.IsDir() {
return fs.FileMode(f.perm | uint32(fs.ModeDir))
}
return fs.FileMode(f.perm)
}
func (f FileInfo) ModTime() time.Time {
return time.Unix(int64(f.modTime), 0)
}
func (f FileInfo) IsDir() bool {
return f.e.Type == inode.Dir
}
func (f FileInfo) Sys() any {
//TODO
return nil
}
+9 -67
View File
@@ -1,80 +1,22 @@
package squashfs
import (
"encoding/binary"
"errors"
"io"
"github.com/CalebQ42/squashfs/internal/inode"
"github.com/CalebQ42/squashfs/internal/toreader"
)
//FragmentEntry is an entry in the fragment table
type fragmentEntry struct {
type fragEntry struct {
Start uint64
Size uint32
_ uint32 //unused
_ uint32
}
//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 uint64
var fragIndex uint32
var fragOffset uint32
if in.Type == inode.FileType {
bf := in.Info.(inode.File)
if !bf.Fragmented {
return make([]byte, 0), nil
}
if bf.BlockStart == 0 {
size = uint64(bf.Size)
} else {
size = uint64(bf.BlockSizes[len(bf.BlockSizes)-1])
}
fragIndex = bf.FragmentIndex
fragOffset = bf.FragmentOffset
} else if in.Type == inode.ExtFileType {
bf := in.Info.(inode.ExtFile)
if !bf.Fragmented {
return make([]byte, 0), nil
}
if bf.BlockStart == 0 {
size = bf.Size
} else {
size = uint64(bf.BlockSizes[len(bf.BlockSizes)-1])
}
fragIndex = bf.FragmentIndex
fragOffset = bf.FragmentOffset
} else {
return nil, errors.New("inode type not supported")
func (r Reader) fragReader(index uint32) (io.Reader, error) {
realSize := r.fragEntries[index].Size &^ (1 << 24)
rdr := io.LimitReader(toreader.NewReader(r.r, int64(r.fragEntries[index].Start)), int64(realSize))
if realSize == r.fragEntries[index].Size {
return rdr, nil
}
//reading the fragment entry first
fragEntryRdr, err := r.newMetadataReader(int64(r.fragOffsets[int(fragIndex/512)]))
if err != nil {
return nil, err
}
_, err = fragEntryRdr.Seek(int64(16*fragIndex), io.SeekStart)
if err != nil {
return nil, err
}
var entry fragmentEntry
err = binary.Read(fragEntryRdr, binary.LittleEndian, &entry)
if err != nil {
return nil, err
}
//now reading the actual fragment
dr, err := r.newDataReader(int64(entry.Start), []uint32{entry.Size})
if err != nil {
return nil, err
}
_, err = dr.Read(make([]byte, fragOffset))
if err != nil {
return nil, err
}
tmp := make([]byte, size)
err = binary.Read(dr, binary.LittleEndian, &tmp)
if err != nil {
return nil, err
}
return tmp, nil
return r.d.Reader(rdr)
}
+1 -1
View File
@@ -1,6 +1,6 @@
module github.com/CalebQ42/squashfs
go 1.17
go 1.18
require (
github.com/CalebQ42/GoAppImage v0.5.0
-76
View File
@@ -1,76 +0,0 @@
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
}
-55
View File
@@ -1,55 +0,0 @@
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
}
-40
View File
@@ -1,40 +0,0 @@
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
}
-30
View File
@@ -1,30 +0,0 @@
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
}
-13
View File
@@ -1,13 +0,0 @@
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)
}
-60
View File
@@ -1,60 +0,0 @@
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
}
-51
View File
@@ -1,51 +0,0 @@
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
}
+115
View File
@@ -0,0 +1,115 @@
package data
import (
"io"
"github.com/CalebQ42/squashfs/internal/decompress"
"github.com/CalebQ42/squashfs/internal/toreader"
)
type FullReader struct {
r io.ReaderAt
d decompress.Decompressor
fragRdr io.Reader
sizes []uint32
blockSize uint32
start uint64
}
func NewFullReader(r io.ReaderAt, start uint64, d decompress.Decompressor, blockSizes []uint32, blockSize uint32) *FullReader {
return &FullReader{
r: r,
start: start,
blockSize: blockSize,
sizes: blockSizes,
d: d,
}
}
func (r *FullReader) AddFragment(rdr io.Reader) {
r.fragRdr = rdr
}
type outDat struct {
err error
data []byte
i int
}
func (r FullReader) process(index int, offset int64, out chan outDat) {
var err error
var dat []byte
size := realSize(r.sizes[index])
rdr := io.LimitReader(toreader.NewReader(r.r, offset), int64(size))
if size == r.sizes[index] {
rdr, err = r.d.Reader(rdr)
}
if err == nil {
dat, err = io.ReadAll(rdr)
}
out <- outDat{
i: index,
err: err,
data: dat,
}
if clr, ok := rdr.(io.Closer); ok {
clr.Close()
}
}
func (r FullReader) WriteTo(w io.Writer) (n int64, err error) {
out := make(chan outDat, len(r.sizes))
offset := r.start
num := len(r.sizes)
for i := 0; i < num; i++ {
if i == num-1 && r.fragRdr != nil {
go func() {
dat, e := io.ReadAll(r.fragRdr)
out <- outDat{
i: num,
err: e,
data: dat,
}
if clr, ok := r.fragRdr.(io.Closer); ok {
clr.Close()
}
}()
continue
}
go r.process(i, int64(offset), out)
offset += uint64(realSize(r.sizes[i]))
}
cache := make(map[int]outDat)
var tmpN int
for cur := 0; cur < num; {
dat := <-out
if dat.err != nil {
err = dat.err
return
}
if dat.i != cur {
cache[dat.i] = dat
continue
}
tmpN, err = w.Write(dat.data)
n += int64(tmpN)
if err != nil {
return
}
cur++
var ok bool
for {
dat, ok = cache[cur]
if !ok {
break
}
tmpN, err = w.Write(dat.data)
n += int64(tmpN)
if err != nil {
return
}
cur++
}
}
return
}
+77
View File
@@ -0,0 +1,77 @@
package data
import (
"bytes"
"io"
"github.com/CalebQ42/squashfs/internal/decompress"
)
type Reader struct {
master io.Reader
cur io.Reader
fragRdr io.Reader
d decompress.Decompressor
blockSizes []uint32
blockSize uint32
}
func NewReader(r io.Reader, d decompress.Decompressor, blockSizes []uint32, blockSize uint32) (*Reader, error) {
var out Reader
out.d = d
out.master = r
out.blockSizes = blockSizes
out.blockSize = blockSize
err := out.advance()
return &out, err
}
func (r *Reader) AddFragment(rdr io.Reader) {
r.fragRdr = rdr
}
func realSize(siz uint32) uint32 {
return siz &^ (1 << 24)
}
func (r *Reader) advance() (err error) {
if clr, ok := r.cur.(io.Closer); ok {
clr.Close()
}
if len(r.blockSizes) == 0 {
return io.EOF
}
if len(r.blockSizes) == 1 && r.fragRdr != nil {
r.cur = r.fragRdr
} else {
size := realSize(r.blockSizes[0])
if size == 0 {
r.cur = bytes.NewReader(make([]byte, r.blockSize))
} else {
r.cur = io.LimitReader(r.master, int64(size))
if size == r.blockSizes[0] {
r.cur, err = r.d.Reader(r.cur)
}
}
}
r.blockSizes = r.blockSizes[1:]
return
}
func (r *Reader) Read(p []byte) (n int, err error) {
n, err = r.cur.Read(p)
if err == io.EOF {
err = r.advance()
if err != nil {
return
}
var tmpN int
tmp := make([]byte, len(p)-n)
tmpN, err = r.Read(tmp)
for i := range tmp {
p[n+i] = tmp[i]
}
n += tmpN
}
return
}
+13
View File
@@ -0,0 +1,13 @@
package decompress
import (
"io"
"github.com/klauspost/compress/zlib"
)
type GZip struct{}
func (g GZip) Reader(src io.Reader) (io.ReadCloser, error) {
return zlib.NewReader(src)
}
+7
View File
@@ -0,0 +1,7 @@
package decompress
import "io"
type Decompressor interface {
Reader(src io.Reader) (io.ReadCloser, error)
}
+13
View File
@@ -0,0 +1,13 @@
package decompress
import (
"io"
"github.com/pierrec/lz4/v4"
)
type Lz4 struct{}
func (l Lz4) Reader(r io.Reader) (io.ReadCloser, error) {
return io.NopCloser(lz4.NewReader(r)), nil
}
+14
View File
@@ -0,0 +1,14 @@
package decompress
import (
"io"
"github.com/ulikunitz/xz/lzma"
)
type Lzma struct{}
func (l Lzma) Reader(r io.Reader) (io.ReadCloser, error) {
rdr, err := lzma.NewReader(r)
return io.NopCloser(rdr), err
}
+18
View File
@@ -0,0 +1,18 @@
package decompress
import (
"bytes"
"io"
"github.com/rasky/go-lzo"
)
type Lzo struct{}
func (l Lzo) Reader(r io.Reader) (io.ReadCloser, error) {
cache, err := lzo.Decompress1X(r, 0, 0)
if err != nil {
return nil, err
}
return io.NopCloser(bytes.NewReader(cache)), nil
}
+14
View File
@@ -0,0 +1,14 @@
package decompress
import (
"io"
"github.com/therootcompany/xz"
)
type Xz struct{}
func (x Xz) Reader(r io.Reader) (io.ReadCloser, error) {
rdr, err := xz.NewReader(r, 0)
return io.NopCloser(rdr), err
}
+14
View File
@@ -0,0 +1,14 @@
package decompress
import (
"io"
"github.com/klauspost/compress/zstd"
)
type Zstd struct{}
func (z Zstd) Reader(src io.Reader) (io.ReadCloser, error) {
r, err := zstd.NewReader(src)
return r.IOReadCloser(), err
}
+59 -68
View File
@@ -6,82 +6,73 @@ import (
"io"
)
//Header is the header for a directory in the directory table
type Header struct {
Count uint32
InodeOffset uint32
InodeNumber uint32
type header struct {
Entries uint32
InodeStart uint32
Num uint32
}
//EntryRaw is the values that can be easily decoded
type EntryRaw struct {
Offset uint16
InodeOffset int16
Type uint16
NameSize uint16
type entryInit struct {
Offset uint16
NumOffset int16
Type uint16
NameSize uint16
}
type entry struct {
entryInit
Name []byte
}
//Entry is an entry in a directory.
type Entry struct {
Name string
InodeOffset uint32
InodeBlockOffset uint16
Type uint16
Name string
BlockStart uint32
Type uint16
Offset uint16
}
//NewEntry creates a new directory entry
func NewEntry(rdr io.Reader) (*Entry, error) {
var raw EntryRaw
err := binary.Read(rdr, binary.LittleEndian, &raw)
func readEntry(r io.Reader) (e entry, err error) {
err = binary.Read(r, binary.LittleEndian, &e.entryInit)
if err != nil {
return nil, err
}
tmp := make([]byte, raw.NameSize+1)
err = binary.Read(rdr, binary.LittleEndian, &tmp)
if err != nil {
return nil, err
}
return &Entry{
InodeBlockOffset: raw.Offset,
Type: raw.Type,
Name: string(tmp),
}, nil
}
//NewDirectory reads the directory from rdr
func NewDirectory(base io.Reader, size uint32) (entries []*Entry, err error) {
tmp := make([]byte, size)
base.Read(tmp)
rdr := bytes.NewBuffer(tmp)
for {
var hdr Header
err = binary.Read(rdr, binary.LittleEndian, &hdr)
if err == io.ErrUnexpectedEOF {
err = nil
break
} else if err != nil {
return nil, err
}
hdr.Count++
headers := hdr.Count / 256
if hdr.Count%256 > 0 {
headers++
}
for i := uint32(0); i < hdr.Count; i++ {
if i != 0 && i%256 == 0 {
err = binary.Read(rdr, binary.LittleEndian, &hdr)
if err != nil {
return nil, err
}
}
var ent *Entry
ent, err = NewEntry(rdr)
if err != nil {
return nil, err
}
ent.InodeOffset = hdr.InodeOffset
entries = append(entries, ent)
}
return
}
e.Name = make([]byte, e.NameSize+1)
err = binary.Read(r, binary.LittleEndian, &e.Name)
return
}
func ReadEntries(rdr io.Reader, size uint32) (e []Entry, err error) {
dat := make([]byte, size)
rdr.Read(dat)
r := bytes.NewReader(dat)
var h header
var en entry
for {
err = binary.Read(r, binary.LittleEndian, &h)
if err == io.ErrUnexpectedEOF {
err = nil
return
} else if err != nil {
return
}
h.Entries++
for i := 0; i < int(h.Entries); i++ {
if i != 0 && i%256 == 0 {
err = binary.Read(r, binary.LittleEndian, &h)
if err != nil {
return
}
}
en, err = readEntry(r)
if err != nil {
return
}
e = append(e, Entry{
Name: string(en.Name),
BlockStart: h.InodeStart,
Type: en.Type,
Offset: en.Offset,
})
}
}
}
+65
View File
@@ -0,0 +1,65 @@
package inode
import (
"encoding/binary"
"io"
)
type Directory struct {
BlockStart uint32
LinkCount uint32
Size uint16
Offset uint16
ParentNum uint32
}
type eDirectoryInit struct {
LinkCount uint32
Size uint32
BlockStart uint32
ParentNum uint32
IndCount uint16
Offset uint16
XattrInd uint32
}
type EDirectory struct {
eDirectoryInit
Indexes []DirectoryIndex
}
type directoryIndexInit struct {
Ind uint32
Start uint32
NameSize uint32
}
type DirectoryIndex struct {
directoryIndexInit
Name []byte
}
func ReadDir(r io.Reader) (d Directory, err error) {
err = binary.Read(r, binary.LittleEndian, &d)
return
}
func ReadEDir(r io.Reader) (d EDirectory, err error) {
err = binary.Read(r, binary.LittleEndian, &d.eDirectoryInit)
if err != nil {
return
}
d.Indexes = make([]DirectoryIndex, d.IndCount)
for i := range d.Indexes {
err = binary.Read(r, binary.LittleEndian, &d.Indexes[i].directoryIndexInit)
if err != nil {
return
}
d.Indexes[i].Name = make([]byte, d.Indexes[i].NameSize+1)
err = binary.Read(r, binary.LittleEndian, &d.Indexes[i].Name)
if err != nil {
return
}
}
return
}
+54
View File
@@ -0,0 +1,54 @@
package inode
import (
"encoding/binary"
"io"
"math"
)
type fileInit struct {
BlockStart uint32
FragInd uint32
Offset uint32
Size uint32
}
type File struct {
fileInit
BlockSizes []uint32
}
type eFileInit struct {
BlockStart uint32
Size uint64
Sparse uint64
LinkCount uint32
FragInd uint32
Offset uint32
XattrInd uint32
}
type EFile struct {
eFileInit
BlockSizes []uint32
}
func ReadFile(r io.Reader, blockSize uint32) (f File, err error) {
err = binary.Read(r, binary.LittleEndian, &f.fileInit)
if err != nil {
return
}
f.BlockSizes = make([]uint32, int(math.Ceil(float64(f.Size)/float64(blockSize))))
err = binary.Read(r, binary.LittleEndian, &f.BlockSizes)
return
}
func ReadEFile(r io.Reader, blockSize uint32) (f EFile, err error) {
err = binary.Read(r, binary.LittleEndian, &f.eFileInit)
if err != nil {
return
}
f.BlockSizes = make([]uint32, int(math.Ceil(float64(f.Size)/float64(blockSize))))
err = binary.Read(r, binary.LittleEndian, &f.BlockSizes)
return
}
+78
View File
@@ -0,0 +1,78 @@
package inode
import (
"encoding/binary"
"errors"
"io"
)
const (
Dir = uint16(iota + 1)
Fil
Sym
Block
Char
Fifo
Sock
EDir
EFil
ESym
EBlock
EChar
EFifo
ESock
)
type Header struct {
Type uint16
Perm uint16
UidInd uint16
GidInd uint16
ModTime uint32
Num uint32
}
type Inode struct {
Header
Data any
}
func Read(r io.Reader, blockSize uint32) (i Inode, err error) {
err = binary.Read(r, binary.LittleEndian, &i.Header)
if err != nil {
return
}
switch i.Type {
case Dir:
i.Data, err = ReadDir(r)
case Fil:
i.Data, err = ReadFile(r, blockSize)
case Sym:
i.Data, err = ReadSym(r)
case Block:
fallthrough
case Char:
i.Data, err = ReadDevice(r)
case Fifo:
fallthrough
case Sock:
i.Data, err = ReadIPC(r)
case EDir:
i.Data, err = ReadEDir(r)
case EFil:
i.Data, err = ReadEFile(r, blockSize)
case ESym:
i.Data, err = ReadESym(r)
case EBlock:
fallthrough
case EChar:
i.Data, err = ReadEDevice(r)
case EFifo:
fallthrough
case ESock:
i.Data, err = ReadEIPC(r)
default:
return i, errors.New("invalid inode type")
}
return
}
-257
View File
@@ -1,257 +0,0 @@
package inode
import (
"encoding/binary"
"io"
)
//The different types of inodes as defined by inodetype
const (
DirType = iota + 1
FileType
SymType
BlockDevType
CharDevType
FifoType
SocketType
ExtDirType
ExtFileType
ExtSymType
ExtBlockDeviceType
ExtCharDeviceType
ExtFifoType
ExtSocketType
)
//Header is the common header for all inodes
type Header struct {
Type uint16
Permissions uint16
UID uint16
GID uint16
ModifiedTime uint32
Number uint32
}
//Dir is self explainatory
type Dir struct {
DirectoryIndex uint32
HardLinks uint32
DirectorySize uint16
DirectoryOffset uint16
ParentInodeNumber uint32
}
//ExtDirInit is the information that can be directoy decoded
type ExtDirInit struct {
HardLinks uint32
DirectorySize uint32
DirectoryIndex uint32
ParentInodeNumber uint32
IndexCount uint16 //one less then directory indexes following structure
DirectoryOffset uint16
XattrIndex uint32
}
//ExtDir is a directory with extra info
type ExtDir struct {
Indexes []DirIndex
ExtDirInit
}
//NewExtendedDirectory creates a new ExtendedDirectory
func NewExtendedDirectory(rdr io.Reader) (ExtDir, error) {
var inode ExtDir
err := binary.Read(rdr, binary.LittleEndian, &inode.ExtDirInit)
if err != nil {
return inode, err
}
for i := uint16(0); i < inode.IndexCount; i++ {
var tmp DirIndex
tmp, err = NewDirectoryIndex(rdr)
if err != nil {
return inode, err
}
inode.Indexes = append(inode.Indexes, tmp)
}
return inode, err
}
//DirIndexInit holds the values that can be easily decoded
type DirIndexInit struct {
Offset uint32
DirTableOffset uint32
NameSize uint32
}
//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) (DirIndex, error) {
var index DirIndex
err := binary.Read(rdr, binary.LittleEndian, &index.DirIndexInit)
if err != nil {
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
}
//FileInit is the information that can be directly decoded
type FileInit struct {
BlockStart uint32
FragmentIndex uint32
FragmentOffset uint32
Size uint32
}
//File is self explainatory
type File struct {
BlockSizes []uint32
Fragmented bool
FileInit
}
//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.FragmentIndex != 0xFFFFFFFF
blocks := inode.Size / blockSize
if inode.Size%blockSize > 0 {
blocks++
}
inode.BlockSizes = make([]uint32, blocks, blocks)
err = binary.Read(rdr, binary.LittleEndian, &inode.BlockSizes)
return inode, err
}
//ExtFileInit is the information that can be directly decoded
type ExtFileInit struct {
BlockStart uint64
Size uint64
Sparse uint64
HardLinks uint32
FragmentIndex uint32
FragmentOffset uint32
XattrIndex uint32
}
//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) (ExtFile, error) {
var inode ExtFile
err := binary.Read(rdr, binary.LittleEndian, &inode.ExtFileInit)
if err != nil {
return inode, err
}
inode.Fragmented = inode.FragmentIndex != 0xFFFFFFFF
blocks := inode.Size / uint64(blockSize)
if inode.Size%uint64(blockSize) > 0 {
blocks++
}
inode.BlockSizes = make([]uint32, blocks, blocks)
err = binary.Read(rdr, binary.LittleEndian, &inode.BlockSizes)
return inode, err
}
//SymInit is all the values that can be directly decoded
type SymInit struct {
HardLinks uint32
TargetPathSize uint32
}
//Sym is a symlink
type Sym struct {
Path string
targetPath []byte //len is TargetPathSize
SymInit
}
//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.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
}
//ExtSymInit is all the values that can be directly decoded
type ExtSymInit struct {
HardLinks uint32
TargetPathSize uint32
}
//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) (ExtSym, error) {
var inode ExtSym
err := binary.Read(rdr, binary.LittleEndian, &inode.ExtSymInit)
if err != nil {
return inode, err
}
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
}
//Device is a device
type Device struct {
HardLinks uint32
Device uint32
}
//ExtDevice is a device with more info
type ExtDevice struct {
Device
XattrIndex uint32
}
//IPC is a Fifo or Socket device
type IPC struct {
HardLink uint32
}
//ExtIPC is a IPC device with extra info
type ExtIPC struct {
IPC
XattrIndex uint32
}
+45
View File
@@ -0,0 +1,45 @@
package inode
import (
"encoding/binary"
"io"
)
type Device struct {
LinkCount uint32
Dev uint32
}
type EDevice struct {
Device
XattrInd uint32
}
func ReadDevice(r io.Reader) (d Device, err error) {
err = binary.Read(r, binary.LittleEndian, &d)
return
}
func ReadEDevice(r io.Reader) (d EDevice, err error) {
err = binary.Read(r, binary.LittleEndian, &d)
return
}
type IPC struct {
LinkCount uint32
}
type EIPC struct {
IPC
XattrInd uint32
}
func ReadIPC(r io.Reader) (i IPC, err error) {
err = binary.Read(r, binary.LittleEndian, &i)
return
}
func ReadEIPC(r io.Reader) (i EIPC, err error) {
err = binary.Read(r, binary.LittleEndian, &i)
return
}
-128
View File
@@ -1,128 +0,0 @@
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
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 in Inode
err := binary.Read(br, binary.LittleEndian, &in.Header)
if err != nil {
return nil, err
}
switch in.Type {
case DirType:
var inode Dir
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return nil, err
}
in.Info = inode
case FileType:
var inode File
inode, err = NewFile(br, blockSize)
if err != nil {
return nil, err
}
in.Info = inode
case SymType:
var inode Sym
inode, err = NewSymlink(br)
if err != nil {
return nil, err
}
in.Info = inode
case BlockDevType:
var inode Device
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return nil, err
}
in.Info = inode
case CharDevType:
var inode Device
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return nil, err
}
in.Info = inode
case FifoType:
var inode IPC
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return nil, err
}
in.Info = inode
case SocketType:
var inode IPC
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return nil, err
}
in.Info = inode
case ExtDirType:
var inode ExtDir
inode, err = NewExtendedDirectory(br)
if err != nil {
return nil, err
}
in.Info = inode
case ExtFileType:
var inode ExtFile
inode, err = NewExtendedFile(br, blockSize)
if err != nil {
return nil, err
}
in.Info = inode
case ExtSymType:
var inode ExtSym
inode, err = NewExtendedSymlink(br)
if err != nil {
return nil, err
}
in.Info = inode
case ExtBlockDeviceType:
var inode ExtDevice
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return nil, err
}
in.Info = inode
case ExtCharDeviceType:
var inode ExtDevice
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return nil, err
}
in.Info = inode
case ExtFifoType:
var inode ExtIPC
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return nil, err
}
in.Info = inode
case ExtSocketType:
var inode ExtIPC
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return nil, err
}
in.Info = inode
default:
return nil, errors.New("Unsupported inode type: " + strconv.Itoa(int(in.Type)))
}
return &in, nil
}
+46
View File
@@ -0,0 +1,46 @@
package inode
import (
"encoding/binary"
"io"
)
type symlinkInit struct {
LinkCount uint32
TargetSize uint32
}
type Symlink struct {
symlinkInit
Target []byte
}
type ESymlink struct {
symlinkInit
Target []byte
XattrInd uint32
}
func ReadSym(r io.Reader) (s Symlink, err error) {
err = binary.Read(r, binary.LittleEndian, &s.symlinkInit)
if err != nil {
return
}
s.Target = make([]byte, s.TargetSize)
err = binary.Read(r, binary.LittleEndian, &s.Target)
return
}
func ReadESym(r io.Reader) (s ESymlink, err error) {
err = binary.Read(r, binary.LittleEndian, &s.symlinkInit)
if err != nil {
return
}
s.Target = make([]byte, s.TargetSize)
err = binary.Read(r, binary.LittleEndian, &s.Target)
if err != nil {
return
}
err = binary.Read(r, binary.LittleEndian, &s.XattrInd)
return
}
+60
View File
@@ -0,0 +1,60 @@
package metadata
import (
"encoding/binary"
"io"
"github.com/CalebQ42/squashfs/internal/decompress"
)
type Reader struct {
master io.Reader
cur io.Reader
d decompress.Decompressor
}
func NewReader(r io.Reader, d decompress.Decompressor) (*Reader, error) {
var out Reader
out.d = d
out.master = r
return &out, out.Advance()
}
func (r *Reader) Advance() error {
if clr, ok := r.cur.(io.Closer); ok {
clr.Close()
}
var size uint16
err := binary.Read(r.master, binary.LittleEndian, &size)
if err != nil {
return err
}
comp := size&0x8000 != 0x8000
size &^= 0x8000
r.cur = io.LimitReader(r.master, int64(size))
if comp {
r.cur, err = r.d.Reader(r.cur)
if err != nil {
return err
}
}
return nil
}
func (r Reader) Read(p []byte) (n int, err error) {
n, err = r.cur.Read(p)
if err == io.EOF {
err = r.Advance()
if err != nil {
return
}
var tmpN int
tmp := make([]byte, len(p)-n)
tmpN, err = r.Read(tmp)
for i := range tmp {
p[n+i] = tmp[i]
}
n += tmpN
}
return
}
-135
View File
@@ -1,135 +0,0 @@
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
}
+25
View File
@@ -0,0 +1,25 @@
package toreader
import "io"
type Reader struct {
r io.ReaderAt
off int64
}
func NewReader(r io.ReaderAt, start int64) *Reader {
return &Reader{
r: r,
off: start,
}
}
func (r *Reader) Read(p []byte) (n int, err error) {
n, err = r.r.ReadAt(p, r.off)
r.off += int64(n)
return
}
func (r Reader) Offset() int64 {
return r.off
}
+23
View File
@@ -0,0 +1,23 @@
package toreader
import "io"
type ReaderAt struct {
d []byte
}
func NewReaderAt(r io.Reader) (ra ReaderAt, err error) {
ra.d, err = io.ReadAll(r)
return
}
func (r ReaderAt) ReadAt(p []byte, off int64) (n int, err error) {
if int(off) >= len(r.d) {
return 0, io.EOF
}
n = copy(p, r.d[off:])
if n != len(p) {
err = io.EOF
}
return
}
-188
View File
@@ -1,188 +0,0 @@
package squashfs
import (
"bytes"
"encoding/binary"
"errors"
"io"
)
type metadata struct {
raw uint16
size uint16
compressed bool
}
//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
headers []*metadata
data []byte
offset int64
readOffset int
}
//NewMetadataReader creates a new MetadataReader, beginning to read at the given offset. It will automatically cache the first block at the location.
func (s *Reader) newMetadataReader(offset int64) (*metadataReader, error) {
var br metadataReader
br.s = s
br.offset = offset
err := br.parseMetadata()
if err != nil {
return nil, err
}
err = br.readNextDataBlock()
if err != nil {
return nil, err
}
return &br, nil
}
//NewMetadataReaderFromInodeRef creates a new MetadataReader with the offsets set by the given inode reference.
func (s *Reader) newMetadataReaderFromInodeRef(ref uint64) (*metadataReader, error) {
offset, metaOffset := processInodeRef(ref)
br, err := s.newMetadataReader(int64(s.super.InodeTableStart + offset))
if err != nil {
return nil, err
}
_, err = br.Seek(int64(metaOffset), io.SeekStart)
if err != nil {
return nil, 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)
if err != nil {
return err
}
br.offset += 2
compressed := raw&0x8000 != 0x8000
size := raw &^ 0x8000
br.headers = append(br.headers, &metadata{
raw: raw,
size: size,
compressed: compressed,
})
return nil
}
func (br *metadataReader) readNextDataBlock() error {
meta := br.headers[len(br.headers)-1]
r := io.NewSectionReader(br.s.r, br.offset, int64(meta.size))
if meta.compressed {
byts, err := br.s.decompressor.Decompress(r)
if err != nil {
return err
}
br.offset += int64(meta.size)
br.data = append(br.data, byts...)
return nil
}
var buf bytes.Buffer
_, err := io.Copy(&buf, r)
if err != nil {
return err
}
br.offset += int64(meta.size)
br.data = append(br.data, buf.Bytes()...)
return nil
}
//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) {
for i := 0; i < len(p); i++ {
p[i] = br.data[br.readOffset+i]
}
br.readOffset += len(p)
return len(p), nil
}
read := 0
for read < len(p) {
err := br.parseMetadata()
if err != nil {
br.readOffset += read
return read, err
}
err = br.readNextDataBlock()
if err != nil {
br.readOffset += read
return read, err
}
for ; read < len(p); read++ {
if br.readOffset+read < len(br.data) {
p[read] = br.data[br.readOffset+read]
} else {
break
}
}
}
br.readOffset += read
if read != len(p) {
return read, errors.New("didn't read enough data")
}
return read, nil
}
//Seek will seek to the specified location (if possible). Seeking is relative to the uncompressed data.
//When io.SeekCurrent or io.SeekStart is set, if seeking would put the offset beyond the current cached data, it will try to read the next data blocks to accomodate. On a failure it will seek to the end of the data.
//When io.SeekEnd is set, it wil seek reletive to the currently cached data.
func (br *metadataReader) Seek(offset int64, whence int) (int64, error) {
switch whence {
case io.SeekCurrent:
br.readOffset += int(offset)
for {
if br.readOffset < len(br.data) {
break
}
err := br.parseMetadata()
if err != nil {
br.readOffset = len(br.data)
return int64(br.readOffset), err
}
err = br.readNextDataBlock()
if err != nil {
br.readOffset = len(br.data)
return int64(br.readOffset), err
}
}
case io.SeekStart:
br.readOffset = int(offset)
for {
if br.readOffset < len(br.data) {
break
}
err := br.parseMetadata()
if err != nil {
br.readOffset = len(br.data)
return int64(br.readOffset), err
}
err = br.readNextDataBlock()
if err != nil {
br.readOffset = len(br.data)
return int64(br.readOffset), err
}
}
case io.SeekEnd:
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), nil
}
+189 -160
View File
@@ -7,197 +7,226 @@ import (
"math"
"time"
"github.com/CalebQ42/squashfs/internal/compression"
"github.com/CalebQ42/squashfs/internal/decompress"
"github.com/CalebQ42/squashfs/internal/directory"
"github.com/CalebQ42/squashfs/internal/inode"
"github.com/CalebQ42/squashfs/internal/rawreader"
"github.com/CalebQ42/squashfs/internal/metadata"
"github.com/CalebQ42/squashfs/internal/toreader"
)
type Reader struct {
*FS
d decompress.Decompressor
r io.ReaderAt
fragEntries []fragEntry
ids []uint32
exportTable []uint64
s superblock
}
var (
ErrorMagic = errors.New("magic incorrect. probably not reading squashfs archive")
ErrorLog = errors.New("block log is incorrect. possible corrupted archive")
)
const (
magic uint32 = 0x73717368
GZipCompression = uint16(iota + 1)
LZMACompression
LZOCompression
XZCompression
LZ4Compression
ZSTDCompression
)
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")
//ErrIncompatibleCompression is returned if the compression type in the superblock doesn't work.
errIncompatibleCompression = errors.New("compression type unsupported")
)
//Reader processes and reads a squashfs archive.
type Reader struct {
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 = rawreader.ConvertReaderAt(r)
err := rdr.Init()
func NewReaderFromReader(r io.Reader) (*Reader, error) {
rdr, err := toreader.NewReaderAt(r)
if err != nil {
return nil, err
}
return &rdr, nil
return NewReader(rdr)
}
//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)
func NewReader(r io.ReaderAt) (*Reader, error) {
var squash Reader
squash.r = r
err := binary.Read(toreader.NewReader(r, 0), binary.LittleEndian, &squash.s)
if err != nil {
return nil, err
}
err = rdr.Init()
if err != nil {
return nil, err
if !squash.s.hasMagic() {
return nil, ErrorMagic
}
return &rdr, nil
}
func (r *Reader) Init() error {
err := binary.Read(r.r, binary.LittleEndian, &r.super)
if err != nil {
return err
if !squash.s.checkBlockLog() {
return nil, ErrorLog
}
if r.super.Magic != magic {
return errNoMagic
switch squash.s.CompType {
case GZipCompression:
squash.d = decompress.GZip{}
case LZMACompression:
squash.d = decompress.Lzma{}
case LZOCompression:
squash.d = decompress.Lzo{}
case XZCompression:
squash.d = decompress.Xz{}
case LZ4Compression:
squash.d = decompress.Lz4{}
case ZSTDCompression:
squash.d = decompress.Zstd{}
default:
return nil, errors.New("uh, I need to do this, OR something if very wrong")
}
if r.super.BlockLog != uint16(math.Log2(float64(r.super.BlockSize))) {
return errors.New("BlockSize and BlockLog doesn't match. The archive is probably corrupt")
}
r.r.Seek(96, io.SeekStart)
r.flags = r.super.GetFlags()
if r.flags.compressorOptions {
switch r.super.CompressionType {
case GzipCompression:
var gzip *compression.Gzip
gzip, err = compression.NewGzipCompressorWithOptions(r.r)
if err != nil {
return err
}
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
}
} 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 !squash.s.noFragments() && squash.s.FragCount > 0 {
fragOffsets := make([]uint64, int(math.Ceil(float64(squash.s.FragCount)/512)))
err = binary.Read(toreader.NewReader(r, int64(squash.s.FragTableStart)), binary.LittleEndian, &fragOffsets)
if err != nil {
return err
return nil, 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)
squash.fragEntries = make([]fragEntry, squash.s.FragCount)
if len(fragOffsets) == 1 {
var rdr *metadata.Reader
rdr, err = metadata.NewReader(toreader.NewReader(r, int64(fragOffsets[0])), squash.d)
if err != nil {
return err
return nil, err
}
err = binary.Read(rdr, binary.LittleEndian, &squash.fragEntries)
if err != nil {
return nil, err
}
} else {
toRead := squash.s.IdCount
var curRead uint16
var tmp []fragEntry
var rdr *metadata.Reader
var offset int
for i := range fragOffsets {
curRead = uint16(math.Min(512, float64(toRead)))
tmp = make([]fragEntry, curRead)
rdr, err = metadata.NewReader(toreader.NewReader(r, int64(fragOffsets[i])), squash.d)
if err != nil {
return nil, err
}
err = binary.Read(rdr, binary.LittleEndian, &tmp)
if err != nil {
return nil, err
}
offset = int(squash.s.IdCount - toRead)
for i := range tmp {
squash.fragEntries[offset+i] = tmp[i]
}
toRead -= curRead
}
r.idTable = append(r.idTable, tmp)
}
unread -= read
}
metaRdr, err := r.newMetadataReaderFromInodeRef(r.super.RootInodeRef)
if squash.s.IdCount > 0 {
idOffsets := make([]uint64, int(math.Ceil(float64(squash.s.IdCount)/2048)))
err = binary.Read(toreader.NewReader(r, int64(squash.s.IdTableStart)), binary.LittleEndian, &idOffsets)
if err != nil {
return nil, err
}
squash.ids = make([]uint32, squash.s.IdCount)
if len(idOffsets) == 1 {
var rdr *metadata.Reader
rdr, err = metadata.NewReader(toreader.NewReader(r, int64(idOffsets[0])), squash.d)
if err != nil {
return nil, err
}
err = binary.Read(rdr, binary.LittleEndian, &squash.ids)
if err != nil {
return nil, err
}
} else {
toRead := squash.s.IdCount
var curRead uint16
var tmp []uint32
var rdr *metadata.Reader
var offset int
for i := range idOffsets {
curRead = uint16(math.Min(2048, float64(toRead)))
tmp = make([]uint32, curRead)
rdr, err = metadata.NewReader(toreader.NewReader(r, int64(idOffsets[i])), squash.d)
if err != nil {
return nil, err
}
err = binary.Read(rdr, binary.LittleEndian, &tmp)
if err != nil {
return nil, err
}
offset = int(squash.s.IdCount - toRead)
for i := range tmp {
squash.ids[offset+i] = tmp[i]
}
toRead -= curRead
}
}
}
root, err := squash.inodeFromRef(squash.s.RootInodeRef)
if err != nil {
return err
return nil, err
}
i, err := inode.ProcessInode(metaRdr, r.super.BlockSize)
rootEnts, err := squash.readDirectory(root)
if err != nil {
return err
return nil, err
}
entries, err := r.readDirFromInode(i)
enType := root.Type
if enType == inode.EDir {
enType = inode.Dir
}
squash.FS = &FS{
e: rootEnts,
File: &File{
rdr: &squash,
i: root,
e: directory.Entry{
Name: "root",
Type: enType,
},
},
}
return &squash, nil
}
func (r *Reader) initExport() (err error) {
num := int(math.Ceil(float64(r.s.InodeCount) / 1024))
offsets := make([]uint64, num)
err = binary.Read(toreader.NewReader(r.r, int64(r.s.ExportTableStart)), binary.LittleEndian, &offsets)
if err != nil {
return err
return
}
r.FS = FS{
i: i,
r: r,
name: "/",
entries: entries,
left := r.s.InodeCount
var toRead uint32
var new []uint64
var rdr *metadata.Reader
for i := range offsets {
rdr, err = metadata.NewReader(toreader.NewReader(r.r, int64(offsets[i])), r.d)
if err != nil {
return
}
toRead = uint32(math.Min(1024, float64(left)))
new = make([]uint64, toRead)
err = binary.Read(rdr, binary.LittleEndian, &new)
if err != nil {
return
}
left -= toRead
r.exportTable = append(r.exportTable, new...)
}
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)
func (r *Reader) inode(index uint32) (i inode.Inode, err error) {
if r.s.exportable() {
if r.exportTable == nil {
err = r.initExport()
if err != nil {
return
}
}
return r.inodeFromRef(r.exportTable[index-1])
}
err = errors.New("archive is not exportable")
return
}
func (r Reader) ModTime() time.Time {
return time.Unix(int64(r.s.ModTime), 0)
}
-242
View File
@@ -1,242 +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
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)
}
}
}
+97 -144
View File
@@ -6,7 +6,7 @@ import (
"io/fs"
"log"
"os"
"path"
"path/filepath"
"strconv"
"strings"
@@ -16,166 +16,152 @@ import (
//File represents a file inside a squashfs archive.
type File struct {
i *inode.Inode
parent *FS
i inode.Inode
rdr io.Reader
fullRdr io.WriterTo
r *Reader
reader *fileReader
name string
parent *FS
e directory.Entry
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
}
var (
ErrReadNotFile = errors.New("read called on non-file")
)
//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)
func (r Reader) newFile(en directory.Entry) (*File, error) {
i, err := r.inodeFromDir(en)
if err != nil {
return nil, err
}
if file.IsRegular() {
file.reader, err = r.newFileReader(file.i)
var rdr io.Reader
var full io.WriterTo
if en.Type == inode.Fil {
rdr, err = r.getData(i)
if err != nil {
return nil, err
}
full, err = r.getFullReader(i)
if err != nil {
return nil, err
}
}
return
return &File{
e: en,
i: i,
rdr: rdr,
fullRdr: full,
r: &r,
}, nil
}
//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
return newFileInfo(f.e, f.i), 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)
if f.i.Type != inode.Fil && f.i.Type != inode.EFil {
return 0, ErrReadNotFile
}
return 0, errors.New("can only read files")
if f.rdr == nil {
return 0, fs.ErrClosed
}
return f.rdr.Read(p)
}
//WriteTo writes all data from the file to the writer. This is multi-threaded.
//The underlying reader is seperate from the one used with Read and can be reused.
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")
return f.fullRdr.WriteTo(w)
}
//Close simply nils the underlying reader. Here mostly to satisfy fs.File
func (f *File) Close() error {
f.reader = nil
f.rdr = 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) {
func (f *File) ReadDir(n int) (out []fs.DirEntry, err error) {
if !f.IsDir() {
return nil, errors.New("File is not a directory")
}
ffs, err := f.FS()
ents, err := f.r.readDirectory(f.i)
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)
start, end := 0, len(ents)
if n > 0 {
start, end = f.dirsRead, f.dirsRead+n
if end > len(f.r.e) {
end = len(f.r.e)
err = io.EOF
}
}
out := make([]fs.DirEntry, end-beg)
for i, ent := range ffs.entries[beg:end] {
out[i] = f.r.newDirEntry(ent, ffs)
var fi FileInfo
for _, e := range ents[start:end] {
fi, err = f.r.newFileInfo(e)
if err != nil {
f.dirsRead += len(out)
return
}
out = append(out, fs.FileInfoToDirEntry(fi))
}
return out, err
f.dirsRead += len(out)
return
}
//FS returns the File as a FS.
func (f File) FS() (*FS, error) {
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)
ents, err := f.r.readDirectory(f.i)
if err != nil {
return nil, err
}
return &FS{
i: f.i,
r: f.r,
parent: f.parent,
name: f.name,
entries: ents,
File: f,
e: 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
return f.i.Type == inode.Dir || f.i.Type == inode.EDir
}
//IsRegular yep.
func (f File) IsRegular() bool {
return f.i.Type == inode.FileType || f.i.Type == inode.ExtFileType
return f.i.Type == inode.Fil || f.i.Type == inode.EFil
}
//IsSymlink yep.
func (f File) IsSymlink() bool {
return f.i.Type == inode.SymType || f.i.Type == inode.ExtSymType
return f.i.Type == inode.Sym || f.i.Type == inode.ESym
}
//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
case inode.Sym:
return string(f.i.Data.(inode.Symlink).Target)
case inode.ESym:
return string(f.i.Data.(inode.ESymlink).Target)
}
return ""
}
func (f File) path() string {
if f.parent == nil {
return f.e.Name
}
return f.parent.path() + "/" + f.e.Name
}
//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 {
@@ -229,28 +215,28 @@ func (f File) ExtractSymlink(folder string) error {
//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
}
}
folder = filepath.Clean(folder)
stat, err := f.Stat()
if err != nil {
return err
}
if f.IsDir() {
if op.notBase {
err = os.Mkdir(folder+"/"+f.name, stat.Mode())
err = os.Mkdir(folder+"/"+f.e.Name, stat.Mode())
if err != nil && !os.IsExist(err) {
return err
}
} else {
op.notBase = true
}
var ents []fs.DirEntry
ents, err = f.ReadDir(0)
var ents []directory.Entry
ents, err = f.r.readDirectory(f.i)
if err != nil {
if op.Verbose {
log.Println("Error while reading children of", f.path())
@@ -259,16 +245,16 @@ func (f File) ExtractWithOptions(folder string, op ExtractionOptions) error {
}
errChan := make(chan error)
for i := 0; i < len(ents); i++ {
go func(ent *DirEntry) {
fil, goErr := ent.File()
go func(ent directory.Entry) {
fil, goErr := f.r.newFile(ent)
if goErr != nil {
errChan <- goErr
fil.Close()
return
}
errChan <- fil.ExtractWithOptions(folder+"/"+f.name, op)
errChan <- fil.ExtractWithOptions(folder+"/"+f.e.Name, op)
fil.Close()
}(ents[i].(*DirEntry))
}(ents[i])
}
for i := 0; i < len(ents); i++ {
err = <-errChan
@@ -279,12 +265,12 @@ func (f File) ExtractWithOptions(folder string, op ExtractionOptions) error {
return nil
} else if f.IsRegular() {
var fil *os.File
fil, err = os.Create(folder + "/" + f.name)
fil, err = os.Create(folder + "/" + f.e.Name)
if os.IsExist(err) {
os.Remove(folder + "/" + f.name)
fil, err = os.Create(folder + "/" + f.name)
os.Remove(folder + "/" + f.e.Name)
fil, err = os.Create(folder + "/" + f.e.Name)
if err != nil {
log.Println("Error while creating", folder+"/"+f.name)
log.Println("Error while creating", folder+"/"+f.e.Name)
return err
}
} else if err != nil {
@@ -292,7 +278,7 @@ func (f File) ExtractWithOptions(folder string, op ExtractionOptions) error {
}
_, err = io.Copy(fil, f)
if err != nil {
log.Println("Error while copying data to", folder+"/"+f.name)
log.Println("Error while copying data to", folder+"/"+f.e.Name)
return err
}
return nil
@@ -302,15 +288,15 @@ func (f File) ExtractWithOptions(folder string, op ExtractionOptions) error {
fil := f.GetSymlinkFile()
if fil == nil {
if op.Verbose {
log.Println("Symlink path(", symPath, ") is unobtainable:", folder+"/"+f.name)
log.Println("Symlink path(", symPath, ") is unobtainable:", folder+"/"+f.e.Name)
}
return errors.New("cannot get symlink target")
}
fil.name = f.name
fil.e.Name = f.e.Name
err = fil.ExtractWithOptions(folder, op)
if err != nil {
if op.Verbose {
log.Println("Error while extracting the symlink's file:", folder+"/"+f.name)
log.Println("Error while extracting the symlink's file:", folder+"/"+f.e.Name)
}
return err
}
@@ -319,27 +305,27 @@ func (f File) ExtractWithOptions(folder string, op ExtractionOptions) error {
fil := f.GetSymlinkFile()
if fil == nil {
if op.Verbose {
log.Println("Symlink path(", symPath, ") is unobtainable:", folder+"/"+f.name)
log.Println("Symlink path(", symPath, ") is unobtainable:", folder+"/"+f.e.Name)
}
return errors.New("cannot get symlink target")
}
extractLoc := path.Clean(folder + "/" + path.Dir(symPath))
extractLoc := filepath.Clean(folder + "/" + filepath.Dir(symPath))
err = fil.ExtractWithOptions(extractLoc, op)
if err != nil {
if op.Verbose {
log.Println("Error while extracting ", folder+"/"+f.name)
log.Println("Error while extracting ", folder+"/"+f.e.Name)
}
return err
}
}
err = os.Symlink(f.SymlinkPath(), folder+"/"+f.name)
err = os.Symlink(f.SymlinkPath(), folder+"/"+f.e.Name)
if os.IsExist(err) {
os.Remove(folder + "/" + f.name)
err = os.Symlink(f.SymlinkPath(), folder+"/"+f.name)
os.Remove(folder + "/" + f.e.Name)
err = os.Symlink(f.SymlinkPath(), folder+"/"+f.e.Name)
}
if err != nil {
if op.Verbose {
log.Println("Error while making symlink:", folder+"/"+f.name)
log.Println("Error while making symlink:", folder+"/"+f.e.Name)
}
return err
}
@@ -347,36 +333,3 @@ func (f File) ExtractWithOptions(folder string, op ExtractionOptions) error {
}
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
}
-107
View File
@@ -1,107 +0,0 @@
package squashfs
import (
"bytes"
"errors"
"io"
"github.com/CalebQ42/squashfs/internal/inode"
)
//FileReader provides a io.Reader interface for files within a squashfs archive
type fileReader struct {
r *Reader
data *dataReader
in *inode.Inode
fragmentData []byte
fragged bool
fragOnly bool
read int
FileSize int //FileSize is the total size of the given file
}
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")
)
//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.FileType && in.Type != inode.ExtFileType {
return nil, errPathIsNotFile
}
switch in.Type {
case inode.FileType:
fil := in.Info.(inode.File)
rdr.fragged = fil.Fragmented
rdr.fragOnly = fil.BlockStart == 0
rdr.FileSize = int(fil.Size)
case inode.ExtFileType:
fil := in.Info.(inode.ExtFile)
rdr.fragged = fil.Fragmented
rdr.fragOnly = fil.BlockStart == 0
rdr.FileSize = int(fil.Size)
}
var err error
if rdr.fragged {
rdr.fragmentData, err = r.getFragmentDataFromInode(in)
if err != nil {
return nil, err
}
}
if !rdr.fragOnly {
rdr.data, err = r.newDataReaderFromInode(in)
if err != nil {
return nil, err
}
}
return &rdr, nil
}
func (f *fileReader) Read(p []byte) (int, error) {
if f.fragOnly {
n, err := bytes.NewBuffer(f.fragmentData[f.read:]).Read(p)
f.read += n
if err != nil {
return n, err
}
return n, nil
}
var read int
n, err := f.data.Read(p)
read += n
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
if err != nil {
return read, err
}
} else if err != nil {
return read, err
}
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
}
+85 -229
View File
@@ -2,11 +2,10 @@ package squashfs
import (
"bytes"
"errors"
"io"
"io/fs"
"os"
"path"
"path/filepath"
"strings"
"github.com/CalebQ42/squashfs/internal/directory"
@@ -16,11 +15,19 @@ import (
//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
*File
e []directory.Entry
}
func (r Reader) newFS(e directory.Entry) (f *FS, err error) {
f = new(FS)
f.i, err = r.inodeFromDir(e)
if err != nil {
return
}
f.r = &r
f.e, err = r.readDirectory(f.i)
return
}
//Open opens the file at name. Returns a squashfs.File.
@@ -32,59 +39,46 @@ func (f FS) Open(name string) (fs.File, error) {
Err: fs.ErrInvalid,
}
}
name = path.Clean(strings.TrimPrefix(name, "/"))
name = filepath.Clean(name)
if name == "." || name == "" {
return f.File, nil
}
split := strings.Split(name, "/")
if split[0] == ".." {
if f.parent == nil {
//This should only happen on the root FS
for i := range f.e {
if f.e[i].Name != split[0] {
continue
}
if len(split) > 1 && f.e[i].Type != inode.Dir {
return nil, &fs.PathError{
Op: "open",
Path: name,
//TODO: make error clearer
Err: errors.New("trying to get file outside of squashfs"),
Err: fs.ErrNotExist,
}
}
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 len(split) > 1 {
newFS, err := f.r.newFS(f.e[i])
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:], "/"))
out, err := newFS.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,
}
err.(*fs.PathError).Path = name
}
return fil, nil
return out, err
}
out, err := f.r.newFile(f.e[i])
if err != nil {
err = &fs.PathError{
Op: "open",
Path: name,
Err: err,
}
}
return out, err
}
return nil, &fs.PathError{
Op: "open",
@@ -95,6 +89,7 @@ func (f FS) Open(name string) (fs.File, error) {
//Glob returns the name of the files at the given pattern.
//All paths are relative to the FS.
//Uses filepath.Match to compare names.
func (f FS) Glob(pattern string) (out []string, err error) {
if !fs.ValidPath(pattern) {
return nil, &fs.PathError{
@@ -103,24 +98,12 @@ func (f FS) Glob(pattern string) (out []string, err error) {
Err: fs.ErrInvalid,
}
}
pattern = path.Clean(strings.TrimPrefix(pattern, "/"))
pattern = filepath.Clean(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 {
for i := 0; i < len(f.e); i++ {
if match, _ := path.Match(split[0], f.e[i].Name); match {
if len(split) == 1 {
out = append(out, f.entries[i].Name)
out = append(out, f.e[i].Name)
continue
}
sub, err := f.Sub(split[0])
@@ -156,7 +139,7 @@ func (f FS) Glob(pattern string) (out []string, err error) {
}
}
for i := 0; i < len(subGlob); i++ {
subGlob[i] = f.name + "/" + subGlob[i]
subGlob[i] = f.File.e.Name + "/" + subGlob[i]
}
out = append(out, subGlob...)
}
@@ -174,28 +157,15 @@ func (f FS) ReadDir(name string) ([]fs.DirEntry, error) {
Err: fs.ErrInvalid,
}
}
name = path.Clean(strings.TrimPrefix(name, "/"))
name = filepath.Clean(name)
if name == "." || name == "" {
return f.File.ReadDir(-1)
}
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 {
for i := 0; i < len(f.e); i++ {
if split[0] == f.e[i].Name {
if len(split) == 1 {
in, err := f.r.getInodeFromEntry(f.entries[i])
fi, err := f.r.newFile(f.e[i])
if err != nil {
return nil, &fs.PathError{
Op: "readdir",
@@ -203,23 +173,15 @@ func (f FS) ReadDir(name string) ([]fs.DirEntry, error) {
Err: err,
}
}
ents, err := f.r.readDirFromInode(in)
out, err := fi.ReadDir(-1)
if err != nil {
return nil, &fs.PathError{
err = &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
return out, err
}
sub, err := f.Sub(split[0])
if err != nil {
@@ -294,41 +256,23 @@ func (f FS) Stat(name string) (fs.FileInfo, error) {
Err: fs.ErrInvalid,
}
}
name = path.Clean(strings.TrimPrefix(name, "/"))
name = filepath.Clean(strings.TrimPrefix(name, "/"))
if name == "." || name == "" {
return f.File.Stat()
}
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 {
for i := 0; i < len(f.e); i++ {
if split[0] == f.e[i].Name {
if len(split) == 1 {
in, err := f.r.getInodeFromEntry(f.entries[i])
in, err := f.r.newFileInfo(f.e[i])
if err != nil {
return nil, &fs.PathError{
err = &fs.PathError{
Op: "stat",
Path: name,
Err: err,
}
}
return FileInfo{
i: in,
parent: &f,
r: f.r,
name: f.entries[i].Name,
}, nil
return in, err
}
sub, err := f.Sub(split[0])
if err != nil {
@@ -381,68 +325,38 @@ func (f FS) Sub(dir string) (fs.FS, error) {
Err: fs.ErrInvalid,
}
}
dir = path.Clean(strings.TrimPrefix(dir, "/"))
dir = filepath.Clean(dir)
if dir == "." || dir == "" {
return f, nil
}
split := strings.Split(dir, "/")
if split[0] == ".." {
if f.parent == nil {
//This should only happen on the root FS
for i := range f.e {
if f.e[i].Name != split[0] {
continue
}
if f.e[i].Type != inode.Dir {
return nil, &fs.PathError{
Op: "sub",
Path: dir,
//TODO: make error clearer
Err: errors.New("trying to get file outside of squashfs"),
Err: fs.ErrNotExist,
}
}
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
newFS, err := f.r.newFS(f.e[i])
if err != nil {
return nil, &fs.PathError{
Op: "sub",
Path: dir,
Err: err,
}
sub, err := f.Sub(strings.Join(split[1:], "/"))
}
if len(split) > 1 {
ret, err := newFS.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,
}
err.(*fs.PathError).Path = dir
}
return sub, nil
return ret, err
}
return newFS, nil
}
return nil, &fs.PathError{
Op: "sub",
@@ -450,61 +364,3 @@ func (f FS) Sub(dir string) (fs.FS, error) {
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
}
+134
View File
@@ -0,0 +1,134 @@
package squashfs
import (
"errors"
"io"
"github.com/CalebQ42/squashfs/internal/data"
"github.com/CalebQ42/squashfs/internal/directory"
"github.com/CalebQ42/squashfs/internal/inode"
"github.com/CalebQ42/squashfs/internal/metadata"
"github.com/CalebQ42/squashfs/internal/toreader"
)
func (r Reader) inodeFromRef(ref uint64) (i inode.Inode, err error) {
offset, meta := (ref>>16)+r.s.InodeTableStart, ref&0xFFFF
rdr, err := metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d)
if err != nil {
return
}
_, err = rdr.Read(make([]byte, meta))
if err != nil {
return
}
return inode.Read(rdr, r.s.BlockSize)
}
func (r Reader) inodeFromDir(e directory.Entry) (i inode.Inode, err error) {
rdr, err := metadata.NewReader(toreader.NewReader(r.r, int64(uint64(e.BlockStart)+r.s.InodeTableStart)), r.d)
if err != nil {
return
}
_, err = rdr.Read(make([]byte, e.Offset))
if err != nil {
return
}
return inode.Read(rdr, r.s.BlockSize)
}
func (r Reader) getData(i inode.Inode) (io.Reader, error) {
var fragOffset uint64
var blockOffset uint32
var blockSizes []uint32
var fragInd uint32
if i.Type == inode.Fil {
fragOffset = uint64(i.Data.(inode.File).Offset)
blockOffset = i.Data.(inode.File).BlockStart
blockSizes = i.Data.(inode.File).BlockSizes
fragInd = i.Data.(inode.File).FragInd
} else if i.Type == inode.EFil {
fragOffset = uint64(i.Data.(inode.EFile).Offset)
blockOffset = i.Data.(inode.EFile).BlockStart
blockSizes = i.Data.(inode.EFile).BlockSizes
fragInd = i.Data.(inode.EFile).FragInd
} else {
return nil, errors.New("getData called on non-file type")
}
rdr, err := data.NewReader(toreader.NewReader(r.r, int64(blockOffset)), r.d, blockSizes, r.s.BlockSize)
if err != nil {
return nil, err
}
if fragInd != 0xFFFFFFFF {
var fragRdr io.Reader
fragRdr, err = r.fragReader(fragInd)
if err != nil {
return nil, err
}
_, err = fragRdr.Read(make([]byte, fragOffset))
if err != nil {
return nil, err
}
rdr.AddFragment(fragRdr)
}
return rdr, nil
}
func (r Reader) getFullReader(i inode.Inode) (rdr *data.FullReader, err error) {
var fragOffset uint64
var blockOffset uint32
var blockSizes []uint32
var fragInd uint32
if i.Type == inode.Fil {
fragOffset = uint64(i.Data.(inode.File).Offset)
blockOffset = i.Data.(inode.File).BlockStart
blockSizes = i.Data.(inode.File).BlockSizes
fragInd = i.Data.(inode.File).FragInd
} else if i.Type == inode.EFil {
fragOffset = uint64(i.Data.(inode.EFile).Offset)
blockOffset = i.Data.(inode.EFile).BlockStart
blockSizes = i.Data.(inode.EFile).BlockSizes
fragInd = i.Data.(inode.EFile).FragInd
} else {
return nil, errors.New("getData called on non-file type")
}
rdr = data.NewFullReader(r.r, uint64(blockOffset), r.d, blockSizes, r.s.BlockSize)
if fragInd != 0xFFFFFFFF {
var fragRdr io.Reader
fragRdr, err = r.fragReader(fragInd)
if err != nil {
return nil, err
}
_, err = fragRdr.Read(make([]byte, fragOffset))
if err != nil {
return nil, err
}
rdr.AddFragment(fragRdr)
}
return rdr, nil
}
func (r Reader) readDirectory(i inode.Inode) ([]directory.Entry, error) {
var offset uint64
var blockOffset uint16
var size uint32
if i.Type == inode.Dir {
offset = uint64(i.Data.(inode.Directory).BlockStart)
blockOffset = i.Data.(inode.Directory).Offset
size = uint32(i.Data.(inode.Directory).Size)
} else if i.Type == inode.EDir {
offset = uint64(i.Data.(inode.EDirectory).BlockStart)
blockOffset = i.Data.(inode.EDirectory).Offset
size = i.Data.(inode.EDirectory).Size
} else {
return nil, errors.New("readDirectory called on non-directory type")
}
rdr, err := metadata.NewReader(toreader.NewReader(r.r, int64(offset+r.s.DirTableStart)), r.d)
if err != nil {
return nil, err
}
_, err = rdr.Read(make([]byte, blockOffset))
if err != nil {
return nil, err
}
return directory.ReadEntries(rdr, size)
}
+4 -4
View File
@@ -36,7 +36,7 @@ func TestSquashfs(t *testing.T) {
if err != nil {
t.Fatal(err)
}
rdr, err := NewSquashfsReader(squashFil)
rdr, err := NewReader(squashFil)
if err != nil {
t.Fatal(err)
}
@@ -56,7 +56,7 @@ func TestSquashfsFromReader(t *testing.T) {
t.Fatal(err)
}
defer resp.Body.Close()
rdr, err := NewSquashfsReaderFromReader(resp.Body)
rdr, err := NewReaderFromReader(resp.Body)
if err != nil {
t.Fatal(err)
}
@@ -92,7 +92,7 @@ func TestAppImage(t *testing.T) {
defer aiFil.Close()
stat, _ := aiFil.Stat()
ai := goappimage.NewAppImage(wd + "/testing/" + appImageName)
rdr, err := NewSquashfsReader(io.NewSectionReader(aiFil, ai.Offset, stat.Size()-ai.Offset))
rdr, err := NewReader(io.NewSectionReader(aiFil, ai.Offset, stat.Size()-ai.Offset))
if err != nil {
t.Fatal(err)
}
@@ -164,7 +164,7 @@ func BenchmarkDragRace(b *testing.B) {
}
unsquashTime := time.Since(start)
start = time.Now()
rdr, err := NewSquashfsReader(io.NewSectionReader(aiFil, ai.Offset, stat.Size()-ai.Offset))
rdr, err := NewReader(io.NewSectionReader(aiFil, ai.Offset, stat.Size()-ai.Offset))
if err != nil {
b.Fatal(err)
}
+51 -101
View File
@@ -1,31 +1,22 @@
package squashfs
//The types of compression supported by squashfs.
const (
GzipCompression = 1 + iota
LzmaCompression
LzoCompression
XzCompression
Lz4Compression
ZstdCompression
)
import "math"
//Superblock contains important information about a squashfs file. Located at the very front of the archive.
type superblock struct {
Magic uint32
InodeCount uint32
CreationTime uint32
ModTime uint32
BlockSize uint32
FragCount uint32
CompressionType uint16
CompType uint16
BlockLog uint16
Flags uint16
IDCount uint16
MajorVersion uint16
MinorVersion uint16
IdCount uint16
VerMaj uint16
VerMin uint16
RootInodeRef uint64
BytesUsed uint64
IDTableStart uint64
Size uint64
IdTableStart uint64
XattrTableStart uint64
InodeTableStart uint64
DirTableStart uint64
@@ -33,94 +24,53 @@ type superblock struct {
ExportTableStart uint64
}
//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
//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
func (s superblock) hasMagic() bool {
return s.Magic == 0x73717368
}
//DefaultFlags are the default SuperblockFlags that are used.
var DefaultFlags = SuperblockFlags{
RemoveDuplicates: true,
Exportable: true,
func (s superblock) checkBlockLog() bool {
return s.BlockLog == uint16(math.Log2(float64(s.BlockSize)))
}
//GetFlags returns a SuperblockFlags for a given superblock.
func (s *superblock) GetFlags() SuperblockFlags {
return SuperblockFlags{
UncompressedInodes: s.Flags&0x1 == 0x1,
UncompressedData: s.Flags&0x2 == 0x2,
check: s.Flags&0x4 == 0x4,
UncompressedFragments: s.Flags&0x8 == 0x8,
NoFragments: s.Flags&0x10 == 0x10,
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,
UncompressedIDs: s.Flags&0x800 == 0x800,
}
func (s superblock) uncompressedInodes() bool {
return s.Flags&0x1 == 0x1
}
//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
func (s superblock) uncompressedData() bool {
return s.Flags&0x2 == 0x2
}
func (s superblock) uncompressedFragments() bool {
return s.Flags&0x8 == 0x8
}
func (s superblock) noFragments() bool {
return s.Flags&0x10 == 0x10
}
func (s superblock) alwaysFragment() bool {
return s.Flags&0x20 == 0x20
}
func (s superblock) duplicates() bool {
return s.Flags&0x40 == 0x40
}
func (s superblock) exportable() bool {
return s.Flags&0x80 == 0x80
}
func (s superblock) uncompressedXattrs() bool {
return s.Flags&0x100 == 0x100
}
func (s superblock) noXattrs() bool {
return s.Flags&0x200 == 0x200
}
func (s superblock) compressionOptions() bool {
return s.Flags&0x400 == 0x400
}
func (s superblock) uncompressedIDs() bool {
return s.Flags&0x800 == 0x800
}