Source file src/archive/tar/tar_test.go

     1  // Copyright 2012 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  	"errors"
    10  	"fmt"
    11  	"internal/testenv"
    12  	"io"
    13  	"io/fs"
    14  	"maps"
    15  	"math"
    16  	"os"
    17  	"path"
    18  	"path/filepath"
    19  	"reflect"
    20  	"slices"
    21  	"strings"
    22  	"testing"
    23  	"time"
    24  )
    25  
    26  type testError struct{ error }
    27  
    28  type fileOps []any // []T where T is (string | int64)
    29  
    30  // testFile is an io.ReadWriteSeeker where the IO operations performed
    31  // on it must match the list of operations in ops.
    32  type testFile struct {
    33  	ops fileOps
    34  	pos int64
    35  }
    36  
    37  func (f *testFile) Read(b []byte) (int, error) {
    38  	if len(b) == 0 {
    39  		return 0, nil
    40  	}
    41  	if len(f.ops) == 0 {
    42  		return 0, io.EOF
    43  	}
    44  	s, ok := f.ops[0].(string)
    45  	if !ok {
    46  		return 0, errors.New("unexpected Read operation")
    47  	}
    48  
    49  	n := copy(b, s)
    50  	if len(s) > n {
    51  		f.ops[0] = s[n:]
    52  	} else {
    53  		f.ops = f.ops[1:]
    54  	}
    55  	f.pos += int64(len(b))
    56  	return n, nil
    57  }
    58  
    59  func (f *testFile) Write(b []byte) (int, error) {
    60  	if len(b) == 0 {
    61  		return 0, nil
    62  	}
    63  	if len(f.ops) == 0 {
    64  		return 0, errors.New("unexpected Write operation")
    65  	}
    66  	s, ok := f.ops[0].(string)
    67  	if !ok {
    68  		return 0, errors.New("unexpected Write operation")
    69  	}
    70  
    71  	if !strings.HasPrefix(s, string(b)) {
    72  		return 0, testError{fmt.Errorf("got Write(%q), want Write(%q)", b, s)}
    73  	}
    74  	if len(s) > len(b) {
    75  		f.ops[0] = s[len(b):]
    76  	} else {
    77  		f.ops = f.ops[1:]
    78  	}
    79  	f.pos += int64(len(b))
    80  	return len(b), nil
    81  }
    82  
    83  func (f *testFile) Seek(pos int64, whence int) (int64, error) {
    84  	if pos == 0 && whence == io.SeekCurrent {
    85  		return f.pos, nil
    86  	}
    87  	if len(f.ops) == 0 {
    88  		return 0, errors.New("unexpected Seek operation")
    89  	}
    90  	s, ok := f.ops[0].(int64)
    91  	if !ok {
    92  		return 0, errors.New("unexpected Seek operation")
    93  	}
    94  
    95  	if s != pos || whence != io.SeekCurrent {
    96  		return 0, testError{fmt.Errorf("got Seek(%d, %d), want Seek(%d, %d)", pos, whence, s, io.SeekCurrent)}
    97  	}
    98  	f.pos += s
    99  	f.ops = f.ops[1:]
   100  	return f.pos, nil
   101  }
   102  
   103  func TestSparseEntries(t *testing.T) {
   104  	vectors := []struct {
   105  		in   []sparseEntry
   106  		size int64
   107  
   108  		wantValid    bool          // Result of validateSparseEntries
   109  		wantAligned  []sparseEntry // Result of alignSparseEntries
   110  		wantInverted []sparseEntry // Result of invertSparseEntries
   111  	}{{
   112  		in: []sparseEntry{}, size: 0,
   113  		wantValid:    true,
   114  		wantInverted: []sparseEntry{{0, 0}},
   115  	}, {
   116  		in: []sparseEntry{}, size: 5000,
   117  		wantValid:    true,
   118  		wantInverted: []sparseEntry{{0, 5000}},
   119  	}, {
   120  		in: []sparseEntry{{0, 5000}}, size: 5000,
   121  		wantValid:    true,
   122  		wantAligned:  []sparseEntry{{0, 5000}},
   123  		wantInverted: []sparseEntry{{5000, 0}},
   124  	}, {
   125  		in: []sparseEntry{{1000, 4000}}, size: 5000,
   126  		wantValid:    true,
   127  		wantAligned:  []sparseEntry{{1024, 3976}},
   128  		wantInverted: []sparseEntry{{0, 1000}, {5000, 0}},
   129  	}, {
   130  		in: []sparseEntry{{0, 3000}}, size: 5000,
   131  		wantValid:    true,
   132  		wantAligned:  []sparseEntry{{0, 2560}},
   133  		wantInverted: []sparseEntry{{3000, 2000}},
   134  	}, {
   135  		in: []sparseEntry{{3000, 2000}}, size: 5000,
   136  		wantValid:    true,
   137  		wantAligned:  []sparseEntry{{3072, 1928}},
   138  		wantInverted: []sparseEntry{{0, 3000}, {5000, 0}},
   139  	}, {
   140  		in: []sparseEntry{{2000, 2000}}, size: 5000,
   141  		wantValid:    true,
   142  		wantAligned:  []sparseEntry{{2048, 1536}},
   143  		wantInverted: []sparseEntry{{0, 2000}, {4000, 1000}},
   144  	}, {
   145  		in: []sparseEntry{{0, 2000}, {8000, 2000}}, size: 10000,
   146  		wantValid:    true,
   147  		wantAligned:  []sparseEntry{{0, 1536}, {8192, 1808}},
   148  		wantInverted: []sparseEntry{{2000, 6000}, {10000, 0}},
   149  	}, {
   150  		in: []sparseEntry{{0, 2000}, {2000, 2000}, {4000, 0}, {4000, 3000}, {7000, 1000}, {8000, 0}, {8000, 2000}}, size: 10000,
   151  		wantValid:    true,
   152  		wantAligned:  []sparseEntry{{0, 1536}, {2048, 1536}, {4096, 2560}, {7168, 512}, {8192, 1808}},
   153  		wantInverted: []sparseEntry{{10000, 0}},
   154  	}, {
   155  		in: []sparseEntry{{0, 0}, {1000, 0}, {2000, 0}, {3000, 0}, {4000, 0}, {5000, 0}}, size: 5000,
   156  		wantValid:    true,
   157  		wantInverted: []sparseEntry{{0, 5000}},
   158  	}, {
   159  		in: []sparseEntry{{1, 0}}, size: 0,
   160  		wantValid: false,
   161  	}, {
   162  		in: []sparseEntry{{-1, 0}}, size: 100,
   163  		wantValid: false,
   164  	}, {
   165  		in: []sparseEntry{{0, -1}}, size: 100,
   166  		wantValid: false,
   167  	}, {
   168  		in: []sparseEntry{{0, 0}}, size: -100,
   169  		wantValid: false,
   170  	}, {
   171  		in: []sparseEntry{{math.MaxInt64, 3}, {6, -5}}, size: 35,
   172  		wantValid: false,
   173  	}, {
   174  		in: []sparseEntry{{1, 3}, {6, -5}}, size: 35,
   175  		wantValid: false,
   176  	}, {
   177  		in: []sparseEntry{{math.MaxInt64, math.MaxInt64}}, size: math.MaxInt64,
   178  		wantValid: false,
   179  	}, {
   180  		in: []sparseEntry{{3, 3}}, size: 5,
   181  		wantValid: false,
   182  	}, {
   183  		in: []sparseEntry{{2, 0}, {1, 0}, {0, 0}}, size: 3,
   184  		wantValid: false,
   185  	}, {
   186  		in: []sparseEntry{{1, 3}, {2, 2}}, size: 10,
   187  		wantValid: false,
   188  	}}
   189  
   190  	for i, v := range vectors {
   191  		gotValid := validateSparseEntries(v.in, v.size)
   192  		if gotValid != v.wantValid {
   193  			t.Errorf("test %d, validateSparseEntries() = %v, want %v", i, gotValid, v.wantValid)
   194  		}
   195  		if !v.wantValid {
   196  			continue
   197  		}
   198  		gotAligned := alignSparseEntries(append([]sparseEntry{}, v.in...), v.size)
   199  		if !slices.Equal(gotAligned, v.wantAligned) {
   200  			t.Errorf("test %d, alignSparseEntries():\ngot  %v\nwant %v", i, gotAligned, v.wantAligned)
   201  		}
   202  		gotInverted := invertSparseEntries(append([]sparseEntry{}, v.in...), v.size)
   203  		if !slices.Equal(gotInverted, v.wantInverted) {
   204  			t.Errorf("test %d, inverseSparseEntries():\ngot  %v\nwant %v", i, gotInverted, v.wantInverted)
   205  		}
   206  	}
   207  }
   208  
   209  func TestFileInfoHeader(t *testing.T) {
   210  	fi, err := os.Stat("testdata/small.txt")
   211  	if err != nil {
   212  		t.Fatal(err)
   213  	}
   214  	h, err := FileInfoHeader(fi, "")
   215  	if err != nil {
   216  		t.Fatalf("FileInfoHeader: %v", err)
   217  	}
   218  	if g, e := h.Name, "small.txt"; g != e {
   219  		t.Errorf("Name = %q; want %q", g, e)
   220  	}
   221  	if g, e := h.Mode, int64(fi.Mode().Perm()); g != e {
   222  		t.Errorf("Mode = %#o; want %#o", g, e)
   223  	}
   224  	if g, e := h.Size, int64(5); g != e {
   225  		t.Errorf("Size = %v; want %v", g, e)
   226  	}
   227  	if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
   228  		t.Errorf("ModTime = %v; want %v", g, e)
   229  	}
   230  	// FileInfoHeader should error when passing nil FileInfo
   231  	if _, err := FileInfoHeader(nil, ""); err == nil {
   232  		t.Fatalf("Expected error when passing nil to FileInfoHeader")
   233  	}
   234  }
   235  
   236  func TestFileInfoHeaderDir(t *testing.T) {
   237  	fi, err := os.Stat("testdata")
   238  	if err != nil {
   239  		t.Fatal(err)
   240  	}
   241  	h, err := FileInfoHeader(fi, "")
   242  	if err != nil {
   243  		t.Fatalf("FileInfoHeader: %v", err)
   244  	}
   245  	if g, e := h.Name, "testdata/"; g != e {
   246  		t.Errorf("Name = %q; want %q", g, e)
   247  	}
   248  	// Ignoring c_ISGID for golang.org/issue/4867
   249  	if g, e := h.Mode&^c_ISGID, int64(fi.Mode().Perm()); g != e {
   250  		t.Errorf("Mode = %#o; want %#o", g, e)
   251  	}
   252  	if g, e := h.Size, int64(0); g != e {
   253  		t.Errorf("Size = %v; want %v", g, e)
   254  	}
   255  	if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
   256  		t.Errorf("ModTime = %v; want %v", g, e)
   257  	}
   258  }
   259  
   260  func TestFileInfoHeaderSymlink(t *testing.T) {
   261  	testenv.MustHaveSymlink(t)
   262  
   263  	tmpdir := t.TempDir()
   264  
   265  	link := filepath.Join(tmpdir, "link")
   266  	target := tmpdir
   267  	if err := os.Symlink(target, link); err != nil {
   268  		t.Fatal(err)
   269  	}
   270  	fi, err := os.Lstat(link)
   271  	if err != nil {
   272  		t.Fatal(err)
   273  	}
   274  
   275  	h, err := FileInfoHeader(fi, target)
   276  	if err != nil {
   277  		t.Fatal(err)
   278  	}
   279  	if g, e := h.Name, fi.Name(); g != e {
   280  		t.Errorf("Name = %q; want %q", g, e)
   281  	}
   282  	if g, e := h.Linkname, target; g != e {
   283  		t.Errorf("Linkname = %q; want %q", g, e)
   284  	}
   285  	if g, e := h.Typeflag, byte(TypeSymlink); g != e {
   286  		t.Errorf("Typeflag = %v; want %v", g, e)
   287  	}
   288  }
   289  
   290  func TestRoundTrip(t *testing.T) {
   291  	data := []byte("some file contents")
   292  
   293  	var b bytes.Buffer
   294  	tw := NewWriter(&b)
   295  	hdr := &Header{
   296  		Name:       "file.txt",
   297  		Uid:        1 << 21, // Too big for 8 octal digits
   298  		Size:       int64(len(data)),
   299  		ModTime:    time.Now().Round(time.Second),
   300  		PAXRecords: map[string]string{"uid": "2097152"},
   301  		Format:     FormatPAX,
   302  		Typeflag:   TypeReg,
   303  	}
   304  	if err := tw.WriteHeader(hdr); err != nil {
   305  		t.Fatalf("tw.WriteHeader: %v", err)
   306  	}
   307  	if _, err := tw.Write(data); err != nil {
   308  		t.Fatalf("tw.Write: %v", err)
   309  	}
   310  	if err := tw.Close(); err != nil {
   311  		t.Fatalf("tw.Close: %v", err)
   312  	}
   313  
   314  	// Read it back.
   315  	tr := NewReader(&b)
   316  	rHdr, err := tr.Next()
   317  	if err != nil {
   318  		t.Fatalf("tr.Next: %v", err)
   319  	}
   320  	if !reflect.DeepEqual(rHdr, hdr) {
   321  		t.Errorf("Header mismatch.\n got %+v\nwant %+v", rHdr, hdr)
   322  	}
   323  	rData, err := io.ReadAll(tr)
   324  	if err != nil {
   325  		t.Fatalf("Read: %v", err)
   326  	}
   327  	if !bytes.Equal(rData, data) {
   328  		t.Errorf("Data mismatch.\n got %q\nwant %q", rData, data)
   329  	}
   330  }
   331  
   332  type headerRoundTripTest struct {
   333  	h  *Header
   334  	fm fs.FileMode
   335  }
   336  
   337  func TestHeaderRoundTrip(t *testing.T) {
   338  	vectors := []headerRoundTripTest{{
   339  		// regular file.
   340  		h: &Header{
   341  			Name:     "test.txt",
   342  			Mode:     0644,
   343  			Size:     12,
   344  			ModTime:  time.Unix(1360600916, 0),
   345  			Typeflag: TypeReg,
   346  		},
   347  		fm: 0644,
   348  	}, {
   349  		// symbolic link.
   350  		h: &Header{
   351  			Name:     "link.txt",
   352  			Mode:     0777,
   353  			Size:     0,
   354  			ModTime:  time.Unix(1360600852, 0),
   355  			Typeflag: TypeSymlink,
   356  		},
   357  		fm: 0777 | fs.ModeSymlink,
   358  	}, {
   359  		// character device node.
   360  		h: &Header{
   361  			Name:     "dev/null",
   362  			Mode:     0666,
   363  			Size:     0,
   364  			ModTime:  time.Unix(1360578951, 0),
   365  			Typeflag: TypeChar,
   366  		},
   367  		fm: 0666 | fs.ModeDevice | fs.ModeCharDevice,
   368  	}, {
   369  		// block device node.
   370  		h: &Header{
   371  			Name:     "dev/sda",
   372  			Mode:     0660,
   373  			Size:     0,
   374  			ModTime:  time.Unix(1360578954, 0),
   375  			Typeflag: TypeBlock,
   376  		},
   377  		fm: 0660 | fs.ModeDevice,
   378  	}, {
   379  		// directory.
   380  		h: &Header{
   381  			Name:     "dir/",
   382  			Mode:     0755,
   383  			Size:     0,
   384  			ModTime:  time.Unix(1360601116, 0),
   385  			Typeflag: TypeDir,
   386  		},
   387  		fm: 0755 | fs.ModeDir,
   388  	}, {
   389  		// fifo node.
   390  		h: &Header{
   391  			Name:     "dev/initctl",
   392  			Mode:     0600,
   393  			Size:     0,
   394  			ModTime:  time.Unix(1360578949, 0),
   395  			Typeflag: TypeFifo,
   396  		},
   397  		fm: 0600 | fs.ModeNamedPipe,
   398  	}, {
   399  		// setuid.
   400  		h: &Header{
   401  			Name:     "bin/su",
   402  			Mode:     0755 | c_ISUID,
   403  			Size:     23232,
   404  			ModTime:  time.Unix(1355405093, 0),
   405  			Typeflag: TypeReg,
   406  		},
   407  		fm: 0755 | fs.ModeSetuid,
   408  	}, {
   409  		// setguid.
   410  		h: &Header{
   411  			Name:     "group.txt",
   412  			Mode:     0750 | c_ISGID,
   413  			Size:     0,
   414  			ModTime:  time.Unix(1360602346, 0),
   415  			Typeflag: TypeReg,
   416  		},
   417  		fm: 0750 | fs.ModeSetgid,
   418  	}, {
   419  		// sticky.
   420  		h: &Header{
   421  			Name:     "sticky.txt",
   422  			Mode:     0600 | c_ISVTX,
   423  			Size:     7,
   424  			ModTime:  time.Unix(1360602540, 0),
   425  			Typeflag: TypeReg,
   426  		},
   427  		fm: 0600 | fs.ModeSticky,
   428  	}, {
   429  		// hard link.
   430  		h: &Header{
   431  			Name:     "hard.txt",
   432  			Mode:     0644,
   433  			Size:     0,
   434  			Linkname: "file.txt",
   435  			ModTime:  time.Unix(1360600916, 0),
   436  			Typeflag: TypeLink,
   437  		},
   438  		fm: 0644,
   439  	}, {
   440  		// More information.
   441  		h: &Header{
   442  			Name:     "info.txt",
   443  			Mode:     0600,
   444  			Size:     0,
   445  			Uid:      1000,
   446  			Gid:      1000,
   447  			ModTime:  time.Unix(1360602540, 0),
   448  			Uname:    "slartibartfast",
   449  			Gname:    "users",
   450  			Typeflag: TypeReg,
   451  		},
   452  		fm: 0600,
   453  	}}
   454  
   455  	for i, v := range vectors {
   456  		fi := v.h.FileInfo()
   457  		h2, err := FileInfoHeader(fi, "")
   458  		if err != nil {
   459  			t.Error(err)
   460  			continue
   461  		}
   462  		if strings.Contains(fi.Name(), "/") {
   463  			t.Errorf("FileInfo of %q contains slash: %q", v.h.Name, fi.Name())
   464  		}
   465  		name := path.Base(v.h.Name)
   466  		if fi.IsDir() {
   467  			name += "/"
   468  		}
   469  		if got, want := h2.Name, name; got != want {
   470  			t.Errorf("i=%d: Name: got %v, want %v", i, got, want)
   471  		}
   472  		if got, want := h2.Size, v.h.Size; got != want {
   473  			t.Errorf("i=%d: Size: got %v, want %v", i, got, want)
   474  		}
   475  		if got, want := h2.Uid, v.h.Uid; got != want {
   476  			t.Errorf("i=%d: Uid: got %d, want %d", i, got, want)
   477  		}
   478  		if got, want := h2.Gid, v.h.Gid; got != want {
   479  			t.Errorf("i=%d: Gid: got %d, want %d", i, got, want)
   480  		}
   481  		if got, want := h2.Uname, v.h.Uname; got != want {
   482  			t.Errorf("i=%d: Uname: got %q, want %q", i, got, want)
   483  		}
   484  		if got, want := h2.Gname, v.h.Gname; got != want {
   485  			t.Errorf("i=%d: Gname: got %q, want %q", i, got, want)
   486  		}
   487  		if got, want := h2.Linkname, v.h.Linkname; got != want {
   488  			t.Errorf("i=%d: Linkname: got %v, want %v", i, got, want)
   489  		}
   490  		if got, want := h2.Typeflag, v.h.Typeflag; got != want {
   491  			t.Logf("%#v %#v", v.h, fi.Sys())
   492  			t.Errorf("i=%d: Typeflag: got %q, want %q", i, got, want)
   493  		}
   494  		if got, want := h2.Mode, v.h.Mode; got != want {
   495  			t.Errorf("i=%d: Mode: got %o, want %o", i, got, want)
   496  		}
   497  		if got, want := fi.Mode(), v.fm; got != want {
   498  			t.Errorf("i=%d: fi.Mode: got %o, want %o", i, got, want)
   499  		}
   500  		if got, want := h2.AccessTime, v.h.AccessTime; got != want {
   501  			t.Errorf("i=%d: AccessTime: got %v, want %v", i, got, want)
   502  		}
   503  		if got, want := h2.ChangeTime, v.h.ChangeTime; got != want {
   504  			t.Errorf("i=%d: ChangeTime: got %v, want %v", i, got, want)
   505  		}
   506  		if got, want := h2.ModTime, v.h.ModTime; got != want {
   507  			t.Errorf("i=%d: ModTime: got %v, want %v", i, got, want)
   508  		}
   509  		if sysh, ok := fi.Sys().(*Header); !ok || sysh != v.h {
   510  			t.Errorf("i=%d: Sys didn't return original *Header", i)
   511  		}
   512  	}
   513  }
   514  
   515  func TestHeaderAllowedFormats(t *testing.T) {
   516  	vectors := []struct {
   517  		header  *Header           // Input header
   518  		paxHdrs map[string]string // Expected PAX headers that may be needed
   519  		formats Format            // Expected formats that can encode the header
   520  	}{{
   521  		header:  &Header{},
   522  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   523  	}, {
   524  		header:  &Header{Size: 077777777777},
   525  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   526  	}, {
   527  		header:  &Header{Size: 077777777777, Format: FormatUSTAR},
   528  		formats: FormatUSTAR,
   529  	}, {
   530  		header:  &Header{Size: 077777777777, Format: FormatPAX},
   531  		formats: FormatUSTAR | FormatPAX,
   532  	}, {
   533  		header:  &Header{Size: 077777777777, Format: FormatGNU},
   534  		formats: FormatGNU,
   535  	}, {
   536  		header:  &Header{Size: 077777777777 + 1},
   537  		paxHdrs: map[string]string{paxSize: "8589934592"},
   538  		formats: FormatPAX | FormatGNU,
   539  	}, {
   540  		header:  &Header{Size: 077777777777 + 1, Format: FormatPAX},
   541  		paxHdrs: map[string]string{paxSize: "8589934592"},
   542  		formats: FormatPAX,
   543  	}, {
   544  		header:  &Header{Size: 077777777777 + 1, Format: FormatGNU},
   545  		paxHdrs: map[string]string{paxSize: "8589934592"},
   546  		formats: FormatGNU,
   547  	}, {
   548  		header:  &Header{Mode: 07777777},
   549  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   550  	}, {
   551  		header:  &Header{Mode: 07777777 + 1},
   552  		formats: FormatGNU,
   553  	}, {
   554  		header:  &Header{Devmajor: -123},
   555  		formats: FormatGNU,
   556  	}, {
   557  		header:  &Header{Devmajor: 1<<56 - 1},
   558  		formats: FormatGNU,
   559  	}, {
   560  		header:  &Header{Devmajor: 1 << 56},
   561  		formats: FormatUnknown,
   562  	}, {
   563  		header:  &Header{Devmajor: -1 << 56},
   564  		formats: FormatGNU,
   565  	}, {
   566  		header:  &Header{Devmajor: -1<<56 - 1},
   567  		formats: FormatUnknown,
   568  	}, {
   569  		header:  &Header{Name: "用戶名", Devmajor: -1 << 56},
   570  		formats: FormatGNU,
   571  	}, {
   572  		header:  &Header{Size: math.MaxInt64},
   573  		paxHdrs: map[string]string{paxSize: "9223372036854775807"},
   574  		formats: FormatPAX | FormatGNU,
   575  	}, {
   576  		header:  &Header{Size: math.MinInt64},
   577  		paxHdrs: map[string]string{paxSize: "-9223372036854775808"},
   578  		formats: FormatUnknown,
   579  	}, {
   580  		header:  &Header{Uname: "0123456789abcdef0123456789abcdef"},
   581  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   582  	}, {
   583  		header:  &Header{Uname: "0123456789abcdef0123456789abcdefx"},
   584  		paxHdrs: map[string]string{paxUname: "0123456789abcdef0123456789abcdefx"},
   585  		formats: FormatPAX,
   586  	}, {
   587  		header:  &Header{Name: "foobar"},
   588  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   589  	}, {
   590  		header:  &Header{Name: strings.Repeat("a", nameSize)},
   591  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   592  	}, {
   593  		header:  &Header{Name: strings.Repeat("a", nameSize+1)},
   594  		paxHdrs: map[string]string{paxPath: strings.Repeat("a", nameSize+1)},
   595  		formats: FormatPAX | FormatGNU,
   596  	}, {
   597  		header:  &Header{Linkname: "用戶名"},
   598  		paxHdrs: map[string]string{paxLinkpath: "用戶名"},
   599  		formats: FormatPAX | FormatGNU,
   600  	}, {
   601  		header:  &Header{Linkname: strings.Repeat("用戶名\x00", nameSize)},
   602  		paxHdrs: map[string]string{paxLinkpath: strings.Repeat("用戶名\x00", nameSize)},
   603  		formats: FormatUnknown,
   604  	}, {
   605  		header:  &Header{Linkname: "\x00hello"},
   606  		paxHdrs: map[string]string{paxLinkpath: "\x00hello"},
   607  		formats: FormatUnknown,
   608  	}, {
   609  		header:  &Header{Uid: 07777777},
   610  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   611  	}, {
   612  		header:  &Header{Uid: 07777777 + 1},
   613  		paxHdrs: map[string]string{paxUid: "2097152"},
   614  		formats: FormatPAX | FormatGNU,
   615  	}, {
   616  		header:  &Header{Xattrs: nil},
   617  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   618  	}, {
   619  		header:  &Header{Xattrs: map[string]string{"foo": "bar"}},
   620  		paxHdrs: map[string]string{paxSchilyXattr + "foo": "bar"},
   621  		formats: FormatPAX,
   622  	}, {
   623  		header:  &Header{Xattrs: map[string]string{"foo": "bar"}, Format: FormatGNU},
   624  		paxHdrs: map[string]string{paxSchilyXattr + "foo": "bar"},
   625  		formats: FormatUnknown,
   626  	}, {
   627  		header:  &Header{Xattrs: map[string]string{"用戶名": "\x00hello"}},
   628  		paxHdrs: map[string]string{paxSchilyXattr + "用戶名": "\x00hello"},
   629  		formats: FormatPAX,
   630  	}, {
   631  		header:  &Header{Xattrs: map[string]string{"foo=bar": "baz"}},
   632  		formats: FormatUnknown,
   633  	}, {
   634  		header:  &Header{Xattrs: map[string]string{"foo": ""}},
   635  		paxHdrs: map[string]string{paxSchilyXattr + "foo": ""},
   636  		formats: FormatPAX,
   637  	}, {
   638  		header:  &Header{ModTime: time.Unix(0, 0)},
   639  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   640  	}, {
   641  		header:  &Header{ModTime: time.Unix(077777777777, 0)},
   642  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   643  	}, {
   644  		header:  &Header{ModTime: time.Unix(077777777777+1, 0)},
   645  		paxHdrs: map[string]string{paxMtime: "8589934592"},
   646  		formats: FormatPAX | FormatGNU,
   647  	}, {
   648  		header:  &Header{ModTime: time.Unix(math.MaxInt64, 0)},
   649  		paxHdrs: map[string]string{paxMtime: "9223372036854775807"},
   650  		formats: FormatPAX | FormatGNU,
   651  	}, {
   652  		header:  &Header{ModTime: time.Unix(math.MaxInt64, 0), Format: FormatUSTAR},
   653  		paxHdrs: map[string]string{paxMtime: "9223372036854775807"},
   654  		formats: FormatUnknown,
   655  	}, {
   656  		header:  &Header{ModTime: time.Unix(-1, 0)},
   657  		paxHdrs: map[string]string{paxMtime: "-1"},
   658  		formats: FormatPAX | FormatGNU,
   659  	}, {
   660  		header:  &Header{ModTime: time.Unix(1, 500)},
   661  		paxHdrs: map[string]string{paxMtime: "1.0000005"},
   662  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   663  	}, {
   664  		header:  &Header{ModTime: time.Unix(1, 0)},
   665  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   666  	}, {
   667  		header:  &Header{ModTime: time.Unix(1, 0), Format: FormatPAX},
   668  		formats: FormatUSTAR | FormatPAX,
   669  	}, {
   670  		header:  &Header{ModTime: time.Unix(1, 500), Format: FormatUSTAR},
   671  		paxHdrs: map[string]string{paxMtime: "1.0000005"},
   672  		formats: FormatUSTAR,
   673  	}, {
   674  		header:  &Header{ModTime: time.Unix(1, 500), Format: FormatPAX},
   675  		paxHdrs: map[string]string{paxMtime: "1.0000005"},
   676  		formats: FormatPAX,
   677  	}, {
   678  		header:  &Header{ModTime: time.Unix(1, 500), Format: FormatGNU},
   679  		paxHdrs: map[string]string{paxMtime: "1.0000005"},
   680  		formats: FormatGNU,
   681  	}, {
   682  		header:  &Header{ModTime: time.Unix(-1, 500)},
   683  		paxHdrs: map[string]string{paxMtime: "-0.9999995"},
   684  		formats: FormatPAX | FormatGNU,
   685  	}, {
   686  		header:  &Header{ModTime: time.Unix(-1, 500), Format: FormatGNU},
   687  		paxHdrs: map[string]string{paxMtime: "-0.9999995"},
   688  		formats: FormatGNU,
   689  	}, {
   690  		header:  &Header{AccessTime: time.Unix(0, 0)},
   691  		paxHdrs: map[string]string{paxAtime: "0"},
   692  		formats: FormatPAX | FormatGNU,
   693  	}, {
   694  		header:  &Header{AccessTime: time.Unix(0, 0), Format: FormatUSTAR},
   695  		paxHdrs: map[string]string{paxAtime: "0"},
   696  		formats: FormatUnknown,
   697  	}, {
   698  		header:  &Header{AccessTime: time.Unix(0, 0), Format: FormatPAX},
   699  		paxHdrs: map[string]string{paxAtime: "0"},
   700  		formats: FormatPAX,
   701  	}, {
   702  		header:  &Header{AccessTime: time.Unix(0, 0), Format: FormatGNU},
   703  		paxHdrs: map[string]string{paxAtime: "0"},
   704  		formats: FormatGNU,
   705  	}, {
   706  		header:  &Header{AccessTime: time.Unix(-123, 0)},
   707  		paxHdrs: map[string]string{paxAtime: "-123"},
   708  		formats: FormatPAX | FormatGNU,
   709  	}, {
   710  		header:  &Header{AccessTime: time.Unix(-123, 0), Format: FormatPAX},
   711  		paxHdrs: map[string]string{paxAtime: "-123"},
   712  		formats: FormatPAX,
   713  	}, {
   714  		header:  &Header{ChangeTime: time.Unix(123, 456)},
   715  		paxHdrs: map[string]string{paxCtime: "123.000000456"},
   716  		formats: FormatPAX | FormatGNU,
   717  	}, {
   718  		header:  &Header{ChangeTime: time.Unix(123, 456), Format: FormatUSTAR},
   719  		paxHdrs: map[string]string{paxCtime: "123.000000456"},
   720  		formats: FormatUnknown,
   721  	}, {
   722  		header:  &Header{ChangeTime: time.Unix(123, 456), Format: FormatGNU},
   723  		paxHdrs: map[string]string{paxCtime: "123.000000456"},
   724  		formats: FormatGNU,
   725  	}, {
   726  		header:  &Header{ChangeTime: time.Unix(123, 456), Format: FormatPAX},
   727  		paxHdrs: map[string]string{paxCtime: "123.000000456"},
   728  		formats: FormatPAX,
   729  	}, {
   730  		header:  &Header{Name: "foo/", Typeflag: TypeDir},
   731  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   732  	}, {
   733  		header:  &Header{Name: "foo/", Typeflag: TypeReg},
   734  		formats: FormatUnknown,
   735  	}, {
   736  		header:  &Header{Name: "foo/", Typeflag: TypeSymlink},
   737  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   738  	}}
   739  
   740  	for i, v := range vectors {
   741  		formats, paxHdrs, err := v.header.allowedFormats()
   742  		if formats != v.formats {
   743  			t.Errorf("test %d, allowedFormats(): got %v, want %v", i, formats, v.formats)
   744  		}
   745  		if formats&FormatPAX > 0 && !maps.Equal(paxHdrs, v.paxHdrs) && !(len(paxHdrs) == 0 && len(v.paxHdrs) == 0) {
   746  			t.Errorf("test %d, allowedFormats():\ngot  %v\nwant %s", i, paxHdrs, v.paxHdrs)
   747  		}
   748  		if (formats != FormatUnknown) && (err != nil) {
   749  			t.Errorf("test %d, unexpected error: %v", i, err)
   750  		}
   751  		if (formats == FormatUnknown) && (err == nil) {
   752  			t.Errorf("test %d, got nil-error, want non-nil error", i)
   753  		}
   754  	}
   755  }
   756  
   757  func Benchmark(b *testing.B) {
   758  	type file struct {
   759  		hdr  *Header
   760  		body []byte
   761  	}
   762  
   763  	vectors := []struct {
   764  		label string
   765  		files []file
   766  	}{{
   767  		"USTAR",
   768  		[]file{{
   769  			&Header{Name: "bar", Mode: 0640, Size: int64(3)},
   770  			[]byte("foo"),
   771  		}, {
   772  			&Header{Name: "world", Mode: 0640, Size: int64(5)},
   773  			[]byte("hello"),
   774  		}},
   775  	}, {
   776  		"GNU",
   777  		[]file{{
   778  			&Header{Name: "bar", Mode: 0640, Size: int64(3), Devmajor: -1},
   779  			[]byte("foo"),
   780  		}, {
   781  			&Header{Name: "world", Mode: 0640, Size: int64(5), Devmajor: -1},
   782  			[]byte("hello"),
   783  		}},
   784  	}, {
   785  		"PAX",
   786  		[]file{{
   787  			&Header{Name: "bar", Mode: 0640, Size: int64(3), Xattrs: map[string]string{"foo": "bar"}},
   788  			[]byte("foo"),
   789  		}, {
   790  			&Header{Name: "world", Mode: 0640, Size: int64(5), Xattrs: map[string]string{"foo": "bar"}},
   791  			[]byte("hello"),
   792  		}},
   793  	}}
   794  
   795  	b.Run("Writer", func(b *testing.B) {
   796  		for _, v := range vectors {
   797  			b.Run(v.label, func(b *testing.B) {
   798  				b.ReportAllocs()
   799  				for i := 0; i < b.N; i++ {
   800  					// Writing to io.Discard because we want to
   801  					// test purely the writer code and not bring in disk performance into this.
   802  					tw := NewWriter(io.Discard)
   803  					for _, file := range v.files {
   804  						if err := tw.WriteHeader(file.hdr); err != nil {
   805  							b.Errorf("unexpected WriteHeader error: %v", err)
   806  						}
   807  						if _, err := tw.Write(file.body); err != nil {
   808  							b.Errorf("unexpected Write error: %v", err)
   809  						}
   810  					}
   811  					if err := tw.Close(); err != nil {
   812  						b.Errorf("unexpected Close error: %v", err)
   813  					}
   814  				}
   815  			})
   816  		}
   817  	})
   818  
   819  	b.Run("Reader", func(b *testing.B) {
   820  		for _, v := range vectors {
   821  			var buf bytes.Buffer
   822  			var r bytes.Reader
   823  
   824  			// Write the archive to a byte buffer.
   825  			tw := NewWriter(&buf)
   826  			for _, file := range v.files {
   827  				tw.WriteHeader(file.hdr)
   828  				tw.Write(file.body)
   829  			}
   830  			tw.Close()
   831  			b.Run(v.label, func(b *testing.B) {
   832  				b.ReportAllocs()
   833  				// Read from the byte buffer.
   834  				for i := 0; i < b.N; i++ {
   835  					r.Reset(buf.Bytes())
   836  					tr := NewReader(&r)
   837  					if _, err := tr.Next(); err != nil {
   838  						b.Errorf("unexpected Next error: %v", err)
   839  					}
   840  					if _, err := io.Copy(io.Discard, tr); err != nil {
   841  						b.Errorf("unexpected Copy error : %v", err)
   842  					}
   843  				}
   844  			})
   845  		}
   846  	})
   847  
   848  }
   849  
   850  var _ fileInfoNames = fileInfoNames{}
   851  
   852  type fileInfoNames struct{}
   853  
   854  func (f *fileInfoNames) Name() string {
   855  	return "tmp"
   856  }
   857  
   858  func (f *fileInfoNames) Size() int64 {
   859  	return 0
   860  }
   861  
   862  func (f *fileInfoNames) Mode() fs.FileMode {
   863  	return 0777
   864  }
   865  
   866  func (f *fileInfoNames) ModTime() time.Time {
   867  	return time.Time{}
   868  }
   869  
   870  func (f *fileInfoNames) IsDir() bool {
   871  	return false
   872  }
   873  
   874  func (f *fileInfoNames) Sys() any {
   875  	return nil
   876  }
   877  
   878  func (f *fileInfoNames) Uname() (string, error) {
   879  	return "Uname", nil
   880  }
   881  
   882  func (f *fileInfoNames) Gname() (string, error) {
   883  	return "Gname", nil
   884  }
   885  
   886  func TestFileInfoHeaderUseFileInfoNames(t *testing.T) {
   887  	info := &fileInfoNames{}
   888  	header, err := FileInfoHeader(info, "")
   889  	if err != nil {
   890  		t.Fatal(err)
   891  	}
   892  	if header.Uname != "Uname" {
   893  		t.Fatalf("header.Uname: got %s, want %s", header.Uname, "Uname")
   894  	}
   895  	if header.Gname != "Gname" {
   896  		t.Fatalf("header.Gname: got %s, want %s", header.Gname, "Gname")
   897  	}
   898  }
   899  

View as plain text