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
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
* Link directly to squashfs-tool using cgo
* 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
import (
"crypto/md5"
"fmt"
"io"
"os"
"testing"
"time"
// import (
// "crypto/md5"
// "fmt"
// "io"
// "os"
// "testing"
// "time"
"github.com/google/go-cmp/cmp"
)
// "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)
// }
// 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
}
// 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)
}
// 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[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[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)
}
// 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)
}
// 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 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)
}
}
// 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()
// // 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",
// 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)
}
// // 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)
}
// 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 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)
}
// 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)
}
// 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 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)
}
// 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)
}
// 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)
}
}
// 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)
}
// 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)
}
// 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 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)
}
// 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)
}
// 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 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)
}
// 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)
}
// 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 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)
}
// 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)
}
}
// // 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)
}
// 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)
}
// 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)
}
// 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)
}
// 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 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)
}
// 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)
}
}
}
// 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'
// // TODO: add test exercising ldirInodeHeader, e.g. '/mnt/loop/ca-certificates-3.39/buildoutput/etc/ssl'
func TestReadXattr(t *testing.T) {
t.Parallel()
// 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()
// // 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)
}
}
}
// 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)
// }
// }
// }
+23 -2
View File
@@ -1,4 +1,4 @@
package squashfs_test
package squashfs
import (
"io"
@@ -15,8 +15,29 @@ const (
squashfsName = "Code_OSS.Squashfs"
)
func TestCreateSquashFromAppImage(t *testing.T) {
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()
squash, err := NewSquashfs(squashFil)
if err != nil {
t.Error(err)
}
t.Fatal("Testing")
}
func TestCreateSquashFromAppImage(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
t.Fatal(err)
+47 -12
View File
@@ -1,24 +1,59 @@
package squashfs
//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
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
Fragments uint32 //Fragments is the number of entries in fragment table
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
Flags uint16 //Flags are the superblock's flags
IDCount uint16 //IDCount is the number of IDs in the id lookup table
Major uint16
Minor uint16
RootInode Inode
BytesUsed int64
IDTableStart int64
XattrIDTableStart int64
InodeTableStart int64
DirectoryTableStart int64
FragmentTableStart int64
LookupTableStart int64
Major uint16 //Major version of squashfs format
Minor uint16 //Minor version of squashfs format
RootInode uint64 //RootInode is a reference to the root of the squashfs
BytesUsed uint64 //BytesUsed is how many bytes the archive is. squashfs archives are often padded to 4KB.
IDTableOffset uint64 //IDTableOff is the byte offset of the IDTable
XattrIDTableOffset uint64 //XattrIDTableOffset is the byte offset of the xattr id table
InodeTableOffset uint64 //InodeTableOffset is the byte offset of the inode table
DirectoryTableOffset uint64 //DirectoryTableOffset is the byte offset of the directory table
FragmentTableOffset uint64 //FragmentTableOffset is the byte offset of the fragment table
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
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 {
rdr *io.Reader //underlyting reader
rdr *io.ReaderAt //underlying reader
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
import (
"bytes"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
// 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"
)
// "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")
// 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
}
// 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
}
// 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("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
}
// 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
}
// 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())
// 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
}
// 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
// // 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
}
// 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 := 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
}
// 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()
// func TestUnsquashfs(t *testing.T) {
// t.Parallel()
ctx, canc := distri.InterruptibleContext()
defer canc()
// ctx, canc := distri.InterruptibleContext()
// defer canc()
if _, err := exec.LookPath("unsquashfs"); err != nil {
t.Skip("unsquashfs not found in $PATH")
}
// 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)
}
// 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 := writeTestImage(f, xattr); err != nil {
// t.Fatal(err)
// }
if err := f.Close(); 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)
}
// // 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)
}
// 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)
}
})
}
})
}
}
// // 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()
// 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)
}
// 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)
}
// if _, err := buf.Seek(0, io.SeekCurrent); err != nil {
// t.Fatal(err)
// }
rd, err := NewReader(buf.BytesReader())
if 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)
}
// 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)
}
})
}
// // 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)
}
// 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)
}
// 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)
}
})
}
})
}
}
// 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)
// }
// })
// }
// })
// }
// }