This repository has been archived by the owner on Oct 26, 2023. It is now read-only.
/
sourcetar.go
158 lines (142 loc) · 3.92 KB
/
sourcetar.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
package source
import (
"archive/tar"
"io"
"os"
"path/filepath"
"strings"
"github.com/mgoltzsche/ctnr/pkg/fs"
"github.com/mgoltzsche/ctnr/pkg/idutils"
"github.com/openSUSE/umoci/oci/layer"
"github.com/openSUSE/umoci/pkg/system"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)
var (
dirAttrs = fs.FileAttrs{Mode: os.ModeDir | 0755}
_ fs.Source = &sourceTar{}
)
type sourceTar struct {
file string
hash string
}
func NewSourceTar(file string) *sourceTar {
return &sourceTar{file, ""}
}
func (s *sourceTar) Attrs() fs.NodeInfo {
return fs.NodeInfo{fs.TypeOverlay, fs.FileAttrs{Mode: os.ModeDir | 0755}}
}
func (s *sourceTar) HashIfAvailable() string {
return s.hash
}
func (s *sourceTar) Hash() (string, error) {
if s.hash == "" {
f, err := os.Open(s.file)
if err != nil {
return "", errors.Errorf("hash: %s", err)
}
defer f.Close()
d, err := digest.FromReader(f)
if err != nil {
return "", errors.Errorf("hash %s: %s", s.file, err)
}
s.hash = d.String()
}
return s.hash, nil
}
func (s *sourceTar) DeriveAttrs() (a fs.DerivedAttrs, err error) {
a.Hash, err = s.Hash()
return
}
func (s *sourceTar) Write(dest, name string, w fs.Writer, written map[fs.Source]string) error {
return w.Lazy(dest, name, s, written)
}
func (s *sourceTar) Expand(dest string, w fs.Writer, written map[fs.Source]string) (err error) {
f, err := os.Open(s.file)
if err != nil {
return errors.Wrap(err, "extract tar")
}
defer f.Close()
if err = unpackTar(f, dest, w); err != nil {
return errors.Wrap(err, "extract tar")
}
return
}
func unpackTar(r io.Reader, dest string, w fs.Writer) error {
tr := tar.NewReader(r)
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return errors.Wrap(err, "read next tar entry")
}
links := map[string]string{}
if err = unpackTarEntry(hdr, tr, dest, w, links); err != nil {
return errors.Wrapf(err, "unpack tar entry: %s", hdr.Name)
}
for path, target := range links {
if err = w.Link(path, target); err != nil {
return errors.Wrapf(err, "unpack tar entry link: %s", hdr.Name)
}
}
}
return nil
}
// Derived from umoci's tar_extract.go to allow separate source/dest interfaces
// and filter archive contents on extraction
func unpackTarEntry(hdr *tar.Header, r io.Reader, dest string, w fs.Writer, links map[string]string) (err error) {
path := layer.CleanPath(filepath.Join(dest, hdr.Name))
dir, file := filepath.Split(path)
// Remove file if whiteout
if strings.HasPrefix(file, fs.WhiteoutPrefix) {
file = strings.TrimPrefix(file, fs.WhiteoutPrefix)
return w.Remove(filepath.Join(dir, file))
}
// Convert attributes
fi := hdr.FileInfo()
a := fs.FileAttrs{
Mode: fi.Mode() | os.FileMode(system.Tarmode(hdr.Typeflag)),
UserIds: idutils.UserIds{uint(hdr.Uid), uint(hdr.Gid)},
FileTimes: fs.FileTimes{
Atime: hdr.AccessTime,
Mtime: hdr.ModTime,
},
Xattrs: hdr.Xattrs,
}
// Write file
switch hdr.Typeflag {
// regular file
case tar.TypeReg, tar.TypeRegA:
delete(links, path)
a.Size = hdr.Size
_, err = w.File(path, NewSourceFile(fs.NewReadable(r), a))
// directory
case tar.TypeDir:
delete(links, path)
err = w.Dir(path, filepath.Base(path), a)
// hard link
case tar.TypeLink:
links[path] = filepath.Join(string(filepath.Separator)+dest, hdr.Linkname)
// symbolic link
case tar.TypeSymlink:
a.Symlink = hdr.Linkname
if filepath.IsAbs(a.Symlink) {
a.Symlink = filepath.Join(string(filepath.Separator)+dest, a.Symlink)
}
delete(links, path)
err = w.Symlink(path, a)
// character device node, block device node
case tar.TypeChar, tar.TypeBlock:
delete(links, path)
err = w.Device(path, fs.DeviceAttrs{a, hdr.Devmajor, hdr.Devminor})
// fifo node
case tar.TypeFifo:
delete(links, path)
err = w.Fifo(path, fs.DeviceAttrs{a, hdr.Devmajor, hdr.Devminor})
default:
err = errors.Errorf("unpack entry: %s: unknown typeflag '\\x%x'", hdr.Name, hdr.Typeflag)
}
return
}