From cea69188d458fa256d866fa8d9aa7b3e7eaf9fe3 Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Sat, 28 Nov 2020 02:22:48 -0600 Subject: [PATCH] Symlink file support You can get the path that the symlink is pointing to, AND get the squashfs.File for it. You can also now give a path FROM a given squashfs.File directory. --- README.md | 36 ++-------------------- file.go | 58 +++++++++++++++++++++++++++++++++++- internal/inode/inodetypes.go | 15 ++++++++-- reader.go | 28 +---------------- squash_test.go | 34 +++++---------------- 5 files changed, 81 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index a5549f1..e04e640 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,13 @@ -# GoSquashfs - -[![PkgGoDev](https://pkg.go.dev/badge/github.com/CalebQ42/GoSquashfs)](https://pkg.go.dev/github.com/CalebQ42/GoSquashfs) +# squashfs [![PkgGoDev](https://pkg.go.dev/badge/github.com/CalebQ42/GoSquashfs)](https://pkg.go.dev/github.com/CalebQ42/GoSquashfs) A PURE Go library to read and write squashfs. -Currently, you can read a squashfs and extract files (only files at the moment). Many things are public that shouldn't be, but you can use it by using NewSquashfsReader and subsequent ReadFile. +Currently, you can read a squashfs and extract files (folder extraction not supported. Yet). Special thanks to https://dr-emann.github.io/squashfs/ for some VERY important information in an easy to understand format. Thanks also to [distri's squashfs library](https://github.com/distr1/distri/tree/master/internal/squashfs) as I referenced it to figure some things out (and double check others). -# Working - -* Extracting files from string paths -* Reading the header -* Reading metadata blocks (whether encrypted or not) -* Reading inodes -* Reading directories -* Basic gzip compression (Shouldn't be too hard to implement other, but for right now, this works) -* Listing all files via a string slice - -# Not Working (Yet). Not necessarily in order. - -* Provide an easy interface to find and list files and their properties - * Maybe squashfs.File -* Make device, socket, symlink, and all extended types of inode work properly. (I need to find an archive that uses it first.) -* Extracting files - * from inodes. - * from file info. -* Give a list of files - * In io.FileStat (?) form -* Reading the UID, GUID, Xatt, Compression Options, and Export tables. -* Implement other compression types (Should be relatively easy) -* Squashing -* Threading processes to speed them up -* Reasonable tests - -# TODO - -* Go over all documentation again (especially for exported structs and functions) to make sure it's easy to understand. +# [TODO](https://github.com/CalebQ42/squashfs/projects/1?fullscreen=true) # Where I'm at diff --git a/file.go b/file.go index 3421bd0..e58d059 100644 --- a/file.go +++ b/file.go @@ -3,6 +3,7 @@ package squashfs import ( "errors" "io" + "strings" "github.com/CalebQ42/squashfs/internal/directory" "github.com/CalebQ42/squashfs/internal/inode" @@ -22,7 +23,7 @@ var ( //When writing, this holds the information on WHERE the file will be placed inside the archive. type File struct { Name string //The name of the file or folder. Root folder will not have a name ("") - Parent *File //The parent directory. If it's the root directory, will be nil + Parent *File //The parent directory. Should ALWAYS be a folder. If it's the root directory, will be nil Reader io.Reader //Underlying reader. When writing, will probably be an os.File. When reading this is kept nil UNTIL reading to save memory. Path string //The path to the folder the File is located in. r *Reader //The squashfs.Reader where this file is contained. @@ -102,11 +103,66 @@ func (f *File) GetChildrenRecursively() (children []*File, err error) { return } +//GetFileAtPath tries to return the File at the given path, relative to the file. +//Returns nil if called on something other then a folder, OR if the path goes oustide the archive. +func (f *File) GetFileAtPath(path string) *File { + if path == "" { + return f + } + path = strings.TrimSuffix(strings.TrimPrefix(path, "/"), "/") + if path != "" && !f.IsDir() { + return nil + } + for strings.HasSuffix(path, "./") { + //since you can TECHNICALLY have an infinite amount of ./ and it would still be valid. + path = strings.TrimPrefix(path, "./") + } + split := strings.Split(path, "/") + children, err := f.GetChildren() + if err != nil { + return nil + } + for _, child := range children { + if child.Name == split[0] { + return child.GetFileAtPath(strings.Join(split[1:], "/")) + } + } + return nil +} + //IsDir returns if the file is a directory. func (f *File) IsDir() bool { return f.filType == inode.BasicDirectoryType || f.filType == inode.ExtDirType } +//IsSymlink returns if the file is a symlink. +func (f *File) IsSymlink() bool { + return f.filType == inode.BasicSymlinkType || f.filType == inode.ExtSymlinkType +} + +//SymlinkPath returns the path the symlink is pointing to. If the file ISN'T a symlink, will return an empty string +func (f *File) SymlinkPath() string { + switch f.filType { + case inode.BasicSymlinkType: + return f.in.Info.(inode.BasicSymlink).Path + case inode.ExtSymlinkType: + return f.in.Info.(inode.ExtendedSymlink).Path + default: + return "" + } +} + +//GetSymlinkFile tries to return the squashfs.File associated with the symlink +func (f *File) GetSymlinkFile() *File { + if !f.IsSymlink() { + return nil + } + if strings.HasSuffix(f.SymlinkPath(), "/") { + return nil + } + return f.r.GetFileAtPath(f.SymlinkPath()) +} + //Close frees up the memory held up by the underlying reader. Should NOT be called when writing. //When reading, Close is safe to use, but any subsequent Read calls resets to the beginning of the file. func (f *File) Close() error { diff --git a/internal/inode/inodetypes.go b/internal/inode/inodetypes.go index f586fe1..f9b7680 100644 --- a/internal/inode/inodetypes.go +++ b/internal/inode/inodetypes.go @@ -181,6 +181,7 @@ type BasicSymlinkInit struct { type BasicSymlink struct { Init BasicSymlinkInit targetPath []byte //len is TargetPathSize + Path string } //NewBasicSymlink creates a new BasicSymlink @@ -192,6 +193,10 @@ func NewBasicSymlink(rdr io.Reader) (BasicSymlink, error) { } inode.targetPath = make([]byte, inode.Init.TargetPathSize, inode.Init.TargetPathSize) err = binary.Read(rdr, binary.LittleEndian, &inode.targetPath) + if err != nil { + return inode, err + } + inode.Path = string(inode.targetPath) return inode, err } @@ -204,7 +209,8 @@ type ExtendedSymlinkInit struct { //ExtendedSymlink is a symlink with extra information type ExtendedSymlink struct { Init ExtendedSymlinkInit - TargetPath []uint8 + targetPath []uint8 + Path string XattrIndex uint32 } @@ -215,7 +221,12 @@ func NewExtendedSymlink(rdr io.Reader) (ExtendedSymlink, error) { if err != nil { return inode, err } - inode.TargetPath = make([]uint8, inode.Init.TargetPathSize, inode.Init.TargetPathSize) + inode.targetPath = make([]uint8, inode.Init.TargetPathSize, inode.Init.TargetPathSize) + err = binary.Read(rdr, binary.LittleEndian, &inode.targetPath) + if err != nil { + return inode, err + } + inode.Path = string(inode.targetPath) err = binary.Read(rdr, binary.LittleEndian, &inode.XattrIndex) return inode, err } diff --git a/reader.go b/reader.go index b4454e1..02f7085 100644 --- a/reader.go +++ b/reader.go @@ -5,7 +5,6 @@ import ( "errors" "io" "math" - "strings" "github.com/CalebQ42/squashfs/internal/inode" ) @@ -160,34 +159,9 @@ func (r *Reader) FindAll(query func(*File) bool) (all []*File) { //GetFileAtPath will return the file at the given path. If the file cannot be found, will return nil. func (r *Reader) GetFileAtPath(path string) *File { - path = strings.TrimSuffix(strings.TrimPrefix(path, "/"), "/") - pathDirs := strings.Split(path, "/") dir, err := r.GetRootFolder() if err != nil { return nil } - children, err := dir.GetChildren() - if err != nil { - return nil - } - for _, folder := range pathDirs { - for _, child := range children { - if child.Name == folder { - dir = child - if dir.IsDir() { - children, err = dir.GetChildren() - if err != nil { - return nil - } - } else { - children = make([]*File, 0) - } - break - } - } - } - if dir.Path+"/"+dir.Name == "/"+path { - return dir - } - return nil + return dir.GetFileAtPath(path) } diff --git a/squash_test.go b/squash_test.go index a9cecde..c1e87e3 100644 --- a/squash_test.go +++ b/squash_test.go @@ -5,15 +5,14 @@ import ( "io" "net/http" "os" - "strings" "testing" goappimage "github.com/CalebQ42/GoAppImage" ) const ( - downloadURL = "https://github.com/zilti/code-oss.AppImage/releases/download/continuous/Code_OSS-x86_64.AppImage" - appImageName = "Code_OSS.AppImage" + downloadURL = "https://github.com/Swordfish90/cool-retro-term/releases/download/1.1.1/Cool-Retro-Term-1.1.1-x86_64.AppImage" + appImageName = "Cool-Retro-Term.AppImage" ) func TestMain(t *testing.T) { @@ -39,31 +38,12 @@ func TestMain(t *testing.T) { if err != nil { t.Fatal(err) } - rdr.FindAll(func(fil *File) bool { - return strings.HasSuffix(fil.Name, ".desktop") - }) - fils, err := rdr.GetAllFiles() - if err != nil { - t.Fatal(err) + fil := rdr.GetFileAtPath("/usr/bin/cool-retro-term") + if fil != nil { + fmt.Println("Worked!", fil.Path+"/"+fil.Name) + } else { + t.Fatal("NOOOOOO!") } - for _, fil := range fils { - fmt.Println(fil.Path + "/" + fil.Name) - } - // extractionFil := "code-oss.desktop" - // os.Remove(wd + "/testing/" + extractionFil) - // desk, err := os.Create(wd + "/testing/" + extractionFil) - // if err != nil { - // t.Fatal(err) - // } - // ext := rdr.GetFileAtPath(extractionFil) - // if ext == nil { - // t.Fatal("Cannot find file") - // } - // defer ext.Close() - // _, err = io.Copy(desk, ext) - // if err != nil { - // t.Fatal(err) - // } t.Fatal("No problems here!") }