forked from u-root/u-root
-
Notifications
You must be signed in to change notification settings - Fork 0
/
tar.go
216 lines (193 loc) · 5.26 KB
/
tar.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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
// Copyright 2019 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tarutil
import (
"archive/tar"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
)
// passesFilters returns true if the given file passes all filters, false otherwise.
func passesFilters(hdr *tar.Header, filters []Filter) bool {
for _, filter := range filters {
if !filter(hdr) {
return false
}
}
return true
}
// applyToArchive applies function f to all files in the given archive
func applyToArchive(
tarFile io.Reader, f func(tr *tar.Reader, hdr *tar.Header) error) error {
tr := tar.NewReader(tarFile)
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
if err := f(tr, hdr); err != nil {
return err
}
}
return nil
}
// ListArchive lists the contents of the given tar archive.
func ListArchive(tarFile io.Reader) error {
return applyToArchive(tarFile, func(tr *tar.Reader, hdr *tar.Header) error {
fmt.Println(hdr.Name)
return nil
})
}
// ExtractDir extracts all the contents of the tar file to the given directory.
func ExtractDir(tarFile io.Reader, dir string) error {
return ExtractDirFilter(tarFile, dir, nil)
}
// ExtractDirFilter extracts a tar file with the given filter.
func ExtractDirFilter(tarFile io.Reader, dir string, filters []Filter) error {
fi, err := os.Stat(dir)
if os.IsNotExist(err) {
if err := os.Mkdir(dir, os.ModePerm); err != nil {
return fmt.Errorf("could not create directory %s: %v", dir, err)
}
} else if err != nil || !fi.IsDir() {
return fmt.Errorf("could not stat directory %s: %v", dir, err)
}
return applyToArchive(tarFile, func(tr *tar.Reader, hdr *tar.Header) error {
if !passesFilters(hdr, filters) {
return nil
}
return createFileInRoot(hdr, tr, dir)
})
}
// CreateTar creates a new tar file with all the contents of a directory.
func CreateTar(tarFile io.Writer, files []string) error {
return CreateTarFilter(tarFile, files, nil)
}
// CreateTarFilter creates a new tar file of the given files, with the given filter.
func CreateTarFilter(tarFile io.Writer, files []string, filters []Filter) error {
tw := tar.NewWriter(tarFile)
for _, file := range files {
err := filepath.Walk(file, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
symlink := ""
if info.Mode()&os.ModeSymlink != 0 {
// TODO: symlinks
return fmt.Errorf("symlinks not yet supported: %q", path)
}
hdr, err := tar.FileInfoHeader(info, symlink)
if err != nil {
return err
}
hdr.Name = path
if !passesFilters(hdr, filters) {
return nil
}
if err := tw.WriteHeader(hdr); err != nil {
return err
}
switch hdr.Typeflag {
case tar.TypeLink, tar.TypeSymlink, tar.TypeChar, tar.TypeBlock, tar.TypeDir, tar.TypeFifo:
default:
f, err := os.Open(path)
if err != nil {
return err
}
if _, err := io.Copy(tw, f); err != nil {
f.Close()
return err
}
f.Close()
}
return nil
})
if err != nil {
return err
}
}
if err := tw.Close(); err != nil {
return err
}
return nil
}
func createFileInRoot(hdr *tar.Header, r io.Reader, rootDir string) error {
fi := hdr.FileInfo()
path := filepath.Clean(filepath.Join(rootDir, hdr.Name))
if !strings.HasPrefix(path, filepath.Clean(rootDir)) {
return fmt.Errorf("file outside root directory: %q", path)
}
switch fi.Mode() & os.ModeType {
case os.ModeSymlink:
// TODO: support symlinks
return fmt.Errorf("symlinks not yet supported: %q", path)
case os.FileMode(0):
f, err := os.Create(path)
if err != nil {
return err
}
if _, err := io.Copy(f, r); err != nil {
f.Close()
return err
}
if err := f.Close(); err != nil {
return err
}
case os.ModeDir:
if err := os.MkdirAll(path, fi.Mode()&os.ModePerm); err != nil {
return err
}
case os.ModeDevice:
// TODO: support block device
return fmt.Errorf("block device not yet supported: %q", path)
case os.ModeCharDevice:
// TODO: support char device
return fmt.Errorf("char device not yet supported: %q", path)
default:
return fmt.Errorf("%q: Unknown type %#o", path, fi.Mode()&os.ModeType)
}
if err := os.Chmod(path, fi.Mode()&os.ModePerm); err != nil {
return fmt.Errorf("error setting mode %#o on %q: %v",
fi.Mode()&os.ModePerm, path, err)
}
// TODO: also set ownership, etc...
return nil
}
// Filter is applied to each file while creating or extracting a tar archive.
// The filter can modify the tar.Header struct. If the filter returns false,
// the file is omitted.
type Filter func(hdr *tar.Header) bool
// NoFilter does not filter or modify any files.
func NoFilter(hdr *tar.Header) bool {
return true
}
// VerboseFilter prints the name of every file.
func VerboseFilter(hdr *tar.Header) bool {
fmt.Println(hdr.Name)
return true
}
// VerboseLogFilter logs the name of every file.
func VerboseLogFilter(hdr *tar.Header) bool {
log.Println(hdr.Name)
return true
}
// SafeFilter filters out all files which are not regular and not directories.
// It also sets sane permissions.
func SafeFilter(hdr *tar.Header) bool {
if hdr.Typeflag == tar.TypeDir {
hdr.Mode = 0770
return true
}
if hdr.Typeflag == tar.TypeReg {
hdr.Mode = 0660
return true
}
return false
}