From b2a3920c1f394395f40d845e06ce22e592e8e6ca Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Sun, 24 Dec 2023 06:02:11 -0600 Subject: [PATCH] Finished? Everything seems to extract fine (though more testing is needed) --- internal/metadata/reader.go | 5 ++ squashfs/base.go | 67 ++++++++++++++++++----- squashfs/data/fullreader.go | 28 ++++++++-- squashfs/data/reader.go | 58 ++++++++++++-------- squashfs/directory.go | 25 +++++---- squashfs/fragment.go | 4 +- squashfs/inode.go | 5 +- squashfs/reader.go | 102 ++++++++++++++++++++++++------------ squashfs/reader_test.go | 73 +++++++++++++++++++++----- squashfs/superblock.go | 28 +++++----- 10 files changed, 277 insertions(+), 118 deletions(-) diff --git a/internal/metadata/reader.go b/internal/metadata/reader.go index 58baa51..b20d63c 100644 --- a/internal/metadata/reader.go +++ b/internal/metadata/reader.go @@ -60,3 +60,8 @@ func (r *Reader) Read(b []byte) (int, error) { } return curRead, nil } + +func (r *Reader) Close() error { + r.dat = nil + return nil +} diff --git a/squashfs/base.go b/squashfs/base.go index 10351a1..2bac355 100644 --- a/squashfs/base.go +++ b/squashfs/base.go @@ -4,7 +4,6 @@ import ( "errors" "io" - "github.com/CalebQ42/squashfs/internal/decompress" "github.com/CalebQ42/squashfs/internal/metadata" "github.com/CalebQ42/squashfs/internal/toreader" "github.com/CalebQ42/squashfs/squashfs/data" @@ -17,26 +16,35 @@ type Base struct { Name string } -func (r *Reader) baseFromInode(i *inode.Inode, name string) *Base { +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) +func (r *Reader) BaseFromEntry(e directory.Entry) (*Base, error) { + rdr := metadata.NewReader(toreader.NewReader(r.r, int64(r.Superblock.InodeTableStart)+int64(e.BlockStart)), r.d) + defer rdr.Close() rdr.Read(make([]byte, e.Offset)) - in, err := inode.Read(rdr, r.sup.BlockSize) + in, err := inode.Read(rdr, r.Superblock.BlockSize) if err != nil { return nil, err } return &Base{Inode: in, Name: e.Name}, nil } +func (r *Reader) BaseFromRef(ref uint64, name string) (*Base, error) { + in, err := r.inodeFromRef(ref) + if err != nil { + return nil, err + } + return &Base{Inode: in, Name: name}, nil +} + func (b *Base) Uid(r *Reader) (uint32, error) { - return r.id(b.Inode.UidInd) + return r.Id(b.Inode.UidInd) } func (b *Base) Gid(r *Reader) (uint32, error) { - return r.id(b.Inode.GidInd) + return r.Id(b.Inode.GidInd) } func (b *Base) IsDir() bool { @@ -44,7 +52,35 @@ func (b *Base) IsDir() bool { } func (b *Base) ToDir(r *Reader) (*Directory, error) { - return r.directoryFromInode(b.Inode, b.Name) + var blockStart uint32 + var size uint32 + var offset uint16 + switch b.Inode.Type { + case inode.Dir: + blockStart = b.Inode.Data.(inode.Directory).BlockStart + size = uint32(b.Inode.Data.(inode.Directory).Size) + offset = b.Inode.Data.(inode.Directory).Offset + case inode.EDir: + blockStart = b.Inode.Data.(inode.EDirectory).BlockStart + size = b.Inode.Data.(inode.EDirectory).Size + offset = b.Inode.Data.(inode.EDirectory).Offset + default: + return nil, errors.New("not a directory") + } + dirRdr := metadata.NewReader(toreader.NewReader(r.r, int64(r.Superblock.DirTableStart)+int64(blockStart)), r.d) + defer dirRdr.Close() + _, 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: *b, + Entries: entries, + }, nil } func (b *Base) IsRegular() bool { @@ -58,36 +94,39 @@ func (b *Base) GetRegFileReaders(r *Reader) (*data.Reader, *data.FullReader, err var blockStart uint64 var fragIndex uint32 var fragOffset uint32 + var fragSize uint64 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 + fragSize = uint64(b.Inode.Data.(inode.File).Size % r.Superblock.BlockSize) } 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 + fragSize = b.Inode.Data.(inode.EFile).Size % uint64(r.Superblock.BlockSize) } - frag := func(rdr io.ReaderAt, d decompress.Decompressor) (*data.Reader, error) { + frag := func() (io.Reader, error) { 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 := data.NewReader(toreader.NewReader(r.r, int64(ent.Start)), r.d, []uint32{ent.Size}, uint64(r.Superblock.BlockSize), r.Superblock.BlockSize) frag.Read(make([]byte, fragOffset)) - return frag, nil + return io.LimitReader(frag, int64(fragSize)), nil } - outRdr := data.NewReader(toreader.NewReader(r.r, int64(blockStart)), r.d, sizes) + outRdr := data.NewReader(toreader.NewReader(r.r, int64(blockStart)), r.d, sizes, fragSize, r.Superblock.BlockSize) if fragIndex != 0xffffffff { - f, err := frag(r.r, r.d) + f, err := frag() if err != nil { return nil, nil, err } outRdr.AddFrag(f) } - outFull := data.NewFullReader(r.r, int64(blockStart), r.d, sizes) + outFull := data.NewFullReader(r.r, int64(blockStart), r.d, sizes, fragSize, r.Superblock.BlockSize) if fragIndex != 0xffffffff { outFull.AddFrag(frag) } diff --git a/squashfs/data/fullreader.go b/squashfs/data/fullreader.go index 8c1d48b..d085e90 100644 --- a/squashfs/data/fullreader.go +++ b/squashfs/data/fullreader.go @@ -4,13 +4,14 @@ import ( "encoding/binary" "errors" "io" + "math" "sync" "github.com/CalebQ42/squashfs/internal/decompress" "github.com/CalebQ42/squashfs/internal/toreader" ) -type FragReaderConstructor func(io.ReaderAt, decompress.Decompressor) (*Reader, error) +type FragReaderConstructor func() (io.Reader, error) type FullReader struct { r io.ReaderAt @@ -19,16 +20,20 @@ type FullReader struct { retPool *sync.Pool sizes []uint32 initialOffset int64 + finalBlockSize uint64 + blockSize uint32 goroutineLimit uint16 } -func NewFullReader(r io.ReaderAt, initialOffset int64, d decompress.Decompressor, sizes []uint32) *FullReader { +func NewFullReader(r io.ReaderAt, initialOffset int64, d decompress.Decompressor, sizes []uint32, finalBlockSize uint64, blockSize uint32) *FullReader { return &FullReader{ r: r, d: d, sizes: sizes, initialOffset: initialOffset, goroutineLimit: 10, + finalBlockSize: finalBlockSize, + blockSize: blockSize, retPool: &sync.Pool{ New: func() any { return &retValue{} @@ -55,6 +60,16 @@ func (r *FullReader) process(index uint64, fileOffset uint64, retChan chan *retV ret := r.retPool.Get().(*retValue) ret.index = index realSize := r.sizes[index] &^ (1 << 24) + if realSize == 0 { + if index == uint64(len(r.sizes))-1 && r.frag == nil { + ret.data = make([]byte, r.finalBlockSize) + } else { + ret.data = make([]byte, r.blockSize) + } + ret.err = nil + retChan <- ret + return + } ret.data = make([]byte, realSize) ret.err = binary.Read(toreader.NewReader(r.r, int64(r.initialOffset)+int64(fileOffset)), binary.LittleEndian, &ret.data) if r.sizes[index] == realSize { @@ -71,7 +86,7 @@ func (r *FullReader) WriteTo(w io.Writer) (int64, error) { cache := make(map[uint64]*retValue) var errCache []error retChan := make(chan *retValue, r.goroutineLimit) - for i := uint64(0); i < uint64(len(r.sizes))/uint64(r.goroutineLimit); i++ { + for i := uint64(0); i < uint64(math.Ceil(float64(len(r.sizes))/float64(r.goroutineLimit))); i++ { toProcess = uint16(len(r.sizes)) - (uint16(i) * r.goroutineLimit) if toProcess > r.goroutineLimit { toProcess = r.goroutineLimit @@ -139,12 +154,17 @@ func (r *FullReader) WriteTo(w io.Writer) (int64, error) { } } if r.frag != nil { - rdr, err := r.frag(r.r, r.d) + rdr, err := r.frag() if err != nil { return wrote, err } wr, err := io.Copy(w, rdr) wrote += wr + if l, ok := rdr.(*io.LimitedReader); ok { + if cl, ok := l.R.(io.Closer); ok { + cl.Close() + } + } if err != nil { return wrote, err } diff --git a/squashfs/data/reader.go b/squashfs/data/reader.go index 82c18cc..922263d 100644 --- a/squashfs/data/reader.go +++ b/squashfs/data/reader.go @@ -8,24 +8,28 @@ import ( ) type Reader struct { - r io.Reader - d decompress.Decompressor - frag *Reader - sizes []uint32 - dat []byte - curOffset uint16 - curIndex uint64 + r io.Reader + d decompress.Decompressor + frag io.Reader + sizes []uint32 + dat []byte + curOffset int + curIndex uint64 + finalBlockSize uint64 + blockSize uint32 } -func NewReader(r io.Reader, d decompress.Decompressor, sizes []uint32) *Reader { +func NewReader(r io.Reader, d decompress.Decompressor, sizes []uint32, finalBlockSize uint64, blockSize uint32) *Reader { return &Reader{ - r: r, - d: d, - sizes: sizes, + r: r, + d: d, + sizes: sizes, + finalBlockSize: finalBlockSize, + blockSize: blockSize, } } -func (r *Reader) AddFrag(fragRdr *Reader) { +func (r *Reader) AddFrag(fragRdr io.Reader) { r.frag = fragRdr } @@ -33,13 +37,21 @@ 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 { + if r.curIndex == uint64(len(r.sizes)) && r.frag != nil { r.dat, err = io.ReadAll(r.frag) return err - } else if r.curIndex >= uint64(len(r.sizes))-1 { + } else if r.curIndex >= uint64(len(r.sizes)) { return io.EOF } - realSize := r.sizes[r.curIndex] &^ 0x8000 + realSize := r.sizes[r.curIndex] &^ (1 << 24) + if realSize == 0 { + if r.curIndex == uint64(len(r.sizes))-1 && r.frag == nil { + r.dat = make([]byte, r.finalBlockSize) + } else { + r.dat = make([]byte, r.blockSize) + } + return nil + } r.dat = make([]byte, realSize) err = binary.Read(r.r, binary.LittleEndian, &r.dat) if err != nil { @@ -56,17 +68,17 @@ 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 r.curOffset >= 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) + if toRead > len(r.dat)-r.curOffset { + toRead = len(r.dat) - r.curOffset } - copy(b[curRead:], r.dat[r.curOffset:int(r.curOffset)+toRead]) - r.curOffset += uint16(toRead) + toRead = copy(b[curRead:], r.dat[r.curOffset:r.curOffset+toRead]) + r.curOffset += toRead curRead += toRead } return curRead, nil @@ -74,7 +86,11 @@ func (r *Reader) Read(b []byte) (int, error) { func (r *Reader) Close() error { if r.frag != nil { - return r.frag.Close() + if l, ok := r.frag.(*io.LimitedReader); ok { + if cl, ok := l.R.(io.Closer); ok { + cl.Close() + } + } } r.dat = nil return nil diff --git a/squashfs/directory.go b/squashfs/directory.go index 3a41e18..9e7db15 100644 --- a/squashfs/directory.go +++ b/squashfs/directory.go @@ -2,6 +2,7 @@ package squashfs import ( "errors" + "fmt" "io/fs" "path/filepath" "slices" @@ -18,7 +19,12 @@ type Directory struct { Entries []directory.Entry } -func (r *Reader) directoryFromInode(i *inode.Inode, name string) (*Directory, error) { +func (r *Reader) directoryFromRef(ref uint64, name string) (*Directory, error) { + i, err := r.inodeFromRef(ref) + if err != nil { + fmt.Println("yo") + return nil, err + } var blockStart uint32 var size uint32 var offset uint16 @@ -34,8 +40,9 @@ func (r *Reader) directoryFromInode(i *inode.Inode, name string) (*Directory, er 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)) + dirRdr := metadata.NewReader(toreader.NewReader(r.r, int64(r.Superblock.DirTableStart)+int64(blockStart)), r.d) + defer dirRdr.Close() + _, err = dirRdr.Read(make([]byte, offset)) if err != nil { return nil, err } @@ -44,19 +51,11 @@ func (r *Reader) directoryFromInode(i *inode.Inode, name string) (*Directory, er return nil, err } return &Directory{ - Base: *r.baseFromInode(i, name), + 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 == "" { @@ -69,7 +68,7 @@ func (d *Directory) Open(r *Reader, path string) (*Base, error) { if !found { return nil, fs.ErrNotExist } - b, err := r.baseFromEntry(d.Entries[i]) + b, err := r.BaseFromEntry(d.Entries[i]) if err != nil { return nil, err } diff --git a/squashfs/fragment.go b/squashfs/fragment.go index 91003e0..ad103d9 100644 --- a/squashfs/fragment.go +++ b/squashfs/fragment.go @@ -1,7 +1,7 @@ package squashfs type fragEntry struct { - start uint64 - size uint32 + Start uint64 + Size uint32 _ uint32 } diff --git a/squashfs/inode.go b/squashfs/inode.go index 531c0e1..205c5ce 100644 --- a/squashfs/inode.go +++ b/squashfs/inode.go @@ -7,11 +7,12 @@ import ( ) func (r *Reader) inodeFromRef(ref uint64) (*inode.Inode, error) { - offset, meta := (ref>>16)+r.sup.InodeTableStart, ref&0xFFFF + offset, meta := (ref>>16)+r.Superblock.InodeTableStart, ref&0xFFFF rdr := metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d) + defer rdr.Close() _, err := rdr.Read(make([]byte, meta)) if err != nil { return nil, err } - return inode.Read(rdr, r.sup.BlockSize) + return inode.Read(rdr, r.Superblock.BlockSize) } diff --git a/squashfs/reader.go b/squashfs/reader.go index b1c3a84..3e8ec89 100644 --- a/squashfs/reader.go +++ b/squashfs/reader.go @@ -8,7 +8,9 @@ import ( "time" "github.com/CalebQ42/squashfs/internal/decompress" + "github.com/CalebQ42/squashfs/internal/metadata" "github.com/CalebQ42/squashfs/internal/toreader" + "github.com/CalebQ42/squashfs/squashfs/inode" ) // The types of compression supported by squashfs @@ -31,30 +33,30 @@ var ( type Reader struct { r io.ReaderAt d decompress.Decompressor - root *Directory + Root *Directory fragTable []fragEntry idTable []uint32 exportTable []uint64 - sup superblock + Superblock 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) + err = binary.Read(toreader.NewReader(r, 0), binary.LittleEndian, &rdr.Superblock) if err != nil { return nil, errors.Join(errors.New("failed to read superblock"), err) } - if !rdr.sup.checkMagic() { + if !rdr.Superblock.ValidMagic() { return nil, ErrorMagic } - if !rdr.sup.checkBlockLog() { + if !rdr.Superblock.ValidBlockLog() { return nil, ErrorLog } - if !rdr.sup.checkVersion() { + if !rdr.Superblock.ValidVersion() { return nil, ErrorVersion } - switch rdr.sup.CompType { + switch rdr.Superblock.CompType { case ZlibCompression: rdr.d = decompress.Zlib{} case LZMACompression: @@ -70,7 +72,7 @@ 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, "") + rdr.Root, err = rdr.directoryFromRef(rdr.Superblock.RootInodeRef, "") if err != nil { return nil, errors.Join(errors.New("failed to read root directory"), err) } @@ -79,36 +81,44 @@ func NewReader(r io.ReaderAt) (rdr *Reader, err error) { // Returns the last time the archive was modified. func (r *Reader) ModTime() time.Time { - return time.Unix(int64(r.sup.ModTime), 0) + return time.Unix(int64(r.Superblock.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) { +// 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 { + } else if i >= r.Superblock.IdCount { return 0, errors.New("id out of bounds") } // Populate the id table as needed - blockNum := uint16(math.Ceil(float64(i) / 2048)) + var blockNum uint32 + if i != 0 { // If i == 0, we go negatives causing issues with uint32s + blockNum = uint32(math.Ceil(float64(i)/2048)) - 1 + } else { + blockNum = 0 + } blocksRead := len(r.idTable) / 2048 - blocksToRead := int(blockNum) - blocksRead + blocksToRead := int(blockNum) - blocksRead + 1 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) + var rdr *metadata.Reader + for i := blocksRead; i < int(blocksRead)+blocksToRead; i++ { + err = binary.Read(toreader.NewReader(r.r, int64(r.Superblock.IdTableStart)+int64(8*i)), binary.LittleEndian, &offset) if err != nil { return 0, err } - idsToRead = r.sup.IdCount - uint16(len(r.idTable)) + idsToRead = r.Superblock.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) + rdr = metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d) + err = binary.Read(rdr, binary.LittleEndian, &idsTmp) + rdr.Close() if err != nil { return 0, err } @@ -121,29 +131,37 @@ func (r *Reader) id(i uint16) (uint32, error) { 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 { + } else if i >= r.Superblock.FragCount { return fragEntry{}, errors.New("fragment out of bounds") } // Populate the fragment table as needed - blockNum := uint32(math.Ceil(float64(i) / 512)) + var blockNum uint32 + if i != 0 { // If i == 0, we go negatives causing issues with uint32s + blockNum = uint32(math.Ceil(float64(i)/512)) - 1 + } else { + blockNum = 0 + } blocksRead := len(r.fragTable) / 512 - blocksToRead := int(blockNum) - blocksRead + blocksToRead := int(blockNum) - blocksRead + 1 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) + var rdr *metadata.Reader + for i := blocksRead; i < int(blocksRead)+blocksToRead; i++ { + err = binary.Read(toreader.NewReader(r.r, int64(r.Superblock.FragTableStart)+int64(8*i)), binary.LittleEndian, &offset) if err != nil { return fragEntry{}, err } - fragsToRead = r.sup.FragCount - uint32(len(r.fragTable)) + fragsToRead = r.Superblock.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) + rdr = metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d) + err = binary.Read(rdr, binary.LittleEndian, &fragsTmp) + rdr.Close() if err != nil { return fragEntry{}, err } @@ -154,34 +172,42 @@ func (r *Reader) fragEntry(i uint32) (fragEntry, error) { // 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() { + if !r.Superblock.Exportable() { return 0, ErrorNotExportable } if len(r.exportTable) > int(i) { return r.exportTable[i], nil - } else if i >= r.sup.InodeCount { + } else if i >= r.Superblock.InodeCount { return 0, errors.New("inode out of bounds") } - // Populate the export table as neede - blockNum := uint32(math.Ceil(float64(i) / 1024)) + // Populate the export table as needed + var blockNum uint32 + if i != 0 { // If i == 0, we go negatives causing issues with uint32s + blockNum = uint32(math.Ceil(float64(i)/1024)) - 1 + } else { + blockNum = 0 + } blocksRead := len(r.exportTable) / 1024 - blocksToRead := int(blockNum) - blocksRead + blocksToRead := int(blockNum) - blocksRead + 1 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) + var rdr *metadata.Reader + for i := blocksRead; i < int(blocksRead)+blocksToRead; i++ { + err = binary.Read(toreader.NewReader(r.r, int64(r.Superblock.ExportTableStart)+int64(8*i)), binary.LittleEndian, &offset) if err != nil { return 0, err } - refsToRead = r.sup.InodeCount - uint32(len(r.exportTable)) + refsToRead = r.Superblock.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) + rdr = metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d) + err = binary.Read(rdr, binary.LittleEndian, &refsTmp) + rdr.Close() if err != nil { return 0, err } @@ -189,3 +215,11 @@ func (r *Reader) inodeRef(i uint32) (uint64, error) { } return r.exportTable[i], nil } + +func (r *Reader) Inode(i uint32) (*inode.Inode, error) { + ref, err := r.inodeRef(i) + if err != nil { + return nil, err + } + return r.inodeFromRef(ref) +} diff --git a/squashfs/reader_test.go b/squashfs/reader_test.go index bba97dd..38bd4ad 100644 --- a/squashfs/reader_test.go +++ b/squashfs/reader_test.go @@ -1,21 +1,18 @@ package squashfs import ( + "fmt" "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) { @@ -62,26 +59,74 @@ func TestReader(t *testing.T) { if err != nil { t.Fatal(err) } - err = checkDir(rdr, rdr.root) + path := filepath.Join(tmpDir, "extractTest") + os.RemoveAll(path) + os.MkdirAll(path, 0777) + err = extractToDir(rdr, &rdr.Root.Base, path) 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) +var singleFile = "PortableApps/CPU-X/CPU-X-v4.2.0-x86_64.AppImage" + +func TestSingleFile(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) + } + path := filepath.Join(tmpDir, "extractTest") + os.RemoveAll(path) + os.MkdirAll(path, 0777) + b, err := rdr.Root.Open(rdr, singleFile) + if err != nil { + t.Fatal(err) + } + err = extractToDir(rdr, b, path) + t.Fatal(err) +} + +func extractToDir(rdr *Reader, b *Base, folder string) error { + path := filepath.Join(folder, b.Name) + if b.IsDir() { + d, err := b.ToDir(rdr) + if err != nil { + return err + } + err = os.MkdirAll(path, 0777) + if err != nil { + return err + } + var nestBast *Base + for _, e := range d.Entries { + nestBast, err = rdr.BaseFromEntry(e) if err != nil { return err } - d, err := b.ToDir(rdr) - if err != nil { - return err - } - err = checkDir(rdr, d) + err = extractToDir(rdr, nestBast, path) if err != nil { return err } } + } else if b.IsRegular() { + _, full, err := b.GetRegFileReaders(rdr) + if err != nil { + fmt.Println("yo", path) + return err + } + fil, err := os.Create(path) + if err != nil { + return err + } + _, err = full.WriteTo(fil) + if err != nil { + return err + } + fmt.Println("Successfully extracted file:", b.Name) } return nil } diff --git a/squashfs/superblock.go b/squashfs/superblock.go index 7916759..0b19dd3 100644 --- a/squashfs/superblock.go +++ b/squashfs/superblock.go @@ -24,57 +24,57 @@ type superblock struct { ExportTableStart uint64 } -func (s superblock) checkMagic() bool { +func (s superblock) ValidMagic() bool { return s.Magic == 0x73717368 } -func (s superblock) checkBlockLog() bool { +func (s superblock) ValidBlockLog() bool { return s.BlockLog == uint16(math.Log2(float64(s.BlockSize))) } -func (s superblock) checkVersion() bool { +func (s superblock) ValidVersion() bool { return s.VerMaj == 4 && s.VerMin == 0 } -func (s superblock) uncompressedInodes() bool { +func (s superblock) UncompressedInodes() bool { return s.Flags&0x1 == 0x1 } -func (s superblock) uncompressedData() bool { +func (s superblock) UncompressedData() bool { return s.Flags&0x2 == 0x2 } -func (s superblock) uncompressedFragments() bool { +func (s superblock) UncompressedFragments() bool { return s.Flags&0x8 == 0x8 } -func (s superblock) noFragments() bool { +func (s superblock) NoFragments() bool { return s.Flags&0x10 == 0x10 } -func (s superblock) alwaysFragment() bool { +func (s superblock) AlwaysFragment() bool { return s.Flags&0x20 == 0x20 } -func (s superblock) duplicates() bool { +func (s superblock) Duplicates() bool { return s.Flags&0x40 == 0x40 } -func (s superblock) exportable() bool { +func (s superblock) Exportable() bool { return s.Flags&0x80 == 0x80 } -func (s superblock) uncompressedXattrs() bool { +func (s superblock) UncompressedXattrs() bool { return s.Flags&0x100 == 0x100 } -func (s superblock) noXattrs() bool { +func (s superblock) NoXattrs() bool { return s.Flags&0x200 == 0x200 } -func (s superblock) compressionOptions() bool { +func (s superblock) CompressionOptions() bool { return s.Flags&0x400 == 0x400 } -func (s superblock) uncompressedIDs() bool { +func (s superblock) UncompressedIDs() bool { return s.Flags&0x800 == 0x800 }