Starting on writing the library

Currently just parses the superblock (but that works!)
This commit is contained in:
Caleb Gardner
2020-11-10 05:54:49 -06:00
parent 40541575f8
commit 630e6e0f7c
10 changed files with 2232 additions and 2094 deletions
+2
View File
@@ -1,6 +1,8 @@
# GoSquashfs # GoSquashfs
My playground to mess around with Squashfs in Go. Might turn into an actual library someday. Mainly for AppImage My playground to mess around with Squashfs in Go. Might turn into an actual library someday. Mainly for AppImage
Right Now it's mostly based on [distri's squashfs library](https://github.com/distr1/distri/tree/master/internal/squashfs)
# Ideas # Ideas
* Link directly to squashfs-tool using cgo * Link directly to squashfs-tool using cgo
* cgo can be a butt * cgo can be a butt
+45
View File
@@ -0,0 +1,45 @@
package squashfs
//CompressionOptions
type CompressionOptions interface {
Decompress([]byte) []byte
}
//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
}
type xzOptionsRaw struct {
dictionarySize int32
executableFilters int32
}
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
}
+553 -553
View File
File diff suppressed because it is too large Load Diff
+328 -328
View File
@@ -1,373 +1,373 @@
package squashfs package squashfs
import ( // import (
"crypto/md5" // "crypto/md5"
"fmt" // "fmt"
"io" // "io"
"os" // "os"
"testing" // "testing"
"time" // "time"
"github.com/google/go-cmp/cmp" // "github.com/google/go-cmp/cmp"
) // )
func cmpFileInfo(got os.FileInfo, want FileInfo) error { // func cmpFileInfo(got os.FileInfo, want FileInfo) error {
if got, want := got.Name(), want.name; got != want { // if got, want := got.Name(), want.name; got != want {
return fmt.Errorf("unexpected file name: got %q, want %q", got, want) // return fmt.Errorf("unexpected file name: got %q, want %q", got, want)
} // }
if got, want := got.Size(), want.size; got != want { // if got, want := got.Size(), want.size; got != want {
return fmt.Errorf("unexpected size: got %d, want %d", got, want) // return fmt.Errorf("unexpected size: got %d, want %d", got, want)
} // }
if got, want := got.IsDir(), want.mode.IsDir(); got != want { // if got, want := got.IsDir(), want.mode.IsDir(); got != want {
return fmt.Errorf("IsDir: got %v, want %v", got, want) // return fmt.Errorf("IsDir: got %v, want %v", got, want)
} // }
// TODO: re-enable when its no longer just a change detector // // TODO: re-enable when its no longer just a change detector
// if got, want := got.ModTime(), want.modTime; !got.Equal(want) { // // if got, want := got.ModTime(), want.modTime; !got.Equal(want) {
// return fmt.Errorf("IsDir: got %v, want %v", got, want) // // return fmt.Errorf("IsDir: got %v, want %v", got, want)
// } // // }
return nil // return nil
} // }
func TestReaddir(t *testing.T) { // func TestReaddir(t *testing.T) {
t.Parallel() // t.Parallel()
// TODO: ship testdata files generated by mksquashfs // // TODO: ship testdata files generated by mksquashfs
pwd, err := os.Getwd() // pwd, err := os.Getwd()
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
fmt.Println(pwd + "/testdata/testing.squashfs") // fmt.Println(pwd + "/testdata/testing.squashfs")
f, err := os.Open(pwd + "/testdata/testing.squashfs") // f, err := os.Open(pwd + "/testdata/testing.squashfs")
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
defer f.Close() // defer f.Close()
rd, err := NewReader(f) // rd, err := NewReader(f)
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
fis, err := rd.Readdir(rd.RootInode()) // fis, err := rd.Readdir(rd.RootInode())
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
for _, fi := range fis { // for _, fi := range fis {
fmt.Println(fi.Name()) // fmt.Println(fi.Name())
} // }
rdr, err := rd.FileReader(fis[0].Sys().(*FileInfo).Inode) // rdr, err := rd.FileReader(fis[0].Sys().(*FileInfo).Inode)
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
err = os.Remove(pwd + "/testdata/Magisk.zip") // err = os.Remove(pwd + "/testdata/Magisk.zip")
if !os.IsNotExist(err) && err != nil { // if !os.IsNotExist(err) && err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
// rdrzlib, err := zlib.NewReader(rdr) // // rdrzlib, err := zlib.NewReader(rdr)
magFil, err := os.Create(pwd + "/testdata/Magisk.zip") // magFil, err := os.Create(pwd + "/testdata/Magisk.zip")
io.Copy(magFil, rdr) // io.Copy(magFil, rdr)
if got, want := len(fis), 3; got != want { // if got, want := len(fis), 3; got != want {
t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) // t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want)
} // }
if err := cmpFileInfo(fis[0], FileInfo{ // if err := cmpFileInfo(fis[0], FileInfo{
name: "bin", // name: "bin",
size: 26, // size: 26,
mode: 0555 | os.ModeDir, // mode: 0555 | os.ModeDir,
modTime: time.Unix(1581275131, 0), // stat -c %Y /ro/ack-amd64-2.24/bin // modTime: time.Unix(1581275131, 0), // stat -c %Y /ro/ack-amd64-2.24/bin
}); err != nil { // }); err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
if err := cmpFileInfo(fis[1], FileInfo{ // if err := cmpFileInfo(fis[1], FileInfo{
name: "lib", // name: "lib",
size: 3, // size: 3,
mode: 0555 | os.ModeDir, // mode: 0555 | os.ModeDir,
modTime: time.Unix(1581275131, 0), // stat -c %Y /ro/ack-amd64-2.24/lib // modTime: time.Unix(1581275131, 0), // stat -c %Y /ro/ack-amd64-2.24/lib
}); err != nil { // }); err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
if err := cmpFileInfo(fis[2], FileInfo{ // if err := cmpFileInfo(fis[2], FileInfo{
name: "out", // name: "out",
size: 48, // size: 48,
mode: 0555 | os.ModeDir, // mode: 0555 | os.ModeDir,
modTime: time.Unix(1581275130, 0), // stat -c %Y /ro/ack-amd64-2.24/out // modTime: time.Unix(1581275130, 0), // stat -c %Y /ro/ack-amd64-2.24/out
}); err != nil { // }); err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
fis, err = rd.Readdir(fis[0].Sys().(*FileInfo).Inode) // fis, err = rd.Readdir(fis[0].Sys().(*FileInfo).Inode)
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
if got, want := len(fis), 1; got != want { // if got, want := len(fis), 1; got != want {
t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) // t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want)
} // }
if err := cmpFileInfo(fis[0], FileInfo{ // if err := cmpFileInfo(fis[0], FileInfo{
name: "ack", // name: "ack",
size: 38400, // size: 38400,
mode: 0755, // mode: 0755,
modTime: time.Unix(1581275131, 0), // stat -c %Y /ro/ack-amd64-2.24/bin/ack // modTime: time.Unix(1581275131, 0), // stat -c %Y /ro/ack-amd64-2.24/bin/ack
}); err != nil { // }); err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
} // }
// TestReaddirSmoke is a smoke-test, reading the root directories of SquashFS // // TestReaddirSmoke is a smoke-test, reading the root directories of SquashFS
// images which are known to trigger code paths which were buggy. // // images which are known to trigger code paths which were buggy.
func TestReaddirSmoke(t *testing.T) { // func TestReaddirSmoke(t *testing.T) {
t.Parallel() // t.Parallel()
for _, fn := range []string{ // for _, fn := range []string{
// bash exercises the code path where an inode is split across metadata // // bash exercises the code path where an inode is split across metadata
// blocks. // // blocks.
"/home/michael/distri/_build/distri/pkg/bash-amd64-5.0-4.squashfs", // "/home/michael/distri/_build/distri/pkg/bash-amd64-5.0-4.squashfs",
// cmake exercises the code path where the root directory entries are // // cmake exercises the code path where the root directory entries are
// located outside of the first block. // // located outside of the first block.
"/home/michael/distri/_build/distri/pkg/cmake-amd64-3.12.4-8.squashfs", // "/home/michael/distri/_build/distri/pkg/cmake-amd64-3.12.4-8.squashfs",
} { // } {
// TODO: ship testdata files generated by mksquashfs // // TODO: ship testdata files generated by mksquashfs
f, err := os.Open(fn) // f, err := os.Open(fn)
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
defer f.Close() // defer f.Close()
rd, err := NewReader(f) // rd, err := NewReader(f)
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
fis, err := rd.Readdir(rd.RootInode()) // fis, err := rd.Readdir(rd.RootInode())
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
if got, want := len(fis), 4; got != want { // if got, want := len(fis), 4; got != want {
t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) // t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want)
} // }
} // }
} // }
func TestReaddirEmpty(t *testing.T) { // func TestReaddirEmpty(t *testing.T) {
t.Parallel() // t.Parallel()
// TODO: ship testdata files generated by mksquashfs // // TODO: ship testdata files generated by mksquashfs
f, err := os.Open("/home/michael/distri/_build/distri/pkg/zlib-amd64-1.2.11-4.squashfs") // f, err := os.Open("/home/michael/distri/_build/distri/pkg/zlib-amd64-1.2.11-4.squashfs")
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
defer f.Close() // defer f.Close()
rd, err := NewReader(f) // rd, err := NewReader(f)
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
fis, err := rd.Readdir(rd.RootInode()) // fis, err := rd.Readdir(rd.RootInode())
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
if got, want := len(fis), 4; got != want { // if got, want := len(fis), 4; got != want {
t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) // t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want)
} // }
if err := cmpFileInfo(fis[0], FileInfo{ // if err := cmpFileInfo(fis[0], FileInfo{
name: "bin", // name: "bin",
size: 3, // size: 3,
mode: 0555 | os.ModeDir, // mode: 0555 | os.ModeDir,
modTime: time.Unix(1583085224, 0), // stat -c %Y /ro/zlib-amd64-1.2.11/bin // modTime: time.Unix(1583085224, 0), // stat -c %Y /ro/zlib-amd64-1.2.11/bin
}); err != nil { // }); err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
fis, err = rd.Readdir(fis[0].Sys().(*FileInfo).Inode) // fis, err = rd.Readdir(fis[0].Sys().(*FileInfo).Inode)
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
if got, want := len(fis), 0; got != want { // if got, want := len(fis), 0; got != want {
t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) // t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want)
} // }
} // }
func TestReaddirSymlink(t *testing.T) { // func TestReaddirSymlink(t *testing.T) {
t.Parallel() // t.Parallel()
// TODO: ship testdata files generated by mksquashfs // // TODO: ship testdata files generated by mksquashfs
f, err := os.Open("/home/michael/distri/_build/distri/pkg/zlib-amd64-1.2.11-4.squashfs") // f, err := os.Open("/home/michael/distri/_build/distri/pkg/zlib-amd64-1.2.11-4.squashfs")
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
defer f.Close() // defer f.Close()
rd, err := NewReader(f) // rd, err := NewReader(f)
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
fis, err := rd.Readdir(rd.RootInode()) // fis, err := rd.Readdir(rd.RootInode())
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
if got, want := len(fis), 4; got != want { // if got, want := len(fis), 4; got != want {
t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) // t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want)
} // }
if err := cmpFileInfo(fis[3], FileInfo{ // if err := cmpFileInfo(fis[3], FileInfo{
name: "out", // name: "out",
size: 54, // size: 54,
mode: 0555 | os.ModeDir, // mode: 0555 | os.ModeDir,
modTime: time.Unix(1583085224, 0), // stat -c %Y /ro/zlib-amd64-1.2.11/out // modTime: time.Unix(1583085224, 0), // stat -c %Y /ro/zlib-amd64-1.2.11/out
}); err != nil { // }); err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
fis, err = rd.Readdir(fis[3].Sys().(*FileInfo).Inode) // fis, err = rd.Readdir(fis[3].Sys().(*FileInfo).Inode)
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
if got, want := len(fis), 3; got != want { // if got, want := len(fis), 3; got != want {
t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) // t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want)
} // }
if err := cmpFileInfo(fis[1], FileInfo{ // if err := cmpFileInfo(fis[1], FileInfo{
name: "lib", // name: "lib",
size: 83, // size: 83,
mode: 0555 | os.ModeDir, // mode: 0555 | os.ModeDir,
modTime: time.Unix(1583085224, 0), // stat -c %Y /ro/zlib-amd64-1.2.11/out/lib // modTime: time.Unix(1583085224, 0), // stat -c %Y /ro/zlib-amd64-1.2.11/out/lib
}); err != nil { // }); err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
fis, err = rd.Readdir(fis[1].Sys().(*FileInfo).Inode) // fis, err = rd.Readdir(fis[1].Sys().(*FileInfo).Inode)
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
if got, want := len(fis), 4; got != want { // if got, want := len(fis), 4; got != want {
t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) // t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want)
} // }
if err := cmpFileInfo(fis[1], FileInfo{ // if err := cmpFileInfo(fis[1], FileInfo{
name: "libz.so", // name: "libz.so",
size: 9, // size: 9,
mode: 0555 | os.ModeSymlink, // mode: 0555 | os.ModeSymlink,
modTime: time.Unix(1583085223, 0), // stat -c %Y /ro/zlib-amd64-1.2.11/out/lib/libz.so // modTime: time.Unix(1583085223, 0), // stat -c %Y /ro/zlib-amd64-1.2.11/out/lib/libz.so
}); err != nil { // }); err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
// TODO: readlink // // TODO: readlink
target, err := rd.ReadLink(fis[1].Sys().(*FileInfo).Inode) // target, err := rd.ReadLink(fis[1].Sys().(*FileInfo).Inode)
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
if got, want := target, "libz.so.1"; got != want { // if got, want := target, "libz.so.1"; got != want {
t.Fatalf("ReadLink(libz.so): got %q, want %q", got, want) // t.Fatalf("ReadLink(libz.so): got %q, want %q", got, want)
} // }
} // }
func TestReadfile(t *testing.T) { // func TestReadfile(t *testing.T) {
t.Parallel() // t.Parallel()
// TODO: ship testdata files generated by mksquashfs // // TODO: ship testdata files generated by mksquashfs
f, err := os.Open("/home/michael/distri/_build/distri/pkg/ack-amd64-3.3.1-7.squashfs") // f, err := os.Open("/home/michael/distri/_build/distri/pkg/ack-amd64-3.3.1-7.squashfs")
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
defer f.Close() // defer f.Close()
rd, err := NewReader(f) // rd, err := NewReader(f)
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
fis, err := rd.Readdir(rd.RootInode()) // fis, err := rd.Readdir(rd.RootInode())
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
if got, want := len(fis), 3; got != want { // if got, want := len(fis), 3; got != want {
t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) // t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want)
} // }
fis, err = rd.Readdir(fis[0].Sys().(*FileInfo).Inode) // fis, err = rd.Readdir(fis[0].Sys().(*FileInfo).Inode)
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
if got, want := len(fis), 1; got != want { // if got, want := len(fis), 1; got != want {
t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) // t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want)
} // }
r, err := rd.FileReader(fis[0].Sys().(*FileInfo).Inode) // r, err := rd.FileReader(fis[0].Sys().(*FileInfo).Inode)
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
for i := 0; i < 2; i++ { // for i := 0; i < 2; i++ {
if _, err := r.Seek(0, io.SeekStart); err != nil { // if _, err := r.Seek(0, io.SeekStart); err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
h := md5.New() // h := md5.New()
if _, err := io.Copy(h, r); err != nil { // if _, err := io.Copy(h, r); err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
sum := fmt.Sprintf("%x", h.Sum(nil)) // sum := fmt.Sprintf("%x", h.Sum(nil))
if got, want := sum, "c6c9b5d4d2a49f1b8b5e501f0f827a5c"; got != want { // if got, want := sum, "c6c9b5d4d2a49f1b8b5e501f0f827a5c"; got != want {
t.Fatalf("md5(bin/ack): got %s, want %s", 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' // // TODO: add test exercising ldirInodeHeader, e.g. '/mnt/loop/ca-certificates-3.39/buildoutput/etc/ssl'
func TestReadXattr(t *testing.T) { // func TestReadXattr(t *testing.T) {
t.Parallel() // t.Parallel()
// TODO: generate a smaller version of this file // // TODO: generate a smaller version of this file
f, err := os.Open("testdata/xattr.squashfs") // f, err := os.Open("testdata/xattr.squashfs")
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
defer f.Close() // defer f.Close()
rd, err := NewReader(f) // rd, err := NewReader(f)
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
for _, tt := range []struct { // for _, tt := range []struct {
Path string // Path string
Want []Xattr // Want []Xattr
}{ // }{
{ // {
Path: "mtr-packet", // Path: "mtr-packet",
Want: []Xattr{ // Want: []Xattr{
{ // {
Type: XattrTypeSecurity, // Type: XattrTypeSecurity,
FullName: "security.capability", // 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}}, // 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", // Path: "gnome-keyring-daemon",
Want: []Xattr{ // Want: []Xattr{
{ // {
Type: XattrTypeSecurity, // Type: XattrTypeSecurity,
FullName: "security.capability", // 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}}, // 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) // inode, err := rd.LookupPath(tt.Path)
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
xattrs, err := rd.ReadXattrs(inode) // xattrs, err := rd.ReadXattrs(inode)
if err != nil { // if err != nil {
t.Fatalf("ReadXattrs(%v): %v", inode, err) // t.Fatalf("ReadXattrs(%v): %v", inode, err)
} // }
if diff := cmp.Diff(tt.Want, xattrs); diff != "" { // if diff := cmp.Diff(tt.Want, xattrs); diff != "" {
t.Fatalf("unexpected ReadXattrs result: diff (-want +got):\n%s", diff) // t.Fatalf("unexpected ReadXattrs result: diff (-want +got):\n%s", diff)
} // }
} // }
} // }
+23 -2
View File
@@ -1,4 +1,4 @@
package squashfs_test package squashfs
import ( import (
"io" "io"
@@ -15,8 +15,29 @@ const (
squashfsName = "Code_OSS.Squashfs" squashfsName = "Code_OSS.Squashfs"
) )
func TestCreateSquashFromAppImage(t *testing.T) { func TestAppImageSquash(t *testing.T) {
t.Parallel() 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()
squash, err := NewSquashfs(squashFil)
if err != nil {
t.Error(err)
}
t.Fatal("Testing")
}
func TestCreateSquashFromAppImage(t *testing.T) {
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
+47 -12
View File
@@ -1,24 +1,59 @@
package squashfs package squashfs
//Superblock is a raw representation of a squashfs
//Descriptions provided by https://dr-emann.github.io/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 //Magic will be 0x73717368 if it's a legit Squashfs filesystem
Inodes uint32 //Inodes is the number of inodes in the inodes table Inodes uint32 //Inodes is the number of inodes in the inodes table
MkfsTime int32 //MkfsTime is when archive was created MkfsTime uint32 //MkfsTime is when archive was created
BlockSize uint32 //BlockSize is the size of data blocks in bytes BlockSize uint32 //BlockSize is the size of data blocks in bytes
Fragments uint32 //Fragments is the number of entries in fragment table Fragments uint32 //Fragments is the number of entries in fragment table
Compression uint16 //Compression is what type of compression is used Compression uint16 //Compression is what type of compression is used
BlockLog uint16 //BlockLog should be log base 2 of BlockSize. If not then the squash might be corrupt BlockLog uint16 //BlockLog should be log base 2 of BlockSize. If not then the squash might be corrupt
Flags uint16 //Flags are the superblock's flags Flags uint16 //Flags are the superblock's flags
IDCount uint16 //IDCount is the number of IDs in the id lookup table IDCount uint16 //IDCount is the number of IDs in the id lookup table
Major uint16 Major uint16 //Major version of squashfs format
Minor uint16 Minor uint16 //Minor version of squashfs format
RootInode Inode RootInode uint64 //RootInode is a reference to the root of the squashfs
BytesUsed int64 BytesUsed uint64 //BytesUsed is how many bytes the archive is. squashfs archives are often padded to 4KB.
IDTableStart int64 IDTableOffset uint64 //IDTableOff is the byte offset of the IDTable
XattrIDTableStart int64 XattrIDTableOffset uint64 //XattrIDTableOffset is the byte offset of the xattr id table
InodeTableStart int64 InodeTableOffset uint64 //InodeTableOffset is the byte offset of the inode table
DirectoryTableStart int64 DirectoryTableOffset uint64 //DirectoryTableOffset is the byte offset of the directory table
FragmentTableStart int64 FragmentTableOffset uint64 //FragmentTableOffset is the byte offset of the fragment table
LookupTableStart int64 ExportTableOffset uint64 //ExportTableOffset is the byte offset of the export table
}
//SuperblockFlags is a parsed list of options set in Superblock.Flags
type SuperblockFlags struct {
UncompressedInodes bool
UncompressedData bool
Check bool //Check is unused in current versions of squashfs
UncompressedFragments bool
NoFragments bool
AlwaysFragments bool
Duplicates bool //Identical files are stored only once
Exportable bool
UncompressedXattrs bool
NoXattrs bool
CompressorOptions bool
UncompressedIDs bool
}
//GetFlags returns the Flags parsed into a SuperblockFlags
func (s *Superblock) GetFlags() SuperblockFlags {
return SuperblockFlags{
UncompressedInodes: s.Flags&0x1 == 0x1,
UncompressedData: s.Flags&0x2 == 0x2,
Check: s.Flags&0x4 == 0x4,
UncompressedFragments: s.Flags&0x8 == 0x8,
NoFragments: s.Flags&0x10 == 0x10,
AlwaysFragments: s.Flags&0x20 == 0x20,
Duplicates: s.Flags&0x40 == 0x40,
Exportable: s.Flags&0x80 == 0x80,
UncompressedXattrs: s.Flags&0x100 == 0x100,
NoXattrs: s.Flags&0x200 == 0x200,
CompressorOptions: s.Flags&0x400 == 0x400,
UncompressedIDs: s.Flags&0x800 == 0x800,
}
} }
+14
View File
@@ -0,0 +1,14 @@
package squashfs
import "io"
func uncompressData(data []byte, compressionType int) []byte {
//TODO: check compression type and uncompress the data
return make([]byte, 0)
}
//same os uncompressData, but uses a reader instead. reader's seek will be
func uncompressReaderData(reader *io.Reader, compressionType int) []byte {
//TODO: check compression type and uncompress the data
return make([]byte, 0)
}
+24 -3
View File
@@ -1,9 +1,30 @@
package squashfs package squashfs
import "io" import (
"encoding/binary"
"io"
)
//Squashfs is a squashfs backed by a reader. //Squashfs is a squashfs backed by a ReadSeeker.
type Squashfs struct { type Squashfs struct {
rdr *io.Reader //underlyting reader rdr *io.ReaderAt //underlying reader
super Superblock super Superblock
} }
//NewSquashfs creates a new Squashfs backed by the given reader
func NewSquashfs(reader io.ReaderAt) (*Squashfs, error) {
var superblock Superblock
err := binary.Read(io.NewSectionReader(reader, 0, int64(binary.Size(superblock))), binary.LittleEndian, &superblock)
if err != nil {
return nil, err
}
return &Squashfs{
rdr: &reader,
super: superblock,
}, nil
}
//GetFlags return the SuperblockFlags of the Squashfs
func (s *Squashfs) GetFlags() SuperblockFlags {
return s.super.GetFlags()
}
+903 -903
View File
File diff suppressed because it is too large Load Diff
+277 -277
View File
@@ -1,310 +1,310 @@
package squashfs package squashfs
import ( // import (
"bytes" // "bytes"
"flag" // "flag"
"fmt" // "fmt"
"io" // "io"
"io/ioutil" // "io/ioutil"
"os" // "os"
"os/exec" // "os/exec"
"path/filepath" // "path/filepath"
"strings" // "strings"
"testing" // "testing"
"time" // "time"
"github.com/distr1/distri" // "github.com/distr1/distri"
// "github.com/distr1/distri/internal/distritest" // // "github.com/distr1/distri/internal/distritest"
"github.com/google/go-cmp/cmp" // "github.com/google/go-cmp/cmp"
"github.com/orcaman/writerseeker" // "github.com/orcaman/writerseeker"
"golang.org/x/sys/unix" // "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") // 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 { // func writeTestImage(iow io.WriteSeeker, xattr bool) error {
w, err := NewWriter(iow, time.Now()) // w, err := NewWriter(iow, time.Now())
if err != nil { // if err != nil {
return err // return err
} // }
var xattrs []Xattr // var xattrs []Xattr
if xattr { // if xattr {
xattrs = append(xattrs, Xattr{ // xattrs = append(xattrs, Xattr{
Type: 2, // Type: 2,
FullName: "capability", // FullName: "capability",
Value: []byte{1, 0, 0, 2, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 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) // ff, err := w.Root.File("hellö wörld", time.Now(), unix.S_IRUSR|unix.S_IRGRP|unix.S_IROTH, xattrs)
if err != nil { // if err != nil {
return err // return err
} // }
if _, err := ff.Write([]byte("hello world!")); err != nil { // if _, err := ff.Write([]byte("hello world!")); err != nil {
return err // return err
} // }
if err := ff.Close(); err != nil { // if err := ff.Close(); err != nil {
return err // return err
} // }
ff, err = w.Root.File("leer", time.Now(), unix.S_IRUSR|unix.S_IRGRP|unix.S_IROTH, nil) // ff, err = w.Root.File("leer", time.Now(), unix.S_IRUSR|unix.S_IRGRP|unix.S_IROTH, nil)
if err != nil { // if err != nil {
return err // return err
} // }
if err := ff.Close(); err != nil { // if err := ff.Close(); err != nil {
return err // return err
} // }
ff, err = w.Root.File("second file", time.Now(), unix.S_IRUSR|unix.S_IXUSR| // ff, err = w.Root.File("second file", time.Now(), unix.S_IRUSR|unix.S_IXUSR|
unix.S_IRGRP|unix.S_IXGRP| // unix.S_IRGRP|unix.S_IXGRP|
unix.S_IROTH|unix.S_IXOTH, // unix.S_IROTH|unix.S_IXOTH,
nil) // nil)
if err != nil { // if err != nil {
return err // return err
} // }
if _, err := ff.Write([]byte("NON.\n")); err != nil { // if _, err := ff.Write([]byte("NON.\n")); err != nil {
return err // return err
} // }
if err := ff.Close(); err != nil { // if err := ff.Close(); err != nil {
return err // return err
} // }
if err := w.Root.Symlink("second file", "second link", time.Now(), unix.S_IRUSR|unix.S_IRGRP|unix.S_IROTH); err != nil { // if err := w.Root.Symlink("second file", "second link", time.Now(), unix.S_IRUSR|unix.S_IRGRP|unix.S_IROTH); err != nil {
return err // return err
} // }
subdir := w.Root.Directory("subdir", time.Now()) // subdir := w.Root.Directory("subdir", time.Now())
subsubdir := subdir.Directory("deep", 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) // ff, err = subsubdir.File("yo", time.Now(), unix.S_IRUSR|unix.S_IRGRP|unix.S_IROTH, nil)
if err != nil { // if err != nil {
return err // return err
} // }
if _, err := ff.Write([]byte("foo\n")); err != nil { // if _, err := ff.Write([]byte("foo\n")); err != nil {
return err // return err
} // }
if err := ff.Close(); err != nil { // if err := ff.Close(); err != nil {
return err // return err
} // }
if err := subsubdir.Flush(); err != nil { // if err := subsubdir.Flush(); err != nil {
return err // return err
} // }
// TODO: write another file in subdir now, will result in invalid parent inode // // 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) // ff, err = subdir.File("third file (in subdir)", time.Now(), unix.S_IRUSR|unix.S_IRGRP|unix.S_IROTH, nil)
if err != nil { // if err != nil {
return err // return err
} // }
if _, err := ff.Write([]byte("contents\n")); err != nil { // if _, err := ff.Write([]byte("contents\n")); err != nil {
return err // return err
} // }
if err := ff.Close(); err != nil { // if err := ff.Close(); err != nil {
return err // return err
} // }
if err := subdir.Flush(); err != nil { // if err := subdir.Flush(); err != nil {
return err // return err
} // }
ff, err = w.Root.File("testbin", time.Now(), unix.S_IRUSR|unix.S_IXUSR| // ff, err = w.Root.File("testbin", time.Now(), unix.S_IRUSR|unix.S_IXUSR|
unix.S_IRGRP|unix.S_IXGRP| // unix.S_IRGRP|unix.S_IXGRP|
unix.S_IROTH|unix.S_IXOTH, // unix.S_IROTH|unix.S_IXOTH,
nil) // nil)
if err != nil { // if err != nil {
return err // return err
} // }
zf, err := os.Open(os.Args[0]) // zf, err := os.Open(os.Args[0])
if err != nil { // if err != nil {
return err // return err
} // }
defer zf.Close() // defer zf.Close()
if _, err := io.Copy(ff, zf); err != nil { // if _, err := io.Copy(ff, zf); err != nil {
return err // return err
} // }
if err := ff.Close(); err != nil { // if err := ff.Close(); err != nil {
return err // return err
} // }
if err := w.Root.Flush(); err != nil { // if err := w.Root.Flush(); err != nil {
return err // return err
} // }
if err := w.Flush(); err != nil { // if err := w.Flush(); err != nil {
return err // return err
} // }
return nil // return nil
} // }
func TestUnsquashfs(t *testing.T) { // func TestUnsquashfs(t *testing.T) {
t.Parallel() // t.Parallel()
ctx, canc := distri.InterruptibleContext() // ctx, canc := distri.InterruptibleContext()
defer canc() // defer canc()
if _, err := exec.LookPath("unsquashfs"); err != nil { // if _, err := exec.LookPath("unsquashfs"); err != nil {
t.Skip("unsquashfs not found in $PATH") // t.Skip("unsquashfs not found in $PATH")
} // }
for _, xattr := range []bool{false, true} { // for _, xattr := range []bool{false, true} {
t.Run(fmt.Sprintf("xattr %v", xattr), func(t *testing.T) { // t.Run(fmt.Sprintf("xattr %v", xattr), func(t *testing.T) {
var ( // var (
f *os.File // f *os.File
err error // err error
) // )
if *fsImagePath != "" { // if *fsImagePath != "" {
f, err = os.Create(*fsImagePath + fmt.Sprintf("-xattr-%v", xattr)) // f, err = os.Create(*fsImagePath + fmt.Sprintf("-xattr-%v", xattr))
} else { // } else {
f, err = ioutil.TempFile("", fmt.Sprintf("squashfs-xattr-%v", xattr)) // f, err = ioutil.TempFile("", fmt.Sprintf("squashfs-xattr-%v", xattr))
if err == nil { // if err == nil {
defer os.Remove(f.Name()) // defer os.Remove(f.Name())
} // }
} // }
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
if err := writeTestImage(f, xattr); err != nil { // if err := writeTestImage(f, xattr); err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
if err := f.Close(); err != nil { // if err := f.Close(); err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
// Extract our generated file system using unsquashfs(1) // // Extract our generated file system using unsquashfs(1)
out, err := ioutil.TempDir("", fmt.Sprintf("unsquashfs-xattr-%v", xattr)) // out, err := ioutil.TempDir("", fmt.Sprintf("unsquashfs-xattr-%v", xattr))
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
// defer distritest.RemoveAll(t, out) // // defer distritest.RemoveAll(t, out)
cmd := exec.CommandContext(ctx, "unsquashfs", "-no-xattrs", "-d", filepath.Join(out, "x"), f.Name()) // cmd := exec.CommandContext(ctx, "unsquashfs", "-no-xattrs", "-d", filepath.Join(out, "x"), f.Name())
cmd.Stderr = os.Stderr // cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil { // if err := cmd.Run(); err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
fbin, err := os.Open(os.Args[0]) // fbin, err := os.Open(os.Args[0])
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
// Verify the extracted files match our expectations. // // Verify the extracted files match our expectations.
for _, entry := range []struct { // for _, entry := range []struct {
path string // path string
contents io.Reader // contents io.Reader
}{ // }{
{"leer", strings.NewReader("")}, // {"leer", strings.NewReader("")},
{"hellö wörld", strings.NewReader("hello world!")}, // {"hellö wörld", strings.NewReader("hello world!")},
{"testbin", fbin}, // {"testbin", fbin},
{"subdir/third file (in subdir)", strings.NewReader("contents\n")}, // {"subdir/third file (in subdir)", strings.NewReader("contents\n")},
} { // } {
entry := entry // copy // entry := entry // copy
t.Run(entry.path, func(t *testing.T) { // t.Run(entry.path, func(t *testing.T) {
in, err := os.Open(filepath.Join(out, "x", entry.path)) // in, err := os.Open(filepath.Join(out, "x", entry.path))
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
got, err := ioutil.ReadAll(in) // got, err := ioutil.ReadAll(in)
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
want, err := ioutil.ReadAll(entry.contents) // want, err := ioutil.ReadAll(entry.contents)
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
if !bytes.Equal(got, want) { // if !bytes.Equal(got, want) {
t.Fatalf("path %q differs", entry.path) // t.Fatalf("path %q differs", entry.path)
} // }
}) // })
} // }
}) // })
} // }
} // }
func TestReader(t *testing.T) { // func TestReader(t *testing.T) {
t.Parallel() // t.Parallel()
for _, xattr := range []bool{false, true} { // for _, xattr := range []bool{false, true} {
t.Run(fmt.Sprintf("xattr %v", xattr), func(t *testing.T) { // t.Run(fmt.Sprintf("xattr %v", xattr), func(t *testing.T) {
var err error // var err error
buf := &writerseeker.WriterSeeker{} // buf := &writerseeker.WriterSeeker{}
if err := writeTestImage(buf, xattr); err != nil { // if err := writeTestImage(buf, xattr); err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
if _, err := buf.Seek(0, io.SeekCurrent); err != nil { // if _, err := buf.Seek(0, io.SeekCurrent); err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
rd, err := NewReader(buf.BytesReader()) // rd, err := NewReader(buf.BytesReader())
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
fbin, err := os.Open(os.Args[0]) // fbin, err := os.Open(os.Args[0])
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
// Verify the extracted files match our expectations. // // Verify the extracted files match our expectations.
for _, entry := range []struct { // for _, entry := range []struct {
path string // path string
contents io.Reader // contents io.Reader
}{ // }{
{"leer", strings.NewReader("")}, // {"leer", strings.NewReader("")},
{"hellö wörld", strings.NewReader("hello world!")}, // {"hellö wörld", strings.NewReader("hello world!")},
{"testbin", fbin}, // {"testbin", fbin},
{"subdir/third file (in subdir)", strings.NewReader("contents\n")}, // {"subdir/third file (in subdir)", strings.NewReader("contents\n")},
} { // } {
entry := entry // copy // entry := entry // copy
t.Run(entry.path, func(t *testing.T) { // t.Run(entry.path, func(t *testing.T) {
// TODO: is this t.Parallel()-safe? // // TODO: is this t.Parallel()-safe?
inode, err := rd.LookupPath(entry.path) // inode, err := rd.LookupPath(entry.path)
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
in, err := rd.FileReader(inode) // in, err := rd.FileReader(inode)
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
got, err := ioutil.ReadAll(in) // got, err := ioutil.ReadAll(in)
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
want, err := ioutil.ReadAll(entry.contents) // want, err := ioutil.ReadAll(entry.contents)
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
if !bytes.Equal(got, want) { // if !bytes.Equal(got, want) {
t.Fatalf("path %q differs", entry.path) // t.Fatalf("path %q differs", entry.path)
} // }
}) // })
} // }
if xattr { // if xattr {
t.Run("xattrs", func(t *testing.T) { // t.Run("xattrs", func(t *testing.T) {
inode, err := rd.LookupPath("hellö wörld") // inode, err := rd.LookupPath("hellö wörld")
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
xattrs, err := rd.ReadXattrs(inode) // xattrs, err := rd.ReadXattrs(inode)
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
if got, want := len(xattrs), 1; got != want { // if got, want := len(xattrs), 1; got != want {
t.Fatalf("unexpected number of extended attributes: got %d, want %d", got, want) // t.Fatalf("unexpected number of extended attributes: got %d, want %d", got, want)
} // }
wantXattr := Xattr{ // wantXattr := Xattr{
Type: 2, // Type: 2,
FullName: "security.capability", // 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}, // 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 != "" { // if diff := cmp.Diff(wantXattr, xattrs[0]); diff != "" {
t.Errorf("unexpected extended attribute: diff (-want +got):\n%s", diff) // t.Errorf("unexpected extended attribute: diff (-want +got):\n%s", diff)
} // }
}) // })
} // }
}) // })
} // }
} // }