1 package tar2ext4
2
3 import (
4 "archive/tar"
5 "bufio"
6 "encoding/binary"
7 "fmt"
8 "io"
9 "os"
10 "path"
11 "strings"
12
13 "github.com/Microsoft/hcsshim/ext4/dmverity"
14 "github.com/Microsoft/hcsshim/ext4/internal/compactext4"
15 "github.com/Microsoft/hcsshim/ext4/internal/format"
16 "github.com/pkg/errors"
17 )
18
19 type params struct {
20 convertWhiteout bool
21 appendVhdFooter bool
22 appendDMVerity bool
23 ext4opts []compactext4.Option
24 }
25
26
27 type Option func(*params)
28
29
30
31 func ConvertWhiteout(p *params) {
32 p.convertWhiteout = true
33 }
34
35
36
37 func AppendVhdFooter(p *params) {
38 p.appendVhdFooter = true
39 }
40
41
42
43 func AppendDMVerity(p *params) {
44 p.appendDMVerity = true
45 }
46
47
48
49
50 func InlineData(p *params) {
51 p.ext4opts = append(p.ext4opts, compactext4.InlineData)
52 }
53
54
55
56
57 func MaximumDiskSize(size int64) Option {
58 return func(p *params) {
59 p.ext4opts = append(p.ext4opts, compactext4.MaximumDiskSize(size))
60 }
61 }
62
63 const (
64 whiteoutPrefix = ".wh."
65 opaqueWhiteout = ".wh..wh..opq"
66 )
67
68
69
70 func ConvertTarToExt4(r io.Reader, w io.ReadWriteSeeker, options ...Option) error {
71 var p params
72 for _, opt := range options {
73 opt(&p)
74 }
75
76 t := tar.NewReader(bufio.NewReader(r))
77 fs := compactext4.NewWriter(w, p.ext4opts...)
78 for {
79 hdr, err := t.Next()
80 if err == io.EOF {
81 break
82 }
83 if err != nil {
84 return err
85 }
86
87 if err = fs.MakeParents(hdr.Name); err != nil {
88 return errors.Wrapf(err, "failed to ensure parent directories for %s", hdr.Name)
89 }
90
91 if p.convertWhiteout {
92 dir, name := path.Split(hdr.Name)
93 if strings.HasPrefix(name, whiteoutPrefix) {
94 if name == opaqueWhiteout {
95
96 f, err := fs.Stat(dir)
97 if err != nil {
98 return errors.Wrapf(err, "failed to stat parent directory of whiteout %s", hdr.Name)
99 }
100 f.Xattrs["trusted.overlay.opaque"] = []byte("y")
101 err = fs.Create(dir, f)
102 if err != nil {
103 return errors.Wrapf(err, "failed to create opaque dir %s", hdr.Name)
104 }
105 } else {
106
107 f := &compactext4.File{
108 Mode: compactext4.S_IFCHR,
109 Devmajor: 0,
110 Devminor: 0,
111 }
112 err = fs.Create(path.Join(dir, name[len(whiteoutPrefix):]), f)
113 if err != nil {
114 return errors.Wrapf(err, "failed to create whiteout file for %s", hdr.Name)
115 }
116 }
117
118 continue
119 }
120 }
121
122 if hdr.Typeflag == tar.TypeLink {
123 err = fs.Link(hdr.Linkname, hdr.Name)
124 if err != nil {
125 return err
126 }
127 } else {
128 f := &compactext4.File{
129 Mode: uint16(hdr.Mode),
130 Atime: hdr.AccessTime,
131 Mtime: hdr.ModTime,
132 Ctime: hdr.ChangeTime,
133 Crtime: hdr.ModTime,
134 Size: hdr.Size,
135 Uid: uint32(hdr.Uid),
136 Gid: uint32(hdr.Gid),
137 Linkname: hdr.Linkname,
138 Devmajor: uint32(hdr.Devmajor),
139 Devminor: uint32(hdr.Devminor),
140 Xattrs: make(map[string][]byte),
141 }
142 for key, value := range hdr.PAXRecords {
143 const xattrPrefix = "SCHILY.xattr."
144 if strings.HasPrefix(key, xattrPrefix) {
145 f.Xattrs[key[len(xattrPrefix):]] = []byte(value)
146 }
147 }
148
149 var typ uint16
150 switch hdr.Typeflag {
151 case tar.TypeReg, tar.TypeRegA:
152 typ = compactext4.S_IFREG
153 case tar.TypeSymlink:
154 typ = compactext4.S_IFLNK
155 case tar.TypeChar:
156 typ = compactext4.S_IFCHR
157 case tar.TypeBlock:
158 typ = compactext4.S_IFBLK
159 case tar.TypeDir:
160 typ = compactext4.S_IFDIR
161 case tar.TypeFifo:
162 typ = compactext4.S_IFIFO
163 }
164 f.Mode &= ^compactext4.TypeMask
165 f.Mode |= typ
166 err = fs.Create(hdr.Name, f)
167 if err != nil {
168 return err
169 }
170 _, err = io.Copy(fs, t)
171 if err != nil {
172 return err
173 }
174 }
175 }
176 return fs.Close()
177 }
178
179
180
181 func Convert(r io.Reader, w io.ReadWriteSeeker, options ...Option) error {
182 var p params
183 for _, opt := range options {
184 opt(&p)
185 }
186
187 if err := ConvertTarToExt4(r, w, options...); err != nil {
188 return err
189 }
190
191 if p.appendDMVerity {
192 if err := dmverity.ComputeAndWriteHashDevice(w, w); err != nil {
193 return err
194 }
195 }
196
197 if p.appendVhdFooter {
198 return ConvertToVhd(w)
199 }
200 return nil
201 }
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218 func ReadExt4SuperBlock(vhdPath string) (*format.SuperBlock, error) {
219 vhd, err := os.OpenFile(vhdPath, os.O_RDONLY, 0)
220 if err != nil {
221 return nil, err
222 }
223 defer vhd.Close()
224
225
226 if _, err := vhd.Seek(1024, io.SeekStart); err != nil {
227 return nil, err
228 }
229 var sb format.SuperBlock
230 if err := binary.Read(vhd, binary.LittleEndian, &sb); err != nil {
231 return nil, err
232 }
233
234 if sb.Magic != format.SuperBlockMagic {
235 return nil, errors.New("not an ext4 file system")
236 }
237 return &sb, nil
238 }
239
240
241
242
243
244 func ConvertAndComputeRootDigest(r io.Reader) (string, error) {
245 out, err := os.CreateTemp("", "")
246 if err != nil {
247 return "", fmt.Errorf("failed to create temporary file: %s", err)
248 }
249 defer func() {
250 _ = os.Remove(out.Name())
251 }()
252 defer out.Close()
253
254 options := []Option{
255 ConvertWhiteout,
256 MaximumDiskSize(dmverity.RecommendedVHDSizeGB),
257 }
258 if err := ConvertTarToExt4(r, out, options...); err != nil {
259 return "", fmt.Errorf("failed to convert tar to ext4: %s", err)
260 }
261
262 if _, err := out.Seek(0, io.SeekStart); err != nil {
263 return "", fmt.Errorf("failed to seek start on temp file when creating merkle tree: %s", err)
264 }
265
266 tree, err := dmverity.MerkleTree(bufio.NewReaderSize(out, dmverity.MerkleTreeBufioSize))
267 if err != nil {
268 return "", fmt.Errorf("failed to create merkle tree: %s", err)
269 }
270
271 hash := dmverity.RootHash(tree)
272 return fmt.Sprintf("%x", hash), nil
273 }
274
275
276 func ConvertToVhd(w io.WriteSeeker) error {
277 size, err := w.Seek(0, io.SeekEnd)
278 if err != nil {
279 return err
280 }
281 return binary.Write(w, binary.BigEndian, makeFixedVHDFooter(size))
282 }
283
View as plain text