github.com/containerd/Containerd@v1.4.13/archive/tar_test.go (about) 1 // +build !windows 2 3 /* 4 Copyright The containerd Authors. 5 6 Licensed under the Apache License, Version 2.0 (the "License"); 7 you may not use this file except in compliance with the License. 8 You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17 */ 18 19 package archive 20 21 import ( 22 "archive/tar" 23 "bytes" 24 "context" 25 "fmt" 26 "io" 27 "io/ioutil" 28 "os" 29 "os/exec" 30 "path/filepath" 31 "testing" 32 "time" 33 34 _ "crypto/sha256" 35 36 "github.com/containerd/containerd/archive/tartest" 37 "github.com/containerd/containerd/pkg/testutil" 38 "github.com/containerd/continuity/fs" 39 "github.com/containerd/continuity/fs/fstest" 40 "github.com/pkg/errors" 41 ) 42 43 const tarCmd = "tar" 44 45 // baseApplier creates a basic filesystem layout 46 // with multiple types of files for basic tests. 47 var baseApplier = fstest.Apply( 48 fstest.CreateDir("/etc/", 0755), 49 fstest.CreateFile("/etc/hosts", []byte("127.0.0.1 localhost"), 0644), 50 fstest.Link("/etc/hosts", "/etc/hosts.allow"), 51 fstest.CreateDir("/usr/local/lib", 0755), 52 fstest.CreateFile("/usr/local/lib/libnothing.so", []byte{0x00, 0x00}, 0755), 53 fstest.Symlink("libnothing.so", "/usr/local/lib/libnothing.so.2"), 54 fstest.CreateDir("/home", 0755), 55 fstest.CreateDir("/home/derek", 0700), 56 ) 57 58 func TestUnpack(t *testing.T) { 59 requireTar(t) 60 61 if err := testApply(baseApplier); err != nil { 62 t.Fatalf("Test apply failed: %+v", err) 63 } 64 } 65 66 func TestBaseDiff(t *testing.T) { 67 requireTar(t) 68 69 if err := testBaseDiff(baseApplier); err != nil { 70 t.Fatalf("Test base diff failed: %+v", err) 71 } 72 } 73 74 func TestRelativeSymlinks(t *testing.T) { 75 breakoutLinks := []fstest.Applier{ 76 fstest.Apply( 77 baseApplier, 78 fstest.Symlink("../other", "/home/derek/other"), 79 fstest.Symlink("../../etc", "/home/derek/etc"), 80 fstest.Symlink("up/../../other", "/home/derek/updown"), 81 ), 82 fstest.Apply( 83 baseApplier, 84 fstest.Symlink("../../../breakout", "/home/derek/breakout"), 85 ), 86 fstest.Apply( 87 baseApplier, 88 fstest.Symlink("../../breakout", "/breakout"), 89 ), 90 fstest.Apply( 91 baseApplier, 92 fstest.Symlink("etc/../../upandout", "/breakout"), 93 ), 94 fstest.Apply( 95 baseApplier, 96 fstest.Symlink("derek/../../../downandout", "/home/breakout"), 97 ), 98 fstest.Apply( 99 baseApplier, 100 fstest.Symlink("/etc", "localetc"), 101 ), 102 } 103 104 for _, bo := range breakoutLinks { 105 if err := testDiffApply(bo); err != nil { 106 t.Fatalf("Test apply failed: %+v", err) 107 } 108 } 109 } 110 111 func TestSymlinks(t *testing.T) { 112 links := [][2]fstest.Applier{ 113 { 114 fstest.Apply( 115 fstest.CreateDir("/bin/", 0755), 116 fstest.CreateFile("/bin/superbinary", []byte{0x00, 0x00}, 0755), 117 fstest.Symlink("../bin/superbinary", "/bin/other1"), 118 ), 119 fstest.Apply( 120 fstest.Remove("/bin/other1"), 121 fstest.Symlink("/bin/superbinary", "/bin/other1"), 122 fstest.Symlink("../bin/superbinary", "/bin/other2"), 123 fstest.Symlink("superbinary", "/bin/other3"), 124 ), 125 }, 126 { 127 fstest.Apply( 128 fstest.CreateDir("/bin/", 0755), 129 fstest.CreateDir("/sbin/", 0755), 130 fstest.CreateFile("/sbin/superbinary", []byte{0x00, 0x00}, 0755), 131 fstest.Symlink("/sbin/superbinary", "/bin/superbinary"), 132 fstest.Symlink("../bin/superbinary", "/bin/other1"), 133 ), 134 fstest.Apply( 135 fstest.Remove("/bin/other1"), 136 fstest.Symlink("/bin/superbinary", "/bin/other1"), 137 fstest.Symlink("superbinary", "/bin/other2"), 138 ), 139 }, 140 { 141 fstest.Apply( 142 fstest.CreateDir("/bin/", 0755), 143 fstest.CreateDir("/sbin/", 0755), 144 fstest.CreateFile("/sbin/superbinary", []byte{0x00, 0x00}, 0755), 145 fstest.Symlink("../sbin/superbinary", "/bin/superbinary"), 146 fstest.Symlink("../bin/superbinary", "/bin/other1"), 147 ), 148 fstest.Apply( 149 fstest.Remove("/bin/other1"), 150 fstest.Symlink("/bin/superbinary", "/bin/other1"), 151 ), 152 }, 153 { 154 fstest.Apply( 155 fstest.CreateDir("/bin/", 0755), 156 fstest.CreateFile("/bin/actualbinary", []byte{0x00, 0x00}, 0755), 157 fstest.Symlink("actualbinary", "/bin/superbinary"), 158 fstest.Symlink("../bin/superbinary", "/bin/other1"), 159 fstest.Symlink("superbinary", "/bin/other2"), 160 ), 161 fstest.Apply( 162 fstest.Remove("/bin/other1"), 163 fstest.Remove("/bin/other2"), 164 fstest.Symlink("/bin/superbinary", "/bin/other1"), 165 fstest.Symlink("superbinary", "/bin/other2"), 166 ), 167 }, 168 { 169 fstest.Apply( 170 fstest.CreateDir("/bin/", 0755), 171 fstest.CreateFile("/bin/actualbinary", []byte{0x00, 0x00}, 0755), 172 fstest.Symlink("actualbinary", "/bin/myapp"), 173 ), 174 fstest.Apply( 175 fstest.Remove("/bin/myapp"), 176 fstest.CreateDir("/bin/myapp", 0755), 177 ), 178 }, 179 } 180 181 for i, l := range links { 182 if err := testDiffApply(l[0], l[1]); err != nil { 183 t.Fatalf("Test[%d] apply failed: %+v", i+1, err) 184 } 185 } 186 } 187 188 func TestTarWithXattr(t *testing.T) { 189 testutil.RequiresRoot(t) 190 191 fileXattrExist := func(f1, xattrKey, xattrValue string) func(string) error { 192 return func(root string) error { 193 values, err := getxattr(filepath.Join(root, f1), xattrKey) 194 if err != nil { 195 return err 196 } 197 if xattrValue != string(values) { 198 return fmt.Errorf("file xattrs expect to be %s, actually get %s", xattrValue, values) 199 } 200 return nil 201 } 202 } 203 204 tests := []struct { 205 name string 206 key string 207 value string 208 err error 209 }{ 210 { 211 name: "WithXattrsUser", 212 key: "user.key", 213 value: "value", 214 }, 215 { 216 // security related xattrs need root permission to test 217 name: "WithXattrSelinux", 218 key: "security.selinux", 219 value: "unconfined_u:object_r:default_t:s0\x00", 220 }, 221 } 222 for _, at := range tests { 223 tc := tartest.TarContext{}.WithUIDGID(os.Getuid(), os.Getgid()).WithModTime(time.Now().UTC()).WithXattrs(map[string]string{ 224 at.key: at.value, 225 }) 226 w := tartest.TarAll(tc.File("/file", []byte{}, 0755)) 227 validator := fileXattrExist("file", at.key, at.value) 228 t.Run(at.name, makeWriterToTarTest(w, nil, validator, at.err)) 229 } 230 } 231 232 func TestBreakouts(t *testing.T) { 233 tc := tartest.TarContext{}.WithUIDGID(os.Getuid(), os.Getgid()).WithModTime(time.Now().UTC()) 234 expected := "unbroken" 235 unbrokenCheck := func(root string) error { 236 b, err := ioutil.ReadFile(filepath.Join(root, "etc", "unbroken")) 237 if err != nil { 238 return errors.Wrap(err, "failed to read unbroken") 239 } 240 if string(b) != expected { 241 return errors.Errorf("/etc/unbroken: unexpected value %s, expected %s", b, expected) 242 } 243 return nil 244 } 245 errFileDiff := errors.New("files differ") 246 td, err := ioutil.TempDir("", "test-breakouts-") 247 if err != nil { 248 t.Fatal(err) 249 } 250 defer os.RemoveAll(td) 251 252 isSymlinkFile := func(f string) func(string) error { 253 return func(root string) error { 254 fi, err := os.Lstat(filepath.Join(root, f)) 255 if err != nil { 256 return err 257 } 258 259 if got := fi.Mode() & os.ModeSymlink; got != os.ModeSymlink { 260 return errors.Errorf("%s should be symlink", fi.Name()) 261 } 262 return nil 263 } 264 } 265 266 sameSymlinkFile := func(f1, f2 string) func(string) error { 267 checkF1, checkF2 := isSymlinkFile(f1), isSymlinkFile(f2) 268 return func(root string) error { 269 if err := checkF1(root); err != nil { 270 return err 271 } 272 273 if err := checkF2(root); err != nil { 274 return err 275 } 276 277 t1, err := os.Readlink(filepath.Join(root, f1)) 278 if err != nil { 279 return err 280 } 281 282 t2, err := os.Readlink(filepath.Join(root, f2)) 283 if err != nil { 284 return err 285 } 286 287 if t1 != t2 { 288 return errors.Wrapf(errFileDiff, "%#v and %#v", t1, t2) 289 } 290 return nil 291 } 292 } 293 294 sameFile := func(f1, f2 string) func(string) error { 295 return func(root string) error { 296 p1, err := fs.RootPath(root, f1) 297 if err != nil { 298 return err 299 } 300 p2, err := fs.RootPath(root, f2) 301 if err != nil { 302 return err 303 } 304 s1, err := os.Stat(p1) 305 if err != nil { 306 return err 307 } 308 s2, err := os.Stat(p2) 309 if err != nil { 310 return err 311 } 312 if !os.SameFile(s1, s2) { 313 return errors.Wrapf(errFileDiff, "%#v and %#v", s1, s2) 314 } 315 return nil 316 } 317 } 318 notSameFile := func(f1, f2 string) func(string) error { 319 same := sameFile(f1, f2) 320 return func(root string) error { 321 err := same(root) 322 if err == nil { 323 return errors.New("files are the same, expected diff") 324 } 325 if !errors.Is(err, errFileDiff) { 326 return err 327 } 328 return nil 329 } 330 } 331 fileValue := func(f1 string, content []byte) func(string) error { 332 return func(root string) error { 333 b, err := ioutil.ReadFile(filepath.Join(root, f1)) 334 if err != nil { 335 return err 336 } 337 if !bytes.Equal(b, content) { 338 return errors.Errorf("content differs: expected %v, got %v", content, b) 339 } 340 return nil 341 } 342 } 343 fileNotExists := func(f1 string) func(string) error { 344 return func(root string) error { 345 _, err := os.Lstat(filepath.Join(root, f1)) 346 if err == nil { 347 return errors.New("file exists") 348 } else if !os.IsNotExist(err) { 349 return err 350 } 351 return nil 352 } 353 354 } 355 all := func(funcs ...func(string) error) func(string) error { 356 return func(root string) error { 357 for _, f := range funcs { 358 if err := f(root); err != nil { 359 return err 360 } 361 } 362 return nil 363 } 364 } 365 366 breakouts := []struct { 367 name string 368 w tartest.WriterToTar 369 apply fstest.Applier 370 validator func(string) error 371 err error 372 }{ 373 { 374 name: "SymlinkAbsolute", 375 w: tartest.TarAll( 376 tc.Dir("etc", 0755), 377 tc.Symlink("/etc", "localetc"), 378 tc.File("/localetc/unbroken", []byte(expected), 0644), 379 ), 380 validator: unbrokenCheck, 381 }, 382 { 383 name: "SymlinkUpAndOut", 384 w: tartest.TarAll( 385 tc.Dir("etc", 0755), 386 tc.Dir("dummy", 0755), 387 tc.Symlink("/dummy/../etc", "localetc"), 388 tc.File("/localetc/unbroken", []byte(expected), 0644), 389 ), 390 validator: unbrokenCheck, 391 }, 392 { 393 name: "SymlinkMultipleAbsolute", 394 w: tartest.TarAll( 395 tc.Dir("etc", 0755), 396 tc.Dir("dummy", 0755), 397 tc.Symlink("/etc", "/dummy/etc"), 398 tc.Symlink("/dummy/etc", "localetc"), 399 tc.File("/dummy/etc/unbroken", []byte(expected), 0644), 400 ), 401 validator: unbrokenCheck, 402 }, 403 { 404 name: "SymlinkMultipleRelative", 405 w: tartest.TarAll( 406 tc.Dir("etc", 0755), 407 tc.Dir("dummy", 0755), 408 tc.Symlink("/etc", "/dummy/etc"), 409 tc.Symlink("./dummy/etc", "localetc"), 410 tc.File("/dummy/etc/unbroken", []byte(expected), 0644), 411 ), 412 validator: unbrokenCheck, 413 }, 414 { 415 name: "SymlinkEmptyFile", 416 w: tartest.TarAll( 417 tc.Dir("etc", 0755), 418 tc.File("etc/emptied", []byte("notempty"), 0644), 419 tc.Symlink("/etc", "localetc"), 420 tc.File("/localetc/emptied", []byte{}, 0644), 421 ), 422 validator: func(root string) error { 423 b, err := ioutil.ReadFile(filepath.Join(root, "etc", "emptied")) 424 if err != nil { 425 return errors.Wrap(err, "failed to read unbroken") 426 } 427 if len(b) > 0 { 428 return errors.Errorf("/etc/emptied: non-empty") 429 } 430 return nil 431 }, 432 }, 433 { 434 name: "HardlinkRelative", 435 w: tartest.TarAll( 436 tc.Dir("etc", 0770), 437 tc.File("/etc/passwd", []byte("inside"), 0644), 438 tc.Dir("breakouts", 0755), 439 tc.Symlink("../../etc", "breakouts/d1"), 440 tc.Link("/breakouts/d1/passwd", "breakouts/mypasswd"), 441 ), 442 validator: sameFile("/breakouts/mypasswd", "/etc/passwd"), 443 }, 444 { 445 name: "HardlinkDownAndOut", 446 w: tartest.TarAll( 447 tc.Dir("etc", 0770), 448 tc.File("/etc/passwd", []byte("inside"), 0644), 449 tc.Dir("breakouts", 0755), 450 tc.Dir("downandout", 0755), 451 tc.Symlink("../downandout/../../etc", "breakouts/d1"), 452 tc.Link("/breakouts/d1/passwd", "breakouts/mypasswd"), 453 ), 454 validator: sameFile("/breakouts/mypasswd", "/etc/passwd"), 455 }, 456 { 457 name: "HardlinkAbsolute", 458 w: tartest.TarAll( 459 tc.Dir("etc", 0770), 460 tc.File("/etc/passwd", []byte("inside"), 0644), 461 tc.Symlink("/etc", "localetc"), 462 tc.Link("/localetc/passwd", "localpasswd"), 463 ), 464 validator: sameFile("localpasswd", "/etc/passwd"), 465 }, 466 { 467 name: "HardlinkRelativeLong", 468 w: tartest.TarAll( 469 tc.Dir("etc", 0770), 470 tc.File("/etc/passwd", []byte("inside"), 0644), 471 tc.Symlink("../../../../../../../etc", "localetc"), 472 tc.Link("/localetc/passwd", "localpasswd"), 473 ), 474 validator: sameFile("localpasswd", "/etc/passwd"), 475 }, 476 { 477 name: "HardlinkRelativeUpAndOut", 478 w: tartest.TarAll( 479 tc.Dir("etc", 0770), 480 tc.File("/etc/passwd", []byte("inside"), 0644), 481 tc.Symlink("upandout/../../../etc", "localetc"), 482 tc.Link("/localetc/passwd", "localpasswd"), 483 ), 484 validator: sameFile("localpasswd", "/etc/passwd"), 485 }, 486 { 487 name: "HardlinkDirectRelative", 488 w: tartest.TarAll( 489 tc.Dir("etc", 0770), 490 tc.File("/etc/passwd", []byte("inside"), 0644), 491 tc.Link("../../../../../etc/passwd", "localpasswd"), 492 ), 493 validator: sameFile("localpasswd", "/etc/passwd"), 494 }, 495 { 496 name: "HardlinkDirectAbsolute", 497 w: tartest.TarAll( 498 tc.Dir("etc", 0770), 499 tc.File("/etc/passwd", []byte("inside"), 0644), 500 tc.Link("/etc/passwd", "localpasswd"), 501 ), 502 validator: sameFile("localpasswd", "/etc/passwd"), 503 }, 504 { 505 name: "HardlinkSymlinkBeforeCreateTarget", 506 w: tartest.TarAll( 507 tc.Dir("etc", 0770), 508 tc.Symlink("/etc/passwd", "localpasswd"), 509 tc.Link("localpasswd", "localpasswd-dup"), 510 tc.File("/etc/passwd", []byte("after"), 0644), 511 ), 512 validator: sameFile("localpasswd-dup", "/etc/passwd"), 513 }, 514 { 515 name: "HardlinkSymlinkRelative", 516 w: tartest.TarAll( 517 tc.Dir("etc", 0770), 518 tc.File("/etc/passwd", []byte("inside"), 0644), 519 tc.Symlink("../../../../../etc/passwd", "passwdlink"), 520 tc.Link("/passwdlink", "localpasswd"), 521 ), 522 validator: all( 523 sameSymlinkFile("/localpasswd", "/passwdlink"), 524 sameFile("/localpasswd", "/etc/passwd"), 525 ), 526 }, 527 { 528 name: "HardlinkSymlinkAbsolute", 529 w: tartest.TarAll( 530 tc.Dir("etc", 0770), 531 tc.File("/etc/passwd", []byte("inside"), 0644), 532 tc.Symlink("/etc/passwd", "passwdlink"), 533 tc.Link("/passwdlink", "localpasswd"), 534 ), 535 validator: all( 536 sameSymlinkFile("/localpasswd", "/passwdlink"), 537 sameFile("/localpasswd", "/etc/passwd"), 538 ), 539 }, 540 { 541 name: "SymlinkParentDirectory", 542 w: tartest.TarAll( 543 tc.Dir("etc", 0770), 544 tc.File("/etc/passwd", []byte("inside"), 0644), 545 tc.Symlink("/etc/", ".."), 546 tc.Link("/etc/passwd", "localpasswd"), 547 ), 548 validator: sameFile("/localpasswd", "/etc/passwd"), 549 }, 550 { 551 name: "SymlinkEmptyFilename", 552 w: tartest.TarAll( 553 tc.Dir("etc", 0770), 554 tc.File("/etc/passwd", []byte("inside"), 0644), 555 tc.Symlink("/etc/", ""), 556 tc.Link("/etc/passwd", "localpasswd"), 557 ), 558 validator: sameFile("/localpasswd", "/etc/passwd"), 559 }, 560 { 561 name: "SymlinkParentRelative", 562 w: tartest.TarAll( 563 tc.Dir("etc", 0770), 564 tc.File("/etc/passwd", []byte("inside"), 0644), 565 tc.Symlink("/etc/", "localetc/sub/.."), 566 tc.Link("/etc/passwd", "/localetc/localpasswd"), 567 ), 568 validator: sameFile("/localetc/localpasswd", "/etc/passwd"), 569 }, 570 { 571 name: "SymlinkSlashEnded", 572 w: tartest.TarAll( 573 tc.Dir("etc", 0770), 574 tc.File("/etc/passwd", []byte("inside"), 0644), 575 tc.Dir("localetc/", 0770), 576 tc.Link("/etc/passwd", "/localetc/localpasswd"), 577 ), 578 validator: sameFile("/localetc/localpasswd", "/etc/passwd"), 579 }, 580 { 581 name: "SymlinkOverrideDirectory", 582 apply: fstest.Apply( 583 fstest.CreateDir("/etc/", 0755), 584 fstest.CreateFile("/etc/passwd", []byte("inside"), 0644), 585 fstest.CreateDir("/localetc/", 0755), 586 ), 587 w: tartest.TarAll( 588 tc.Symlink("/etc", "localetc"), 589 tc.Link("/etc/passwd", "/localetc/localpasswd"), 590 ), 591 validator: sameFile("/localetc/localpasswd", "/etc/passwd"), 592 }, 593 { 594 name: "SymlinkOverrideDirectoryRelative", 595 apply: fstest.Apply( 596 fstest.CreateDir("/etc/", 0755), 597 fstest.CreateFile("/etc/passwd", []byte("inside"), 0644), 598 fstest.CreateDir("/localetc/", 0755), 599 ), 600 w: tartest.TarAll( 601 tc.Symlink("../../etc", "localetc"), 602 tc.Link("/etc/passwd", "/localetc/localpasswd"), 603 ), 604 validator: sameFile("/localetc/localpasswd", "/etc/passwd"), 605 }, 606 { 607 name: "DirectoryOverrideSymlink", 608 apply: fstest.Apply( 609 fstest.CreateDir("/etc/", 0755), 610 fstest.CreateFile("/etc/passwd", []byte("inside"), 0644), 611 fstest.Symlink("/etc", "localetc"), 612 ), 613 w: tartest.TarAll( 614 tc.Dir("/localetc/", 0755), 615 tc.Link("/etc/passwd", "/localetc/localpasswd"), 616 ), 617 validator: sameFile("/localetc/localpasswd", "/etc/passwd"), 618 }, 619 { 620 name: "DirectoryOverrideSymlinkAndHardlink", 621 apply: fstest.Apply( 622 fstest.CreateDir("/etc/", 0755), 623 fstest.CreateFile("/etc/passwd", []byte("inside"), 0644), 624 fstest.Symlink("etc", "localetc"), 625 fstest.Link("/etc/passwd", "/localetc/localpasswd"), 626 ), 627 w: tartest.TarAll( 628 tc.Dir("/localetc/", 0755), 629 tc.File("/localetc/localpasswd", []byte("different"), 0644), 630 ), 631 validator: notSameFile("/localetc/localpasswd", "/etc/passwd"), 632 }, 633 { 634 name: "WhiteoutRootParent", 635 apply: fstest.Apply( 636 fstest.CreateDir("/etc/", 0755), 637 fstest.CreateFile("/etc/passwd", []byte("inside"), 0644), 638 ), 639 w: tartest.TarAll( 640 tc.File(".wh...", []byte{}, 0644), // Should wipe out whole directory 641 ), 642 err: errInvalidArchive, 643 }, 644 { 645 name: "WhiteoutParent", 646 apply: fstest.Apply( 647 fstest.CreateDir("/etc/", 0755), 648 fstest.CreateFile("/etc/passwd", []byte("inside"), 0644), 649 ), 650 w: tartest.TarAll( 651 tc.File("etc/.wh...", []byte{}, 0644), 652 ), 653 err: errInvalidArchive, 654 }, 655 { 656 name: "WhiteoutRoot", 657 apply: fstest.Apply( 658 fstest.CreateDir("/etc/", 0755), 659 fstest.CreateFile("/etc/passwd", []byte("inside"), 0644), 660 ), 661 w: tartest.TarAll( 662 tc.File(".wh..", []byte{}, 0644), 663 ), 664 err: errInvalidArchive, 665 }, 666 { 667 name: "WhiteoutCurrentDirectory", 668 apply: fstest.Apply( 669 fstest.CreateDir("/etc/", 0755), 670 fstest.CreateFile("/etc/passwd", []byte("inside"), 0644), 671 ), 672 w: tartest.TarAll( 673 tc.File("etc/.wh..", []byte{}, 0644), // Should wipe out whole directory 674 ), 675 err: errInvalidArchive, 676 }, 677 { 678 name: "WhiteoutSymlink", 679 apply: fstest.Apply( 680 fstest.CreateDir("/etc/", 0755), 681 fstest.CreateFile("/etc/passwd", []byte("all users"), 0644), 682 fstest.Symlink("/etc", "localetc"), 683 ), 684 w: tartest.TarAll( 685 tc.File(".wh.localetc", []byte{}, 0644), // Should wipe out whole directory 686 ), 687 validator: all( 688 fileValue("etc/passwd", []byte("all users")), 689 fileNotExists("localetc"), 690 ), 691 }, 692 { 693 // TODO: This test should change once archive apply is disallowing 694 // symlinks as parents in the name 695 name: "WhiteoutSymlinkPath", 696 apply: fstest.Apply( 697 fstest.CreateDir("/etc/", 0755), 698 fstest.CreateFile("/etc/passwd", []byte("all users"), 0644), 699 fstest.CreateFile("/etc/whitedout", []byte("ahhhh whiteout"), 0644), 700 fstest.Symlink("/etc", "localetc"), 701 ), 702 w: tartest.TarAll( 703 tc.File("localetc/.wh.whitedout", []byte{}, 0644), 704 ), 705 validator: all( 706 fileValue("etc/passwd", []byte("all users")), 707 fileNotExists("etc/whitedout"), 708 ), 709 }, 710 { 711 name: "WhiteoutDirectoryName", 712 apply: fstest.Apply( 713 fstest.CreateDir("/etc/", 0755), 714 fstest.CreateFile("/etc/passwd", []byte("all users"), 0644), 715 fstest.CreateFile("/etc/whitedout", []byte("ahhhh whiteout"), 0644), 716 fstest.Symlink("/etc", "localetc"), 717 ), 718 w: tartest.TarAll( 719 tc.File(".wh.etc/somefile", []byte("non-empty"), 0644), 720 ), 721 validator: all( 722 fileValue("etc/passwd", []byte("all users")), 723 fileValue(".wh.etc/somefile", []byte("non-empty")), 724 ), 725 }, 726 { 727 name: "WhiteoutDeadSymlinkParent", 728 apply: fstest.Apply( 729 fstest.CreateDir("/etc/", 0755), 730 fstest.CreateFile("/etc/passwd", []byte("all users"), 0644), 731 fstest.Symlink("/dne", "localetc"), 732 ), 733 w: tartest.TarAll( 734 tc.File("localetc/.wh.etc", []byte{}, 0644), 735 ), 736 // no-op, remove does not 737 validator: fileValue("etc/passwd", []byte("all users")), 738 }, 739 { 740 name: "WhiteoutRelativePath", 741 apply: fstest.Apply( 742 fstest.CreateDir("/etc/", 0755), 743 fstest.CreateFile("/etc/passwd", []byte("all users"), 0644), 744 fstest.Symlink("/dne", "localetc"), 745 ), 746 w: tartest.TarAll( 747 tc.File("dne/../.wh.etc", []byte{}, 0644), 748 ), 749 // resolution ends up just removing etc 750 validator: fileNotExists("etc/passwd"), 751 }, 752 { 753 754 name: "HardlinkSymlinkChmod", 755 w: func() tartest.WriterToTar { 756 p := filepath.Join(td, "perm400") 757 if err := ioutil.WriteFile(p, []byte("..."), 0400); err != nil { 758 t.Fatal(err) 759 } 760 ep := filepath.Join(td, "also-exists-outside-root") 761 if err := ioutil.WriteFile(ep, []byte("..."), 0640); err != nil { 762 t.Fatal(err) 763 } 764 765 return tartest.TarAll( 766 tc.Symlink(p, ep), 767 tc.Link(ep, "sketchylink"), 768 ) 769 }(), 770 validator: func(string) error { 771 p := filepath.Join(td, "perm400") 772 fi, err := os.Lstat(p) 773 if err != nil { 774 return err 775 } 776 if perm := fi.Mode() & os.ModePerm; perm != 0400 { 777 return errors.Errorf("%s perm changed from 0400 to %04o", p, perm) 778 } 779 return nil 780 }, 781 }, 782 } 783 784 for _, bo := range breakouts { 785 t.Run(bo.name, makeWriterToTarTest(bo.w, bo.apply, bo.validator, bo.err)) 786 } 787 } 788 789 func TestDiffApply(t *testing.T) { 790 fstest.FSSuite(t, diffApplier{}) 791 } 792 793 func TestApplyTar(t *testing.T) { 794 tc := tartest.TarContext{}.WithUIDGID(os.Getuid(), os.Getgid()).WithModTime(time.Now().UTC()) 795 directoriesExist := func(dirs ...string) func(string) error { 796 return func(root string) error { 797 for _, d := range dirs { 798 p, err := fs.RootPath(root, d) 799 if err != nil { 800 return err 801 } 802 if _, err := os.Stat(p); err != nil { 803 return errors.Wrapf(err, "failure checking existence for %v", d) 804 } 805 } 806 return nil 807 } 808 } 809 810 tests := []struct { 811 name string 812 w tartest.WriterToTar 813 apply fstest.Applier 814 validator func(string) error 815 err error 816 }{ 817 { 818 name: "DirectoryCreation", 819 apply: fstest.Apply( 820 fstest.CreateDir("/etc/", 0755), 821 ), 822 w: tartest.TarAll( 823 tc.Dir("/etc/subdir", 0755), 824 tc.Dir("/etc/subdir2/", 0755), 825 tc.Dir("/etc/subdir2/more", 0755), 826 tc.Dir("/other/noparent-1/1", 0755), 827 tc.Dir("/other/noparent-2/2/", 0755), 828 ), 829 validator: directoriesExist( 830 "etc/subdir", 831 "etc/subdir2", 832 "etc/subdir2/more", 833 "other/noparent-1/1", 834 "other/noparent-2/2", 835 ), 836 }, 837 } 838 839 for _, at := range tests { 840 t.Run(at.name, makeWriterToTarTest(at.w, at.apply, at.validator, at.err)) 841 } 842 } 843 844 func testApply(a fstest.Applier) error { 845 td, err := ioutil.TempDir("", "test-apply-") 846 if err != nil { 847 return errors.Wrap(err, "failed to create temp dir") 848 } 849 defer os.RemoveAll(td) 850 dest, err := ioutil.TempDir("", "test-apply-dest-") 851 if err != nil { 852 return errors.Wrap(err, "failed to create temp dir") 853 } 854 defer os.RemoveAll(dest) 855 856 if err := a.Apply(td); err != nil { 857 return errors.Wrap(err, "failed to apply filesystem changes") 858 } 859 860 tarArgs := []string{"c", "-C", td} 861 names, err := readDirNames(td) 862 if err != nil { 863 return errors.Wrap(err, "failed to read directory names") 864 } 865 tarArgs = append(tarArgs, names...) 866 867 cmd := exec.Command(tarCmd, tarArgs...) 868 869 arch, err := cmd.StdoutPipe() 870 if err != nil { 871 return errors.Wrap(err, "failed to create stdout pipe") 872 } 873 874 if err := cmd.Start(); err != nil { 875 return errors.Wrap(err, "failed to start command") 876 } 877 878 if _, err := Apply(context.Background(), dest, arch); err != nil { 879 return errors.Wrap(err, "failed to apply tar stream") 880 } 881 882 return fstest.CheckDirectoryEqual(td, dest) 883 } 884 885 func testBaseDiff(a fstest.Applier) error { 886 td, err := ioutil.TempDir("", "test-base-diff-") 887 if err != nil { 888 return errors.Wrap(err, "failed to create temp dir") 889 } 890 defer os.RemoveAll(td) 891 dest, err := ioutil.TempDir("", "test-base-diff-dest-") 892 if err != nil { 893 return errors.Wrap(err, "failed to create temp dir") 894 } 895 defer os.RemoveAll(dest) 896 897 if err := a.Apply(td); err != nil { 898 return errors.Wrap(err, "failed to apply filesystem changes") 899 } 900 901 arch := Diff(context.Background(), "", td) 902 903 cmd := exec.Command(tarCmd, "x", "-C", dest) 904 cmd.Stdin = arch 905 if err := cmd.Run(); err != nil { 906 return errors.Wrap(err, "tar command failed") 907 } 908 909 return fstest.CheckDirectoryEqual(td, dest) 910 } 911 912 func testDiffApply(appliers ...fstest.Applier) error { 913 td, err := ioutil.TempDir("", "test-diff-apply-") 914 if err != nil { 915 return errors.Wrap(err, "failed to create temp dir") 916 } 917 defer os.RemoveAll(td) 918 dest, err := ioutil.TempDir("", "test-diff-apply-dest-") 919 if err != nil { 920 return errors.Wrap(err, "failed to create temp dir") 921 } 922 defer os.RemoveAll(dest) 923 924 for _, a := range appliers { 925 if err := a.Apply(td); err != nil { 926 return errors.Wrap(err, "failed to apply filesystem changes") 927 } 928 } 929 930 // Apply base changes before diff 931 if len(appliers) > 1 { 932 for _, a := range appliers[:len(appliers)-1] { 933 if err := a.Apply(dest); err != nil { 934 return errors.Wrap(err, "failed to apply base filesystem changes") 935 } 936 } 937 } 938 939 diffBytes, err := ioutil.ReadAll(Diff(context.Background(), dest, td)) 940 if err != nil { 941 return errors.Wrap(err, "failed to create diff") 942 } 943 944 if _, err := Apply(context.Background(), dest, bytes.NewReader(diffBytes)); err != nil { 945 return errors.Wrap(err, "failed to apply tar stream") 946 } 947 948 return fstest.CheckDirectoryEqual(td, dest) 949 } 950 951 func makeWriterToTarTest(wt tartest.WriterToTar, a fstest.Applier, validate func(string) error, applyErr error) func(*testing.T) { 952 return func(t *testing.T) { 953 td, err := ioutil.TempDir("", "test-writer-to-tar-") 954 if err != nil { 955 t.Fatalf("Failed to create temp dir: %v", err) 956 } 957 defer os.RemoveAll(td) 958 959 if a != nil { 960 if err := a.Apply(td); err != nil { 961 t.Fatalf("Failed to apply filesystem to directory: %v", err) 962 } 963 } 964 965 tr := tartest.TarFromWriterTo(wt) 966 967 if _, err := Apply(context.Background(), td, tr); err != nil { 968 if applyErr == nil { 969 t.Fatalf("Failed to apply tar: %v", err) 970 } else if !errors.Is(err, applyErr) { 971 t.Fatalf("Unexpected apply error: %v, expected %v", err, applyErr) 972 } 973 return 974 } else if applyErr != nil { 975 t.Fatalf("Expected apply error, got none: %v", applyErr) 976 } 977 978 if validate != nil { 979 if err := validate(td); err != nil { 980 t.Errorf("Validation failed: %v", err) 981 } 982 983 } 984 985 } 986 } 987 988 func TestDiffTar(t *testing.T) { 989 tests := []struct { 990 name string 991 validators []tarEntryValidator 992 a fstest.Applier 993 b fstest.Applier 994 }{ 995 { 996 name: "EmptyDiff", 997 validators: []tarEntryValidator{}, 998 a: fstest.Apply( 999 fstest.CreateDir("/etc/", 0755), 1000 ), 1001 b: fstest.Apply(), 1002 }, 1003 { 1004 name: "ParentInclusion", 1005 validators: []tarEntryValidator{ 1006 dirEntry("d1/", 0755), 1007 dirEntry("d1/d/", 0700), 1008 dirEntry("d2/", 0770), 1009 fileEntry("d2/f", []byte("ok"), 0644), 1010 }, 1011 a: fstest.Apply( 1012 fstest.CreateDir("/d1/", 0755), 1013 fstest.CreateDir("/d2/", 0770), 1014 ), 1015 b: fstest.Apply( 1016 fstest.CreateDir("/d1/d", 0700), 1017 fstest.CreateFile("/d2/f", []byte("ok"), 0644), 1018 ), 1019 }, 1020 { 1021 name: "HardlinkParentInclusion", 1022 validators: []tarEntryValidator{ 1023 dirEntry("d2/", 0755), 1024 fileEntry("d2/l1", []byte("link me"), 0644), 1025 // d1/f1 and its parent is included after the new link, 1026 // before the new link was included, these files would 1027 // not have been needed 1028 dirEntry("d1/", 0755), 1029 linkEntry("d1/f1", "d2/l1"), 1030 dirEntry("d3/", 0755), 1031 fileEntry("d3/l1", []byte("link me"), 0644), 1032 dirEntry("d4/", 0755), 1033 linkEntry("d4/f1", "d3/l1"), 1034 dirEntry("d6/", 0755), 1035 whiteoutEntry("d6/l1"), 1036 whiteoutEntry("d6/l2"), 1037 }, 1038 a: fstest.Apply( 1039 fstest.CreateDir("/d1/", 0755), 1040 fstest.CreateFile("/d1/f1", []byte("link me"), 0644), 1041 fstest.CreateDir("/d2/", 0755), 1042 fstest.CreateFile("/d2/f1", []byte("link me"), 0644), 1043 fstest.CreateDir("/d3/", 0755), 1044 fstest.CreateDir("/d4/", 0755), 1045 fstest.CreateFile("/d4/f1", []byte("link me"), 0644), 1046 fstest.CreateDir("/d5/", 0755), 1047 fstest.CreateFile("/d5/f1", []byte("link me"), 0644), 1048 fstest.CreateDir("/d6/", 0755), 1049 fstest.Link("/d1/f1", "/d6/l1"), 1050 fstest.Link("/d5/f1", "/d6/l2"), 1051 ), 1052 b: fstest.Apply( 1053 fstest.Link("/d1/f1", "/d2/l1"), 1054 fstest.Link("/d4/f1", "/d3/l1"), 1055 fstest.Remove("/d6/l1"), 1056 fstest.Remove("/d6/l2"), 1057 ), 1058 }, 1059 { 1060 name: "UpdateDirectoryPermission", 1061 validators: []tarEntryValidator{ 1062 dirEntry("d1/", 0777), 1063 dirEntry("d1/d/", 0700), 1064 dirEntry("d2/", 0770), 1065 fileEntry("d2/f", []byte("ok"), 0644), 1066 }, 1067 a: fstest.Apply( 1068 fstest.CreateDir("/d1/", 0755), 1069 fstest.CreateDir("/d2/", 0770), 1070 ), 1071 b: fstest.Apply( 1072 fstest.Chmod("/d1", 0777), 1073 fstest.CreateDir("/d1/d", 0700), 1074 fstest.CreateFile("/d2/f", []byte("ok"), 0644), 1075 ), 1076 }, 1077 { 1078 name: "HardlinkUpdatedParent", 1079 validators: []tarEntryValidator{ 1080 dirEntry("d1/", 0777), 1081 dirEntry("d2/", 0755), 1082 fileEntry("d2/l1", []byte("link me"), 0644), 1083 // d1/f1 is included after the new link, its 1084 // parent has already changed and therefore 1085 // only the linked file is included 1086 linkEntry("d1/f1", "d2/l1"), 1087 dirEntry("d4/", 0777), 1088 fileEntry("d4/l1", []byte("link me"), 0644), 1089 dirEntry("d3/", 0755), 1090 linkEntry("d3/f1", "d4/l1"), 1091 }, 1092 a: fstest.Apply( 1093 fstest.CreateDir("/d1/", 0755), 1094 fstest.CreateFile("/d1/f1", []byte("link me"), 0644), 1095 fstest.CreateDir("/d2/", 0755), 1096 fstest.CreateFile("/d2/f1", []byte("link me"), 0644), 1097 fstest.CreateDir("/d3/", 0755), 1098 fstest.CreateFile("/d3/f1", []byte("link me"), 0644), 1099 fstest.CreateDir("/d4/", 0755), 1100 ), 1101 b: fstest.Apply( 1102 fstest.Chmod("/d1", 0777), 1103 fstest.Link("/d1/f1", "/d2/l1"), 1104 fstest.Chmod("/d4", 0777), 1105 fstest.Link("/d3/f1", "/d4/l1"), 1106 ), 1107 }, 1108 { 1109 name: "WhiteoutIncludesParents", 1110 validators: []tarEntryValidator{ 1111 dirEntry("d1/", 0755), 1112 whiteoutEntry("d1/f1"), 1113 dirEntry("d2/", 0755), 1114 whiteoutEntry("d2/f1"), 1115 fileEntry("d2/f2", []byte("content"), 0777), 1116 dirEntry("d3/", 0755), 1117 whiteoutEntry("d3/f1"), 1118 fileEntry("d3/f2", []byte("content"), 0644), 1119 dirEntry("d4/", 0755), 1120 fileEntry("d4/f0", []byte("content"), 0644), 1121 whiteoutEntry("d4/f1"), 1122 whiteoutEntry("d5"), 1123 }, 1124 a: fstest.Apply( 1125 fstest.CreateDir("/d1/", 0755), 1126 fstest.CreateFile("/d1/f1", []byte("content"), 0644), 1127 fstest.CreateDir("/d2/", 0755), 1128 fstest.CreateFile("/d2/f1", []byte("content"), 0644), 1129 fstest.CreateFile("/d2/f2", []byte("content"), 0644), 1130 fstest.CreateDir("/d3/", 0755), 1131 fstest.CreateFile("/d3/f1", []byte("content"), 0644), 1132 fstest.CreateDir("/d4/", 0755), 1133 fstest.CreateFile("/d4/f1", []byte("content"), 0644), 1134 fstest.CreateDir("/d5/", 0755), 1135 fstest.CreateFile("/d5/f1", []byte("content"), 0644), 1136 ), 1137 b: fstest.Apply( 1138 fstest.Remove("/d1/f1"), 1139 fstest.Remove("/d2/f1"), 1140 fstest.Chmod("/d2/f2", 0777), 1141 fstest.Remove("/d3/f1"), 1142 fstest.CreateFile("/d3/f2", []byte("content"), 0644), 1143 fstest.Remove("/d4/f1"), 1144 fstest.CreateFile("/d4/f0", []byte("content"), 0644), 1145 fstest.RemoveAll("/d5"), 1146 ), 1147 }, 1148 { 1149 name: "WhiteoutParentRemoval", 1150 validators: []tarEntryValidator{ 1151 whiteoutEntry("d1"), 1152 whiteoutEntry("d2"), 1153 dirEntry("d3/", 0755), 1154 }, 1155 a: fstest.Apply( 1156 fstest.CreateDir("/d1/", 0755), 1157 fstest.CreateDir("/d2/", 0755), 1158 fstest.CreateFile("/d2/f1", []byte("content"), 0644), 1159 ), 1160 b: fstest.Apply( 1161 fstest.RemoveAll("/d1"), 1162 fstest.RemoveAll("/d2"), 1163 fstest.CreateDir("/d3/", 0755), 1164 ), 1165 }, 1166 { 1167 name: "IgnoreSockets", 1168 validators: []tarEntryValidator{ 1169 fileEntry("f2", []byte("content"), 0644), 1170 // There should be _no_ socket here, despite the fstest.CreateSocket below 1171 fileEntry("f3", []byte("content"), 0644), 1172 }, 1173 a: fstest.Apply( 1174 fstest.CreateFile("/f1", []byte("content"), 0644), 1175 ), 1176 b: fstest.Apply( 1177 fstest.CreateFile("/f2", []byte("content"), 0644), 1178 fstest.CreateSocket("/s0", 0644), 1179 fstest.CreateFile("/f3", []byte("content"), 0644), 1180 ), 1181 }, 1182 } 1183 1184 for _, at := range tests { 1185 t.Run(at.name, makeDiffTarTest(at.validators, at.a, at.b)) 1186 } 1187 } 1188 1189 type tarEntryValidator func(*tar.Header, []byte) error 1190 1191 func dirEntry(name string, mode int) tarEntryValidator { 1192 return func(hdr *tar.Header, b []byte) error { 1193 if hdr.Typeflag != tar.TypeDir { 1194 return errors.New("not directory type") 1195 } 1196 if hdr.Name != name { 1197 return errors.Errorf("wrong name %q, expected %q", hdr.Name, name) 1198 } 1199 if hdr.Mode != int64(mode) { 1200 return errors.Errorf("wrong mode %o, expected %o", hdr.Mode, mode) 1201 } 1202 return nil 1203 } 1204 } 1205 1206 func fileEntry(name string, expected []byte, mode int) tarEntryValidator { 1207 return func(hdr *tar.Header, b []byte) error { 1208 if hdr.Typeflag != tar.TypeReg { 1209 return errors.New("not file type") 1210 } 1211 if hdr.Name != name { 1212 return errors.Errorf("wrong name %q, expected %q", hdr.Name, name) 1213 } 1214 if hdr.Mode != int64(mode) { 1215 return errors.Errorf("wrong mode %o, expected %o", hdr.Mode, mode) 1216 } 1217 if !bytes.Equal(b, expected) { 1218 return errors.Errorf("different file content") 1219 } 1220 return nil 1221 } 1222 } 1223 1224 func linkEntry(name, link string) tarEntryValidator { 1225 return func(hdr *tar.Header, b []byte) error { 1226 if hdr.Typeflag != tar.TypeLink { 1227 return errors.New("not link type") 1228 } 1229 if hdr.Name != name { 1230 return errors.Errorf("wrong name %q, expected %q", hdr.Name, name) 1231 } 1232 if hdr.Linkname != link { 1233 return errors.Errorf("wrong link %q, expected %q", hdr.Linkname, link) 1234 } 1235 return nil 1236 } 1237 } 1238 1239 func whiteoutEntry(name string) tarEntryValidator { 1240 whiteOutDir := filepath.Dir(name) 1241 whiteOutBase := filepath.Base(name) 1242 whiteOut := filepath.Join(whiteOutDir, whiteoutPrefix+whiteOutBase) 1243 1244 return func(hdr *tar.Header, b []byte) error { 1245 if hdr.Typeflag != tar.TypeReg { 1246 return errors.Errorf("not file type: %q", hdr.Typeflag) 1247 } 1248 if hdr.Name != whiteOut { 1249 return errors.Errorf("wrong name %q, expected whiteout %q", hdr.Name, name) 1250 } 1251 return nil 1252 } 1253 } 1254 1255 func makeDiffTarTest(validators []tarEntryValidator, a, b fstest.Applier) func(*testing.T) { 1256 return func(t *testing.T) { 1257 ad, err := ioutil.TempDir("", "test-make-diff-tar-") 1258 if err != nil { 1259 t.Fatalf("failed to create temp dir: %v", err) 1260 } 1261 defer os.RemoveAll(ad) 1262 if err := a.Apply(ad); err != nil { 1263 t.Fatalf("failed to apply a: %v", err) 1264 } 1265 1266 bd, err := ioutil.TempDir("", "test-make-diff-tar-") 1267 if err != nil { 1268 t.Fatalf("failed to create temp dir: %v", err) 1269 } 1270 defer os.RemoveAll(bd) 1271 if err := fs.CopyDir(bd, ad); err != nil { 1272 t.Fatalf("failed to copy dir: %v", err) 1273 } 1274 if err := b.Apply(bd); err != nil { 1275 t.Fatalf("failed to apply b: %v", err) 1276 } 1277 1278 rc := Diff(context.Background(), ad, bd) 1279 defer rc.Close() 1280 1281 tr := tar.NewReader(rc) 1282 for i := 0; ; i++ { 1283 hdr, err := tr.Next() 1284 if err != nil { 1285 if err == io.EOF { 1286 break 1287 } 1288 t.Fatalf("tar read error: %v", err) 1289 } 1290 var b []byte 1291 if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 { 1292 b, err = ioutil.ReadAll(tr) 1293 if err != nil { 1294 t.Fatalf("tar read file error: %v", err) 1295 } 1296 } 1297 if i >= len(validators) { 1298 t.Fatal("no validator for entry") 1299 } 1300 if err := validators[i](hdr, b); err != nil { 1301 t.Fatalf("tar entry[%d] validation fail: %#v", i, err) 1302 } 1303 } 1304 } 1305 } 1306 1307 type diffApplier struct{} 1308 1309 func (d diffApplier) TestContext(ctx context.Context) (context.Context, func(), error) { 1310 base, err := ioutil.TempDir("", "test-diff-apply-") 1311 if err != nil { 1312 return ctx, nil, errors.Wrap(err, "failed to create temp dir") 1313 } 1314 return context.WithValue(ctx, d, base), func() { 1315 os.RemoveAll(base) 1316 }, nil 1317 } 1318 1319 func (d diffApplier) Apply(ctx context.Context, a fstest.Applier) (string, func(), error) { 1320 base := ctx.Value(d).(string) 1321 1322 applyCopy, err := ioutil.TempDir("", "test-diffapply-apply-copy-") 1323 if err != nil { 1324 return "", nil, errors.Wrap(err, "failed to create temp dir") 1325 } 1326 defer os.RemoveAll(applyCopy) 1327 if err = fs.CopyDir(applyCopy, base); err != nil { 1328 return "", nil, errors.Wrap(err, "failed to copy base") 1329 } 1330 if err := a.Apply(applyCopy); err != nil { 1331 return "", nil, errors.Wrap(err, "failed to apply changes to copy of base") 1332 } 1333 1334 diffBytes, err := ioutil.ReadAll(Diff(ctx, base, applyCopy)) 1335 if err != nil { 1336 return "", nil, errors.Wrap(err, "failed to create diff") 1337 } 1338 1339 if _, err = Apply(ctx, base, bytes.NewReader(diffBytes)); err != nil { 1340 return "", nil, errors.Wrap(err, "failed to apply tar stream") 1341 } 1342 1343 return base, nil, nil 1344 } 1345 1346 func readDirNames(p string) ([]string, error) { 1347 fis, err := ioutil.ReadDir(p) 1348 if err != nil { 1349 return nil, err 1350 } 1351 names := make([]string, len(fis)) 1352 for i, fi := range fis { 1353 names[i] = fi.Name() 1354 } 1355 return names, nil 1356 } 1357 1358 func requireTar(t *testing.T) { 1359 if _, err := exec.LookPath(tarCmd); err != nil { 1360 t.Skipf("%s not found, skipping", tarCmd) 1361 } 1362 }