diff --git a/go.mod b/go.mod index 7376fb4..cb26438 100644 --- a/go.mod +++ b/go.mod @@ -15,4 +15,4 @@ require ( golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect -) \ No newline at end of file +) diff --git a/writer.go b/writer.go index 7f94b39..231cfce 100644 --- a/writer.go +++ b/writer.go @@ -2,80 +2,182 @@ package squashfs import ( "errors" + "log" "os" "path" + "sort" + "strings" + + "github.com/CalebQ42/squashfs/internal/inode" ) -//Writer is an interface to write a squashfs. Doesn't write until you call Write (TODO: maybe not do Write...) +//Writer is an interface to write a squashfs. Doesn't write until you call Write (TODO: maybe not do Write...). +//If AllowErrors is true, when errors are encountered, it just prints to the log instead of failing. type Writer struct { files map[string][]*File directories []string - resolveSymlinks bool + symlinkTable map[string]string //symlinkTable holds info about symlink'd to files that had to be moved from their original position. [originalpath]newpath + ResolveSymlinks bool + AllowErrors bool compression int temp []*File } -//NewWriter creates a new squashfs.Writer with the default settings (gzip compression and autoresolving symlinks) +//NewWriter creates a new squashfs.Writer with the default settings (gzip compression, autoresolving symlinks, and allowErrors) func NewWriter() (*Writer, error) { - return NewWriterWithOptions(true, GzipCompression) + return NewWriterWithOptions(true, true, GzipCompression) } //NewWriterWithOptions creates a new squashfs.Writer with the given options. -//ResolveSymlinks tries to make sure symlinks aren't broken, and if they would be -func NewWriterWithOptions(resolveSymlinks bool, compressionType int) (*Writer, error) { - if compressionType < 0 || compressionType > 6 || compressionType == 3 { - return nil, errors.New("Incompatible compression type") +//ResolveSymlinks tries to make sure symlinks aren't broken. It will either try to make the link's location work +func NewWriterWithOptions(resolveSymlinks, allowErrors bool, compressionType int) (*Writer, error) { + if compressionType < 0 || compressionType > 6 { + return nil, errors.New("Incorrect compression type") } - return &Writer{ + if compressionType == 3 { + return nil, errors.New("Lzo compression is not currently supported") + } + out := Writer{ files: map[string][]*File{ "/": make([]*File, 0), }, - directories: []string{"/"}, - resolveSymlinks: resolveSymlinks, + ResolveSymlinks: resolveSymlinks, + AllowErrors: allowErrors, compression: compressionType, - }, nil + } + if resolveSymlinks { + out.symlinkTable = make(map[string]string) + } + return &out, nil } -func (w *Writer) convertFile(squashfsPath string, file *os.File) error { +//convertFile converts the given os.File to a squashfs.File and then adds it to the Writer's temp File slice. +func (w *Writer) convertFile(squashfsPath string, file *os.File, errChan chan error) { var fil File fil.Reader = file fil.name = path.Base(file.Name()) fil.path = squashfsPath stat, err := file.Stat() if err != nil { - return err + if w.AllowErrors { + log.Println("Error while getting FileInfo for", file.Name()+":") + log.Println(err) + err = nil + } + errChan <- err + return } - defer func() { w.temp = append(w.temp, &fil) }() if stat.IsDir() { + fil.filType = inode.BasicDirectoryType dirs, err := file.Readdirnames(-1) if err != nil { - return err + if w.AllowErrors { + log.Println("Error when getting directory names for", file.Name()+":") + log.Println(err) + err = nil + } + errChan <- err + return } + subDirErrChan := make(chan error) for _, dir := range dirs { - subFil, err := os.Open(file.Name() + dir) + go func(newFilename string, errChan chan error) { + subFil, err := os.Open(file.Name() + newFilename) + if err != nil { + if w.AllowErrors { + log.Println("Error when opening sub-directory", subFil.Name()+":") + log.Println(err) + err = nil + } + errChan <- err + return + } + subDirErrChan := make(chan error) + w.convertFile(fil.Path(), subFil, subDirErrChan) + errChan <- <-subDirErrChan + return + }(dir, subDirErrChan) + } + for range dirs { + err = <-subDirErrChan if err != nil { - return err - } - err = w.convertFile(fil.Path(), subFil) - if err != nil { - return err + errChan <- err + return } } + w.temp = append(w.temp, &fil) + errChan <- nil + return + } else if stat.Mode().IsRegular() { + fil.filType = inode.BasicFileType + w.temp = append(w.temp, &fil) + errChan <- nil + return + } else if stat.Mode()&os.ModeSymlink == os.ModeSymlink { + linkLocation, err := os.Readlink(file.Name()) + if err != nil { + if w.AllowErrors { + log.Println("Error when reading symlink's target", file.Name()+":") + log.Println(err) + err = nil + } + errChan <- err + return + } + if w.ResolveSymlinks { + if w.symlinkTable[linkLocation] != "" { + linkLocation = w.symlinkTable[linkLocation] + } + } + //TODO: finish symlink support } - //TODO: reg files & symlinks - return nil + errChan <- errors.New("Unsupported file type") + return } //AddFilesToPath adds the give os.Files to the given path within the squashfs archive. +//If AllowErrors is true, this will ALWAYS return nil func (w *Writer) AddFilesToPath(squashfsPath string, files ...*os.File) error { - //TODO - return errors.New("Don't") + squashfsPath = path.Clean(squashfsPath) + if strings.HasPrefix(squashfsPath, "/") { + squashfsPath = strings.TrimPrefix(squashfsPath, "/") + } + if squashfsPath == "." { + squashfsPath = "/" + } + errChan := make(chan error) + for _, fil := range files { + go w.convertFile(squashfsPath, fil, errChan) + } + var firstError error + for range files { + err := <-errChan + if firstError != nil && err != nil { + firstError = err + } + } + if firstError != nil { + w.temp = nil + return firstError + } + for _, tempFil := range w.temp { + if tempFil.path != "/" { + ind := sort.SearchStrings(w.directories, tempFil.path) + if ind == len(w.directories) { + w.directories = append(w.directories, tempFil.path) + sort.Strings(w.directories) + } + } + w.files[tempFil.path] = append(w.files[tempFil.path], tempFil) + } + w.temp = nil + return nil } //AddFiles adds all files given to the root directory +//If AllowErrors is true, this will ALWAYS return nil func (w *Writer) AddFiles(files ...*os.File) error { - //TODO - return errors.New("Don't") + return w.AddFilesToPath("/", files...) } //RemoveFileAt removes the file at filepath from the Writer.