package archiver import ( "archive/tar" "bytes" "compress/gzip" "fmt" "io" "os" "path/filepath" ) // Create creates a tar.gz archive from the given files relative to rootDir // Returns: archive reader, archive size, error func Create(files []string, rootDir string) (io.Reader, int64, error) { var buf bytes.Buffer gzWriter := gzip.NewWriter(&buf) tarWriter := tar.NewWriter(gzWriter) for _, file := range files { // Get full path fullPath := filepath.Join(rootDir, file) // Open file f, err := os.Open(fullPath) if err != nil { return nil, 0, fmt.Errorf("failed to open file %s: %w", file, err) } // Get file info info, err := f.Stat() if err != nil { f.Close() return nil, 0, fmt.Errorf("failed to stat file %s: %w", file, err) } // Create tar header header := &tar.Header{ Name: file, // Use relative path Size: info.Size(), Mode: int64(info.Mode()), ModTime: info.ModTime(), } // Write header if err := tarWriter.WriteHeader(header); err != nil { f.Close() return nil, 0, fmt.Errorf("failed to write tar header for %s: %w", file, err) } // Write file content if _, err := io.Copy(tarWriter, f); err != nil { f.Close() return nil, 0, fmt.Errorf("failed to write file content for %s: %w", file, err) } f.Close() } // Close tar writer if err := tarWriter.Close(); err != nil { return nil, 0, fmt.Errorf("failed to close tar writer: %w", err) } // Close gzip writer if err := gzWriter.Close(); err != nil { return nil, 0, fmt.Errorf("failed to close gzip writer: %w", err) } size := int64(buf.Len()) return &buf, size, nil } // Extract extracts a tar.gz archive to the destination directory // Returns: list of extracted files, error func Extract(archive io.Reader, destDir string) ([]string, error) { // Create gzip reader gzReader, err := gzip.NewReader(archive) if err != nil { return nil, fmt.Errorf("failed to create gzip reader: %w", err) } defer gzReader.Close() // Create tar reader tarReader := tar.NewReader(gzReader) var extractedFiles []string for { header, err := tarReader.Next() if err == io.EOF { break } if err != nil { return nil, fmt.Errorf("failed to read tar header: %w", err) } // Get target path targetPath := filepath.Join(destDir, header.Name) // Ensure directory exists dir := filepath.Dir(targetPath) if err := os.MkdirAll(dir, 0755); err != nil { return nil, fmt.Errorf("failed to create directory %s: %w", dir, err) } // Create file f, err := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(header.Mode)) if err != nil { return nil, fmt.Errorf("failed to create file %s: %w", targetPath, err) } // Copy content if _, err := io.Copy(f, tarReader); err != nil { f.Close() return nil, fmt.Errorf("failed to write file content for %s: %w", targetPath, err) } f.Close() extractedFiles = append(extractedFiles, header.Name) } return extractedFiles, nil } // List lists files in the archive without extracting func List(archive io.Reader) ([]string, error) { // Create gzip reader gzReader, err := gzip.NewReader(archive) if err != nil { return nil, fmt.Errorf("failed to create gzip reader: %w", err) } defer gzReader.Close() // Create tar reader tarReader := tar.NewReader(gzReader) var files []string for { header, err := tarReader.Next() if err == io.EOF { break } if err != nil { return nil, fmt.Errorf("failed to read tar header: %w", err) } files = append(files, header.Name) } return files, nil }