github.com/mdempsky/go@v0.0.0-20151201204031-5dd372bd1e70/src/archive/tar/writer_test.go (about) 1 // Copyright 2009 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package tar 6 7 import ( 8 "bytes" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "os" 13 "reflect" 14 "sort" 15 "strings" 16 "testing" 17 "testing/iotest" 18 "time" 19 ) 20 21 type writerTestEntry struct { 22 header *Header 23 contents string 24 } 25 26 type writerTest struct { 27 file string // filename of expected output 28 entries []*writerTestEntry 29 } 30 31 var writerTests = []*writerTest{ 32 // The writer test file was produced with this command: 33 // tar (GNU tar) 1.26 34 // ln -s small.txt link.txt 35 // tar -b 1 --format=ustar -c -f writer.tar small.txt small2.txt link.txt 36 { 37 file: "testdata/writer.tar", 38 entries: []*writerTestEntry{ 39 { 40 header: &Header{ 41 Name: "small.txt", 42 Mode: 0640, 43 Uid: 73025, 44 Gid: 5000, 45 Size: 5, 46 ModTime: time.Unix(1246508266, 0), 47 Typeflag: '0', 48 Uname: "dsymonds", 49 Gname: "eng", 50 }, 51 contents: "Kilts", 52 }, 53 { 54 header: &Header{ 55 Name: "small2.txt", 56 Mode: 0640, 57 Uid: 73025, 58 Gid: 5000, 59 Size: 11, 60 ModTime: time.Unix(1245217492, 0), 61 Typeflag: '0', 62 Uname: "dsymonds", 63 Gname: "eng", 64 }, 65 contents: "Google.com\n", 66 }, 67 { 68 header: &Header{ 69 Name: "link.txt", 70 Mode: 0777, 71 Uid: 1000, 72 Gid: 1000, 73 Size: 0, 74 ModTime: time.Unix(1314603082, 0), 75 Typeflag: '2', 76 Linkname: "small.txt", 77 Uname: "strings", 78 Gname: "strings", 79 }, 80 // no contents 81 }, 82 }, 83 }, 84 // The truncated test file was produced using these commands: 85 // dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt 86 // tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar 87 { 88 file: "testdata/writer-big.tar", 89 entries: []*writerTestEntry{ 90 { 91 header: &Header{ 92 Name: "tmp/16gig.txt", 93 Mode: 0640, 94 Uid: 73025, 95 Gid: 5000, 96 Size: 16 << 30, 97 ModTime: time.Unix(1254699560, 0), 98 Typeflag: '0', 99 Uname: "dsymonds", 100 Gname: "eng", 101 }, 102 // fake contents 103 contents: strings.Repeat("\x00", 4<<10), 104 }, 105 }, 106 }, 107 // The truncated test file was produced using these commands: 108 // dd if=/dev/zero bs=1048576 count=16384 > (longname/)*15 /16gig.txt 109 // tar -b 1 -c -f- (longname/)*15 /16gig.txt | dd bs=512 count=8 > writer-big-long.tar 110 { 111 file: "testdata/writer-big-long.tar", 112 entries: []*writerTestEntry{ 113 { 114 header: &Header{ 115 Name: strings.Repeat("longname/", 15) + "16gig.txt", 116 Mode: 0644, 117 Uid: 1000, 118 Gid: 1000, 119 Size: 16 << 30, 120 ModTime: time.Unix(1399583047, 0), 121 Typeflag: '0', 122 Uname: "guillaume", 123 Gname: "guillaume", 124 }, 125 // fake contents 126 contents: strings.Repeat("\x00", 4<<10), 127 }, 128 }, 129 }, 130 // This file was produced using gnu tar 1.17 131 // gnutar -b 4 --format=ustar (longname/)*15 + file.txt 132 { 133 file: "testdata/ustar.tar", 134 entries: []*writerTestEntry{ 135 { 136 header: &Header{ 137 Name: strings.Repeat("longname/", 15) + "file.txt", 138 Mode: 0644, 139 Uid: 0765, 140 Gid: 024, 141 Size: 06, 142 ModTime: time.Unix(1360135598, 0), 143 Typeflag: '0', 144 Uname: "shane", 145 Gname: "staff", 146 }, 147 contents: "hello\n", 148 }, 149 }, 150 }, 151 // This file was produced using gnu tar 1.26 152 // echo "Slartibartfast" > file.txt 153 // ln file.txt hard.txt 154 // tar -b 1 --format=ustar -c -f hardlink.tar file.txt hard.txt 155 { 156 file: "testdata/hardlink.tar", 157 entries: []*writerTestEntry{ 158 { 159 header: &Header{ 160 Name: "file.txt", 161 Mode: 0644, 162 Uid: 1000, 163 Gid: 100, 164 Size: 15, 165 ModTime: time.Unix(1425484303, 0), 166 Typeflag: '0', 167 Uname: "vbatts", 168 Gname: "users", 169 }, 170 contents: "Slartibartfast\n", 171 }, 172 { 173 header: &Header{ 174 Name: "hard.txt", 175 Mode: 0644, 176 Uid: 1000, 177 Gid: 100, 178 Size: 0, 179 ModTime: time.Unix(1425484303, 0), 180 Typeflag: '1', 181 Linkname: "file.txt", 182 Uname: "vbatts", 183 Gname: "users", 184 }, 185 // no contents 186 }, 187 }, 188 }, 189 } 190 191 // Render byte array in a two-character hexadecimal string, spaced for easy visual inspection. 192 func bytestr(offset int, b []byte) string { 193 const rowLen = 32 194 s := fmt.Sprintf("%04x ", offset) 195 for _, ch := range b { 196 switch { 197 case '0' <= ch && ch <= '9', 'A' <= ch && ch <= 'Z', 'a' <= ch && ch <= 'z': 198 s += fmt.Sprintf(" %c", ch) 199 default: 200 s += fmt.Sprintf(" %02x", ch) 201 } 202 } 203 return s 204 } 205 206 // Render a pseudo-diff between two blocks of bytes. 207 func bytediff(a []byte, b []byte) string { 208 const rowLen = 32 209 s := fmt.Sprintf("(%d bytes vs. %d bytes)\n", len(a), len(b)) 210 for offset := 0; len(a)+len(b) > 0; offset += rowLen { 211 na, nb := rowLen, rowLen 212 if na > len(a) { 213 na = len(a) 214 } 215 if nb > len(b) { 216 nb = len(b) 217 } 218 sa := bytestr(offset, a[0:na]) 219 sb := bytestr(offset, b[0:nb]) 220 if sa != sb { 221 s += fmt.Sprintf("-%v\n+%v\n", sa, sb) 222 } 223 a = a[na:] 224 b = b[nb:] 225 } 226 return s 227 } 228 229 func TestWriter(t *testing.T) { 230 testLoop: 231 for i, test := range writerTests { 232 expected, err := ioutil.ReadFile(test.file) 233 if err != nil { 234 t.Errorf("test %d: Unexpected error: %v", i, err) 235 continue 236 } 237 238 buf := new(bytes.Buffer) 239 tw := NewWriter(iotest.TruncateWriter(buf, 4<<10)) // only catch the first 4 KB 240 big := false 241 for j, entry := range test.entries { 242 big = big || entry.header.Size > 1<<10 243 if err := tw.WriteHeader(entry.header); err != nil { 244 t.Errorf("test %d, entry %d: Failed writing header: %v", i, j, err) 245 continue testLoop 246 } 247 if _, err := io.WriteString(tw, entry.contents); err != nil { 248 t.Errorf("test %d, entry %d: Failed writing contents: %v", i, j, err) 249 continue testLoop 250 } 251 } 252 // Only interested in Close failures for the small tests. 253 if err := tw.Close(); err != nil && !big { 254 t.Errorf("test %d: Failed closing archive: %v", i, err) 255 continue testLoop 256 } 257 258 actual := buf.Bytes() 259 if !bytes.Equal(expected, actual) { 260 t.Errorf("test %d: Incorrect result: (-=expected, +=actual)\n%v", 261 i, bytediff(expected, actual)) 262 } 263 if testing.Short() { // The second test is expensive. 264 break 265 } 266 } 267 } 268 269 func TestPax(t *testing.T) { 270 // Create an archive with a large name 271 fileinfo, err := os.Stat("testdata/small.txt") 272 if err != nil { 273 t.Fatal(err) 274 } 275 hdr, err := FileInfoHeader(fileinfo, "") 276 if err != nil { 277 t.Fatalf("os.Stat: %v", err) 278 } 279 // Force a PAX long name to be written 280 longName := strings.Repeat("ab", 100) 281 contents := strings.Repeat(" ", int(hdr.Size)) 282 hdr.Name = longName 283 var buf bytes.Buffer 284 writer := NewWriter(&buf) 285 if err := writer.WriteHeader(hdr); err != nil { 286 t.Fatal(err) 287 } 288 if _, err = writer.Write([]byte(contents)); err != nil { 289 t.Fatal(err) 290 } 291 if err := writer.Close(); err != nil { 292 t.Fatal(err) 293 } 294 // Simple test to make sure PAX extensions are in effect 295 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { 296 t.Fatal("Expected at least one PAX header to be written.") 297 } 298 // Test that we can get a long name back out of the archive. 299 reader := NewReader(&buf) 300 hdr, err = reader.Next() 301 if err != nil { 302 t.Fatal(err) 303 } 304 if hdr.Name != longName { 305 t.Fatal("Couldn't recover long file name") 306 } 307 } 308 309 func TestPaxSymlink(t *testing.T) { 310 // Create an archive with a large linkname 311 fileinfo, err := os.Stat("testdata/small.txt") 312 if err != nil { 313 t.Fatal(err) 314 } 315 hdr, err := FileInfoHeader(fileinfo, "") 316 hdr.Typeflag = TypeSymlink 317 if err != nil { 318 t.Fatalf("os.Stat:1 %v", err) 319 } 320 // Force a PAX long linkname to be written 321 longLinkname := strings.Repeat("1234567890/1234567890", 10) 322 hdr.Linkname = longLinkname 323 324 hdr.Size = 0 325 var buf bytes.Buffer 326 writer := NewWriter(&buf) 327 if err := writer.WriteHeader(hdr); err != nil { 328 t.Fatal(err) 329 } 330 if err := writer.Close(); err != nil { 331 t.Fatal(err) 332 } 333 // Simple test to make sure PAX extensions are in effect 334 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { 335 t.Fatal("Expected at least one PAX header to be written.") 336 } 337 // Test that we can get a long name back out of the archive. 338 reader := NewReader(&buf) 339 hdr, err = reader.Next() 340 if err != nil { 341 t.Fatal(err) 342 } 343 if hdr.Linkname != longLinkname { 344 t.Fatal("Couldn't recover long link name") 345 } 346 } 347 348 func TestPaxNonAscii(t *testing.T) { 349 // Create an archive with non ascii. These should trigger a pax header 350 // because pax headers have a defined utf-8 encoding. 351 fileinfo, err := os.Stat("testdata/small.txt") 352 if err != nil { 353 t.Fatal(err) 354 } 355 356 hdr, err := FileInfoHeader(fileinfo, "") 357 if err != nil { 358 t.Fatalf("os.Stat:1 %v", err) 359 } 360 361 // some sample data 362 chineseFilename := "文件名" 363 chineseGroupname := "組" 364 chineseUsername := "用戶名" 365 366 hdr.Name = chineseFilename 367 hdr.Gname = chineseGroupname 368 hdr.Uname = chineseUsername 369 370 contents := strings.Repeat(" ", int(hdr.Size)) 371 372 var buf bytes.Buffer 373 writer := NewWriter(&buf) 374 if err := writer.WriteHeader(hdr); err != nil { 375 t.Fatal(err) 376 } 377 if _, err = writer.Write([]byte(contents)); err != nil { 378 t.Fatal(err) 379 } 380 if err := writer.Close(); err != nil { 381 t.Fatal(err) 382 } 383 // Simple test to make sure PAX extensions are in effect 384 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { 385 t.Fatal("Expected at least one PAX header to be written.") 386 } 387 // Test that we can get a long name back out of the archive. 388 reader := NewReader(&buf) 389 hdr, err = reader.Next() 390 if err != nil { 391 t.Fatal(err) 392 } 393 if hdr.Name != chineseFilename { 394 t.Fatal("Couldn't recover unicode name") 395 } 396 if hdr.Gname != chineseGroupname { 397 t.Fatal("Couldn't recover unicode group") 398 } 399 if hdr.Uname != chineseUsername { 400 t.Fatal("Couldn't recover unicode user") 401 } 402 } 403 404 func TestPaxXattrs(t *testing.T) { 405 xattrs := map[string]string{ 406 "user.key": "value", 407 } 408 409 // Create an archive with an xattr 410 fileinfo, err := os.Stat("testdata/small.txt") 411 if err != nil { 412 t.Fatal(err) 413 } 414 hdr, err := FileInfoHeader(fileinfo, "") 415 if err != nil { 416 t.Fatalf("os.Stat: %v", err) 417 } 418 contents := "Kilts" 419 hdr.Xattrs = xattrs 420 var buf bytes.Buffer 421 writer := NewWriter(&buf) 422 if err := writer.WriteHeader(hdr); err != nil { 423 t.Fatal(err) 424 } 425 if _, err = writer.Write([]byte(contents)); err != nil { 426 t.Fatal(err) 427 } 428 if err := writer.Close(); err != nil { 429 t.Fatal(err) 430 } 431 // Test that we can get the xattrs back out of the archive. 432 reader := NewReader(&buf) 433 hdr, err = reader.Next() 434 if err != nil { 435 t.Fatal(err) 436 } 437 if !reflect.DeepEqual(hdr.Xattrs, xattrs) { 438 t.Fatalf("xattrs did not survive round trip: got %+v, want %+v", 439 hdr.Xattrs, xattrs) 440 } 441 } 442 443 func TestPaxHeadersSorted(t *testing.T) { 444 fileinfo, err := os.Stat("testdata/small.txt") 445 if err != nil { 446 t.Fatal(err) 447 } 448 hdr, err := FileInfoHeader(fileinfo, "") 449 if err != nil { 450 t.Fatalf("os.Stat: %v", err) 451 } 452 contents := strings.Repeat(" ", int(hdr.Size)) 453 454 hdr.Xattrs = map[string]string{ 455 "foo": "foo", 456 "bar": "bar", 457 "baz": "baz", 458 "qux": "qux", 459 } 460 461 var buf bytes.Buffer 462 writer := NewWriter(&buf) 463 if err := writer.WriteHeader(hdr); err != nil { 464 t.Fatal(err) 465 } 466 if _, err = writer.Write([]byte(contents)); err != nil { 467 t.Fatal(err) 468 } 469 if err := writer.Close(); err != nil { 470 t.Fatal(err) 471 } 472 // Simple test to make sure PAX extensions are in effect 473 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { 474 t.Fatal("Expected at least one PAX header to be written.") 475 } 476 477 // xattr bar should always appear before others 478 indices := []int{ 479 bytes.Index(buf.Bytes(), []byte("bar=bar")), 480 bytes.Index(buf.Bytes(), []byte("baz=baz")), 481 bytes.Index(buf.Bytes(), []byte("foo=foo")), 482 bytes.Index(buf.Bytes(), []byte("qux=qux")), 483 } 484 if !sort.IntsAreSorted(indices) { 485 t.Fatal("PAX headers are not sorted") 486 } 487 } 488 489 func TestPAXHeader(t *testing.T) { 490 medName := strings.Repeat("CD", 50) 491 longName := strings.Repeat("AB", 100) 492 paxTests := [][2]string{ 493 {paxPath + "=/etc/hosts", "19 path=/etc/hosts\n"}, 494 {"a=b", "6 a=b\n"}, // Single digit length 495 {"a=names", "11 a=names\n"}, // Test case involving carries 496 {paxPath + "=" + longName, fmt.Sprintf("210 path=%s\n", longName)}, 497 {paxPath + "=" + medName, fmt.Sprintf("110 path=%s\n", medName)}} 498 499 for _, test := range paxTests { 500 key, expected := test[0], test[1] 501 if result := paxHeader(key); result != expected { 502 t.Fatalf("paxHeader: got %s, expected %s", result, expected) 503 } 504 } 505 } 506 507 func TestUSTARLongName(t *testing.T) { 508 // Create an archive with a path that failed to split with USTAR extension in previous versions. 509 fileinfo, err := os.Stat("testdata/small.txt") 510 if err != nil { 511 t.Fatal(err) 512 } 513 hdr, err := FileInfoHeader(fileinfo, "") 514 hdr.Typeflag = TypeDir 515 if err != nil { 516 t.Fatalf("os.Stat:1 %v", err) 517 } 518 // Force a PAX long name to be written. The name was taken from a practical example 519 // that fails and replaced ever char through numbers to anonymize the sample. 520 longName := "/0000_0000000/00000-000000000/0000_0000000/00000-0000000000000/0000_0000000/00000-0000000-00000000/0000_0000000/00000000/0000_0000000/000/0000_0000000/00000000v00/0000_0000000/000000/0000_0000000/0000000/0000_0000000/00000y-00/0000/0000/00000000/0x000000/" 521 hdr.Name = longName 522 523 hdr.Size = 0 524 var buf bytes.Buffer 525 writer := NewWriter(&buf) 526 if err := writer.WriteHeader(hdr); err != nil { 527 t.Fatal(err) 528 } 529 if err := writer.Close(); err != nil { 530 t.Fatal(err) 531 } 532 // Test that we can get a long name back out of the archive. 533 reader := NewReader(&buf) 534 hdr, err = reader.Next() 535 if err != nil { 536 t.Fatal(err) 537 } 538 if hdr.Name != longName { 539 t.Fatal("Couldn't recover long name") 540 } 541 } 542 543 func TestValidTypeflagWithPAXHeader(t *testing.T) { 544 var buffer bytes.Buffer 545 tw := NewWriter(&buffer) 546 547 fileName := strings.Repeat("ab", 100) 548 549 hdr := &Header{ 550 Name: fileName, 551 Size: 4, 552 Typeflag: 0, 553 } 554 if err := tw.WriteHeader(hdr); err != nil { 555 t.Fatalf("Failed to write header: %s", err) 556 } 557 if _, err := tw.Write([]byte("fooo")); err != nil { 558 t.Fatalf("Failed to write the file's data: %s", err) 559 } 560 tw.Close() 561 562 tr := NewReader(&buffer) 563 564 for { 565 header, err := tr.Next() 566 if err == io.EOF { 567 break 568 } 569 if err != nil { 570 t.Fatalf("Failed to read header: %s", err) 571 } 572 if header.Typeflag != 0 { 573 t.Fatalf("Typeflag should've been 0, found %d", header.Typeflag) 574 } 575 } 576 } 577 578 func TestWriteAfterClose(t *testing.T) { 579 var buffer bytes.Buffer 580 tw := NewWriter(&buffer) 581 582 hdr := &Header{ 583 Name: "small.txt", 584 Size: 5, 585 } 586 if err := tw.WriteHeader(hdr); err != nil { 587 t.Fatalf("Failed to write header: %s", err) 588 } 589 tw.Close() 590 if _, err := tw.Write([]byte("Kilts")); err != ErrWriteAfterClose { 591 t.Fatalf("Write: got %v; want ErrWriteAfterClose", err) 592 } 593 } 594 595 func TestSplitUSTARPath(t *testing.T) { 596 var sr = strings.Repeat 597 598 var vectors = []struct { 599 input string // Input path 600 prefix string // Expected output prefix 601 suffix string // Expected output suffix 602 ok bool // Split success? 603 }{ 604 {"", "", "", false}, 605 {"abc", "", "", false}, 606 {"用戶名", "", "", false}, 607 {sr("a", fileNameSize), "", "", false}, 608 {sr("a", fileNameSize) + "/", "", "", false}, 609 {sr("a", fileNameSize) + "/a", sr("a", fileNameSize), "a", true}, 610 {sr("a", fileNamePrefixSize) + "/", "", "", false}, 611 {sr("a", fileNamePrefixSize) + "/a", sr("a", fileNamePrefixSize), "a", true}, 612 {sr("a", fileNameSize+1), "", "", false}, 613 {sr("/", fileNameSize+1), sr("/", fileNameSize-1), "/", true}, 614 {sr("a", fileNamePrefixSize) + "/" + sr("b", fileNameSize), 615 sr("a", fileNamePrefixSize), sr("b", fileNameSize), true}, 616 {sr("a", fileNamePrefixSize) + "//" + sr("b", fileNameSize), "", "", false}, 617 {sr("a/", fileNameSize), sr("a/", 77) + "a", sr("a/", 22), true}, 618 } 619 620 for _, v := range vectors { 621 prefix, suffix, ok := splitUSTARPath(v.input) 622 if prefix != v.prefix || suffix != v.suffix || ok != v.ok { 623 t.Errorf("splitUSTARPath(%q):\ngot (%q, %q, %v)\nwant (%q, %q, %v)", 624 v.input, prefix, suffix, ok, v.prefix, v.suffix, v.ok) 625 } 626 } 627 }