Browse code

Vendor Microsoft/hcsshim @ v0.7.9

Signed-off-by: John Howard <jhoward@microsoft.com>

John Howard authored on 2018/10/03 07:25:41
Showing 6 changed files
... ...
@@ -1,6 +1,6 @@
1 1
 # the following lines are in sorted order, FYI
2 2
 github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109
3
-github.com/Microsoft/hcsshim v0.7.6
3
+github.com/Microsoft/hcsshim v0.7.9
4 4
 github.com/Microsoft/go-winio v0.4.11
5 5
 github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a
6 6
 github.com/go-check/check 4ed411733c5785b40214c70bce814c3a3a689609 https://github.com/cpuguy83/check.git
7 7
new file mode 100644
... ...
@@ -0,0 +1,1263 @@
0
+package compactext4
1
+
2
+import (
3
+	"bufio"
4
+	"bytes"
5
+	"encoding/binary"
6
+	"errors"
7
+	"fmt"
8
+	"io"
9
+	"path"
10
+	"sort"
11
+	"strings"
12
+	"time"
13
+
14
+	"github.com/Microsoft/hcsshim/ext4/internal/format"
15
+)
16
+
17
+// Writer writes a compact ext4 file system.
18
+type Writer struct {
19
+	f                    io.ReadWriteSeeker
20
+	bw                   *bufio.Writer
21
+	inodes               []*inode
22
+	curName              string
23
+	curInode             *inode
24
+	pos                  int64
25
+	dataWritten, dataMax int64
26
+	err                  error
27
+	initialized          bool
28
+	supportInlineData    bool
29
+	maxDiskSize          int64
30
+	gdBlocks             uint32
31
+}
32
+
33
+// Mode flags for Linux files.
34
+const (
35
+	S_IXOTH  = format.S_IXOTH
36
+	S_IWOTH  = format.S_IWOTH
37
+	S_IROTH  = format.S_IROTH
38
+	S_IXGRP  = format.S_IXGRP
39
+	S_IWGRP  = format.S_IWGRP
40
+	S_IRGRP  = format.S_IRGRP
41
+	S_IXUSR  = format.S_IXUSR
42
+	S_IWUSR  = format.S_IWUSR
43
+	S_IRUSR  = format.S_IRUSR
44
+	S_ISVTX  = format.S_ISVTX
45
+	S_ISGID  = format.S_ISGID
46
+	S_ISUID  = format.S_ISUID
47
+	S_IFIFO  = format.S_IFIFO
48
+	S_IFCHR  = format.S_IFCHR
49
+	S_IFDIR  = format.S_IFDIR
50
+	S_IFBLK  = format.S_IFBLK
51
+	S_IFREG  = format.S_IFREG
52
+	S_IFLNK  = format.S_IFLNK
53
+	S_IFSOCK = format.S_IFSOCK
54
+
55
+	TypeMask = format.TypeMask
56
+)
57
+
58
+type inode struct {
59
+	Size                        int64
60
+	Atime, Ctime, Mtime, Crtime uint64
61
+	Number                      format.InodeNumber
62
+	Mode                        uint16
63
+	Uid, Gid                    uint32
64
+	LinkCount                   uint32
65
+	XattrBlock                  uint32
66
+	BlockCount                  uint32
67
+	Devmajor, Devminor          uint32
68
+	Flags                       format.InodeFlag
69
+	Data                        []byte
70
+	XattrInline                 []byte
71
+	Children                    directory
72
+}
73
+
74
+func (node *inode) FileType() uint16 {
75
+	return node.Mode & format.TypeMask
76
+}
77
+
78
+func (node *inode) IsDir() bool {
79
+	return node.FileType() == S_IFDIR
80
+}
81
+
82
+// A File represents a file to be added to an ext4 file system.
83
+type File struct {
84
+	Linkname                    string
85
+	Size                        int64
86
+	Mode                        uint16
87
+	Uid, Gid                    uint32
88
+	Atime, Ctime, Mtime, Crtime time.Time
89
+	Devmajor, Devminor          uint32
90
+	Xattrs                      map[string][]byte
91
+}
92
+
93
+const (
94
+	inodeFirst        = 11
95
+	inodeLostAndFound = inodeFirst
96
+
97
+	blockSize               = 4096
98
+	blocksPerGroup          = blockSize * 8
99
+	inodeSize               = 256
100
+	maxInodesPerGroup       = blockSize * 8 // Limited by the inode bitmap
101
+	inodesPerGroupIncrement = blockSize / inodeSize
102
+
103
+	defaultMaxDiskSize = 16 * 1024 * 1024 * 1024        // 16GB
104
+	maxMaxDiskSize     = 16 * 1024 * 1024 * 1024 * 1024 // 16TB
105
+
106
+	groupDescriptorSize      = 32 // Use the small group descriptor
107
+	groupsPerDescriptorBlock = blockSize / groupDescriptorSize
108
+
109
+	maxFileSize             = 128 * 1024 * 1024 * 1024 // 128GB file size maximum for now
110
+	smallSymlinkSize        = 59                       // max symlink size that goes directly in the inode
111
+	maxBlocksPerExtent      = 0x8000                   // maximum number of blocks in an extent
112
+	inodeDataSize           = 60
113
+	inodeUsedSize           = 152 // fields through CrtimeExtra
114
+	inodeExtraSize          = inodeSize - inodeUsedSize
115
+	xattrInodeOverhead      = 4 + 4                       // magic number + empty next entry value
116
+	xattrBlockOverhead      = 32 + 4                      // header + empty next entry value
117
+	inlineDataXattrOverhead = xattrInodeOverhead + 16 + 4 // entry + "data"
118
+	inlineDataSize          = inodeDataSize + inodeExtraSize - inlineDataXattrOverhead
119
+)
120
+
121
+type exceededMaxSizeError struct {
122
+	Size int64
123
+}
124
+
125
+func (err exceededMaxSizeError) Error() string {
126
+	return fmt.Sprintf("disk exceeded maximum size of %d bytes", err.Size)
127
+}
128
+
129
+var directoryEntrySize = binary.Size(format.DirectoryEntry{})
130
+var extraIsize = uint16(inodeUsedSize - 128)
131
+
132
+type directory map[string]*inode
133
+
134
+func splitFirst(p string) (string, string) {
135
+	n := strings.IndexByte(p, '/')
136
+	if n >= 0 {
137
+		return p[:n], p[n+1:]
138
+	}
139
+	return p, ""
140
+}
141
+
142
+func (w *Writer) findPath(root *inode, p string) *inode {
143
+	inode := root
144
+	for inode != nil && len(p) != 0 {
145
+		name, rest := splitFirst(p)
146
+		p = rest
147
+		inode = inode.Children[name]
148
+	}
149
+	return inode
150
+}
151
+
152
+func timeToFsTime(t time.Time) uint64 {
153
+	if t.IsZero() {
154
+		return 0
155
+	}
156
+	s := t.Unix()
157
+	if s < -0x80000000 {
158
+		return 0x80000000
159
+	}
160
+	if s > 0x37fffffff {
161
+		return 0x37fffffff
162
+	}
163
+	return uint64(s) | uint64(t.Nanosecond())<<34
164
+}
165
+
166
+func fsTimeToTime(t uint64) time.Time {
167
+	if t == 0 {
168
+		return time.Time{}
169
+	}
170
+	s := int64(t & 0x3ffffffff)
171
+	if s > 0x7fffffff && s < 0x100000000 {
172
+		s = int64(int32(uint32(s)))
173
+	}
174
+	return time.Unix(s, int64(t>>34))
175
+}
176
+
177
+func (w *Writer) getInode(i format.InodeNumber) *inode {
178
+	if i == 0 || int(i) > len(w.inodes) {
179
+		return nil
180
+	}
181
+	return w.inodes[i-1]
182
+}
183
+
184
+var xattrPrefixes = []struct {
185
+	Index  uint8
186
+	Prefix string
187
+}{
188
+	{2, "system.posix_acl_access"},
189
+	{3, "system.posix_acl_default"},
190
+	{8, "system.richacl"},
191
+	{7, "system."},
192
+	{1, "user."},
193
+	{4, "trusted."},
194
+	{6, "security."},
195
+}
196
+
197
+func compressXattrName(name string) (uint8, string) {
198
+	for _, p := range xattrPrefixes {
199
+		if strings.HasPrefix(name, p.Prefix) {
200
+			return p.Index, name[len(p.Prefix):]
201
+		}
202
+	}
203
+	return 0, name
204
+}
205
+
206
+func decompressXattrName(index uint8, name string) string {
207
+	for _, p := range xattrPrefixes {
208
+		if index == p.Index {
209
+			return p.Prefix + name
210
+		}
211
+	}
212
+	return name
213
+}
214
+
215
+func hashXattrEntry(name string, value []byte) uint32 {
216
+	var hash uint32
217
+	for i := 0; i < len(name); i++ {
218
+		hash = (hash << 5) ^ (hash >> 27) ^ uint32(name[i])
219
+	}
220
+
221
+	for i := 0; i+3 < len(value); i += 4 {
222
+		hash = (hash << 16) ^ (hash >> 16) ^ binary.LittleEndian.Uint32(value[i:i+4])
223
+	}
224
+
225
+	if len(value)%4 != 0 {
226
+		var last [4]byte
227
+		copy(last[:], value[len(value)&^3:])
228
+		hash = (hash << 16) ^ (hash >> 16) ^ binary.LittleEndian.Uint32(last[:])
229
+	}
230
+	return hash
231
+}
232
+
233
+type xattr struct {
234
+	Name  string
235
+	Index uint8
236
+	Value []byte
237
+}
238
+
239
+func (x *xattr) EntryLen() int {
240
+	return (len(x.Name)+3)&^3 + 16
241
+}
242
+
243
+func (x *xattr) ValueLen() int {
244
+	return (len(x.Value) + 3) &^ 3
245
+}
246
+
247
+type xattrState struct {
248
+	inode, block         []xattr
249
+	inodeLeft, blockLeft int
250
+}
251
+
252
+func (s *xattrState) init() {
253
+	s.inodeLeft = inodeExtraSize - xattrInodeOverhead
254
+	s.blockLeft = blockSize - xattrBlockOverhead
255
+}
256
+
257
+func (s *xattrState) addXattr(name string, value []byte) bool {
258
+	index, name := compressXattrName(name)
259
+	x := xattr{
260
+		Index: index,
261
+		Name:  name,
262
+		Value: value,
263
+	}
264
+	length := x.EntryLen() + x.ValueLen()
265
+	if s.inodeLeft >= length {
266
+		s.inode = append(s.inode, x)
267
+		s.inodeLeft -= length
268
+	} else if s.blockLeft >= length {
269
+		s.block = append(s.block, x)
270
+		s.blockLeft -= length
271
+	} else {
272
+		return false
273
+	}
274
+	return true
275
+}
276
+
277
+func putXattrs(xattrs []xattr, b []byte, offsetDelta uint16) {
278
+	offset := uint16(len(b)) + offsetDelta
279
+	eb := b
280
+	db := b
281
+	for _, xattr := range xattrs {
282
+		vl := xattr.ValueLen()
283
+		offset -= uint16(vl)
284
+		eb[0] = uint8(len(xattr.Name))
285
+		eb[1] = xattr.Index
286
+		binary.LittleEndian.PutUint16(eb[2:], offset)
287
+		binary.LittleEndian.PutUint32(eb[8:], uint32(len(xattr.Value)))
288
+		binary.LittleEndian.PutUint32(eb[12:], hashXattrEntry(xattr.Name, xattr.Value))
289
+		copy(eb[16:], xattr.Name)
290
+		eb = eb[xattr.EntryLen():]
291
+		copy(db[len(db)-vl:], xattr.Value)
292
+		db = db[:len(db)-vl]
293
+	}
294
+}
295
+
296
+func getXattrs(b []byte, xattrs map[string][]byte, offsetDelta uint16) {
297
+	eb := b
298
+	for len(eb) != 0 {
299
+		nameLen := eb[0]
300
+		if nameLen == 0 {
301
+			break
302
+		}
303
+		index := eb[1]
304
+		offset := binary.LittleEndian.Uint16(eb[2:]) - offsetDelta
305
+		valueLen := binary.LittleEndian.Uint32(eb[8:])
306
+		attr := xattr{
307
+			Index: index,
308
+			Name:  string(eb[16 : 16+nameLen]),
309
+			Value: b[offset : uint32(offset)+valueLen],
310
+		}
311
+		xattrs[decompressXattrName(index, attr.Name)] = attr.Value
312
+		eb = eb[attr.EntryLen():]
313
+	}
314
+}
315
+
316
+func (w *Writer) writeXattrs(inode *inode, state *xattrState) error {
317
+	// Write the inline attributes.
318
+	if len(state.inode) != 0 {
319
+		inode.XattrInline = make([]byte, inodeExtraSize)
320
+		binary.LittleEndian.PutUint32(inode.XattrInline[0:], format.XAttrHeaderMagic) // Magic
321
+		putXattrs(state.inode, inode.XattrInline[4:], 0)
322
+	}
323
+
324
+	// Write the block attributes. If there was previously an xattr block, then
325
+	// rewrite it even if it is now empty.
326
+	if len(state.block) != 0 || inode.XattrBlock != 0 {
327
+		sort.Slice(state.block, func(i, j int) bool {
328
+			return state.block[i].Index < state.block[j].Index ||
329
+				len(state.block[i].Name) < len(state.block[j].Name) ||
330
+				state.block[i].Name < state.block[j].Name
331
+		})
332
+
333
+		var b [blockSize]byte
334
+		binary.LittleEndian.PutUint32(b[0:], format.XAttrHeaderMagic) // Magic
335
+		binary.LittleEndian.PutUint32(b[4:], 1)                       // ReferenceCount
336
+		binary.LittleEndian.PutUint32(b[8:], 1)                       // Blocks
337
+		putXattrs(state.block, b[32:], 32)
338
+
339
+		orig := w.block()
340
+		if inode.XattrBlock == 0 {
341
+			inode.XattrBlock = orig
342
+			inode.BlockCount++
343
+		} else {
344
+			// Reuse the original block.
345
+			w.seekBlock(inode.XattrBlock)
346
+			defer w.seekBlock(orig)
347
+		}
348
+
349
+		if _, err := w.write(b[:]); err != nil {
350
+			return err
351
+		}
352
+	}
353
+
354
+	return nil
355
+}
356
+
357
+func (w *Writer) write(b []byte) (int, error) {
358
+	if w.err != nil {
359
+		return 0, w.err
360
+	}
361
+	if w.pos+int64(len(b)) > w.maxDiskSize {
362
+		w.err = exceededMaxSizeError{w.maxDiskSize}
363
+		return 0, w.err
364
+	}
365
+	n, err := w.bw.Write(b)
366
+	w.pos += int64(n)
367
+	w.err = err
368
+	return n, err
369
+}
370
+
371
+func (w *Writer) zero(n int64) (int64, error) {
372
+	if w.err != nil {
373
+		return 0, w.err
374
+	}
375
+	if w.pos+int64(n) > w.maxDiskSize {
376
+		w.err = exceededMaxSizeError{w.maxDiskSize}
377
+		return 0, w.err
378
+	}
379
+	n, err := io.CopyN(w.bw, zero, n)
380
+	w.pos += n
381
+	w.err = err
382
+	return n, err
383
+}
384
+
385
+func (w *Writer) makeInode(f *File, node *inode) (*inode, error) {
386
+	mode := f.Mode
387
+	if mode&format.TypeMask == 0 {
388
+		mode |= format.S_IFREG
389
+	}
390
+	typ := mode & format.TypeMask
391
+	ino := format.InodeNumber(len(w.inodes) + 1)
392
+	if node == nil {
393
+		node = &inode{
394
+			Number: ino,
395
+		}
396
+		if typ == S_IFDIR {
397
+			node.Children = make(directory)
398
+			node.LinkCount = 1 // A directory is linked to itself.
399
+		}
400
+	} else if node.Flags&format.InodeFlagExtents != 0 {
401
+		// Since we cannot deallocate or reuse blocks, don't allow updates that
402
+		// would invalidate data that has already been written.
403
+		return nil, errors.New("cannot overwrite file with non-inline data")
404
+	}
405
+	node.Mode = mode
406
+	node.Uid = f.Uid
407
+	node.Gid = f.Gid
408
+	node.Flags = format.InodeFlagHugeFile
409
+	node.Atime = timeToFsTime(f.Atime)
410
+	node.Ctime = timeToFsTime(f.Ctime)
411
+	node.Mtime = timeToFsTime(f.Mtime)
412
+	node.Crtime = timeToFsTime(f.Crtime)
413
+	node.Devmajor = f.Devmajor
414
+	node.Devminor = f.Devminor
415
+	node.Data = nil
416
+	node.XattrInline = nil
417
+
418
+	var xstate xattrState
419
+	xstate.init()
420
+
421
+	var size int64
422
+	switch typ {
423
+	case format.S_IFREG:
424
+		size = f.Size
425
+		if f.Size > maxFileSize {
426
+			return nil, fmt.Errorf("file too big: %d > %d", f.Size, maxFileSize)
427
+		}
428
+		if f.Size <= inlineDataSize && w.supportInlineData {
429
+			node.Data = make([]byte, f.Size)
430
+			extra := 0
431
+			if f.Size > inodeDataSize {
432
+				extra = int(f.Size - inodeDataSize)
433
+			}
434
+			// Add a dummy entry for now.
435
+			if !xstate.addXattr("system.data", node.Data[:extra]) {
436
+				panic("not enough room for inline data")
437
+			}
438
+			node.Flags |= format.InodeFlagInlineData
439
+		}
440
+	case format.S_IFLNK:
441
+		node.Mode |= 0777 // Symlinks should appear as ugw rwx
442
+		size = int64(len(f.Linkname))
443
+		if size <= smallSymlinkSize {
444
+			// Special case: small symlinks go directly in Block without setting
445
+			// an inline data flag.
446
+			node.Data = make([]byte, len(f.Linkname))
447
+			copy(node.Data, f.Linkname)
448
+		}
449
+	case format.S_IFDIR, format.S_IFIFO, format.S_IFSOCK, format.S_IFCHR, format.S_IFBLK:
450
+	default:
451
+		return nil, fmt.Errorf("invalid mode %o", mode)
452
+	}
453
+
454
+	// Accumulate the extended attributes.
455
+	if len(f.Xattrs) != 0 {
456
+		// Sort the xattrs to avoid non-determinism in map iteration.
457
+		var xattrs []string
458
+		for name := range f.Xattrs {
459
+			xattrs = append(xattrs, name)
460
+		}
461
+		sort.Strings(xattrs)
462
+		for _, name := range xattrs {
463
+			if !xstate.addXattr(name, f.Xattrs[name]) {
464
+				return nil, fmt.Errorf("could not fit xattr %s", name)
465
+			}
466
+		}
467
+	}
468
+
469
+	if err := w.writeXattrs(node, &xstate); err != nil {
470
+		return nil, err
471
+	}
472
+
473
+	node.Size = size
474
+	if typ == format.S_IFLNK && size > smallSymlinkSize {
475
+		// Write the link name as data.
476
+		w.startInode("", node, size)
477
+		if _, err := w.Write([]byte(f.Linkname)); err != nil {
478
+			return nil, err
479
+		}
480
+		if err := w.finishInode(); err != nil {
481
+			return nil, err
482
+		}
483
+	}
484
+
485
+	if int(node.Number-1) >= len(w.inodes) {
486
+		w.inodes = append(w.inodes, node)
487
+	}
488
+	return node, nil
489
+}
490
+
491
+func (w *Writer) root() *inode {
492
+	return w.getInode(format.InodeRoot)
493
+}
494
+
495
+func (w *Writer) lookup(name string, mustExist bool) (*inode, *inode, string, error) {
496
+	root := w.root()
497
+	cleanname := path.Clean("/" + name)[1:]
498
+	if len(cleanname) == 0 {
499
+		return root, root, "", nil
500
+	}
501
+	dirname, childname := path.Split(cleanname)
502
+	if len(childname) == 0 || len(childname) > 0xff {
503
+		return nil, nil, "", fmt.Errorf("%s: invalid name", name)
504
+	}
505
+	dir := w.findPath(root, dirname)
506
+	if dir == nil || !dir.IsDir() {
507
+		return nil, nil, "", fmt.Errorf("%s: path not found", name)
508
+	}
509
+	child := dir.Children[childname]
510
+	if child == nil && mustExist {
511
+		return nil, nil, "", fmt.Errorf("%s: file not found", name)
512
+	}
513
+	return dir, child, childname, nil
514
+}
515
+
516
+// Create adds a file to the file system.
517
+func (w *Writer) Create(name string, f *File) error {
518
+	if err := w.finishInode(); err != nil {
519
+		return err
520
+	}
521
+	dir, existing, childname, err := w.lookup(name, false)
522
+	if err != nil {
523
+		return err
524
+	}
525
+	var reuse *inode
526
+	if existing != nil {
527
+		if existing.IsDir() {
528
+			if f.Mode&TypeMask != S_IFDIR {
529
+				return fmt.Errorf("%s: cannot replace a directory with a file", name)
530
+			}
531
+			reuse = existing
532
+		} else if f.Mode&TypeMask == S_IFDIR {
533
+			return fmt.Errorf("%s: cannot replace a file with a directory", name)
534
+		} else if existing.LinkCount < 2 {
535
+			reuse = existing
536
+		}
537
+	} else {
538
+		if f.Mode&TypeMask == S_IFDIR && dir.LinkCount >= format.MaxLinks {
539
+			return fmt.Errorf("%s: exceeded parent directory maximum link count", name)
540
+		}
541
+	}
542
+	child, err := w.makeInode(f, reuse)
543
+	if err != nil {
544
+		return fmt.Errorf("%s: %s", name, err)
545
+	}
546
+	if existing != child {
547
+		if existing != nil {
548
+			existing.LinkCount--
549
+		}
550
+		dir.Children[childname] = child
551
+		child.LinkCount++
552
+		if child.IsDir() {
553
+			dir.LinkCount++
554
+		}
555
+	}
556
+	if child.Mode&format.TypeMask == format.S_IFREG {
557
+		w.startInode(name, child, f.Size)
558
+	}
559
+	return nil
560
+}
561
+
562
+// Link adds a hard link to the file system.
563
+func (w *Writer) Link(oldname, newname string) error {
564
+	if err := w.finishInode(); err != nil {
565
+		return err
566
+	}
567
+	newdir, existing, newchildname, err := w.lookup(newname, false)
568
+	if err != nil {
569
+		return err
570
+	}
571
+	if existing != nil && (existing.IsDir() || existing.LinkCount < 2) {
572
+		return fmt.Errorf("%s: cannot orphan existing file or directory", newname)
573
+	}
574
+
575
+	_, oldfile, _, err := w.lookup(oldname, true)
576
+	if err != nil {
577
+		return err
578
+	}
579
+	switch oldfile.Mode & format.TypeMask {
580
+	case format.S_IFDIR, format.S_IFLNK:
581
+		return fmt.Errorf("%s: link target cannot be a directory or symlink: %s", newname, oldname)
582
+	}
583
+
584
+	if existing != oldfile && oldfile.LinkCount >= format.MaxLinks {
585
+		return fmt.Errorf("%s: link target would exceed maximum link count: %s", newname, oldname)
586
+	}
587
+
588
+	if existing != nil {
589
+		existing.LinkCount--
590
+	}
591
+	oldfile.LinkCount++
592
+	newdir.Children[newchildname] = oldfile
593
+	return nil
594
+}
595
+
596
+// Stat returns information about a file that has been written.
597
+func (w *Writer) Stat(name string) (*File, error) {
598
+	if err := w.finishInode(); err != nil {
599
+		return nil, err
600
+	}
601
+	_, node, _, err := w.lookup(name, true)
602
+	if err != nil {
603
+		return nil, err
604
+	}
605
+	f := &File{
606
+		Size:     node.Size,
607
+		Mode:     node.Mode,
608
+		Uid:      node.Uid,
609
+		Gid:      node.Gid,
610
+		Atime:    fsTimeToTime(node.Atime),
611
+		Ctime:    fsTimeToTime(node.Ctime),
612
+		Mtime:    fsTimeToTime(node.Mtime),
613
+		Crtime:   fsTimeToTime(node.Crtime),
614
+		Devmajor: node.Devmajor,
615
+		Devminor: node.Devminor,
616
+	}
617
+	f.Xattrs = make(map[string][]byte)
618
+	if node.XattrBlock != 0 || len(node.XattrInline) != 0 {
619
+		if node.XattrBlock != 0 {
620
+			orig := w.block()
621
+			w.seekBlock(node.XattrBlock)
622
+			if w.err != nil {
623
+				return nil, w.err
624
+			}
625
+			var b [blockSize]byte
626
+			_, err := w.f.Read(b[:])
627
+			w.seekBlock(orig)
628
+			if err != nil {
629
+				return nil, err
630
+			}
631
+			getXattrs(b[32:], f.Xattrs, 32)
632
+		}
633
+		if len(node.XattrInline) != 0 {
634
+			getXattrs(node.XattrInline[4:], f.Xattrs, 0)
635
+			delete(f.Xattrs, "system.data")
636
+		}
637
+	}
638
+	if node.FileType() == S_IFLNK {
639
+		if node.Size > smallSymlinkSize {
640
+			return nil, fmt.Errorf("%s: cannot retrieve link information", name)
641
+		}
642
+		f.Linkname = string(node.Data)
643
+	}
644
+	return f, nil
645
+}
646
+
647
+func (w *Writer) Write(b []byte) (int, error) {
648
+	if len(b) == 0 {
649
+		return 0, nil
650
+	}
651
+	if w.dataWritten+int64(len(b)) > w.dataMax {
652
+		return 0, fmt.Errorf("%s: wrote too much: %d > %d", w.curName, w.dataWritten+int64(len(b)), w.dataMax)
653
+	}
654
+
655
+	if w.curInode.Flags&format.InodeFlagInlineData != 0 {
656
+		copy(w.curInode.Data[w.dataWritten:], b)
657
+		w.dataWritten += int64(len(b))
658
+		return len(b), nil
659
+	}
660
+
661
+	n, err := w.write(b)
662
+	w.dataWritten += int64(n)
663
+	return n, err
664
+}
665
+
666
+func (w *Writer) startInode(name string, inode *inode, size int64) {
667
+	if w.curInode != nil {
668
+		panic("inode already in progress")
669
+	}
670
+	w.curName = name
671
+	w.curInode = inode
672
+	w.dataWritten = 0
673
+	w.dataMax = size
674
+}
675
+
676
+func (w *Writer) block() uint32 {
677
+	return uint32(w.pos / blockSize)
678
+}
679
+
680
+func (w *Writer) seekBlock(block uint32) {
681
+	w.pos = int64(block) * blockSize
682
+	if w.err != nil {
683
+		return
684
+	}
685
+	w.err = w.bw.Flush()
686
+	if w.err != nil {
687
+		return
688
+	}
689
+	_, w.err = w.f.Seek(w.pos, io.SeekStart)
690
+}
691
+
692
+func (w *Writer) nextBlock() {
693
+	if w.pos%blockSize != 0 {
694
+		// Simplify callers; w.err is updated on failure.
695
+		w.zero(blockSize - w.pos%blockSize)
696
+	}
697
+}
698
+
699
+func fillExtents(hdr *format.ExtentHeader, extents []format.ExtentLeafNode, startBlock, offset, inodeSize uint32) {
700
+	*hdr = format.ExtentHeader{
701
+		Magic:   format.ExtentHeaderMagic,
702
+		Entries: uint16(len(extents)),
703
+		Max:     uint16(cap(extents)),
704
+		Depth:   0,
705
+	}
706
+	for i := range extents {
707
+		block := offset + uint32(i)*maxBlocksPerExtent
708
+		length := inodeSize - block
709
+		if length > maxBlocksPerExtent {
710
+			length = maxBlocksPerExtent
711
+		}
712
+		start := startBlock + block
713
+		extents[i] = format.ExtentLeafNode{
714
+			Block:    block,
715
+			Length:   uint16(length),
716
+			StartLow: start,
717
+		}
718
+	}
719
+}
720
+
721
+func (w *Writer) writeExtents(inode *inode) error {
722
+	start := w.pos - w.dataWritten
723
+	if start%blockSize != 0 {
724
+		panic("unaligned")
725
+	}
726
+	w.nextBlock()
727
+
728
+	startBlock := uint32(start / blockSize)
729
+	blocks := w.block() - startBlock
730
+	usedBlocks := blocks
731
+
732
+	const extentNodeSize = 12
733
+	const extentsPerBlock = blockSize/extentNodeSize - 1
734
+
735
+	extents := (blocks + maxBlocksPerExtent - 1) / maxBlocksPerExtent
736
+	var b bytes.Buffer
737
+	if extents == 0 {
738
+		// Nothing to do.
739
+	} else if extents <= 4 {
740
+		var root struct {
741
+			hdr     format.ExtentHeader
742
+			extents [4]format.ExtentLeafNode
743
+		}
744
+		fillExtents(&root.hdr, root.extents[:extents], startBlock, 0, blocks)
745
+		binary.Write(&b, binary.LittleEndian, root)
746
+	} else if extents <= 4*extentsPerBlock {
747
+		const extentsPerBlock = blockSize/extentNodeSize - 1
748
+		extentBlocks := extents/extentsPerBlock + 1
749
+		usedBlocks += extentBlocks
750
+		var b2 bytes.Buffer
751
+
752
+		var root struct {
753
+			hdr   format.ExtentHeader
754
+			nodes [4]format.ExtentIndexNode
755
+		}
756
+		root.hdr = format.ExtentHeader{
757
+			Magic:   format.ExtentHeaderMagic,
758
+			Entries: uint16(extentBlocks),
759
+			Max:     4,
760
+			Depth:   1,
761
+		}
762
+		for i := uint32(0); i < extentBlocks; i++ {
763
+			root.nodes[i] = format.ExtentIndexNode{
764
+				Block:   i * extentsPerBlock * maxBlocksPerExtent,
765
+				LeafLow: w.block(),
766
+			}
767
+			extentsInBlock := extents - i*extentBlocks
768
+			if extentsInBlock > extentsPerBlock {
769
+				extentsInBlock = extentsPerBlock
770
+			}
771
+
772
+			var node struct {
773
+				hdr     format.ExtentHeader
774
+				extents [extentsPerBlock]format.ExtentLeafNode
775
+				_       [blockSize - (extentsPerBlock+1)*extentNodeSize]byte
776
+			}
777
+
778
+			offset := i * extentsPerBlock * maxBlocksPerExtent
779
+			fillExtents(&node.hdr, node.extents[:extentsInBlock], startBlock+offset, offset, blocks)
780
+			binary.Write(&b2, binary.LittleEndian, node)
781
+			if _, err := w.write(b2.Next(blockSize)); err != nil {
782
+				return err
783
+			}
784
+		}
785
+		binary.Write(&b, binary.LittleEndian, root)
786
+	} else {
787
+		panic("file too big")
788
+	}
789
+
790
+	inode.Data = b.Bytes()
791
+	inode.Flags |= format.InodeFlagExtents
792
+	inode.BlockCount += usedBlocks
793
+	return w.err
794
+}
795
+
796
+func (w *Writer) finishInode() error {
797
+	if !w.initialized {
798
+		if err := w.init(); err != nil {
799
+			return err
800
+		}
801
+	}
802
+	if w.curInode == nil {
803
+		return nil
804
+	}
805
+	if w.dataWritten != w.dataMax {
806
+		return fmt.Errorf("did not write the right amount: %d != %d", w.dataWritten, w.dataMax)
807
+	}
808
+
809
+	if w.dataMax != 0 && w.curInode.Flags&format.InodeFlagInlineData == 0 {
810
+		if err := w.writeExtents(w.curInode); err != nil {
811
+			return err
812
+		}
813
+	}
814
+
815
+	w.dataWritten = 0
816
+	w.dataMax = 0
817
+	w.curInode = nil
818
+	return w.err
819
+}
820
+
821
+func modeToFileType(mode uint16) format.FileType {
822
+	switch mode & format.TypeMask {
823
+	default:
824
+		return format.FileTypeUnknown
825
+	case format.S_IFREG:
826
+		return format.FileTypeRegular
827
+	case format.S_IFDIR:
828
+		return format.FileTypeDirectory
829
+	case format.S_IFCHR:
830
+		return format.FileTypeCharacter
831
+	case format.S_IFBLK:
832
+		return format.FileTypeBlock
833
+	case format.S_IFIFO:
834
+		return format.FileTypeFIFO
835
+	case format.S_IFSOCK:
836
+		return format.FileTypeSocket
837
+	case format.S_IFLNK:
838
+		return format.FileTypeSymbolicLink
839
+	}
840
+}
841
+
842
+type constReader byte
843
+
844
+var zero = constReader(0)
845
+
846
+func (r constReader) Read(b []byte) (int, error) {
847
+	for i := range b {
848
+		b[i] = byte(r)
849
+	}
850
+	return len(b), nil
851
+}
852
+
853
+func (w *Writer) writeDirectory(dir, parent *inode) error {
854
+	if err := w.finishInode(); err != nil {
855
+		return err
856
+	}
857
+
858
+	// The size of the directory is not known yet.
859
+	w.startInode("", dir, 0x7fffffffffffffff)
860
+	left := blockSize
861
+	finishBlock := func() error {
862
+		if left > 0 {
863
+			e := format.DirectoryEntry{
864
+				RecordLength: uint16(left),
865
+			}
866
+			err := binary.Write(w, binary.LittleEndian, e)
867
+			if err != nil {
868
+				return err
869
+			}
870
+			left -= directoryEntrySize
871
+			if left < 4 {
872
+				panic("not enough space for trailing entry")
873
+			}
874
+			_, err = io.CopyN(w, zero, int64(left))
875
+			if err != nil {
876
+				return err
877
+			}
878
+		}
879
+		left = blockSize
880
+		return nil
881
+	}
882
+
883
+	writeEntry := func(ino format.InodeNumber, name string) error {
884
+		rlb := directoryEntrySize + len(name)
885
+		rl := (rlb + 3) & ^3
886
+		if left < rl+12 {
887
+			if err := finishBlock(); err != nil {
888
+				return err
889
+			}
890
+		}
891
+		e := format.DirectoryEntry{
892
+			Inode:        ino,
893
+			RecordLength: uint16(rl),
894
+			NameLength:   uint8(len(name)),
895
+			FileType:     modeToFileType(w.getInode(ino).Mode),
896
+		}
897
+		err := binary.Write(w, binary.LittleEndian, e)
898
+		if err != nil {
899
+			return err
900
+		}
901
+		_, err = w.Write([]byte(name))
902
+		if err != nil {
903
+			return err
904
+		}
905
+		var zero [4]byte
906
+		_, err = w.Write(zero[:rl-rlb])
907
+		if err != nil {
908
+			return err
909
+		}
910
+		left -= rl
911
+		return nil
912
+	}
913
+	if err := writeEntry(dir.Number, "."); err != nil {
914
+		return err
915
+	}
916
+	if err := writeEntry(parent.Number, ".."); err != nil {
917
+		return err
918
+	}
919
+
920
+	// Follow e2fsck's convention and sort the children by inode number.
921
+	var children []string
922
+	for name := range dir.Children {
923
+		children = append(children, name)
924
+	}
925
+	sort.Slice(children, func(i, j int) bool {
926
+		return dir.Children[children[i]].Number < dir.Children[children[j]].Number
927
+	})
928
+
929
+	for _, name := range children {
930
+		child := dir.Children[name]
931
+		if err := writeEntry(child.Number, name); err != nil {
932
+			return err
933
+		}
934
+	}
935
+	if err := finishBlock(); err != nil {
936
+		return err
937
+	}
938
+	w.curInode.Size = w.dataWritten
939
+	w.dataMax = w.dataWritten
940
+	return nil
941
+}
942
+
943
+func (w *Writer) writeDirectoryRecursive(dir, parent *inode) error {
944
+	if err := w.writeDirectory(dir, parent); err != nil {
945
+		return err
946
+	}
947
+	for _, child := range dir.Children {
948
+		if child.IsDir() {
949
+			if err := w.writeDirectoryRecursive(child, dir); err != nil {
950
+				return err
951
+			}
952
+		}
953
+	}
954
+	return nil
955
+}
956
+
957
+func (w *Writer) writeInodeTable(tableSize uint32) error {
958
+	var b bytes.Buffer
959
+	for _, inode := range w.inodes {
960
+		if inode != nil {
961
+			binode := format.Inode{
962
+				Mode:          inode.Mode,
963
+				Uid:           uint16(inode.Uid & 0xffff),
964
+				Gid:           uint16(inode.Gid & 0xffff),
965
+				SizeLow:       uint32(inode.Size & 0xffffffff),
966
+				SizeHigh:      uint32(inode.Size >> 32),
967
+				LinksCount:    uint16(inode.LinkCount),
968
+				BlocksLow:     inode.BlockCount,
969
+				Flags:         inode.Flags,
970
+				XattrBlockLow: inode.XattrBlock,
971
+				UidHigh:       uint16(inode.Uid >> 16),
972
+				GidHigh:       uint16(inode.Gid >> 16),
973
+				ExtraIsize:    uint16(inodeUsedSize - 128),
974
+				Atime:         uint32(inode.Atime),
975
+				AtimeExtra:    uint32(inode.Atime >> 32),
976
+				Ctime:         uint32(inode.Ctime),
977
+				CtimeExtra:    uint32(inode.Ctime >> 32),
978
+				Mtime:         uint32(inode.Mtime),
979
+				MtimeExtra:    uint32(inode.Mtime >> 32),
980
+				Crtime:        uint32(inode.Crtime),
981
+				CrtimeExtra:   uint32(inode.Crtime >> 32),
982
+			}
983
+			switch inode.Mode & format.TypeMask {
984
+			case format.S_IFDIR, format.S_IFREG, format.S_IFLNK:
985
+				n := copy(binode.Block[:], inode.Data)
986
+				if n < len(inode.Data) {
987
+					// Rewrite the first xattr with the data.
988
+					xattr := [1]xattr{{
989
+						Name:  "data",
990
+						Index: 7, // "system."
991
+						Value: inode.Data[n:],
992
+					}}
993
+					putXattrs(xattr[:], inode.XattrInline[4:], 0)
994
+				}
995
+			case format.S_IFBLK, format.S_IFCHR:
996
+				dev := inode.Devminor&0xff | inode.Devmajor<<8 | (inode.Devminor&0xffffff00)<<12
997
+				binary.LittleEndian.PutUint32(binode.Block[4:], dev)
998
+			}
999
+
1000
+			binary.Write(&b, binary.LittleEndian, binode)
1001
+			b.Truncate(inodeUsedSize)
1002
+			n, _ := b.Write(inode.XattrInline)
1003
+			io.CopyN(&b, zero, int64(inodeExtraSize-n))
1004
+		} else {
1005
+			io.CopyN(&b, zero, inodeSize)
1006
+		}
1007
+		if _, err := w.write(b.Next(inodeSize)); err != nil {
1008
+			return err
1009
+		}
1010
+	}
1011
+	rest := tableSize - uint32(len(w.inodes)*inodeSize)
1012
+	if _, err := w.zero(int64(rest)); err != nil {
1013
+		return err
1014
+	}
1015
+	return nil
1016
+}
1017
+
1018
+// NewWriter returns a Writer that writes an ext4 file system to the provided
1019
+// WriteSeeker.
1020
+func NewWriter(f io.ReadWriteSeeker, opts ...Option) *Writer {
1021
+	w := &Writer{
1022
+		f:           f,
1023
+		bw:          bufio.NewWriterSize(f, 65536*8),
1024
+		maxDiskSize: defaultMaxDiskSize,
1025
+	}
1026
+	for _, opt := range opts {
1027
+		opt(w)
1028
+	}
1029
+	return w
1030
+}
1031
+
1032
+// An Option provides extra options to NewWriter.
1033
+type Option func(*Writer)
1034
+
1035
+// InlineData instructs the Writer to write small files into the inode
1036
+// structures directly. This creates smaller images but currently is not
1037
+// compatible with DAX.
1038
+func InlineData(w *Writer) {
1039
+	w.supportInlineData = true
1040
+}
1041
+
1042
+// MaximumDiskSize instructs the writer to reserve enough metadata space for the
1043
+// specified disk size. If not provided, then 16GB is the default.
1044
+func MaximumDiskSize(size int64) Option {
1045
+	return func(w *Writer) {
1046
+		if size < 0 || size > maxMaxDiskSize {
1047
+			w.maxDiskSize = maxMaxDiskSize
1048
+		} else if size == 0 {
1049
+			w.maxDiskSize = defaultMaxDiskSize
1050
+		} else {
1051
+			w.maxDiskSize = (size + blockSize - 1) &^ (blockSize - 1)
1052
+		}
1053
+	}
1054
+}
1055
+
1056
+func (w *Writer) init() error {
1057
+	// Skip the defective block inode.
1058
+	w.inodes = make([]*inode, 1, 32)
1059
+	// Create the root directory.
1060
+	root, _ := w.makeInode(&File{
1061
+		Mode: format.S_IFDIR | 0755,
1062
+	}, nil)
1063
+	root.LinkCount++ // The root is linked to itself.
1064
+	// Skip until the first non-reserved inode.
1065
+	w.inodes = append(w.inodes, make([]*inode, inodeFirst-len(w.inodes)-1)...)
1066
+	maxBlocks := (w.maxDiskSize-1)/blockSize + 1
1067
+	maxGroups := (maxBlocks-1)/blocksPerGroup + 1
1068
+	w.gdBlocks = uint32((maxGroups-1)/groupsPerDescriptorBlock + 1)
1069
+
1070
+	// Skip past the superblock and block descriptor table.
1071
+	w.seekBlock(1 + w.gdBlocks)
1072
+	w.initialized = true
1073
+
1074
+	// The lost+found directory is required to exist for e2fsck to pass.
1075
+	if err := w.Create("lost+found", &File{Mode: format.S_IFDIR | 0700}); err != nil {
1076
+		return err
1077
+	}
1078
+	return w.err
1079
+}
1080
+
1081
+func groupCount(blocks uint32, inodes uint32, inodesPerGroup uint32) uint32 {
1082
+	inodeBlocksPerGroup := inodesPerGroup * inodeSize / blockSize
1083
+	dataBlocksPerGroup := blocksPerGroup - inodeBlocksPerGroup - 2 // save room for the bitmaps
1084
+
1085
+	// Increase the block count to ensure there are enough groups for all the
1086
+	// inodes.
1087
+	minBlocks := (inodes-1)/inodesPerGroup*dataBlocksPerGroup + 1
1088
+	if blocks < minBlocks {
1089
+		blocks = minBlocks
1090
+	}
1091
+
1092
+	return (blocks + dataBlocksPerGroup - 1) / dataBlocksPerGroup
1093
+}
1094
+
1095
+func bestGroupCount(blocks uint32, inodes uint32) (groups uint32, inodesPerGroup uint32) {
1096
+	groups = 0xffffffff
1097
+	for ipg := uint32(inodesPerGroupIncrement); ipg <= maxInodesPerGroup; ipg += inodesPerGroupIncrement {
1098
+		g := groupCount(blocks, inodes, ipg)
1099
+		if g < groups {
1100
+			groups = g
1101
+			inodesPerGroup = ipg
1102
+		}
1103
+	}
1104
+	return
1105
+}
1106
+
1107
+func (w *Writer) Close() error {
1108
+	if err := w.finishInode(); err != nil {
1109
+		return err
1110
+	}
1111
+	root := w.root()
1112
+	if err := w.writeDirectoryRecursive(root, root); err != nil {
1113
+		return err
1114
+	}
1115
+	// Finish the last inode (probably a directory).
1116
+	if err := w.finishInode(); err != nil {
1117
+		return err
1118
+	}
1119
+
1120
+	// Write the inode table
1121
+	inodeTableOffset := w.block()
1122
+	groups, inodesPerGroup := bestGroupCount(inodeTableOffset, uint32(len(w.inodes)))
1123
+	err := w.writeInodeTable(groups * inodesPerGroup * inodeSize)
1124
+	if err != nil {
1125
+		return err
1126
+	}
1127
+
1128
+	// Write the bitmaps.
1129
+	bitmapOffset := w.block()
1130
+	bitmapSize := groups * 2
1131
+	validDataSize := bitmapOffset + bitmapSize
1132
+	diskSize := validDataSize
1133
+	minSize := (groups-1)*blocksPerGroup + 1
1134
+	if diskSize < minSize {
1135
+		diskSize = minSize
1136
+	}
1137
+
1138
+	usedGdBlocks := (groups-1)/groupDescriptorSize + 1
1139
+	if usedGdBlocks > w.gdBlocks {
1140
+		return exceededMaxSizeError{w.maxDiskSize}
1141
+	}
1142
+
1143
+	gds := make([]format.GroupDescriptor, w.gdBlocks*groupsPerDescriptorBlock)
1144
+	inodeTableSizePerGroup := inodesPerGroup * inodeSize / blockSize
1145
+	var totalUsedBlocks, totalUsedInodes uint32
1146
+	for g := uint32(0); g < groups; g++ {
1147
+		var b [blockSize * 2]byte
1148
+		var dirCount, usedInodeCount, usedBlockCount uint16
1149
+
1150
+		// Block bitmap
1151
+		if (g+1)*blocksPerGroup <= validDataSize {
1152
+			// This group is fully allocated.
1153
+			for j := range b[:blockSize] {
1154
+				b[j] = 0xff
1155
+			}
1156
+			usedBlockCount = blocksPerGroup
1157
+		} else if g*blocksPerGroup < validDataSize {
1158
+			for j := uint32(0); j < validDataSize-g*blocksPerGroup; j++ {
1159
+				b[j/8] |= 1 << (j % 8)
1160
+				usedBlockCount++
1161
+			}
1162
+		}
1163
+		if g == 0 {
1164
+			// Unused group descriptor blocks should be cleared.
1165
+			for j := 1 + usedGdBlocks; j < 1+w.gdBlocks; j++ {
1166
+				b[j/8] &^= 1 << (j % 8)
1167
+				usedBlockCount--
1168
+			}
1169
+		}
1170
+		if g == groups-1 && diskSize%blocksPerGroup != 0 {
1171
+			// Blocks that aren't present in the disk should be marked as
1172
+			// allocated.
1173
+			for j := diskSize % blocksPerGroup; j < blocksPerGroup; j++ {
1174
+				b[j/8] |= 1 << (j % 8)
1175
+				usedBlockCount++
1176
+			}
1177
+		}
1178
+		// Inode bitmap
1179
+		for j := uint32(0); j < inodesPerGroup; j++ {
1180
+			ino := format.InodeNumber(1 + g*inodesPerGroup + j)
1181
+			inode := w.getInode(ino)
1182
+			if ino < inodeFirst || inode != nil {
1183
+				b[blockSize+j/8] |= 1 << (j % 8)
1184
+				usedInodeCount++
1185
+			}
1186
+			if inode != nil && inode.Mode&format.TypeMask == format.S_IFDIR {
1187
+				dirCount++
1188
+			}
1189
+		}
1190
+		_, err := w.write(b[:])
1191
+		if err != nil {
1192
+			return err
1193
+		}
1194
+		gds[g] = format.GroupDescriptor{
1195
+			BlockBitmapLow:     bitmapOffset + 2*g,
1196
+			InodeBitmapLow:     bitmapOffset + 2*g + 1,
1197
+			InodeTableLow:      inodeTableOffset + g*inodeTableSizePerGroup,
1198
+			UsedDirsCountLow:   dirCount,
1199
+			FreeInodesCountLow: uint16(inodesPerGroup) - usedInodeCount,
1200
+			FreeBlocksCountLow: blocksPerGroup - usedBlockCount,
1201
+		}
1202
+
1203
+		totalUsedBlocks += uint32(usedBlockCount)
1204
+		totalUsedInodes += uint32(usedInodeCount)
1205
+	}
1206
+
1207
+	// Zero up to the disk size.
1208
+	_, err = w.zero(int64(diskSize-bitmapOffset-bitmapSize) * blockSize)
1209
+	if err != nil {
1210
+		return err
1211
+	}
1212
+
1213
+	// Write the block descriptors
1214
+	w.seekBlock(1)
1215
+	if w.err != nil {
1216
+		return w.err
1217
+	}
1218
+	err = binary.Write(w.bw, binary.LittleEndian, gds)
1219
+	if err != nil {
1220
+		return err
1221
+	}
1222
+
1223
+	// Write the super block
1224
+	var blk [blockSize]byte
1225
+	b := bytes.NewBuffer(blk[:1024])
1226
+	sb := &format.SuperBlock{
1227
+		InodesCount:        inodesPerGroup * groups,
1228
+		BlocksCountLow:     diskSize,
1229
+		FreeBlocksCountLow: blocksPerGroup*groups - totalUsedBlocks,
1230
+		FreeInodesCount:    inodesPerGroup*groups - totalUsedInodes,
1231
+		FirstDataBlock:     0,
1232
+		LogBlockSize:       2, // 2^(10 + 2)
1233
+		LogClusterSize:     2,
1234
+		BlocksPerGroup:     blocksPerGroup,
1235
+		ClustersPerGroup:   blocksPerGroup,
1236
+		InodesPerGroup:     inodesPerGroup,
1237
+		Magic:              format.SuperBlockMagic,
1238
+		State:              1, // cleanly unmounted
1239
+		Errors:             1, // continue on error?
1240
+		CreatorOS:          0, // Linux
1241
+		RevisionLevel:      1, // dynamic inode sizes
1242
+		FirstInode:         inodeFirst,
1243
+		LpfInode:           inodeLostAndFound,
1244
+		InodeSize:          inodeSize,
1245
+		FeatureCompat:      format.CompatSparseSuper2 | format.CompatExtAttr,
1246
+		FeatureIncompat:    format.IncompatFiletype | format.IncompatExtents | format.IncompatFlexBg,
1247
+		FeatureRoCompat:    format.RoCompatLargeFile | format.RoCompatHugeFile | format.RoCompatExtraIsize | format.RoCompatReadonly,
1248
+		MinExtraIsize:      extraIsize,
1249
+		WantExtraIsize:     extraIsize,
1250
+		LogGroupsPerFlex:   31,
1251
+	}
1252
+	if w.supportInlineData {
1253
+		sb.FeatureIncompat |= format.IncompatInlineData
1254
+	}
1255
+	binary.Write(b, binary.LittleEndian, sb)
1256
+	w.seekBlock(0)
1257
+	if _, err := w.write(blk[:]); err != nil {
1258
+		return err
1259
+	}
1260
+	w.seekBlock(diskSize)
1261
+	return w.err
1262
+}
0 1263
new file mode 100644
... ...
@@ -0,0 +1,411 @@
0
+package format
1
+
2
+type SuperBlock struct {
3
+	InodesCount          uint32
4
+	BlocksCountLow       uint32
5
+	RootBlocksCountLow   uint32
6
+	FreeBlocksCountLow   uint32
7
+	FreeInodesCount      uint32
8
+	FirstDataBlock       uint32
9
+	LogBlockSize         uint32
10
+	LogClusterSize       uint32
11
+	BlocksPerGroup       uint32
12
+	ClustersPerGroup     uint32
13
+	InodesPerGroup       uint32
14
+	Mtime                uint32
15
+	Wtime                uint32
16
+	MountCount           uint16
17
+	MaxMountCount        uint16
18
+	Magic                uint16
19
+	State                uint16
20
+	Errors               uint16
21
+	MinorRevisionLevel   uint16
22
+	LastCheck            uint32
23
+	CheckInterval        uint32
24
+	CreatorOS            uint32
25
+	RevisionLevel        uint32
26
+	DefaultReservedUid   uint16
27
+	DefaultReservedGid   uint16
28
+	FirstInode           uint32
29
+	InodeSize            uint16
30
+	BlockGroupNr         uint16
31
+	FeatureCompat        CompatFeature
32
+	FeatureIncompat      IncompatFeature
33
+	FeatureRoCompat      RoCompatFeature
34
+	UUID                 [16]uint8
35
+	VolumeName           [16]byte
36
+	LastMounted          [64]byte
37
+	AlgorithmUsageBitmap uint32
38
+	PreallocBlocks       uint8
39
+	PreallocDirBlocks    uint8
40
+	ReservedGdtBlocks    uint16
41
+	JournalUUID          [16]uint8
42
+	JournalInum          uint32
43
+	JournalDev           uint32
44
+	LastOrphan           uint32
45
+	HashSeed             [4]uint32
46
+	DefHashVersion       uint8
47
+	JournalBackupType    uint8
48
+	DescSize             uint16
49
+	DefaultMountOpts     uint32
50
+	FirstMetaBg          uint32
51
+	MkfsTime             uint32
52
+	JournalBlocks        [17]uint32
53
+	BlocksCountHigh      uint32
54
+	RBlocksCountHigh     uint32
55
+	FreeBlocksCountHigh  uint32
56
+	MinExtraIsize        uint16
57
+	WantExtraIsize       uint16
58
+	Flags                uint32
59
+	RaidStride           uint16
60
+	MmpInterval          uint16
61
+	MmpBlock             uint64
62
+	RaidStripeWidth      uint32
63
+	LogGroupsPerFlex     uint8
64
+	ChecksumType         uint8
65
+	ReservedPad          uint16
66
+	KbytesWritten        uint64
67
+	SnapshotInum         uint32
68
+	SnapshotID           uint32
69
+	SnapshotRBlocksCount uint64
70
+	SnapshotList         uint32
71
+	ErrorCount           uint32
72
+	FirstErrorTime       uint32
73
+	FirstErrorInode      uint32
74
+	FirstErrorBlock      uint64
75
+	FirstErrorFunc       [32]uint8
76
+	FirstErrorLine       uint32
77
+	LastErrorTime        uint32
78
+	LastErrorInode       uint32
79
+	LastErrorLine        uint32
80
+	LastErrorBlock       uint64
81
+	LastErrorFunc        [32]uint8
82
+	MountOpts            [64]uint8
83
+	UserQuotaInum        uint32
84
+	GroupQuotaInum       uint32
85
+	OverheadBlocks       uint32
86
+	BackupBgs            [2]uint32
87
+	EncryptAlgos         [4]uint8
88
+	EncryptPwSalt        [16]uint8
89
+	LpfInode             uint32
90
+	ProjectQuotaInum     uint32
91
+	ChecksumSeed         uint32
92
+	WtimeHigh            uint8
93
+	MtimeHigh            uint8
94
+	MkfsTimeHigh         uint8
95
+	LastcheckHigh        uint8
96
+	FirstErrorTimeHigh   uint8
97
+	LastErrorTimeHigh    uint8
98
+	Pad                  [2]uint8
99
+	Reserved             [96]uint32
100
+	Checksum             uint32
101
+}
102
+
103
+const SuperBlockMagic uint16 = 0xef53
104
+
105
+type CompatFeature uint32
106
+type IncompatFeature uint32
107
+type RoCompatFeature uint32
108
+
109
+const (
110
+	CompatDirPrealloc   CompatFeature = 0x1
111
+	CompatImagicInodes  CompatFeature = 0x2
112
+	CompatHasJournal    CompatFeature = 0x4
113
+	CompatExtAttr       CompatFeature = 0x8
114
+	CompatResizeInode   CompatFeature = 0x10
115
+	CompatDirIndex      CompatFeature = 0x20
116
+	CompatLazyBg        CompatFeature = 0x40
117
+	CompatExcludeInode  CompatFeature = 0x80
118
+	CompatExcludeBitmap CompatFeature = 0x100
119
+	CompatSparseSuper2  CompatFeature = 0x200
120
+
121
+	IncompatCompression IncompatFeature = 0x1
122
+	IncompatFiletype    IncompatFeature = 0x2
123
+	IncompatRecover     IncompatFeature = 0x4
124
+	IncompatJournalDev  IncompatFeature = 0x8
125
+	IncompatMetaBg      IncompatFeature = 0x10
126
+	IncompatExtents     IncompatFeature = 0x40
127
+	Incompat_64Bit      IncompatFeature = 0x80
128
+	IncompatMmp         IncompatFeature = 0x100
129
+	IncompatFlexBg      IncompatFeature = 0x200
130
+	IncompatEaInode     IncompatFeature = 0x400
131
+	IncompatDirdata     IncompatFeature = 0x1000
132
+	IncompatCsumSeed    IncompatFeature = 0x2000
133
+	IncompatLargedir    IncompatFeature = 0x4000
134
+	IncompatInlineData  IncompatFeature = 0x8000
135
+	IncompatEncrypt     IncompatFeature = 0x10000
136
+
137
+	RoCompatSparseSuper  RoCompatFeature = 0x1
138
+	RoCompatLargeFile    RoCompatFeature = 0x2
139
+	RoCompatBtreeDir     RoCompatFeature = 0x4
140
+	RoCompatHugeFile     RoCompatFeature = 0x8
141
+	RoCompatGdtCsum      RoCompatFeature = 0x10
142
+	RoCompatDirNlink     RoCompatFeature = 0x20
143
+	RoCompatExtraIsize   RoCompatFeature = 0x40
144
+	RoCompatHasSnapshot  RoCompatFeature = 0x80
145
+	RoCompatQuota        RoCompatFeature = 0x100
146
+	RoCompatBigalloc     RoCompatFeature = 0x200
147
+	RoCompatMetadataCsum RoCompatFeature = 0x400
148
+	RoCompatReplica      RoCompatFeature = 0x800
149
+	RoCompatReadonly     RoCompatFeature = 0x1000
150
+	RoCompatProject      RoCompatFeature = 0x2000
151
+)
152
+
153
+type BlockGroupFlag uint16
154
+
155
+const (
156
+	BlockGroupInodeUninit BlockGroupFlag = 0x1
157
+	BlockGroupBlockUninit BlockGroupFlag = 0x2
158
+	BlockGroupInodeZeroed BlockGroupFlag = 0x4
159
+)
160
+
161
+type GroupDescriptor struct {
162
+	BlockBitmapLow     uint32
163
+	InodeBitmapLow     uint32
164
+	InodeTableLow      uint32
165
+	FreeBlocksCountLow uint16
166
+	FreeInodesCountLow uint16
167
+	UsedDirsCountLow   uint16
168
+	Flags              BlockGroupFlag
169
+	ExcludeBitmapLow   uint32
170
+	BlockBitmapCsumLow uint16
171
+	InodeBitmapCsumLow uint16
172
+	ItableUnusedLow    uint16
173
+	Checksum           uint16
174
+}
175
+
176
+type GroupDescriptor64 struct {
177
+	GroupDescriptor
178
+	BlockBitmapHigh     uint32
179
+	InodeBitmapHigh     uint32
180
+	InodeTableHigh      uint32
181
+	FreeBlocksCountHigh uint16
182
+	FreeInodesCountHigh uint16
183
+	UsedDirsCountHigh   uint16
184
+	ItableUnusedHigh    uint16
185
+	ExcludeBitmapHigh   uint32
186
+	BlockBitmapCsumHigh uint16
187
+	InodeBitmapCsumHigh uint16
188
+	Reserved            uint32
189
+}
190
+
191
+const (
192
+	S_IXOTH  = 0x1
193
+	S_IWOTH  = 0x2
194
+	S_IROTH  = 0x4
195
+	S_IXGRP  = 0x8
196
+	S_IWGRP  = 0x10
197
+	S_IRGRP  = 0x20
198
+	S_IXUSR  = 0x40
199
+	S_IWUSR  = 0x80
200
+	S_IRUSR  = 0x100
201
+	S_ISVTX  = 0x200
202
+	S_ISGID  = 0x400
203
+	S_ISUID  = 0x800
204
+	S_IFIFO  = 0x1000
205
+	S_IFCHR  = 0x2000
206
+	S_IFDIR  = 0x4000
207
+	S_IFBLK  = 0x6000
208
+	S_IFREG  = 0x8000
209
+	S_IFLNK  = 0xA000
210
+	S_IFSOCK = 0xC000
211
+
212
+	TypeMask uint16 = 0xF000
213
+)
214
+
215
+type InodeNumber uint32
216
+
217
+const (
218
+	InodeRoot = 2
219
+)
220
+
221
+type Inode struct {
222
+	Mode                 uint16
223
+	Uid                  uint16
224
+	SizeLow              uint32
225
+	Atime                uint32
226
+	Ctime                uint32
227
+	Mtime                uint32
228
+	Dtime                uint32
229
+	Gid                  uint16
230
+	LinksCount           uint16
231
+	BlocksLow            uint32
232
+	Flags                InodeFlag
233
+	Version              uint32
234
+	Block                [60]byte
235
+	Generation           uint32
236
+	XattrBlockLow        uint32
237
+	SizeHigh             uint32
238
+	ObsoleteFragmentAddr uint32
239
+	BlocksHigh           uint16
240
+	XattrBlockHigh       uint16
241
+	UidHigh              uint16
242
+	GidHigh              uint16
243
+	ChecksumLow          uint16
244
+	Reserved             uint16
245
+	ExtraIsize           uint16
246
+	ChecksumHigh         uint16
247
+	CtimeExtra           uint32
248
+	MtimeExtra           uint32
249
+	AtimeExtra           uint32
250
+	Crtime               uint32
251
+	CrtimeExtra          uint32
252
+	VersionHigh          uint32
253
+	Projid               uint32
254
+}
255
+
256
+type InodeFlag uint32
257
+
258
+const (
259
+	InodeFlagSecRm              InodeFlag = 0x1
260
+	InodeFlagUnRm               InodeFlag = 0x2
261
+	InodeFlagCompressed         InodeFlag = 0x4
262
+	InodeFlagSync               InodeFlag = 0x8
263
+	InodeFlagImmutable          InodeFlag = 0x10
264
+	InodeFlagAppend             InodeFlag = 0x20
265
+	InodeFlagNoDump             InodeFlag = 0x40
266
+	InodeFlagNoAtime            InodeFlag = 0x80
267
+	InodeFlagDirtyCompressed    InodeFlag = 0x100
268
+	InodeFlagCompressedClusters InodeFlag = 0x200
269
+	InodeFlagNoCompress         InodeFlag = 0x400
270
+	InodeFlagEncrypted          InodeFlag = 0x800
271
+	InodeFlagHashedIndex        InodeFlag = 0x1000
272
+	InodeFlagMagic              InodeFlag = 0x2000
273
+	InodeFlagJournalData        InodeFlag = 0x4000
274
+	InodeFlagNoTail             InodeFlag = 0x8000
275
+	InodeFlagDirSync            InodeFlag = 0x10000
276
+	InodeFlagTopDir             InodeFlag = 0x20000
277
+	InodeFlagHugeFile           InodeFlag = 0x40000
278
+	InodeFlagExtents            InodeFlag = 0x80000
279
+	InodeFlagEaInode            InodeFlag = 0x200000
280
+	InodeFlagEOFBlocks          InodeFlag = 0x400000
281
+	InodeFlagSnapfile           InodeFlag = 0x01000000
282
+	InodeFlagSnapfileDeleted    InodeFlag = 0x04000000
283
+	InodeFlagSnapfileShrunk     InodeFlag = 0x08000000
284
+	InodeFlagInlineData         InodeFlag = 0x10000000
285
+	InodeFlagProjectIDInherit   InodeFlag = 0x20000000
286
+	InodeFlagReserved           InodeFlag = 0x80000000
287
+)
288
+
289
+const (
290
+	MaxLinks = 65000
291
+)
292
+
293
+type ExtentHeader struct {
294
+	Magic      uint16
295
+	Entries    uint16
296
+	Max        uint16
297
+	Depth      uint16
298
+	Generation uint32
299
+}
300
+
301
+const ExtentHeaderMagic uint16 = 0xf30a
302
+
303
+type ExtentIndexNode struct {
304
+	Block    uint32
305
+	LeafLow  uint32
306
+	LeafHigh uint16
307
+	Unused   uint16
308
+}
309
+
310
+type ExtentLeafNode struct {
311
+	Block     uint32
312
+	Length    uint16
313
+	StartHigh uint16
314
+	StartLow  uint32
315
+}
316
+
317
+type ExtentTail struct {
318
+	Checksum uint32
319
+}
320
+
321
+type DirectoryEntry struct {
322
+	Inode        InodeNumber
323
+	RecordLength uint16
324
+	NameLength   uint8
325
+	FileType     FileType
326
+	//Name         []byte
327
+}
328
+
329
+type FileType uint8
330
+
331
+const (
332
+	FileTypeUnknown      FileType = 0x0
333
+	FileTypeRegular      FileType = 0x1
334
+	FileTypeDirectory    FileType = 0x2
335
+	FileTypeCharacter    FileType = 0x3
336
+	FileTypeBlock        FileType = 0x4
337
+	FileTypeFIFO         FileType = 0x5
338
+	FileTypeSocket       FileType = 0x6
339
+	FileTypeSymbolicLink FileType = 0x7
340
+)
341
+
342
+type DirectoryEntryTail struct {
343
+	ReservedZero1 uint32
344
+	RecordLength  uint16
345
+	ReservedZero2 uint8
346
+	FileType      uint8
347
+	Checksum      uint32
348
+}
349
+
350
+type DirectoryTreeRoot struct {
351
+	Dot            DirectoryEntry
352
+	DotName        [4]byte
353
+	DotDot         DirectoryEntry
354
+	DotDotName     [4]byte
355
+	ReservedZero   uint32
356
+	HashVersion    uint8
357
+	InfoLength     uint8
358
+	IndirectLevels uint8
359
+	UnusedFlags    uint8
360
+	Limit          uint16
361
+	Count          uint16
362
+	Block          uint32
363
+	//Entries        []DirectoryTreeEntry
364
+}
365
+
366
+type DirectoryTreeNode struct {
367
+	FakeInode        uint32
368
+	FakeRecordLength uint16
369
+	NameLength       uint8
370
+	FileType         uint8
371
+	Limit            uint16
372
+	Count            uint16
373
+	Block            uint32
374
+	//Entries          []DirectoryTreeEntry
375
+}
376
+
377
+type DirectoryTreeEntry struct {
378
+	Hash  uint32
379
+	Block uint32
380
+}
381
+
382
+type DirectoryTreeTail struct {
383
+	Reserved uint32
384
+	Checksum uint32
385
+}
386
+
387
+type XAttrInodeBodyHeader struct {
388
+	Magic uint32
389
+}
390
+
391
+type XAttrHeader struct {
392
+	Magic          uint32
393
+	ReferenceCount uint32
394
+	Blocks         uint32
395
+	Hash           uint32
396
+	Checksum       uint32
397
+	Reserved       [3]uint32
398
+}
399
+
400
+const XAttrHeaderMagic uint32 = 0xea020000
401
+
402
+type XAttrEntry struct {
403
+	NameLength  uint8
404
+	NameIndex   uint8
405
+	ValueOffset uint16
406
+	ValueInum   uint32
407
+	ValueSize   uint32
408
+	Hash        uint32
409
+	//Name        []byte
410
+}
0 411
new file mode 100644
... ...
@@ -0,0 +1,174 @@
0
+package tar2ext4
1
+
2
+import (
3
+	"archive/tar"
4
+	"bufio"
5
+	"encoding/binary"
6
+	"io"
7
+	"path"
8
+	"strings"
9
+
10
+	"github.com/Microsoft/hcsshim/ext4/internal/compactext4"
11
+)
12
+
13
+type params struct {
14
+	convertWhiteout bool
15
+	appendVhdFooter bool
16
+	ext4opts        []compactext4.Option
17
+}
18
+
19
+// Option is the type for optional parameters to Convert.
20
+type Option func(*params)
21
+
22
+// ConvertWhiteout instructs the converter to convert OCI-style whiteouts
23
+// (beginning with .wh.) to overlay-style whiteouts.
24
+func ConvertWhiteout(p *params) {
25
+	p.convertWhiteout = true
26
+}
27
+
28
+// AppendVhdFooter instructs the converter to add a fixed VHD footer to the
29
+// file.
30
+func AppendVhdFooter(p *params) {
31
+	p.appendVhdFooter = true
32
+}
33
+
34
+// InlineData instructs the converter to write small files into the inode
35
+// structures directly. This creates smaller images but currently is not
36
+// compatible with DAX.
37
+func InlineData(p *params) {
38
+	p.ext4opts = append(p.ext4opts, compactext4.InlineData)
39
+}
40
+
41
+// MaximumDiskSize instructs the writer to limit the disk size to the specified
42
+// value. This also reserves enough metadata space for the specified disk size.
43
+// If not provided, then 16GB is the default.
44
+func MaximumDiskSize(size int64) Option {
45
+	return func(p *params) {
46
+		p.ext4opts = append(p.ext4opts, compactext4.MaximumDiskSize(size))
47
+	}
48
+}
49
+
50
+const (
51
+	whiteoutPrefix = ".wh."
52
+	opaqueWhiteout = ".wh..wh..opq"
53
+)
54
+
55
+// Convert writes a compact ext4 file system image that contains the files in the
56
+// input tar stream.
57
+func Convert(r io.Reader, w io.ReadWriteSeeker, options ...Option) error {
58
+	var p params
59
+	for _, opt := range options {
60
+		opt(&p)
61
+	}
62
+	t := tar.NewReader(bufio.NewReader(r))
63
+	fs := compactext4.NewWriter(w, p.ext4opts...)
64
+	for {
65
+		hdr, err := t.Next()
66
+		if err == io.EOF {
67
+			break
68
+		}
69
+		if err != nil {
70
+			return err
71
+		}
72
+
73
+		if p.convertWhiteout {
74
+			dir, name := path.Split(hdr.Name)
75
+			if strings.HasPrefix(name, whiteoutPrefix) {
76
+				if name == opaqueWhiteout {
77
+					// Update the directory with the appropriate xattr.
78
+					f, err := fs.Stat(dir)
79
+					if err != nil {
80
+						return err
81
+					}
82
+					f.Xattrs["trusted.overlay.opaque"] = []byte("y")
83
+					err = fs.Create(dir, f)
84
+					if err != nil {
85
+						return err
86
+					}
87
+				} else {
88
+					// Create an overlay-style whiteout.
89
+					f := &compactext4.File{
90
+						Mode:     compactext4.S_IFCHR,
91
+						Devmajor: 0,
92
+						Devminor: 0,
93
+					}
94
+					err = fs.Create(path.Join(dir, name[len(whiteoutPrefix):]), f)
95
+					if err != nil {
96
+						return err
97
+					}
98
+				}
99
+
100
+				continue
101
+			}
102
+		}
103
+
104
+		if hdr.Typeflag == tar.TypeLink {
105
+			err = fs.Link(hdr.Linkname, hdr.Name)
106
+			if err != nil {
107
+				return err
108
+			}
109
+		} else {
110
+			f := &compactext4.File{
111
+				Mode:     uint16(hdr.Mode),
112
+				Atime:    hdr.AccessTime,
113
+				Mtime:    hdr.ModTime,
114
+				Ctime:    hdr.ChangeTime,
115
+				Crtime:   hdr.ModTime,
116
+				Size:     hdr.Size,
117
+				Uid:      uint32(hdr.Uid),
118
+				Gid:      uint32(hdr.Gid),
119
+				Linkname: hdr.Linkname,
120
+				Devmajor: uint32(hdr.Devmajor),
121
+				Devminor: uint32(hdr.Devminor),
122
+				Xattrs:   make(map[string][]byte),
123
+			}
124
+			for key, value := range hdr.PAXRecords {
125
+				const xattrPrefix = "SCHILY.xattr."
126
+				if strings.HasPrefix(key, xattrPrefix) {
127
+					f.Xattrs[key[len(xattrPrefix):]] = []byte(value)
128
+				}
129
+			}
130
+
131
+			var typ uint16
132
+			switch hdr.Typeflag {
133
+			case tar.TypeReg, tar.TypeRegA:
134
+				typ = compactext4.S_IFREG
135
+			case tar.TypeSymlink:
136
+				typ = compactext4.S_IFLNK
137
+			case tar.TypeChar:
138
+				typ = compactext4.S_IFCHR
139
+			case tar.TypeBlock:
140
+				typ = compactext4.S_IFBLK
141
+			case tar.TypeDir:
142
+				typ = compactext4.S_IFDIR
143
+			case tar.TypeFifo:
144
+				typ = compactext4.S_IFIFO
145
+			}
146
+			f.Mode &= ^compactext4.TypeMask
147
+			f.Mode |= typ
148
+			err = fs.Create(hdr.Name, f)
149
+			if err != nil {
150
+				return err
151
+			}
152
+			_, err = io.Copy(fs, t)
153
+			if err != nil {
154
+				return err
155
+			}
156
+		}
157
+	}
158
+	err := fs.Close()
159
+	if err != nil {
160
+		return err
161
+	}
162
+	if p.appendVhdFooter {
163
+		size, err := w.Seek(0, io.SeekEnd)
164
+		if err != nil {
165
+			return err
166
+		}
167
+		err = binary.Write(w, binary.BigEndian, makeFixedVHDFooter(size))
168
+		if err != nil {
169
+			return err
170
+		}
171
+	}
172
+	return nil
173
+}
0 174
new file mode 100644
... ...
@@ -0,0 +1,76 @@
0
+package tar2ext4
1
+
2
+import (
3
+	"bytes"
4
+	"crypto/rand"
5
+	"encoding/binary"
6
+)
7
+
8
+// Constants for the VHD footer
9
+const (
10
+	cookieMagic            = "conectix"
11
+	featureMask            = 0x2
12
+	fileFormatVersionMagic = 0x00010000
13
+	fixedDataOffset        = -1
14
+	creatorVersionMagic    = 0x000a0000
15
+	diskTypeFixed          = 2
16
+)
17
+
18
+type vhdFooter struct {
19
+	Cookie             [8]byte
20
+	Features           uint32
21
+	FileFormatVersion  uint32
22
+	DataOffset         int64
23
+	TimeStamp          uint32
24
+	CreatorApplication [4]byte
25
+	CreatorVersion     uint32
26
+	CreatorHostOS      [4]byte
27
+	OriginalSize       int64
28
+	CurrentSize        int64
29
+	DiskGeometry       uint32
30
+	DiskType           uint32
31
+	Checksum           uint32
32
+	UniqueID           [16]uint8
33
+	SavedState         uint8
34
+	Reserved           [427]uint8
35
+}
36
+
37
+func makeFixedVHDFooter(size int64) *vhdFooter {
38
+	footer := &vhdFooter{
39
+		Features:          featureMask,
40
+		FileFormatVersion: fileFormatVersionMagic,
41
+		DataOffset:        fixedDataOffset,
42
+		CreatorVersion:    creatorVersionMagic,
43
+		OriginalSize:      size,
44
+		CurrentSize:       size,
45
+		DiskType:          diskTypeFixed,
46
+		UniqueID:          generateUUID(),
47
+	}
48
+	copy(footer.Cookie[:], cookieMagic)
49
+	footer.Checksum = calculateCheckSum(footer)
50
+	return footer
51
+}
52
+
53
+func calculateCheckSum(footer *vhdFooter) uint32 {
54
+	oldchk := footer.Checksum
55
+	footer.Checksum = 0
56
+
57
+	buf := &bytes.Buffer{}
58
+	binary.Write(buf, binary.BigEndian, footer)
59
+
60
+	var chk uint32
61
+	bufBytes := buf.Bytes()
62
+	for i := 0; i < len(bufBytes); i++ {
63
+		chk += uint32(bufBytes[i])
64
+	}
65
+	footer.Checksum = oldchk
66
+	return uint32(^chk)
67
+}
68
+
69
+func generateUUID() [16]byte {
70
+	res := [16]byte{}
71
+	if _, err := rand.Read(res[:]); err != nil {
72
+		panic(err)
73
+	}
74
+	return res
75
+}
0 76
deleted file mode 100644
... ...
@@ -1,6 +0,0 @@
1
-package hcsshim
2
-
3
-// IsTP4 returns whether the currently running Windows build is at least TP4.
4
-func IsTP4() bool {
5
-	return false
6
-}