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)
|
[](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.
|
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.
|
* 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.
|
* 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`
|
* Fifo files are ignored on `darwin`
|
||||||
|
|
||||||
## Issues
|
## 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