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 }