Source file src/archive/tar/writer_test.go

     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  	"encoding/hex"
    10  	"errors"
    11  	"io"
    12  	"io/fs"
    13  	"maps"
    14  	"os"
    15  	"path"
    16  	"slices"
    17  	"sort"
    18  	"strings"
    19  	"testing"
    20  	"testing/fstest"
    21  	"testing/iotest"
    22  	"time"
    23  )
    24  
    25  func bytediff(a, b []byte) string {
    26  	const (
    27  		uniqueA  = "-  "
    28  		uniqueB  = "+  "
    29  		identity = "   "
    30  	)
    31  	var ss []string
    32  	sa := strings.Split(strings.TrimSpace(hex.Dump(a)), "\n")
    33  	sb := strings.Split(strings.TrimSpace(hex.Dump(b)), "\n")
    34  	for len(sa) > 0 && len(sb) > 0 {
    35  		if sa[0] == sb[0] {
    36  			ss = append(ss, identity+sa[0])
    37  		} else {
    38  			ss = append(ss, uniqueA+sa[0])
    39  			ss = append(ss, uniqueB+sb[0])
    40  		}
    41  		sa, sb = sa[1:], sb[1:]
    42  	}
    43  	for len(sa) > 0 {
    44  		ss = append(ss, uniqueA+sa[0])
    45  		sa = sa[1:]
    46  	}
    47  	for len(sb) > 0 {
    48  		ss = append(ss, uniqueB+sb[0])
    49  		sb = sb[1:]
    50  	}
    51  	return strings.Join(ss, "\n")
    52  }
    53  
    54  func TestWriter(t *testing.T) {
    55  	type (
    56  		testHeader struct { // WriteHeader(hdr) == wantErr
    57  			hdr     Header
    58  			wantErr error
    59  		}
    60  		testWrite struct { // Write(str) == (wantCnt, wantErr)
    61  			str     string
    62  			wantCnt int
    63  			wantErr error
    64  		}
    65  		testReadFrom struct { // ReadFrom(testFile{ops}) == (wantCnt, wantErr)
    66  			ops     fileOps
    67  			wantCnt int64
    68  			wantErr error
    69  		}
    70  		testClose struct { // Close() == wantErr
    71  			wantErr error
    72  		}
    73  		testFnc any // testHeader | testWrite | testReadFrom | testClose
    74  	)
    75  
    76  	vectors := []struct {
    77  		file  string // Optional filename of expected output
    78  		tests []testFnc
    79  	}{{
    80  		// The writer test file was produced with this command:
    81  		// tar (GNU tar) 1.26
    82  		//   ln -s small.txt link.txt
    83  		//   tar -b 1 --format=ustar -c -f writer.tar small.txt small2.txt link.txt
    84  		file: "testdata/writer.tar",
    85  		tests: []testFnc{
    86  			testHeader{Header{
    87  				Typeflag: TypeReg,
    88  				Name:     "small.txt",
    89  				Size:     5,
    90  				Mode:     0640,
    91  				Uid:      73025,
    92  				Gid:      5000,
    93  				Uname:    "dsymonds",
    94  				Gname:    "eng",
    95  				ModTime:  time.Unix(1246508266, 0),
    96  			}, nil},
    97  			testWrite{"Kilts", 5, nil},
    98  
    99  			testHeader{Header{
   100  				Typeflag: TypeReg,
   101  				Name:     "small2.txt",
   102  				Size:     11,
   103  				Mode:     0640,
   104  				Uid:      73025,
   105  				Uname:    "dsymonds",
   106  				Gname:    "eng",
   107  				Gid:      5000,
   108  				ModTime:  time.Unix(1245217492, 0),
   109  			}, nil},
   110  			testWrite{"Google.com\n", 11, nil},
   111  
   112  			testHeader{Header{
   113  				Typeflag: TypeSymlink,
   114  				Name:     "link.txt",
   115  				Linkname: "small.txt",
   116  				Mode:     0777,
   117  				Uid:      1000,
   118  				Gid:      1000,
   119  				Uname:    "strings",
   120  				Gname:    "strings",
   121  				ModTime:  time.Unix(1314603082, 0),
   122  			}, nil},
   123  			testWrite{"", 0, nil},
   124  
   125  			testClose{nil},
   126  		},
   127  	}, {
   128  		// The truncated test file was produced using these commands:
   129  		//   dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt
   130  		//   tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar
   131  		file: "testdata/writer-big.tar",
   132  		tests: []testFnc{
   133  			testHeader{Header{
   134  				Typeflag: TypeReg,
   135  				Name:     "tmp/16gig.txt",
   136  				Size:     16 << 30,
   137  				Mode:     0640,
   138  				Uid:      73025,
   139  				Gid:      5000,
   140  				Uname:    "dsymonds",
   141  				Gname:    "eng",
   142  				ModTime:  time.Unix(1254699560, 0),
   143  				Format:   FormatGNU,
   144  			}, nil},
   145  		},
   146  	}, {
   147  		// This truncated file was produced using this library.
   148  		// It was verified to work with GNU tar 1.27.1 and BSD tar 3.1.2.
   149  		//  dd if=/dev/zero bs=1G count=16 >> writer-big-long.tar
   150  		//  gnutar -xvf writer-big-long.tar
   151  		//  bsdtar -xvf writer-big-long.tar
   152  		//
   153  		// This file is in PAX format.
   154  		file: "testdata/writer-big-long.tar",
   155  		tests: []testFnc{
   156  			testHeader{Header{
   157  				Typeflag: TypeReg,
   158  				Name:     strings.Repeat("longname/", 15) + "16gig.txt",
   159  				Size:     16 << 30,
   160  				Mode:     0644,
   161  				Uid:      1000,
   162  				Gid:      1000,
   163  				Uname:    "guillaume",
   164  				Gname:    "guillaume",
   165  				ModTime:  time.Unix(1399583047, 0),
   166  			}, nil},
   167  		},
   168  	}, {
   169  		// This file was produced using GNU tar v1.17.
   170  		//	gnutar -b 4 --format=ustar (longname/)*15 + file.txt
   171  		file: "testdata/ustar.tar",
   172  		tests: []testFnc{
   173  			testHeader{Header{
   174  				Typeflag: TypeReg,
   175  				Name:     strings.Repeat("longname/", 15) + "file.txt",
   176  				Size:     6,
   177  				Mode:     0644,
   178  				Uid:      501,
   179  				Gid:      20,
   180  				Uname:    "shane",
   181  				Gname:    "staff",
   182  				ModTime:  time.Unix(1360135598, 0),
   183  			}, nil},
   184  			testWrite{"hello\n", 6, nil},
   185  			testClose{nil},
   186  		},
   187  	}, {
   188  		// This file was produced using GNU tar v1.26:
   189  		//	echo "Slartibartfast" > file.txt
   190  		//	ln file.txt hard.txt
   191  		//	tar -b 1 --format=ustar -c -f hardlink.tar file.txt hard.txt
   192  		file: "testdata/hardlink.tar",
   193  		tests: []testFnc{
   194  			testHeader{Header{
   195  				Typeflag: TypeReg,
   196  				Name:     "file.txt",
   197  				Size:     15,
   198  				Mode:     0644,
   199  				Uid:      1000,
   200  				Gid:      100,
   201  				Uname:    "vbatts",
   202  				Gname:    "users",
   203  				ModTime:  time.Unix(1425484303, 0),
   204  			}, nil},
   205  			testWrite{"Slartibartfast\n", 15, nil},
   206  
   207  			testHeader{Header{
   208  				Typeflag: TypeLink,
   209  				Name:     "hard.txt",
   210  				Linkname: "file.txt",
   211  				Mode:     0644,
   212  				Uid:      1000,
   213  				Gid:      100,
   214  				Uname:    "vbatts",
   215  				Gname:    "users",
   216  				ModTime:  time.Unix(1425484303, 0),
   217  			}, nil},
   218  			testWrite{"", 0, nil},
   219  
   220  			testClose{nil},
   221  		},
   222  	}, {
   223  		tests: []testFnc{
   224  			testHeader{Header{
   225  				Typeflag: TypeReg,
   226  				Name:     "bad-null.txt",
   227  				Xattrs:   map[string]string{"null\x00null\x00": "fizzbuzz"},
   228  			}, headerError{}},
   229  		},
   230  	}, {
   231  		tests: []testFnc{
   232  			testHeader{Header{
   233  				Typeflag: TypeReg,
   234  				Name:     "null\x00.txt",
   235  			}, headerError{}},
   236  		},
   237  	}, {
   238  		file: "testdata/pax-records.tar",
   239  		tests: []testFnc{
   240  			testHeader{Header{
   241  				Typeflag: TypeReg,
   242  				Name:     "file",
   243  				Uname:    strings.Repeat("long", 10),
   244  				PAXRecords: map[string]string{
   245  					"path":           "FILE", // Should be ignored
   246  					"GNU.sparse.map": "0,0",  // Should be ignored
   247  					"comment":        "Hello, 世界",
   248  					"GOLANG.pkg":     "tar",
   249  				},
   250  			}, nil},
   251  			testClose{nil},
   252  		},
   253  	}, {
   254  		// Craft a theoretically valid PAX archive with global headers.
   255  		// The GNU and BSD tar tools do not parse these the same way.
   256  		//
   257  		// BSD tar v3.1.2 parses and ignores all global headers;
   258  		// the behavior is verified by researching the source code.
   259  		//
   260  		//	$ bsdtar -tvf pax-global-records.tar
   261  		//	----------  0 0      0           0 Dec 31  1969 file1
   262  		//	----------  0 0      0           0 Dec 31  1969 file2
   263  		//	----------  0 0      0           0 Dec 31  1969 file3
   264  		//	----------  0 0      0           0 May 13  2014 file4
   265  		//
   266  		// GNU tar v1.27.1 applies global headers to subsequent records,
   267  		// but does not do the following properly:
   268  		//	* It does not treat an empty record as deletion.
   269  		//	* It does not use subsequent global headers to update previous ones.
   270  		//
   271  		//	$ gnutar -tvf pax-global-records.tar
   272  		//	---------- 0/0               0 2017-07-13 19:40 global1
   273  		//	---------- 0/0               0 2017-07-13 19:40 file2
   274  		//	gnutar: Substituting `.' for empty member name
   275  		//	---------- 0/0               0 1969-12-31 16:00
   276  		//	gnutar: Substituting `.' for empty member name
   277  		//	---------- 0/0               0 2014-05-13 09:53
   278  		//
   279  		// According to the PAX specification, this should have been the result:
   280  		//	---------- 0/0               0 2017-07-13 19:40 global1
   281  		//	---------- 0/0               0 2017-07-13 19:40 file2
   282  		//	---------- 0/0               0 2017-07-13 19:40 file3
   283  		//	---------- 0/0               0 2014-05-13 09:53 file4
   284  		file: "testdata/pax-global-records.tar",
   285  		tests: []testFnc{
   286  			testHeader{Header{
   287  				Typeflag:   TypeXGlobalHeader,
   288  				PAXRecords: map[string]string{"path": "global1", "mtime": "1500000000.0"},
   289  			}, nil},
   290  			testHeader{Header{
   291  				Typeflag: TypeReg, Name: "file1",
   292  			}, nil},
   293  			testHeader{Header{
   294  				Typeflag:   TypeReg,
   295  				Name:       "file2",
   296  				PAXRecords: map[string]string{"path": "file2"},
   297  			}, nil},
   298  			testHeader{Header{
   299  				Typeflag:   TypeXGlobalHeader,
   300  				PAXRecords: map[string]string{"path": ""}, // Should delete "path", but keep "mtime"
   301  			}, nil},
   302  			testHeader{Header{
   303  				Typeflag: TypeReg, Name: "file3",
   304  			}, nil},
   305  			testHeader{Header{
   306  				Typeflag:   TypeReg,
   307  				Name:       "file4",
   308  				ModTime:    time.Unix(1400000000, 0),
   309  				PAXRecords: map[string]string{"mtime": "1400000000"},
   310  			}, nil},
   311  			testClose{nil},
   312  		},
   313  	}, {
   314  		file: "testdata/gnu-utf8.tar",
   315  		tests: []testFnc{
   316  			testHeader{Header{
   317  				Typeflag: TypeReg,
   318  				Name:     "☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹",
   319  				Mode:     0644,
   320  				Uid:      1000, Gid: 1000,
   321  				Uname:   "☺",
   322  				Gname:   "⚹",
   323  				ModTime: time.Unix(0, 0),
   324  				Format:  FormatGNU,
   325  			}, nil},
   326  			testClose{nil},
   327  		},
   328  	}, {
   329  		file: "testdata/gnu-not-utf8.tar",
   330  		tests: []testFnc{
   331  			testHeader{Header{
   332  				Typeflag: TypeReg,
   333  				Name:     "hi\x80\x81\x82\x83bye",
   334  				Mode:     0644,
   335  				Uid:      1000,
   336  				Gid:      1000,
   337  				Uname:    "rawr",
   338  				Gname:    "dsnet",
   339  				ModTime:  time.Unix(0, 0),
   340  				Format:   FormatGNU,
   341  			}, nil},
   342  			testClose{nil},
   343  		},
   344  		// TODO(dsnet): Re-enable this test when adding sparse support.
   345  		// See https://golang.org/issue/22735
   346  		/*
   347  			}, {
   348  				file: "testdata/gnu-nil-sparse-data.tar",
   349  				tests: []testFnc{
   350  					testHeader{Header{
   351  						Typeflag:    TypeGNUSparse,
   352  						Name:        "sparse.db",
   353  						Size:        1000,
   354  						SparseHoles: []sparseEntry{{Offset: 1000, Length: 0}},
   355  					}, nil},
   356  					testWrite{strings.Repeat("0123456789", 100), 1000, nil},
   357  					testClose{},
   358  				},
   359  			}, {
   360  				file: "testdata/gnu-nil-sparse-hole.tar",
   361  				tests: []testFnc{
   362  					testHeader{Header{
   363  						Typeflag:    TypeGNUSparse,
   364  						Name:        "sparse.db",
   365  						Size:        1000,
   366  						SparseHoles: []sparseEntry{{Offset: 0, Length: 1000}},
   367  					}, nil},
   368  					testWrite{strings.Repeat("\x00", 1000), 1000, nil},
   369  					testClose{},
   370  				},
   371  			}, {
   372  				file: "testdata/pax-nil-sparse-data.tar",
   373  				tests: []testFnc{
   374  					testHeader{Header{
   375  						Typeflag:    TypeReg,
   376  						Name:        "sparse.db",
   377  						Size:        1000,
   378  						SparseHoles: []sparseEntry{{Offset: 1000, Length: 0}},
   379  					}, nil},
   380  					testWrite{strings.Repeat("0123456789", 100), 1000, nil},
   381  					testClose{},
   382  				},
   383  			}, {
   384  				file: "testdata/pax-nil-sparse-hole.tar",
   385  				tests: []testFnc{
   386  					testHeader{Header{
   387  						Typeflag:    TypeReg,
   388  						Name:        "sparse.db",
   389  						Size:        1000,
   390  						SparseHoles: []sparseEntry{{Offset: 0, Length: 1000}},
   391  					}, nil},
   392  					testWrite{strings.Repeat("\x00", 1000), 1000, nil},
   393  					testClose{},
   394  				},
   395  			}, {
   396  				file: "testdata/gnu-sparse-big.tar",
   397  				tests: []testFnc{
   398  					testHeader{Header{
   399  						Typeflag: TypeGNUSparse,
   400  						Name:     "gnu-sparse",
   401  						Size:     6e10,
   402  						SparseHoles: []sparseEntry{
   403  							{Offset: 0e10, Length: 1e10 - 100},
   404  							{Offset: 1e10, Length: 1e10 - 100},
   405  							{Offset: 2e10, Length: 1e10 - 100},
   406  							{Offset: 3e10, Length: 1e10 - 100},
   407  							{Offset: 4e10, Length: 1e10 - 100},
   408  							{Offset: 5e10, Length: 1e10 - 100},
   409  						},
   410  					}, nil},
   411  					testReadFrom{fileOps{
   412  						int64(1e10 - blockSize),
   413  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   414  						int64(1e10 - blockSize),
   415  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   416  						int64(1e10 - blockSize),
   417  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   418  						int64(1e10 - blockSize),
   419  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   420  						int64(1e10 - blockSize),
   421  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   422  						int64(1e10 - blockSize),
   423  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   424  					}, 6e10, nil},
   425  					testClose{nil},
   426  				},
   427  			}, {
   428  				file: "testdata/pax-sparse-big.tar",
   429  				tests: []testFnc{
   430  					testHeader{Header{
   431  						Typeflag: TypeReg,
   432  						Name:     "pax-sparse",
   433  						Size:     6e10,
   434  						SparseHoles: []sparseEntry{
   435  							{Offset: 0e10, Length: 1e10 - 100},
   436  							{Offset: 1e10, Length: 1e10 - 100},
   437  							{Offset: 2e10, Length: 1e10 - 100},
   438  							{Offset: 3e10, Length: 1e10 - 100},
   439  							{Offset: 4e10, Length: 1e10 - 100},
   440  							{Offset: 5e10, Length: 1e10 - 100},
   441  						},
   442  					}, nil},
   443  					testReadFrom{fileOps{
   444  						int64(1e10 - blockSize),
   445  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   446  						int64(1e10 - blockSize),
   447  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   448  						int64(1e10 - blockSize),
   449  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   450  						int64(1e10 - blockSize),
   451  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   452  						int64(1e10 - blockSize),
   453  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   454  						int64(1e10 - blockSize),
   455  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   456  					}, 6e10, nil},
   457  					testClose{nil},
   458  				},
   459  		*/
   460  	}, {
   461  		file: "testdata/trailing-slash.tar",
   462  		tests: []testFnc{
   463  			testHeader{Header{Name: strings.Repeat("123456789/", 30)}, nil},
   464  			testClose{nil},
   465  		},
   466  	}, {
   467  		// Automatically promote zero value of Typeflag depending on the name.
   468  		file: "testdata/file-and-dir.tar",
   469  		tests: []testFnc{
   470  			testHeader{Header{Name: "small.txt", Size: 5}, nil},
   471  			testWrite{"Kilts", 5, nil},
   472  			testHeader{Header{Name: "dir/"}, nil},
   473  			testClose{nil},
   474  		},
   475  	}}
   476  
   477  	equalError := func(x, y error) bool {
   478  		_, ok1 := x.(headerError)
   479  		_, ok2 := y.(headerError)
   480  		if ok1 || ok2 {
   481  			return ok1 && ok2
   482  		}
   483  		return x == y
   484  	}
   485  	for _, v := range vectors {
   486  		t.Run(path.Base(v.file), func(t *testing.T) {
   487  			const maxSize = 10 << 10 // 10KiB
   488  			buf := new(bytes.Buffer)
   489  			tw := NewWriter(iotest.TruncateWriter(buf, maxSize))
   490  
   491  			for i, tf := range v.tests {
   492  				switch tf := tf.(type) {
   493  				case testHeader:
   494  					err := tw.WriteHeader(&tf.hdr)
   495  					if !equalError(err, tf.wantErr) {
   496  						t.Fatalf("test %d, WriteHeader() = %v, want %v", i, err, tf.wantErr)
   497  					}
   498  				case testWrite:
   499  					got, err := tw.Write([]byte(tf.str))
   500  					if got != tf.wantCnt || !equalError(err, tf.wantErr) {
   501  						t.Fatalf("test %d, Write() = (%d, %v), want (%d, %v)", i, got, err, tf.wantCnt, tf.wantErr)
   502  					}
   503  				case testReadFrom:
   504  					f := &testFile{ops: tf.ops}
   505  					got, err := tw.readFrom(f)
   506  					if _, ok := err.(testError); ok {
   507  						t.Errorf("test %d, ReadFrom(): %v", i, err)
   508  					} else if got != tf.wantCnt || !equalError(err, tf.wantErr) {
   509  						t.Errorf("test %d, ReadFrom() = (%d, %v), want (%d, %v)", i, got, err, tf.wantCnt, tf.wantErr)
   510  					}
   511  					if len(f.ops) > 0 {
   512  						t.Errorf("test %d, expected %d more operations", i, len(f.ops))
   513  					}
   514  				case testClose:
   515  					err := tw.Close()
   516  					if !equalError(err, tf.wantErr) {
   517  						t.Fatalf("test %d, Close() = %v, want %v", i, err, tf.wantErr)
   518  					}
   519  				default:
   520  					t.Fatalf("test %d, unknown test operation: %T", i, tf)
   521  				}
   522  			}
   523  
   524  			if v.file != "" {
   525  				want, err := os.ReadFile(v.file)
   526  				if err != nil {
   527  					t.Fatalf("ReadFile() = %v, want nil", err)
   528  				}
   529  				got := buf.Bytes()
   530  				if !bytes.Equal(want, got) {
   531  					t.Fatalf("incorrect result: (-got +want)\n%v", bytediff(got, want))
   532  				}
   533  			}
   534  		})
   535  	}
   536  }
   537  
   538  func TestPax(t *testing.T) {
   539  	// Create an archive with a large name
   540  	fileinfo, err := os.Stat("testdata/small.txt")
   541  	if err != nil {
   542  		t.Fatal(err)
   543  	}
   544  	hdr, err := FileInfoHeader(fileinfo, "")
   545  	if err != nil {
   546  		t.Fatalf("os.Stat: %v", err)
   547  	}
   548  	// Force a PAX long name to be written
   549  	longName := strings.Repeat("ab", 100)
   550  	contents := strings.Repeat(" ", int(hdr.Size))
   551  	hdr.Name = longName
   552  	var buf bytes.Buffer
   553  	writer := NewWriter(&buf)
   554  	if err := writer.WriteHeader(hdr); err != nil {
   555  		t.Fatal(err)
   556  	}
   557  	if _, err = writer.Write([]byte(contents)); err != nil {
   558  		t.Fatal(err)
   559  	}
   560  	if err := writer.Close(); err != nil {
   561  		t.Fatal(err)
   562  	}
   563  	// Simple test to make sure PAX extensions are in effect
   564  	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
   565  		t.Fatal("Expected at least one PAX header to be written.")
   566  	}
   567  	// Test that we can get a long name back out of the archive.
   568  	reader := NewReader(&buf)
   569  	hdr, err = reader.Next()
   570  	if err != nil {
   571  		t.Fatal(err)
   572  	}
   573  	if hdr.Name != longName {
   574  		t.Fatal("Couldn't recover long file name")
   575  	}
   576  }
   577  
   578  func TestPaxSymlink(t *testing.T) {
   579  	// Create an archive with a large linkname
   580  	fileinfo, err := os.Stat("testdata/small.txt")
   581  	if err != nil {
   582  		t.Fatal(err)
   583  	}
   584  	hdr, err := FileInfoHeader(fileinfo, "")
   585  	if err != nil {
   586  		t.Fatalf("os.Stat:1 %v", err)
   587  	}
   588  	hdr.Typeflag = TypeSymlink
   589  	// Force a PAX long linkname to be written
   590  	longLinkname := strings.Repeat("1234567890/1234567890", 10)
   591  	hdr.Linkname = longLinkname
   592  
   593  	hdr.Size = 0
   594  	var buf bytes.Buffer
   595  	writer := NewWriter(&buf)
   596  	if err := writer.WriteHeader(hdr); err != nil {
   597  		t.Fatal(err)
   598  	}
   599  	if err := writer.Close(); err != nil {
   600  		t.Fatal(err)
   601  	}
   602  	// Simple test to make sure PAX extensions are in effect
   603  	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
   604  		t.Fatal("Expected at least one PAX header to be written.")
   605  	}
   606  	// Test that we can get a long name back out of the archive.
   607  	reader := NewReader(&buf)
   608  	hdr, err = reader.Next()
   609  	if err != nil {
   610  		t.Fatal(err)
   611  	}
   612  	if hdr.Linkname != longLinkname {
   613  		t.Fatal("Couldn't recover long link name")
   614  	}
   615  }
   616  
   617  func TestPaxNonAscii(t *testing.T) {
   618  	// Create an archive with non ascii. These should trigger a pax header
   619  	// because pax headers have a defined utf-8 encoding.
   620  	fileinfo, err := os.Stat("testdata/small.txt")
   621  	if err != nil {
   622  		t.Fatal(err)
   623  	}
   624  
   625  	hdr, err := FileInfoHeader(fileinfo, "")
   626  	if err != nil {
   627  		t.Fatalf("os.Stat:1 %v", err)
   628  	}
   629  
   630  	// some sample data
   631  	chineseFilename := "文件名"
   632  	chineseGroupname := "組"
   633  	chineseUsername := "用戶名"
   634  
   635  	hdr.Name = chineseFilename
   636  	hdr.Gname = chineseGroupname
   637  	hdr.Uname = chineseUsername
   638  
   639  	contents := strings.Repeat(" ", int(hdr.Size))
   640  
   641  	var buf bytes.Buffer
   642  	writer := NewWriter(&buf)
   643  	if err := writer.WriteHeader(hdr); err != nil {
   644  		t.Fatal(err)
   645  	}
   646  	if _, err = writer.Write([]byte(contents)); err != nil {
   647  		t.Fatal(err)
   648  	}
   649  	if err := writer.Close(); err != nil {
   650  		t.Fatal(err)
   651  	}
   652  	// Simple test to make sure PAX extensions are in effect
   653  	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
   654  		t.Fatal("Expected at least one PAX header to be written.")
   655  	}
   656  	// Test that we can get a long name back out of the archive.
   657  	reader := NewReader(&buf)
   658  	hdr, err = reader.Next()
   659  	if err != nil {
   660  		t.Fatal(err)
   661  	}
   662  	if hdr.Name != chineseFilename {
   663  		t.Fatal("Couldn't recover unicode name")
   664  	}
   665  	if hdr.Gname != chineseGroupname {
   666  		t.Fatal("Couldn't recover unicode group")
   667  	}
   668  	if hdr.Uname != chineseUsername {
   669  		t.Fatal("Couldn't recover unicode user")
   670  	}
   671  }
   672  
   673  func TestPaxXattrs(t *testing.T) {
   674  	xattrs := map[string]string{
   675  		"user.key": "value",
   676  	}
   677  
   678  	// Create an archive with an xattr
   679  	fileinfo, err := os.Stat("testdata/small.txt")
   680  	if err != nil {
   681  		t.Fatal(err)
   682  	}
   683  	hdr, err := FileInfoHeader(fileinfo, "")
   684  	if err != nil {
   685  		t.Fatalf("os.Stat: %v", err)
   686  	}
   687  	contents := "Kilts"
   688  	hdr.Xattrs = xattrs
   689  	var buf bytes.Buffer
   690  	writer := NewWriter(&buf)
   691  	if err := writer.WriteHeader(hdr); err != nil {
   692  		t.Fatal(err)
   693  	}
   694  	if _, err = writer.Write([]byte(contents)); err != nil {
   695  		t.Fatal(err)
   696  	}
   697  	if err := writer.Close(); err != nil {
   698  		t.Fatal(err)
   699  	}
   700  	// Test that we can get the xattrs back out of the archive.
   701  	reader := NewReader(&buf)
   702  	hdr, err = reader.Next()
   703  	if err != nil {
   704  		t.Fatal(err)
   705  	}
   706  	if !maps.Equal(hdr.Xattrs, xattrs) {
   707  		t.Fatalf("xattrs did not survive round trip: got %+v, want %+v",
   708  			hdr.Xattrs, xattrs)
   709  	}
   710  }
   711  
   712  func TestPaxHeadersSorted(t *testing.T) {
   713  	fileinfo, err := os.Stat("testdata/small.txt")
   714  	if err != nil {
   715  		t.Fatal(err)
   716  	}
   717  	hdr, err := FileInfoHeader(fileinfo, "")
   718  	if err != nil {
   719  		t.Fatalf("os.Stat: %v", err)
   720  	}
   721  	contents := strings.Repeat(" ", int(hdr.Size))
   722  
   723  	hdr.Xattrs = map[string]string{
   724  		"foo": "foo",
   725  		"bar": "bar",
   726  		"baz": "baz",
   727  		"qux": "qux",
   728  	}
   729  
   730  	var buf bytes.Buffer
   731  	writer := NewWriter(&buf)
   732  	if err := writer.WriteHeader(hdr); err != nil {
   733  		t.Fatal(err)
   734  	}
   735  	if _, err = writer.Write([]byte(contents)); err != nil {
   736  		t.Fatal(err)
   737  	}
   738  	if err := writer.Close(); err != nil {
   739  		t.Fatal(err)
   740  	}
   741  	// Simple test to make sure PAX extensions are in effect
   742  	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
   743  		t.Fatal("Expected at least one PAX header to be written.")
   744  	}
   745  
   746  	// xattr bar should always appear before others
   747  	indices := []int{
   748  		bytes.Index(buf.Bytes(), []byte("bar=bar")),
   749  		bytes.Index(buf.Bytes(), []byte("baz=baz")),
   750  		bytes.Index(buf.Bytes(), []byte("foo=foo")),
   751  		bytes.Index(buf.Bytes(), []byte("qux=qux")),
   752  	}
   753  	if !slices.IsSorted(indices) {
   754  		t.Fatal("PAX headers are not sorted")
   755  	}
   756  }
   757  
   758  func TestUSTARLongName(t *testing.T) {
   759  	// Create an archive with a path that failed to split with USTAR extension in previous versions.
   760  	fileinfo, err := os.Stat("testdata/small.txt")
   761  	if err != nil {
   762  		t.Fatal(err)
   763  	}
   764  	hdr, err := FileInfoHeader(fileinfo, "")
   765  	if err != nil {
   766  		t.Fatalf("os.Stat:1 %v", err)
   767  	}
   768  	hdr.Typeflag = TypeDir
   769  	// Force a PAX long name to be written. The name was taken from a practical example
   770  	// that fails and replaced ever char through numbers to anonymize the sample.
   771  	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/"
   772  	hdr.Name = longName
   773  
   774  	hdr.Size = 0
   775  	var buf bytes.Buffer
   776  	writer := NewWriter(&buf)
   777  	if err := writer.WriteHeader(hdr); err != nil {
   778  		t.Fatal(err)
   779  	}
   780  	if err := writer.Close(); err != nil {
   781  		t.Fatal(err)
   782  	}
   783  	// Test that we can get a long name back out of the archive.
   784  	reader := NewReader(&buf)
   785  	hdr, err = reader.Next()
   786  	if err != nil && err != ErrInsecurePath {
   787  		t.Fatal(err)
   788  	}
   789  	if hdr.Name != longName {
   790  		t.Fatal("Couldn't recover long name")
   791  	}
   792  }
   793  
   794  func TestValidTypeflagWithPAXHeader(t *testing.T) {
   795  	var buffer bytes.Buffer
   796  	tw := NewWriter(&buffer)
   797  
   798  	fileName := strings.Repeat("ab", 100)
   799  
   800  	hdr := &Header{
   801  		Name:     fileName,
   802  		Size:     4,
   803  		Typeflag: 0,
   804  	}
   805  	if err := tw.WriteHeader(hdr); err != nil {
   806  		t.Fatalf("Failed to write header: %s", err)
   807  	}
   808  	if _, err := tw.Write([]byte("fooo")); err != nil {
   809  		t.Fatalf("Failed to write the file's data: %s", err)
   810  	}
   811  	tw.Close()
   812  
   813  	tr := NewReader(&buffer)
   814  
   815  	for {
   816  		header, err := tr.Next()
   817  		if err == io.EOF {
   818  			break
   819  		}
   820  		if err != nil {
   821  			t.Fatalf("Failed to read header: %s", err)
   822  		}
   823  		if header.Typeflag != TypeReg {
   824  			t.Fatalf("Typeflag should've been %d, found %d", TypeReg, header.Typeflag)
   825  		}
   826  	}
   827  }
   828  
   829  // failOnceWriter fails exactly once and then always reports success.
   830  type failOnceWriter bool
   831  
   832  func (w *failOnceWriter) Write(b []byte) (int, error) {
   833  	if !*w {
   834  		return 0, io.ErrShortWrite
   835  	}
   836  	*w = true
   837  	return len(b), nil
   838  }
   839  
   840  func TestWriterErrors(t *testing.T) {
   841  	t.Run("HeaderOnly", func(t *testing.T) {
   842  		tw := NewWriter(new(bytes.Buffer))
   843  		hdr := &Header{Name: "dir/", Typeflag: TypeDir}
   844  		if err := tw.WriteHeader(hdr); err != nil {
   845  			t.Fatalf("WriteHeader() = %v, want nil", err)
   846  		}
   847  		if _, err := tw.Write([]byte{0x00}); err != ErrWriteTooLong {
   848  			t.Fatalf("Write() = %v, want %v", err, ErrWriteTooLong)
   849  		}
   850  	})
   851  
   852  	t.Run("NegativeSize", func(t *testing.T) {
   853  		tw := NewWriter(new(bytes.Buffer))
   854  		hdr := &Header{Name: "small.txt", Size: -1}
   855  		if err := tw.WriteHeader(hdr); err == nil {
   856  			t.Fatalf("WriteHeader() = nil, want non-nil error")
   857  		}
   858  	})
   859  
   860  	t.Run("BeforeHeader", func(t *testing.T) {
   861  		tw := NewWriter(new(bytes.Buffer))
   862  		if _, err := tw.Write([]byte("Kilts")); err != ErrWriteTooLong {
   863  			t.Fatalf("Write() = %v, want %v", err, ErrWriteTooLong)
   864  		}
   865  	})
   866  
   867  	t.Run("AfterClose", func(t *testing.T) {
   868  		tw := NewWriter(new(bytes.Buffer))
   869  		hdr := &Header{Name: "small.txt"}
   870  		if err := tw.WriteHeader(hdr); err != nil {
   871  			t.Fatalf("WriteHeader() = %v, want nil", err)
   872  		}
   873  		if err := tw.Close(); err != nil {
   874  			t.Fatalf("Close() = %v, want nil", err)
   875  		}
   876  		if _, err := tw.Write([]byte("Kilts")); err != ErrWriteAfterClose {
   877  			t.Fatalf("Write() = %v, want %v", err, ErrWriteAfterClose)
   878  		}
   879  		if err := tw.Flush(); err != ErrWriteAfterClose {
   880  			t.Fatalf("Flush() = %v, want %v", err, ErrWriteAfterClose)
   881  		}
   882  		if err := tw.Close(); err != nil {
   883  			t.Fatalf("Close() = %v, want nil", err)
   884  		}
   885  	})
   886  
   887  	t.Run("PrematureFlush", func(t *testing.T) {
   888  		tw := NewWriter(new(bytes.Buffer))
   889  		hdr := &Header{Name: "small.txt", Size: 5}
   890  		if err := tw.WriteHeader(hdr); err != nil {
   891  			t.Fatalf("WriteHeader() = %v, want nil", err)
   892  		}
   893  		if err := tw.Flush(); err == nil {
   894  			t.Fatalf("Flush() = %v, want non-nil error", err)
   895  		}
   896  	})
   897  
   898  	t.Run("PrematureClose", func(t *testing.T) {
   899  		tw := NewWriter(new(bytes.Buffer))
   900  		hdr := &Header{Name: "small.txt", Size: 5}
   901  		if err := tw.WriteHeader(hdr); err != nil {
   902  			t.Fatalf("WriteHeader() = %v, want nil", err)
   903  		}
   904  		if err := tw.Close(); err == nil {
   905  			t.Fatalf("Close() = %v, want non-nil error", err)
   906  		}
   907  	})
   908  
   909  	t.Run("Persistence", func(t *testing.T) {
   910  		tw := NewWriter(new(failOnceWriter))
   911  		if err := tw.WriteHeader(&Header{}); err != io.ErrShortWrite {
   912  			t.Fatalf("WriteHeader() = %v, want %v", err, io.ErrShortWrite)
   913  		}
   914  		if err := tw.WriteHeader(&Header{Name: "small.txt"}); err == nil {
   915  			t.Errorf("WriteHeader() = got %v, want non-nil error", err)
   916  		}
   917  		if _, err := tw.Write(nil); err == nil {
   918  			t.Errorf("Write() = %v, want non-nil error", err)
   919  		}
   920  		if err := tw.Flush(); err == nil {
   921  			t.Errorf("Flush() = %v, want non-nil error", err)
   922  		}
   923  		if err := tw.Close(); err == nil {
   924  			t.Errorf("Close() = %v, want non-nil error", err)
   925  		}
   926  	})
   927  }
   928  
   929  func TestSplitUSTARPath(t *testing.T) {
   930  	sr := strings.Repeat
   931  
   932  	vectors := []struct {
   933  		input  string // Input path
   934  		prefix string // Expected output prefix
   935  		suffix string // Expected output suffix
   936  		ok     bool   // Split success?
   937  	}{
   938  		{"", "", "", false},
   939  		{"abc", "", "", false},
   940  		{"用戶名", "", "", false},
   941  		{sr("a", nameSize), "", "", false},
   942  		{sr("a", nameSize) + "/", "", "", false},
   943  		{sr("a", nameSize) + "/a", sr("a", nameSize), "a", true},
   944  		{sr("a", prefixSize) + "/", "", "", false},
   945  		{sr("a", prefixSize) + "/a", sr("a", prefixSize), "a", true},
   946  		{sr("a", nameSize+1), "", "", false},
   947  		{sr("/", nameSize+1), sr("/", nameSize-1), "/", true},
   948  		{sr("a", prefixSize) + "/" + sr("b", nameSize),
   949  			sr("a", prefixSize), sr("b", nameSize), true},
   950  		{sr("a", prefixSize) + "//" + sr("b", nameSize), "", "", false},
   951  		{sr("a/", nameSize), sr("a/", 77) + "a", sr("a/", 22), true},
   952  	}
   953  
   954  	for _, v := range vectors {
   955  		prefix, suffix, ok := splitUSTARPath(v.input)
   956  		if prefix != v.prefix || suffix != v.suffix || ok != v.ok {
   957  			t.Errorf("splitUSTARPath(%q):\ngot  (%q, %q, %v)\nwant (%q, %q, %v)",
   958  				v.input, prefix, suffix, ok, v.prefix, v.suffix, v.ok)
   959  		}
   960  	}
   961  }
   962  
   963  // TestIssue12594 tests that the Writer does not attempt to populate the prefix
   964  // field when encoding a header in the GNU format. The prefix field is valid
   965  // in USTAR and PAX, but not GNU.
   966  func TestIssue12594(t *testing.T) {
   967  	names := []string{
   968  		"0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/file.txt",
   969  		"0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/file.txt",
   970  		"0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/333/file.txt",
   971  		"0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34/35/36/37/38/39/40/file.txt",
   972  		"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000/file.txt",
   973  		"/home/support/.openoffice.org/3/user/uno_packages/cache/registry/com.sun.star.comp.deployment.executable.PackageRegistryBackend",
   974  	}
   975  
   976  	for i, name := range names {
   977  		var b bytes.Buffer
   978  
   979  		tw := NewWriter(&b)
   980  		if err := tw.WriteHeader(&Header{
   981  			Name: name,
   982  			Uid:  1 << 25, // Prevent USTAR format
   983  		}); err != nil {
   984  			t.Errorf("test %d, unexpected WriteHeader error: %v", i, err)
   985  		}
   986  		if err := tw.Close(); err != nil {
   987  			t.Errorf("test %d, unexpected Close error: %v", i, err)
   988  		}
   989  
   990  		// The prefix field should never appear in the GNU format.
   991  		var blk block
   992  		copy(blk[:], b.Bytes())
   993  		prefix := string(blk.toUSTAR().prefix())
   994  		prefix, _, _ = strings.Cut(prefix, "\x00") // Truncate at the NUL terminator
   995  		if blk.getFormat() == FormatGNU && len(prefix) > 0 && strings.HasPrefix(name, prefix) {
   996  			t.Errorf("test %d, found prefix in GNU format: %s", i, prefix)
   997  		}
   998  
   999  		tr := NewReader(&b)
  1000  		hdr, err := tr.Next()
  1001  		if err != nil && err != ErrInsecurePath {
  1002  			t.Errorf("test %d, unexpected Next error: %v", i, err)
  1003  		}
  1004  		if hdr.Name != name {
  1005  			t.Errorf("test %d, hdr.Name = %s, want %s", i, hdr.Name, name)
  1006  		}
  1007  	}
  1008  }
  1009  
  1010  func TestWriteLongHeader(t *testing.T) {
  1011  	for _, test := range []struct {
  1012  		name string
  1013  		h    *Header
  1014  	}{{
  1015  		name: "name too long",
  1016  		h:    &Header{Name: strings.Repeat("a", maxSpecialFileSize)},
  1017  	}, {
  1018  		name: "linkname too long",
  1019  		h:    &Header{Linkname: strings.Repeat("a", maxSpecialFileSize)},
  1020  	}, {
  1021  		name: "uname too long",
  1022  		h:    &Header{Uname: strings.Repeat("a", maxSpecialFileSize)},
  1023  	}, {
  1024  		name: "gname too long",
  1025  		h:    &Header{Gname: strings.Repeat("a", maxSpecialFileSize)},
  1026  	}, {
  1027  		name: "PAX header too long",
  1028  		h:    &Header{PAXRecords: map[string]string{"GOLANG.x": strings.Repeat("a", maxSpecialFileSize)}},
  1029  	}} {
  1030  		w := NewWriter(io.Discard)
  1031  		if err := w.WriteHeader(test.h); err != ErrFieldTooLong {
  1032  			t.Errorf("%v: w.WriteHeader() = %v, want ErrFieldTooLong", test.name, err)
  1033  		}
  1034  	}
  1035  }
  1036  
  1037  // testNonEmptyWriter wraps an io.Writer and ensures that
  1038  // Write is never called with an empty buffer.
  1039  type testNonEmptyWriter struct{ io.Writer }
  1040  
  1041  func (w testNonEmptyWriter) Write(b []byte) (int, error) {
  1042  	if len(b) == 0 {
  1043  		return 0, errors.New("unexpected empty Write call")
  1044  	}
  1045  	return w.Writer.Write(b)
  1046  }
  1047  
  1048  func TestFileWriter(t *testing.T) {
  1049  	type (
  1050  		testWrite struct { // Write(str) == (wantCnt, wantErr)
  1051  			str     string
  1052  			wantCnt int
  1053  			wantErr error
  1054  		}
  1055  		testReadFrom struct { // ReadFrom(testFile{ops}) == (wantCnt, wantErr)
  1056  			ops     fileOps
  1057  			wantCnt int64
  1058  			wantErr error
  1059  		}
  1060  		testRemaining struct { // logicalRemaining() == wantLCnt, physicalRemaining() == wantPCnt
  1061  			wantLCnt int64
  1062  			wantPCnt int64
  1063  		}
  1064  		testFnc any // testWrite | testReadFrom | testRemaining
  1065  	)
  1066  
  1067  	type (
  1068  		makeReg struct {
  1069  			size    int64
  1070  			wantStr string
  1071  		}
  1072  		makeSparse struct {
  1073  			makeReg makeReg
  1074  			sph     sparseHoles
  1075  			size    int64
  1076  		}
  1077  		fileMaker any // makeReg | makeSparse
  1078  	)
  1079  
  1080  	vectors := []struct {
  1081  		maker fileMaker
  1082  		tests []testFnc
  1083  	}{{
  1084  		maker: makeReg{0, ""},
  1085  		tests: []testFnc{
  1086  			testRemaining{0, 0},
  1087  			testWrite{"", 0, nil},
  1088  			testWrite{"a", 0, ErrWriteTooLong},
  1089  			testReadFrom{fileOps{""}, 0, nil},
  1090  			testReadFrom{fileOps{"a"}, 0, ErrWriteTooLong},
  1091  			testRemaining{0, 0},
  1092  		},
  1093  	}, {
  1094  		maker: makeReg{1, "a"},
  1095  		tests: []testFnc{
  1096  			testRemaining{1, 1},
  1097  			testWrite{"", 0, nil},
  1098  			testWrite{"a", 1, nil},
  1099  			testWrite{"bcde", 0, ErrWriteTooLong},
  1100  			testWrite{"", 0, nil},
  1101  			testReadFrom{fileOps{""}, 0, nil},
  1102  			testReadFrom{fileOps{"a"}, 0, ErrWriteTooLong},
  1103  			testRemaining{0, 0},
  1104  		},
  1105  	}, {
  1106  		maker: makeReg{5, "hello"},
  1107  		tests: []testFnc{
  1108  			testRemaining{5, 5},
  1109  			testWrite{"hello", 5, nil},
  1110  			testRemaining{0, 0},
  1111  		},
  1112  	}, {
  1113  		maker: makeReg{5, "\x00\x00\x00\x00\x00"},
  1114  		tests: []testFnc{
  1115  			testRemaining{5, 5},
  1116  			testReadFrom{fileOps{"\x00\x00\x00\x00\x00"}, 5, nil},
  1117  			testRemaining{0, 0},
  1118  		},
  1119  	}, {
  1120  		maker: makeReg{5, "\x00\x00\x00\x00\x00"},
  1121  		tests: []testFnc{
  1122  			testRemaining{5, 5},
  1123  			testReadFrom{fileOps{"\x00\x00\x00\x00\x00extra"}, 5, ErrWriteTooLong},
  1124  			testRemaining{0, 0},
  1125  		},
  1126  	}, {
  1127  		maker: makeReg{5, "abc\x00\x00"},
  1128  		tests: []testFnc{
  1129  			testRemaining{5, 5},
  1130  			testWrite{"abc", 3, nil},
  1131  			testRemaining{2, 2},
  1132  			testReadFrom{fileOps{"\x00\x00"}, 2, nil},
  1133  			testRemaining{0, 0},
  1134  		},
  1135  	}, {
  1136  		maker: makeReg{5, "\x00\x00abc"},
  1137  		tests: []testFnc{
  1138  			testRemaining{5, 5},
  1139  			testWrite{"\x00\x00", 2, nil},
  1140  			testRemaining{3, 3},
  1141  			testWrite{"abc", 3, nil},
  1142  			testReadFrom{fileOps{"z"}, 0, ErrWriteTooLong},
  1143  			testWrite{"z", 0, ErrWriteTooLong},
  1144  			testRemaining{0, 0},
  1145  		},
  1146  	}, {
  1147  		maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8},
  1148  		tests: []testFnc{
  1149  			testRemaining{8, 5},
  1150  			testWrite{"ab\x00\x00\x00cde", 8, nil},
  1151  			testWrite{"a", 0, ErrWriteTooLong},
  1152  			testRemaining{0, 0},
  1153  		},
  1154  	}, {
  1155  		maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8},
  1156  		tests: []testFnc{
  1157  			testWrite{"ab\x00\x00\x00cdez", 8, ErrWriteTooLong},
  1158  			testRemaining{0, 0},
  1159  		},
  1160  	}, {
  1161  		maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8},
  1162  		tests: []testFnc{
  1163  			testWrite{"ab\x00", 3, nil},
  1164  			testRemaining{5, 3},
  1165  			testWrite{"\x00\x00cde", 5, nil},
  1166  			testWrite{"a", 0, ErrWriteTooLong},
  1167  			testRemaining{0, 0},
  1168  		},
  1169  	}, {
  1170  		maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8},
  1171  		tests: []testFnc{
  1172  			testWrite{"ab", 2, nil},
  1173  			testRemaining{6, 3},
  1174  			testReadFrom{fileOps{int64(3), "cde"}, 6, nil},
  1175  			testRemaining{0, 0},
  1176  		},
  1177  	}, {
  1178  		maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8},
  1179  		tests: []testFnc{
  1180  			testReadFrom{fileOps{"ab", int64(3), "cde"}, 8, nil},
  1181  			testRemaining{0, 0},
  1182  		},
  1183  	}, {
  1184  		maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8},
  1185  		tests: []testFnc{
  1186  			testReadFrom{fileOps{"ab", int64(3), "cdeX"}, 8, ErrWriteTooLong},
  1187  			testRemaining{0, 0},
  1188  		},
  1189  	}, {
  1190  		maker: makeSparse{makeReg{4, "abcd"}, sparseHoles{{2, 3}}, 8},
  1191  		tests: []testFnc{
  1192  			testReadFrom{fileOps{"ab", int64(3), "cd"}, 7, io.ErrUnexpectedEOF},
  1193  			testRemaining{1, 0},
  1194  		},
  1195  	}, {
  1196  		maker: makeSparse{makeReg{4, "abcd"}, sparseHoles{{2, 3}}, 8},
  1197  		tests: []testFnc{
  1198  			testReadFrom{fileOps{"ab", int64(3), "cde"}, 7, errMissData},
  1199  			testRemaining{1, 0},
  1200  		},
  1201  	}, {
  1202  		maker: makeSparse{makeReg{6, "abcde"}, sparseHoles{{2, 3}}, 8},
  1203  		tests: []testFnc{
  1204  			testReadFrom{fileOps{"ab", int64(3), "cde"}, 8, errUnrefData},
  1205  			testRemaining{0, 1},
  1206  		},
  1207  	}, {
  1208  		maker: makeSparse{makeReg{4, "abcd"}, sparseHoles{{2, 3}}, 8},
  1209  		tests: []testFnc{
  1210  			testWrite{"ab", 2, nil},
  1211  			testRemaining{6, 2},
  1212  			testWrite{"\x00\x00\x00", 3, nil},
  1213  			testRemaining{3, 2},
  1214  			testWrite{"cde", 2, errMissData},
  1215  			testRemaining{1, 0},
  1216  		},
  1217  	}, {
  1218  		maker: makeSparse{makeReg{6, "abcde"}, sparseHoles{{2, 3}}, 8},
  1219  		tests: []testFnc{
  1220  			testWrite{"ab", 2, nil},
  1221  			testRemaining{6, 4},
  1222  			testWrite{"\x00\x00\x00", 3, nil},
  1223  			testRemaining{3, 4},
  1224  			testWrite{"cde", 3, errUnrefData},
  1225  			testRemaining{0, 1},
  1226  		},
  1227  	}, {
  1228  		maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1229  		tests: []testFnc{
  1230  			testRemaining{7, 3},
  1231  			testWrite{"\x00\x00abc\x00\x00", 7, nil},
  1232  			testRemaining{0, 0},
  1233  		},
  1234  	}, {
  1235  		maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1236  		tests: []testFnc{
  1237  			testRemaining{7, 3},
  1238  			testReadFrom{fileOps{int64(2), "abc", int64(1), "\x00"}, 7, nil},
  1239  			testRemaining{0, 0},
  1240  		},
  1241  	}, {
  1242  		maker: makeSparse{makeReg{3, ""}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1243  		tests: []testFnc{
  1244  			testWrite{"abcdefg", 0, errWriteHole},
  1245  		},
  1246  	}, {
  1247  		maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1248  		tests: []testFnc{
  1249  			testWrite{"\x00\x00abcde", 5, errWriteHole},
  1250  		},
  1251  	}, {
  1252  		maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1253  		tests: []testFnc{
  1254  			testWrite{"\x00\x00abc\x00\x00z", 7, ErrWriteTooLong},
  1255  			testRemaining{0, 0},
  1256  		},
  1257  	}, {
  1258  		maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1259  		tests: []testFnc{
  1260  			testWrite{"\x00\x00", 2, nil},
  1261  			testRemaining{5, 3},
  1262  			testWrite{"abc", 3, nil},
  1263  			testRemaining{2, 0},
  1264  			testWrite{"\x00\x00", 2, nil},
  1265  			testRemaining{0, 0},
  1266  		},
  1267  	}, {
  1268  		maker: makeSparse{makeReg{2, "ab"}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1269  		tests: []testFnc{
  1270  			testWrite{"\x00\x00", 2, nil},
  1271  			testWrite{"abc", 2, errMissData},
  1272  			testWrite{"\x00\x00", 0, errMissData},
  1273  		},
  1274  	}, {
  1275  		maker: makeSparse{makeReg{4, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1276  		tests: []testFnc{
  1277  			testWrite{"\x00\x00", 2, nil},
  1278  			testWrite{"abc", 3, nil},
  1279  			testWrite{"\x00\x00", 2, errUnrefData},
  1280  		},
  1281  	}}
  1282  
  1283  	for i, v := range vectors {
  1284  		var wantStr string
  1285  		bb := new(strings.Builder)
  1286  		w := testNonEmptyWriter{bb}
  1287  		var fw fileWriter
  1288  		switch maker := v.maker.(type) {
  1289  		case makeReg:
  1290  			fw = &regFileWriter{w, maker.size}
  1291  			wantStr = maker.wantStr
  1292  		case makeSparse:
  1293  			if !validateSparseEntries(maker.sph, maker.size) {
  1294  				t.Fatalf("invalid sparse map: %v", maker.sph)
  1295  			}
  1296  			spd := invertSparseEntries(maker.sph, maker.size)
  1297  			fw = &regFileWriter{w, maker.makeReg.size}
  1298  			fw = &sparseFileWriter{fw, spd, 0}
  1299  			wantStr = maker.makeReg.wantStr
  1300  		default:
  1301  			t.Fatalf("test %d, unknown make operation: %T", i, maker)
  1302  		}
  1303  
  1304  		for j, tf := range v.tests {
  1305  			switch tf := tf.(type) {
  1306  			case testWrite:
  1307  				got, err := fw.Write([]byte(tf.str))
  1308  				if got != tf.wantCnt || err != tf.wantErr {
  1309  					t.Errorf("test %d.%d, Write(%s):\ngot  (%d, %v)\nwant (%d, %v)", i, j, tf.str, got, err, tf.wantCnt, tf.wantErr)
  1310  				}
  1311  			case testReadFrom:
  1312  				f := &testFile{ops: tf.ops}
  1313  				got, err := fw.ReadFrom(f)
  1314  				if _, ok := err.(testError); ok {
  1315  					t.Errorf("test %d.%d, ReadFrom(): %v", i, j, err)
  1316  				} else if got != tf.wantCnt || err != tf.wantErr {
  1317  					t.Errorf("test %d.%d, ReadFrom() = (%d, %v), want (%d, %v)", i, j, got, err, tf.wantCnt, tf.wantErr)
  1318  				}
  1319  				if len(f.ops) > 0 {
  1320  					t.Errorf("test %d.%d, expected %d more operations", i, j, len(f.ops))
  1321  				}
  1322  			case testRemaining:
  1323  				if got := fw.logicalRemaining(); got != tf.wantLCnt {
  1324  					t.Errorf("test %d.%d, logicalRemaining() = %d, want %d", i, j, got, tf.wantLCnt)
  1325  				}
  1326  				if got := fw.physicalRemaining(); got != tf.wantPCnt {
  1327  					t.Errorf("test %d.%d, physicalRemaining() = %d, want %d", i, j, got, tf.wantPCnt)
  1328  				}
  1329  			default:
  1330  				t.Fatalf("test %d.%d, unknown test operation: %T", i, j, tf)
  1331  			}
  1332  		}
  1333  
  1334  		if got := bb.String(); got != wantStr {
  1335  			t.Fatalf("test %d, String() = %q, want %q", i, got, wantStr)
  1336  		}
  1337  	}
  1338  }
  1339  
  1340  func TestWriterAddFS(t *testing.T) {
  1341  	fsys := fstest.MapFS{
  1342  		"emptyfolder":          {Mode: 0o755 | os.ModeDir},
  1343  		"file.go":              {Data: []byte("hello")},
  1344  		"subfolder/another.go": {Data: []byte("world")},
  1345  		// Notably missing here is the "subfolder" directory. This makes sure even
  1346  		// if we don't have a subfolder directory listed.
  1347  	}
  1348  	var buf bytes.Buffer
  1349  	tw := NewWriter(&buf)
  1350  	if err := tw.AddFS(fsys); err != nil {
  1351  		t.Fatal(err)
  1352  	}
  1353  	if err := tw.Close(); err != nil {
  1354  		t.Fatal(err)
  1355  	}
  1356  
  1357  	// Add subfolder into fsys to match what we'll read from the tar.
  1358  	fsys["subfolder"] = &fstest.MapFile{Mode: 0o555 | os.ModeDir}
  1359  
  1360  	// Test that we can get the files back from the archive
  1361  	tr := NewReader(&buf)
  1362  
  1363  	names := make([]string, 0, len(fsys))
  1364  	for name := range fsys {
  1365  		names = append(names, name)
  1366  	}
  1367  	sort.Strings(names)
  1368  
  1369  	entriesLeft := len(fsys)
  1370  	for _, name := range names {
  1371  		entriesLeft--
  1372  
  1373  		entryInfo, err := fsys.Stat(name)
  1374  		if err != nil {
  1375  			t.Fatalf("getting entry info error: %v", err)
  1376  		}
  1377  		hdr, err := tr.Next()
  1378  		if err == io.EOF {
  1379  			break // End of archive
  1380  		}
  1381  		if err != nil {
  1382  			t.Fatal(err)
  1383  		}
  1384  
  1385  		if hdr.Name != name {
  1386  			t.Errorf("test fs has filename %v; archive header has %v",
  1387  				name, hdr.Name)
  1388  		}
  1389  
  1390  		if entryInfo.Mode() != hdr.FileInfo().Mode() {
  1391  			t.Errorf("%s: test fs has mode %v; archive header has %v",
  1392  				name, entryInfo.Mode(), hdr.FileInfo().Mode())
  1393  		}
  1394  
  1395  		if entryInfo.IsDir() {
  1396  			continue
  1397  		}
  1398  
  1399  		data, err := io.ReadAll(tr)
  1400  		if err != nil {
  1401  			t.Fatal(err)
  1402  		}
  1403  		origdata := fsys[name].Data
  1404  		if string(data) != string(origdata) {
  1405  			t.Fatalf("test fs has file content %v; archive header has %v",
  1406  				data, origdata)
  1407  		}
  1408  	}
  1409  	if entriesLeft > 0 {
  1410  		t.Fatalf("not all entries are in the archive")
  1411  	}
  1412  }
  1413  
  1414  func TestWriterAddFSNonRegularFiles(t *testing.T) {
  1415  	fsys := fstest.MapFS{
  1416  		"device":  {Data: []byte("hello"), Mode: 0755 | fs.ModeDevice},
  1417  		"symlink": {Data: []byte("world"), Mode: 0755 | fs.ModeSymlink},
  1418  	}
  1419  	var buf bytes.Buffer
  1420  	tw := NewWriter(&buf)
  1421  	if err := tw.AddFS(fsys); err == nil {
  1422  		t.Fatal("expected error, got nil")
  1423  	}
  1424  }
  1425  

View as plain text