Inode parsing and directory decoding
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
type FullReader struct{
|
||||||
|
|
||||||
|
}
|
||||||
+12
-4
@@ -10,22 +10,22 @@ import (
|
|||||||
type Reader struct {
|
type Reader struct {
|
||||||
r io.Reader
|
r io.Reader
|
||||||
d decompress.Decompressor
|
d decompress.Decompressor
|
||||||
frag io.Reader
|
frag *Reader
|
||||||
sizes []uint32
|
sizes []uint32
|
||||||
dat []byte
|
dat []byte
|
||||||
curOffset uint16
|
curOffset uint16
|
||||||
curIndex uint64
|
curIndex uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReader(r io.Reader, d decompress.Decompressor, sizes []uint32) (*Reader, error) {
|
func NewReader(r io.Reader, d decompress.Decompressor, sizes []uint32) *Reader {
|
||||||
return &Reader{
|
return &Reader{
|
||||||
r: r,
|
r: r,
|
||||||
d: d,
|
d: d,
|
||||||
sizes: sizes,
|
sizes: sizes,
|
||||||
}, nil
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) AddFrag(fragRdr io.Reader) {
|
func (r *Reader) AddFrag(fragRdr *Reader) {
|
||||||
r.frag = fragRdr
|
r.frag = fragRdr
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,3 +71,11 @@ func (r *Reader) Read(b []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
return curRead, nil
|
return curRead, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Reader) Close() error {
|
||||||
|
if r.frag != nil {
|
||||||
|
return r.frag.Close()
|
||||||
|
}
|
||||||
|
r.dat = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package squashfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/CalebQ42/squashfs/internal/data"
|
||||||
|
"github.com/CalebQ42/squashfs/internal/metadata"
|
||||||
|
"github.com/CalebQ42/squashfs/internal/toreader"
|
||||||
|
"github.com/CalebQ42/squashfs/squashfs/directory"
|
||||||
|
"github.com/CalebQ42/squashfs/squashfs/inode"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Base struct {
|
||||||
|
Inode *inode.Inode
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) baseFromInode(i *inode.Inode, name string) *Base {
|
||||||
|
return &Base{Inode: i, Name: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) baseFromEntry(e directory.Entry) (*Base, error) {
|
||||||
|
rdr := metadata.NewReader(toreader.NewReader(r.r, int64(r.sup.InodeTableStart)+int64(e.BlockStart)), r.d)
|
||||||
|
rdr.Read(make([]byte, e.Offset))
|
||||||
|
in, err := inode.Read(rdr, r.sup.BlockSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Base{Inode: in, Name: e.Name}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Base) Uid(r *Reader) (uint32, error) {
|
||||||
|
return r.id(b.Inode.UidInd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Base) Gid(r *Reader) (uint32, error) {
|
||||||
|
return r.id(b.Inode.GidInd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Base) IsDir() bool {
|
||||||
|
return b.Inode.Type == inode.Dir || b.Inode.Type == inode.EDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Base) ToDir(r *Reader) (*Directory, error) {
|
||||||
|
return r.directoryFromInode(b.Inode, b.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Base) IsRegular() bool {
|
||||||
|
return b.Inode.Type == inode.Fil || b.Inode.Type == inode.EFil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Base) GetRegFileReaders(r *Reader) (io.ReadCloser, error) {
|
||||||
|
if !b.IsRegular() {
|
||||||
|
return nil, errors.New("not a regular file")
|
||||||
|
}
|
||||||
|
var blockStart uint64
|
||||||
|
var fragIndex uint32
|
||||||
|
var fragOffset uint32
|
||||||
|
var sizes []uint32
|
||||||
|
if b.Inode.Type == inode.Fil {
|
||||||
|
blockStart = uint64(b.Inode.Data.(inode.File).BlockStart)
|
||||||
|
fragIndex = b.Inode.Data.(inode.File).FragInd
|
||||||
|
fragOffset = b.Inode.Data.(inode.File).FragOffset
|
||||||
|
sizes = b.Inode.Data.(inode.File).BlockSizes
|
||||||
|
} else {
|
||||||
|
blockStart = b.Inode.Data.(inode.EFile).BlockStart
|
||||||
|
fragIndex = b.Inode.Data.(inode.EFile).FragInd
|
||||||
|
fragOffset = b.Inode.Data.(inode.EFile).FragOffset
|
||||||
|
sizes = b.Inode.Data.(inode.EFile).BlockSizes
|
||||||
|
}
|
||||||
|
var frag *data.Reader
|
||||||
|
if fragIndex != 0xFFFFFFFF {
|
||||||
|
ent, err := r.fragEntry(fragIndex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
frag = data.NewReader(toreader.NewReader(r.r, int64(ent.start)), r.d, []uint32{ent.size})
|
||||||
|
frag.Read(make([]byte, fragOffset))
|
||||||
|
}
|
||||||
|
out := data.NewReader(toreader.NewReader(r.r, int64(blockStart)), r.d, sizes)
|
||||||
|
if frag != nil {
|
||||||
|
out.AddFrag(out)
|
||||||
|
}
|
||||||
|
//TODO: implement and add full reader
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package squashfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/fs"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/CalebQ42/squashfs/internal/metadata"
|
||||||
|
"github.com/CalebQ42/squashfs/internal/toreader"
|
||||||
|
"github.com/CalebQ42/squashfs/squashfs/directory"
|
||||||
|
"github.com/CalebQ42/squashfs/squashfs/inode"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Directory struct {
|
||||||
|
Base
|
||||||
|
Entries []directory.Entry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) directoryFromInode(i *inode.Inode, name string) (*Directory, error) {
|
||||||
|
var blockStart uint32
|
||||||
|
var size uint32
|
||||||
|
var offset uint16
|
||||||
|
switch i.Type {
|
||||||
|
case inode.Dir:
|
||||||
|
blockStart = i.Data.(inode.Directory).BlockStart
|
||||||
|
size = uint32(i.Data.(inode.Directory).Size)
|
||||||
|
offset = i.Data.(inode.Directory).Offset
|
||||||
|
case inode.EDir:
|
||||||
|
blockStart = i.Data.(inode.EDirectory).BlockStart
|
||||||
|
size = i.Data.(inode.EDirectory).Size
|
||||||
|
offset = i.Data.(inode.EDirectory).Offset
|
||||||
|
default:
|
||||||
|
return nil, errors.New("not a directory")
|
||||||
|
}
|
||||||
|
dirRdr := metadata.NewReader(toreader.NewReader(r.r, int64(r.sup.DirTableStart)+int64(blockStart)), r.d)
|
||||||
|
_, err := dirRdr.Read(make([]byte, offset))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entries, err := directory.ReadDirectory(dirRdr, size)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Directory{
|
||||||
|
Base: *r.baseFromInode(i, name),
|
||||||
|
Entries: entries,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) directoryFromRef(ref uint64, name string) (*Directory, error) {
|
||||||
|
in, err := r.inodeFromRef(ref)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return r.directoryFromInode(in, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Directory) Open(r *Reader, path string) (*Base, error) {
|
||||||
|
path = filepath.Clean(path)
|
||||||
|
if path == "." || path == "" {
|
||||||
|
return &d.Base, nil
|
||||||
|
}
|
||||||
|
split := strings.Split(path, "/")
|
||||||
|
i, found := slices.BinarySearchFunc(d.Entries, split[0], func(e directory.Entry, name string) int {
|
||||||
|
return strings.Compare(e.Name, name)
|
||||||
|
})
|
||||||
|
if !found {
|
||||||
|
return nil, fs.ErrNotExist
|
||||||
|
}
|
||||||
|
b, err := r.baseFromEntry(d.Entries[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(split) == 1 {
|
||||||
|
return b, nil
|
||||||
|
} else if !b.IsDir() {
|
||||||
|
return nil, fs.ErrNotExist
|
||||||
|
}
|
||||||
|
dir, err := b.ToDir(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dir.Open(r, strings.Join(split[1:], "/"))
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package directory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type header struct {
|
||||||
|
Count uint32
|
||||||
|
BlockStart uint32
|
||||||
|
Num uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type decEntry struct {
|
||||||
|
Offset uint16
|
||||||
|
NumOffset int16
|
||||||
|
InodeType uint16
|
||||||
|
NameSize uint16
|
||||||
|
// Name []byte (not decoded along with decEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Entry struct {
|
||||||
|
Name string
|
||||||
|
BlockStart uint32
|
||||||
|
Offset uint16
|
||||||
|
InodeType uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadDirectory(r io.Reader, size uint32) (out []Entry, err error) {
|
||||||
|
size -= 3
|
||||||
|
var curRead uint32
|
||||||
|
var h header
|
||||||
|
var de decEntry
|
||||||
|
for curRead < size {
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &h)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
curRead += 12
|
||||||
|
for i := uint32(0); i < h.Count+1 && curRead < size; i++ {
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &de)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nameTmp := make([]byte, de.NameSize+1)
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &nameTmp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
curRead += 8 + uint32(de.NameSize) + 1
|
||||||
|
out = append(out, Entry{
|
||||||
|
BlockStart: h.BlockStart,
|
||||||
|
Offset: de.Offset,
|
||||||
|
Name: string(nameTmp),
|
||||||
|
InodeType: de.InodeType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@ var (
|
|||||||
type Reader struct {
|
type Reader struct {
|
||||||
r io.ReaderAt
|
r io.ReaderAt
|
||||||
d decompress.Decompressor
|
d decompress.Decompressor
|
||||||
|
root *Directory
|
||||||
fragTable []fragEntry
|
fragTable []fragEntry
|
||||||
idTable []uint32
|
idTable []uint32
|
||||||
exportTable []uint64
|
exportTable []uint64
|
||||||
@@ -69,6 +70,10 @@ func NewReader(r io.ReaderAt) (rdr *Reader, err error) {
|
|||||||
default:
|
default:
|
||||||
return nil, errors.New("invalid compression type. possible corrupted archive")
|
return nil, errors.New("invalid compression type. possible corrupted archive")
|
||||||
}
|
}
|
||||||
|
rdr.root, err = rdr.directoryFromRef(rdr.sup.RootInodeRef, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(errors.New("failed to read root directory"), err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package squashfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/CalebQ42/squashfs/squashfs/inode"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
squashfsURL = "https://darkstorm.tech/files/LinuxPATest.sfs"
|
||||||
|
squashfsName = "LinuxPATest.sfs"
|
||||||
|
|
||||||
|
// filePath = "PortableApps/Notepad++Portable/App/DefaultData/Config/contextMenu.xml"
|
||||||
|
)
|
||||||
|
|
||||||
|
func preTest(dir string) (fil *os.File, err error) {
|
||||||
|
fil, err = os.Open(filepath.Join(dir, squashfsName))
|
||||||
|
if err != nil {
|
||||||
|
_, err = os.Open(dir)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = os.Mkdir(dir, 0755)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
os.Remove(filepath.Join(dir, squashfsName))
|
||||||
|
fil, err = os.Create(filepath.Join(dir, squashfsName))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var resp *http.Response
|
||||||
|
resp, err = http.DefaultClient.Get(squashfsURL)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = io.Copy(fil, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err = exec.LookPath("unsquashfs")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = exec.LookPath("mksquashfs")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReader(t *testing.T) {
|
||||||
|
tmpDir := "../testing"
|
||||||
|
fil, err := preTest(tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer fil.Close()
|
||||||
|
rdr, err := NewReader(fil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = checkDir(rdr, rdr.root)
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkDir(rdr *Reader, d *Directory) error {
|
||||||
|
for _, e := range d.Entries {
|
||||||
|
if e.InodeType == inode.Dir {
|
||||||
|
b, err := d.Open(rdr, e.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d, err := b.ToDir(rdr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = checkDir(rdr, d)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user