github.com/mizzy/docker@v1.5.0/pkg/archive/archive_test.go (about) 1 package archive 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "os/exec" 10 "path" 11 "path/filepath" 12 "strings" 13 "syscall" 14 "testing" 15 "time" 16 17 "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" 18 ) 19 20 func TestCmdStreamLargeStderr(t *testing.T) { 21 cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello") 22 out, err := CmdStream(cmd, nil) 23 if err != nil { 24 t.Fatalf("Failed to start command: %s", err) 25 } 26 errCh := make(chan error) 27 go func() { 28 _, err := io.Copy(ioutil.Discard, out) 29 errCh <- err 30 }() 31 select { 32 case err := <-errCh: 33 if err != nil { 34 t.Fatalf("Command should not have failed (err=%.100s...)", err) 35 } 36 case <-time.After(5 * time.Second): 37 t.Fatalf("Command did not complete in 5 seconds; probable deadlock") 38 } 39 } 40 41 func TestCmdStreamBad(t *testing.T) { 42 badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1") 43 out, err := CmdStream(badCmd, nil) 44 if err != nil { 45 t.Fatalf("Failed to start command: %s", err) 46 } 47 if output, err := ioutil.ReadAll(out); err == nil { 48 t.Fatalf("Command should have failed") 49 } else if err.Error() != "exit status 1: error couldn't reverse the phase pulser\n" { 50 t.Fatalf("Wrong error value (%s)", err) 51 } else if s := string(output); s != "hello\n" { 52 t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output) 53 } 54 } 55 56 func TestCmdStreamGood(t *testing.T) { 57 cmd := exec.Command("/bin/sh", "-c", "echo hello; exit 0") 58 out, err := CmdStream(cmd, nil) 59 if err != nil { 60 t.Fatal(err) 61 } 62 if output, err := ioutil.ReadAll(out); err != nil { 63 t.Fatalf("Command should not have failed (err=%s)", err) 64 } else if s := string(output); s != "hello\n" { 65 t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output) 66 } 67 } 68 69 func TestTarFiles(t *testing.T) { 70 // try without hardlinks 71 if err := checkNoChanges(1000, false); err != nil { 72 t.Fatal(err) 73 } 74 // try with hardlinks 75 if err := checkNoChanges(1000, true); err != nil { 76 t.Fatal(err) 77 } 78 } 79 80 func checkNoChanges(fileNum int, hardlinks bool) error { 81 srcDir, err := ioutil.TempDir("", "docker-test-srcDir") 82 if err != nil { 83 return err 84 } 85 defer os.RemoveAll(srcDir) 86 87 destDir, err := ioutil.TempDir("", "docker-test-destDir") 88 if err != nil { 89 return err 90 } 91 defer os.RemoveAll(destDir) 92 93 _, err = prepareUntarSourceDirectory(fileNum, srcDir, hardlinks) 94 if err != nil { 95 return err 96 } 97 98 err = TarUntar(srcDir, destDir) 99 if err != nil { 100 return err 101 } 102 103 changes, err := ChangesDirs(destDir, srcDir) 104 if err != nil { 105 return err 106 } 107 if len(changes) > 0 { 108 return fmt.Errorf("with %d files and %v hardlinks: expected 0 changes, got %d", fileNum, hardlinks, len(changes)) 109 } 110 return nil 111 } 112 113 func tarUntar(t *testing.T, origin string, options *TarOptions) ([]Change, error) { 114 archive, err := TarWithOptions(origin, options) 115 if err != nil { 116 t.Fatal(err) 117 } 118 defer archive.Close() 119 120 buf := make([]byte, 10) 121 if _, err := archive.Read(buf); err != nil { 122 return nil, err 123 } 124 wrap := io.MultiReader(bytes.NewReader(buf), archive) 125 126 detectedCompression := DetectCompression(buf) 127 compression := options.Compression 128 if detectedCompression.Extension() != compression.Extension() { 129 return nil, fmt.Errorf("Wrong compression detected. Actual compression: %s, found %s", compression.Extension(), detectedCompression.Extension()) 130 } 131 132 tmp, err := ioutil.TempDir("", "docker-test-untar") 133 if err != nil { 134 return nil, err 135 } 136 defer os.RemoveAll(tmp) 137 if err := Untar(wrap, tmp, nil); err != nil { 138 return nil, err 139 } 140 if _, err := os.Stat(tmp); err != nil { 141 return nil, err 142 } 143 144 return ChangesDirs(origin, tmp) 145 } 146 147 func TestTarUntar(t *testing.T) { 148 origin, err := ioutil.TempDir("", "docker-test-untar-origin") 149 if err != nil { 150 t.Fatal(err) 151 } 152 defer os.RemoveAll(origin) 153 if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil { 154 t.Fatal(err) 155 } 156 if err := ioutil.WriteFile(path.Join(origin, "2"), []byte("welcome!"), 0700); err != nil { 157 t.Fatal(err) 158 } 159 if err := ioutil.WriteFile(path.Join(origin, "3"), []byte("will be ignored"), 0700); err != nil { 160 t.Fatal(err) 161 } 162 163 for _, c := range []Compression{ 164 Uncompressed, 165 Gzip, 166 } { 167 changes, err := tarUntar(t, origin, &TarOptions{ 168 Compression: c, 169 ExcludePatterns: []string{"3"}, 170 }) 171 172 if err != nil { 173 t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err) 174 } 175 176 if len(changes) != 1 || changes[0].Path != "/3" { 177 t.Fatalf("Unexpected differences after tarUntar: %v", changes) 178 } 179 } 180 } 181 182 func TestTarWithOptions(t *testing.T) { 183 origin, err := ioutil.TempDir("", "docker-test-untar-origin") 184 if err != nil { 185 t.Fatal(err) 186 } 187 defer os.RemoveAll(origin) 188 if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil { 189 t.Fatal(err) 190 } 191 if err := ioutil.WriteFile(path.Join(origin, "2"), []byte("welcome!"), 0700); err != nil { 192 t.Fatal(err) 193 } 194 195 cases := []struct { 196 opts *TarOptions 197 numChanges int 198 }{ 199 {&TarOptions{IncludeFiles: []string{"1"}}, 1}, 200 {&TarOptions{ExcludePatterns: []string{"2"}}, 1}, 201 } 202 for _, testCase := range cases { 203 changes, err := tarUntar(t, origin, testCase.opts) 204 if err != nil { 205 t.Fatalf("Error tar/untar when testing inclusion/exclusion: %s", err) 206 } 207 if len(changes) != testCase.numChanges { 208 t.Errorf("Expected %d changes, got %d for %+v:", 209 testCase.numChanges, len(changes), testCase.opts) 210 } 211 } 212 } 213 214 // Some tar archives such as http://haproxy.1wt.eu/download/1.5/src/devel/haproxy-1.5-dev21.tar.gz 215 // use PAX Global Extended Headers. 216 // Failing prevents the archives from being uncompressed during ADD 217 func TestTypeXGlobalHeaderDoesNotFail(t *testing.T) { 218 hdr := tar.Header{Typeflag: tar.TypeXGlobalHeader} 219 tmpDir, err := ioutil.TempDir("", "docker-test-archive-pax-test") 220 if err != nil { 221 t.Fatal(err) 222 } 223 defer os.RemoveAll(tmpDir) 224 err = createTarFile(filepath.Join(tmpDir, "pax_global_header"), tmpDir, &hdr, nil, true) 225 if err != nil { 226 t.Fatal(err) 227 } 228 } 229 230 // Some tar have both GNU specific (huge uid) and Ustar specific (long name) things. 231 // Not supposed to happen (should use PAX instead of Ustar for long name) but it does and it should still work. 232 func TestUntarUstarGnuConflict(t *testing.T) { 233 f, err := os.Open("testdata/broken.tar") 234 if err != nil { 235 t.Fatal(err) 236 } 237 found := false 238 tr := tar.NewReader(f) 239 // Iterate through the files in the archive. 240 for { 241 hdr, err := tr.Next() 242 if err == io.EOF { 243 // end of tar archive 244 break 245 } 246 if err != nil { 247 t.Fatal(err) 248 } 249 if hdr.Name == "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm" { 250 found = true 251 break 252 } 253 } 254 if !found { 255 t.Fatalf("%s not found in the archive", "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm") 256 } 257 } 258 259 func TestTarWithHardLink(t *testing.T) { 260 origin, err := ioutil.TempDir("", "docker-test-tar-hardlink") 261 if err != nil { 262 t.Fatal(err) 263 } 264 defer os.RemoveAll(origin) 265 if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil { 266 t.Fatal(err) 267 } 268 if err := os.Link(path.Join(origin, "1"), path.Join(origin, "2")); err != nil { 269 t.Fatal(err) 270 } 271 272 var i1, i2 uint64 273 if i1, err = getNlink(path.Join(origin, "1")); err != nil { 274 t.Fatal(err) 275 } 276 // sanity check that we can hardlink 277 if i1 != 2 { 278 t.Skipf("skipping since hardlinks don't work here; expected 2 links, got %d", i1) 279 } 280 281 dest, err := ioutil.TempDir("", "docker-test-tar-hardlink-dest") 282 if err != nil { 283 t.Fatal(err) 284 } 285 defer os.RemoveAll(dest) 286 287 // we'll do this in two steps to separate failure 288 fh, err := Tar(origin, Uncompressed) 289 if err != nil { 290 t.Fatal(err) 291 } 292 293 // ensure we can read the whole thing with no error, before writing back out 294 buf, err := ioutil.ReadAll(fh) 295 if err != nil { 296 t.Fatal(err) 297 } 298 299 bRdr := bytes.NewReader(buf) 300 err = Untar(bRdr, dest, &TarOptions{Compression: Uncompressed}) 301 if err != nil { 302 t.Fatal(err) 303 } 304 305 if i1, err = getInode(path.Join(dest, "1")); err != nil { 306 t.Fatal(err) 307 } 308 if i2, err = getInode(path.Join(dest, "2")); err != nil { 309 t.Fatal(err) 310 } 311 312 if i1 != i2 { 313 t.Errorf("expected matching inodes, but got %d and %d", i1, i2) 314 } 315 } 316 317 func getNlink(path string) (uint64, error) { 318 stat, err := os.Stat(path) 319 if err != nil { 320 return 0, err 321 } 322 statT, ok := stat.Sys().(*syscall.Stat_t) 323 if !ok { 324 return 0, fmt.Errorf("expected type *syscall.Stat_t, got %t", stat.Sys()) 325 } 326 return statT.Nlink, nil 327 } 328 329 func getInode(path string) (uint64, error) { 330 stat, err := os.Stat(path) 331 if err != nil { 332 return 0, err 333 } 334 statT, ok := stat.Sys().(*syscall.Stat_t) 335 if !ok { 336 return 0, fmt.Errorf("expected type *syscall.Stat_t, got %t", stat.Sys()) 337 } 338 return statT.Ino, nil 339 } 340 341 func prepareUntarSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) { 342 fileData := []byte("fooo") 343 for n := 0; n < numberOfFiles; n++ { 344 fileName := fmt.Sprintf("file-%d", n) 345 if err := ioutil.WriteFile(path.Join(targetPath, fileName), fileData, 0700); err != nil { 346 return 0, err 347 } 348 if makeLinks { 349 if err := os.Link(path.Join(targetPath, fileName), path.Join(targetPath, fileName+"-link")); err != nil { 350 return 0, err 351 } 352 } 353 } 354 totalSize := numberOfFiles * len(fileData) 355 return totalSize, nil 356 } 357 358 func BenchmarkTarUntar(b *testing.B) { 359 origin, err := ioutil.TempDir("", "docker-test-untar-origin") 360 if err != nil { 361 b.Fatal(err) 362 } 363 tempDir, err := ioutil.TempDir("", "docker-test-untar-destination") 364 if err != nil { 365 b.Fatal(err) 366 } 367 target := path.Join(tempDir, "dest") 368 n, err := prepareUntarSourceDirectory(100, origin, false) 369 if err != nil { 370 b.Fatal(err) 371 } 372 defer os.RemoveAll(origin) 373 defer os.RemoveAll(tempDir) 374 375 b.ResetTimer() 376 b.SetBytes(int64(n)) 377 for n := 0; n < b.N; n++ { 378 err := TarUntar(origin, target) 379 if err != nil { 380 b.Fatal(err) 381 } 382 os.RemoveAll(target) 383 } 384 } 385 386 func BenchmarkTarUntarWithLinks(b *testing.B) { 387 origin, err := ioutil.TempDir("", "docker-test-untar-origin") 388 if err != nil { 389 b.Fatal(err) 390 } 391 tempDir, err := ioutil.TempDir("", "docker-test-untar-destination") 392 if err != nil { 393 b.Fatal(err) 394 } 395 target := path.Join(tempDir, "dest") 396 n, err := prepareUntarSourceDirectory(100, origin, true) 397 if err != nil { 398 b.Fatal(err) 399 } 400 defer os.RemoveAll(origin) 401 defer os.RemoveAll(tempDir) 402 403 b.ResetTimer() 404 b.SetBytes(int64(n)) 405 for n := 0; n < b.N; n++ { 406 err := TarUntar(origin, target) 407 if err != nil { 408 b.Fatal(err) 409 } 410 os.RemoveAll(target) 411 } 412 } 413 414 func TestUntarInvalidFilenames(t *testing.T) { 415 for i, headers := range [][]*tar.Header{ 416 { 417 { 418 Name: "../victim/dotdot", 419 Typeflag: tar.TypeReg, 420 Mode: 0644, 421 }, 422 }, 423 { 424 { 425 // Note the leading slash 426 Name: "/../victim/slash-dotdot", 427 Typeflag: tar.TypeReg, 428 Mode: 0644, 429 }, 430 }, 431 } { 432 if err := testBreakout("untar", "docker-TestUntarInvalidFilenames", headers); err != nil { 433 t.Fatalf("i=%d. %v", i, err) 434 } 435 } 436 } 437 438 func TestUntarInvalidHardlink(t *testing.T) { 439 for i, headers := range [][]*tar.Header{ 440 { // try reading victim/hello (../) 441 { 442 Name: "dotdot", 443 Typeflag: tar.TypeLink, 444 Linkname: "../victim/hello", 445 Mode: 0644, 446 }, 447 }, 448 { // try reading victim/hello (/../) 449 { 450 Name: "slash-dotdot", 451 Typeflag: tar.TypeLink, 452 // Note the leading slash 453 Linkname: "/../victim/hello", 454 Mode: 0644, 455 }, 456 }, 457 { // try writing victim/file 458 { 459 Name: "loophole-victim", 460 Typeflag: tar.TypeLink, 461 Linkname: "../victim", 462 Mode: 0755, 463 }, 464 { 465 Name: "loophole-victim/file", 466 Typeflag: tar.TypeReg, 467 Mode: 0644, 468 }, 469 }, 470 { // try reading victim/hello (hardlink, symlink) 471 { 472 Name: "loophole-victim", 473 Typeflag: tar.TypeLink, 474 Linkname: "../victim", 475 Mode: 0755, 476 }, 477 { 478 Name: "symlink", 479 Typeflag: tar.TypeSymlink, 480 Linkname: "loophole-victim/hello", 481 Mode: 0644, 482 }, 483 }, 484 { // Try reading victim/hello (hardlink, hardlink) 485 { 486 Name: "loophole-victim", 487 Typeflag: tar.TypeLink, 488 Linkname: "../victim", 489 Mode: 0755, 490 }, 491 { 492 Name: "hardlink", 493 Typeflag: tar.TypeLink, 494 Linkname: "loophole-victim/hello", 495 Mode: 0644, 496 }, 497 }, 498 { // Try removing victim directory (hardlink) 499 { 500 Name: "loophole-victim", 501 Typeflag: tar.TypeLink, 502 Linkname: "../victim", 503 Mode: 0755, 504 }, 505 { 506 Name: "loophole-victim", 507 Typeflag: tar.TypeReg, 508 Mode: 0644, 509 }, 510 }, 511 } { 512 if err := testBreakout("untar", "docker-TestUntarInvalidHardlink", headers); err != nil { 513 t.Fatalf("i=%d. %v", i, err) 514 } 515 } 516 } 517 518 func TestUntarInvalidSymlink(t *testing.T) { 519 for i, headers := range [][]*tar.Header{ 520 { // try reading victim/hello (../) 521 { 522 Name: "dotdot", 523 Typeflag: tar.TypeSymlink, 524 Linkname: "../victim/hello", 525 Mode: 0644, 526 }, 527 }, 528 { // try reading victim/hello (/../) 529 { 530 Name: "slash-dotdot", 531 Typeflag: tar.TypeSymlink, 532 // Note the leading slash 533 Linkname: "/../victim/hello", 534 Mode: 0644, 535 }, 536 }, 537 { // try writing victim/file 538 { 539 Name: "loophole-victim", 540 Typeflag: tar.TypeSymlink, 541 Linkname: "../victim", 542 Mode: 0755, 543 }, 544 { 545 Name: "loophole-victim/file", 546 Typeflag: tar.TypeReg, 547 Mode: 0644, 548 }, 549 }, 550 { // try reading victim/hello (symlink, symlink) 551 { 552 Name: "loophole-victim", 553 Typeflag: tar.TypeSymlink, 554 Linkname: "../victim", 555 Mode: 0755, 556 }, 557 { 558 Name: "symlink", 559 Typeflag: tar.TypeSymlink, 560 Linkname: "loophole-victim/hello", 561 Mode: 0644, 562 }, 563 }, 564 { // try reading victim/hello (symlink, hardlink) 565 { 566 Name: "loophole-victim", 567 Typeflag: tar.TypeSymlink, 568 Linkname: "../victim", 569 Mode: 0755, 570 }, 571 { 572 Name: "hardlink", 573 Typeflag: tar.TypeLink, 574 Linkname: "loophole-victim/hello", 575 Mode: 0644, 576 }, 577 }, 578 { // try removing victim directory (symlink) 579 { 580 Name: "loophole-victim", 581 Typeflag: tar.TypeSymlink, 582 Linkname: "../victim", 583 Mode: 0755, 584 }, 585 { 586 Name: "loophole-victim", 587 Typeflag: tar.TypeReg, 588 Mode: 0644, 589 }, 590 }, 591 { // try writing to victim/newdir/newfile with a symlink in the path 592 { 593 // this header needs to be before the next one, or else there is an error 594 Name: "dir/loophole", 595 Typeflag: tar.TypeSymlink, 596 Linkname: "../../victim", 597 Mode: 0755, 598 }, 599 { 600 Name: "dir/loophole/newdir/newfile", 601 Typeflag: tar.TypeReg, 602 Mode: 0644, 603 }, 604 }, 605 } { 606 if err := testBreakout("untar", "docker-TestUntarInvalidSymlink", headers); err != nil { 607 t.Fatalf("i=%d. %v", i, err) 608 } 609 } 610 } 611 612 func TestTempArchiveCloseMultipleTimes(t *testing.T) { 613 reader := ioutil.NopCloser(strings.NewReader("hello")) 614 tempArchive, err := NewTempArchive(reader, "") 615 buf := make([]byte, 10) 616 n, err := tempArchive.Read(buf) 617 if n != 5 { 618 t.Fatalf("Expected to read 5 bytes. Read %d instead", n) 619 } 620 for i := 0; i < 3; i++ { 621 if err = tempArchive.Close(); err != nil { 622 t.Fatalf("i=%d. Unexpected error closing temp archive: %v", i, err) 623 } 624 } 625 }