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

View as plain text