From 1f0868fb21a5b56ac43d530b7a90ceabf90f69ad Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Sun, 25 May 2025 13:35:40 -0500 Subject: [PATCH] Added -show-hard-links to go-unsquashfs Exposed the underlying squashfslow values for File and FS --- cmd/go-unsquashfs/main.go | 94 ++++++++++++++++++++++++--------------- file.go | 68 ++++++++++++++-------------- fs.go | 28 ++++++------ reader.go | 4 +- 4 files changed, 108 insertions(+), 86 deletions(-) diff --git a/cmd/go-unsquashfs/main.go b/cmd/go-unsquashfs/main.go index 8a7a344..e56e564 100644 --- a/cmd/go-unsquashfs/main.go +++ b/cmd/go-unsquashfs/main.go @@ -3,7 +3,6 @@ package main import ( "flag" "fmt" - "io/fs" "os" "os/user" "path/filepath" @@ -12,6 +11,7 @@ import ( "time" "github.com/CalebQ42/squashfs" + squashfslow "github.com/CalebQ42/squashfs/low" ) func userName(uid int, numeric bool) string { @@ -36,31 +36,73 @@ func groupName(gid int, numeric bool) string { return gs } -func printEntry(path string, d fs.DirEntry, numeric bool) { - fi, _ := d.Info() +var hardLinks = make(map[uint32]string) + +func printFile(rdr *squashfs.Reader, path string, f *squashfs.File) { + path = filepath.Join(path, f.Low.Name) + fi, _ := f.Stat() sfi := fi.(squashfs.FileInfo) owner := fmt.Sprintf("%s/%s", - userName(sfi.Uid(), numeric), - groupName(sfi.Gid(), numeric)) - link := "" + userName(sfi.Uid(), *numeric), + groupName(sfi.Gid(), *numeric)) + link, isHardLink := hardLinks[f.Low.Inode.Num] + var size int64 + if isHardLink { + size = 0 + } else { + size = fi.Size() + hardLinks[f.Low.Inode.Num] = path + } if sfi.IsSymlink() { link = " -> " + sfi.SymlinkPath() + } else if isHardLink { + link = " link to " + link } fmt.Printf("%s %s %*d %s %s%s\n", strings.ToLower(fi.Mode().String()), - owner, 26-len(owner), fi.Size(), + owner, 26-len(owner), size, fi.ModTime().Format("2006-01-02 15:04"), - filepath.Join(path), link) + path, link) + if f.IsDir() { + fs, _ := f.FS() + printDir(rdr, path, fs) + } } +func printDir(rdr *squashfs.Reader, path string, f squashfs.FS) { + var base squashfslow.FileBase + var fil squashfs.File + var err error + for _, e := range f.LowDir.Entries { + base, err = rdr.Low.BaseFromEntry(e) + if err != nil { + panic(err) + } + fil = rdr.FileFromBase(base, f) + printFile(rdr, path, &fil) + } +} + +var ( + verbose *bool + list *bool + long *bool + numeric *bool + offset *int64 + ignore *bool + file *string + showHardLinks *bool +) + 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") - file := flag.String("e", "", "File or folder to extract") + 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") + showHardLinks = flag.Bool("show-hard-links", false, "When used with ll or lln, shows hard links") + offset = flag.Int64("o", 0, "Offset") + ignore = flag.Bool("ip", false, "Ignore Permissions and extract all files/folders with 0755") + file = flag.String("e", "", "File or folder to extract") flag.Parse() if (*list || *long || *numeric) && flag.NArg() < 1 { fmt.Println("Please provide a file name") @@ -85,27 +127,7 @@ func main() { } } if *list || *long || *numeric { - if extractFil.IsDir() { - var filFs squashfs.FS - filFs, err = extractFil.FS() - if err != nil { - panic(err) - } - err = fs.WalkDir(filFs, ".", func(path string, d fs.DirEntry, err error) error { - if err != nil { - panic(err) - } - if *long || *numeric { - printEntry(path, d, *numeric) - } else { - fmt.Println(filepath.Clean(path)) - } - return nil - }) - if err != nil { - panic(err) - } - } + printFile(&r, "", extractFil) return } op := squashfs.DefaultOptions() diff --git a/file.go b/file.go index b06819c..5c3cc3d 100644 --- a/file.go +++ b/file.go @@ -23,14 +23,14 @@ type File struct { rdrInit bool parent FS r *Reader - b squashfslow.FileBase + Low squashfslow.FileBase dirsRead int } // Creates a new *File from the given *squashfs.Base func (r *Reader) FileFromBase(b squashfslow.FileBase, parent FS) File { return File{ - b: b, + Low: b, parent: parent, r: r, } @@ -40,11 +40,11 @@ func (f File) FS() (FS, error) { if !f.IsDir() { return FS{}, errors.New("not a directory") } - d, err := f.b.ToDir(f.r.Low) + d, err := f.Low.ToDir(f.r.Low) if err != nil { return FS{}, err } - return FS{d: d, parent: &f.parent, r: f.r}, nil + return FS{LowDir: d, parent: &f.parent, r: f.r}, nil } // Closes the underlying readers. @@ -74,21 +74,21 @@ func (f File) GetSymlinkFile() fs.File { // Returns whether the file is a directory. func (f File) IsDir() bool { - return f.b.IsDir() + return f.Low.IsDir() } // Returns whether the file is a regular file. func (f File) IsRegular() bool { - return f.b.IsRegular() + return f.Low.IsRegular() } // Returns whether the file is a symlink. func (f File) IsSymlink() bool { - return f.b.Inode.Type == inode.Sym || f.b.Inode.Type == inode.ESym + return f.Low.Inode.Type == inode.Sym || f.Low.Inode.Type == inode.ESym } func (f File) Mode() fs.FileMode { - return f.b.Inode.Mode() + return f.Low.Inode.Mode() } // Read reads the data from the file. Only works if file is a normal file. @@ -111,7 +111,7 @@ func (f *File) ReadDir(n int) ([]fs.DirEntry, error) { if !f.IsDir() { return nil, errors.New("file is not a directory") } - d, err := f.b.ToDir(f.r.Low) + d, err := f.Low.ToDir(f.r.Low) if err != nil { return nil, err } @@ -139,24 +139,24 @@ func (f *File) ReadDir(n int) ([]fs.DirEntry, error) { // Returns the file's fs.FileInfo func (f File) Stat() (fs.FileInfo, error) { - uid, err := f.b.Uid(&f.r.Low) + uid, err := f.Low.Uid(&f.r.Low) if err != nil { return nil, err } - gid, err := f.b.Gid(&f.r.Low) + gid, err := f.Low.Gid(&f.r.Low) if err != nil { return nil, err } - return newFileInfo(f.b.Name, uid, gid, &f.b.Inode), nil + return newFileInfo(f.Low.Name, uid, gid, &f.Low.Inode), nil } // SymlinkPath returns the symlink's target path. Is the File isn't a symlink, returns an empty string. func (f File) SymlinkPath() string { - switch f.b.Inode.Type { + switch f.Low.Inode.Type { case inode.Sym: - return string(f.b.Inode.Data.(inode.Symlink).Target) + return string(f.Low.Inode.Data.(inode.Symlink).Target) case inode.ESym: - return string(f.b.Inode.Data.(inode.ESymlink).Target) + return string(f.Low.Inode.Data.(inode.ESymlink).Target) } return "" } @@ -178,7 +178,7 @@ func (f *File) WriteTo(w io.Writer) (int64, error) { func (f *File) initializeReaders() error { var err error - f.rdr, f.full, err = f.b.GetRegFileReaders(f.r.Low) + f.rdr, f.full, err = f.Low.GetRegFileReaders(f.r.Low) if err == nil { f.rdrInit = true } else { @@ -190,20 +190,20 @@ func (f *File) initializeReaders() error { func (f File) deviceDevices() (maj uint32, min uint32) { var dev uint32 - switch f.b.Inode.Type { + switch f.Low.Inode.Type { case inode.Char, inode.Block: - dev = f.b.Inode.Data.(inode.Device).Dev + dev = f.Low.Inode.Data.(inode.Device).Dev case inode.EChar, inode.EBlock: - dev = f.b.Inode.Data.(inode.EDevice).Dev + dev = f.Low.Inode.Data.(inode.EDevice).Dev } return dev >> 8, dev & 0x000FF } func (f File) path() string { - if f.parent.d.Name == "" { - return f.b.Name + if f.parent.LowDir.Name == "" { + return f.Low.Name } - return filepath.Join(f.parent.path(), f.b.Name) + return filepath.Join(f.parent.path(), f.Low.Name) } // Extract the file to the given folder. If the file is a folder, the folder's contents will be extracted to the folder. @@ -228,9 +228,9 @@ func (f File) ExtractWithOptions(path string, op *ExtractionOptions) error { return err } } - switch f.b.Inode.Type { + switch f.Low.Inode.Type { case inode.Dir, inode.EDir: - d, err := f.b.ToDir(f.r.Low) + d, err := f.Low.ToDir(f.r.Low) if err != nil { if op.Verbose { log.Println("Failed to create squashfs.Directory for", path) @@ -288,7 +288,7 @@ func (f File) ExtractWithOptions(path string, op *ExtractionOptions) error { return errors.Join(errors.New("failed to extract folder: "+path), errors.Join(errCache...)) } case inode.Fil, inode.EFil: - path = filepath.Join(path, f.b.Name) + path = filepath.Join(path, f.Low.Name) outFil, err := os.Create(path) if err != nil { if op.Verbose { @@ -297,7 +297,7 @@ func (f File) ExtractWithOptions(path string, op *ExtractionOptions) error { return errors.Join(errors.New("failed to create file: "+path), err) } defer outFil.Close() - full, err := f.b.GetFullReader(&f.r.Low) + full, err := f.Low.GetFullReader(&f.r.Low) if err != nil { if op.Verbose { log.Println("Failed to create full reader for", path) @@ -323,11 +323,11 @@ func (f File) ExtractWithOptions(path string, op *ExtractionOptions) error { return errors.New("failed to get symlink's file") } fil := filTmp.(*File) - fil.b.Name = f.b.Name + fil.Low.Name = f.Low.Name err := fil.ExtractWithOptions(path, op) if err != nil { if op.Verbose { - log.Println("Failed to extract symlink's file:", filepath.Join(path, f.b.Name)) + log.Println("Failed to extract symlink's file:", filepath.Join(path, f.Low.Name)) } return errors.Join(errors.New("failed to extract symlink's file: "+path), err) } @@ -350,7 +350,7 @@ func (f File) ExtractWithOptions(path string, op *ExtractionOptions) error { return errors.Join(errors.New("failed to extract symlink's file: "+extractLoc), err) } } - path = filepath.Join(path, f.b.Name) + path = filepath.Join(path, f.Low.Name) err := os.Symlink(f.SymlinkPath(), path) if err != nil { if op.Verbose { @@ -373,9 +373,9 @@ func (f File) ExtractWithOptions(path string, op *ExtractionOptions) error { } return errors.Join(errors.New("mknot command not found"), err) } - path = filepath.Join(path, f.b.Name) + path = filepath.Join(path, f.Low.Name) var typ string - switch f.b.Inode.Type { + switch f.Low.Inode.Type { case inode.Char, inode.EChar: typ = "c" case inode.Block, inode.EBlock: @@ -411,7 +411,7 @@ func (f File) ExtractWithOptions(path string, op *ExtractionOptions) error { } return nil default: - return errors.New("Unsupported file type. Inode type: " + strconv.Itoa(int(f.b.Inode.Type))) + return errors.New("Unsupported file type. Inode type: " + strconv.Itoa(int(f.Low.Inode.Type))) } if op.Verbose { log.Println(f.path(), "extracted to", path) @@ -419,7 +419,7 @@ func (f File) ExtractWithOptions(path string, op *ExtractionOptions) error { if op.IgnorePerm { return nil } - uid, err := f.b.Uid(&f.r.Low) + uid, err := f.Low.Uid(&f.r.Low) if err != nil { if op.Verbose { log.Println("Failed to get uid for", path) @@ -427,7 +427,7 @@ func (f File) ExtractWithOptions(path string, op *ExtractionOptions) error { } return nil } - gid, err := f.b.Gid(&f.r.Low) + gid, err := f.Low.Gid(&f.r.Low) if err != nil { if op.Verbose { log.Println("Failed to get gid for", path) diff --git a/fs.go b/fs.go index e30bee8..203162d 100644 --- a/fs.go +++ b/fs.go @@ -17,13 +17,13 @@ import ( type FS struct { r *Reader parent *FS - d squashfslow.Directory + LowDir squashfslow.Directory } // Creates a new *FS from the given squashfs.directory func (r *Reader) FSFromDirectory(d squashfslow.Directory, parent FS) FS { return FS{ - d: d, + LowDir: d, r: r, parent: &parent, } @@ -42,10 +42,10 @@ func (f *FS) Glob(pattern string) (out []string, err error) { } } split := strings.Split(pattern, "/") - for i := range f.d.Entries { - if match, _ := path.Match(split[0], f.d.Entries[i].Name); match { + for i := range f.LowDir.Entries { + if match, _ := path.Match(split[0], f.LowDir.Entries[i].Name); match { if len(split) == 1 { - out = append(out, f.d.Entries[i].Name) + out = append(out, f.LowDir.Entries[i].Name) continue } sub, err := f.Sub(split[0]) @@ -81,7 +81,7 @@ func (f *FS) Glob(pattern string) (out []string, err error) { } } for i := range subGlob { - subGlob[i] = f.d.Name + "/" + subGlob[i] + subGlob[i] = f.LowDir.Name + "/" + subGlob[i] } out = append(out, subGlob...) } @@ -118,7 +118,7 @@ func (f FS) OpenFile(name string) (*File, error) { return f.parent.OpenFile(strings.Join(split[1:], "/")) } } - i, found := slices.BinarySearchFunc(f.d.Entries, split[0], func(e directory.Entry, name string) int { + i, found := slices.BinarySearchFunc(f.LowDir.Entries, split[0], func(e directory.Entry, name string) int { return strings.Compare(e.Name, name) }) if !found { @@ -128,13 +128,13 @@ func (f FS) OpenFile(name string) (*File, error) { Err: fs.ErrNotExist, } } - b, err := f.r.Low.BaseFromEntry(f.d.Entries[i]) + b, err := f.r.Low.BaseFromEntry(f.LowDir.Entries[i]) if err != nil { return nil, err } if len(split) == 1 { return &File{ - b: b, + Low: b, r: f.r, parent: f, }, nil @@ -260,20 +260,20 @@ func (f FS) ExtractWithOptions(folder string, op *ExtractionOptions) error { func (f FS) File() *File { if f.parent != nil { return &File{ - b: f.d.FileBase, + Low: f.LowDir.FileBase, parent: *f.parent, r: f.r, } } return &File{ - b: f.d.FileBase, - r: f.r, + Low: f.LowDir.FileBase, + r: f.r, } } func (f FS) path() string { if f.parent == nil { - return f.d.Name + return f.LowDir.Name } - return filepath.Join(f.parent.path(), f.d.Name) + return filepath.Join(f.parent.path(), f.LowDir.Name) } diff --git a/reader.go b/reader.go index 94361f0..2e04a89 100644 --- a/reader.go +++ b/reader.go @@ -22,8 +22,8 @@ func NewReader(r io.ReaderAt) (Reader, error) { Low: rdr, } out.FS = FS{ - d: rdr.Root, - r: &out, + LowDir: rdr.Root, + r: &out, } return out, nil }