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  }