From 0574bbed39933d1e57a169b94a8cf49781b9a34b Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Sat, 23 Dec 2023 05:47:21 -0600 Subject: [PATCH] Inode parsing and directory decoding --- internal/data/fullreader.go | 5 ++ internal/data/reader.go | 16 ++++-- squashfs/base.go | 87 +++++++++++++++++++++++++++++++++ squashfs/directory.go | 86 ++++++++++++++++++++++++++++++++ squashfs/directory/directory.go | 60 +++++++++++++++++++++++ squashfs/reader.go | 5 ++ squashfs/reader_test.go | 87 +++++++++++++++++++++++++++++++++ 7 files changed, 342 insertions(+), 4 deletions(-) create mode 100644 internal/data/fullreader.go create mode 100644 squashfs/base.go create mode 100644 squashfs/directory.go create mode 100644 squashfs/directory/directory.go create mode 100644 squashfs/reader_test.go diff --git a/internal/data/fullreader.go b/internal/data/fullreader.go new file mode 100644 index 0000000..8e3a79f --- /dev/null +++ b/internal/data/fullreader.go @@ -0,0 +1,5 @@ +package data + +type FullReader struct{ + +} \ No newline at end of file diff --git a/internal/data/reader.go b/internal/data/reader.go index e4d5c16..82c18cc 100644 --- a/internal/data/reader.go +++ b/internal/data/reader.go @@ -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 } @@ -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 +} diff --git a/squashfs/base.go b/squashfs/base.go new file mode 100644 index 0000000..bd5f432 --- /dev/null +++ b/squashfs/base.go @@ -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 +} diff --git a/squashfs/directory.go b/squashfs/directory.go new file mode 100644 index 0000000..3a41e18 --- /dev/null +++ b/squashfs/directory.go @@ -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:], "/")) +} diff --git a/squashfs/directory/directory.go b/squashfs/directory/directory.go new file mode 100644 index 0000000..1464335 --- /dev/null +++ b/squashfs/directory/directory.go @@ -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 +} diff --git a/squashfs/reader.go b/squashfs/reader.go index c4d8fbe..b1c3a84 100644 --- a/squashfs/reader.go +++ b/squashfs/reader.go @@ -31,6 +31,7 @@ var ( type Reader struct { r io.ReaderAt d decompress.Decompressor + root *Directory fragTable []fragEntry idTable []uint32 exportTable []uint64 @@ -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 } diff --git a/squashfs/reader_test.go b/squashfs/reader_test.go new file mode 100644 index 0000000..bba97dd --- /dev/null +++ b/squashfs/reader_test.go @@ -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 +}