From e0c1309ed4bde952271f59be1760c13ac100502d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20F=20Bj=C3=B6rklund?= Date: Sat, 15 Mar 2025 17:32:27 +0100 Subject: [PATCH 1/4] Add list option to unsquashfs --- cmd/go-unsquashfs/main.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cmd/go-unsquashfs/main.go b/cmd/go-unsquashfs/main.go index 75be53f..9915b0a 100644 --- a/cmd/go-unsquashfs/main.go +++ b/cmd/go-unsquashfs/main.go @@ -3,7 +3,9 @@ package main import ( "flag" "fmt" + "io/fs" "os" + "path/filepath" "time" "github.com/CalebQ42/squashfs" @@ -11,6 +13,7 @@ import ( func main() { verbose := flag.Bool("v", false, "Verbose") + list := flag.Bool("l", false, "List") ignore := flag.Bool("ip", false, "Ignore Permissions and extract all files/folders with 0755") flag.Parse() if len(flag.Args()) < 2 { @@ -25,6 +28,17 @@ func main() { if err != nil { panic(err) } + if *list { + root := flag.Arg(1) + fs.WalkDir(r, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + panic(err) + } + fmt.Println(filepath.Join(root, path)) + return nil + }) + return + } op := squashfs.DefaultOptions() op.Verbose = *verbose op.IgnorePerm = *ignore From 24a9457c6bcd3332d2ac0ab02c4cc99bb13488fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20F=20Bj=C3=B6rklund?= Date: Sat, 15 Mar 2025 17:35:40 +0100 Subject: [PATCH 2/4] Refactor: export FileInfo --- file.go | 2 +- file_info.go | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/file.go b/file.go index d52e2b9..1024fa1 100644 --- a/file.go +++ b/file.go @@ -127,7 +127,7 @@ func (f *File) ReadDir(n int) ([]fs.DirEntry, error) { } } var out []fs.DirEntry - var fi fileInfo + var fi FileInfo for _, e := range d.Entries[start:end] { fi, err = f.r.newFileInfo(e) if err != nil { diff --git a/file_info.go b/file_info.go index 74ecdbc..06f90c7 100644 --- a/file_info.go +++ b/file_info.go @@ -8,7 +8,7 @@ import ( "github.com/CalebQ42/squashfs/low/inode" ) -type fileInfo struct { +type FileInfo struct { name string size int64 perm uint32 @@ -16,15 +16,15 @@ type fileInfo struct { fileType uint16 } -func (r Reader) newFileInfo(e directory.Entry) (fileInfo, error) { +func (r Reader) newFileInfo(e directory.Entry) (FileInfo, error) { i, err := r.Low.InodeFromEntry(e) if err != nil { - return fileInfo{}, err + return FileInfo{}, err } return newFileInfo(e.Name, &i), nil } -func newFileInfo(name string, i *inode.Inode) fileInfo { +func newFileInfo(name string, i *inode.Inode) FileInfo { var size int64 switch i.Type { case inode.Fil: @@ -32,7 +32,7 @@ func newFileInfo(name string, i *inode.Inode) fileInfo { case inode.EFil: size = int64(i.Data.(inode.EFile).Size) } - return fileInfo{ + return FileInfo{ name: name, size: size, perm: uint32(i.Perm), @@ -41,15 +41,15 @@ func newFileInfo(name string, i *inode.Inode) fileInfo { } } -func (f fileInfo) Name() string { +func (f FileInfo) Name() string { return f.name } -func (f fileInfo) Size() int64 { +func (f FileInfo) Size() int64 { return f.size } -func (f fileInfo) Mode() fs.FileMode { +func (f FileInfo) Mode() fs.FileMode { switch f.fileType { case inode.Dir, inode.EDir: return fs.FileMode(f.perm | uint32(fs.ModeDir)) @@ -65,31 +65,31 @@ func (f fileInfo) Mode() fs.FileMode { return fs.FileMode(f.perm) } -func (f fileInfo) ModTime() time.Time { +func (f FileInfo) ModTime() time.Time { return time.Unix(int64(f.modTime), 0) } -func (f fileInfo) IsDir() bool { +func (f FileInfo) IsDir() bool { return f.fileType == inode.Dir || f.fileType == inode.EDir } -func (f fileInfo) IsSymlink() bool { +func (f FileInfo) IsSymlink() bool { return f.fileType == inode.Sym || f.fileType == inode.ESym } -func (f fileInfo) IsDevice() bool { +func (f FileInfo) IsDevice() bool { return f.fileType == inode.Block || f.fileType == inode.EBlock || f.fileType == inode.Char || f.fileType == inode.EChar } -func (f fileInfo) IsFifo() bool { +func (f FileInfo) IsFifo() bool { return f.fileType == inode.Fifo || f.fileType == inode.EFifo } -func (f fileInfo) IsSocket() bool { +func (f FileInfo) IsSocket() bool { return f.fileType == inode.Sock || f.fileType == inode.ESock } -func (f fileInfo) Sys() any { +func (f FileInfo) Sys() any { return nil } From cef9090210413bf7b9a716f512bf8fa4970f4340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20F=20Bj=C3=B6rklund?= Date: Sat, 15 Mar 2025 17:43:55 +0100 Subject: [PATCH 3/4] Add support for symlinks --- cmd/go-unsquashfs/main.go | 27 +++++++++++++++++++++++++-- file_info.go | 11 +++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/cmd/go-unsquashfs/main.go b/cmd/go-unsquashfs/main.go index 9915b0a..fe4ce13 100644 --- a/cmd/go-unsquashfs/main.go +++ b/cmd/go-unsquashfs/main.go @@ -6,14 +6,33 @@ import ( "io/fs" "os" "path/filepath" + "strings" "time" "github.com/CalebQ42/squashfs" ) +func printEntry(root, path string, d fs.DirEntry) { + fi, _ := d.Info() + sfi := fi.(squashfs.FileInfo) + owner := fmt.Sprintf("%d/%d", + sfi.Uid(), + sfi.Gid()) + link := "" + if sfi.IsSymlink() { + link = " -> " + sfi.SymlinkPath() + } + fmt.Printf("%s %s %*d %s %s%s\n", + strings.ToLower(fi.Mode().String()), + owner, 26-len(owner), fi.Size(), + fi.ModTime().Format("2006-01-02 15:04"), + filepath.Join(root, path), link) +} + func main() { verbose := flag.Bool("v", false, "Verbose") list := flag.Bool("l", false, "List") + long := flag.Bool("ll", false, "List with attributes") ignore := flag.Bool("ip", false, "Ignore Permissions and extract all files/folders with 0755") flag.Parse() if len(flag.Args()) < 2 { @@ -28,13 +47,17 @@ func main() { if err != nil { panic(err) } - if *list { + if *list || *long { root := flag.Arg(1) fs.WalkDir(r, ".", func(path string, d fs.DirEntry, err error) error { if err != nil { panic(err) } - fmt.Println(filepath.Join(root, path)) + if *long { + printEntry(root, path, d) + } else { + fmt.Println(filepath.Join(root, path)) + } return nil }) return diff --git a/file_info.go b/file_info.go index 06f90c7..6c00017 100644 --- a/file_info.go +++ b/file_info.go @@ -11,6 +11,7 @@ import ( type FileInfo struct { name string size int64 + target string perm uint32 modTime uint32 fileType uint16 @@ -26,15 +27,21 @@ func (r Reader) newFileInfo(e directory.Entry) (FileInfo, error) { func newFileInfo(name string, i *inode.Inode) FileInfo { var size int64 + var target string switch i.Type { case inode.Fil: size = int64(i.Data.(inode.File).Size) case inode.EFil: size = int64(i.Data.(inode.EFile).Size) + case inode.Sym: + target = string(i.Data.(inode.Symlink).Target) + case inode.ESym: + target = string(i.Data.(inode.ESymlink).Target) } return FileInfo{ name: name, size: size, + target: target, perm: uint32(i.Perm), modTime: i.ModTime, fileType: i.Type, @@ -49,6 +56,10 @@ func (f FileInfo) Size() int64 { return f.size } +func (f FileInfo) SymlinkPath() string { + return f.target +} + func (f FileInfo) Mode() fs.FileMode { switch f.fileType { case inode.Dir, inode.EDir: From e6b0b83dcb76e2242ba5e9739c01a1bde129b692 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20F=20Bj=C3=B6rklund?= Date: Sat, 15 Mar 2025 17:49:27 +0100 Subject: [PATCH 4/4] Add support for uid/gid --- cmd/go-unsquashfs/main.go | 39 ++++++++++++++++++++++++++++++++------- file.go | 10 +++++++++- file_info.go | 26 +++++++++++++++++++++++--- 3 files changed, 64 insertions(+), 11 deletions(-) diff --git a/cmd/go-unsquashfs/main.go b/cmd/go-unsquashfs/main.go index fe4ce13..1b6a530 100644 --- a/cmd/go-unsquashfs/main.go +++ b/cmd/go-unsquashfs/main.go @@ -5,19 +5,43 @@ import ( "fmt" "io/fs" "os" + "os/user" "path/filepath" + "strconv" "strings" "time" "github.com/CalebQ42/squashfs" ) -func printEntry(root, path string, d fs.DirEntry) { +func userName(uid int, numeric bool) string { + us := strconv.Itoa(uid) + if numeric { + return us + } + if u, err := user.LookupId(us); err == nil { + return u.Username + } + return us +} + +func groupName(gid int, numeric bool) string { + gs := strconv.Itoa(gid) + if numeric { + return gs + } + if g, err := user.LookupGroupId(gs); err == nil { + return g.Name + } + return gs +} + +func printEntry(root, path string, d fs.DirEntry, numeric bool) { fi, _ := d.Info() sfi := fi.(squashfs.FileInfo) - owner := fmt.Sprintf("%d/%d", - sfi.Uid(), - sfi.Gid()) + owner := fmt.Sprintf("%s/%s", + userName(sfi.Uid(), numeric), + groupName(sfi.Gid(), numeric)) link := "" if sfi.IsSymlink() { link = " -> " + sfi.SymlinkPath() @@ -33,6 +57,7 @@ func main() { verbose := flag.Bool("v", false, "Verbose") list := flag.Bool("l", false, "List") long := flag.Bool("ll", false, "List with attributes") + numeric := flag.Bool("lln", false, "List with attributes and numeric ids") ignore := flag.Bool("ip", false, "Ignore Permissions and extract all files/folders with 0755") flag.Parse() if len(flag.Args()) < 2 { @@ -47,14 +72,14 @@ func main() { if err != nil { panic(err) } - if *list || *long { + if *list || *long || *numeric { root := flag.Arg(1) fs.WalkDir(r, ".", func(path string, d fs.DirEntry, err error) error { if err != nil { panic(err) } - if *long { - printEntry(root, path, d) + if *long || *numeric { + printEntry(root, path, d, *numeric) } else { fmt.Println(filepath.Join(root, path)) } diff --git a/file.go b/file.go index 1024fa1..c3ebf6f 100644 --- a/file.go +++ b/file.go @@ -142,7 +142,15 @@ func (f *File) ReadDir(n int) ([]fs.DirEntry, error) { // Returns the file's fs.FileInfo func (f *File) Stat() (fs.FileInfo, error) { - return newFileInfo(f.b.Name, &f.b.Inode), nil + uid, err := f.b.Uid(&f.r.Low) + if err != nil { + return nil, err + } + gid, err := f.b.Gid(&f.r.Low) + if err != nil { + return nil, err + } + return newFileInfo(f.b.Name, uid, gid, &f.b.Inode), nil } // SymlinkPath returns the symlink's target path. Is the File isn't a symlink, returns an empty string. diff --git a/file_info.go b/file_info.go index 6c00017..20ddfc7 100644 --- a/file_info.go +++ b/file_info.go @@ -10,6 +10,8 @@ import ( type FileInfo struct { name string + uid uint32 + gid uint32 size int64 target string perm uint32 @@ -18,14 +20,22 @@ type FileInfo struct { } func (r Reader) newFileInfo(e directory.Entry) (FileInfo, error) { - i, err := r.Low.InodeFromEntry(e) + b, err := r.Low.BaseFromEntry(e) if err != nil { return FileInfo{}, err } - return newFileInfo(e.Name, &i), nil + uid, err := b.Uid(&r.Low) + if err != nil { + return FileInfo{}, err + } + gid, err := b.Gid(&r.Low) + if err != nil { + return FileInfo{}, err + } + return newFileInfo(e.Name, uid, gid, &b.Inode), nil } -func newFileInfo(name string, i *inode.Inode) FileInfo { +func newFileInfo(name string, uid, gid uint32, i *inode.Inode) FileInfo { var size int64 var target string switch i.Type { @@ -40,6 +50,8 @@ func newFileInfo(name string, i *inode.Inode) FileInfo { } return FileInfo{ name: name, + uid: uid, + gid: gid, size: size, target: target, perm: uint32(i.Perm), @@ -52,6 +64,14 @@ func (f FileInfo) Name() string { return f.name } +func (f FileInfo) Uid() int { + return int(f.uid) +} + +func (f FileInfo) Gid() int { + return int(f.gid) +} + func (f FileInfo) Size() int64 { return f.size }