Initial work
Create Reader Pulled back in Inode decoding and superblock New Data and Metadata readers Added getting of id, fragment, and export table data lazily Added README to squashfs/squashfs
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://pkg.go.dev/github.com/CalebQ42/squashfs) [](https://goreportcard.com/report/github.com/CalebQ42/squashfs)
|
||||
|
||||
A PURE Go library to read squashfs. There is currently no plans to add archive creation support as it will almost always be better to just call `mksquashfs`. I could see some possible use cases, but probably won't spend time on it unless it's requested (open a discussion fi you want this feature).
|
||||
A PURE Go library to read squashfs. There is currently no plans to add archive creation support as it will almost always be better to just call `mksquashfs`. I could see some possible use cases, but probably won't spend time on it unless it's requested (open a discussion if you want this feature).
|
||||
|
||||
Currently has support for reading squashfs files and extracting files and folders.
|
||||
|
||||
@@ -15,7 +15,7 @@ Thanks also to [distri's squashfs library](https://github.com/distr1/distri/tree
|
||||
|
||||
* 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.
|
||||
* Socket files are not extracted.
|
||||
* From my research, it seems like a socket file would be useless if it could be created.
|
||||
* From my research, it seems like a socket file would be useless if it could be created. They are still exposed when fuse mounted.
|
||||
* Fifo files are ignored on `darwin`
|
||||
|
||||
## Issues
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
module github.com/CalebQ42/squashfs
|
||||
|
||||
go 1.21.5
|
||||
|
||||
require (
|
||||
github.com/pierrec/lz4/v4 v4.1.19
|
||||
github.com/ulikunitz/xz v0.5.11
|
||||
github.com/klauspost/compress v1.17.4
|
||||
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e
|
||||
github.com/therootcompany/xz v1.0.1
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/pierrec/lz4/v4 v4.1.19 h1:tYLzDnjDXh9qIxSTKHwXwOYmm9d887Y7Y1ZkyXYHAN4=
|
||||
github.com/pierrec/lz4/v4 v4.1.19/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
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/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
|
||||
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
|
||||
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
||||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
@@ -0,0 +1,73 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/CalebQ42/squashfs/internal/decompress"
|
||||
)
|
||||
|
||||
type Reader struct {
|
||||
r io.Reader
|
||||
d decompress.Decompressor
|
||||
frag io.Reader
|
||||
sizes []uint32
|
||||
dat []byte
|
||||
curOffset uint16
|
||||
curIndex uint64
|
||||
}
|
||||
|
||||
func NewReader(r io.Reader, d decompress.Decompressor, sizes []uint32) (*Reader, error) {
|
||||
return &Reader{
|
||||
r: r,
|
||||
d: d,
|
||||
sizes: sizes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Reader) AddFrag(fragRdr io.Reader) {
|
||||
r.frag = fragRdr
|
||||
}
|
||||
|
||||
func (r *Reader) advance() error {
|
||||
r.curOffset = 0
|
||||
defer func() { r.curIndex++ }()
|
||||
var err error
|
||||
if r.curIndex == uint64(len(r.sizes))-1 && r.frag != nil {
|
||||
r.dat, err = io.ReadAll(r.frag)
|
||||
return err
|
||||
} else if r.curIndex >= uint64(len(r.sizes))-1 {
|
||||
return io.EOF
|
||||
}
|
||||
realSize := r.sizes[r.curIndex] &^ 0x8000
|
||||
r.dat = make([]byte, realSize)
|
||||
err = binary.Read(r.r, binary.LittleEndian, &r.dat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.sizes[r.curIndex] != realSize {
|
||||
return nil
|
||||
}
|
||||
r.dat, err = r.d.Decompress(r.dat)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Reader) Read(b []byte) (int, error) {
|
||||
curRead := 0
|
||||
var toRead int
|
||||
for curRead < len(b) {
|
||||
if r.curOffset >= uint16(len(r.dat)) {
|
||||
if err := r.advance(); err != nil {
|
||||
return curRead, err
|
||||
}
|
||||
}
|
||||
toRead = len(b) - curRead
|
||||
if toRead > len(r.dat)-int(r.curOffset) {
|
||||
toRead = len(r.dat) - int(r.curOffset)
|
||||
}
|
||||
copy(b[curRead:], r.dat[r.curOffset:int(r.curOffset)+toRead])
|
||||
r.curOffset += uint16(toRead)
|
||||
curRead += toRead
|
||||
}
|
||||
return curRead, nil
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package decompress
|
||||
|
||||
type Decompressor interface {
|
||||
Decompress([]byte) ([]byte, error)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package decompress
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"github.com/pierrec/lz4/v4"
|
||||
)
|
||||
|
||||
type Lz4 struct{}
|
||||
|
||||
func (l Lz4) Decompress(data []byte) ([]byte, error) {
|
||||
rdr := lz4.NewReader(bytes.NewReader(data))
|
||||
return io.ReadAll(rdr)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package decompress
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"github.com/ulikunitz/xz/lzma"
|
||||
)
|
||||
|
||||
type Lzma struct{}
|
||||
|
||||
func (l Lzma) Decompress(data []byte) ([]byte, error) {
|
||||
rdr, err := lzma.NewReader(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return io.ReadAll(rdr)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package decompress
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/rasky/go-lzo"
|
||||
)
|
||||
|
||||
type Lzo struct{}
|
||||
|
||||
func (l Lzo) Decompress(data []byte) ([]byte, error) {
|
||||
return lzo.Decompress1X(bytes.NewReader(data), len(data), 0)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package decompress
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"github.com/therootcompany/xz"
|
||||
)
|
||||
|
||||
type Xz struct{}
|
||||
|
||||
func (x Xz) Decompress(data []byte) ([]byte, error) {
|
||||
rdr, err := xz.NewReader(bytes.NewReader(data), 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return io.ReadAll(rdr)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package decompress
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Zlib struct{}
|
||||
|
||||
func (z Zlib) Decompress(data []byte) ([]byte, error) {
|
||||
rdr, err := zlib.NewReader(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rdr.Close()
|
||||
return io.ReadAll(rdr)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package decompress
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"github.com/klauspost/compress/zstd"
|
||||
)
|
||||
|
||||
type Zstd struct{}
|
||||
|
||||
func (z Zstd) Decompress(data []byte) ([]byte, error) {
|
||||
rdr, err := zstd.NewReader(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rdr.Close()
|
||||
return io.ReadAll(rdr)
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/CalebQ42/squashfs/internal/decompress"
|
||||
)
|
||||
|
||||
type Reader struct {
|
||||
r io.Reader
|
||||
d decompress.Decompressor
|
||||
dat []byte
|
||||
curOffset uint16
|
||||
}
|
||||
|
||||
func NewReader(r io.Reader, d decompress.Decompressor) *Reader {
|
||||
return &Reader{
|
||||
r: r,
|
||||
d: d,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reader) advance() error {
|
||||
r.curOffset = 0
|
||||
var size uint16
|
||||
err := binary.Read(r.r, binary.LittleEndian, &size)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
realSize := size &^ 0x8000
|
||||
r.dat = make([]byte, realSize)
|
||||
err = binary.Read(r.r, binary.LittleEndian, &r.dat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if size != realSize {
|
||||
return nil
|
||||
}
|
||||
r.dat, err = r.d.Decompress(r.dat)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Reader) Read(b []byte) (int, error) {
|
||||
curRead := 0
|
||||
var toRead int
|
||||
for curRead < len(b) {
|
||||
if r.curOffset >= uint16(len(r.dat)) {
|
||||
if err := r.advance(); err != nil {
|
||||
return curRead, err
|
||||
}
|
||||
}
|
||||
toRead = len(b) - curRead
|
||||
if toRead > len(r.dat)-int(r.curOffset) {
|
||||
toRead = len(r.dat) - int(r.curOffset)
|
||||
}
|
||||
copy(b[curRead:], r.dat[r.curOffset:int(r.curOffset)+toRead])
|
||||
r.curOffset += uint16(toRead)
|
||||
curRead += toRead
|
||||
}
|
||||
return curRead, nil
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package toreader
|
||||
|
||||
import "io"
|
||||
|
||||
type Reader struct {
|
||||
r io.ReaderAt
|
||||
offset int64
|
||||
}
|
||||
|
||||
func NewReader(r io.ReaderAt, start int64) *Reader {
|
||||
return &Reader{
|
||||
r: r,
|
||||
offset: start,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reader) Read(b []byte) (int, error) {
|
||||
n, err := r.r.ReadAt(b, r.offset)
|
||||
r.offset += int64(n)
|
||||
return n, err
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Lower-Level Squashfs
|
||||
|
||||
This library is a lower level version of the main [squashfs](https://github.com/CalebQ42/squashfs) library that doesn't try to be easy to use and exposes a lot of information that is not necesary for must use cases.
|
||||
@@ -0,0 +1,7 @@
|
||||
package squashfs
|
||||
|
||||
type fragEntry struct {
|
||||
start uint64
|
||||
size uint32
|
||||
_ uint32
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package squashfs
|
||||
|
||||
import (
|
||||
"github.com/CalebQ42/squashfs/internal/metadata"
|
||||
"github.com/CalebQ42/squashfs/internal/toreader"
|
||||
"github.com/CalebQ42/squashfs/squashfs/inode"
|
||||
)
|
||||
|
||||
func (r *Reader) inodeFromRef(ref uint64) (*inode.Inode, error) {
|
||||
offset, meta := (ref>>16)+r.sup.InodeTableStart, ref&0xFFFF
|
||||
rdr := metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d)
|
||||
_, err := rdr.Read(make([]byte, meta))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return inode.Read(rdr, r.sup.BlockSize)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package inode
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"math"
|
||||
)
|
||||
|
||||
type fileInit struct {
|
||||
BlockStart uint32
|
||||
FragInd uint32
|
||||
FragOffset uint32
|
||||
Size uint32
|
||||
}
|
||||
|
||||
type File struct {
|
||||
fileInit
|
||||
BlockSizes []uint32
|
||||
}
|
||||
|
||||
type eFileInit struct {
|
||||
BlockStart uint64
|
||||
Size uint64
|
||||
Sparse uint64
|
||||
LinkCount uint32
|
||||
FragInd uint32
|
||||
FragOffset 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
|
||||
}
|
||||
toRead := int(math.Floor(float64(f.Size) / float64(blockSize)))
|
||||
if f.FragInd == 0xFFFFFFFF && f.Size%blockSize > 0 {
|
||||
toRead++
|
||||
}
|
||||
f.BlockSizes = make([]uint32, toRead)
|
||||
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
|
||||
}
|
||||
toRead := int(math.Floor(float64(f.Size) / float64(blockSize)))
|
||||
if f.FragInd == 0xFFFFFFFF && f.Size%uint64(blockSize) > 0 {
|
||||
toRead++
|
||||
}
|
||||
f.BlockSizes = make([]uint32, toRead)
|
||||
err = binary.Read(r, binary.LittleEndian, &f.BlockSizes)
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package inode
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
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) {
|
||||
i = new(Inode)
|
||||
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 " + strconv.Itoa(int(i.Type)))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (i Inode) Mode() (out fs.FileMode) {
|
||||
out = fs.FileMode(i.Perm)
|
||||
switch i.Data.(type) {
|
||||
case Directory:
|
||||
out |= fs.ModeDir
|
||||
case EDirectory:
|
||||
out |= fs.ModeDir
|
||||
case Symlink:
|
||||
out |= fs.ModeSymlink
|
||||
case ESymlink:
|
||||
out |= fs.ModeSymlink
|
||||
case Device:
|
||||
out |= fs.ModeDevice
|
||||
case EDevice:
|
||||
out |= fs.ModeDevice
|
||||
case IPC:
|
||||
out |= fs.ModeNamedPipe
|
||||
case EIPC:
|
||||
out |= fs.ModeNamedPipe
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (i Inode) LinkCount() uint32 {
|
||||
switch i.Data.(type) {
|
||||
case EFile:
|
||||
return i.Data.(EFile).LinkCount
|
||||
case Directory:
|
||||
return i.Data.(Directory).LinkCount
|
||||
case EDirectory:
|
||||
return i.Data.(EDirectory).LinkCount
|
||||
case Device:
|
||||
return i.Data.(Device).LinkCount
|
||||
case EDevice:
|
||||
return i.Data.(EDevice).LinkCount
|
||||
case IPC:
|
||||
return i.Data.(IPC).LinkCount
|
||||
case EIPC:
|
||||
return i.Data.(EIPC).LinkCount
|
||||
case Symlink:
|
||||
return i.Data.(Symlink).LinkCount
|
||||
case ESymlink:
|
||||
return i.Data.(ESymlink).LinkCount
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (i Inode) Size() uint64 {
|
||||
switch i.Data.(type) {
|
||||
case File:
|
||||
return uint64(i.Data.(File).Size)
|
||||
case EFile:
|
||||
return i.Data.(EFile).Size
|
||||
// case Directory:
|
||||
// return uint64(i.Data.(Directory).Size)
|
||||
// case EDirectory:
|
||||
// return uint64(i.Data.(EDirectory).Size)
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
package squashfs
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/CalebQ42/squashfs/internal/decompress"
|
||||
"github.com/CalebQ42/squashfs/internal/toreader"
|
||||
)
|
||||
|
||||
// The types of compression supported by squashfs
|
||||
const (
|
||||
ZlibCompression = uint16(iota + 1)
|
||||
LZMACompression
|
||||
LZOCompression
|
||||
XZCompression
|
||||
LZ4Compression
|
||||
ZSTDCompression
|
||||
)
|
||||
|
||||
var (
|
||||
ErrorMagic = errors.New("magic incorrect. probably not reading squashfs archive or archive is corrupted")
|
||||
ErrorLog = errors.New("block log is incorrect. possible corrupted archive")
|
||||
ErrorVersion = errors.New("squashfs version of archive is not 4.0. may be corrupted")
|
||||
ErrorNotExportable = errors.New("archive does not have an export table")
|
||||
)
|
||||
|
||||
type Reader struct {
|
||||
r io.ReaderAt
|
||||
d decompress.Decompressor
|
||||
fragTable []fragEntry
|
||||
idTable []uint32
|
||||
exportTable []uint64
|
||||
sup superblock
|
||||
}
|
||||
|
||||
func NewReader(r io.ReaderAt) (rdr *Reader, err error) {
|
||||
rdr = new(Reader)
|
||||
rdr.r = r
|
||||
err = binary.Read(toreader.NewReader(r, 0), binary.LittleEndian, &rdr.sup)
|
||||
if err != nil {
|
||||
return nil, errors.Join(errors.New("failed to read superblock"), err)
|
||||
}
|
||||
if !rdr.sup.checkMagic() {
|
||||
return nil, ErrorMagic
|
||||
}
|
||||
if !rdr.sup.checkBlockLog() {
|
||||
return nil, ErrorLog
|
||||
}
|
||||
if !rdr.sup.checkVersion() {
|
||||
return nil, ErrorVersion
|
||||
}
|
||||
switch rdr.sup.CompType {
|
||||
case ZlibCompression:
|
||||
rdr.d = decompress.Zlib{}
|
||||
case LZMACompression:
|
||||
rdr.d = decompress.Lzma{}
|
||||
case LZOCompression:
|
||||
rdr.d = decompress.Lzo{}
|
||||
case XZCompression:
|
||||
rdr.d = decompress.Xz{}
|
||||
case LZ4Compression:
|
||||
rdr.d = decompress.Lz4{}
|
||||
case ZSTDCompression:
|
||||
rdr.d = &decompress.Zstd{}
|
||||
default:
|
||||
return nil, errors.New("invalid compression type. possible corrupted archive")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Returns the last time the archive was modified.
|
||||
func (r *Reader) ModTime() time.Time {
|
||||
return time.Unix(int64(r.sup.ModTime), 0)
|
||||
}
|
||||
|
||||
// Get a uid/gid at the given index. Lazily populates the reader's id table as necessary.
|
||||
func (r *Reader) id(i uint16) (uint32, error) {
|
||||
if len(r.idTable) > int(i) {
|
||||
return r.idTable[i], nil
|
||||
} else if i >= r.sup.IdCount {
|
||||
return 0, errors.New("id out of bounds")
|
||||
}
|
||||
// Populate the id table as needed
|
||||
blockNum := uint16(math.Ceil(float64(i) / 2048))
|
||||
blocksRead := len(r.idTable) / 2048
|
||||
blocksToRead := int(blockNum) - blocksRead
|
||||
|
||||
var offset uint64
|
||||
var idsToRead uint16
|
||||
var idsTmp []uint32
|
||||
var err error
|
||||
for i := blocksRead; i < int(blockNum)+blocksToRead; i++ {
|
||||
err = binary.Read(toreader.NewReader(r.r, int64(r.sup.IdTableStart)+int64(8*i)), binary.LittleEndian, &offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
idsToRead = r.sup.IdCount - uint16(len(r.idTable))
|
||||
if idsToRead > 2048 {
|
||||
idsToRead = 2048
|
||||
}
|
||||
idsTmp = make([]uint32, idsToRead)
|
||||
err = binary.Read(toreader.NewReader(r.r, int64(offset)), binary.LittleEndian, &idsTmp)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
r.idTable = append(r.idTable, idsTmp...)
|
||||
}
|
||||
return r.idTable[i], nil
|
||||
}
|
||||
|
||||
// Get a fragment entry at the given index. Lazily populates the reader's fragment table as necessary.
|
||||
func (r *Reader) fragEntry(i uint32) (fragEntry, error) {
|
||||
if len(r.fragTable) > int(i) {
|
||||
return r.fragTable[i], nil
|
||||
} else if i >= r.sup.FragCount {
|
||||
return fragEntry{}, errors.New("fragment out of bounds")
|
||||
}
|
||||
// Populate the fragment table as needed
|
||||
blockNum := uint32(math.Ceil(float64(i) / 512))
|
||||
blocksRead := len(r.fragTable) / 512
|
||||
blocksToRead := int(blockNum) - blocksRead
|
||||
|
||||
var offset uint64
|
||||
var fragsToRead uint32
|
||||
var fragsTmp []fragEntry
|
||||
var err error
|
||||
for i := blocksRead; i < int(blockNum)+blocksToRead; i++ {
|
||||
err = binary.Read(toreader.NewReader(r.r, int64(r.sup.FragTableStart)+int64(8*i)), binary.LittleEndian, &offset)
|
||||
if err != nil {
|
||||
return fragEntry{}, err
|
||||
}
|
||||
fragsToRead = r.sup.FragCount - uint32(len(r.fragTable))
|
||||
if fragsToRead > 512 {
|
||||
fragsToRead = 512
|
||||
}
|
||||
fragsTmp = make([]fragEntry, fragsToRead)
|
||||
err = binary.Read(toreader.NewReader(r.r, int64(offset)), binary.LittleEndian, &fragsTmp)
|
||||
if err != nil {
|
||||
return fragEntry{}, err
|
||||
}
|
||||
r.fragTable = append(r.fragTable, fragsTmp...)
|
||||
}
|
||||
return r.fragTable[i], nil
|
||||
}
|
||||
|
||||
// Get an inode reference at the given index. Lazily populates the reader's export table as necessary.
|
||||
func (r *Reader) inodeRef(i uint32) (uint64, error) {
|
||||
if !r.sup.exportable() {
|
||||
return 0, ErrorNotExportable
|
||||
}
|
||||
if len(r.exportTable) > int(i) {
|
||||
return r.exportTable[i], nil
|
||||
} else if i >= r.sup.InodeCount {
|
||||
return 0, errors.New("inode out of bounds")
|
||||
}
|
||||
// Populate the export table as neede
|
||||
blockNum := uint32(math.Ceil(float64(i) / 1024))
|
||||
blocksRead := len(r.exportTable) / 1024
|
||||
blocksToRead := int(blockNum) - blocksRead
|
||||
|
||||
var offset uint64
|
||||
var refsToRead uint32
|
||||
var refsTmp []uint64
|
||||
var err error
|
||||
for i := blocksRead; i < int(blockNum)+blocksToRead; i++ {
|
||||
err = binary.Read(toreader.NewReader(r.r, int64(r.sup.ExportTableStart)+int64(8*i)), binary.LittleEndian, &offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
refsToRead = r.sup.InodeCount - uint32(len(r.exportTable))
|
||||
if refsToRead > 1024 {
|
||||
refsToRead = 1024
|
||||
}
|
||||
refsTmp = make([]uint64, refsToRead)
|
||||
err = binary.Read(toreader.NewReader(r.r, int64(offset)), binary.LittleEndian, &refsTmp)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
r.exportTable = append(r.exportTable, refsTmp...)
|
||||
}
|
||||
return r.exportTable[i], nil
|
||||
}
|
||||
Reference in New Issue
Block a user