Compare commits

..

11 Commits

Author SHA1 Message Date
Caleb Gardner edd63a422b Support for ../ in directory paths 2020-11-28 02:39:58 -06:00
Caleb Gardner 938fd30d73 Updated README 2020-11-28 02:35:46 -06:00
Caleb Gardner cea69188d4 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.
2020-11-28 02:22:48 -06:00
Caleb Gardner 23ec7ea6dd First version of File interface.
This will allow you to easily find and extract files.
Extraction of whole folders coming next. (Maybe)
2020-11-27 00:36:21 -06:00
Caleb Gardner 8358cb2805 Added a couple ways to find a particular file. 2020-11-26 09:16:55 -06:00
Caleb Gardner 9471b93ead Working on a better API to interact with squashfs
New API uses a File that can hold more information.
2020-11-26 03:47:44 -06:00
Caleb Gardner 77222f55f5 More setup for Files. 2020-11-25 13:57:38 -06:00
Caleb Gardner 9beca864c3 Starting work on file.
File will be the primary way to interact with squashfs files in the future.
I will be making Files for both reading and writing
2020-11-25 13:20:42 -06:00
Caleb Gardner b28b4ae978 Test now use a straight appimage.
Reads straight from the appimage instead of extracting the squashfs first
2020-11-25 12:12:16 -06:00
Caleb Gardner 426903a222 Renamed library to squashfs for ease of use 2020-11-25 10:51:59 -06:00
Caleb Gardner dcb26057fa Updated README 2020-11-25 08:57:18 -06:00
12 changed files with 356 additions and 147 deletions
+5 -28
View File
@@ -1,37 +1,14 @@
# GoSquashfs # squashfs [![PkgGoDev](https://pkg.go.dev/badge/github.com/CalebQ42/squashfs)](https://pkg.go.dev/github.com/CalebQ42/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. 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. 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). 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 # [TODO](https://github.com/CalebQ42/squashfs/projects/1?fullscreen=true)
* Extracting files from string paths # Where I'm at
* 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). Roughly in order. * Working on the File interface that should make it easier to deal with squashfs files. I'm also trying to make them capable for when I get squashing working.
* Reading the UID, GUID, Xatt, Compression Options, and Export tables.
* 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
# Where I'm at.
* I FINALLY GOT FILE EXTRACTION WORKING!!
+4
View File
@@ -11,6 +11,10 @@ type decompressor interface {
Decompress(io.Reader) ([]byte, error) Decompress(io.Reader) ([]byte, error)
} }
type compressor interface {
Compress(io.Reader) ([]byte, error)
}
//ZlibDecompressor is a decompressor for gzip type compression //ZlibDecompressor is a decompressor for gzip type compression
type zlibDecompressor struct{} type zlibDecompressor struct{}
+15 -6
View File
@@ -5,14 +5,14 @@ import (
"errors" "errors"
"io" "io"
"github.com/CalebQ42/GoSquashfs/internal/inode" "github.com/CalebQ42/squashfs/internal/inode"
) )
var ( var (
//ErrInodeNotFile is given when giving an inode, but the function requires a file inode. //ErrInodeNotFile is given when giving an inode, but the function requires a file inode.
ErrInodeNotFile = errors.New("Given inode is NOT a file type") errInodeNotFile = errors.New("Given inode is NOT a file type")
//ErrInodeOnlyFragment is given when trying to make a DataReader from an inode, but the inode only had data in a fragment //ErrInodeOnlyFragment is given when trying to make a DataReader from an inode, but the inode only had data in a fragment
ErrInodeOnlyFragment = errors.New("Given inode ONLY has fragment data") errInodeOnlyFragment = errors.New("Given inode ONLY has fragment data")
) )
//DataReader reads data from data blocks. //DataReader reads data from data blocks.
@@ -66,7 +66,7 @@ func (r *Reader) newDataReaderFromInode(i *inode.Inode) (*dataReader, error) {
case inode.BasicFileType: case inode.BasicFileType:
fil := i.Info.(inode.BasicFile) fil := i.Info.(inode.BasicFile)
if fil.Init.BlockStart == 0 { if fil.Init.BlockStart == 0 {
return nil, ErrInodeOnlyFragment return nil, errInodeOnlyFragment
} }
rdr.offset = int64(fil.Init.BlockStart) rdr.offset = int64(fil.Init.BlockStart)
for _, sizes := range fil.BlockSizes { for _, sizes := range fil.BlockSizes {
@@ -78,7 +78,7 @@ func (r *Reader) newDataReaderFromInode(i *inode.Inode) (*dataReader, error) {
case inode.ExtFileType: case inode.ExtFileType:
fil := i.Info.(inode.ExtendedFile) fil := i.Info.(inode.ExtendedFile)
if fil.Init.BlockStart == 0 { if fil.Init.BlockStart == 0 {
return nil, ErrInodeOnlyFragment return nil, errInodeOnlyFragment
} }
rdr.offset = int64(fil.Init.BlockStart) rdr.offset = int64(fil.Init.BlockStart)
for _, sizes := range fil.BlockSizes { for _, sizes := range fil.BlockSizes {
@@ -88,7 +88,7 @@ func (r *Reader) newDataReaderFromInode(i *inode.Inode) (*dataReader, error) {
rdr.blocks = rdr.blocks[:len(rdr.blocks)-1] rdr.blocks = rdr.blocks[:len(rdr.blocks)-1]
} }
default: default:
return nil, ErrInodeNotFile return nil, errInodeNotFile
} }
err := rdr.readCurBlock() err := rdr.readCurBlock()
if err != nil { if err != nil {
@@ -145,7 +145,16 @@ func (d *dataReader) readCurBlock() error {
return err return err
} }
//Close frees up the curData from memory
func (d *dataReader) Close() error {
d.curData = nil
return nil
}
func (d *dataReader) Read(p []byte) (int, error) { func (d *dataReader) Read(p []byte) (int, error) {
if d.curData == nil {
d.readCurBlock()
}
if d.curReadOffset+len(p) < len(d.curData) { if d.curReadOffset+len(p) < len(d.curData) {
for i := 0; i < len(p); i++ { for i := 0; i < len(p); i++ {
p[i] = d.curData[d.curReadOffset+i] p[i] = d.curData[d.curReadOffset+i]
+201
View File
@@ -0,0 +1,201 @@
package squashfs
import (
"errors"
"io"
"strings"
"github.com/CalebQ42/squashfs/internal/directory"
"github.com/CalebQ42/squashfs/internal/inode"
)
var (
//ErrNotDirectory is returned when you're trying to do directory things with a non-directory
ErrNotDirectory = errors.New("File is not a directory")
//ErrNotFile is returned when you're trying to do file things with a directory
ErrNotFile = errors.New("File is not a file")
//ErrNotReading is returned when running functions that are only meant to be used when reading a squashfs
ErrNotReading = errors.New("Function only supported when reading a squashfs")
)
//File is the main way to interact with files within squashfs, or when putting files into a squashfs.
//File can be either a file or folder. When reading from a squashfs, it reads from the datablocks.
//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. 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.
in *inode.Inode //Underlyting inode when reading.
filType int //The file's type, using inode types.
}
//get a File from a directory.entry
func (r *Reader) newFileFromDirEntry(entry *directory.Entry) (fil *File, err error) {
fil = new(File)
fil.in, err = r.getInodeFromEntry(entry)
if err != nil {
return nil, err
}
fil.Name = entry.Name
fil.r = r
fil.filType = fil.in.Type
return
}
//GetChildren returns a *squashfs.File slice of every direct child of the directory. If the File is not a directory, will return ErrNotDirectory
func (f *File) GetChildren() (children []*File, err error) {
children = make([]*File, 0)
if f.r == nil {
return nil, ErrNotReading
}
if !f.IsDir() {
return nil, ErrNotDirectory
}
dir, err := f.r.readDirFromInode(f.in)
if err != nil {
return
}
var fil *File
for _, entry := range dir.Entries {
fil, err = f.r.newFileFromDirEntry(&entry)
if err != nil {
return
}
fil.Parent = f
if f.Name != "" {
fil.Path = f.Path + "/" + f.Name
}
children = append(children, fil)
}
return
}
//GetChildrenRecursively returns ALL children. Goes down ALL folder paths.
func (f *File) GetChildrenRecursively() (children []*File, err error) {
children = make([]*File, 0)
if f.r == nil {
return nil, ErrNotReading
}
if !f.IsDir() {
return nil, ErrNotDirectory
}
chil, err := f.GetChildren()
if err != nil {
return
}
var childFolders []*File
for _, child := range chil {
children = append(children, child)
if child.IsDir() {
childFolders = append(childFolders, child)
}
}
for _, folds := range childFolders {
var childs []*File
childs, err = folds.GetChildrenRecursively()
if err != nil {
return
}
children = append(children, childs...)
}
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, "/")
if split[0] == ".." && f.Name == "" {
return nil
} else if split[0] == ".." {
if f.Parent != nil {
return f.Parent.GetFileAtPath(strings.Join(split[1:], "/"))
}
return nil
}
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 {
if f.IsDir() {
return ErrNotFile
}
if closer, is := f.Reader.(io.Closer); is {
closer.Close()
}
f.Reader = nil
return nil
}
//Read from the file. Doesn't do anything fancy, just pases it to the underlying io.Reader. If a directory, return io.EOF.
func (f *File) Read(p []byte) (int, error) {
if f.IsDir() {
return 0, io.EOF
}
var err error
if f.Reader == nil && f.r != nil {
f.Reader, err = f.r.newFileReader(f.in)
if err != nil {
return 0, err
}
}
return f.Reader.Read(p)
}
+20 -10
View File
@@ -5,13 +5,14 @@ import (
"errors" "errors"
"io" "io"
"github.com/CalebQ42/GoSquashfs/internal/inode" "github.com/CalebQ42/squashfs/internal/inode"
) )
//FileReader provides a io.Reader interface for files within a squashfs archive //FileReader provides a io.Reader interface for files within a squashfs archive
type FileReader struct { type fileReader struct {
r *Reader r *Reader
data *dataReader data *dataReader
in *inode.Inode
fragmentData []byte fragmentData []byte
fragged bool fragged bool
fragOnly bool fragOnly bool
@@ -25,13 +26,9 @@ var (
) )
//ReadFile provides a squashfs.FileReader for the file at the given location. //ReadFile provides a squashfs.FileReader for the file at the given location.
func (r *Reader) ReadFile(location string) (*FileReader, error) { func (r *Reader) newFileReader(in *inode.Inode) (*fileReader, error) {
var rdr FileReader var rdr fileReader
rdr.r = r rdr.in = in
in, err := r.getInodeFromPath(location)
if err != nil {
return nil, err
}
if in.Type != inode.BasicFileType && in.Type != inode.ExtFileType { if in.Type != inode.BasicFileType && in.Type != inode.ExtFileType {
return nil, ErrPathIsNotFile return nil, ErrPathIsNotFile
} }
@@ -47,6 +44,7 @@ func (r *Reader) ReadFile(location string) (*FileReader, error) {
rdr.fragOnly = fil.Init.BlockStart == 0 rdr.fragOnly = fil.Init.BlockStart == 0
rdr.FileSize = int(fil.Init.Size) rdr.FileSize = int(fil.Init.Size)
} }
var err error
if rdr.fragged { if rdr.fragged {
rdr.fragmentData, err = r.getFragmentDataFromInode(in) rdr.fragmentData, err = r.getFragmentDataFromInode(in)
if err != nil { if err != nil {
@@ -59,7 +57,16 @@ func (r *Reader) ReadFile(location string) (*FileReader, error) {
return &rdr, nil return &rdr, nil
} }
func (f *FileReader) Read(p []byte) (int, error) { //Close runs Close on the data reader and frees the fragmentdata
func (f *fileReader) Close() error {
if f.data != nil {
f.data.Close()
}
f.fragmentData = nil
return nil
}
func (f *fileReader) Read(p []byte) (int, error) {
if f.fragOnly { if f.fragOnly {
n, err := bytes.NewBuffer(f.fragmentData[f.read:]).Read(p) n, err := bytes.NewBuffer(f.fragmentData[f.read:]).Read(p)
f.read += n f.read += n
@@ -72,6 +79,9 @@ func (f *FileReader) Read(p []byte) (int, error) {
n, err := f.data.Read(p) n, err := f.data.Read(p)
read += n read += n
if f.fragged && err == io.EOF { if f.fragged && err == io.EOF {
if f.fragmentData == nil {
f.fragmentData, err = f.r.getFragmentDataFromInode(f.in)
}
n, err = bytes.NewBuffer(f.fragmentData).Read(p[read:]) n, err = bytes.NewBuffer(f.fragmentData).Read(p[read:])
read += n read += n
if err != nil { if err != nil {
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"errors" "errors"
"io" "io"
"github.com/CalebQ42/GoSquashfs/internal/inode" "github.com/CalebQ42/squashfs/internal/inode"
) )
//FragmentEntry is an entry in the fragment table //FragmentEntry is an entry in the fragment table
+1 -3
View File
@@ -1,9 +1,7 @@
module github.com/CalebQ42/GoSquashfs module github.com/CalebQ42/squashfs
go 1.15 go 1.15
require ( require (
github.com/CalebQ42/GoAppImage v0.4.0 github.com/CalebQ42/GoAppImage v0.4.0
github.com/google/go-cmp v0.5.4 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
) )
+2 -2
View File
@@ -1,5 +1,7 @@
github.com/CalebQ42/GoAppImage v0.4.0 h1:aF+Y/vyo/RGhoyZEW1CMY6WyRWrZZO4ydsRFAtIGnaY= github.com/CalebQ42/GoAppImage v0.4.0 h1:aF+Y/vyo/RGhoyZEW1CMY6WyRWrZZO4ydsRFAtIGnaY=
github.com/CalebQ42/GoAppImage v0.4.0/go.mod h1:qHudJKAn/dlkNWNnH4h1YKXp29EZ7Bppsn7sNP2HuvU= github.com/CalebQ42/GoAppImage v0.4.0/go.mod h1:qHudJKAn/dlkNWNnH4h1YKXp29EZ7Bppsn7sNP2HuvU=
github.com/CalebQ42/GoSquashfs v0.1.0 h1:1E6oeZLxGwjFgB0M5BcDD/IpKOQq1aO0gGsN0llCFoE=
github.com/CalebQ42/GoSquashfs v0.1.0/go.mod h1:NzAR1YC1SVKOKhRao5IiWY3GhOMI+IxBy1xeZJeVKlQ=
github.com/adrg/xdg v0.2.2 h1:A7ZHKRz5KGOLJX/bg7IPzStryhvCzAE1wX+KWawPiAo= github.com/adrg/xdg v0.2.2 h1:A7ZHKRz5KGOLJX/bg7IPzStryhvCzAE1wX+KWawPiAo=
github.com/adrg/xdg v0.2.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ= github.com/adrg/xdg v0.2.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -7,8 +9,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
+13 -2
View File
@@ -181,6 +181,7 @@ type BasicSymlinkInit struct {
type BasicSymlink struct { type BasicSymlink struct {
Init BasicSymlinkInit Init BasicSymlinkInit
targetPath []byte //len is TargetPathSize targetPath []byte //len is TargetPathSize
Path string
} }
//NewBasicSymlink creates a new BasicSymlink //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) inode.targetPath = make([]byte, inode.Init.TargetPathSize, inode.Init.TargetPathSize)
err = binary.Read(rdr, binary.LittleEndian, &inode.targetPath) err = binary.Read(rdr, binary.LittleEndian, &inode.targetPath)
if err != nil {
return inode, err
}
inode.Path = string(inode.targetPath)
return inode, err return inode, err
} }
@@ -204,7 +209,8 @@ type ExtendedSymlinkInit struct {
//ExtendedSymlink is a symlink with extra information //ExtendedSymlink is a symlink with extra information
type ExtendedSymlink struct { type ExtendedSymlink struct {
Init ExtendedSymlinkInit Init ExtendedSymlinkInit
TargetPath []uint8 targetPath []uint8
Path string
XattrIndex uint32 XattrIndex uint32
} }
@@ -215,7 +221,12 @@ func NewExtendedSymlink(rdr io.Reader) (ExtendedSymlink, error) {
if err != nil { if err != nil {
return inode, err 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) err = binary.Read(rdr, binary.LittleEndian, &inode.XattrIndex)
return inode, err return inode, err
} }
+77 -32
View File
@@ -6,7 +6,7 @@ import (
"io" "io"
"math" "math"
"github.com/CalebQ42/GoSquashfs/internal/inode" "github.com/CalebQ42/squashfs/internal/inode"
) )
const ( const (
@@ -22,7 +22,7 @@ var (
ErrCompressorOptions = errors.New("Compressor options is not currently supported") ErrCompressorOptions = errors.New("Compressor options is not currently supported")
//ErrFragmentTableIssues is returned if there's trouble reading the fragment table when creating a reader. //ErrFragmentTableIssues is returned if there's trouble reading the fragment table when creating a reader.
//When this is returned, the reader is still returned. //When this is returned, the reader is still returned.
ErrFragmentTableIssues = errors.New("Trouble while reading the fragment table") errFragmentTableIssues = errors.New("Trouble while reading the fragment table")
) )
//Reader processes and reads a squashfs archive. //Reader processes and reads a squashfs archive.
@@ -73,50 +73,95 @@ func NewSquashfsReader(r io.ReaderAt) (*Reader, error) {
return &rdr, nil return &rdr, nil
} }
//GetFilesList returns a list of ALL files in the squashfs, going down every folder. //GetRootFolder returns a squashfs.File that references the root directory of the squashfs archive.
//Folders end in / func (r *Reader) GetRootFolder() (root *File, err error) {
func (r *Reader) GetFilesList() ([]string, error) { root = new(File)
inoderdr, err := r.newMetadataReaderFromInodeRef(r.super.RootInodeRef) mr, err := r.newMetadataReaderFromInodeRef(r.super.RootInodeRef)
if err != nil { if err != nil {
return nil, err return nil, err
} }
i, err := inode.ProcessInode(inoderdr, r.super.BlockSize) root.in, err = inode.ProcessInode(mr, r.super.BlockSize)
if err != nil { if err != nil {
return nil, err return nil, err
} }
paths, err := r.readDir(i) root.Path = "/"
if err != nil { root.filType = root.in.Type
return nil, err root.r = r
} return root, nil
return paths, nil
} }
//readDir returns a list of all decendents of a given inode. Inode given MUST be a directory type. //GetAllFiles returns a slice of ALL files and folders contained in the squashfs.
func (r *Reader) readDir(i *inode.Inode) (paths []string, err error) { func (r *Reader) GetAllFiles() (fils []*File, err error) {
dir, err := r.readDirFromInode(i) root, err := r.GetRootFolder()
if err != nil { if err != nil {
return return nil, err
} }
for _, entry := range dir.Entries { return root.GetChildrenRecursively()
if entry.Init.Type == inode.BasicDirectoryType { }
paths = append(paths)
i, err = r.getInodeFromEntry(&entry) //FindFile returns the first file (in the same order as Reader.GetAllFiles) that the given function returns true for. Returns nil if nothing is found.
func (r *Reader) FindFile(query func(*File) bool) *File {
root, err := r.GetRootFolder()
if err != nil {
return nil
}
fils, err := root.GetChildren()
if err != nil {
return nil
}
var childrenDirs []*File
for _, fil := range fils {
if query(fil) {
return fil
}
if fil.IsDir() {
childrenDirs = append(childrenDirs, fil)
}
}
for len(childrenDirs) != 0 {
var tmp []*File
for _, dirs := range childrenDirs {
chil, err := dirs.GetChildren()
if err != nil { if err != nil {
return return nil
} }
var subPaths []string for _, child := range chil {
subPaths, err = r.readDir(i) if query(child) {
if err != nil { return child
return }
if child.IsDir() {
tmp = append(tmp, child)
}
} }
for pathI := range subPaths { }
subPaths[pathI] = entry.Name + "/" + subPaths[pathI] childrenDirs = tmp
} }
paths = append(paths, entry.Name+"/") return nil
paths = append(paths, subPaths...) }
} else {
paths = append(paths, entry.Name) //FindAll returns all files where the given function returns true.
func (r *Reader) FindAll(query func(*File) bool) (all []*File) {
root, err := r.GetRootFolder()
if err != nil {
return nil
}
fils, err := root.GetChildrenRecursively()
if err != nil {
return nil
}
for _, fil := range fils {
if query(fil) {
all = append(all, fil)
} }
} }
return return
} }
//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 {
dir, err := r.GetRootFolder()
if err != nil {
return nil
}
return dir.GetFileAtPath(path)
}
+15 -61
View File
@@ -11,9 +11,8 @@ import (
) )
const ( const (
downloadURL = "https://github.com/zilti/code-oss.AppImage/releases/download/continuous/Code_OSS-x86_64.AppImage" downloadURL = "https://github.com/Swordfish90/cool-retro-term/releases/download/1.1.1/Cool-Retro-Term-1.1.1-x86_64.AppImage"
appImageName = "Code_OSS.AppImage" appImageName = "Cool-Retro-Term.AppImage"
squashfsName = "testing.squashfs"
) )
func TestMain(t *testing.T) { func TestMain(t *testing.T) {
@@ -22,80 +21,35 @@ func TestMain(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
squashFil, err := os.Open(wd + "/testing/" + squashfsName) aiFil, err := os.Open(wd + "/testing/" + appImageName)
if os.IsNotExist(err) {
TestCreateSquashFromAppImage(t)
squashFil, err = os.Open(wd + "/testing/" + squashfsName)
if err != nil {
t.Fatal(err)
}
}
defer squashFil.Close()
stat, _ := squashFil.Stat()
rdr, err := NewSquashfsReader(io.NewSectionReader(squashFil, 0, stat.Size()))
if err != nil {
t.Fatal(err)
}
//testing code to print out the directory structure
// rdr.GetFileStructure()
extractionFil := "Proton-5.9-GE-8-ST.tar.gz"
os.Remove(wd + "/testing/" + extractionFil)
desk, err := os.Create(wd + "/testing/" + extractionFil)
if err != nil {
t.Fatal(err)
}
ext, err := rdr.ReadFile(extractionFil)
if err != nil {
t.Fatal(err)
}
fmt.Println("Size!", ext.FileSize)
n, err := io.CopyBuffer(desk, ext, make([]byte, rdr.super.BlockSize/2))
if err != nil {
fmt.Println("Read", n)
t.Fatal(err)
}
t.Fatal("No problems here!")
}
func TestCreateSquashFromAppImage(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.Mkdir(wd+"/testing", 0777)
if err != nil && !os.IsExist(err) {
t.Fatal(err)
}
_, err = os.Open(wd + "/testing/" + appImageName)
if os.IsNotExist(err) { if os.IsNotExist(err) {
downloadTestAppImage(t, wd+"/testing") downloadTestAppImage(t, wd+"/testing")
_, err = os.Open(wd + "/testing/" + appImageName) aiFil, err = os.Open(wd + "/testing/" + appImageName)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
} else if err != nil { } else if err != nil {
t.Fatal(err) t.Fatal(err)
} }
ai := goappimage.NewAppImage(wd + "/testing/" + appImageName)
aiFil, err := os.Open(wd + "/testing/" + appImageName)
if err != nil {
t.Fatal(err)
}
defer aiFil.Close() defer aiFil.Close()
aiFil.Seek(ai.Offset, 0) stat, _ := aiFil.Stat()
os.Remove(wd + "/testing/" + squashfsName) ai := goappimage.NewAppImage(wd + "/testing/" + appImageName)
aiSquash, err := os.Create(wd + "/testing/" + squashfsName) rdr, err := NewSquashfsReader(io.NewSectionReader(aiFil, ai.Offset, stat.Size()-ai.Offset))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, err = io.Copy(aiSquash, aiFil) fil := rdr.GetFileAtPath("/usr/bin/cool-retro-term")
if err != nil { if fil != nil {
t.Fatal(err) fmt.Println("Worked!", fil.Path+"/"+fil.Name)
} else {
t.Fatal("NOOOOOO!")
} }
t.Fatal("No problems here!")
} }
func downloadTestAppImage(t *testing.T, dir string) { func downloadTestAppImage(t *testing.T, dir string) {
//seems to time out. Need to fix that at some point //seems to time out on slow connections. Might fix that at some point... or not
os.Mkdir(dir, 0777)
appImage, err := os.Create(dir + "/" + appImageName) appImage, err := os.Create(dir + "/" + appImageName)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"io" "io"
"strings" "strings"
"github.com/CalebQ42/GoSquashfs/internal/directory" "github.com/CalebQ42/squashfs/internal/directory"
"github.com/CalebQ42/GoSquashfs/internal/inode" "github.com/CalebQ42/squashfs/internal/inode"
) )
var ( var (