Home | History | Annotate | Download | only in tar
      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