Skip to content

Commit 84b3a66

Browse files
committed
Add untar package
Source: golang.org/x/build/internal/untar
1 parent 4311e98 commit 84b3a66

File tree

1 file changed

+123
-0
lines changed

1 file changed

+123
-0
lines changed

pkg/untar/untar.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Copyright 2020 The FluxCD contributors. All rights reserved.
6+
// Adapted from: golang.org/x/build/internal/untar
7+
8+
// Package untar untars a tarball to disk.
9+
package untar
10+
11+
import (
12+
"archive/tar"
13+
"compress/gzip"
14+
"fmt"
15+
"io"
16+
"log"
17+
"os"
18+
"path/filepath"
19+
"strings"
20+
"time"
21+
)
22+
23+
// Untar reads the gzip-compressed tar file from r and writes it into dir.
24+
func Untar(r io.Reader, dir string) (summary string, err error) {
25+
t0 := time.Now()
26+
nFiles := 0
27+
madeDir := map[string]bool{}
28+
defer func() {
29+
td := time.Since(t0)
30+
if err == nil {
31+
summary = fmt.Sprintf("Extracted tarball into %s: %d files, %d dirs (%v)", dir, nFiles, len(madeDir), td)
32+
} else {
33+
summary = fmt.Sprintf("Error extracting tarball into %s after %d files, %d dirs, %v: %v", dir, nFiles, len(madeDir), td, err)
34+
}
35+
}()
36+
zr, err := gzip.NewReader(r)
37+
if err != nil {
38+
return summary, fmt.Errorf("requires gzip-compressed body: %v", err)
39+
}
40+
tr := tar.NewReader(zr)
41+
loggedChtimesError := false
42+
for {
43+
f, err := tr.Next()
44+
if err == io.EOF {
45+
break
46+
}
47+
if err != nil {
48+
log.Printf("tar reading error: %v", err)
49+
return summary, fmt.Errorf("tar error: %v", err)
50+
}
51+
if !validRelPath(f.Name) {
52+
return summary, fmt.Errorf("tar contained invalid name error %q", f.Name)
53+
}
54+
rel := filepath.FromSlash(f.Name)
55+
abs := filepath.Join(dir, rel)
56+
57+
fi := f.FileInfo()
58+
mode := fi.Mode()
59+
switch {
60+
case mode.IsRegular():
61+
// Make the directory. This is redundant because it should
62+
// already be made by a directory entry in the tar
63+
// beforehand. Thus, don't check for errors; the next
64+
// write will fail with the same error.
65+
dir := filepath.Dir(abs)
66+
if !madeDir[dir] {
67+
if err := os.MkdirAll(filepath.Dir(abs), 0755); err != nil {
68+
return summary, err
69+
}
70+
madeDir[dir] = true
71+
}
72+
wf, err := os.OpenFile(abs, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode.Perm())
73+
if err != nil {
74+
return summary, err
75+
}
76+
n, err := io.Copy(wf, tr)
77+
if closeErr := wf.Close(); closeErr != nil && err == nil {
78+
err = closeErr
79+
}
80+
if err != nil {
81+
return summary, fmt.Errorf("error writing to %s: %v", abs, err)
82+
}
83+
if n != f.Size {
84+
return summary, fmt.Errorf("only wrote %d bytes to %s; expected %d", n, abs, f.Size)
85+
}
86+
modTime := f.ModTime
87+
if modTime.After(t0) {
88+
// Clamp modtimes at system time. See
89+
// golang.org/issue/19062 when clock on
90+
// buildlet was behind the gitmirror server
91+
// doing the git-archive.
92+
modTime = t0
93+
}
94+
if !modTime.IsZero() {
95+
if err := os.Chtimes(abs, modTime, modTime); err != nil && !loggedChtimesError {
96+
// benign error. Gerrit doesn't even set the
97+
// modtime in these, and we don't end up relying
98+
// on it anywhere (the gomote push command relies
99+
// on digests only), so this is a little pointless
100+
// for now.
101+
log.Printf("error changing modtime: %v (further Chtimes errors suppressed)", err)
102+
loggedChtimesError = true // once is enough
103+
}
104+
}
105+
nFiles++
106+
case mode.IsDir():
107+
if err := os.MkdirAll(abs, 0755); err != nil {
108+
return summary, err
109+
}
110+
madeDir[abs] = true
111+
default:
112+
return summary, fmt.Errorf("tar file entry %s contained unsupported file type %v", f.Name, mode)
113+
}
114+
}
115+
return summary, nil
116+
}
117+
118+
func validRelPath(p string) bool {
119+
if p == "" || strings.Contains(p, `\`) || strings.HasPrefix(p, "/") || strings.Contains(p, "../") {
120+
return false
121+
}
122+
return true
123+
}

0 commit comments

Comments
 (0)