-
Notifications
You must be signed in to change notification settings - Fork 2.4k
/
decompress.go
112 lines (95 loc) · 3.48 KB
/
decompress.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package compression
import (
"io"
"os"
"path/filepath"
"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 (
decompressedFileFlag = os.O_CREATE | os.O_TRUNC | os.O_WRONLY
macOs = "darwin"
progressBarPrefix = "Extracting compressed file"
zipExt = ".zip"
magicNumberMaxBytes = 10
)
type decompressor interface {
compressedFileSize() int64
compressedFileMode() os.FileMode
compressedFileReader() (io.ReadCloser, error)
decompress(w io.WriteSeeker, r io.Reader) error
close()
}
func Decompress(compressedVMFile *define.VMFile, decompressedFilePath string) error {
compressedFilePath := compressedVMFile.GetPath()
compressedFileMagicNum, err := compressedVMFile.ReadMagicNumber(magicNumberMaxBytes)
if err != nil {
return err
}
var d decompressor
if d, err = newDecompressor(compressedFilePath, compressedFileMagicNum); err != nil {
return err
}
return runDecompression(d, decompressedFilePath)
}
func newDecompressor(compressedFilePath string, compressedFileMagicNum []byte) (decompressor, error) {
compressionType := archive.DetectCompression(compressedFileMagicNum)
hasZipSuffix := strings.HasSuffix(compressedFilePath, zipExt)
switch {
// Zip files are not guaranteed to have a magic number at the beginning
// of the file, so we need to use the file name to detect them.
case compressionType == archive.Uncompressed && hasZipSuffix:
return newZipDecompressor(compressedFilePath)
case compressionType == archive.Uncompressed:
return newUncompressedDecompressor(compressedFilePath)
default:
return newGenericDecompressor(compressedFilePath)
}
}
func runDecompression(d decompressor, decompressedFilePath string) (retErr error) {
compressedFileReader, err := d.compressedFileReader()
if err != nil {
return err
}
defer d.close()
filesize := d.compressedFileSize()
// bar.ProxyReader() returns nil when the bar is finished.
// When the size is set to 0 the bar will finfish immediately, but this happens
// in a different goroutine so it is race and only happens sometimes.
// In general if the input is an empty file then we do not have to display a
// progress bar at all as there is nothing to copy/extract really
// https://github.com/containers/podman/issues/23281
if filesize > 0 {
initMsg := progressBarPrefix + ": " + filepath.Base(decompressedFilePath)
finalMsg := initMsg + ": done"
p, bar := utils.ProgressBar(initMsg, filesize, finalMsg)
// Wait for bars to complete and then shut down the bars container
defer p.Wait()
compressedFileReader = bar.ProxyReader(compressedFileReader)
// Interrupts the bar goroutine. It's important that
// bar.Abort(false) is called before p.Wait(), otherwise
// can hang.
defer bar.Abort(false)
}
var decompressedFileWriter *os.File
if decompressedFileWriter, err = os.OpenFile(decompressedFilePath, decompressedFileFlag, d.compressedFileMode()); err != nil {
logrus.Errorf("Unable to open destination file %s for writing: %q", decompressedFilePath, err)
return err
}
defer func() {
if err := decompressedFileWriter.Close(); err != nil {
logrus.Warnf("Unable to to close destination file %s: %q", decompressedFilePath, err)
if retErr == nil {
retErr = err
}
}
}()
if err = d.decompress(decompressedFileWriter, compressedFileReader); err != nil {
logrus.Errorf("Error extracting compressed file: %q", err)
return err
}
return nil
}