github.com/containers/podman/v4@v4.9.4/pkg/machine/compression/decompress.go (about)

     1  package compression
     2  
     3  import (
     4  	"archive/zip"
     5  	"bufio"
     6  	"errors"
     7  	"io"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"runtime"
    12  	"strings"
    13  
    14  	"github.com/containers/image/v5/pkg/compression"
    15  	"github.com/containers/podman/v4/pkg/machine/define"
    16  	"github.com/containers/podman/v4/utils"
    17  	"github.com/containers/storage/pkg/archive"
    18  	"github.com/sirupsen/logrus"
    19  	"github.com/ulikunitz/xz"
    20  )
    21  
    22  func Decompress(localPath *define.VMFile, uncompressedPath string) error {
    23  	var isZip bool
    24  	uncompressedFileWriter, err := os.OpenFile(uncompressedPath, os.O_CREATE|os.O_RDWR, 0600)
    25  	if err != nil {
    26  		return err
    27  	}
    28  	sourceFile, err := localPath.Read()
    29  	if err != nil {
    30  		return err
    31  	}
    32  	if strings.HasSuffix(localPath.GetPath(), ".zip") {
    33  		isZip = true
    34  	}
    35  	prefix := "Copying uncompressed file"
    36  	compressionType := archive.DetectCompression(sourceFile)
    37  	if compressionType != archive.Uncompressed || isZip {
    38  		prefix = "Extracting compressed file"
    39  	}
    40  	prefix += ": " + filepath.Base(uncompressedPath)
    41  	if compressionType == archive.Xz {
    42  		return decompressXZ(prefix, localPath.GetPath(), uncompressedFileWriter)
    43  	}
    44  	if isZip && runtime.GOOS == "windows" {
    45  		return decompressZip(prefix, localPath.GetPath(), uncompressedFileWriter)
    46  	}
    47  	return decompressEverythingElse(prefix, localPath.GetPath(), uncompressedFileWriter)
    48  }
    49  
    50  // Will error out if file without .Xz already exists
    51  // Maybe extracting then renaming is a good idea here..
    52  // depends on Xz: not pre-installed on mac, so it becomes a brew dependency
    53  func decompressXZ(prefix string, src string, output io.WriteCloser) error {
    54  	var read io.Reader
    55  	var cmd *exec.Cmd
    56  
    57  	stat, err := os.Stat(src)
    58  	if err != nil {
    59  		return err
    60  	}
    61  	file, err := os.Open(src)
    62  	if err != nil {
    63  		return err
    64  	}
    65  	defer file.Close()
    66  
    67  	p, bar := utils.ProgressBar(prefix, stat.Size(), prefix+": done")
    68  	proxyReader := bar.ProxyReader(file)
    69  	defer func() {
    70  		if err := proxyReader.Close(); err != nil {
    71  			logrus.Error(err)
    72  		}
    73  	}()
    74  
    75  	// Prefer Xz utils for fastest performance, fallback to go xi2 impl
    76  	if _, err := exec.LookPath("xz"); err == nil {
    77  		cmd = exec.Command("xz", "-d", "-c")
    78  		cmd.Stdin = proxyReader
    79  		read, err = cmd.StdoutPipe()
    80  		if err != nil {
    81  			return err
    82  		}
    83  		cmd.Stderr = os.Stderr
    84  	} else {
    85  		// This XZ implementation is reliant on buffering. It is also 3x+ slower than XZ utils.
    86  		// Consider replacing with a faster implementation (e.g. xi2) if podman machine is
    87  		// updated with a larger image for the distribution base.
    88  		buf := bufio.NewReader(proxyReader)
    89  		read, err = xz.NewReader(buf)
    90  		if err != nil {
    91  			return err
    92  		}
    93  	}
    94  
    95  	done := make(chan bool)
    96  	go func() {
    97  		if _, err := io.Copy(output, read); err != nil {
    98  			logrus.Error(err)
    99  		}
   100  		output.Close()
   101  		done <- true
   102  	}()
   103  
   104  	if cmd != nil {
   105  		err := cmd.Start()
   106  		if err != nil {
   107  			return err
   108  		}
   109  		p.Wait()
   110  		return cmd.Wait()
   111  	}
   112  	<-done
   113  	p.Wait()
   114  	return nil
   115  }
   116  
   117  func decompressEverythingElse(prefix string, src string, output io.WriteCloser) error {
   118  	stat, err := os.Stat(src)
   119  	if err != nil {
   120  		return err
   121  	}
   122  	f, err := os.Open(src)
   123  	if err != nil {
   124  		return err
   125  	}
   126  	p, bar := utils.ProgressBar(prefix, stat.Size(), prefix+": done")
   127  	proxyReader := bar.ProxyReader(f)
   128  	defer func() {
   129  		if err := proxyReader.Close(); err != nil {
   130  			logrus.Error(err)
   131  		}
   132  	}()
   133  	uncompressStream, _, err := compression.AutoDecompress(proxyReader)
   134  	if err != nil {
   135  		return err
   136  	}
   137  	defer func() {
   138  		if err := uncompressStream.Close(); err != nil {
   139  			logrus.Error(err)
   140  		}
   141  		if err := output.Close(); err != nil {
   142  			logrus.Error(err)
   143  		}
   144  	}()
   145  
   146  	_, err = io.Copy(output, uncompressStream)
   147  	p.Wait()
   148  	return err
   149  }
   150  
   151  func decompressZip(prefix string, src string, output io.WriteCloser) error {
   152  	zipReader, err := zip.OpenReader(src)
   153  	if err != nil {
   154  		return err
   155  	}
   156  	if len(zipReader.File) != 1 {
   157  		return errors.New("machine image files should consist of a single compressed file")
   158  	}
   159  	f, err := zipReader.File[0].Open()
   160  	if err != nil {
   161  		return err
   162  	}
   163  	defer func() {
   164  		if err := f.Close(); err != nil {
   165  			logrus.Error(err)
   166  		}
   167  	}()
   168  	defer func() {
   169  		if err := output.Close(); err != nil {
   170  			logrus.Error(err)
   171  		}
   172  	}()
   173  	size := int64(zipReader.File[0].CompressedSize64)
   174  	p, bar := utils.ProgressBar(prefix, size, prefix+": done")
   175  	proxyReader := bar.ProxyReader(f)
   176  	defer func() {
   177  		if err := proxyReader.Close(); err != nil {
   178  			logrus.Error(err)
   179  		}
   180  	}()
   181  	_, err = io.Copy(output, proxyReader)
   182  	p.Wait()
   183  	return err
   184  }