Finished. Now for bug fixes

This commit is contained in:
Caleb Gardner
2023-12-24 18:20:05 -06:00
parent 5de59627df
commit d9132ab6a4
6 changed files with 385 additions and 20 deletions
+236 -2
View File
@@ -4,8 +4,14 @@ import (
"errors"
"io"
"io/fs"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"github.com/CalebQ42/squashfs/internal/routinemanager"
"github.com/CalebQ42/squashfs/squashfs"
"github.com/CalebQ42/squashfs/squashfs/data"
"github.com/CalebQ42/squashfs/squashfs/inode"
@@ -162,6 +168,20 @@ func (f *File) initializeReaders() error {
return err
}
func (f *File) deviceDevices() (maj uint32, min uint32) {
var dev uint32
if f.b.Inode.Type == inode.Char || f.b.Inode.Type == inode.Block {
dev = f.b.Inode.Data.(inode.Device).Dev
} else if f.b.Inode.Type == inode.EChar || f.b.Inode.Type == inode.EBlock {
dev = f.b.Inode.Data.(inode.EDevice).Dev
}
return dev >> 8, dev & 0x000FF
}
func (f *File) path() string {
return filepath.Join(f.parent.path(), f.b.Name)
}
// Extract the file to the given folder. If the file is a folder, the folder's contents will be extracted to the folder.
// Uses default extraction options.
func (f *File) Extract(folder string) error {
@@ -170,6 +190,220 @@ func (f *File) Extract(folder string) error {
// Extract the file to the given folder. If the file is a folder, the folder's contents will be extracted to the folder.
// Allows setting various extraction options via ExtractionOptions.
func (f *File) ExtractWithOptions(folder string, op *ExtractionOptions) error {
//TODO
func (f *File) ExtractWithOptions(path string, op *ExtractionOptions) error {
if op.manager == nil {
op.manager = routinemanager.NewManager(op.SimultaneousFiles)
log.SetOutput(op.LogOutput)
}
switch f.b.Inode.Type {
case inode.Dir, inode.EDir:
d, err := f.b.ToDir(f.r.r)
if err != nil {
if op.Verbose {
log.Println("Failed to create squashfs.Directory for", path)
}
return errors.Join(errors.New("failed to create squashfs.Directory: "+path), err)
}
errChan := make(chan error, len(d.Entries))
files := len(d.Entries)
for i := range d.Entries {
b, err := f.r.r.BaseFromEntry(d.Entries[i])
if err != nil {
if op.Verbose {
log.Println("Failed to get squashfs.Base from entry for", path)
}
return errors.Join(errors.New("failed to get base from entry: "+path), err)
}
if b.IsDir() {
files--
extDir := filepath.Join(path, b.Name)
err = os.Mkdir(extDir, 0777)
if err != nil {
if op.Verbose {
log.Println("Failed to create directory", path)
}
return errors.Join(errors.New("failed to create directory: "+path), err)
}
err = f.ExtractWithOptions(extDir, op)
if err != nil {
if op.Verbose {
log.Println("Failed to extract directory", path)
}
return errors.Join(errors.New("failed to extract directory: "+path), err)
}
} else {
fil := &File{
b: b,
r: f.r,
}
go func(fil *File, folder string) {
i := op.manager.Lock()
defer op.manager.Unlock(i)
errChan <- fil.ExtractWithOptions(folder, op)
}(fil, path)
}
}
var errCache []error
for i := 0; i < files; i++ {
err := <-errChan
if err != nil {
errCache = append(errCache, err)
}
}
if len(errCache) > 0 {
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)
outFil, err := os.Create(path)
if err != nil {
if op.Verbose {
log.Println("Failed to create file", path)
}
return errors.Join(errors.New("failed to create file: "+path), err)
}
defer outFil.Close()
full, err := f.b.GetFullReader(f.r.r)
if err != nil {
if op.Verbose {
log.Println("Failed to create full reader for", path)
}
return errors.Join(errors.New("failed to create full reader: "+path), err)
}
full.SetGoroutineLimit(op.ExtractionRoutines)
_, err = full.WriteTo(outFil)
if err != nil {
if op.Verbose {
log.Println("Failed to write file", path)
}
return errors.Join(errors.New("failed to write file: "+path), err)
}
if op.Verbose {
log.Println(f.path(), "extracted to", path)
}
case inode.Sym, inode.ESym:
symPath := f.SymlinkPath()
if op.DereferenceSymlink {
filTmp := f.GetSymlinkFile()
if filTmp == nil {
if op.Verbose {
log.Println("Failed to get symlink's file:", f.path())
}
return errors.New("failed to get symlink's file")
}
fil := filTmp.(*File)
fil.b.Name = f.b.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))
}
return errors.Join(errors.New("failed to extract symlink's file: "+path), err)
}
} else {
if op.UnbreakSymlink {
filTmp := f.GetSymlinkFile()
if filTmp == nil {
if op.Verbose {
log.Println("Failed to get symlink's file:", f.path())
}
return errors.New("failed to get symlink's file")
}
extractLoc := filepath.Join(path, filepath.Dir(symPath))
fil := filTmp.(*File)
err := fil.ExtractWithOptions(extractLoc, op)
if err != nil {
if op.Verbose {
log.Println("Error while extracting", fil.path(), "to make sure symlink at", f.path(), "is unbroken")
}
return errors.Join(errors.New("failed to extract symlink's file: "+extractLoc), err)
}
}
path = filepath.Join(path, f.b.Name)
err := os.Symlink(f.SymlinkPath(), path)
if err != nil {
if op.Verbose {
log.Println("Failed to create symlink:", path)
}
return errors.Join(errors.New("failed to create symlink: "+path), err)
}
}
case inode.Char, inode.EChar, inode.Block, inode.EBlock, inode.Fifo, inode.EFifo:
if runtime.GOOS == "windows" {
if op.Verbose {
log.Println(f.path(), "ignored. A device link and can't be created on Windows.")
}
return nil
}
_, err := exec.LookPath("mknod")
if err != nil {
if op.Verbose {
log.Println("mknot command not found, cannot create device link for", f.path())
}
return errors.Join(errors.New("mknot command not found"), err)
}
path = filepath.Join(path, f.b.Name)
var typ string
if f.b.Inode.Type == inode.Char || f.b.Inode.Type == inode.EChar {
typ = "c"
} else if f.b.Inode.Type == inode.Block || f.b.Inode.Type == inode.EBlock {
typ = "b"
} else { //Fifo IPC
if runtime.GOOS == "darwin" {
if op.Verbose {
log.Println(f.path(), "ignored. A Fifo file and can't be created on Darwin.")
}
return nil
}
typ = "p"
}
cmd := exec.Command("mknod", path, typ)
if typ != "p" {
maj, min := f.deviceDevices()
cmd.Args = append(cmd.Args, strconv.Itoa(int(maj)), strconv.Itoa(int(min)))
}
if op.Verbose {
cmd.Stdout = op.LogOutput
cmd.Stderr = op.LogOutput
}
err = cmd.Run()
if err != nil {
if op.Verbose {
log.Println("Error while running mknod for", path)
}
return errors.Join(errors.New("error while running mknod for "+path), err)
}
case inode.Sock, inode.ESock:
if op.Verbose {
log.Println(f.path(), "ignored since it's a socket file.")
}
return nil
default:
return errors.New("Unsupported file type. Inode type: " + strconv.Itoa(int(f.b.Inode.Type)))
}
if op.Verbose {
log.Println(f.path(), "extracted to", path)
}
if op.IgnorePerm {
return nil
}
uid, err := f.b.Uid(f.r.r)
if err != nil {
if op.Verbose {
log.Println("Failed to get uid for", path)
log.Println(err)
}
return nil
}
gid, err := f.b.Gid(f.r.r)
if err != nil {
if op.Verbose {
log.Println("Failed to get gid for", path)
log.Println(err)
}
return nil
}
os.Chmod(path, f.Mode())
os.Chown(path, int(uid), int(gid))
return nil
}