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