Files
podman/pkg/machine/compression/decompress.go
Mario Loriedo 88af8852db Refactor machine decompress.go
Added some tests to verify that files extractions works
with different compression format.

Created a decompressor interface with 2 main methods:
  reader(): returns an io.Reader for the specific compression algorithm
  copy(): extracts the compressed file into the file provided as param

Created 5 decompressor types:
- gzip: extract gzip files
- xz: extract xz files
- zip: extract zip files
- generic: extract any other file using github.com/containers/image/v5/pkg/compression
- uncompressed: only do a copy of the file

Minor fix to the progress bar instances: added a call to bar.Abort(false)
that happens before Progress.Wait() to avoid that it hangs when a bar is
not set as completed although extraction is done.

Signed-off-by: Mario Loriedo <mario.loriedo@gmail.com>
2024-02-25 22:29:02 +01:00

115 lines
3.2 KiB
Go

package compression
import (
"io"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/containers/podman/v5/pkg/machine/define"
"github.com/containers/podman/v5/utils"
"github.com/containers/storage/pkg/archive"
"github.com/sirupsen/logrus"
)
const (
zipExt = ".zip"
progressBarPrefix = "Extracting compressed file"
macOs = "darwin"
)
type decompressor interface {
srcFilePath() string
reader() (io.Reader, error)
copy(w *os.File, r io.Reader) error
close()
}
func newDecompressor(compressedFilePath string, compressedFileContent []byte) decompressor {
compressionType := archive.DetectCompression(compressedFileContent)
os := runtime.GOOS
hasZipSuffix := strings.HasSuffix(compressedFilePath, zipExt)
switch {
case compressionType == archive.Xz:
return newXzDecompressor(compressedFilePath)
case compressionType == archive.Uncompressed && hasZipSuffix:
return newZipDecompressor(compressedFilePath)
case compressionType == archive.Uncompressed:
return newUncompressedDecompressor(compressedFilePath)
case compressionType == archive.Gzip && os == macOs:
return newGzipDecompressor(compressedFilePath)
default:
return newGenericDecompressor(compressedFilePath)
}
}
func Decompress(srcVMFile *define.VMFile, dstFilePath string) error {
srcFilePath := srcVMFile.GetPath()
// Are we reading full image file?
// Only few bytes are read to detect
// the compression type
srcFileContent, err := srcVMFile.Read()
if err != nil {
return err
}
d := newDecompressor(srcFilePath, srcFileContent)
return runDecompression(d, dstFilePath)
}
func runDecompression(d decompressor, dstFilePath string) error {
decompressorReader, err := d.reader()
if err != nil {
return err
}
defer d.close()
stat, err := os.Stat(d.srcFilePath())
if err != nil {
return err
}
initMsg := progressBarPrefix + ": " + filepath.Base(dstFilePath)
finalMsg := initMsg + ": done"
// We are getting the compressed file size but
// the progress bar needs the full size of the
// decompressed file.
// As a result the progress bar shows 100%
// before the decompression completes.
// A workaround is to set the size to -1 but the
// side effect is that we won't see any advancment in
// the bar.
// An update in utils.ProgressBar to handle is needed
// to improve the case of size=-1 (i.e. unkwonw size).
p, bar := utils.ProgressBar(initMsg, stat.Size(), finalMsg)
// Wait for bars to complete and then shut down the bars container
defer p.Wait()
readProxy := bar.ProxyReader(decompressorReader)
// Interrupts the bar goroutine. It's important that
// bar.Abort(false) is called before p.Wait(), otherwise
// can hang.
defer bar.Abort(false)
dstFileWriter, err := os.OpenFile(dstFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, stat.Mode())
if err != nil {
logrus.Errorf("Unable to open destination file %s for writing: %q", dstFilePath, err)
return err
}
defer func() {
if err := dstFileWriter.Close(); err != nil {
logrus.Errorf("Unable to to close destination file %s: %q", dstFilePath, err)
}
}()
err = d.copy(dstFileWriter, readProxy)
if err != nil {
logrus.Errorf("Error extracting compressed file: %q", err)
return err
}
return nil
}