diff --git a/cmd/go-unsquashfs/main.go b/cmd/go-unsquashfs/main.go index 0781bcc..7dc84e5 100644 --- a/cmd/go-unsquashfs/main.go +++ b/cmd/go-unsquashfs/main.go @@ -3,14 +3,61 @@ package main import ( "flag" "fmt" + "io/fs" "os" + "os/user" + "path/filepath" + "strconv" + "strings" "time" "github.com/CalebQ42/squashfs" ) +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("%s/%s", + userName(sfi.Uid(), numeric), + groupName(sfi.Gid(), numeric)) + 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") + numeric := flag.Bool("lln", false, "List with attributes and numeric ids") offset := flag.Int64("o", 0, "Offset") ignore := flag.Bool("ip", false, "Ignore Permissions and extract all files/folders with 0755") flag.Parse() @@ -26,6 +73,21 @@ func main() { if err != nil { panic(err) } + 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 || *numeric { + printEntry(root, path, d, *numeric) + } else { + fmt.Println(filepath.Join(root, path)) + } + return nil + }) + return + } op := squashfs.DefaultOptions() op.Verbose = *verbose op.IgnorePerm = *ignore diff --git a/file.go b/file.go index d52e2b9..c3ebf6f 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 { @@ -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 74ecdbc..20ddfc7 100644 --- a/file_info.go +++ b/file_info.go @@ -8,48 +8,79 @@ import ( "github.com/CalebQ42/squashfs/low/inode" ) -type fileInfo struct { +type FileInfo struct { name string + uid uint32 + gid uint32 size int64 + target string perm uint32 modTime uint32 fileType uint16 } -func (r Reader) newFileInfo(e directory.Entry) (fileInfo, error) { - i, err := r.Low.InodeFromEntry(e) +func (r Reader) newFileInfo(e directory.Entry) (FileInfo, error) { + b, err := r.Low.BaseFromEntry(e) if err != nil { - return fileInfo{}, err + 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 { 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{ + return FileInfo{ name: name, + uid: uid, + gid: gid, size: size, + target: target, perm: uint32(i.Perm), modTime: i.ModTime, fileType: i.Type, } } -func (f fileInfo) Name() string { +func (f FileInfo) Name() string { return f.name } -func (f fileInfo) Size() int64 { +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 } -func (f fileInfo) Mode() fs.FileMode { +func (f FileInfo) SymlinkPath() string { + return f.target +} + +func (f FileInfo) Mode() fs.FileMode { switch f.fileType { case inode.Dir, inode.EDir: return fs.FileMode(f.perm | uint32(fs.ModeDir)) @@ -65,31 +96,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 }