Files
squashfs/writer.go
T
Caleb Gardner 43fe4f91a2 More work on Writer
Adding files to the Writer should work properly now (except symlinks)
Threaded the adding of files.
Added ability to ignore errors when adding files.
2021-01-03 04:39:39 -06:00

190 lines
5.2 KiB
Go

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...).
//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
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, autoresolving symlinks, and allowErrors)
func NewWriter() (*Writer, error) {
return NewWriterWithOptions(true, true, GzipCompression)
}
//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
}
//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 {
if w.AllowErrors {
log.Println("Error while getting FileInfo for", file.Name()+":")
log.Println(err)
err = nil
}
errChan <- err
return
}
if stat.IsDir() {
fil.filType = inode.BasicDirectoryType
dirs, err := file.Readdirnames(-1)
if err != nil {
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 {
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 {
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
}
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 {
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 {
return w.AddFilesToPath("/", files...)
}
//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
}