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