Compare commits

...

3 Commits

Author SHA1 Message Date
Caleb Gardner 94b45c8402 Added IgnorePerm to ExtractionOptions 2023-04-12 07:57:57 -05:00
Caleb Gardner 01de43a5ae Added ErrReadNotFile to ReatAt, WriteTo 2023-04-11 00:34:43 -05:00
Caleb Gardner 5b29f4d029 Updated README 2023-04-09 21:09:53 -05:00
2 changed files with 71 additions and 14 deletions
+2 -2
View File
@@ -1,8 +1,8 @@
# squashfs (WIP)
# squashfs
[![PkgGoDev](https://pkg.go.dev/badge/github.com/CalebQ42/squashfs)](https://pkg.go.dev/github.com/CalebQ42/squashfs) [![Go Report Card](https://goreportcard.com/badge/github.com/CalebQ42/squashfs)](https://goreportcard.com/report/github.com/CalebQ42/squashfs)
A PURE Go library to read and write squashfs.
A PURE Go library to read squashfs. There is currently no plans to add archive creation support as it will almost always be better to just call `mksquashfs`. I could see some possible use cases, but probably won't spend time on it unless it's requested (open a discussion fi you want this feature).
Currently has support for reading squashfs files and extracting files and folders.
+69 -12
View File
@@ -8,6 +8,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
@@ -59,6 +60,21 @@ func (f File) Stat() (fs.FileInfo, error) {
return newFileInfo(f.e, f.i), nil
}
// Mode returns the file's fs.FileMode
func (f File) Mode() fs.FileMode {
switch f.e.Type {
case inode.Dir:
return fs.FileMode(f.i.Perm) | fs.ModeDir
case inode.Char:
return fs.FileMode(f.i.Perm) | fs.ModeCharDevice
case inode.Block:
return fs.FileMode(f.i.Perm) | fs.ModeDevice
case inode.Sym:
return fs.FileMode(f.i.Perm) | fs.ModeSymlink
}
return fs.FileMode(f.i.Perm)
}
// Read reads the data from the file. Only works if file is a normal file.
func (f File) Read(p []byte) (int, error) {
if f.i.Type != inode.Fil && f.i.Type != inode.EFil {
@@ -71,16 +87,22 @@ func (f File) Read(p []byte) (int, error) {
}
func (f File) ReadAt(p []byte, off int64) (int, error) {
if f.i.Type != inode.Fil && f.i.Type != inode.EFil {
return 0, ErrReadNotFile
}
return f.fullRdr.ReadAt(p, off)
}
// WriteTo writes all data from the file to the writer. This is multi-threaded.
// The underlying reader is seperate from the one used with Read and can be reused.
func (f File) WriteTo(w io.Writer) (int64, error) {
if f.i.Type != inode.Fil && f.i.Type != inode.EFil {
return 0, ErrReadNotFile
}
return f.fullRdr.WriteTo(w)
}
// Close simply nils the underlying reader. Here mostly to satisfy fs.File
// Close simply nils the underlying reader.
func (f *File) Close() error {
f.rdr = nil
return nil
@@ -90,7 +112,7 @@ func (f *File) Close() error {
// If n <= 0 all fs.DirEntry's are returned.
func (f *File) ReadDir(n int) (out []fs.DirEntry, err error) {
if !f.IsDir() {
return nil, errors.New("File is not a directory")
return nil, errors.New("file is not a directory")
}
ents, err := f.r.readDirectory(f.i)
if err != nil {
@@ -198,10 +220,11 @@ func (f File) GetSymlinkFile() *File {
// ExtractionOptions are available options on how to extract.
type ExtractionOptions struct {
LogOutput io.Writer //Where error log should write. If nil, uses os.Stdout. Has no effect if verbose is false.
DereferenceSymlink bool //Replace symlinks with the target file
UnbreakSymlink bool //Try to make sure symlinks remain unbroken when extracted, without changing the symlink
Verbose bool //Prints extra info to log on an error
FolderPerm fs.FileMode //The permissions used when creating the extraction folder
DereferenceSymlink bool //Replace symlinks with the target file.
UnbreakSymlink bool //Try to make sure symlinks remain unbroken when extracted, without changing the symlink.
Verbose bool //Prints extra info to log on an error.
IgnorePerm bool //ignore the file's permission and instead use FolderPerm.
FolderPerm fs.FileMode //The permissions used when creating the extraction folder. Defaults to 0755.
}
// DefaultOptions is the default ExtractionOptions.
@@ -220,10 +243,9 @@ func (f File) ExtractTo(folder string) error {
// ExtractSymlink extracts the File to the folder with the DereferenceSymlink option.
// If the File is a directory, it instead extracts the directory's contents to the folder.
func (f File) ExtractSymlink(folder string) error {
return f.ExtractWithOptions(folder, ExtractionOptions{
DereferenceSymlink: true,
FolderPerm: 0755,
})
op := DefaultOptions()
op.DereferenceSymlink = true
return f.ExtractWithOptions(folder, op)
}
// ExtractWithOptions extracts the File to the given folder with the given ExtrationOptions.
@@ -270,8 +292,11 @@ func (f File) realExtract(folder string, op ExtractionOptions) error {
return
}
if fil.IsDir() {
info, _ := fil.Stat()
err = os.Mkdir(filepath.Join(folder, fil.e.Name), info.Mode())
perm := f.Mode()
if op.IgnorePerm {
perm = (op.FolderPerm & fs.ModePerm) | (perm & fs.ModeType)
}
err = os.Mkdir(filepath.Join(folder, fil.e.Name), perm)
if err != nil {
if op.Verbose {
log.Println("Error while creating", filepath.Join(folder, fil.e.Name))
@@ -310,6 +335,7 @@ func (f File) realExtract(folder string, op ExtractionOptions) error {
}
return err
}
defer fil.Close()
_, err = io.Copy(fil, f)
if err != nil {
if op.Verbose {
@@ -317,6 +343,11 @@ func (f File) realExtract(folder string, op ExtractionOptions) error {
}
return err
}
if op.IgnorePerm {
os.Chmod(fil.Name(), op.FolderPerm)
} else {
os.Chmod(fil.Name(), f.Mode())
}
case f.IsSymlink():
symPath := f.SymlinkPath()
if op.DereferenceSymlink {
@@ -364,7 +395,18 @@ func (f File) realExtract(folder string, op ExtractionOptions) error {
}
return err
}
if op.IgnorePerm {
os.Chmod(folder+"/"+f.e.Name, op.FolderPerm)
} else {
os.Chmod(folder+"/"+f.e.Name, f.Mode())
}
case f.isDeviceOrFifo():
if runtime.GOOS == "windows" {
if op.Verbose {
log.Println(folder+"/"+f.e.Name, "ignored since it's a device link and can't be created on Windows.")
}
return nil
}
_, err = exec.LookPath("mknod")
if err != nil {
if op.Verbose {
@@ -378,6 +420,12 @@ func (f File) realExtract(folder string, op ExtractionOptions) error {
} else if f.i.Type == inode.Block || f.i.Type == inode.EBlock {
typ = "b"
} else { //Fifo IPC
if runtime.GOOS == "darwin" {
if op.Verbose {
log.Println(folder+"/"+f.e.Name, "ignored since it's a Fifo file and can't be created on Darwin.")
}
return nil
}
typ = "p"
}
cmd := exec.Command("mknod", folder+"/"+f.e.Name, typ)
@@ -396,6 +444,15 @@ func (f File) realExtract(folder string, op ExtractionOptions) error {
}
return err
}
if op.IgnorePerm {
os.Chmod(folder+"/"+f.e.Name, op.FolderPerm)
} else {
os.Chmod(folder+"/"+f.e.Name, f.Mode())
}
case f.e.Type == inode.Sock:
if op.Verbose {
log.Println(folder+"/"+f.e.Name, "ignored since it's a socket file.")
}
default:
return errors.New("Unsupported file type. Inode type: " + strconv.Itoa(int(f.i.Type)))
}