/
archive_linux.go
146 lines (126 loc) · 4.17 KB
/
archive_linux.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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package shared
import (
"bytes"
"fmt"
"io"
"os"
"strings"
"golang.org/x/sys/unix"
"github.com/lxc/lxd/shared/ioprogress"
"github.com/lxc/lxd/shared/logger"
)
func DetectCompression(fname string) ([]string, string, []string, error) {
f, err := os.Open(fname)
if err != nil {
return nil, "", nil, err
}
defer f.Close()
return DetectCompressionFile(f)
}
func DetectCompressionFile(f io.ReadSeeker) ([]string, string, []string, error) {
// read header parts to detect compression method
// bz2 - 2 bytes, 'BZ' signature/magic number
// gz - 2 bytes, 0x1f 0x8b
// lzma - 6 bytes, { [0x000, 0xE0], '7', 'z', 'X', 'Z', 0x00 } -
// xy - 6 bytes, header format { 0xFD, '7', 'z', 'X', 'Z', 0x00 }
// tar - 263 bytes, trying to get ustar from 257 - 262
header := make([]byte, 263)
_, err := f.Read(header)
if err != nil {
return nil, "", nil, err
}
switch {
case bytes.Equal(header[0:2], []byte{'B', 'Z'}):
return []string{"-jxf"}, ".tar.bz2", []string{"bzip2", "-d"}, nil
case bytes.Equal(header[0:2], []byte{0x1f, 0x8b}):
return []string{"-zxf"}, ".tar.gz", []string{"gzip", "-d"}, nil
case (bytes.Equal(header[1:5], []byte{'7', 'z', 'X', 'Z'}) && header[0] == 0xFD):
return []string{"-Jxf"}, ".tar.xz", []string{"xz", "-d"}, nil
case (bytes.Equal(header[1:5], []byte{'7', 'z', 'X', 'Z'}) && header[0] != 0xFD):
return []string{"--lzma", "-xf"}, ".tar.lzma", []string{"lzma", "-d"}, nil
case bytes.Equal(header[0:3], []byte{0x5d, 0x00, 0x00}):
return []string{"--lzma", "-xf"}, ".tar.lzma", []string{"lzma", "-d"}, nil
case bytes.Equal(header[257:262], []byte{'u', 's', 't', 'a', 'r'}):
return []string{"-xf"}, ".tar", []string{}, nil
case bytes.Equal(header[0:4], []byte{'h', 's', 'q', 's'}):
return []string{""}, ".squashfs", nil, nil
default:
return nil, "", nil, fmt.Errorf("Unsupported compression")
}
}
func Unpack(file string, path string, blockBackend bool, runningInUserns bool, tracker *ioprogress.ProgressTracker) error {
extractArgs, extension, _, err := DetectCompression(file)
if err != nil {
return err
}
command := ""
args := []string{}
var reader io.Reader
if strings.HasPrefix(extension, ".tar") {
command = "tar"
if runningInUserns {
args = append(args, "--wildcards")
args = append(args, "--exclude=dev/*")
args = append(args, "--exclude=./dev/*")
args = append(args, "--exclude=rootfs/dev/*")
args = append(args, "--exclude=rootfs/./dev/*")
}
args = append(args, "-C", path, "--numeric-owner", "--xattrs-include=*")
args = append(args, extractArgs...)
args = append(args, "-")
f, err := os.Open(file)
if err != nil {
return err
}
defer f.Close()
reader = f
// Attach the ProgressTracker if supplied.
if tracker != nil {
fsinfo, err := f.Stat()
if err != nil {
return err
}
tracker.Length = fsinfo.Size()
reader = &ioprogress.ProgressReader{
ReadCloser: f,
Tracker: tracker,
}
}
} else if strings.HasPrefix(extension, ".squashfs") {
// unsquashfs does not support reading from stdin,
// so ProgressTracker is not possible.
command = "unsquashfs"
args = append(args, "-f", "-d", path, "-n")
// Limit unsquashfs chunk size to 10% of memory and up to 256MB (default)
// When running on a low memory system, also disable multi-processing
mem, err := DeviceTotalMemory()
mem = mem / 1024 / 1024 / 10
if err == nil && mem < 256 {
args = append(args, "-da", fmt.Sprintf("%d", mem), "-fr", fmt.Sprintf("%d", mem), "-p", "1")
}
args = append(args, file)
} else {
return fmt.Errorf("Unsupported image format: %s", extension)
}
err = RunCommandWithFds(reader, nil, command, args...)
if err != nil {
// Check if we ran out of space
fs := unix.Statfs_t{}
err1 := unix.Statfs(path, &fs)
if err1 != nil {
return err1
}
// Check if we're running out of space
if int64(fs.Bfree) < int64(2*fs.Bsize) {
if blockBackend {
return fmt.Errorf("Unable to unpack image, run out of disk space (consider increasing your pool's volume.size)")
} else {
return fmt.Errorf("Unable to unpack image, run out of disk space")
}
}
logger.Debugf("Unpacking failed")
logger.Debugf(err.Error())
return fmt.Errorf("Unpack failed, %s.", err)
}
return nil
}