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