package archive import ( "archive/tar" "compress/gzip" "fmt" "io" "os" "path/filepath" "strings" makeshift "git.towk2.me/towk/makeshift/pkg" ) func Create(filenames []string, buf io.Writer, hooks []makeshift.Hook) error { // Create new Writers for gzip and tar // These writers are chained. Writing to the tar writer will // write to the gzip writer which in turn will write to // the "buf" writer gw := gzip.NewWriter(buf) defer gw.Close() tw := tar.NewWriter(gw) defer tw.Close() // Iterate over files and add them to the tar archive for _, filename := range filenames { err := addToArchive(tw, filename, hooks) if err != nil { return err } } return nil } func Expand(tarname, xpath string) error { tarfile, err := os.Open(tarname) if err != nil { return err } defer tarfile.Close() // absPath, err := filepath.Abs(xpath) // if err != nil { // return err // } tr := tar.NewReader(tarfile) if strings.HasSuffix(tarname, ".gz") { gz, err := gzip.NewReader(tarfile) if err != nil { return fmt.Errorf("failed to create new gzip reader: %v", err) } defer gz.Close() tr = tar.NewReader(gz) } // untar each segment for { header, err := tr.Next() if err == io.EOF { break } if err != nil { return fmt.Errorf("failed to get next tar header: %v", err) } // determine proper file path info var ( fileinfo = header.FileInfo() filename = header.Name file *os.File abspath string dirpath string ) // absFileName := filepath.Join(absPath, filename) // if a dir, create it, then go to next segment if fileinfo.Mode().IsDir() { if err := os.MkdirAll(filename, 0o755); err != nil { return fmt.Errorf("failed to make directory '%s': %v", filename, err) } continue } dirpath = filepath.Dir(filename) if err = os.MkdirAll(dirpath, 0o777); err != nil { return fmt.Errorf("failed to make directory '%s': %v", err) } // create new file with original file mode abspath, err = filepath.Abs(filename) if err != nil { return fmt.Errorf("failed to get absolute path: %v", err) } file, err = os.OpenFile( abspath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fileinfo.Mode().Perm(), ) if err != nil { return fmt.Errorf("failed to open file: %v", err) } // fmt.Printf("x %s\n", filename) // copy the contents to the new file n, err := io.Copy(file, tr) if err != nil { return fmt.Errorf("failed to copy file: %v", err) } if err = file.Close(); err != nil { return fmt.Errorf("failed to close file: %v", err) } if n != fileinfo.Size() { return fmt.Errorf("wrote %d, want %d", n, fileinfo.Size()) } } return nil } func addToArchive(tw *tar.Writer, filename string, hooks []makeshift.Hook) error { var ( tempfile = fmt.Sprintf("%s.tmp", filename) file *os.File contents []byte data any err error ) // run pre-hooks to modify the contents of the file // before archiving using plugins for _, hook := range hooks { // set the file in the data store before running hook contents, err = os.ReadFile(filename) if err != nil { return fmt.Errorf("failed to read '%s' to download: %v", filename, err) } hook.Data.Set("file", contents) err = hook.Run() if err != nil { return err } // create temporary file to use to add to archive hook = hooks[len(hooks)-1] data, err = hook.Data.Get("out") if err != nil { return fmt.Errorf("failed to get output data from '%s' plugin: %v", hook.Plugin.Name(), err) } err = os.WriteFile(tempfile, data.([]byte), 0o777) if err != nil { return fmt.Errorf("failed to write temporary file: %v", err) } } // use original file if no hooks to write archive if len(hooks) == 0 { file, err = os.Open(filename) } else { file, err = os.Open(tempfile) } if err != nil { return fmt.Errorf("failed to open archive file: %v", err) } defer file.Close() // get FileInfo for file size, mode, etc. info, err := file.Stat() if err != nil { return err } // skip file if it's a directory if info.IsDir() { return nil } // create a tar Header from the FileInfo data header, err := tar.FileInfoHeader(info, info.Name()) if err != nil { return fmt.Errorf("failed to create FileInfoHeader: %v", err) } // use full path as name (FileInfoHeader only takes the basename) to // preserve directory structure // see for more info: https://golang.org/src/archive/tar/common.go?#L626 header.Name = filename // Write file header to the tar archive err = tw.WriteHeader(header) if err != nil { return err } // copy file content to tar archive if len(hooks) == 0 { _, err = io.Copy(tw, file) } else { _, err = io.Copy(tw, strings.NewReader(string(data.([]byte)))) } if err != nil { return err } // delete the temporary file since we're done with it if len(hooks) != 0 { err = os.Remove(tempfile) if err != nil { return err } } return nil }