1package layer // import "github.com/docker/docker/layer"
2
3import (
4	"bytes"
5	"io"
6	"io/ioutil"
7	"os"
8	"path/filepath"
9	"runtime"
10	"strings"
11	"testing"
12
13	"github.com/containerd/continuity/driver"
14	"github.com/docker/docker/daemon/graphdriver"
15	"github.com/docker/docker/daemon/graphdriver/vfs"
16	"github.com/docker/docker/pkg/archive"
17	"github.com/docker/docker/pkg/containerfs"
18	"github.com/docker/docker/pkg/idtools"
19	"github.com/docker/docker/pkg/stringid"
20	digest "github.com/opencontainers/go-digest"
21)
22
23func init() {
24	graphdriver.ApplyUncompressedLayer = archive.UnpackLayer
25	defaultArchiver := archive.NewDefaultArchiver()
26	vfs.CopyDir = defaultArchiver.CopyWithTar
27}
28
29func newVFSGraphDriver(td string) (graphdriver.Driver, error) {
30	uidMap := []idtools.IDMap{
31		{
32			ContainerID: 0,
33			HostID:      os.Getuid(),
34			Size:        1,
35		},
36	}
37	gidMap := []idtools.IDMap{
38		{
39			ContainerID: 0,
40			HostID:      os.Getgid(),
41			Size:        1,
42		},
43	}
44
45	options := graphdriver.Options{Root: td, UIDMaps: uidMap, GIDMaps: gidMap}
46	return graphdriver.GetDriver("vfs", nil, options)
47}
48
49func newTestGraphDriver(t *testing.T) (graphdriver.Driver, func()) {
50	td, err := ioutil.TempDir("", "graph-")
51	if err != nil {
52		t.Fatal(err)
53	}
54
55	driver, err := newVFSGraphDriver(td)
56	if err != nil {
57		t.Fatal(err)
58	}
59
60	return driver, func() {
61		os.RemoveAll(td)
62	}
63}
64
65func newTestStore(t *testing.T) (Store, string, func()) {
66	td, err := ioutil.TempDir("", "layerstore-")
67	if err != nil {
68		t.Fatal(err)
69	}
70
71	graph, graphcleanup := newTestGraphDriver(t)
72
73	ls, err := newStoreFromGraphDriver(td, graph, runtime.GOOS)
74	if err != nil {
75		t.Fatal(err)
76	}
77
78	return ls, td, func() {
79		graphcleanup()
80		os.RemoveAll(td)
81	}
82}
83
84type layerInit func(root containerfs.ContainerFS) error
85
86func createLayer(ls Store, parent ChainID, layerFunc layerInit) (Layer, error) {
87	containerID := stringid.GenerateRandomID()
88	mount, err := ls.CreateRWLayer(containerID, parent, nil)
89	if err != nil {
90		return nil, err
91	}
92
93	pathFS, err := mount.Mount("")
94	if err != nil {
95		return nil, err
96	}
97
98	if err := layerFunc(pathFS); err != nil {
99		return nil, err
100	}
101
102	ts, err := mount.TarStream()
103	if err != nil {
104		return nil, err
105	}
106	defer ts.Close()
107
108	layer, err := ls.Register(ts, parent)
109	if err != nil {
110		return nil, err
111	}
112
113	if err := mount.Unmount(); err != nil {
114		return nil, err
115	}
116
117	if _, err := ls.ReleaseRWLayer(mount); err != nil {
118		return nil, err
119	}
120
121	return layer, nil
122}
123
124type FileApplier interface {
125	ApplyFile(root containerfs.ContainerFS) error
126}
127
128type testFile struct {
129	name       string
130	content    []byte
131	permission os.FileMode
132}
133
134func newTestFile(name string, content []byte, perm os.FileMode) FileApplier {
135	return &testFile{
136		name:       name,
137		content:    content,
138		permission: perm,
139	}
140}
141
142func (tf *testFile) ApplyFile(root containerfs.ContainerFS) error {
143	fullPath := root.Join(root.Path(), tf.name)
144	if err := root.MkdirAll(root.Dir(fullPath), 0755); err != nil {
145		return err
146	}
147	// Check if already exists
148	if stat, err := root.Stat(fullPath); err == nil && stat.Mode().Perm() != tf.permission {
149		if err := root.Lchmod(fullPath, tf.permission); err != nil {
150			return err
151		}
152	}
153	return driver.WriteFile(root, fullPath, tf.content, tf.permission)
154}
155
156func initWithFiles(files ...FileApplier) layerInit {
157	return func(root containerfs.ContainerFS) error {
158		for _, f := range files {
159			if err := f.ApplyFile(root); err != nil {
160				return err
161			}
162		}
163		return nil
164	}
165}
166
167func getCachedLayer(l Layer) *roLayer {
168	if rl, ok := l.(*referencedCacheLayer); ok {
169		return rl.roLayer
170	}
171	return l.(*roLayer)
172}
173
174func createMetadata(layers ...Layer) []Metadata {
175	metadata := make([]Metadata, len(layers))
176	for i := range layers {
177		size, err := layers[i].Size()
178		if err != nil {
179			panic(err)
180		}
181
182		metadata[i].ChainID = layers[i].ChainID()
183		metadata[i].DiffID = layers[i].DiffID()
184		metadata[i].Size = size
185		metadata[i].DiffSize = getCachedLayer(layers[i]).size
186	}
187
188	return metadata
189}
190
191func assertMetadata(t *testing.T, metadata, expectedMetadata []Metadata) {
192	if len(metadata) != len(expectedMetadata) {
193		t.Fatalf("Unexpected number of deletes %d, expected %d", len(metadata), len(expectedMetadata))
194	}
195
196	for i := range metadata {
197		if metadata[i] != expectedMetadata[i] {
198			t.Errorf("Unexpected metadata\n\tExpected: %#v\n\tActual: %#v", expectedMetadata[i], metadata[i])
199		}
200	}
201	if t.Failed() {
202		t.FailNow()
203	}
204}
205
206func releaseAndCheckDeleted(t *testing.T, ls Store, layer Layer, removed ...Layer) {
207	layerCount := len(ls.(*layerStore).layerMap)
208	expectedMetadata := createMetadata(removed...)
209	metadata, err := ls.Release(layer)
210	if err != nil {
211		t.Fatal(err)
212	}
213
214	assertMetadata(t, metadata, expectedMetadata)
215
216	if expected := layerCount - len(removed); len(ls.(*layerStore).layerMap) != expected {
217		t.Fatalf("Unexpected number of layers %d, expected %d", len(ls.(*layerStore).layerMap), expected)
218	}
219}
220
221func cacheID(l Layer) string {
222	return getCachedLayer(l).cacheID
223}
224
225func assertLayerEqual(t *testing.T, l1, l2 Layer) {
226	if l1.ChainID() != l2.ChainID() {
227		t.Fatalf("Mismatched ChainID: %s vs %s", l1.ChainID(), l2.ChainID())
228	}
229	if l1.DiffID() != l2.DiffID() {
230		t.Fatalf("Mismatched DiffID: %s vs %s", l1.DiffID(), l2.DiffID())
231	}
232
233	size1, err := l1.Size()
234	if err != nil {
235		t.Fatal(err)
236	}
237
238	size2, err := l2.Size()
239	if err != nil {
240		t.Fatal(err)
241	}
242
243	if size1 != size2 {
244		t.Fatalf("Mismatched size: %d vs %d", size1, size2)
245	}
246
247	if cacheID(l1) != cacheID(l2) {
248		t.Fatalf("Mismatched cache id: %s vs %s", cacheID(l1), cacheID(l2))
249	}
250
251	p1 := l1.Parent()
252	p2 := l2.Parent()
253	if p1 != nil && p2 != nil {
254		assertLayerEqual(t, p1, p2)
255	} else if p1 != nil || p2 != nil {
256		t.Fatalf("Mismatched parents: %v vs %v", p1, p2)
257	}
258}
259
260func TestMountAndRegister(t *testing.T) {
261	ls, _, cleanup := newTestStore(t)
262	defer cleanup()
263
264	li := initWithFiles(newTestFile("testfile.txt", []byte("some test data"), 0644))
265	layer, err := createLayer(ls, "", li)
266	if err != nil {
267		t.Fatal(err)
268	}
269
270	size, _ := layer.Size()
271	t.Logf("Layer size: %d", size)
272
273	mount2, err := ls.CreateRWLayer("new-test-mount", layer.ChainID(), nil)
274	if err != nil {
275		t.Fatal(err)
276	}
277
278	path2, err := mount2.Mount("")
279	if err != nil {
280		t.Fatal(err)
281	}
282
283	b, err := driver.ReadFile(path2, path2.Join(path2.Path(), "testfile.txt"))
284	if err != nil {
285		t.Fatal(err)
286	}
287
288	if expected := "some test data"; string(b) != expected {
289		t.Fatalf("Wrong file data, expected %q, got %q", expected, string(b))
290	}
291
292	if err := mount2.Unmount(); err != nil {
293		t.Fatal(err)
294	}
295
296	if _, err := ls.ReleaseRWLayer(mount2); err != nil {
297		t.Fatal(err)
298	}
299}
300
301func TestLayerRelease(t *testing.T) {
302	// TODO Windows: Figure out why this is failing
303	if runtime.GOOS == "windows" {
304		t.Skip("Failing on Windows")
305	}
306	ls, _, cleanup := newTestStore(t)
307	defer cleanup()
308
309	layer1, err := createLayer(ls, "", initWithFiles(newTestFile("layer1.txt", []byte("layer 1 file"), 0644)))
310	if err != nil {
311		t.Fatal(err)
312	}
313
314	layer2, err := createLayer(ls, layer1.ChainID(), initWithFiles(newTestFile("layer2.txt", []byte("layer 2 file"), 0644)))
315	if err != nil {
316		t.Fatal(err)
317	}
318
319	if _, err := ls.Release(layer1); err != nil {
320		t.Fatal(err)
321	}
322
323	layer3a, err := createLayer(ls, layer2.ChainID(), initWithFiles(newTestFile("layer3.txt", []byte("layer 3a file"), 0644)))
324	if err != nil {
325		t.Fatal(err)
326	}
327
328	layer3b, err := createLayer(ls, layer2.ChainID(), initWithFiles(newTestFile("layer3.txt", []byte("layer 3b file"), 0644)))
329	if err != nil {
330		t.Fatal(err)
331	}
332
333	if _, err := ls.Release(layer2); err != nil {
334		t.Fatal(err)
335	}
336
337	t.Logf("Layer1:  %s", layer1.ChainID())
338	t.Logf("Layer2:  %s", layer2.ChainID())
339	t.Logf("Layer3a: %s", layer3a.ChainID())
340	t.Logf("Layer3b: %s", layer3b.ChainID())
341
342	if expected := 4; len(ls.(*layerStore).layerMap) != expected {
343		t.Fatalf("Unexpected number of layers %d, expected %d", len(ls.(*layerStore).layerMap), expected)
344	}
345
346	releaseAndCheckDeleted(t, ls, layer3b, layer3b)
347	releaseAndCheckDeleted(t, ls, layer3a, layer3a, layer2, layer1)
348}
349
350func TestStoreRestore(t *testing.T) {
351	// TODO Windows: Figure out why this is failing
352	if runtime.GOOS == "windows" {
353		t.Skip("Failing on Windows")
354	}
355	ls, _, cleanup := newTestStore(t)
356	defer cleanup()
357
358	layer1, err := createLayer(ls, "", initWithFiles(newTestFile("layer1.txt", []byte("layer 1 file"), 0644)))
359	if err != nil {
360		t.Fatal(err)
361	}
362
363	layer2, err := createLayer(ls, layer1.ChainID(), initWithFiles(newTestFile("layer2.txt", []byte("layer 2 file"), 0644)))
364	if err != nil {
365		t.Fatal(err)
366	}
367
368	if _, err := ls.Release(layer1); err != nil {
369		t.Fatal(err)
370	}
371
372	layer3, err := createLayer(ls, layer2.ChainID(), initWithFiles(newTestFile("layer3.txt", []byte("layer 3 file"), 0644)))
373	if err != nil {
374		t.Fatal(err)
375	}
376
377	if _, err := ls.Release(layer2); err != nil {
378		t.Fatal(err)
379	}
380
381	m, err := ls.CreateRWLayer("some-mount_name", layer3.ChainID(), nil)
382	if err != nil {
383		t.Fatal(err)
384	}
385
386	pathFS, err := m.Mount("")
387	if err != nil {
388		t.Fatal(err)
389	}
390
391	if err := driver.WriteFile(pathFS, pathFS.Join(pathFS.Path(), "testfile.txt"), []byte("nothing here"), 0644); err != nil {
392		t.Fatal(err)
393	}
394
395	if err := m.Unmount(); err != nil {
396		t.Fatal(err)
397	}
398
399	ls2, err := newStoreFromGraphDriver(ls.(*layerStore).store.root, ls.(*layerStore).driver, runtime.GOOS)
400	if err != nil {
401		t.Fatal(err)
402	}
403
404	layer3b, err := ls2.Get(layer3.ChainID())
405	if err != nil {
406		t.Fatal(err)
407	}
408
409	assertLayerEqual(t, layer3b, layer3)
410
411	// Create again with same name, should return error
412	if _, err := ls2.CreateRWLayer("some-mount_name", layer3b.ChainID(), nil); err == nil {
413		t.Fatal("Expected error creating mount with same name")
414	} else if err != ErrMountNameConflict {
415		t.Fatal(err)
416	}
417
418	m2, err := ls2.GetRWLayer("some-mount_name")
419	if err != nil {
420		t.Fatal(err)
421	}
422
423	if mountPath, err := m2.Mount(""); err != nil {
424		t.Fatal(err)
425	} else if pathFS.Path() != mountPath.Path() {
426		t.Fatalf("Unexpected path %s, expected %s", mountPath.Path(), pathFS.Path())
427	}
428
429	if mountPath, err := m2.Mount(""); err != nil {
430		t.Fatal(err)
431	} else if pathFS.Path() != mountPath.Path() {
432		t.Fatalf("Unexpected path %s, expected %s", mountPath.Path(), pathFS.Path())
433	}
434	if err := m2.Unmount(); err != nil {
435		t.Fatal(err)
436	}
437
438	b, err := driver.ReadFile(pathFS, pathFS.Join(pathFS.Path(), "testfile.txt"))
439	if err != nil {
440		t.Fatal(err)
441	}
442	if expected := "nothing here"; string(b) != expected {
443		t.Fatalf("Unexpected content %q, expected %q", string(b), expected)
444	}
445
446	if err := m2.Unmount(); err != nil {
447		t.Fatal(err)
448	}
449
450	if metadata, err := ls2.ReleaseRWLayer(m2); err != nil {
451		t.Fatal(err)
452	} else if len(metadata) != 0 {
453		t.Fatalf("Unexpectedly deleted layers: %#v", metadata)
454	}
455
456	if metadata, err := ls2.ReleaseRWLayer(m2); err != nil {
457		t.Fatal(err)
458	} else if len(metadata) != 0 {
459		t.Fatalf("Unexpectedly deleted layers: %#v", metadata)
460	}
461
462	releaseAndCheckDeleted(t, ls2, layer3b, layer3, layer2, layer1)
463}
464
465func TestTarStreamStability(t *testing.T) {
466	// TODO Windows: Figure out why this is failing
467	if runtime.GOOS == "windows" {
468		t.Skip("Failing on Windows")
469	}
470	ls, _, cleanup := newTestStore(t)
471	defer cleanup()
472
473	files1 := []FileApplier{
474		newTestFile("/etc/hosts", []byte("mydomain 10.0.0.1"), 0644),
475		newTestFile("/etc/profile", []byte("PATH=/usr/bin"), 0644),
476	}
477	addedFile := newTestFile("/etc/shadow", []byte("root:::::::"), 0644)
478	files2 := []FileApplier{
479		newTestFile("/etc/hosts", []byte("mydomain 10.0.0.2"), 0644),
480		newTestFile("/etc/profile", []byte("PATH=/usr/bin"), 0664),
481		newTestFile("/root/.bashrc", []byte("PATH=/usr/sbin:/usr/bin"), 0644),
482	}
483
484	tar1, err := tarFromFiles(files1...)
485	if err != nil {
486		t.Fatal(err)
487	}
488
489	tar2, err := tarFromFiles(files2...)
490	if err != nil {
491		t.Fatal(err)
492	}
493
494	layer1, err := ls.Register(bytes.NewReader(tar1), "")
495	if err != nil {
496		t.Fatal(err)
497	}
498
499	// hack layer to add file
500	p, err := ls.(*layerStore).driver.Get(layer1.(*referencedCacheLayer).cacheID, "")
501	if err != nil {
502		t.Fatal(err)
503	}
504
505	if err := addedFile.ApplyFile(p); err != nil {
506		t.Fatal(err)
507	}
508
509	if err := ls.(*layerStore).driver.Put(layer1.(*referencedCacheLayer).cacheID); err != nil {
510		t.Fatal(err)
511	}
512
513	layer2, err := ls.Register(bytes.NewReader(tar2), layer1.ChainID())
514	if err != nil {
515		t.Fatal(err)
516	}
517
518	id1 := layer1.ChainID()
519	t.Logf("Layer 1: %s", layer1.ChainID())
520	t.Logf("Layer 2: %s", layer2.ChainID())
521
522	if _, err := ls.Release(layer1); err != nil {
523		t.Fatal(err)
524	}
525
526	assertLayerDiff(t, tar2, layer2)
527
528	layer1b, err := ls.Get(id1)
529	if err != nil {
530		t.Logf("Content of layer map: %#v", ls.(*layerStore).layerMap)
531		t.Fatal(err)
532	}
533
534	if _, err := ls.Release(layer2); err != nil {
535		t.Fatal(err)
536	}
537
538	assertLayerDiff(t, tar1, layer1b)
539
540	if _, err := ls.Release(layer1b); err != nil {
541		t.Fatal(err)
542	}
543}
544
545func assertLayerDiff(t *testing.T, expected []byte, layer Layer) {
546	expectedDigest := digest.FromBytes(expected)
547
548	if digest.Digest(layer.DiffID()) != expectedDigest {
549		t.Fatalf("Mismatched diff id for %s, got %s, expected %s", layer.ChainID(), layer.DiffID(), expected)
550	}
551
552	ts, err := layer.TarStream()
553	if err != nil {
554		t.Fatal(err)
555	}
556	defer ts.Close()
557
558	actual, err := ioutil.ReadAll(ts)
559	if err != nil {
560		t.Fatal(err)
561	}
562
563	if len(actual) != len(expected) {
564		logByteDiff(t, actual, expected)
565		t.Fatalf("Mismatched tar stream size for %s, got %d, expected %d", layer.ChainID(), len(actual), len(expected))
566	}
567
568	actualDigest := digest.FromBytes(actual)
569
570	if actualDigest != expectedDigest {
571		logByteDiff(t, actual, expected)
572		t.Fatalf("Wrong digest of tar stream, got %s, expected %s", actualDigest, expectedDigest)
573	}
574}
575
576const maxByteLog = 4 * 1024
577
578func logByteDiff(t *testing.T, actual, expected []byte) {
579	d1, d2 := byteDiff(actual, expected)
580	if len(d1) == 0 && len(d2) == 0 {
581		return
582	}
583
584	prefix := len(actual) - len(d1)
585	if len(d1) > maxByteLog || len(d2) > maxByteLog {
586		t.Logf("Byte diff after %d matching bytes", prefix)
587	} else {
588		t.Logf("Byte diff after %d matching bytes\nActual bytes after prefix:\n%x\nExpected bytes after prefix:\n%x", prefix, d1, d2)
589	}
590}
591
592// byteDiff returns the differing bytes after the matching prefix
593func byteDiff(b1, b2 []byte) ([]byte, []byte) {
594	i := 0
595	for i < len(b1) && i < len(b2) {
596		if b1[i] != b2[i] {
597			break
598		}
599		i++
600	}
601
602	return b1[i:], b2[i:]
603}
604
605func tarFromFiles(files ...FileApplier) ([]byte, error) {
606	td, err := ioutil.TempDir("", "tar-")
607	if err != nil {
608		return nil, err
609	}
610	defer os.RemoveAll(td)
611
612	for _, f := range files {
613		if err := f.ApplyFile(containerfs.NewLocalContainerFS(td)); err != nil {
614			return nil, err
615		}
616	}
617
618	r, err := archive.Tar(td, archive.Uncompressed)
619	if err != nil {
620		return nil, err
621	}
622
623	buf := bytes.NewBuffer(nil)
624	if _, err := io.Copy(buf, r); err != nil {
625		return nil, err
626	}
627
628	return buf.Bytes(), nil
629}
630
631// assertReferences asserts that all the references are to the same
632// image and represent the full set of references to that image.
633func assertReferences(t *testing.T, references ...Layer) {
634	if len(references) == 0 {
635		return
636	}
637	base := references[0].(*referencedCacheLayer).roLayer
638	seenReferences := map[Layer]struct{}{
639		references[0]: {},
640	}
641	for i := 1; i < len(references); i++ {
642		other := references[i].(*referencedCacheLayer).roLayer
643		if base != other {
644			t.Fatalf("Unexpected referenced cache layer %s, expecting %s", other.ChainID(), base.ChainID())
645		}
646		if _, ok := base.references[references[i]]; !ok {
647			t.Fatalf("Reference not part of reference list: %v", references[i])
648		}
649		if _, ok := seenReferences[references[i]]; ok {
650			t.Fatalf("Duplicated reference %v", references[i])
651		}
652	}
653	if rc := len(base.references); rc != len(references) {
654		t.Fatalf("Unexpected number of references %d, expecting %d", rc, len(references))
655	}
656}
657
658func TestRegisterExistingLayer(t *testing.T) {
659	ls, _, cleanup := newTestStore(t)
660	defer cleanup()
661
662	baseFiles := []FileApplier{
663		newTestFile("/etc/profile", []byte("# Base configuration"), 0644),
664	}
665
666	layerFiles := []FileApplier{
667		newTestFile("/root/.bashrc", []byte("# Root configuration"), 0644),
668	}
669
670	li := initWithFiles(baseFiles...)
671	layer1, err := createLayer(ls, "", li)
672	if err != nil {
673		t.Fatal(err)
674	}
675
676	tar1, err := tarFromFiles(layerFiles...)
677	if err != nil {
678		t.Fatal(err)
679	}
680
681	layer2a, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID())
682	if err != nil {
683		t.Fatal(err)
684	}
685
686	layer2b, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID())
687	if err != nil {
688		t.Fatal(err)
689	}
690
691	assertReferences(t, layer2a, layer2b)
692}
693
694func TestTarStreamVerification(t *testing.T) {
695	// TODO Windows: Figure out why this is failing
696	if runtime.GOOS == "windows" {
697		t.Skip("Failing on Windows")
698	}
699	ls, tmpdir, cleanup := newTestStore(t)
700	defer cleanup()
701
702	files1 := []FileApplier{
703		newTestFile("/foo", []byte("abc"), 0644),
704		newTestFile("/bar", []byte("def"), 0644),
705	}
706	files2 := []FileApplier{
707		newTestFile("/foo", []byte("abc"), 0644),
708		newTestFile("/bar", []byte("def"), 0600), // different perm
709	}
710
711	tar1, err := tarFromFiles(files1...)
712	if err != nil {
713		t.Fatal(err)
714	}
715
716	tar2, err := tarFromFiles(files2...)
717	if err != nil {
718		t.Fatal(err)
719	}
720
721	layer1, err := ls.Register(bytes.NewReader(tar1), "")
722	if err != nil {
723		t.Fatal(err)
724	}
725
726	layer2, err := ls.Register(bytes.NewReader(tar2), "")
727	if err != nil {
728		t.Fatal(err)
729	}
730	id1 := digest.Digest(layer1.ChainID())
731	id2 := digest.Digest(layer2.ChainID())
732
733	// Replace tar data files
734	src, err := os.Open(filepath.Join(tmpdir, id1.Algorithm().String(), id1.Hex(), "tar-split.json.gz"))
735	if err != nil {
736		t.Fatal(err)
737	}
738	defer src.Close()
739
740	dst, err := os.Create(filepath.Join(tmpdir, id2.Algorithm().String(), id2.Hex(), "tar-split.json.gz"))
741	if err != nil {
742		t.Fatal(err)
743	}
744	defer dst.Close()
745
746	if _, err := io.Copy(dst, src); err != nil {
747		t.Fatal(err)
748	}
749
750	src.Sync()
751	dst.Sync()
752
753	ts, err := layer2.TarStream()
754	if err != nil {
755		t.Fatal(err)
756	}
757	_, err = io.Copy(ioutil.Discard, ts)
758	if err == nil {
759		t.Fatal("expected data verification to fail")
760	}
761	if !strings.Contains(err.Error(), "could not verify layer data") {
762		t.Fatalf("wrong error returned from tarstream: %q", err)
763	}
764}
765