diff --git a/writer.go b/writer.go index b8ed0ba..ce8f394 100644 --- a/writer.go +++ b/writer.go @@ -2,180 +2,167 @@ package squashfs import ( "errors" + "io" "log" "os" "path" "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...). -//If AllowErrors is true, when errors are encountered, it just prints to the log instead of failing. +type fileHolder struct { + reader io.Reader + path string + name string + symLocation string + folder bool + symlink bool +} + +//Writer is used to creaste squashfs archives. Currently unusable +//TODO: Make usable type Writer struct { - files map[string][]*File + structure map[string][]*fileHolder symlinkTable map[string]string //[oldpath]newpath - symTableTemp map[string]string - directories []string - compression int - ResolveSymlinks bool - AllowErrors bool + compressionType int + allowErrors bool //AllowErrors allows errors when adding folders and their children. } -//NewWriter creates a new squashfs.Writer with the default settings (gzip compression, autoresolving symlinks, and allowErrors) +//NewWriter creates a new func NewWriter() (*Writer, error) { - return NewWriterWithOptions(true, true, GzipCompression) + return NewWriterWithOptions(GzipCompression, true) } -//NewWriterWithOptions creates a new squashfs.Writer with the given options. -//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") - } - if compressionType == 3 { - return nil, errors.New("Lzo compression is not currently supported") - } - out := Writer{ - files: map[string][]*File{ - "/": make([]*File, 0), - }, - ResolveSymlinks: resolveSymlinks, - AllowErrors: allowErrors, - compression: compressionType, - } - if resolveSymlinks { - out.symlinkTable = make(map[string]string) - } - return &out, nil +func NewWriterWithOptions(compressionType int, allowErrors bool) (*Writer, error) { + } -type fileError struct { - err error - files []*File +//AddFile attempts to add an os.File to the archive at it's root. +func (w *Writer) AddFile(file *os.File) error { + return w.AddFileToFolder("/", file) } -//convertFile converts the given os.File to a squashfs.File. Returns the errors and converted file to the channels. -func (w *Writer) convertFile(squashfsPath string, file *os.File, subDir bool, fileErrChan chan fileError) { - var out fileError - var fil File - fil.Reader = file - fil.path = squashfsPath - fil.name = path.Base(file.Name()) - mode := fil.Mode() +//AddFileToFolder adds the given file to the squashfs archive, placing it inside the given folder. +func (w *Writer) AddFileToFolder(folder string, file *os.File) error { + name := path.Base(file.Name()) + if !strings.HasSuffix(folder, "/") { + folder += "/" + } + return w.AddFileTo(folder+name, file) +} - if mode.IsRegular() { - fil.filType = inode.BasicFileType - goto successExit - } else if mode.IsDir() { - fil.filType = inode.BasicSymlinkType - subDirs, err := file.Readdirnames(-1) +//AddFileTo adds the given file to the squashfs archive at the given filepath. +func (w *Writer) AddFileTo(filepath string, file *os.File) error { + filepath = path.Clean(filepath) + if !strings.HasPrefix(filepath, "/") { + filepath = "/" + filepath + } + var holder fileHolder + holder.path, holder.name = path.Split(filepath) + holder.reader = file + stat, err := file.Stat() + if err != nil { + return err + } + holder.folder = stat.IsDir() + holder.symlink = (stat.Mode()&os.ModeSymlink == os.ModeSymlink) + if holder.symlink { + target, err := os.Readlink(file.Name()) if err != nil { - if w.AllowErrors && !subDir { - log.Println("Can't get sub-directories for", file.Name()) - log.Println(err) - } else { - out.err = err - } - goto failExit + return err } - subDirChan := make(chan fileError) - for _, filName := range subDirs { - go func(filename string, returnChan chan fileError) { - subFil, err := os.Open(filename) - if err != nil { - out.err = err - returnChan <- fileError{ - err: err, - } - return - } - w.convertFile(fil.Path(), subFil, true, subDirChan) - }(file.Name()+filName, subDirChan) - } - for range subDirs { - filErr := <-subDirChan - if filErr.err != nil { - if w.AllowErrors && !subDir { - log.Println("Error while adding subdirectory of", file.Name()) - log.Println(filErr.err) - } else if subDir { - if out.err == nil { - out.err = filErr.err - } - } else { - out.err = err - goto failExit - } - continue - } - out.files = append(out.files, filErr.files...) - } - goto successExit - } else if mode&os.ModeSymlink == os.ModeSymlink { - fil.filType = inode.BasicSymlinkType - symLocation, err := os.Readlink(file.Name()) + holder.symLocation = target + } else if holder.folder { + subDirNames, err := file.Readdirnames(-1) if err != nil { - if w.AllowErrors && !subDir { - log.Println("Error while getting symlink's information for", file.Name()) + return err + } + dirsAdded := make([]string, 0) + for _, subDir := range subDirNames { + fil, err := os.Open(file.Name() + subDir) + if err != nil { + return err + } + err = w.AddFileToFolder(holder.path+"/"+holder.name, fil) + if err != nil && !w.AllowErrors { + for _, dir := range dirsAdded { + w.Remove(dir) + } + return err + } else if err != nil { + log.Println("Error while adding", fil.Name()) log.Println(err) - } else { - out.err = err } - goto failExit - } - if w.ResolveSymlinks { - if val, ok := w.symlinkTable[symLocation]; ok { - symLocation = val - } else if val, ok := w.symTableTemp[symLocation]; ok { - symLocation = val - } else { - //TODO: either add the file, or place the file in this location. Maybe defer this until after all the other files are added? + if !w.AllowErrors { + dirsAdded = append(dirsAdded, holder.path+"/"+holder.name) } } - //TODO: store the symLocation inside the File somehow.... + } else if !stat.Mode().IsRegular() { + return errors.New("Unsupported file type " + file.Name()) } - if w.AllowErrors && !subDir { - log.Println("Unsupported file type for", file.Name()) - } else { - out.err = errors.New("Unsupported file type") - } -failExit: //before this is used, make sure to log or set the error. - fileErrChan <- out - return -successExit: - out.files = []*File{&fil} - fileErrChan <- out - return + w.structure[holder.path] = append(w.structure[holder.path], &holder) + return nil } -//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 { - squashfsPath = path.Clean(squashfsPath) - if strings.HasPrefix(squashfsPath, "/") { - squashfsPath = strings.TrimPrefix(squashfsPath, "/") +//AddReaderTo adds the data from the given reader to the archive as a file located at the given filepath. +//Data from the reader is not read until the squashfs archive is writen. +//If the given reader implements io.Closer, it will be closed after it is fully read. +func (w *Writer) AddReaderTo(filepath string, reader io.Reader) error { + filepath = path.Clean(filepath) + if !strings.HasPrefix(filepath, "/") { + filepath = "/" + filepath } - if squashfsPath == "." { - squashfsPath = "/" - } - fileErrChan := make(chan fileError) - for _, fil := range files { - go w.convertFile(squashfsPath, fil, false, fileErrChan) - } - return errors.New("Not yet ready") + var holder fileHolder + holder.path, holder.name = path.Split(filepath) + holder.reader = reader + w.structure[holder.path] = append(w.structure[holder.path], &holder) + 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 { - return w.AddFilesToPath("/", files...) +//Remove tries to remove the file(s) at the given filepath. If wildcards are used, it will remove all files that match. +//Returns true if one or more files are removed. +func (w *Writer) Remove(filepath string) bool { + var matchFound bool + filepath = path.Clean(filepath) + if !strings.HasPrefix(filepath, "/") { + filepath = "/" + filepath + } + dir, name := path.Split(filepath) + for structDir, files := range w.structure { + if match, _ := path.Match(dir, structDir); match { + for i, fil := range files { + if match, _ = path.Match(name, fil.name); match { + matchFound = true + if i != len(files)-1 { + w.structure[structDir] = append(w.structure[structDir][:i], w.structure[structDir][i+1:]...) + } else { + w.structure[structDir] = w.structure[structDir][:i] + } + } + } + } + } + return matchFound } -//RemoveFileAt removes the file at filepath from the Writer. -//If multiple files match the given filepath (such as if there are wildcards), all matching files are removed. -//If one or more files are removed, returns true. -func (w *Writer) RemoveFileAt(filepath string) bool { - //TODO - return false +//FixSymlinks will scan through the squashfs archive and try to find broken symlinks and fix them. +//This done by replacing the symlink with the target file and then pointing other symlinks to that file. +// +//If this is not run before writing, you may end up with broken symlinks. +func (w *Writer) FixSymlinks() error { + return errors.New("DON'T") +} + +//WriteToFilename creates the squashfs archive with the given filepath. +func (w *Writer) WriteToFilename(filepath string) error { + newFil, err := os.Create(filepath) + if err != nil { + return err + } + _, err = w.WriteTo(newFil) + return err +} + +//WriteTo attempts to write the archive to the given io.Writer. +func (w *Writer) WriteTo(write io.Writer) (int64, error) { + return 0, errors.New("I SAID DON'T") }