github.com/lalkh/containerd@v1.4.3/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 247 isSymlinkFile := func(f string) func(string) error { 248 return func(root string) error { 249 fi, err := os.Lstat(filepath.Join(root, f)) 250 if err != nil { 251 return err 252 } 253 254 if got := fi.Mode() & os.ModeSymlink; got != os.ModeSymlink { 255 return errors.Errorf("%s should be symlink", fi.Name()) 256 } 257 return nil 258 } 259 } 260 261 sameSymlinkFile := func(f1, f2 string) func(string) error { 262 checkF1, checkF2 := isSymlinkFile(f1), isSymlinkFile(f2) 263 return func(root string) error { 264 if err := checkF1(root); err != nil { 265 return err 266 } 267 268 if err := checkF2(root); err != nil { 269 return err 270 } 271 272 t1, err := os.Readlink(filepath.Join(root, f1)) 273 if err != nil { 274 return err 275 } 276 277 t2, err := os.Readlink(filepath.Join(root, f2)) 278 if err != nil { 279 return err 280 } 281 282 if t1 != t2 { 283 return errors.Wrapf(errFileDiff, "%#v and %#v", t1, t2) 284 } 285 return nil 286 } 287 } 288 289 sameFile := func(f1, f2 string) func(string) error { 290 return func(root string) error { 291 p1, err := fs.RootPath(root, f1) 292 if err != nil { 293 return err 294 } 295 p2, err := fs.RootPath(root, f2) 296 if err != nil { 297 return err 298 } 299 s1, err := os.Stat(p1) 300 if err != nil { 301 return err 302 } 303 s2, err := os.Stat(p2) 304 if err != nil { 305 return err 306 } 307 if !os.SameFile(s1, s2) { 308 return errors.Wrapf(errFileDiff, "%#v and %#v", s1, s2) 309 } 310 return nil 311 } 312 } 313 notSameFile := func(f1, f2 string) func(string) error { 314 same := sameFile(f1, f2) 315 return func(root string) error { 316 err := same(root) 317 if err == nil { 318 return errors.New("files are the same, expected diff") 319 } 320 if !errors.Is(err, errFileDiff) { 321 return err 322 } 323 return nil 324 } 325 } 326 fileValue := func(f1 string, content []byte) func(string) error { 327 return func(root string) error { 328 b, err := ioutil.ReadFile(filepath.Join(root, f1)) 329 if err != nil { 330 return err 331 } 332 if !bytes.Equal(b, content) { 333 return errors.Errorf("content differs: expected %v, got %v", content, b) 334 } 335 return nil 336 } 337 } 338 fileNotExists := func(f1 string) func(string) error { 339 return func(root string) error { 340 _, err := os.Lstat(filepath.Join(root, f1)) 341 if err == nil { 342 return errors.New("file exists") 343 } else if !os.IsNotExist(err) { 344 return err 345 } 346 return nil 347 } 348 349 } 350 all := func(funcs ...func(string) error) func(string) error { 351 return func(root string) error { 352 for _, f := range funcs { 353 if err := f(root); err != nil { 354 return err 355 } 356 } 357 return nil 358 } 359 } 360 361 breakouts := []struct { 362 name string 363 w tartest.WriterToTar 364 apply fstest.Applier 365 validator func(string) error 366 err error 367 }{ 368 { 369 name: "SymlinkAbsolute", 370 w: tartest.TarAll( 371 tc.Dir("etc", 0755), 372 tc.Symlink("/etc", "localetc"), 373 tc.File("/localetc/unbroken", []byte(expected), 0644), 374 ), 375 validator: unbrokenCheck, 376 }, 377 { 378 name: "SymlinkUpAndOut", 379 w: tartest.TarAll( 380 tc.Dir("etc", 0755), 381 tc.Dir("dummy", 0755), 382 tc.Symlink("/dummy/../etc", "localetc"), 383 tc.File("/localetc/unbroken", []byte(expected), 0644), 384 ), 385 validator: unbrokenCheck, 386 }, 387 { 388 name: "SymlinkMultipleAbsolute", 389 w: tartest.TarAll( 390 tc.Dir("etc", 0755), 391 tc.Dir("dummy", 0755), 392 tc.Symlink("/etc", "/dummy/etc"), 393 tc.Symlink("/dummy/etc", "localetc"), 394 tc.File("/dummy/etc/unbroken", []byte(expected), 0644), 395 ), 396 validator: unbrokenCheck, 397 }, 398 { 399 name: "SymlinkMultipleRelative", 400 w: tartest.TarAll( 401 tc.Dir("etc", 0755), 402 tc.Dir("dummy", 0755), 403 tc.Symlink("/etc", "/dummy/etc"), 404 tc.Symlink("./dummy/etc", "localetc"), 405 tc.File("/dummy/etc/unbroken", []byte(expected), 0644), 406 ), 407 validator: unbrokenCheck, 408 }, 409 { 410 name: "SymlinkEmptyFile", 411 w: tartest.TarAll( 412 tc.Dir("etc", 0755), 413 tc.File("etc/emptied", []byte("notempty"), 0644), 414 tc.Symlink("/etc", "localetc"), 415 tc.File("/localetc/emptied", []byte{}, 0644), 416 ), 417 validator: func(root string) error { 418 b, err := ioutil.ReadFile(filepath.Join(root, "etc", "emptied")) 419 if err != nil { 420 return errors.Wrap(err, "failed to read unbroken") 421 } 422 if len(b) > 0 { 423 return errors.Errorf("/etc/emptied: non-empty") 424 } 425 return nil 426 }, 427 }, 428 { 429 name: "HardlinkRelative", 430 w: tartest.TarAll( 431 tc.Dir("etc", 0770), 432 tc.File("/etc/passwd", []byte("inside"), 0644), 433 tc.Dir("breakouts", 0755), 434 tc.Symlink("../../etc", "breakouts/d1"), 435 tc.Link("/breakouts/d1/passwd", "breakouts/mypasswd"), 436 ), 437 validator: sameFile("/breakouts/mypasswd", "/etc/passwd"), 438 }, 439 { 440 name: "HardlinkDownAndOut", 441 w: tartest.TarAll( 442 tc.Dir("etc", 0770), 443 tc.File("/etc/passwd", []byte("inside"), 0644), 444 tc.Dir("breakouts", 0755), 445 tc.Dir("downandout", 0755), 446 tc.Symlink("../downandout/../../etc", "breakouts/d1"), 447 tc.Link("/breakouts/d1/passwd", "breakouts/mypasswd"), 448 ), 449 validator: sameFile("/breakouts/mypasswd", "/etc/passwd"), 450 }, 451 { 452 name: "HardlinkAbsolute", 453 w: tartest.TarAll( 454 tc.Dir("etc", 0770), 455 tc.File("/etc/passwd", []byte("inside"), 0644), 456 tc.Symlink("/etc", "localetc"), 457 tc.Link("/localetc/passwd", "localpasswd"), 458 ), 459 validator: sameFile("localpasswd", "/etc/passwd"), 460 }, 461 { 462 name: "HardlinkRelativeLong", 463 w: tartest.TarAll( 464 tc.Dir("etc", 0770), 465 tc.File("/etc/passwd", []byte("inside"), 0644), 466 tc.Symlink("../../../../../../../etc", "localetc"), 467 tc.Link("/localetc/passwd", "localpasswd"), 468 ), 469 validator: sameFile("localpasswd", "/etc/passwd"), 470 }, 471 { 472 name: "HardlinkRelativeUpAndOut", 473 w: tartest.TarAll( 474 tc.Dir("etc", 0770), 475 tc.File("/etc/passwd", []byte("inside"), 0644), 476 tc.Symlink("upandout/../../../etc", "localetc"), 477 tc.Link("/localetc/passwd", "localpasswd"), 478 ), 479 validator: sameFile("localpasswd", "/etc/passwd"), 480 }, 481 { 482 name: "HardlinkDirectRelative", 483 w: tartest.TarAll( 484 tc.Dir("etc", 0770), 485 tc.File("/etc/passwd", []byte("inside"), 0644), 486 tc.Link("../../../../../etc/passwd", "localpasswd"), 487 ), 488 validator: sameFile("localpasswd", "/etc/passwd"), 489 }, 490 { 491 name: "HardlinkDirectAbsolute", 492 w: tartest.TarAll( 493 tc.Dir("etc", 0770), 494 tc.File("/etc/passwd", []byte("inside"), 0644), 495 tc.Link("/etc/passwd", "localpasswd"), 496 ), 497 validator: sameFile("localpasswd", "/etc/passwd"), 498 }, 499 { 500 name: "HardlinkSymlinkBeforeCreateTarget", 501 w: tartest.TarAll( 502 tc.Dir("etc", 0770), 503 tc.Symlink("/etc/passwd", "localpasswd"), 504 tc.Link("localpasswd", "localpasswd-dup"), 505 tc.File("/etc/passwd", []byte("after"), 0644), 506 ), 507 validator: sameFile("localpasswd-dup", "/etc/passwd"), 508 }, 509 { 510 name: "HardlinkSymlinkRelative", 511 w: tartest.TarAll( 512 tc.Dir("etc", 0770), 513 tc.File("/etc/passwd", []byte("inside"), 0644), 514 tc.Symlink("../../../../../etc/passwd", "passwdlink"), 515 tc.Link("/passwdlink", "localpasswd"), 516 ), 517 validator: all( 518 sameSymlinkFile("/localpasswd", "/passwdlink"), 519 sameFile("/localpasswd", "/etc/passwd"), 520 ), 521 }, 522 { 523 name: "HardlinkSymlinkAbsolute", 524 w: tartest.TarAll( 525 tc.Dir("etc", 0770), 526 tc.File("/etc/passwd", []byte("inside"), 0644), 527 tc.Symlink("/etc/passwd", "passwdlink"), 528 tc.Link("/passwdlink", "localpasswd"), 529 ), 530 validator: all( 531 sameSymlinkFile("/localpasswd", "/passwdlink"), 532 sameFile("/localpasswd", "/etc/passwd"), 533 ), 534 }, 535 { 536 name: "SymlinkParentDirectory", 537 w: tartest.TarAll( 538 tc.Dir("etc", 0770), 539 tc.File("/etc/passwd", []byte("inside"), 0644), 540 tc.Symlink("/etc/", ".."), 541 tc.Link("/etc/passwd", "localpasswd"), 542 ), 543 validator: sameFile("/localpasswd", "/etc/passwd"), 544 }, 545 { 546 name: "SymlinkEmptyFilename", 547 w: tartest.TarAll( 548 tc.Dir("etc", 0770), 549 tc.File("/etc/passwd", []byte("inside"), 0644), 550 tc.Symlink("/etc/", ""), 551 tc.Link("/etc/passwd", "localpasswd"), 552 ), 553 validator: sameFile("/localpasswd", "/etc/passwd"), 554 }, 555 { 556 name: "SymlinkParentRelative", 557 w: tartest.TarAll( 558 tc.Dir("etc", 0770), 559 tc.File("/etc/passwd", []byte("inside"), 0644), 560 tc.Symlink("/etc/", "localetc/sub/.."), 561 tc.Link("/etc/passwd", "/localetc/localpasswd"), 562 ), 563 validator: sameFile("/localetc/localpasswd", "/etc/passwd"), 564 }, 565 { 566 name: "SymlinkSlashEnded", 567 w: tartest.TarAll( 568 tc.Dir("etc", 0770), 569 tc.File("/etc/passwd", []byte("inside"), 0644), 570 tc.Dir("localetc/", 0770), 571 tc.Link("/etc/passwd", "/localetc/localpasswd"), 572 ), 573 validator: sameFile("/localetc/localpasswd", "/etc/passwd"), 574 }, 575 { 576 name: "SymlinkOverrideDirectory", 577 apply: fstest.Apply( 578 fstest.CreateDir("/etc/", 0755), 579 fstest.CreateFile("/etc/passwd", []byte("inside"), 0644), 580 fstest.CreateDir("/localetc/", 0755), 581 ), 582 w: tartest.TarAll( 583 tc.Symlink("/etc", "localetc"), 584 tc.Link("/etc/passwd", "/localetc/localpasswd"), 585 ), 586 validator: sameFile("/localetc/localpasswd", "/etc/passwd"), 587 }, 588 { 589 name: "SymlinkOverrideDirectoryRelative", 590 apply: fstest.Apply( 591 fstest.CreateDir("/etc/", 0755), 592 fstest.CreateFile("/etc/passwd", []byte("inside"), 0644), 593 fstest.CreateDir("/localetc/", 0755), 594 ), 595 w: tartest.TarAll( 596 tc.Symlink("../../etc", "localetc"), 597 tc.Link("/etc/passwd", "/localetc/localpasswd"), 598 ), 599 validator: sameFile("/localetc/localpasswd", "/etc/passwd"), 600 }, 601 { 602 name: "DirectoryOverrideSymlink", 603 apply: fstest.Apply( 604 fstest.CreateDir("/etc/", 0755), 605 fstest.CreateFile("/etc/passwd", []byte("inside"), 0644), 606 fstest.Symlink("/etc", "localetc"), 607 ), 608 w: tartest.TarAll( 609 tc.Dir("/localetc/", 0755), 610 tc.Link("/etc/passwd", "/localetc/localpasswd"), 611 ), 612 validator: sameFile("/localetc/localpasswd", "/etc/passwd"), 613 }, 614 { 615 name: "DirectoryOverrideSymlinkAndHardlink", 616 apply: fstest.Apply( 617 fstest.CreateDir("/etc/", 0755), 618 fstest.CreateFile("/etc/passwd", []byte("inside"), 0644), 619 fstest.Symlink("etc", "localetc"), 620 fstest.Link("/etc/passwd", "/localetc/localpasswd"), 621 ), 622 w: tartest.TarAll( 623 tc.Dir("/localetc/", 0755), 624 tc.File("/localetc/localpasswd", []byte("different"), 0644), 625 ), 626 validator: notSameFile("/localetc/localpasswd", "/etc/passwd"), 627 }, 628 { 629 name: "WhiteoutRootParent", 630 apply: fstest.Apply( 631 fstest.CreateDir("/etc/", 0755), 632 fstest.CreateFile("/etc/passwd", []byte("inside"), 0644), 633 ), 634 w: tartest.TarAll( 635 tc.File(".wh...", []byte{}, 0644), // Should wipe out whole directory 636 ), 637 err: errInvalidArchive, 638 }, 639 { 640 name: "WhiteoutParent", 641 apply: fstest.Apply( 642 fstest.CreateDir("/etc/", 0755), 643 fstest.CreateFile("/etc/passwd", []byte("inside"), 0644), 644 ), 645 w: tartest.TarAll( 646 tc.File("etc/.wh...", []byte{}, 0644), 647 ), 648 err: errInvalidArchive, 649 }, 650 { 651 name: "WhiteoutRoot", 652 apply: fstest.Apply( 653 fstest.CreateDir("/etc/", 0755), 654 fstest.CreateFile("/etc/passwd", []byte("inside"), 0644), 655 ), 656 w: tartest.TarAll( 657 tc.File(".wh..", []byte{}, 0644), 658 ), 659 err: errInvalidArchive, 660 }, 661 { 662 name: "WhiteoutCurrentDirectory", 663 apply: fstest.Apply( 664 fstest.CreateDir("/etc/", 0755), 665 fstest.CreateFile("/etc/passwd", []byte("inside"), 0644), 666 ), 667 w: tartest.TarAll( 668 tc.File("etc/.wh..", []byte{}, 0644), // Should wipe out whole directory 669 ), 670 err: errInvalidArchive, 671 }, 672 { 673 name: "WhiteoutSymlink", 674 apply: fstest.Apply( 675 fstest.CreateDir("/etc/", 0755), 676 fstest.CreateFile("/etc/passwd", []byte("all users"), 0644), 677 fstest.Symlink("/etc", "localetc"), 678 ), 679 w: tartest.TarAll( 680 tc.File(".wh.localetc", []byte{}, 0644), // Should wipe out whole directory 681 ), 682 validator: all( 683 fileValue("etc/passwd", []byte("all users")), 684 fileNotExists("localetc"), 685 ), 686 }, 687 { 688 // TODO: This test should change once archive apply is disallowing 689 // symlinks as parents in the name 690 name: "WhiteoutSymlinkPath", 691 apply: fstest.Apply( 692 fstest.CreateDir("/etc/", 0755), 693 fstest.CreateFile("/etc/passwd", []byte("all users"), 0644), 694 fstest.CreateFile("/etc/whitedout", []byte("ahhhh whiteout"), 0644), 695 fstest.Symlink("/etc", "localetc"), 696 ), 697 w: tartest.TarAll( 698 tc.File("localetc/.wh.whitedout", []byte{}, 0644), 699 ), 700 validator: all( 701 fileValue("etc/passwd", []byte("all users")), 702 fileNotExists("etc/whitedout"), 703 ), 704 }, 705 { 706 name: "WhiteoutDirectoryName", 707 apply: fstest.Apply( 708 fstest.CreateDir("/etc/", 0755), 709 fstest.CreateFile("/etc/passwd", []byte("all users"), 0644), 710 fstest.CreateFile("/etc/whitedout", []byte("ahhhh whiteout"), 0644), 711 fstest.Symlink("/etc", "localetc"), 712 ), 713 w: tartest.TarAll( 714 tc.File(".wh.etc/somefile", []byte("non-empty"), 0644), 715 ), 716 validator: all( 717 fileValue("etc/passwd", []byte("all users")), 718 fileValue(".wh.etc/somefile", []byte("non-empty")), 719 ), 720 }, 721 { 722 name: "WhiteoutDeadSymlinkParent", 723 apply: fstest.Apply( 724 fstest.CreateDir("/etc/", 0755), 725 fstest.CreateFile("/etc/passwd", []byte("all users"), 0644), 726 fstest.Symlink("/dne", "localetc"), 727 ), 728 w: tartest.TarAll( 729 tc.File("localetc/.wh.etc", []byte{}, 0644), 730 ), 731 // no-op, remove does not 732 validator: fileValue("etc/passwd", []byte("all users")), 733 }, 734 { 735 name: "WhiteoutRelativePath", 736 apply: fstest.Apply( 737 fstest.CreateDir("/etc/", 0755), 738 fstest.CreateFile("/etc/passwd", []byte("all users"), 0644), 739 fstest.Symlink("/dne", "localetc"), 740 ), 741 w: tartest.TarAll( 742 tc.File("dne/../.wh.etc", []byte{}, 0644), 743 ), 744 // resolution ends up just removing etc 745 validator: fileNotExists("etc/passwd"), 746 }, 747 } 748 749 for _, bo := range breakouts { 750 t.Run(bo.name, makeWriterToTarTest(bo.w, bo.apply, bo.validator, bo.err)) 751 } 752 } 753 754 func TestDiffApply(t *testing.T) { 755 fstest.FSSuite(t, diffApplier{}) 756 } 757 758 func TestApplyTar(t *testing.T) { 759 tc := tartest.TarContext{}.WithUIDGID(os.Getuid(), os.Getgid()).WithModTime(time.Now().UTC()) 760 directoriesExist := func(dirs ...string) func(string) error { 761 return func(root string) error { 762 for _, d := range dirs { 763 p, err := fs.RootPath(root, d) 764 if err != nil { 765 return err 766 } 767 if _, err := os.Stat(p); err != nil { 768 return errors.Wrapf(err, "failure checking existence for %v", d) 769 } 770 } 771 return nil 772 } 773 } 774 775 tests := []struct { 776 name string 777 w tartest.WriterToTar 778 apply fstest.Applier 779 validator func(string) error 780 err error 781 }{ 782 { 783 name: "DirectoryCreation", 784 apply: fstest.Apply( 785 fstest.CreateDir("/etc/", 0755), 786 ), 787 w: tartest.TarAll( 788 tc.Dir("/etc/subdir", 0755), 789 tc.Dir("/etc/subdir2/", 0755), 790 tc.Dir("/etc/subdir2/more", 0755), 791 tc.Dir("/other/noparent-1/1", 0755), 792 tc.Dir("/other/noparent-2/2/", 0755), 793 ), 794 validator: directoriesExist( 795 "etc/subdir", 796 "etc/subdir2", 797 "etc/subdir2/more", 798 "other/noparent-1/1", 799 "other/noparent-2/2", 800 ), 801 }, 802 } 803 804 for _, at := range tests { 805 t.Run(at.name, makeWriterToTarTest(at.w, at.apply, at.validator, at.err)) 806 } 807 } 808 809 func testApply(a fstest.Applier) error { 810 td, err := ioutil.TempDir("", "test-apply-") 811 if err != nil { 812 return errors.Wrap(err, "failed to create temp dir") 813 } 814 defer os.RemoveAll(td) 815 dest, err := ioutil.TempDir("", "test-apply-dest-") 816 if err != nil { 817 return errors.Wrap(err, "failed to create temp dir") 818 } 819 defer os.RemoveAll(dest) 820 821 if err := a.Apply(td); err != nil { 822 return errors.Wrap(err, "failed to apply filesystem changes") 823 } 824 825 tarArgs := []string{"c", "-C", td} 826 names, err := readDirNames(td) 827 if err != nil { 828 return errors.Wrap(err, "failed to read directory names") 829 } 830 tarArgs = append(tarArgs, names...) 831 832 cmd := exec.Command(tarCmd, tarArgs...) 833 834 arch, err := cmd.StdoutPipe() 835 if err != nil { 836 return errors.Wrap(err, "failed to create stdout pipe") 837 } 838 839 if err := cmd.Start(); err != nil { 840 return errors.Wrap(err, "failed to start command") 841 } 842 843 if _, err := Apply(context.Background(), dest, arch); err != nil { 844 return errors.Wrap(err, "failed to apply tar stream") 845 } 846 847 return fstest.CheckDirectoryEqual(td, dest) 848 } 849 850 func testBaseDiff(a fstest.Applier) error { 851 td, err := ioutil.TempDir("", "test-base-diff-") 852 if err != nil { 853 return errors.Wrap(err, "failed to create temp dir") 854 } 855 defer os.RemoveAll(td) 856 dest, err := ioutil.TempDir("", "test-base-diff-dest-") 857 if err != nil { 858 return errors.Wrap(err, "failed to create temp dir") 859 } 860 defer os.RemoveAll(dest) 861 862 if err := a.Apply(td); err != nil { 863 return errors.Wrap(err, "failed to apply filesystem changes") 864 } 865 866 arch := Diff(context.Background(), "", td) 867 868 cmd := exec.Command(tarCmd, "x", "-C", dest) 869 cmd.Stdin = arch 870 if err := cmd.Run(); err != nil { 871 return errors.Wrap(err, "tar command failed") 872 } 873 874 return fstest.CheckDirectoryEqual(td, dest) 875 } 876 877 func testDiffApply(appliers ...fstest.Applier) error { 878 td, err := ioutil.TempDir("", "test-diff-apply-") 879 if err != nil { 880 return errors.Wrap(err, "failed to create temp dir") 881 } 882 defer os.RemoveAll(td) 883 dest, err := ioutil.TempDir("", "test-diff-apply-dest-") 884 if err != nil { 885 return errors.Wrap(err, "failed to create temp dir") 886 } 887 defer os.RemoveAll(dest) 888 889 for _, a := range appliers { 890 if err := a.Apply(td); err != nil { 891 return errors.Wrap(err, "failed to apply filesystem changes") 892 } 893 } 894 895 // Apply base changes before diff 896 if len(appliers) > 1 { 897 for _, a := range appliers[:len(appliers)-1] { 898 if err := a.Apply(dest); err != nil { 899 return errors.Wrap(err, "failed to apply base filesystem changes") 900 } 901 } 902 } 903 904 diffBytes, err := ioutil.ReadAll(Diff(context.Background(), dest, td)) 905 if err != nil { 906 return errors.Wrap(err, "failed to create diff") 907 } 908 909 if _, err := Apply(context.Background(), dest, bytes.NewReader(diffBytes)); err != nil { 910 return errors.Wrap(err, "failed to apply tar stream") 911 } 912 913 return fstest.CheckDirectoryEqual(td, dest) 914 } 915 916 func makeWriterToTarTest(wt tartest.WriterToTar, a fstest.Applier, validate func(string) error, applyErr error) func(*testing.T) { 917 return func(t *testing.T) { 918 td, err := ioutil.TempDir("", "test-writer-to-tar-") 919 if err != nil { 920 t.Fatalf("Failed to create temp dir: %v", err) 921 } 922 defer os.RemoveAll(td) 923 924 if a != nil { 925 if err := a.Apply(td); err != nil { 926 t.Fatalf("Failed to apply filesystem to directory: %v", err) 927 } 928 } 929 930 tr := tartest.TarFromWriterTo(wt) 931 932 if _, err := Apply(context.Background(), td, tr); err != nil { 933 if applyErr == nil { 934 t.Fatalf("Failed to apply tar: %v", err) 935 } else if !errors.Is(err, applyErr) { 936 t.Fatalf("Unexpected apply error: %v, expected %v", err, applyErr) 937 } 938 return 939 } else if applyErr != nil { 940 t.Fatalf("Expected apply error, got none: %v", applyErr) 941 } 942 943 if validate != nil { 944 if err := validate(td); err != nil { 945 t.Errorf("Validation failed: %v", err) 946 } 947 948 } 949 950 } 951 } 952 953 func TestDiffTar(t *testing.T) { 954 tests := []struct { 955 name string 956 validators []tarEntryValidator 957 a fstest.Applier 958 b fstest.Applier 959 }{ 960 { 961 name: "EmptyDiff", 962 validators: []tarEntryValidator{}, 963 a: fstest.Apply( 964 fstest.CreateDir("/etc/", 0755), 965 ), 966 b: fstest.Apply(), 967 }, 968 { 969 name: "ParentInclusion", 970 validators: []tarEntryValidator{ 971 dirEntry("d1/", 0755), 972 dirEntry("d1/d/", 0700), 973 dirEntry("d2/", 0770), 974 fileEntry("d2/f", []byte("ok"), 0644), 975 }, 976 a: fstest.Apply( 977 fstest.CreateDir("/d1/", 0755), 978 fstest.CreateDir("/d2/", 0770), 979 ), 980 b: fstest.Apply( 981 fstest.CreateDir("/d1/d", 0700), 982 fstest.CreateFile("/d2/f", []byte("ok"), 0644), 983 ), 984 }, 985 { 986 name: "HardlinkParentInclusion", 987 validators: []tarEntryValidator{ 988 dirEntry("d2/", 0755), 989 fileEntry("d2/l1", []byte("link me"), 0644), 990 // d1/f1 and its parent is included after the new link, 991 // before the new link was included, these files would 992 // not have been needed 993 dirEntry("d1/", 0755), 994 linkEntry("d1/f1", "d2/l1"), 995 dirEntry("d3/", 0755), 996 fileEntry("d3/l1", []byte("link me"), 0644), 997 dirEntry("d4/", 0755), 998 linkEntry("d4/f1", "d3/l1"), 999 dirEntry("d6/", 0755), 1000 whiteoutEntry("d6/l1"), 1001 whiteoutEntry("d6/l2"), 1002 }, 1003 a: fstest.Apply( 1004 fstest.CreateDir("/d1/", 0755), 1005 fstest.CreateFile("/d1/f1", []byte("link me"), 0644), 1006 fstest.CreateDir("/d2/", 0755), 1007 fstest.CreateFile("/d2/f1", []byte("link me"), 0644), 1008 fstest.CreateDir("/d3/", 0755), 1009 fstest.CreateDir("/d4/", 0755), 1010 fstest.CreateFile("/d4/f1", []byte("link me"), 0644), 1011 fstest.CreateDir("/d5/", 0755), 1012 fstest.CreateFile("/d5/f1", []byte("link me"), 0644), 1013 fstest.CreateDir("/d6/", 0755), 1014 fstest.Link("/d1/f1", "/d6/l1"), 1015 fstest.Link("/d5/f1", "/d6/l2"), 1016 ), 1017 b: fstest.Apply( 1018 fstest.Link("/d1/f1", "/d2/l1"), 1019 fstest.Link("/d4/f1", "/d3/l1"), 1020 fstest.Remove("/d6/l1"), 1021 fstest.Remove("/d6/l2"), 1022 ), 1023 }, 1024 { 1025 name: "UpdateDirectoryPermission", 1026 validators: []tarEntryValidator{ 1027 dirEntry("d1/", 0777), 1028 dirEntry("d1/d/", 0700), 1029 dirEntry("d2/", 0770), 1030 fileEntry("d2/f", []byte("ok"), 0644), 1031 }, 1032 a: fstest.Apply( 1033 fstest.CreateDir("/d1/", 0755), 1034 fstest.CreateDir("/d2/", 0770), 1035 ), 1036 b: fstest.Apply( 1037 fstest.Chmod("/d1", 0777), 1038 fstest.CreateDir("/d1/d", 0700), 1039 fstest.CreateFile("/d2/f", []byte("ok"), 0644), 1040 ), 1041 }, 1042 { 1043 name: "HardlinkUpdatedParent", 1044 validators: []tarEntryValidator{ 1045 dirEntry("d1/", 0777), 1046 dirEntry("d2/", 0755), 1047 fileEntry("d2/l1", []byte("link me"), 0644), 1048 // d1/f1 is included after the new link, its 1049 // parent has already changed and therefore 1050 // only the linked file is included 1051 linkEntry("d1/f1", "d2/l1"), 1052 dirEntry("d4/", 0777), 1053 fileEntry("d4/l1", []byte("link me"), 0644), 1054 dirEntry("d3/", 0755), 1055 linkEntry("d3/f1", "d4/l1"), 1056 }, 1057 a: fstest.Apply( 1058 fstest.CreateDir("/d1/", 0755), 1059 fstest.CreateFile("/d1/f1", []byte("link me"), 0644), 1060 fstest.CreateDir("/d2/", 0755), 1061 fstest.CreateFile("/d2/f1", []byte("link me"), 0644), 1062 fstest.CreateDir("/d3/", 0755), 1063 fstest.CreateFile("/d3/f1", []byte("link me"), 0644), 1064 fstest.CreateDir("/d4/", 0755), 1065 ), 1066 b: fstest.Apply( 1067 fstest.Chmod("/d1", 0777), 1068 fstest.Link("/d1/f1", "/d2/l1"), 1069 fstest.Chmod("/d4", 0777), 1070 fstest.Link("/d3/f1", "/d4/l1"), 1071 ), 1072 }, 1073 { 1074 name: "WhiteoutIncludesParents", 1075 validators: []tarEntryValidator{ 1076 dirEntry("d1/", 0755), 1077 whiteoutEntry("d1/f1"), 1078 dirEntry("d2/", 0755), 1079 whiteoutEntry("d2/f1"), 1080 fileEntry("d2/f2", []byte("content"), 0777), 1081 dirEntry("d3/", 0755), 1082 whiteoutEntry("d3/f1"), 1083 fileEntry("d3/f2", []byte("content"), 0644), 1084 dirEntry("d4/", 0755), 1085 fileEntry("d4/f0", []byte("content"), 0644), 1086 whiteoutEntry("d4/f1"), 1087 whiteoutEntry("d5"), 1088 }, 1089 a: fstest.Apply( 1090 fstest.CreateDir("/d1/", 0755), 1091 fstest.CreateFile("/d1/f1", []byte("content"), 0644), 1092 fstest.CreateDir("/d2/", 0755), 1093 fstest.CreateFile("/d2/f1", []byte("content"), 0644), 1094 fstest.CreateFile("/d2/f2", []byte("content"), 0644), 1095 fstest.CreateDir("/d3/", 0755), 1096 fstest.CreateFile("/d3/f1", []byte("content"), 0644), 1097 fstest.CreateDir("/d4/", 0755), 1098 fstest.CreateFile("/d4/f1", []byte("content"), 0644), 1099 fstest.CreateDir("/d5/", 0755), 1100 fstest.CreateFile("/d5/f1", []byte("content"), 0644), 1101 ), 1102 b: fstest.Apply( 1103 fstest.Remove("/d1/f1"), 1104 fstest.Remove("/d2/f1"), 1105 fstest.Chmod("/d2/f2", 0777), 1106 fstest.Remove("/d3/f1"), 1107 fstest.CreateFile("/d3/f2", []byte("content"), 0644), 1108 fstest.Remove("/d4/f1"), 1109 fstest.CreateFile("/d4/f0", []byte("content"), 0644), 1110 fstest.RemoveAll("/d5"), 1111 ), 1112 }, 1113 { 1114 name: "WhiteoutParentRemoval", 1115 validators: []tarEntryValidator{ 1116 whiteoutEntry("d1"), 1117 whiteoutEntry("d2"), 1118 dirEntry("d3/", 0755), 1119 }, 1120 a: fstest.Apply( 1121 fstest.CreateDir("/d1/", 0755), 1122 fstest.CreateDir("/d2/", 0755), 1123 fstest.CreateFile("/d2/f1", []byte("content"), 0644), 1124 ), 1125 b: fstest.Apply( 1126 fstest.RemoveAll("/d1"), 1127 fstest.RemoveAll("/d2"), 1128 fstest.CreateDir("/d3/", 0755), 1129 ), 1130 }, 1131 { 1132 name: "IgnoreSockets", 1133 validators: []tarEntryValidator{ 1134 fileEntry("f2", []byte("content"), 0644), 1135 // There should be _no_ socket here, despite the fstest.CreateSocket below 1136 fileEntry("f3", []byte("content"), 0644), 1137 }, 1138 a: fstest.Apply( 1139 fstest.CreateFile("/f1", []byte("content"), 0644), 1140 ), 1141 b: fstest.Apply( 1142 fstest.CreateFile("/f2", []byte("content"), 0644), 1143 fstest.CreateSocket("/s0", 0644), 1144 fstest.CreateFile("/f3", []byte("content"), 0644), 1145 ), 1146 }, 1147 } 1148 1149 for _, at := range tests { 1150 t.Run(at.name, makeDiffTarTest(at.validators, at.a, at.b)) 1151 } 1152 } 1153 1154 type tarEntryValidator func(*tar.Header, []byte) error 1155 1156 func dirEntry(name string, mode int) tarEntryValidator { 1157 return func(hdr *tar.Header, b []byte) error { 1158 if hdr.Typeflag != tar.TypeDir { 1159 return errors.New("not directory type") 1160 } 1161 if hdr.Name != name { 1162 return errors.Errorf("wrong name %q, expected %q", hdr.Name, name) 1163 } 1164 if hdr.Mode != int64(mode) { 1165 return errors.Errorf("wrong mode %o, expected %o", hdr.Mode, mode) 1166 } 1167 return nil 1168 } 1169 } 1170 1171 func fileEntry(name string, expected []byte, mode int) tarEntryValidator { 1172 return func(hdr *tar.Header, b []byte) error { 1173 if hdr.Typeflag != tar.TypeReg { 1174 return errors.New("not file type") 1175 } 1176 if hdr.Name != name { 1177 return errors.Errorf("wrong name %q, expected %q", hdr.Name, name) 1178 } 1179 if hdr.Mode != int64(mode) { 1180 return errors.Errorf("wrong mode %o, expected %o", hdr.Mode, mode) 1181 } 1182 if !bytes.Equal(b, expected) { 1183 return errors.Errorf("different file content") 1184 } 1185 return nil 1186 } 1187 } 1188 1189 func linkEntry(name, link string) tarEntryValidator { 1190 return func(hdr *tar.Header, b []byte) error { 1191 if hdr.Typeflag != tar.TypeLink { 1192 return errors.New("not link type") 1193 } 1194 if hdr.Name != name { 1195 return errors.Errorf("wrong name %q, expected %q", hdr.Name, name) 1196 } 1197 if hdr.Linkname != link { 1198 return errors.Errorf("wrong link %q, expected %q", hdr.Linkname, link) 1199 } 1200 return nil 1201 } 1202 } 1203 1204 func whiteoutEntry(name string) tarEntryValidator { 1205 whiteOutDir := filepath.Dir(name) 1206 whiteOutBase := filepath.Base(name) 1207 whiteOut := filepath.Join(whiteOutDir, whiteoutPrefix+whiteOutBase) 1208 1209 return func(hdr *tar.Header, b []byte) error { 1210 if hdr.Typeflag != tar.TypeReg { 1211 return errors.Errorf("not file type: %q", hdr.Typeflag) 1212 } 1213 if hdr.Name != whiteOut { 1214 return errors.Errorf("wrong name %q, expected whiteout %q", hdr.Name, name) 1215 } 1216 return nil 1217 } 1218 } 1219 1220 func makeDiffTarTest(validators []tarEntryValidator, a, b fstest.Applier) func(*testing.T) { 1221 return func(t *testing.T) { 1222 ad, err := ioutil.TempDir("", "test-make-diff-tar-") 1223 if err != nil { 1224 t.Fatalf("failed to create temp dir: %v", err) 1225 } 1226 defer os.RemoveAll(ad) 1227 if err := a.Apply(ad); err != nil { 1228 t.Fatalf("failed to apply a: %v", err) 1229 } 1230 1231 bd, err := ioutil.TempDir("", "test-make-diff-tar-") 1232 if err != nil { 1233 t.Fatalf("failed to create temp dir: %v", err) 1234 } 1235 defer os.RemoveAll(bd) 1236 if err := fs.CopyDir(bd, ad); err != nil { 1237 t.Fatalf("failed to copy dir: %v", err) 1238 } 1239 if err := b.Apply(bd); err != nil { 1240 t.Fatalf("failed to apply b: %v", err) 1241 } 1242 1243 rc := Diff(context.Background(), ad, bd) 1244 defer rc.Close() 1245 1246 tr := tar.NewReader(rc) 1247 for i := 0; ; i++ { 1248 hdr, err := tr.Next() 1249 if err != nil { 1250 if err == io.EOF { 1251 break 1252 } 1253 t.Fatalf("tar read error: %v", err) 1254 } 1255 var b []byte 1256 if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 { 1257 b, err = ioutil.ReadAll(tr) 1258 if err != nil { 1259 t.Fatalf("tar read file error: %v", err) 1260 } 1261 } 1262 if i >= len(validators) { 1263 t.Fatal("no validator for entry") 1264 } 1265 if err := validators[i](hdr, b); err != nil { 1266 t.Fatalf("tar entry[%d] validation fail: %#v", i, err) 1267 } 1268 } 1269 } 1270 } 1271 1272 type diffApplier struct{} 1273 1274 func (d diffApplier) TestContext(ctx context.Context) (context.Context, func(), error) { 1275 base, err := ioutil.TempDir("", "test-diff-apply-") 1276 if err != nil { 1277 return ctx, nil, errors.Wrap(err, "failed to create temp dir") 1278 } 1279 return context.WithValue(ctx, d, base), func() { 1280 os.RemoveAll(base) 1281 }, nil 1282 } 1283 1284 func (d diffApplier) Apply(ctx context.Context, a fstest.Applier) (string, func(), error) { 1285 base := ctx.Value(d).(string) 1286 1287 applyCopy, err := ioutil.TempDir("", "test-diffapply-apply-copy-") 1288 if err != nil { 1289 return "", nil, errors.Wrap(err, "failed to create temp dir") 1290 } 1291 defer os.RemoveAll(applyCopy) 1292 if err = fs.CopyDir(applyCopy, base); err != nil { 1293 return "", nil, errors.Wrap(err, "failed to copy base") 1294 } 1295 if err := a.Apply(applyCopy); err != nil { 1296 return "", nil, errors.Wrap(err, "failed to apply changes to copy of base") 1297 } 1298 1299 diffBytes, err := ioutil.ReadAll(Diff(ctx, base, applyCopy)) 1300 if err != nil { 1301 return "", nil, errors.Wrap(err, "failed to create diff") 1302 } 1303 1304 if _, err = Apply(ctx, base, bytes.NewReader(diffBytes)); err != nil { 1305 return "", nil, errors.Wrap(err, "failed to apply tar stream") 1306 } 1307 1308 return base, nil, nil 1309 } 1310 1311 func readDirNames(p string) ([]string, error) { 1312 fis, err := ioutil.ReadDir(p) 1313 if err != nil { 1314 return nil, err 1315 } 1316 names := make([]string, len(fis)) 1317 for i, fi := range fis { 1318 names[i] = fi.Name() 1319 } 1320 return names, nil 1321 } 1322 1323 func requireTar(t *testing.T) { 1324 if _, err := exec.LookPath(tarCmd); err != nil { 1325 t.Skipf("%s not found, skipping", tarCmd) 1326 } 1327 }