github.com/d4l3k/go@v0.0.0-20151015000803-65fc379daeda/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  	"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  
   548  func TestSplitUSTARPath(t *testing.T) {
   549  	var sr = strings.Repeat
   550  
   551  	var vectors = []struct {
   552  		input  string // Input path
   553  		prefix string // Expected output prefix
   554  		suffix string // Expected output suffix
   555  		ok     bool   // Split success?
   556  	}{
   557  		{"", "", "", false},
   558  		{"abc", "", "", false},
   559  		{"用戶名", "", "", false},
   560  		{sr("a", fileNameSize), "", "", false},
   561  		{sr("a", fileNameSize) + "/", "", "", false},
   562  		{sr("a", fileNameSize) + "/a", sr("a", fileNameSize), "a", true},
   563  		{sr("a", fileNamePrefixSize) + "/", "", "", false},
   564  		{sr("a", fileNamePrefixSize) + "/a", sr("a", fileNamePrefixSize), "a", true},
   565  		{sr("a", fileNameSize+1), "", "", false},
   566  		{sr("/", fileNameSize+1), sr("/", fileNameSize-1), "/", true},
   567  		{sr("a", fileNamePrefixSize) + "/" + sr("b", fileNameSize),
   568  			sr("a", fileNamePrefixSize), sr("b", fileNameSize), true},
   569  		{sr("a", fileNamePrefixSize) + "//" + sr("b", fileNameSize), "", "", false},
   570  		{sr("a/", fileNameSize), sr("a/", 77) + "a", sr("a/", 22), true},
   571  	}
   572  
   573  	for _, v := range vectors {
   574  		prefix, suffix, ok := splitUSTARPath(v.input)
   575  		if prefix != v.prefix || suffix != v.suffix || ok != v.ok {
   576  			t.Errorf("splitUSTARPath(%q):\ngot  (%q, %q, %v)\nwant (%q, %q, %v)",
   577  				v.input, prefix, suffix, ok, v.prefix, v.suffix, v.ok)
   578  		}
   579  	}
   580  }