Skip to content

Commit

Permalink
Inode parsing and directory decoding
Browse files Browse the repository at this point in the history
  • Loading branch information
CalebQ42 committed Dec 23, 2023
1 parent 707391b commit 0574bbe
Show file tree
Hide file tree
Showing 7 changed files with 342 additions and 4 deletions.
5 changes: 5 additions & 0 deletions internal/data/fullreader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package data

type FullReader struct{

}
16 changes: 12 additions & 4 deletions internal/data/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,22 @@ import (
type Reader struct {
r io.Reader
d decompress.Decompressor
frag io.Reader
frag *Reader
sizes []uint32
dat []byte
curOffset uint16
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{
r: r,
d: d,
sizes: sizes,
}, nil
}
}

func (r *Reader) AddFrag(fragRdr io.Reader) {
func (r *Reader) AddFrag(fragRdr *Reader) {
r.frag = fragRdr
}

Expand Down Expand Up @@ -71,3 +71,11 @@ func (r *Reader) Read(b []byte) (int, error) {
}
return curRead, nil
}

func (r *Reader) Close() error {
if r.frag != nil {
return r.frag.Close()
}
r.dat = nil
return nil
}
87 changes: 87 additions & 0 deletions squashfs/base.go
Original file line number Diff line number Diff line change
@@ -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
}
86 changes: 86 additions & 0 deletions squashfs/directory.go
Original file line number Diff line number Diff line change
@@ -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:], "/"))
}
60 changes: 60 additions & 0 deletions squashfs/directory/directory.go
Original file line number Diff line number Diff line change
@@ -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
}
5 changes: 5 additions & 0 deletions squashfs/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ var (
type Reader struct {
r io.ReaderAt
d decompress.Decompressor
root *Directory
fragTable []fragEntry
idTable []uint32
exportTable []uint64
Expand Down Expand Up @@ -69,6 +70,10 @@ func NewReader(r io.ReaderAt) (rdr *Reader, err error) {
default:
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
}

Expand Down
87 changes: 87 additions & 0 deletions squashfs/reader_test.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 0574bbe

Please sign in to comment.