Re-wrote a bunch to try to figure out why things weren't working.

Turned out I was reading if a block was compressed exactly opposite.
Started to work more on looking up dirs.
This commit is contained in:
Caleb Gardner
2020-11-16 14:56:19 -06:00
parent 06b188d53c
commit 7a2f9a87ba
25 changed files with 504 additions and 3363 deletions
+7 -5
View File
@@ -10,16 +10,18 @@ I am focusing purely on unsquashing before squashing.
# Working # Working
* Reading the header * Reading the header
* Reading data (slightly important :P)
* Reading inodes
* Reading directories
* Basic gzip compression (Shouldn't be too hard to implement other, but for right now, this works)
# Not Working (Yet). Roughly in order. # Not Working (Yet). Roughly in order.
* Actually reading the compressed data * Understanding the directory table. It's a bit weird TBH.
* Reading Inodes * Reading the UID, GUID, Xatt, Compression Options, Export, and Fragment tables.
* Reading the Directory structure
* Implement other compression types * Implement other compression types
* Squashing * Squashing
# Where I'm at # Where I'm at
* Redid a bunch. Implemented a custom reader that can read across blocks. * Re-redid a bunch to try to make sure I wasn't durping. After that didn't work, I tried to figure out why things wheren't working, then realized HOW I was durping.
* As of yet, doesn't seem to be reading things quite right (seems to be issue with encryption reading)
+167
View File
@@ -0,0 +1,167 @@
package squashfs
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
)
type metadata struct {
raw uint16
size uint16
compressed bool
}
type BlockReader struct {
s *Reader
offset int64
headers []*metadata
data []byte
readOffset int
}
func (s *Reader) NewBlockReader(offset int64) (*BlockReader, error) {
fmt.Println("Offset", offset)
var br BlockReader
br.s = s
br.offset = offset
err := br.parseMetadata()
if err != nil {
return nil, err
}
err = br.readNextDataBlock()
if err != nil {
return nil, err
}
return &br, nil
}
func (br *BlockReader) parseMetadata() error {
fmt.Println("meta offset", br.offset)
var raw uint16
err := binary.Read(io.NewSectionReader(br.s.r, br.offset, 2), binary.LittleEndian, &raw)
if err != nil {
return err
}
br.offset += 2
compressed := !(raw&0x8000 == 0x8000)
size := raw &^ 0x8000
br.headers = append(br.headers, &metadata{
raw: raw,
size: size,
compressed: compressed,
})
fmt.Println("compression", compressed)
return nil
}
func (br *BlockReader) readNextDataBlock() error {
meta := br.headers[len(br.headers)-1]
r := io.NewSectionReader(br.s.r, br.offset, int64(meta.size))
if meta.compressed {
byts, err := br.s.decompressor.Decompress(r)
if err != nil {
return err
}
br.offset += int64(meta.size)
br.data = append(br.data, byts...)
return nil
}
var buf bytes.Buffer
_, err := io.Copy(&buf, r)
if err != nil {
return err
}
br.offset += int64(meta.size)
br.data = append(br.data, buf.Bytes()...)
return nil
}
func (br *BlockReader) Read(p []byte) (int, error) {
if br.readOffset+len(p) < len(br.data) {
for i := 0; i < len(p); i++ {
p[i] = br.data[br.readOffset+i]
}
br.readOffset += len(p)
return len(p), nil
}
read := 0
for read < len(p) {
if read+br.readOffset == len(br.data) {
err := br.parseMetadata()
if err != nil {
br.readOffset += read
return read, err
}
err = br.readNextDataBlock()
if err != nil {
br.readOffset += read
return read, err
}
}
for ; read < len(p); read++ {
if br.readOffset+read < len(br.data) {
p[read] = br.data[br.readOffset+read]
} else {
break
}
}
}
br.readOffset += read
if read != len(p) {
return read, errors.New("Didn't read enough data")
}
fmt.Println("Read", p)
return read, nil
}
//Seek will seek to the specified location (if possible).
//When io.SeekCurrent or io.SeekStart is set, if seeking would put the offset beyond the current cached data, it will try to read the next data blocks to accomodate. On a failure it will seek to the end of the data.
//When io.SeekEnd is set, it wil seek reletive to the currently cached data.
func (br *BlockReader) Seek(offset int64, whence int) (int64, error) {
switch whence {
case io.SeekCurrent:
br.readOffset += int(offset)
for {
if br.readOffset < len(br.data) {
break
}
err := br.parseMetadata()
if err != nil {
br.readOffset = len(br.data)
return int64(br.readOffset), err
}
err = br.readNextDataBlock()
if err != nil {
br.readOffset = len(br.data)
return int64(br.readOffset), err
}
}
case io.SeekStart:
br.readOffset = int(offset)
for {
if br.readOffset < len(br.data) {
break
}
err := br.parseMetadata()
if err != nil {
br.readOffset = len(br.data)
return int64(br.readOffset), err
}
err = br.readNextDataBlock()
if err != nil {
br.readOffset = len(br.data)
return int64(br.readOffset), err
}
}
case io.SeekEnd:
br.readOffset = len(br.data) - int(offset)
if br.readOffset < 0 {
br.readOffset = 0
return int64(br.readOffset), errors.New("Trying to seek to a negative value")
}
}
return int64(br.readOffset), nil
}
-117
View File
@@ -1,117 +0,0 @@
package squashfs
import (
"encoding/binary"
"fmt"
"io"
"github.com/CalebQ42/GoSquashfs/bytereadwrite"
)
type MetadataHeader struct {
rawHeader uint16
Compressed bool
Size uint16
}
type BlockReader struct {
initalOffset int64
offset int64
squash *Squashfs
headers []MetadataHeader
dataCache []byte
dataOffset int64
}
//NewBlockReader creates a new BlockReader from a squashfs.Reader. Reads the first header and caches the first set of data.
func (s *Squashfs) NewBlockReader(offset int64) (*BlockReader, error) {
var br BlockReader
br.squash = s
br.initalOffset = offset
br.offset = offset
br.headers = make([]MetadataHeader, 0)
br.dataCache = make([]byte, 0)
err := br.parseNewBlock()
if err != nil {
fmt.Println("Problem creating BlockReader")
return nil, err
}
return &br, nil
}
func (br *BlockReader) parseNewBlock() error {
var header MetadataHeader
err := binary.Read(io.NewSectionReader(&br.squash.r, br.offset, 2), binary.LittleEndian, &header.rawHeader)
if err != nil {
fmt.Println("Error while reading the header ", len(br.headers), " in BlockReader")
return err
}
header.Compressed = (header.rawHeader&0x8000 == 0x8000)
header.Size = header.rawHeader &^ 0x8000
br.headers = append(br.headers, header)
br.offset += 2
sectionReader := io.NewSectionReader(&br.squash.r, br.offset, br.offset+int64(header.Size))
dataWriter := bytereadwrite.NewByteReaderWriter()
_, err = io.Copy(dataWriter, sectionReader)
br.offset += int64(header.Size)
if header.Compressed {
data, err := br.squash.compression.Decompress(dataWriter)
if err != nil {
fmt.Println("Error while reading the encrypted data block in header", len(br.headers))
return err
}
br.dataCache = append(br.dataCache, data...)
return nil
}
if err != nil {
fmt.Println("Error while reading uncompressed data in header", len(br.headers))
return err
}
br.dataCache = append(br.dataCache, dataWriter.GetBytes()...)
return nil
}
//Read reads data into p. If it reaches EOF, tries to read a new block and add it's data to the cache
func (br *BlockReader) Read(p []byte) (n int, err error) {
read := 0
for {
byter := bytereadwrite.NewByteReaderWriterFromBytes(br.dataCache[br.dataOffset:])
temp, err := byter.Read(p[read:])
br.dataOffset += int64(temp)
read += temp
if err == nil {
return read, nil
} else if err != io.EOF {
return read, err
}
if err == io.EOF {
err = br.parseNewBlock()
if err != nil {
fmt.Println("Error while reading a new block")
return read, err
}
}
}
}
//ReadAt reads data into p from the offset. If it reaches EOF, tries to read a new block and add it's data to the cache.
//
//Offset is reletive to the offset set on creation.
func (br *BlockReader) ReadAt(p []byte, offset int) (n int, err error) {
read := 0
for err == io.EOF {
byter := bytereadwrite.NewByteReaderWriterFromBytes(br.dataCache[offset+read:])
temp, inErr := byter.Read(p[read:])
err = inErr
read += temp
br.dataOffset += int64(temp)
if err == io.EOF {
inErr = br.parseNewBlock()
if inErr != nil {
fmt.Println("Error while reading a new block")
return read, inErr
}
}
}
return
}
-60
View File
@@ -1,60 +0,0 @@
package bytereadwrite
import "io"
//ByteReaderWriter allows you to read to and from a byte slice. When writing, it expands the slice to accomodate any data.
type ByteReaderWriter struct {
byts []byte
offset int
}
//NewByteReaderWriter creates a ByteReaderWriter with an internal byte slice of the given length.
func NewByteReaderWriter() *ByteReaderWriter {
return &ByteReaderWriter{
byts: make([]byte, 0),
offset: 0,
}
}
//NewByteReaderWriter creates a ByteReaderWriter with an internal byte slice of the given length.
func NewByteReaderWriterWithLength(length int) *ByteReaderWriter {
return &ByteReaderWriter{
byts: make([]byte, length),
offset: 0,
}
}
//NewByteReaderWriterFromBytes creates a new ByteReaderWriter initialized with the given bytes
func NewByteReaderWriterFromBytes(byts []byte) *ByteReaderWriter {
return &ByteReaderWriter{
byts: byts,
offset: 0,
}
}
//GetBytes return the underlyting byte slice of the readerwriter
func (bwr *ByteReaderWriter) GetBytes() []byte {
return bwr.byts
}
//Read reads the bytes.
func (bwr *ByteReaderWriter) Read(byt []byte) (int, error) {
if len(bwr.byts) < bwr.offset+len(byt) {
bytesWritten := len(bwr.byts) - bwr.offset
for i := 0; i < bytesWritten; i++ {
byt[i] = bwr.byts[i+bwr.offset]
}
return bytesWritten, io.EOF
}
for i := 0; i < len(byt); i++ {
byt[i] = bwr.byts[bwr.offset+i]
}
bwr.offset += len(byt)
return len(byt), nil
}
//Write writes to the end of the bytes. WILL expand to accept the incoming bytes.
func (bwr *ByteReaderWriter) Write(byts []byte) (int, error) {
bwr.byts = append(bwr.byts, byts...)
return len(byts), nil
}
+39
View File
@@ -0,0 +1,39 @@
package squashfs
import (
"bytes"
"compress/zlib"
"io"
)
type Decompressor interface {
Decompress(io.Reader) ([]byte, error)
DecompressCopy(*io.Writer, *io.SectionReader) error
}
type ZlibDecompressor struct{}
func (z *ZlibDecompressor) Decompress(r io.Reader) ([]byte, error) {
rdr, err := zlib.NewReader(r)
if err != nil {
return nil, err
}
var data bytes.Buffer
_, err = io.Copy(&data, rdr)
if err != nil {
return nil, err
}
return data.Bytes(), nil
}
func (z *ZlibDecompressor) DecompressCopy(w *io.Writer, r *io.SectionReader) error {
rdr, err := zlib.NewReader(r)
if err != nil {
return err
}
_, err = io.Copy(*w, rdr)
if err != nil {
return err
}
return err
}
-136
View File
@@ -1,136 +0,0 @@
package squashfs
import (
"compress/gzip"
"io"
"github.com/CalebQ42/GoSquashfs/bytereadwrite"
)
const (
gzipCompression = 1 + iota
lzmaCompression
lzoCompression
xzCompression
lz4Compression
zstdCompression
)
//TODO: implement for each type of Options
type CompressionOptions interface {
Decompress(io.Reader) ([]byte, error)
DecompressCopy(*io.Reader, *io.Writer) (int, error)
Compress(*io.Reader) ([]byte, error)
CompressCopy(*io.Reader, *io.Writer) (int, error)
}
//TODO: Allow creation of options for compression.
type gzipOptionsRaw struct {
CompressionLevel int32
WindowSize int16
Strategies int16
}
//GzipOptions is the options used for gzip compression. Backed by the raw format, with strategies parsed.
type GzipOptions struct {
CompressionOptions
raw *gzipOptionsRaw
DefaultStrategy bool
FilteredStrategy bool
HuffmanOnlyStrategy bool
RunLengthEncodedStrategy bool
FixedStretegy bool
}
func NewGzipOptions(raw gzipOptionsRaw) *GzipOptions {
//TODO: parse strategies
return &GzipOptions{
raw: &raw,
}
}
func (gzipOp *GzipOptions) Decompress(rdr io.Reader) ([]byte, error) {
gzipRdr, err := gzip.NewReader(rdr)
// defer gzipRdr.Close()
if err != nil {
return nil, err
}
bytrw := bytereadwrite.NewByteReaderWriter()
_, err = io.Copy(bytrw, gzipRdr)
if err != nil {
return nil, err
}
return bytrw.GetBytes(), nil
}
func (gzipOp *GzipOptions) DecompressCopy(rdr *io.Reader, wrt *io.Writer) (int, error) {
gzipRdr, err := gzip.NewReader(*rdr)
defer gzipRdr.Close()
if err != nil {
return 0, err
}
n, err := io.Copy(*wrt, gzipRdr)
return int(n), err
}
func (gzipOp *GzipOptions) Compress(rdr *io.Reader) ([]byte, error) {
bytWrt := bytereadwrite.NewByteReaderWriter()
gzipWrt := gzip.NewWriter(bytWrt) //TODO: allow setting level
defer gzipWrt.Close()
_, err := io.Copy(gzipWrt, *rdr)
if err != nil {
return bytWrt.GetBytes(), err
}
return bytWrt.GetBytes(), nil
}
func (gzipOp *GzipOptions) CompressCopy(rdr *io.Reader, wrt *io.Writer) (int, error) {
gzipWrt := gzip.NewWriter(*wrt) //TODO: allow setting level
defer gzipWrt.Close()
n, err := io.Copy(gzipWrt, *rdr)
return int(n), err
}
type xzOptionsRaw struct {
DictionarySize int32
ExecutableFilters int32
}
type XzOptions struct {
CompressionOptions //TODO: Remove
raw *xzOptionsRaw
Execx86 bool
ExecPower bool
Execa64 bool
ExecArm bool
ExecArmThumb bool
ExecSparc bool
}
func NewXzOption(raw xzOptionsRaw) XzOptions {
return XzOptions{
raw: &raw,
Execx86: raw.ExecutableFilters&0x1 == 0x1,
ExecPower: raw.ExecutableFilters&0x2 == 0x2,
Execa64: raw.ExecutableFilters&0x4 == 0x4,
ExecArm: raw.ExecutableFilters&0x8 == 0x8,
ExecArmThumb: raw.ExecutableFilters&0x10 == 0x10,
ExecSparc: raw.ExecutableFilters&0x20 == 0x20,
}
}
type lz4OptionsRaw struct {
Version int32
Flags int32
}
//ZstdOptions is the options set for zstdOptions
type ZstdOptions struct {
CompressionLevel int32 //CompressionLevel should be between 1 and 22
}
type lzoOptionsRaw struct {
Algorithm int32
CompressionLevel int32
}
+5 -5
View File
@@ -30,12 +30,12 @@ type Entry struct {
//NewEntry creates a new directory entry //NewEntry creates a new directory entry
func NewEntry(rdr io.Reader) (Entry, error) { func NewEntry(rdr io.Reader) (Entry, error) {
var entry Entry var entry Entry
err := binary.Read(rdr, binary.LittleEndian, entry.Init) err := binary.Read(rdr, binary.LittleEndian, &entry.Init)
if err != nil { if err != nil {
return entry, err return entry, err
} }
entry.Name = make([]byte, entry.Init.NameSize, entry.Init.NameSize) entry.Name = make([]byte, entry.Init.NameSize+1, entry.Init.NameSize+1)
err = binary.Read(rdr, binary.LittleEndian, entry.Name) err = binary.Read(rdr, binary.LittleEndian, &entry.Name)
return entry, err return entry, err
} }
@@ -54,9 +54,9 @@ func NewDirectory(rdr io.Reader) (*Directory, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
fmt.Println(hdr) hdr.Count++
headers := hdr.Count / 256 headers := hdr.Count / 256
if headers%256 > 0 { if hdr.Count%256 > 0 {
headers++ headers++
} }
headersRead := 1 headersRead := 1
+36 -19
View File
@@ -6,8 +6,25 @@ import (
"io" "io"
) )
//Common is the comon header for all inodes const (
type Common struct { BasicDirectoryType = iota + 1
BasicFileType
BasicSymlinkType
BasicBlockDeviceType
BasicCharDeviceType
BasicFifoType
BasicSocketType
ExtDirType
ExtFileType
ExtSymlinkType
ExtBlockDeviceType
ExtCharDeviceType
ExtFifoType
ExtSocketType
)
//Header is the common header for all inodes
type Header struct {
InodeType uint16 InodeType uint16
Permissions uint16 Permissions uint16
UID uint16 UID uint16
@@ -43,9 +60,9 @@ type ExtendedDirectory struct {
} }
//NewExtendedDirectory creates a new ExtendedDirectory //NewExtendedDirectory creates a new ExtendedDirectory
func NewExtendedDirectory(rdr *io.Reader) (*ExtendedDirectory, error) { func NewExtendedDirectory(rdr io.Reader) (*ExtendedDirectory, error) {
var inode ExtendedDirectory var inode ExtendedDirectory
err := binary.Read(*rdr, binary.LittleEndian, inode.Init) err := binary.Read(rdr, binary.LittleEndian, &inode.Init)
if err != nil { if err != nil {
return &inode, err return &inode, err
} }
@@ -76,14 +93,14 @@ type DirectoryIndex struct {
} }
//NewDirectoryIndex return a new DirectoryIndex //NewDirectoryIndex return a new DirectoryIndex
func NewDirectoryIndex(rdr *io.Reader) (DirectoryIndex, error) { func NewDirectoryIndex(rdr io.Reader) (DirectoryIndex, error) {
var index DirectoryIndex var index DirectoryIndex
err := binary.Read(*rdr, binary.LittleEndian, index.Init) err := binary.Read(rdr, binary.LittleEndian, &index.Init)
if err != nil { if err != nil {
return index, err return index, err
} }
index.Name = make([]byte, index.Init.NameSize, index.Init.NameSize) index.Name = make([]byte, index.Init.NameSize, index.Init.NameSize)
err = binary.Read(*rdr, binary.LittleEndian, index.Name) err = binary.Read(rdr, binary.LittleEndian, &index.Name)
return index, err return index, err
} }
@@ -102,9 +119,9 @@ type BasicFile struct {
} }
//NewBasicFile creates a new BasicFile //NewBasicFile creates a new BasicFile
func NewBasicFile(rdr *io.Reader, blockSize uint32) (*BasicFile, error) { func NewBasicFile(rdr io.Reader, blockSize uint32) (*BasicFile, error) {
var inode BasicFile var inode BasicFile
err := binary.Read(*rdr, binary.LittleEndian, inode.Init) err := binary.Read(rdr, binary.LittleEndian, &inode.Init)
if err != nil { if err != nil {
return &inode, err return &inode, err
} }
@@ -113,7 +130,7 @@ func NewBasicFile(rdr *io.Reader, blockSize uint32) (*BasicFile, error) {
blocks++ blocks++
} }
inode.BlockSizes = make([]uint32, blocks, blocks) inode.BlockSizes = make([]uint32, blocks, blocks)
err = binary.Read(*rdr, binary.LittleEndian, inode.BlockSizes) err = binary.Read(rdr, binary.LittleEndian, &inode.BlockSizes)
return &inode, err return &inode, err
} }
@@ -135,9 +152,9 @@ type ExtendedFile struct {
} }
//NewExtendedFile creates a new ExtendedFile //NewExtendedFile creates a new ExtendedFile
func NewExtendedFile(rdr *io.Reader, blockSize uint32) (ExtendedFile, error) { func NewExtendedFile(rdr io.Reader, blockSize uint32) (ExtendedFile, error) {
var inode ExtendedFile var inode ExtendedFile
err := binary.Read(*rdr, binary.LittleEndian, inode.Init) err := binary.Read(rdr, binary.LittleEndian, &inode.Init)
if err != nil { if err != nil {
return inode, err return inode, err
} }
@@ -146,7 +163,7 @@ func NewExtendedFile(rdr *io.Reader, blockSize uint32) (ExtendedFile, error) {
blocks++ blocks++
} }
inode.BlockSizes = make([]uint32, blocks, blocks) inode.BlockSizes = make([]uint32, blocks, blocks)
err = binary.Read(*rdr, binary.LittleEndian, inode.BlockSizes) err = binary.Read(rdr, binary.LittleEndian, &inode.BlockSizes)
return inode, err return inode, err
} }
@@ -163,14 +180,14 @@ type BasicSymlink struct {
} }
//NewBasicSymlink creates a new BasicSymlink //NewBasicSymlink creates a new BasicSymlink
func NewBasicSymlink(rdr *io.Reader) (BasicSymlink, error) { func NewBasicSymlink(rdr io.Reader) (BasicSymlink, error) {
var inode BasicSymlink var inode BasicSymlink
err := binary.Read(*rdr, binary.LittleEndian, inode.Init) err := binary.Read(rdr, binary.LittleEndian, &inode.Init)
if err != nil { if err != nil {
return inode, err return inode, err
} }
inode.targetPath = make([]byte, inode.Init.TargetPathSize, inode.Init.TargetPathSize) inode.targetPath = make([]byte, inode.Init.TargetPathSize, inode.Init.TargetPathSize)
err = binary.Read(*rdr, binary.LittleEndian, inode.targetPath) err = binary.Read(rdr, binary.LittleEndian, &inode.targetPath)
return inode, err return inode, err
} }
@@ -188,14 +205,14 @@ type ExtendedSymlink struct {
} }
//NewExtendedSymlink creates a new ExtendedSymlink //NewExtendedSymlink creates a new ExtendedSymlink
func NewExtendedSymlink(rdr *io.Reader) (ExtendedSymlink, error) { func NewExtendedSymlink(rdr io.Reader) (ExtendedSymlink, error) {
var inode ExtendedSymlink var inode ExtendedSymlink
err := binary.Read(*rdr, binary.LittleEndian, inode.Init) err := binary.Read(rdr, binary.LittleEndian, &inode.Init)
if err != nil { if err != nil {
return inode, err return inode, err
} }
inode.TargetPath = make([]uint8, inode.Init.TargetPathSize, inode.Init.TargetPathSize) inode.TargetPath = make([]uint8, inode.Init.TargetPathSize, inode.Init.TargetPathSize)
err = binary.Read(*rdr, binary.LittleEndian, &inode.XattrIndex) err = binary.Read(rdr, binary.LittleEndian, &inode.XattrIndex)
return inode, err return inode, err
} }
+125
View File
@@ -0,0 +1,125 @@
package inode
import (
"encoding/binary"
"io"
)
//Inode holds an inode. Header is the header that's common for all inodes.
//
//Info holds the actual Inode. Due to each inode type being a different type, it's store as an interface{}
type Inode struct {
Header Header
Type int //Type the inode type defined in the header. Here so it's easy to access
Info interface{} //Info is the parsed specific data. It's type is defined by Type.
}
//ProcessInode tries to read an inode from the BlockReader
func ProcessInode(br io.Reader, blockSize uint32) (Inode, error) {
var head Header
err := binary.Read(br, binary.LittleEndian, &head)
if err != nil {
return Inode{}, err
}
var info interface{}
switch head.InodeType {
case BasicDirectoryType:
var inode BasicDirectory
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return Inode{}, err
}
info = inode
case BasicFileType:
inode, err := NewBasicFile(br, blockSize)
if err != nil {
return Inode{}, err
}
info = inode
case BasicSymlinkType:
inode, err := NewBasicSymlink(br)
if err != nil {
return Inode{}, err
}
info = inode
case BasicBlockDeviceType:
var inode BasicDevice
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return Inode{}, err
}
info = inode
case BasicCharDeviceType:
var inode BasicDevice
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return Inode{}, err
}
info = inode
case BasicFifoType:
var inode BasicIPC
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return Inode{}, err
}
info = inode
case BasicSocketType:
var inode BasicIPC
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return Inode{}, err
}
info = inode
case ExtDirType:
inode, err := NewExtendedDirectory(br)
if err != nil {
return Inode{}, err
}
info = inode
case ExtFileType:
inode, err := NewExtendedFile(br, blockSize)
if err != nil {
return Inode{}, err
}
info = inode
case ExtSymlinkType:
inode, err := NewExtendedSymlink(br)
if err != nil {
return Inode{}, err
}
info = inode
case ExtBlockDeviceType:
var inode ExtendedDevice
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return Inode{}, err
}
info = inode
case ExtCharDeviceType:
var inode ExtendedDevice
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return Inode{}, err
}
info = inode
case ExtFifoType:
var inode ExtendedIPC
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return Inode{}, err
}
info = inode
case ExtSocketType:
var inode ExtendedIPC
err = binary.Read(br, binary.LittleEndian, &inode)
if err != nil {
return Inode{}, err
}
info = inode
}
return Inode{
Type: int(head.InodeType),
Header: head,
Info: info,
}, nil
}
-91
View File
@@ -1,91 +0,0 @@
package inode
import (
"encoding/binary"
"errors"
"io"
"strconv"
)
const (
//The inode type from inode.Common.InodeType
BasicDirectoryType = iota
BasicFileType
BasicSymlinkType
BasicBlockDeviceType
BasicCharDeviceType
BasicFifoType
BasicSocketType
ExtendedDirectoryType
ExtendedFileType
ExtendedSymlinkType
ExtendedBlockDeviceType
ExtendedCharDeviceType
ExtendedFifoType
ExtendedSocketType
)
//ProcessInode processes the next inode in the given reader
func ProcessInode(rdr io.Reader, blockSize uint32) (*Common, interface{}, error) {
var inodeHeader Common
err := binary.Read(rdr, binary.LittleEndian, &inodeHeader)
if err != nil {
return nil, nil, err
}
switch inodeHeader.InodeType {
case BasicDirectoryType:
var inode BasicDirectory
err = binary.Read(rdr, binary.LittleEndian, &inode)
return &inodeHeader, &inode, err
case BasicFileType:
inode, err := NewBasicFile(&rdr, blockSize)
return &inodeHeader, inode, err
case BasicSymlinkType:
inode, err := NewBasicSymlink(&rdr)
return &inodeHeader, inode, err
case BasicBlockDeviceType:
var inode BasicDevice
err = binary.Read(rdr, binary.LittleEndian, &inode)
return &inodeHeader, inode, err
case BasicCharDeviceType:
var inode BasicDevice
err = binary.Read(rdr, binary.LittleEndian, &inode)
return &inodeHeader, inode, err
case BasicFifoType:
var inode BasicIPC
err = binary.Read(rdr, binary.LittleEndian, &inode)
return &inodeHeader, inode, err
case BasicSocketType:
var inode BasicIPC
err = binary.Read(rdr, binary.LittleEndian, &inode)
return &inodeHeader, inode, err
case ExtendedDirectoryType:
inode, err := NewExtendedDirectory(&rdr)
return &inodeHeader, inode, err
case ExtendedFileType:
inode, err := NewExtendedFile(&rdr, blockSize)
return &inodeHeader, inode, err
case ExtendedSymlinkType:
inode, err := NewExtendedSymlink(&rdr)
return &inodeHeader, inode, err
case ExtendedBlockDeviceType:
var inode ExtendedDevice
err = binary.Read(rdr, binary.LittleEndian, &inode)
return &inodeHeader, inode, err
case ExtendedCharDeviceType:
var inode ExtendedDevice
err = binary.Read(rdr, binary.LittleEndian, &inode)
return &inodeHeader, inode, err
case ExtendedFifoType:
var inode ExtendedIPC
err = binary.Read(rdr, binary.LittleEndian, &inode)
return &inodeHeader, inode, err
case ExtendedSocketType:
var inode ExtendedIPC
err = binary.Read(rdr, binary.LittleEndian, &inode)
return &inodeHeader, inode, err
default:
return nil, nil, errors.New("Inode type is unrecognized: " + strconv.FormatInt(int64(inodeHeader.InodeType), 2))
}
}
-48
View File
@@ -1,48 +0,0 @@
package squashfs
import "io"
type byteWriterReader struct {
byts []byte
offset int
}
func newByteReadWrite(length int) *byteWriterReader {
return &byteWriterReader{
byts: make([]byte, length),
offset: 0,
}
}
func newByteReadWriteFromBytes(byts []byte) *byteWriterReader {
return &byteWriterReader{
byts: byts,
offset: 0,
}
}
func (bwr *byteWriterReader) getBytes() []byte {
return bwr.byts
}
//Read reads the bytes.
func (bwr *byteWriterReader) Read(byt []byte) (int, error) {
if len(bwr.byts) < bwr.offset+len(byt) {
bytesWritten := len(bwr.byts) - bwr.offset
for i := 0; i < bytesWritten; i++ {
byt[i] = bwr.byts[i+bwr.offset]
}
return bytesWritten, io.EOF
}
for i := 0; i < len(byt); i++ {
byt[i] = bwr.byts[bwr.offset+i]
}
bwr.offset += len(byt)
return len(byt), nil
}
//Write writes to the bytes. WILL expand to accept the incoming bytes.
func (bwr *byteWriterReader) Write(byts []byte) (int, error) {
bwr.byts = append(bwr.byts, byts...)
return len(byts), nil
}
-143
View File
@@ -1,143 +0,0 @@
package squashfs
import (
"compress/zlib"
"io"
"gopkg.in/src-d/go-git.v4/utils/ioutil"
)
const (
zlibCompression = 1 + iota
lzmaCompression
lzoCompression
xzCompression
lz4Compression
zstdCompression
)
//TODO: implement for each type of Options
type CompressionOptions interface {
Decompress(*io.SectionReader) ([]byte, error)
DecompressCopy(*io.Reader, *io.Writer) (int, error)
Compress(*io.SectionReader) ([]byte, error)
CompressCopy(*io.Reader, *io.Writer) (int, error)
Reader(io.Reader) (*io.ReadCloser, error)
}
//TODO: Allow creation of options for compression.
type zlibOptionsRaw struct {
compressionLevel int32
windowSize int16
strategies int16
}
//ZlibOptions is the options used for zlib compression. Backed by the raw format, with strategies parsed.
type ZlibOptions struct {
CompressionOptions
raw *zlibOptionsRaw
DefaultStrategy bool
FilteredStrategy bool
HuffmanOnlyStrategy bool
RunLengthEncodedStrategy bool
FixedStretegy bool
}
func NewZlibOptions(raw zlibOptionsRaw) *ZlibOptions {
//TODO: parse strategies
return &ZlibOptions{
raw: &raw,
}
}
func (zlibOp *ZlibOptions) Decompress(rdr *io.SectionReader) ([]byte, error) {
zlibRdr, err := zlib.NewReader(rdr)
defer zlibRdr.Close()
if err != nil {
return nil, err
}
bytrw := newByteReadWrite(0)
_, err = io.Copy(bytrw, zlibRdr)
if err != nil {
return bytrw.byts, err
}
return bytrw.byts, nil
}
func (zlibOp *ZlibOptions) DecompressCopy(rdr *io.Reader, wrt *io.Writer) (int, error) {
zlibRdr, err := zlib.NewReader(*rdr)
defer zlibRdr.Close()
if err != nil {
return 0, err
}
n, err := io.Copy(*wrt, zlibRdr)
return int(n), err
}
func (zlibOp *ZlibOptions) Compress(rdr *io.SectionReader) ([]byte, error) {
bytWrt := newByteReadWrite(0)
zlibWrt := zlib.NewWriter(bytWrt) //TODO: allow setting level
defer zlibWrt.Close()
_, err := io.Copy(zlibWrt, rdr)
if err != nil {
return bytWrt.byts, err
}
return bytWrt.byts, nil
}
func (zlibOp *ZlibOptions) CompressCopy(rdr *io.Reader, wrt *io.Writer) (int, error) {
zlibWrt := zlib.NewWriter(*wrt) //TODO: allow setting level
defer zlibWrt.Close()
n, err := io.Copy(zlibWrt, *rdr)
return int(n), err
}
func (zlibOp *ZlibOptions) Reader(rdr io.Reader) (*io.ReadCloser, error) {
read, err := zlib.NewReader(rdr)
redClo := ioutil.NewReadCloser(NewByteBufferedReader(read), read)
return &redClo, err
}
type xzOptionsRaw struct {
dictionarySize int32
executableFilters int32
}
type XzOptions struct {
CompressionOptions //TODO: Remove
raw *xzOptionsRaw
Execx86 bool
ExecPower bool
Execa64 bool
ExecArm bool
ExecArmThumb bool
ExecSparc bool
}
func NewXzOption(raw xzOptionsRaw) XzOptions {
return XzOptions{
raw: &raw,
Execx86: raw.executableFilters&0x1 == 0x1,
ExecPower: raw.executableFilters&0x2 == 0x2,
Execa64: raw.executableFilters&0x4 == 0x4,
ExecArm: raw.executableFilters&0x8 == 0x8,
ExecArmThumb: raw.executableFilters&0x10 == 0x10,
ExecSparc: raw.executableFilters&0x20 == 0x20,
}
}
type lz4OptionsRaw struct {
version int32
flags int32
}
//ZstdOptions is the options set for zstdOptions
type ZstdOptions struct {
CompressionLevel int32 //CompressionLevel should be between 1 and 22
}
type lzoOptionsRaw struct {
algorithm int32
compressionLevel int32
}
-59
View File
@@ -1,59 +0,0 @@
package squashfs
import (
"errors"
"io"
)
//TODO: possible custom reader because I'm havng some issuse...
//Reader is a reader which implements Reader, ReaderAt, and Seeker, all with an accesible offset (for reasons)
type Reader struct {
rdr io.ReaderAt
offset int64
}
//NewReader creates a squashfs.Reader from a io.ReaderAt
func NewReader(baseReader io.ReaderAt) Reader {
return Reader{
rdr: baseReader,
offset: 0,
}
}
//Read reads len(byt) into byt. Advances the internal offset
func (r *Reader) Read(byt []byte) (int, error) {
n, err := r.rdr.ReadAt(byt, r.offset)
r.offset += int64(n)
return n, err
}
//ReadAt wraps the internal io.ReadAt's function. DOES NOT advance the internal offset for Read function.
//Returns how many bytes were read.
func (r *Reader) ReadAt(byt []byte, offset int64) (int, error) {
return r.rdr.ReadAt(byt, offset)
}
//ReadAtFromOffset is the same as ReadAt, but the given offset is offset by the internal offset. DOES NOT advance the internal offset.
//Returns how many bytes were read.
func (r *Reader) ReadAtFromOffset(byt []byte, offset int64) (int, error) {
offset += r.offset
return r.rdr.ReadAt(byt, offset)
}
//Seek advances the internal offset. SeekEnd DOES NOT work
//Might not be necessary, but here just in case
func (r *Reader) Seek(offset int64, whence int) (int64, error) {
switch whence {
case io.SeekCurrent:
n, err := r.Read(make([]byte, offset))
return int64(n), err
case io.SeekStart:
r.offset = 0
n, err := r.Read(make([]byte, offset))
return int64(n), err
case io.SeekEnd:
return 0, errors.New("SeekEnd is NOT supported")
}
return 0, errors.New("incorrect whence")
}
-618
View File
@@ -1,618 +0,0 @@
package squashfs
// import (
// "bytes"
// "encoding/binary"
// "fmt"
// "io"
// "io/ioutil"
// "log"
// "os"
// "path/filepath"
// "strings"
// "sync"
// "syscall"
// "time"
// "golang.org/x/xerrors"
// )
// type Reader struct {
// r io.ReaderAt
// super superblock
// }
// func NewReader(r io.ReaderAt) (*Reader, error) {
// var sb superblock
// if err := binary.Read(io.NewSectionReader(r, 0, int64(binary.Size(sb))), binary.LittleEndian, &sb); err != nil {
// return nil, fmt.Errorf("reading superblock: %v", err)
// }
// if got, want := sb.Magic, uint32(magic); got != want {
// return nil, fmt.Errorf("invalid magic (not a SquashFS image?): got %x, want %x", got, want)
// }
// //log.Printf("superblock: %+v", sb)
// return &Reader{
// r: r,
// super: sb,
// }, nil
// }
// // TODO: maybe mmap instead of seeking?
// func (r *Reader) inode(i Inode) (blockoffset int64, offset int64) {
// return int64(i >> 16), int64(i & 0xFFFF)
// }
// type blockReader struct {
// r io.ReadSeeker
// lenBuf [2]byte
// buf []byte
// i int64
// off int64 // TODO: remove this once using mmap
// }
// func (br *blockReader) Read(p []byte) (n int, err error) {
// if br.i >= int64(len(br.buf)) {
// br.i = 0
// if _, err := io.ReadFull(br.r, br.lenBuf[:]); err != nil {
// return 0, err
// }
// l := binary.LittleEndian.Uint16(br.lenBuf[:])
// //uncompressed := l&0x8000 > 0
// l &= 0x7FFF
// //log.Printf("block of len %d, uncompressed: %v", l, uncompressed)
// if int(l) > cap(br.buf) {
// br.buf = make([]byte, int(l))
// }
// br.buf = br.buf[:l]
// if _, err := io.ReadFull(br.r, br.buf); err != nil {
// return 0, err
// }
// //log.Printf("(retry) n = %v, err = %v", n, err)
// }
// n = copy(p, br.buf[br.i:])
// br.i += int64(n)
// return n, err
// }
// func (br *blockReader) Close() error {
// blockReaderPool.Put(br)
// return nil
// }
// var blockReaderPool = sync.Pool{
// New: func() interface{} {
// return &blockReader{
// buf: make([]byte, 0, metadataBlockSize),
// }
// },
// }
// func (r *Reader) blockReader(blockoffset, offset int64) (io.ReadCloser, error) {
// //log.Printf("blockoffset %v (%x), offset %v (%x)", blockoffset, blockoffset, offset, offset)
// br := blockReaderPool.Get().(*blockReader)
// br.buf = br.buf[:0]
// br.r = io.NewSectionReader(r.r, blockoffset, 5500*1024*1024) // TODO: correct limit? can we use IntMax
// br.off = blockoffset
// br.i = 0
// //log.Printf("discarding %d bytes", offset)
// for n := int64(0); n < offset; {
// remaining := offset - n
// if remaining > metadataBlockSize {
// remaining = metadataBlockSize
// }
// nn, err := br.Read(br.buf[:remaining])
// if err != nil {
// return nil, err
// }
// n += int64(nn)
// }
// return br, nil
// }
// // TODO: define an inode type to use instead of interface{}?
// func (r *Reader) readInode(i Inode) (interface{}, error) {
// blockoffset, offset := r.inode(i)
// br, err := r.blockReader(r.super.InodeTableStart+blockoffset, offset)
// if err != nil {
// fmt.Println("oops! ", err)
// return nil, err
// }
// defer br.Close()
// fmt.Println("Hello There")
// // We need the inode type before we know which type to pass to binary.Read,
// // so we need to read it twice:
// var inodeType uint16
// typeBuf := bytes.NewBuffer(make([]byte, 0, binary.Size(inodeType)))
// if err := binary.Read(io.TeeReader(br, typeBuf), binary.LittleEndian, &inodeType); err != nil {
// return nil, err
// }
// br = ioutil.NopCloser(io.MultiReader(typeBuf, br))
// // var ih inodeHeader
// // if err := binary.Read(br, binary.LittleEndian, &ih); err != nil {
// // return err
// // }
// // //log.Printf("ih: %+v", ih)
// //log.Printf("inode type: %v", inodeType)
// switch inodeType {
// case dirType:
// var di dirInodeHeader
// if err := binary.Read(br, binary.LittleEndian, &di); err != nil {
// return nil, err
// }
// return di, nil
// case fileType:
// var ri regInodeHeader
// if err := binary.Read(br, binary.LittleEndian, &ri); err != nil {
// return nil, err
// }
// return ri, nil
// case symlinkType:
// var si symlinkInodeHeader
// if err := binary.Read(br, binary.LittleEndian, &si); err != nil {
// return nil, err
// }
// return si, nil
// case ldirType:
// var di ldirInodeHeader
// if err := binary.Read(br, binary.LittleEndian, &di); err != nil {
// return nil, err
// }
// return di, nil
// case lregType:
// var di lregInodeHeader
// if err := binary.Read(br, binary.LittleEndian, &di); err != nil {
// return nil, err
// }
// return di, nil
// // TODO:
// // blkdevType
// // chrdevType
// // fifoType
// // socketType
// // // The larger types are used for e.g. sparse files, xattrs, etc.
// // ldirType
// // lsymlinkType
// // lblkdevType
// // lchrdevType
// // lfifoType
// // lsocketType
// }
// return nil, fmt.Errorf("unknown inode type %d", inodeType)
// }
// func (r *Reader) RootInode() Inode {
// return r.super.RootInode
// }
// func (r *Reader) Stat(name string, i Inode) (os.FileInfo, error) {
// inode, err := r.readInode(i)
// if err != nil {
// return nil, err
// }
// //log.Printf("i %d, inode: %T, %+v", i, inode, inode)
// switch x := inode.(type) {
// case dirInodeHeader:
// return &FileInfo{
// name: name,
// size: int64(x.FileSize),
// mode: os.ModeDir | os.FileMode(x.Mode),
// modTime: time.Unix(int64(x.Mtime), 0),
// Inode: i,
// }, nil
// case ldirInodeHeader:
// return &FileInfo{
// name: name,
// size: int64(x.FileSize),
// mode: os.ModeDir | os.FileMode(x.Mode),
// modTime: time.Unix(int64(x.Mtime), 0),
// Inode: i,
// }, nil
// case regInodeHeader:
// mode := os.FileMode(x.Mode & 0777)
// if x.Mode&syscall.S_ISUID != 0 {
// mode |= os.ModeSetuid
// }
// return &FileInfo{
// name: name,
// size: int64(x.FileSize),
// mode: mode,
// modTime: time.Unix(int64(x.Mtime), 0),
// Inode: i,
// }, nil
// case lregInodeHeader:
// mode := os.FileMode(x.Mode & 0777)
// if x.Mode&syscall.S_ISUID != 0 {
// mode |= os.ModeSetuid
// }
// return &FileInfo{
// name: name,
// size: int64(x.FileSize),
// mode: mode,
// modTime: time.Unix(int64(x.Mtime), 0),
// Inode: i,
// }, nil
// case symlinkInodeHeader:
// return &FileInfo{
// name: name,
// size: int64(x.SymlinkSize),
// mode: os.ModeSymlink | os.FileMode(x.Mode),
// modTime: time.Unix(int64(x.Mtime), 0),
// Inode: i,
// }, nil
// }
// return nil, fmt.Errorf("unknown inode type %T", inode)
// }
// func (r *Reader) ReadLink(i Inode) (string, error) {
// // TODO: reduce code duplication with readInode
// blockoffset, offset := r.inode(i)
// br, err := r.blockReader(r.super.InodeTableStart+blockoffset, offset)
// if err != nil {
// return "", err
// }
// defer br.Close()
// // We need the inode type before we know which type to pass to binary.Read,
// // so we need to read it twice:
// var inodeType uint16
// typeBuf := bytes.NewBuffer(make([]byte, 0, binary.Size(inodeType)))
// if err := binary.Read(io.TeeReader(br, typeBuf), binary.LittleEndian, &inodeType); err != nil {
// return "", err
// }
// br = ioutil.NopCloser(io.MultiReader(typeBuf, br))
// if inodeType != symlinkType {
// return "", fmt.Errorf("invalid inode type: got %d instead of symlink", inodeType)
// }
// var si symlinkInodeHeader
// if err := binary.Read(br, binary.LittleEndian, &si); err != nil {
// return "", err
// }
// // Assumption: r.r is positioned right after the inode
// buf := make([]byte, si.SymlinkSize)
// if _, err := io.ReadFull(br, buf); err != nil {
// return "", err
// }
// return string(buf), nil
// }
// func (r *Reader) FileReader(inode Inode) (*io.SectionReader, error) {
// //log.Printf("Readfile(%v)", inode)
// i, err := r.readInode(inode)
// if err != nil {
// return nil, err
// }
// //log.Printf("i: %+v", i)
// // TODO(compression): read the blocksizes to read compressed blocks
// switch ri := i.(type) {
// case regInodeHeader:
// off := int64(ri.StartBlock) + int64(ri.Offset)
// return io.NewSectionReader(r.r, off, int64(ri.FileSize)), nil
// case lregInodeHeader:
// off := int64(ri.StartBlock) + int64(ri.Offset)
// return io.NewSectionReader(r.r, off, int64(ri.FileSize)), nil
// default:
// return nil, fmt.Errorf("BUG: non-file inode type")
// }
// }
// type FileNotFoundError struct {
// path string
// }
// func (e *FileNotFoundError) Error() string {
// return fmt.Sprintf("%q not found", e.path)
// }
// func (r *Reader) lookupComponent(parent Inode, component string) (Inode, error) {
// rfis, err := r.readdir(parent, false)
// if err != nil {
// return 0, err
// }
// for _, rfi := range rfis {
// if rfi.Name() == component {
// return rfi.Sys().(*FileInfo).Inode, nil
// }
// }
// return 0, &FileNotFoundError{path: component}
// }
// func (r *Reader) lookupPath(path string, followSymlink bool) (Inode, error) {
// inode := r.RootInode()
// parts := strings.Split(path, "/")
// for idx, part := range parts {
// var err error
// inode, err = r.lookupComponent(inode, part)
// if err != nil {
// if _, ok := err.(*FileNotFoundError); ok {
// return 0, &FileNotFoundError{path: path}
// }
// return 0, err
// }
// if !followSymlink {
// continue
// }
// i, err := r.readInode(inode)
// if err != nil {
// return 0, xerrors.Errorf("Stat(%d): %v", inode, err)
// }
// if _, ok := i.(symlinkInodeHeader); ok {
// target, err := r.ReadLink(inode)
// if err != nil {
// return 0, err
// }
// //log.Printf("component %q (full: %q) resolved to %q", part, parts[:idx+1], target)
// target = filepath.Clean(filepath.Join(append(parts[:idx] /* parent */, target)...))
// //log.Printf("-> %s", target)
// i, err := r.LookupPath(target)
// if err != nil {
// return 0, err
// }
// inode = i
// }
// }
// return inode, nil
// }
// func (r *Reader) LookupPath(path string) (Inode, error) {
// return r.lookupPath(path, true)
// }
// // LlookupPath is like LookupPath, but does not follow symbolic links, i.e. will
// // instead return the inode of the link itself.
// func (r *Reader) LlookupPath(path string) (Inode, error) {
// return r.lookupPath(path, false)
// }
// func (r *Reader) Readdir(dirInode Inode) ([]os.FileInfo, error) {
// return r.readdir(dirInode, true)
// }
// // Like Readdir, but does not call Stat on each file. The returned FileInfo
// // structs will still have a filled in Name, partly filled in Mode, and filled
// // in Inode.
// func (r *Reader) ReaddirNoStat(dirInode Inode) ([]os.FileInfo, error) {
// return r.readdir(dirInode, false)
// }
// var nameBufPool = sync.Pool{
// New: func() interface{} {
// return &bytes.Buffer{}
// },
// }
// func (r *Reader) readdir(dirInode Inode, stat bool) ([]os.FileInfo, error) {
// //log.Printf("Readdir(%v (%x))", dirInode, dirInode)
// i, err := r.readInode(dirInode)
// fmt.Println("Yodle")
// if err != nil {
// return nil, err
// }
// var (
// startBlock int64
// fileSize int64
// offset int64
// )
// switch x := i.(type) {
// case dirInodeHeader:
// startBlock = int64(x.StartBlock)
// fileSize = int64(x.FileSize)
// offset = int64(x.Offset)
// case ldirInodeHeader:
// startBlock = int64(x.StartBlock)
// fileSize = int64(x.FileSize)
// offset = int64(x.Offset)
// default:
// return nil, fmt.Errorf("unknown directory inode type %T", i)
// }
// br, err := r.blockReader(r.super.DirectoryTableStart+startBlock, offset)
// if err != nil {
// return nil, err
// }
// defer br.Close()
// // See also https://elixir.bootlin.com/linux/v4.18.9/source/fs/squashfs/dir.c#L63
// limit := fileSize - int64(len(".")) - int64(len(".."))
// br = ioutil.NopCloser(io.LimitReader(br, limit))
// var fis []os.FileInfo
// var dh dirHeader
// var de dirEntry
// var dhBuf [12]byte
// var deBuf [8]byte
// nameBuf := nameBufPool.Get().(*bytes.Buffer)
// defer nameBufPool.Put(nameBuf)
// for {
// if _, err := io.ReadFull(br, dhBuf[:]); err != nil {
// if err == io.EOF {
// return fis, nil
// }
// return nil, err
// }
// dh.Unmarshal(dhBuf[:])
// dh.Count++ // SquashFS stores count-1
// //log.Printf("dh: %+v", dh)
// for i := 0; i < int(dh.Count); i++ {
// if _, err := io.ReadFull(br, deBuf[:]); err != nil {
// return nil, err
// }
// de.Unmarshal(deBuf[:])
// de.Size++ // SquashFS stores size-1
// //log.Printf("de: %+v", de)
// nameBuf.Reset()
// nameBuf.Grow(int(de.Size))
// nb := nameBuf.Bytes()[:de.Size]
// if _, err := io.ReadFull(br, nb); err != nil {
// return nil, err
// }
// name := string(nb)
// //log.Printf("name: %q", string(name))
// var fi os.FileInfo
// if stat {
// var err error
// fi, err = r.Stat(name, Inode(int64(dh.StartBlock)<<16|int64(de.Offset)))
// if err != nil {
// return nil, err
// }
// } else {
// ffi := &FileInfo{
// name: name,
// Inode: Inode(int64(dh.StartBlock)<<16 | int64(de.Offset)),
// }
// switch de.EntryType {
// case dirType, ldirType:
// ffi.mode |= os.ModeDir
// case symlinkType, lsymlinkType:
// ffi.mode |= os.ModeSymlink
// }
// fi = ffi
// }
// fis = append(fis, fi)
// }
// }
// return fis, nil
// }
// type FileInfo struct {
// name string
// size int64
// mode os.FileMode
// modTime time.Time
// Inode Inode
// }
// func (fi *FileInfo) Name() string { return fi.name }
// func (fi *FileInfo) Size() int64 { return fi.size }
// func (fi *FileInfo) Mode() os.FileMode { return fi.mode }
// func (fi *FileInfo) IsDir() bool { return fi.mode.IsDir() }
// func (fi *FileInfo) ModTime() time.Time { return fi.modTime }
// func (fi *FileInfo) Sys() interface{} { return fi }
// func (r *Reader) readXattr(tableHeader xattrTableHeader, id xattrId) (*Xattr, error) {
// blockoffset, offset := r.inode(Inode(id.Xattr))
// br, err := r.blockReader(int64(tableHeader.XattrTableStart)+blockoffset, offset)
// if err != nil {
// return nil, err
// }
// defer br.Close()
// var typ, nameSize uint16
// if err := binary.Read(br, binary.LittleEndian, &typ); err != nil {
// return nil, err
// }
// if err := binary.Read(br, binary.LittleEndian, &nameSize); err != nil {
// return nil, err
// }
// log.Printf("type = %v, nameSize = %v", typ, nameSize)
// name := make([]byte, nameSize)
// if _, err := io.ReadFull(br, name); err != nil {
// return nil, err
// }
// log.Printf("name = %v", string(name))
// var valSize uint32
// if err := binary.Read(br, binary.LittleEndian, &valSize); err != nil {
// return nil, err
// }
// val := make([]byte, valSize)
// if _, err := io.ReadFull(br, val); err != nil {
// return nil, err
// }
// log.Printf("val = %x", val)
// return &Xattr{
// Type: typ,
// FullName: xattrPrefix[int(typ)] + string(name),
// Value: val,
// }, nil
// }
// func (r *Reader) ReadXattrs(inode Inode) ([]Xattr, error) {
// //log.Printf("Readdir(%v (%x))", dirInode, dirInode)
// i, err := r.readInode(inode)
// if err != nil {
// return nil, err
// }
// var xid uint32
// switch x := i.(type) {
// case regInodeHeader,
// dirInodeHeader,
// ldirInodeHeader,
// symlinkInodeHeader:
// return nil, nil // no extended attributes
// case lregInodeHeader:
// if x.Xattr == invalidXattr {
// return nil, nil // file has no extended attributes
// }
// xid = x.Xattr
// default:
// return nil, fmt.Errorf("unknown inode type %T", i)
// }
// const idEntriesPerBlock = 512 // = 8192 / 16 /* sizeof(xattrId) */
// block := xid / idEntriesPerBlock
// offset := (xid % idEntriesPerBlock) * 16
// log.Printf("xattr id %d, block %d, offset %d", xid, block, offset)
// log.Printf("r.super.XattrIdTableStart = 0x%x, r.super.XattrIdTableStart = %v", r.super.XattrIdTableStart, r.super.XattrIdTableStart)
// br := ioutil.NopCloser(io.NewSectionReader(r.r, r.super.XattrIdTableStart, int64(16 /* sizeof(xattrTableHeader) */ +(block+1)*4 /* sizeof(uint32) */)))
// var tableHeader xattrTableHeader
// if err := binary.Read(br, binary.LittleEndian, &tableHeader); err != nil {
// return nil, err
// }
// // index starts here
// if _, err := io.CopyN(ioutil.Discard, br, int64(block*4 /* sizeof(uint32) */)); err != nil {
// return nil, err
// }
// var blockOffset uint32
// if err := binary.Read(br, binary.LittleEndian, &blockOffset); err != nil {
// return nil, err
// }
// log.Printf("blockOffset = 0x%x (%d)", blockOffset, blockOffset)
// br, err = r.blockReader(int64(blockOffset), int64(offset))
// if err != nil {
// return nil, err
// }
// defer br.Close()
// var id xattrId
// if err := binary.Read(br, binary.LittleEndian, &id); err != nil {
// return nil, err
// }
// log.Printf("id: %+v", id)
// log.Printf("tableHeader: %+v (start 0x%x)", tableHeader, tableHeader.XattrTableStart)
// var xattrs []Xattr
// for i := 0; i < int(id.Count); i++ {
// xattr, err := r.readXattr(tableHeader, id)
// if err != nil {
// return nil, err
// }
// xattrs = append(xattrs, *xattr)
// }
// return xattrs, nil
// }
-373
View File
@@ -1,373 +0,0 @@
package squashfs
// import (
// "crypto/md5"
// "fmt"
// "io"
// "os"
// "testing"
// "time"
// "github.com/google/go-cmp/cmp"
// )
// func cmpFileInfo(got os.FileInfo, want FileInfo) error {
// if got, want := got.Name(), want.name; got != want {
// return fmt.Errorf("unexpected file name: got %q, want %q", got, want)
// }
// if got, want := got.Size(), want.size; got != want {
// return fmt.Errorf("unexpected size: got %d, want %d", got, want)
// }
// if got, want := got.IsDir(), want.mode.IsDir(); got != want {
// return fmt.Errorf("IsDir: got %v, want %v", got, want)
// }
// // TODO: re-enable when its no longer just a change detector
// // if got, want := got.ModTime(), want.modTime; !got.Equal(want) {
// // return fmt.Errorf("IsDir: got %v, want %v", got, want)
// // }
// return nil
// }
// func TestReaddir(t *testing.T) {
// t.Parallel()
// // TODO: ship testdata files generated by mksquashfs
// pwd, err := os.Getwd()
// if err != nil {
// t.Fatal(err)
// }
// fmt.Println(pwd + "/testdata/testing.squashfs")
// f, err := os.Open(pwd + "/testdata/testing.squashfs")
// if err != nil {
// t.Fatal(err)
// }
// defer f.Close()
// rd, err := NewReader(f)
// if err != nil {
// t.Fatal(err)
// }
// fis, err := rd.Readdir(rd.RootInode())
// if err != nil {
// t.Fatal(err)
// }
// for _, fi := range fis {
// fmt.Println(fi.Name())
// }
// rdr, err := rd.FileReader(fis[0].Sys().(*FileInfo).Inode)
// if err != nil {
// t.Fatal(err)
// }
// err = os.Remove(pwd + "/testdata/Magisk.zip")
// if !os.IsNotExist(err) && err != nil {
// t.Fatal(err)
// }
// // rdrzlib, err := zlib.NewReader(rdr)
// magFil, err := os.Create(pwd + "/testdata/Magisk.zip")
// io.Copy(magFil, rdr)
// if got, want := len(fis), 3; got != want {
// t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want)
// }
// if err := cmpFileInfo(fis[0], FileInfo{
// name: "bin",
// size: 26,
// mode: 0555 | os.ModeDir,
// modTime: time.Unix(1581275131, 0), // stat -c %Y /ro/ack-amd64-2.24/bin
// }); err != nil {
// t.Fatal(err)
// }
// if err := cmpFileInfo(fis[1], FileInfo{
// name: "lib",
// size: 3,
// mode: 0555 | os.ModeDir,
// modTime: time.Unix(1581275131, 0), // stat -c %Y /ro/ack-amd64-2.24/lib
// }); err != nil {
// t.Fatal(err)
// }
// if err := cmpFileInfo(fis[2], FileInfo{
// name: "out",
// size: 48,
// mode: 0555 | os.ModeDir,
// modTime: time.Unix(1581275130, 0), // stat -c %Y /ro/ack-amd64-2.24/out
// }); err != nil {
// t.Fatal(err)
// }
// fis, err = rd.Readdir(fis[0].Sys().(*FileInfo).Inode)
// if err != nil {
// t.Fatal(err)
// }
// if got, want := len(fis), 1; got != want {
// t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want)
// }
// if err := cmpFileInfo(fis[0], FileInfo{
// name: "ack",
// size: 38400,
// mode: 0755,
// modTime: time.Unix(1581275131, 0), // stat -c %Y /ro/ack-amd64-2.24/bin/ack
// }); err != nil {
// t.Fatal(err)
// }
// }
// // TestReaddirSmoke is a smoke-test, reading the root directories of SquashFS
// // images which are known to trigger code paths which were buggy.
// func TestReaddirSmoke(t *testing.T) {
// t.Parallel()
// for _, fn := range []string{
// // bash exercises the code path where an inode is split across metadata
// // blocks.
// "/home/michael/distri/_build/distri/pkg/bash-amd64-5.0-4.squashfs",
// // cmake exercises the code path where the root directory entries are
// // located outside of the first block.
// "/home/michael/distri/_build/distri/pkg/cmake-amd64-3.12.4-8.squashfs",
// } {
// // TODO: ship testdata files generated by mksquashfs
// f, err := os.Open(fn)
// if err != nil {
// t.Fatal(err)
// }
// defer f.Close()
// rd, err := NewReader(f)
// if err != nil {
// t.Fatal(err)
// }
// fis, err := rd.Readdir(rd.RootInode())
// if err != nil {
// t.Fatal(err)
// }
// if got, want := len(fis), 4; got != want {
// t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want)
// }
// }
// }
// func TestReaddirEmpty(t *testing.T) {
// t.Parallel()
// // TODO: ship testdata files generated by mksquashfs
// f, err := os.Open("/home/michael/distri/_build/distri/pkg/zlib-amd64-1.2.11-4.squashfs")
// if err != nil {
// t.Fatal(err)
// }
// defer f.Close()
// rd, err := NewReader(f)
// if err != nil {
// t.Fatal(err)
// }
// fis, err := rd.Readdir(rd.RootInode())
// if err != nil {
// t.Fatal(err)
// }
// if got, want := len(fis), 4; got != want {
// t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want)
// }
// if err := cmpFileInfo(fis[0], FileInfo{
// name: "bin",
// size: 3,
// mode: 0555 | os.ModeDir,
// modTime: time.Unix(1583085224, 0), // stat -c %Y /ro/zlib-amd64-1.2.11/bin
// }); err != nil {
// t.Fatal(err)
// }
// fis, err = rd.Readdir(fis[0].Sys().(*FileInfo).Inode)
// if err != nil {
// t.Fatal(err)
// }
// if got, want := len(fis), 0; got != want {
// t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want)
// }
// }
// func TestReaddirSymlink(t *testing.T) {
// t.Parallel()
// // TODO: ship testdata files generated by mksquashfs
// f, err := os.Open("/home/michael/distri/_build/distri/pkg/zlib-amd64-1.2.11-4.squashfs")
// if err != nil {
// t.Fatal(err)
// }
// defer f.Close()
// rd, err := NewReader(f)
// if err != nil {
// t.Fatal(err)
// }
// fis, err := rd.Readdir(rd.RootInode())
// if err != nil {
// t.Fatal(err)
// }
// if got, want := len(fis), 4; got != want {
// t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want)
// }
// if err := cmpFileInfo(fis[3], FileInfo{
// name: "out",
// size: 54,
// mode: 0555 | os.ModeDir,
// modTime: time.Unix(1583085224, 0), // stat -c %Y /ro/zlib-amd64-1.2.11/out
// }); err != nil {
// t.Fatal(err)
// }
// fis, err = rd.Readdir(fis[3].Sys().(*FileInfo).Inode)
// if err != nil {
// t.Fatal(err)
// }
// if got, want := len(fis), 3; got != want {
// t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want)
// }
// if err := cmpFileInfo(fis[1], FileInfo{
// name: "lib",
// size: 83,
// mode: 0555 | os.ModeDir,
// modTime: time.Unix(1583085224, 0), // stat -c %Y /ro/zlib-amd64-1.2.11/out/lib
// }); err != nil {
// t.Fatal(err)
// }
// fis, err = rd.Readdir(fis[1].Sys().(*FileInfo).Inode)
// if err != nil {
// t.Fatal(err)
// }
// if got, want := len(fis), 4; got != want {
// t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want)
// }
// if err := cmpFileInfo(fis[1], FileInfo{
// name: "libz.so",
// size: 9,
// mode: 0555 | os.ModeSymlink,
// modTime: time.Unix(1583085223, 0), // stat -c %Y /ro/zlib-amd64-1.2.11/out/lib/libz.so
// }); err != nil {
// t.Fatal(err)
// }
// // TODO: readlink
// target, err := rd.ReadLink(fis[1].Sys().(*FileInfo).Inode)
// if err != nil {
// t.Fatal(err)
// }
// if got, want := target, "libz.so.1"; got != want {
// t.Fatalf("ReadLink(libz.so): got %q, want %q", got, want)
// }
// }
// func TestReadfile(t *testing.T) {
// t.Parallel()
// // TODO: ship testdata files generated by mksquashfs
// f, err := os.Open("/home/michael/distri/_build/distri/pkg/ack-amd64-3.3.1-7.squashfs")
// if err != nil {
// t.Fatal(err)
// }
// defer f.Close()
// rd, err := NewReader(f)
// if err != nil {
// t.Fatal(err)
// }
// fis, err := rd.Readdir(rd.RootInode())
// if err != nil {
// t.Fatal(err)
// }
// if got, want := len(fis), 3; got != want {
// t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want)
// }
// fis, err = rd.Readdir(fis[0].Sys().(*FileInfo).Inode)
// if err != nil {
// t.Fatal(err)
// }
// if got, want := len(fis), 1; got != want {
// t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want)
// }
// r, err := rd.FileReader(fis[0].Sys().(*FileInfo).Inode)
// if err != nil {
// t.Fatal(err)
// }
// for i := 0; i < 2; i++ {
// if _, err := r.Seek(0, io.SeekStart); err != nil {
// t.Fatal(err)
// }
// h := md5.New()
// if _, err := io.Copy(h, r); err != nil {
// t.Fatal(err)
// }
// sum := fmt.Sprintf("%x", h.Sum(nil))
// if got, want := sum, "c6c9b5d4d2a49f1b8b5e501f0f827a5c"; got != want {
// t.Fatalf("md5(bin/ack): got %s, want %s", got, want)
// }
// }
// }
// // TODO: add test exercising ldirInodeHeader, e.g. '/mnt/loop/ca-certificates-3.39/buildoutput/etc/ssl'
// func TestReadXattr(t *testing.T) {
// t.Parallel()
// // TODO: generate a smaller version of this file
// f, err := os.Open("testdata/xattr.squashfs")
// if err != nil {
// t.Fatal(err)
// }
// defer f.Close()
// rd, err := NewReader(f)
// if err != nil {
// t.Fatal(err)
// }
// for _, tt := range []struct {
// Path string
// Want []Xattr
// }{
// {
// Path: "mtr-packet",
// Want: []Xattr{
// {
// Type: XattrTypeSecurity,
// FullName: "security.capability",
// Value: []byte{1, 0, 0, 2, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
// },
// },
// {
// Path: "gnome-keyring-daemon",
// Want: []Xattr{
// {
// Type: XattrTypeSecurity,
// FullName: "security.capability",
// Value: []byte{1, 0, 0, 2, 0, 0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
// },
// },
// } {
// inode, err := rd.LookupPath(tt.Path)
// if err != nil {
// t.Fatal(err)
// }
// xattrs, err := rd.ReadXattrs(inode)
// if err != nil {
// t.Fatalf("ReadXattrs(%v): %v", inode, err)
// }
// if diff := cmp.Diff(tt.Want, xattrs); diff != "" {
// t.Fatalf("unexpected ReadXattrs result: diff (-want +got):\n%s", diff)
// }
// }
// }
-181
View File
@@ -1,181 +0,0 @@
package squashfs
import (
"encoding/binary"
"errors"
"fmt"
"io"
"github.com/CalebQ42/GoSquashfs/old/internal/inode"
)
var (
//ErrNotMagical happens when making a new Squashfs and it doesn't have the magic number
ErrNotMagical = errors.New("Not Magical")
)
//Squashfs is a squashfs backed by a ReadSeeker.
type Squashfs struct {
rdr *Reader //underlying reader
offset int
super Superblock
flags SuperblockFlags
compressionOptions CompressionOptions
}
//NewSquashfs creates a new Squashfs backed by the given reader
func NewSquashfs(reader io.ReaderAt) (*Squashfs, error) {
rdr := NewReader(reader)
var superblock Superblock
err := binary.Read(&rdr, binary.LittleEndian, &superblock)
if err != nil {
return nil, err
}
if superblock.Magic != squashfsMagic {
return nil, ErrNotMagical
}
flags := superblock.GetFlags()
var compressionOptions CompressionOptions
switch superblock.Compression {
case zlibCompression:
if flags.CompressorOptions {
var zlibOpRaw zlibOptionsRaw
err = binary.Read(&rdr, binary.LittleEndian, &zlibOpRaw)
if err != nil {
return nil, err
}
compressionOptions = NewZlibOptions(zlibOpRaw)
} else {
compressionOptions = NewZlibOptions(zlibOptionsRaw{})
}
case xzCompression:
if flags.CompressorOptions {
var xzOpRaw xzOptionsRaw
err = binary.Read(&rdr, binary.LittleEndian, xzOpRaw)
if err != nil {
return nil, err
}
compressionOptions = NewXzOption(xzOpRaw)
} else {
compressionOptions = NewXzOption(xzOptionsRaw{})
}
default:
//TODO: all the compression options
return nil, errors.New("This type of compression isn't supported yet")
}
//TODO: parse more info
return &Squashfs{
rdr: &rdr,
super: superblock,
flags: flags,
compressionOptions: compressionOptions,
}, nil
}
func (s *Squashfs) readRootDirectoryTable() error {
offset, metaOffset := inode.ProcessInodeRef(s.super.RootInode)
meta, err := s.parseMetadataAt(int64(s.super.InodeTableOffset) + int64(offset))
if err != nil {
fmt.Println("Error processing metadata")
return err
}
_, err = meta.Data.Read(make([]byte, metaOffset))
if err != nil {
fmt.Println("error reading forward to offset")
return err
}
common, _, err := inode.ProcessInode(&meta.Data, s.super.BlockSize)
if err != nil {
fmt.Println("Error reading inode")
return err
}
if common.InodeType != inode.BasicDirectoryType {
return errors.New("Not a basic directory")
}
// rootDir := inodeInterface.(inode.BasicDirectory)
// dirTable, err := directory.NewDirectory(meta.Data)
// if err != nil {
// return err
// }
// for _, entry := range dirTable.Entries {
// fmt.Println(entry.Name)
// }
return nil
}
//GetFlags return the SuperblockFlags of the Squashfs
func (s *Squashfs) GetFlags() SuperblockFlags {
return s.flags
}
//metadata is a parsed metadata block
type metadata struct {
Compressed bool
Size uint16
Data io.Reader
}
func (m *metadata) close() {
switch m.Data.(type) {
case io.ReadCloser:
m.Data.(io.ReadCloser).Close()
}
}
func (s *Squashfs) parseNextMetadata() (*metadata, error) {
var metaHeader uint16
err := binary.Read(s.rdr, binary.LittleEndian, &metaHeader)
if err != nil {
return nil, err
}
reader := io.NewSectionReader(s.rdr, s.rdr.offset, s.rdr.offset+int64(metaHeader&^0x8000))
if metaHeader&0x8000 == 0x8000 {
metaHeader = metaHeader &^ 0x8000
compressRead, err := s.compressionOptions.Reader(reader)
return &metadata{
Compressed: true,
Size: metaHeader,
Data: *compressRead,
}, err
}
return &metadata{
Compressed: false,
Size: metaHeader,
Data: reader,
}, nil
}
func (s *Squashfs) parseMetadataAt(offset int64) (*metadata, error) {
// tmp := s.rdr.offset
// meta, err := s.parseNextMetadata()
// s.rdr.offset = tmp
// return meta, err
var metaHeader uint16
var headerBytes []byte
headerBytes = make([]byte, 2)
s.rdr.ReadAt(headerBytes, offset)
metaHeader = binary.LittleEndian.Uint16(headerBytes)
if metaHeader&0x8000 == 0x8000 {
fmt.Println("Compressed")
metaHeader = metaHeader &^ 0x8000
compressRead, err := s.compressionOptions.Reader(io.NewSectionReader(s.rdr, s.rdr.offset, s.rdr.offset+int64(s.super.BlockSize)))
return &metadata{
Compressed: true,
Size: metaHeader,
Data: *compressRead,
}, err
}
return &metadata{
Compressed: false,
Size: metaHeader,
Data: io.NewSectionReader(s.rdr, s.rdr.offset, s.rdr.offset+int64(s.super.BlockSize)),
}, nil
//TODO: figure out why things break when I use a straight zlib reader, but not when I use a byte reader writer
// byts, err := s.compressionOptions.Decompress(io.NewSectionReader(s.rdr, s.rdr.offset, s.rdr.offset+int64(s.super.BlockSize)))
// return &metadata{
// Compressed: false,
// Size: metaHeader,
// Data: newByteReadWriteFromBytes(byts),
// }, err
}
-106
View File
@@ -1,106 +0,0 @@
package squashfs
import (
"io"
"net/http"
"os"
"testing"
appimage "github.com/CalebQ42/GoAppImage"
)
const (
downloadURL = "https://github.com/zilti/code-oss.AppImage/releases/download/continuous/Code_OSS-x86_64.AppImage"
appImageName = "Code_OSS.AppImage"
squashfsName = "Code_OSS.Squashfs"
)
func TestAppImageSquash(t *testing.T) {
t.Parallel()
wd, err := os.Getwd()
if err != nil {
t.Error(err)
}
squashFil, err := os.Open(wd + "/testing/" + squashfsName)
if os.IsNotExist(err) {
TestCreateSquashFromAppImage(t)
squashFil, err = os.Open(wd + "/testing/" + squashfsName)
if err != nil {
t.Error(err)
}
}
defer squashFil.Close()
stat, _ := squashFil.Stat()
squash, err := NewSquashfs(io.NewSectionReader(squashFil, 0, stat.Size()))
if err != nil {
t.Error(err)
}
err = squash.readRootDirectoryTable()
t.Fatal(err)
}
func TestCreateSquashFromAppImage(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.Mkdir(wd+"/testing", 0777)
if err != nil && !os.IsExist(err) {
t.Fatal(err)
}
_, err = os.Open(wd + "/testing/" + appImageName)
if os.IsNotExist(err) {
downloadTestAppImage(t, wd+"/testing")
_, err = os.Open(wd + "/testing/" + appImageName)
if err != nil {
t.Fatal(err)
}
} else if err != nil {
t.Fatal(err)
}
ai := appimage.NewAppImage(wd + "/testing/" + appImageName)
aiFil, err := os.Open(wd + "/testing/" + appImageName)
if err != nil {
t.Fatal(err)
}
defer aiFil.Close()
aiFil.Seek(ai.Offset, 0)
os.Remove(wd + "/testing/" + squashfsName)
aiSquash, err := os.Create(wd + "/testing/" + squashfsName)
if err != nil {
t.Fatal(err)
}
_, err = io.Copy(aiSquash, aiFil)
if err != nil {
t.Fatal(err)
}
}
func downloadTestAppImage(t *testing.T, dir string) {
//seems to time out. Need to fix that at some point
appImage, err := os.Create(dir + "/" + appImageName)
if err != nil {
t.Fatal(err)
}
defer appImage.Close()
check := http.Client{
CheckRedirect: func(r *http.Request, via []*http.Request) error {
r.URL.Opaque = r.URL.Path
return nil
},
}
resp, err := check.Get(downloadURL)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
_, err = io.Copy(appImage, resp.Body)
if err != nil {
t.Fatal(err)
}
}
func TestLookInsideSquash(t *testing.T) {
t.Parallel()
//TODO
}
-913
View File
@@ -1,913 +0,0 @@
// // Package squashfs implements writing SquashFS file system images using zlib
// // compression for data blocks (inodes and directory entries are written
// // uncompressed for simplicity).
// //
// // Note that SquashFS requires directory entries to be sorted, i.e. files and
// // directories need to be added in the correct order.
// //
// // This package intentionally only implements a subset of SquashFS. Notably,
// // block devices, character devices, FIFOs, sockets and xattrs are not
// // supported.
package squashfs
// import (
// "bytes"
// "compress/zlib"
// "encoding/binary"
// "io"
// "os"
// "path/filepath"
// "strings"
// "time"
// "golang.org/x/sys/unix"
// )
// // inode contains a block number + offset within that block.
// type Inode int64
// const (
// zlibCompression = 1 + iota
// lzmaCompression
// lzoCompression
// xzCompression
// lz4Compression
// zstdCompression
// )
// const (
// invalidFragment = 0xFFFFFFFF
// invalidXattr = 0xFFFFFFFF
// )
// const (
// dirType = 1 + iota
// fileType
// symlinkType
// blkdevType
// chrdevType
// fifoType
// socketType
// // The larger types are used for e.g. sparse files, xattrs, etc.
// ldirType
// lregType
// lsymlinkType
// lblkdevType
// lchrdevType
// lfifoType
// lsocketType
// )
// type inodeHeader struct {
// InodeType uint16
// Mode uint16
// Uid uint16
// Gid uint16
// Mtime int32
// InodeNumber uint32
// }
// // fileType
// type regInodeHeader struct {
// inodeHeader
// // full byte offset from the start of the file system, e.g. 96 for first
// // file contents. Not using fragments limits us to 2^32-1-96 (≈ 4GiB) bytes
// // of file contents.
// StartBlock uint32
// Fragment uint32
// Offset uint32
// FileSize uint32
// // Followed by a uint32 array of compressed block sizes.
// }
// // lregType
// type lregInodeHeader struct {
// inodeHeader
// // full byte offset from the start of the file system, e.g. 96 for first
// // file contents. Not using fragments limits us to 2^32-1-96 (≈ 4GiB) bytes
// // of file contents.
// StartBlock uint64
// FileSize uint64
// Sparse uint64
// Nlink uint32
// Fragment uint32
// Offset uint32
// Xattr uint32
// // Followed by a uint32 array of compressed block sizes.
// }
// // symlinkType
// type symlinkInodeHeader struct {
// inodeHeader
// Nlink uint32
// SymlinkSize uint32
// // Followed by a byte array of SymlinkSize bytes.
// }
// // chrdevType and blkdevType
// type devInodeHeader struct {
// inodeHeader
// Nlink uint32
// Rdev uint32
// }
// // fifoType and socketType
// type ipcInodeHeader struct {
// inodeHeader
// Nlink uint32
// }
// // dirType
// type dirInodeHeader struct {
// inodeHeader
// StartBlock uint32
// Nlink uint32
// FileSize uint16
// Offset uint16
// ParentInode uint32
// }
// // ldirType
// type ldirInodeHeader struct {
// inodeHeader
// Nlink uint32
// FileSize uint32
// StartBlock uint32
// ParentInode uint32
// Icount uint16
// Offset uint16
// Xattr uint32
// }
// type dirHeader struct {
// Count uint32
// StartBlock uint32
// InodeOffset uint32
// }
// func (d *dirHeader) Unmarshal(b []byte) {
// _ = b[11]
// e := binary.LittleEndian
// d.Count = e.Uint32(b)
// d.StartBlock = e.Uint32(b[4:])
// d.InodeOffset = e.Uint32(b[8:])
// }
// type dirEntry struct {
// Offset uint16
// InodeNumber int16
// EntryType uint16
// Size uint16
// // Followed by a byte array of Size bytes.
// }
// func (d *dirEntry) Unmarshal(b []byte) {
// _ = b[7]
// e := binary.LittleEndian
// d.Offset = e.Uint16(b)
// d.InodeNumber = int16(e.Uint16(b[2:]))
// d.EntryType = e.Uint16(b[4:])
// d.Size = e.Uint16(b[6:])
// }
// // xattr types
// const (
// XattrTypeUser = iota
// XattrTypeTrusted
// XattrTypeSecurity
// )
// var xattrPrefix = map[int]string{
// XattrTypeUser: "user.",
// XattrTypeTrusted: "trusted.",
// XattrTypeSecurity: "security.",
// }
// type Xattr struct {
// Type uint16
// FullName string
// Value []byte
// }
// func XattrFromAttr(attr string, val []byte) Xattr {
// for typ, prefix := range xattrPrefix {
// if !strings.HasPrefix(attr, prefix) {
// continue
// }
// return Xattr{
// Type: uint16(typ),
// FullName: strings.TrimPrefix(attr, prefix),
// Value: val,
// }
// }
// return Xattr{}
// }
// type xattrId struct {
// Xattr uint64
// Count uint32
// Size uint32
// }
// func writeIdTable(w io.WriteSeeker, ids []uint32) (start int64, err error) {
// metaOff, err := w.Seek(0, io.SeekCurrent)
// if err != nil {
// return 0, err
// }
// var buf bytes.Buffer
// if err := binary.Write(&buf, binary.LittleEndian, ids); err != nil {
// return 0, err
// }
// if err := binary.Write(w, binary.LittleEndian, uint16(buf.Len())|0x8000); err != nil {
// return 0, err
// }
// if _, err := io.Copy(w, &buf); err != nil {
// return 0, err
// }
// off, err := w.Seek(0, io.SeekCurrent)
// if err != nil {
// return 0, err
// }
// return off, binary.Write(w, binary.LittleEndian, metaOff)
// }
// type fullDirEntry struct {
// startBlock uint32
// offset uint16
// inodeNumber uint32
// entryType uint16
// name string
// }
// const (
// magic = 0x73717368
// dataBlockSize = 131072
// metadataBlockSize = 8192
// majorVersion = 4
// minorVersion = 0
// )
// type Writer struct {
// // Root represents the file system root. Like all directories, Flush must be
// // called precisely once.
// Root *Directory
// xattrs []Xattr
// xattrIds []xattrId
// w io.WriteSeeker
// sb superblock
// inodeBuf bytes.Buffer
// dirBuf bytes.Buffer
// writeInodeNumTo map[string][]int64
// }
// // TODO: document what this is doing and what it is used for
// func slog(block uint32) uint16 {
// for i := uint16(12); i <= 20; i++ {
// if block == (1 << i) {
// return i
// }
// }
// return 0
// }
// // filesystemFlags returns flags for a SquashFS file system created by this
// // package (disabling most features for now).
// func filesystemFlags() uint16 {
// const (
// noI = 1 << iota // uncompressed metadata
// noD // uncompressed data
// _
// noF // uncompressed fragments
// noFrag // never use fragments
// alwaysFrag // always use fragments
// duplicateChecking // de-duplication
// exportable // exportable via NFS
// noX // uncompressed xattrs
// noXattr // no xattrs
// compopt // compressor-specific options present?
// )
// return noI | noF | noFrag | noX | noXattr
// }
// // NewWriter returns a Writer which will write a SquashFS file system image to w
// // once Flush is called.
// //
// // Create new files and directories with the corresponding methods on the Root
// // directory of the Writer.
// //
// // File data is written to w even before Flush is called.
// func NewWriter(w io.WriteSeeker, mkfsTime time.Time) (*Writer, error) {
// // Skip over superblock to the data area, we come back to the superblock
// // when flushing.
// if _, err := w.Seek(96, io.SeekStart); err != nil {
// return nil, err
// }
// wr := &Writer{
// w: w,
// sb: superblock{
// Magic: magic,
// MkfsTime: int32(mkfsTime.Unix()),
// BlockSize: dataBlockSize,
// Fragments: 0,
// Compression: zlibCompression,
// BlockLog: slog(dataBlockSize),
// Flags: filesystemFlags(),
// NoIds: 1, // just one uid/gid mapping (for root)
// Major: majorVersion,
// Minor: minorVersion,
// XattrIdTableStart: -1, // not present
// LookupTableStart: -1, // not present
// },
// writeInodeNumTo: make(map[string][]int64),
// }
// wr.Root = &Directory{
// w: wr,
// name: "", // root
// modTime: mkfsTime,
// }
// return wr, nil
// }
// // Directory represents a SquashFS directory.
// type Directory struct {
// w *Writer
// name string
// modTime time.Time
// dirEntries []fullDirEntry
// parent *Directory
// }
// func (d *Directory) path() string {
// if d.parent == nil {
// return d.name
// }
// return filepath.Join(d.parent.path(), d.name)
// }
// type file struct {
// w *Writer
// d *Directory
// off int64
// size uint32
// name string
// modTime time.Time
// mode uint16
// // buf accumulates at least dataBlockSize bytes, at which point a new block
// // is being written.
// buf bytes.Buffer
// // blocksizes stores, for each block of dataBlockSize bytes (uncompressed),
// // the number of bytes the block compressed down to.
// blocksizes []uint32
// // compBuf is used for holding a block during compression to avoid memory
// // allocations.
// compBuf *bytes.Buffer
// // zlibWriter is re-used for each compressed block
// zlibWriter *zlib.Writer
// xattrRef uint32
// }
// // Directory creates a new directory with the specified name and modTime.
// func (d *Directory) Directory(name string, modTime time.Time) *Directory {
// return &Directory{
// w: d.w,
// name: name,
// modTime: modTime,
// parent: d,
// }
// }
// // File creates a file with the specified name, modTime and mode. The returned
// // io.WriterCloser must be closed after writing the file.
// func (d *Directory) File(name string, modTime time.Time, mode uint16, xattrs []Xattr) (io.WriteCloser, error) {
// off, err := d.w.w.Seek(0, io.SeekCurrent)
// if err != nil {
// return nil, err
// }
// // zlib.BestSpeed results in only a 2x slow-down over no compression
// // (compared to >4x slow-down with DefaultCompression), but generates
// // results which are in the same ball park (10% larger).
// zw, err := zlib.NewWriterLevel(nil, zlib.BestSpeed)
// if err != nil {
// return nil, err
// }
// xattrRef := uint32(invalidXattr)
// if len(xattrs) > 0 {
// xattrRef = uint32(len(d.w.xattrs))
// d.w.xattrs = append(d.w.xattrs, xattrs[0]) // TODO: support multiple
// size := len(xattrs[0].FullName) + len(xattrs[0].Value)
// d.w.xattrIds = append(d.w.xattrIds, xattrId{
// // Xattr is populated in writeXattrTables
// Count: 1, // TODO: support multiple
// Size: uint32(size),
// })
// }
// return &file{
// w: d.w,
// d: d,
// off: off,
// name: name,
// modTime: modTime,
// mode: mode,
// compBuf: bytes.NewBuffer(make([]byte, dataBlockSize)),
// zlibWriter: zw,
// xattrRef: xattrRef,
// }, nil
// }
// // Symlink creates a symbolic link from newname to oldname with the specified
// // modTime and mode.
// func (d *Directory) Symlink(oldname, newname string, modTime time.Time, mode os.FileMode) error {
// startBlock := d.w.inodeBuf.Len() / metadataBlockSize
// offset := d.w.inodeBuf.Len() - startBlock*metadataBlockSize
// if err := binary.Write(&d.w.inodeBuf, binary.LittleEndian, symlinkInodeHeader{
// inodeHeader: inodeHeader{
// InodeType: symlinkType,
// Mode: uint16(mode),
// Uid: 0,
// Gid: 0,
// Mtime: int32(modTime.Unix()),
// InodeNumber: d.w.sb.Inodes + 1,
// },
// Nlink: 1, // TODO(later): when is this not 1?
// SymlinkSize: uint32(len(oldname)),
// }); err != nil {
// return err
// }
// if _, err := d.w.inodeBuf.Write([]byte(oldname)); err != nil {
// return err
// }
// d.dirEntries = append(d.dirEntries, fullDirEntry{
// startBlock: uint32(startBlock),
// offset: uint16(offset),
// inodeNumber: d.w.sb.Inodes + 1,
// entryType: symlinkType,
// name: newname,
// })
// d.w.sb.Inodes++
// return nil
// }
// // Flush writes directory entries and creates inodes for the directory.
// func (d *Directory) Flush() error {
// countByStartBlock := make(map[uint32]uint32)
// for _, de := range d.dirEntries {
// countByStartBlock[de.startBlock]++
// }
// dirBufStartBlock := d.w.dirBuf.Len() / metadataBlockSize
// dirBufOffset := d.w.dirBuf.Len()
// currentBlock := int64(-1)
// currentInodeOffset := int64(-1)
// var subdirs int
// for _, de := range d.dirEntries {
// if de.entryType == dirType {
// subdirs++
// }
// if int64(de.startBlock) != currentBlock {
// dh := dirHeader{
// Count: countByStartBlock[de.startBlock] - 1,
// StartBlock: de.startBlock * (metadataBlockSize + 2),
// InodeOffset: de.inodeNumber,
// }
// if err := binary.Write(&d.w.dirBuf, binary.LittleEndian, &dh); err != nil {
// return err
// }
// currentBlock = int64(de.startBlock)
// currentInodeOffset = int64(de.inodeNumber)
// }
// if err := binary.Write(&d.w.dirBuf, binary.LittleEndian, &dirEntry{
// Offset: de.offset,
// InodeNumber: int16(de.inodeNumber - uint32(currentInodeOffset)),
// EntryType: de.entryType,
// Size: uint16(len(de.name) - 1),
// }); err != nil {
// return err
// }
// if _, err := d.w.dirBuf.Write([]byte(de.name)); err != nil {
// return err
// }
// }
// startBlock := d.w.inodeBuf.Len() / metadataBlockSize
// offset := d.w.inodeBuf.Len() - startBlock*metadataBlockSize
// inodeBufOffset := d.w.inodeBuf.Len()
// // parentInodeOffset is the offset (in bytes) of the ParentInode field
// // within a dirInodeHeader or ldirInodeHeader
// var parentInodeOffset int64
// if len(d.dirEntries) > 256 ||
// d.w.dirBuf.Len()-dirBufOffset > metadataBlockSize {
// parentInodeOffset = (2 + 2 + 2 + 2 + 4 + 4) + 4 + 4 + 4
// if err := binary.Write(&d.w.inodeBuf, binary.LittleEndian, ldirInodeHeader{
// inodeHeader: inodeHeader{
// InodeType: ldirType,
// Mode: unix.S_IRUSR | unix.S_IWUSR | unix.S_IXUSR |
// unix.S_IRGRP | unix.S_IXGRP |
// unix.S_IROTH | unix.S_IXOTH,
// Uid: 0,
// Gid: 0,
// Mtime: int32(d.modTime.Unix()),
// InodeNumber: d.w.sb.Inodes + 1,
// },
// Nlink: uint32(subdirs + 2 - 1), // + 2 for . and ..
// FileSize: uint32(d.w.dirBuf.Len()-dirBufOffset) + 3,
// StartBlock: uint32(dirBufStartBlock * (metadataBlockSize + 2)),
// ParentInode: d.w.sb.Inodes + 2, // invalid
// Icount: 0, // no directory index
// Offset: uint16(dirBufOffset - dirBufStartBlock*metadataBlockSize),
// Xattr: invalidXattr,
// }); err != nil {
// return err
// }
// } else {
// parentInodeOffset = (2 + 2 + 2 + 2 + 4 + 4) + 4 + 4 + 2 + 2
// if err := binary.Write(&d.w.inodeBuf, binary.LittleEndian, dirInodeHeader{
// inodeHeader: inodeHeader{
// InodeType: dirType,
// Mode: unix.S_IRUSR | unix.S_IWUSR | unix.S_IXUSR |
// unix.S_IRGRP | unix.S_IXGRP |
// unix.S_IROTH | unix.S_IXOTH,
// Uid: 0,
// Gid: 0,
// Mtime: int32(d.modTime.Unix()),
// InodeNumber: d.w.sb.Inodes + 1,
// },
// StartBlock: uint32(dirBufStartBlock * (metadataBlockSize + 2)),
// Nlink: uint32(subdirs + 2 - 1), // + 2 for . and ..
// FileSize: uint16(d.w.dirBuf.Len()-dirBufOffset) + 3,
// Offset: uint16(dirBufOffset - dirBufStartBlock*metadataBlockSize),
// ParentInode: d.w.sb.Inodes + 2, // invalid
// }); err != nil {
// return err
// }
// }
// path := d.path()
// for _, offset := range d.w.writeInodeNumTo[path] {
// // Directly manipulating unread data in bytes.Buffer via Bytes(), as per
// // https://groups.google.com/d/msg/golang-nuts/1ON9XVQ1jXE/8j9RaeSYxuEJ
// b := d.w.inodeBuf.Bytes()
// binary.LittleEndian.PutUint32(b[offset:offset+4], d.w.sb.Inodes+1)
// }
// if d.parent != nil {
// parentPath := filepath.Dir(d.path())
// if parentPath == "." {
// parentPath = ""
// }
// d.w.writeInodeNumTo[parentPath] = append(d.w.writeInodeNumTo[parentPath], int64(inodeBufOffset)+parentInodeOffset)
// d.parent.dirEntries = append(d.parent.dirEntries, fullDirEntry{
// startBlock: uint32(startBlock),
// offset: uint16(offset),
// inodeNumber: d.w.sb.Inodes + 1,
// entryType: dirType,
// name: d.name,
// })
// } else { // root
// d.w.sb.RootInode = Inode((startBlock*(metadataBlockSize+2))<<16 | offset)
// }
// d.w.sb.Inodes++
// return nil
// }
// // Write implements io.Writer
// func (f *file) Write(p []byte) (n int, err error) {
// n, err = f.buf.Write(p)
// if n > 0 {
// // Keep track of the uncompressed file size.
// f.size += uint32(n)
// for f.buf.Len() >= dataBlockSize {
// if err := f.writeBlock(); err != nil {
// return 0, err
// }
// }
// }
// return n, err
// }
// func (f *file) writeBlock() error {
// n := f.buf.Len()
// if n > dataBlockSize {
// n = dataBlockSize
// }
// // Feed dataBlockSize bytes to the compressor
// b := f.buf.Bytes()
// block := b[:n]
// rest := b[n:]
// /*
// f.compBuf.Reset()
// f.zlibWriter.Reset(f.compBuf)
// if _, err := f.zlibWriter.Write(block); err != nil {
// return err
// }
// if err := f.zlibWriter.Close(); err != nil {
// return err
// }
// size := f.compBuf.Len()
// if size > len(block) {
// // Copy uncompressed data: Linux returns i/o errors when it encounters a
// // compressed block which is larger than the uncompressed data:
// // https://github.com/torvalds/linux/blob/3ca24ce9ff764bc27bceb9b2fd8ece74846c3fd3/fs/squashfs/block.c#L150
// size = len(block) | (1 << 24) // SQUASHFS_COMPRESSED_BIT_BLOCK
// if _, err := f.w.w.Write(block); err != nil {
// return err
// }
// } else {
// if _, err := io.Copy(f.w.w, f.compBuf); err != nil {
// return err
// }
// }
// */
// // Copy uncompressed data: Linux returns i/o errors when it encounters a
// // compressed block which is larger than the uncompressed data:
// // https://github.com/torvalds/linux/blob/3ca24ce9ff764bc27bceb9b2fd8ece74846c3fd3/fs/squashfs/block.c#L150
// size := len(block) | (1 << 24) // SQUASHFS_COMPRESSED_BIT_BLOCK
// if _, err := f.w.w.Write(block); err != nil {
// return err
// }
// f.blocksizes = append(f.blocksizes, uint32(size))
// // Keep the rest in f.buf for the next write
// copy(b, rest)
// f.buf.Truncate(len(rest))
// return nil
// }
// // Close implements io.Closer
// func (f *file) Close() error {
// for f.buf.Len() > 0 {
// if err := f.writeBlock(); err != nil {
// return err
// }
// }
// startBlock := f.w.inodeBuf.Len() / metadataBlockSize
// offset := f.w.inodeBuf.Len() - startBlock*metadataBlockSize
// if err := binary.Write(&f.w.inodeBuf, binary.LittleEndian, lregInodeHeader{
// inodeHeader: inodeHeader{
// InodeType: lregType,
// Mode: f.mode,
// Uid: 0,
// Gid: 0,
// Mtime: int32(f.modTime.Unix()),
// InodeNumber: f.w.sb.Inodes + 1,
// },
// StartBlock: uint64(f.off),
// FileSize: uint64(f.size),
// Nlink: 1,
// Fragment: invalidFragment,
// Offset: 0,
// Xattr: f.xattrRef,
// }); err != nil {
// return err
// }
// if err := binary.Write(&f.w.inodeBuf, binary.LittleEndian, f.blocksizes); err != nil {
// return err
// }
// f.d.dirEntries = append(f.d.dirEntries, fullDirEntry{
// startBlock: uint32(startBlock),
// offset: uint16(offset),
// inodeNumber: f.w.sb.Inodes + 1,
// entryType: fileType,
// name: f.name,
// })
// f.w.sb.Inodes++
// return nil
// }
// func writeXattr(w io.Writer, xattrs []Xattr) error {
// for _, attr := range xattrs {
// if err := binary.Write(w, binary.LittleEndian, struct {
// Type uint16
// NameSize uint16
// }{
// Type: attr.Type,
// NameSize: uint16(len(attr.FullName)),
// }); err != nil {
// return err
// }
// if _, err := w.Write([]byte(attr.FullName)); err != nil {
// return err
// }
// if err := binary.Write(w, binary.LittleEndian, struct {
// ValSize uint32
// }{
// ValSize: uint32(len(attr.Value)),
// }); err != nil {
// return err
// }
// if _, err := w.Write(attr.Value); err != nil {
// return err
// }
// }
// return nil
// }
// type xattrTableHeader struct {
// XattrTableStart uint64
// XattrIds uint32
// Unused uint32
// }
// func (w *Writer) writeXattrTables() (int64, error) {
// if len(w.xattrs) == 0 {
// return -1, nil
// }
// off, err := w.w.Seek(0, io.SeekCurrent)
// if err != nil {
// return 0, err
// }
// xattrTableStart := uint64(off)
// var xattrBuf bytes.Buffer
// if err := writeXattr(&xattrBuf, w.xattrs); err != nil {
// return 0, err
// }
// xattrBlocks := (xattrBuf.Len() + (metadataBlockSize - 1)) / metadataBlockSize
// if err := w.writeMetadataChunks(&xattrBuf); err != nil {
// return 0, err
// }
// // write xattr id table
// off, err = w.w.Seek(0, io.SeekCurrent)
// if err != nil {
// return 0, err
// }
// idTableOff := uint64(off)
// var xattrIdBuf bytes.Buffer
// size := uint64(0)
// for _, id := range w.xattrIds {
// id.Xattr = uint64(size)
// size += uint64(id.Size) + 8 /* sizeof(Type+NameSize+ValSize) */
// if err := binary.Write(&xattrIdBuf, binary.LittleEndian, id); err != nil {
// return 0, err
// }
// }
// if err := w.writeMetadataChunks(&xattrIdBuf); err != nil {
// return 0, err
// }
// // xattr table header
// off, err = w.w.Seek(0, io.SeekCurrent)
// if err != nil {
// return 0, err
// }
// if err := binary.Write(w.w, binary.LittleEndian, xattrTableHeader{
// XattrTableStart: xattrTableStart,
// XattrIds: uint32(len(w.xattrs)),
// }); err != nil {
// return 0, err
// }
// // write block index
// for i := 0; i < xattrBlocks; i++ {
// if err := binary.Write(w.w, binary.LittleEndian, struct {
// BlockOffset uint64
// }{
// BlockOffset: idTableOff + (uint64(i) * (8192 + 2 /* sizeof(uint16) */)),
// }); err != nil {
// return 0, err
// }
// }
// return off, nil
// }
// // writeMetadataChunks copies from r to w in blocks of metadataBlockSize bytes
// // each, prefixing each block with a uint16 length header, setting the
// // uncompressed bit.
// func (w *Writer) writeMetadataChunks(r io.Reader) error {
// buf := make([]byte, metadataBlockSize)
// for {
// buf = buf[:metadataBlockSize]
// n, err := r.Read(buf)
// if err != nil {
// if err == io.EOF { // done
// return nil
// }
// return err
// }
// buf = buf[:n]
// if err := binary.Write(w.w, binary.LittleEndian, uint16(len(buf))|0x8000); err != nil {
// return err
// }
// if _, err := w.w.Write(buf); err != nil {
// return err
// }
// }
// }
// // Flush writes the SquashFS file system. The Writer must not be used after
// // calling Flush.
// func (w *Writer) Flush() error {
// // (1) superblock will be written later
// // (2) compressor-specific options omitted
// // (3) data has already been written
// // (4) write inode table
// off, err := w.w.Seek(0, io.SeekCurrent)
// if err != nil {
// return err
// }
// w.sb.InodeTableStart = off
// if err := w.writeMetadataChunks(&w.inodeBuf); err != nil {
// return err
// }
// // (5) write directory table
// off, err = w.w.Seek(0, io.SeekCurrent)
// if err != nil {
// return err
// }
// w.sb.DirectoryTableStart = off
// if err := w.writeMetadataChunks(&w.dirBuf); err != nil {
// return err
// }
// // (6) fragment table omitted
// off, err = w.w.Seek(0, io.SeekCurrent)
// if err != nil {
// return err
// }
// w.sb.FragmentTableStart = off
// // (7) export table omitted
// // (8) write uid/gid lookup table
// idTableStart, err := writeIdTable(w.w, []uint32{0})
// if err != nil {
// return err
// }
// w.sb.IdTableStart = idTableStart
// // (9) xattr table
// off, err = w.writeXattrTables()
// if err != nil {
// return err
// }
// w.sb.XattrIdTableStart = off
// off, err = w.w.Seek(0, io.SeekCurrent)
// if err != nil {
// return err
// }
// w.sb.BytesUsed = off
// // Pad to 4096, required for the kernel to be able to access all pages
// if pad := off % 4096; pad > 0 {
// padding := make([]byte, 4096-pad)
// if _, err := w.w.Write(padding); err != nil {
// return err
// }
// }
// // (1) Write superblock
// if _, err := w.w.Seek(0, io.SeekStart); err != nil {
// return err
// }
// return binary.Write(w.w, binary.LittleEndian, &w.sb)
// }
-310
View File
@@ -1,310 +0,0 @@
package squashfs
// import (
// "bytes"
// "flag"
// "fmt"
// "io"
// "io/ioutil"
// "os"
// "os/exec"
// "path/filepath"
// "strings"
// "testing"
// "time"
// "github.com/distr1/distri"
// // "github.com/distr1/distri/internal/distritest"
// "github.com/google/go-cmp/cmp"
// "github.com/orcaman/writerseeker"
// "golang.org/x/sys/unix"
// )
// var fsImagePath = flag.String("fs_image_path", "", "Store the SquashFS test file system in the specified path for manual inspection")
// func writeTestImage(iow io.WriteSeeker, xattr bool) error {
// w, err := NewWriter(iow, time.Now())
// if err != nil {
// return err
// }
// var xattrs []Xattr
// if xattr {
// xattrs = append(xattrs, Xattr{
// Type: 2,
// FullName: "capability",
// Value: []byte{1, 0, 0, 2, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
// })
// }
// ff, err := w.Root.File("hellö wörld", time.Now(), unix.S_IRUSR|unix.S_IRGRP|unix.S_IROTH, xattrs)
// if err != nil {
// return err
// }
// if _, err := ff.Write([]byte("hello world!")); err != nil {
// return err
// }
// if err := ff.Close(); err != nil {
// return err
// }
// ff, err = w.Root.File("leer", time.Now(), unix.S_IRUSR|unix.S_IRGRP|unix.S_IROTH, nil)
// if err != nil {
// return err
// }
// if err := ff.Close(); err != nil {
// return err
// }
// ff, err = w.Root.File("second file", time.Now(), unix.S_IRUSR|unix.S_IXUSR|
// unix.S_IRGRP|unix.S_IXGRP|
// unix.S_IROTH|unix.S_IXOTH,
// nil)
// if err != nil {
// return err
// }
// if _, err := ff.Write([]byte("NON.\n")); err != nil {
// return err
// }
// if err := ff.Close(); err != nil {
// return err
// }
// if err := w.Root.Symlink("second file", "second link", time.Now(), unix.S_IRUSR|unix.S_IRGRP|unix.S_IROTH); err != nil {
// return err
// }
// subdir := w.Root.Directory("subdir", time.Now())
// subsubdir := subdir.Directory("deep", time.Now())
// ff, err = subsubdir.File("yo", time.Now(), unix.S_IRUSR|unix.S_IRGRP|unix.S_IROTH, nil)
// if err != nil {
// return err
// }
// if _, err := ff.Write([]byte("foo\n")); err != nil {
// return err
// }
// if err := ff.Close(); err != nil {
// return err
// }
// if err := subsubdir.Flush(); err != nil {
// return err
// }
// // TODO: write another file in subdir now, will result in invalid parent inode
// ff, err = subdir.File("third file (in subdir)", time.Now(), unix.S_IRUSR|unix.S_IRGRP|unix.S_IROTH, nil)
// if err != nil {
// return err
// }
// if _, err := ff.Write([]byte("contents\n")); err != nil {
// return err
// }
// if err := ff.Close(); err != nil {
// return err
// }
// if err := subdir.Flush(); err != nil {
// return err
// }
// ff, err = w.Root.File("testbin", time.Now(), unix.S_IRUSR|unix.S_IXUSR|
// unix.S_IRGRP|unix.S_IXGRP|
// unix.S_IROTH|unix.S_IXOTH,
// nil)
// if err != nil {
// return err
// }
// zf, err := os.Open(os.Args[0])
// if err != nil {
// return err
// }
// defer zf.Close()
// if _, err := io.Copy(ff, zf); err != nil {
// return err
// }
// if err := ff.Close(); err != nil {
// return err
// }
// if err := w.Root.Flush(); err != nil {
// return err
// }
// if err := w.Flush(); err != nil {
// return err
// }
// return nil
// }
// func TestUnsquashfs(t *testing.T) {
// t.Parallel()
// ctx, canc := distri.InterruptibleContext()
// defer canc()
// if _, err := exec.LookPath("unsquashfs"); err != nil {
// t.Skip("unsquashfs not found in $PATH")
// }
// for _, xattr := range []bool{false, true} {
// t.Run(fmt.Sprintf("xattr %v", xattr), func(t *testing.T) {
// var (
// f *os.File
// err error
// )
// if *fsImagePath != "" {
// f, err = os.Create(*fsImagePath + fmt.Sprintf("-xattr-%v", xattr))
// } else {
// f, err = ioutil.TempFile("", fmt.Sprintf("squashfs-xattr-%v", xattr))
// if err == nil {
// defer os.Remove(f.Name())
// }
// }
// if err != nil {
// t.Fatal(err)
// }
// if err := writeTestImage(f, xattr); err != nil {
// t.Fatal(err)
// }
// if err := f.Close(); err != nil {
// t.Fatal(err)
// }
// // Extract our generated file system using unsquashfs(1)
// out, err := ioutil.TempDir("", fmt.Sprintf("unsquashfs-xattr-%v", xattr))
// if err != nil {
// t.Fatal(err)
// }
// // defer distritest.RemoveAll(t, out)
// cmd := exec.CommandContext(ctx, "unsquashfs", "-no-xattrs", "-d", filepath.Join(out, "x"), f.Name())
// cmd.Stderr = os.Stderr
// if err := cmd.Run(); err != nil {
// t.Fatal(err)
// }
// fbin, err := os.Open(os.Args[0])
// if err != nil {
// t.Fatal(err)
// }
// // Verify the extracted files match our expectations.
// for _, entry := range []struct {
// path string
// contents io.Reader
// }{
// {"leer", strings.NewReader("")},
// {"hellö wörld", strings.NewReader("hello world!")},
// {"testbin", fbin},
// {"subdir/third file (in subdir)", strings.NewReader("contents\n")},
// } {
// entry := entry // copy
// t.Run(entry.path, func(t *testing.T) {
// in, err := os.Open(filepath.Join(out, "x", entry.path))
// if err != nil {
// t.Fatal(err)
// }
// got, err := ioutil.ReadAll(in)
// if err != nil {
// t.Fatal(err)
// }
// want, err := ioutil.ReadAll(entry.contents)
// if err != nil {
// t.Fatal(err)
// }
// if !bytes.Equal(got, want) {
// t.Fatalf("path %q differs", entry.path)
// }
// })
// }
// })
// }
// }
// func TestReader(t *testing.T) {
// t.Parallel()
// for _, xattr := range []bool{false, true} {
// t.Run(fmt.Sprintf("xattr %v", xattr), func(t *testing.T) {
// var err error
// buf := &writerseeker.WriterSeeker{}
// if err := writeTestImage(buf, xattr); err != nil {
// t.Fatal(err)
// }
// if _, err := buf.Seek(0, io.SeekCurrent); err != nil {
// t.Fatal(err)
// }
// rd, err := NewReader(buf.BytesReader())
// if err != nil {
// t.Fatal(err)
// }
// fbin, err := os.Open(os.Args[0])
// if err != nil {
// t.Fatal(err)
// }
// // Verify the extracted files match our expectations.
// for _, entry := range []struct {
// path string
// contents io.Reader
// }{
// {"leer", strings.NewReader("")},
// {"hellö wörld", strings.NewReader("hello world!")},
// {"testbin", fbin},
// {"subdir/third file (in subdir)", strings.NewReader("contents\n")},
// } {
// entry := entry // copy
// t.Run(entry.path, func(t *testing.T) {
// // TODO: is this t.Parallel()-safe?
// inode, err := rd.LookupPath(entry.path)
// if err != nil {
// t.Fatal(err)
// }
// in, err := rd.FileReader(inode)
// if err != nil {
// t.Fatal(err)
// }
// got, err := ioutil.ReadAll(in)
// if err != nil {
// t.Fatal(err)
// }
// want, err := ioutil.ReadAll(entry.contents)
// if err != nil {
// t.Fatal(err)
// }
// if !bytes.Equal(got, want) {
// t.Fatalf("path %q differs", entry.path)
// }
// })
// }
// if xattr {
// t.Run("xattrs", func(t *testing.T) {
// inode, err := rd.LookupPath("hellö wörld")
// if err != nil {
// t.Fatal(err)
// }
// xattrs, err := rd.ReadXattrs(inode)
// if err != nil {
// t.Fatal(err)
// }
// if got, want := len(xattrs), 1; got != want {
// t.Fatalf("unexpected number of extended attributes: got %d, want %d", got, want)
// }
// wantXattr := Xattr{
// Type: 2,
// FullName: "security.capability",
// Value: []byte{1, 0, 0, 2, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
// }
// if diff := cmp.Diff(wantXattr, xattrs[0]); diff != "" {
// t.Errorf("unexpected extended attribute: diff (-want +got):\n%s", diff)
// }
// })
// }
// })
// }
// }
-59
View File
@@ -1,59 +0,0 @@
package squashfs
import (
"errors"
"io"
)
//TODO: possible custom reader because I'm havng some issuse...
//Reader is a reader which implements Reader, ReaderAt, and Seeker, all with an accesible offset (for reasons)
type Reader struct {
rdr io.ReaderAt
offset int64
}
//NewReader creates a squashfs.Reader from a io.ReaderAt
func NewReader(baseReader io.ReaderAt) Reader {
return Reader{
rdr: baseReader,
offset: 0,
}
}
//Read reads len(byt) into byt. Advances the internal offset
func (r *Reader) Read(byt []byte) (int, error) {
n, err := r.rdr.ReadAt(byt, r.offset)
r.offset += int64(n)
return n, err
}
//ReadAt wraps the internal io.ReadAt's function. DOES NOT advance the internal offset for Read function.
//Returns how many bytes were read.
func (r *Reader) ReadAt(byt []byte, offset int64) (int, error) {
return r.rdr.ReadAt(byt, offset)
}
//ReadAtFromOffset is the same as ReadAt, but the given offset is offset by the internal offset. DOES NOT advance the internal offset.
//Returns how many bytes were read.
func (r *Reader) ReadAtFromOffset(byt []byte, offset int64) (int, error) {
offset += r.offset
return r.rdr.ReadAt(byt, offset)
}
//Seek advances the internal offset. SeekEnd DOES NOT work
//Might not be necessary, but here just in case
func (r *Reader) Seek(offset int64, whence int) (int64, error) {
switch whence {
case io.SeekCurrent:
n, err := r.Read(make([]byte, offset))
return int64(n), err
case io.SeekStart:
r.offset = 0
n, err := r.Read(make([]byte, offset))
return int64(n), err
case io.SeekEnd:
return 0, errors.New("SeekEnd is NOT supported")
}
return 0, errors.New("incorrect whence")
}
+11 -13
View File
@@ -6,7 +6,7 @@ import (
"os" "os"
"testing" "testing"
appimage "github.com/CalebQ42/GoAppImage" goappimage "github.com/CalebQ42/GoAppImage"
) )
const ( const (
@@ -15,27 +15,30 @@ const (
squashfsName = "Code_OSS.Squashfs" squashfsName = "Code_OSS.Squashfs"
) )
func TestAppImageSquash(t *testing.T) { func TestMain(t *testing.T) {
t.Parallel() t.Parallel()
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
t.Error(err) t.Fatal(err)
} }
squashFil, err := os.Open(wd + "/testing/" + squashfsName) squashFil, err := os.Open(wd + "/testing/" + squashfsName)
if os.IsNotExist(err) { if os.IsNotExist(err) {
TestCreateSquashFromAppImage(t) TestCreateSquashFromAppImage(t)
squashFil, err = os.Open(wd + "/testing/" + squashfsName) squashFil, err = os.Open(wd + "/testing/" + squashfsName)
if err != nil { if err != nil {
t.Error(err) t.Fatal(err)
} }
} }
defer squashFil.Close() defer squashFil.Close()
stat, _ := squashFil.Stat() stat, _ := squashFil.Stat()
squash, err := NewSquashfs(io.NewSectionReader(squashFil, 0, stat.Size())) rdr, err := NewSquashfsReader(io.NewSectionReader(squashFil, 0, stat.Size()))
if err != nil { if err != nil {
t.Error(err) t.Fatal(err)
}
err = rdr.readRootDirTable()
if err != nil {
t.Fatal(err)
} }
err = squash.printDirTable()
t.Fatal(err) t.Fatal(err)
} }
@@ -58,7 +61,7 @@ func TestCreateSquashFromAppImage(t *testing.T) {
} else if err != nil { } else if err != nil {
t.Fatal(err) t.Fatal(err)
} }
ai := appimage.NewAppImage(wd + "/testing/" + appImageName) ai := goappimage.NewAppImage(wd + "/testing/" + appImageName)
aiFil, err := os.Open(wd + "/testing/" + appImageName) aiFil, err := os.Open(wd + "/testing/" + appImageName)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -99,8 +102,3 @@ func downloadTestAppImage(t *testing.T, dir string) {
t.Fatal(err) t.Fatal(err)
} }
} }
func TestLookInsideSquash(t *testing.T) {
t.Parallel()
//TODO
}
-74
View File
@@ -1,74 +0,0 @@
package squashfs
import (
"encoding/binary"
"fmt"
"io"
"github.com/CalebQ42/GoSquashfs/internal/directory"
"github.com/CalebQ42/GoSquashfs/internal/inode"
)
type Squashfs struct {
r Reader
super Superblock
flags SuperblockFlags
compression CompressionOptions
}
func NewSquashfs(rdr io.ReaderAt) (*Squashfs, error) {
var squash Squashfs
squash.r = NewReader(rdr)
err := binary.Read(&squash.r, binary.LittleEndian, &squash.super)
if err != nil {
return nil, err
}
squash.flags = squash.super.getFlags()
switch squash.super.Compression {
case gzipCompression:
var raw gzipOptionsRaw
err := binary.Read(&squash.r, binary.LittleEndian, &raw)
if err != nil {
return nil, err
}
squash.compression = NewGzipOptions(raw)
default:
fmt.Println("Other compression options are not currently supported")
return nil, err
}
return &squash, nil
}
func (s *Squashfs) printDirTable() error {
offset, metaOffset := inode.ProcessInodeRef(s.super.RootInode)
br, err := s.NewBlockReader(int64(offset))
if err != nil {
return err
}
fmt.Println(offset, metaOffset)
br.dataOffset = int64(metaOffset)
_, inodeType, err := inode.ProcessInode(br, s.super.BlockSize)
if err != nil {
return err
}
rootDir := inodeType.(*inode.BasicDirectory)
fmt.Println(*rootDir)
br, err = s.NewBlockReader(int64(s.super.DirectoryTableOffset) + int64(rootDir.DirectoryIndex))
if err != nil {
return err
}
br.dataOffset = int64(rootDir.DirectoryOffset)
dir, err := directory.NewDirectory(br)
if err != nil {
return err
}
for _, entry := range dir.Entries {
fmt.Println(entry.Name)
}
return nil
}
//GetFlags returns the SuperblockFlags from the Superblock
func (s *Squashfs) GetFlags() SuperblockFlags {
return s.flags
}
+75
View File
@@ -0,0 +1,75 @@
package squashfs
import (
"encoding/binary"
"errors"
"fmt"
"io"
"github.com/CalebQ42/GoSquashfs/internal/directory"
"github.com/CalebQ42/GoSquashfs/internal/inode"
)
const (
magic = 0x73717368
)
type Reader struct {
r io.ReaderAt
super Superblock
flags SuperblockFlags
decompressor Decompressor
}
func NewSquashfsReader(r io.ReaderAt) (*Reader, error) {
var rdr Reader
rdr.r = r
err := binary.Read(io.NewSectionReader(rdr.r, 0, int64(binary.Size(rdr.super))), binary.LittleEndian, &rdr.super)
if err != nil {
return nil, err
}
if rdr.super.Magic != magic {
return nil, errors.New("doesn't have magic number, probably isn't a squashfs")
}
rdr.flags = rdr.super.GetFlags()
switch rdr.super.CompressionType {
case gzipCompression:
rdr.decompressor = &ZlibDecompressor{}
default:
return nil, errors.New("Unsupported compression type")
}
if rdr.flags.CompressorOptions {
//TODO: parse compressor options
fmt.Println("Compressor options is NOT currently supported")
return nil, errors.New("Has compressor options")
}
return &rdr, nil
}
func (r *Reader) readRootDirTable() error {
offset, blockOffset := processInodeRef(r.super.RootInodeRef)
br, err := r.NewBlockReader(int64(r.super.InodeTableStart + offset))
if err != nil {
return err
}
_, err = br.Seek(int64(blockOffset), io.SeekStart)
if err != nil {
return err
}
i, err := inode.ProcessInode(br, r.super.BlockSize)
if err != nil {
return err
}
dirRdr, err := r.NewBlockReader(int64(r.super.DirTableStart + uint64(i.Info.(inode.BasicDirectory).DirectoryIndex)))
if err != nil {
return err
}
dir, err := directory.NewDirectory(dirRdr)
if err != nil {
return err
}
for _, entry := range dir.Entries {
fmt.Println(string(entry.Name))
}
return nil
}
+34 -30
View File
@@ -1,48 +1,52 @@
package squashfs package squashfs
const squashfsMagic = 0x73717368 const (
gzipCompression = 1 + iota
lzmaCompression
lzoCompression
xzCompression
lz4Compression
zstdCompression
)
//Superblock is a raw representation of a squashfs
//Descriptions provided by https://dr-emann.github.io/squashfs/
type Superblock struct { type Superblock struct {
Magic uint32 //Magic will be 0x73717368 if it's a legit Squashfs filesystem Magic uint32
Inodes uint32 //Inodes is the number of inodes in the inodes table InodeCount uint32
MkfsTime uint32 //MkfsTime is when archive was created CreationTime uint32
BlockSize uint32 //BlockSize is the size of data blocks in bytes BlockSize uint32
Fragments uint32 //Fragments is the number of entries in fragment table FragCount uint32
Compression uint16 //Compression is what type of compression is used CompressionType uint16
BlockLog uint16 //BlockLog should be log base 2 of BlockSize. If not then the squash might be corrupt BlockLog uint16
Flags uint16 //Flags are the superblock's flags Flags uint16
IDCount uint16 //IDCount is the number of IDs in the id lookup table IDCount uint16
Major uint16 //Major version of squashfs format MajorVersion uint16
Minor uint16 //Minor version of squashfs format MinorVersion uint16
RootInode uint64 //RootInode is a reference to the root of the squashfs RootInodeRef uint64
BytesUsed uint64 //BytesUsed is how many bytes the archive is. squashfs archives are often padded to 4KB. BytesUsed uint64
IDTableOffset uint64 //IDTableOff is the byte offset of the IDTable IDTableStart uint64
XattrIDTableOffset uint64 //XattrIDTableOffset is the byte offset of the xattr id table XattrTableStart uint64
InodeTableOffset uint64 //InodeTableOffset is the byte offset of the inode table InodeTableStart uint64
DirectoryTableOffset uint64 //DirectoryTableOffset is the byte offset of the directory table DirTableStart uint64
FragmentTableOffset uint64 //FragmentTableOffset is the byte offset of the fragment table FragTableStart uint64
ExportTableOffset uint64 //ExportTableOffset is the byte offset of the export table ExportTableStart uint64
} }
//SuperblockFlags is a parsed list of options set in Superblock.Flags
type SuperblockFlags struct { type SuperblockFlags struct {
UncompressedInodes bool UncompressedInodes bool
UncompressedData bool UncompressedData bool
Check bool //Check is unused in current versions of squashfs Check bool
UncompressedFragments bool UncompressedFragments bool
NoFragments bool NoFragments bool
AlwaysFragments bool AlwaysFragments bool
Duplicates bool //Identical files are stored only once Duplicates bool
Exportable bool Exportable bool
UncompressedXattrs bool UncompressedXattr bool
NoXattrs bool NoXattr bool
CompressorOptions bool CompressorOptions bool
UncompressedIDs bool UncompressedIDs bool
} }
func (s *Superblock) getFlags() SuperblockFlags { func (s *Superblock) GetFlags() SuperblockFlags {
return SuperblockFlags{ return SuperblockFlags{
UncompressedInodes: s.Flags&0x1 == 0x1, UncompressedInodes: s.Flags&0x1 == 0x1,
UncompressedData: s.Flags&0x2 == 0x2, UncompressedData: s.Flags&0x2 == 0x2,
@@ -52,8 +56,8 @@ func (s *Superblock) getFlags() SuperblockFlags {
AlwaysFragments: s.Flags&0x20 == 0x20, AlwaysFragments: s.Flags&0x20 == 0x20,
Duplicates: s.Flags&0x40 == 0x40, Duplicates: s.Flags&0x40 == 0x40,
Exportable: s.Flags&0x80 == 0x80, Exportable: s.Flags&0x80 == 0x80,
UncompressedXattrs: s.Flags&0x100 == 0x100, UncompressedXattr: s.Flags&0x100 == 0x100,
NoXattrs: s.Flags&0x200 == 0x200, NoXattr: s.Flags&0x200 == 0x200,
CompressorOptions: s.Flags&0x400 == 0x400, CompressorOptions: s.Flags&0x400 == 0x400,
UncompressedIDs: s.Flags&0x800 == 0x800, UncompressedIDs: s.Flags&0x800 == 0x800,
} }
+5 -3
View File
@@ -1,9 +1,11 @@
package inode package squashfs
//ProcessInodeRef processes an inode reference and returns two values //ProcessInodeRef processes an inode reference and returns two values
//The first value is the inode table offset. AKA, it's where the metadata block of the inode STARTS. //
//The first value is the inode table offset. AKA, it's where the metadata block of the inode STARTS relative to the inode table.
//
//The second value is the offset of the inode, INSIDE of the metadata. //The second value is the offset of the inode, INSIDE of the metadata.
func ProcessInodeRef(inodeRef uint64) (tableOffset uint64, metaOffset uint64) { func processInodeRef(inodeRef uint64) (tableOffset uint64, metaOffset uint64) {
tableOffset = inodeRef >> 16 tableOffset = inodeRef >> 16
metaOffset = inodeRef &^ 0xFFFFFFFF0000 metaOffset = inodeRef &^ 0xFFFFFFFF0000
return return