github.com/containerd/Containerd@v1.4.13/archive/tar_test.go (about)

     1  // +build !windows
     2  
     3  /*
     4     Copyright The containerd Authors.
     5  
     6     Licensed under the Apache License, Version 2.0 (the "License");
     7     you may not use this file except in compliance with the License.
     8     You may obtain a copy of the License at
     9  
    10         http://www.apache.org/licenses/LICENSE-2.0
    11  
    12     Unless required by applicable law or agreed to in writing, software
    13     distributed under the License is distributed on an "AS IS" BASIS,
    14     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15     See the License for the specific language governing permissions and
    16     limitations under the License.
    17  */
    18  
    19  package archive
    20  
    21  import (
    22  	"archive/tar"
    23  	"bytes"
    24  	"context"
    25  	"fmt"
    26  	"io"
    27  	"io/ioutil"
    28  	"os"
    29  	"os/exec"
    30  	"path/filepath"
    31  	"testing"
    32  	"time"
    33  
    34  	_ "crypto/sha256"
    35  
    36  	"github.com/containerd/containerd/archive/tartest"
    37  	"github.com/containerd/containerd/pkg/testutil"
    38  	"github.com/containerd/continuity/fs"
    39  	"github.com/containerd/continuity/fs/fstest"
    40  	"github.com/pkg/errors"
    41  )
    42  
    43  const tarCmd = "tar"
    44  
    45  // baseApplier creates a basic filesystem layout
    46  // with multiple types of files for basic tests.
    47  var baseApplier = fstest.Apply(
    48  	fstest.CreateDir("/etc/", 0755),
    49  	fstest.CreateFile("/etc/hosts", []byte("127.0.0.1 localhost"), 0644),
    50  	fstest.Link("/etc/hosts", "/etc/hosts.allow"),
    51  	fstest.CreateDir("/usr/local/lib", 0755),
    52  	fstest.CreateFile("/usr/local/lib/libnothing.so", []byte{0x00, 0x00}, 0755),
    53  	fstest.Symlink("libnothing.so", "/usr/local/lib/libnothing.so.2"),
    54  	fstest.CreateDir("/home", 0755),
    55  	fstest.CreateDir("/home/derek", 0700),
    56  )
    57  
    58  func TestUnpack(t *testing.T) {
    59  	requireTar(t)
    60  
    61  	if err := testApply(baseApplier); err != nil {
    62  		t.Fatalf("Test apply failed: %+v", err)
    63  	}
    64  }
    65  
    66  func TestBaseDiff(t *testing.T) {
    67  	requireTar(t)
    68  
    69  	if err := testBaseDiff(baseApplier); err != nil {
    70  		t.Fatalf("Test base diff failed: %+v", err)
    71  	}
    72  }
    73  
    74  func TestRelativeSymlinks(t *testing.T) {
    75  	breakoutLinks := []fstest.Applier{
    76  		fstest.Apply(
    77  			baseApplier,
    78  			fstest.Symlink("../other", "/home/derek/other"),
    79  			fstest.Symlink("../../etc", "/home/derek/etc"),
    80  			fstest.Symlink("up/../../other", "/home/derek/updown"),
    81  		),
    82  		fstest.Apply(
    83  			baseApplier,
    84  			fstest.Symlink("../../../breakout", "/home/derek/breakout"),
    85  		),
    86  		fstest.Apply(
    87  			baseApplier,
    88  			fstest.Symlink("../../breakout", "/breakout"),
    89  		),
    90  		fstest.Apply(
    91  			baseApplier,
    92  			fstest.Symlink("etc/../../upandout", "/breakout"),
    93  		),
    94  		fstest.Apply(
    95  			baseApplier,
    96  			fstest.Symlink("derek/../../../downandout", "/home/breakout"),
    97  		),
    98  		fstest.Apply(
    99  			baseApplier,
   100  			fstest.Symlink("/etc", "localetc"),
   101  		),
   102  	}
   103  
   104  	for _, bo := range breakoutLinks {
   105  		if err := testDiffApply(bo); err != nil {
   106  			t.Fatalf("Test apply failed: %+v", err)
   107  		}
   108  	}
   109  }
   110  
   111  func TestSymlinks(t *testing.T) {
   112  	links := [][2]fstest.Applier{
   113  		{
   114  			fstest.Apply(
   115  				fstest.CreateDir("/bin/", 0755),
   116  				fstest.CreateFile("/bin/superbinary", []byte{0x00, 0x00}, 0755),
   117  				fstest.Symlink("../bin/superbinary", "/bin/other1"),
   118  			),
   119  			fstest.Apply(
   120  				fstest.Remove("/bin/other1"),
   121  				fstest.Symlink("/bin/superbinary", "/bin/other1"),
   122  				fstest.Symlink("../bin/superbinary", "/bin/other2"),
   123  				fstest.Symlink("superbinary", "/bin/other3"),
   124  			),
   125  		},
   126  		{
   127  			fstest.Apply(
   128  				fstest.CreateDir("/bin/", 0755),
   129  				fstest.CreateDir("/sbin/", 0755),
   130  				fstest.CreateFile("/sbin/superbinary", []byte{0x00, 0x00}, 0755),
   131  				fstest.Symlink("/sbin/superbinary", "/bin/superbinary"),
   132  				fstest.Symlink("../bin/superbinary", "/bin/other1"),
   133  			),
   134  			fstest.Apply(
   135  				fstest.Remove("/bin/other1"),
   136  				fstest.Symlink("/bin/superbinary", "/bin/other1"),
   137  				fstest.Symlink("superbinary", "/bin/other2"),
   138  			),
   139  		},
   140  		{
   141  			fstest.Apply(
   142  				fstest.CreateDir("/bin/", 0755),
   143  				fstest.CreateDir("/sbin/", 0755),
   144  				fstest.CreateFile("/sbin/superbinary", []byte{0x00, 0x00}, 0755),
   145  				fstest.Symlink("../sbin/superbinary", "/bin/superbinary"),
   146  				fstest.Symlink("../bin/superbinary", "/bin/other1"),
   147  			),
   148  			fstest.Apply(
   149  				fstest.Remove("/bin/other1"),
   150  				fstest.Symlink("/bin/superbinary", "/bin/other1"),
   151  			),
   152  		},
   153  		{
   154  			fstest.Apply(
   155  				fstest.CreateDir("/bin/", 0755),
   156  				fstest.CreateFile("/bin/actualbinary", []byte{0x00, 0x00}, 0755),
   157  				fstest.Symlink("actualbinary", "/bin/superbinary"),
   158  				fstest.Symlink("../bin/superbinary", "/bin/other1"),
   159  				fstest.Symlink("superbinary", "/bin/other2"),
   160  			),
   161  			fstest.Apply(
   162  				fstest.Remove("/bin/other1"),
   163  				fstest.Remove("/bin/other2"),
   164  				fstest.Symlink("/bin/superbinary", "/bin/other1"),
   165  				fstest.Symlink("superbinary", "/bin/other2"),
   166  			),
   167  		},
   168  		{
   169  			fstest.Apply(
   170  				fstest.CreateDir("/bin/", 0755),
   171  				fstest.CreateFile("/bin/actualbinary", []byte{0x00, 0x00}, 0755),
   172  				fstest.Symlink("actualbinary", "/bin/myapp"),
   173  			),
   174  			fstest.Apply(
   175  				fstest.Remove("/bin/myapp"),
   176  				fstest.CreateDir("/bin/myapp", 0755),
   177  			),
   178  		},
   179  	}
   180  
   181  	for i, l := range links {
   182  		if err := testDiffApply(l[0], l[1]); err != nil {
   183  			t.Fatalf("Test[%d] apply failed: %+v", i+1, err)
   184  		}
   185  	}
   186  }
   187  
   188  func TestTarWithXattr(t *testing.T) {
   189  	testutil.RequiresRoot(t)
   190  
   191  	fileXattrExist := func(f1, xattrKey, xattrValue string) func(string) error {
   192  		return func(root string) error {
   193  			values, err := getxattr(filepath.Join(root, f1), xattrKey)
   194  			if err != nil {
   195  				return err
   196  			}
   197  			if xattrValue != string(values) {
   198  				return fmt.Errorf("file xattrs expect to be %s, actually get %s", xattrValue, values)
   199  			}
   200  			return nil
   201  		}
   202  	}
   203  
   204  	tests := []struct {
   205  		name  string
   206  		key   string
   207  		value string
   208  		err   error
   209  	}{
   210  		{
   211  			name:  "WithXattrsUser",
   212  			key:   "user.key",
   213  			value: "value",
   214  		},
   215  		{
   216  			// security related xattrs need root permission to test
   217  			name:  "WithXattrSelinux",
   218  			key:   "security.selinux",
   219  			value: "unconfined_u:object_r:default_t:s0\x00",
   220  		},
   221  	}
   222  	for _, at := range tests {
   223  		tc := tartest.TarContext{}.WithUIDGID(os.Getuid(), os.Getgid()).WithModTime(time.Now().UTC()).WithXattrs(map[string]string{
   224  			at.key: at.value,
   225  		})
   226  		w := tartest.TarAll(tc.File("/file", []byte{}, 0755))
   227  		validator := fileXattrExist("file", at.key, at.value)
   228  		t.Run(at.name, makeWriterToTarTest(w, nil, validator, at.err))
   229  	}
   230  }
   231  
   232  func TestBreakouts(t *testing.T) {
   233  	tc := tartest.TarContext{}.WithUIDGID(os.Getuid(), os.Getgid()).WithModTime(time.Now().UTC())
   234  	expected := "unbroken"
   235  	unbrokenCheck := func(root string) error {
   236  		b, err := ioutil.ReadFile(filepath.Join(root, "etc", "unbroken"))
   237  		if err != nil {
   238  			return errors.Wrap(err, "failed to read unbroken")
   239  		}
   240  		if string(b) != expected {
   241  			return errors.Errorf("/etc/unbroken: unexpected value %s, expected %s", b, expected)
   242  		}
   243  		return nil
   244  	}
   245  	errFileDiff := errors.New("files differ")
   246  	td, err := ioutil.TempDir("", "test-breakouts-")
   247  	if err != nil {
   248  		t.Fatal(err)
   249  	}
   250  	defer os.RemoveAll(td)
   251  
   252  	isSymlinkFile := func(f string) func(string) error {
   253  		return func(root string) error {
   254  			fi, err := os.Lstat(filepath.Join(root, f))
   255  			if err != nil {
   256  				return err
   257  			}
   258  
   259  			if got := fi.Mode() & os.ModeSymlink; got != os.ModeSymlink {
   260  				return errors.Errorf("%s should be symlink", fi.Name())
   261  			}
   262  			return nil
   263  		}
   264  	}
   265  
   266  	sameSymlinkFile := func(f1, f2 string) func(string) error {
   267  		checkF1, checkF2 := isSymlinkFile(f1), isSymlinkFile(f2)
   268  		return func(root string) error {
   269  			if err := checkF1(root); err != nil {
   270  				return err
   271  			}
   272  
   273  			if err := checkF2(root); err != nil {
   274  				return err
   275  			}
   276  
   277  			t1, err := os.Readlink(filepath.Join(root, f1))
   278  			if err != nil {
   279  				return err
   280  			}
   281  
   282  			t2, err := os.Readlink(filepath.Join(root, f2))
   283  			if err != nil {
   284  				return err
   285  			}
   286  
   287  			if t1 != t2 {
   288  				return errors.Wrapf(errFileDiff, "%#v and %#v", t1, t2)
   289  			}
   290  			return nil
   291  		}
   292  	}
   293  
   294  	sameFile := func(f1, f2 string) func(string) error {
   295  		return func(root string) error {
   296  			p1, err := fs.RootPath(root, f1)
   297  			if err != nil {
   298  				return err
   299  			}
   300  			p2, err := fs.RootPath(root, f2)
   301  			if err != nil {
   302  				return err
   303  			}
   304  			s1, err := os.Stat(p1)
   305  			if err != nil {
   306  				return err
   307  			}
   308  			s2, err := os.Stat(p2)
   309  			if err != nil {
   310  				return err
   311  			}
   312  			if !os.SameFile(s1, s2) {
   313  				return errors.Wrapf(errFileDiff, "%#v and %#v", s1, s2)
   314  			}
   315  			return nil
   316  		}
   317  	}
   318  	notSameFile := func(f1, f2 string) func(string) error {
   319  		same := sameFile(f1, f2)
   320  		return func(root string) error {
   321  			err := same(root)
   322  			if err == nil {
   323  				return errors.New("files are the same, expected diff")
   324  			}
   325  			if !errors.Is(err, errFileDiff) {
   326  				return err
   327  			}
   328  			return nil
   329  		}
   330  	}
   331  	fileValue := func(f1 string, content []byte) func(string) error {
   332  		return func(root string) error {
   333  			b, err := ioutil.ReadFile(filepath.Join(root, f1))
   334  			if err != nil {
   335  				return err
   336  			}
   337  			if !bytes.Equal(b, content) {
   338  				return errors.Errorf("content differs: expected %v, got %v", content, b)
   339  			}
   340  			return nil
   341  		}
   342  	}
   343  	fileNotExists := func(f1 string) func(string) error {
   344  		return func(root string) error {
   345  			_, err := os.Lstat(filepath.Join(root, f1))
   346  			if err == nil {
   347  				return errors.New("file exists")
   348  			} else if !os.IsNotExist(err) {
   349  				return err
   350  			}
   351  			return nil
   352  		}
   353  
   354  	}
   355  	all := func(funcs ...func(string) error) func(string) error {
   356  		return func(root string) error {
   357  			for _, f := range funcs {
   358  				if err := f(root); err != nil {
   359  					return err
   360  				}
   361  			}
   362  			return nil
   363  		}
   364  	}
   365  
   366  	breakouts := []struct {
   367  		name      string
   368  		w         tartest.WriterToTar
   369  		apply     fstest.Applier
   370  		validator func(string) error
   371  		err       error
   372  	}{
   373  		{
   374  			name: "SymlinkAbsolute",
   375  			w: tartest.TarAll(
   376  				tc.Dir("etc", 0755),
   377  				tc.Symlink("/etc", "localetc"),
   378  				tc.File("/localetc/unbroken", []byte(expected), 0644),
   379  			),
   380  			validator: unbrokenCheck,
   381  		},
   382  		{
   383  			name: "SymlinkUpAndOut",
   384  			w: tartest.TarAll(
   385  				tc.Dir("etc", 0755),
   386  				tc.Dir("dummy", 0755),
   387  				tc.Symlink("/dummy/../etc", "localetc"),
   388  				tc.File("/localetc/unbroken", []byte(expected), 0644),
   389  			),
   390  			validator: unbrokenCheck,
   391  		},
   392  		{
   393  			name: "SymlinkMultipleAbsolute",
   394  			w: tartest.TarAll(
   395  				tc.Dir("etc", 0755),
   396  				tc.Dir("dummy", 0755),
   397  				tc.Symlink("/etc", "/dummy/etc"),
   398  				tc.Symlink("/dummy/etc", "localetc"),
   399  				tc.File("/dummy/etc/unbroken", []byte(expected), 0644),
   400  			),
   401  			validator: unbrokenCheck,
   402  		},
   403  		{
   404  			name: "SymlinkMultipleRelative",
   405  			w: tartest.TarAll(
   406  				tc.Dir("etc", 0755),
   407  				tc.Dir("dummy", 0755),
   408  				tc.Symlink("/etc", "/dummy/etc"),
   409  				tc.Symlink("./dummy/etc", "localetc"),
   410  				tc.File("/dummy/etc/unbroken", []byte(expected), 0644),
   411  			),
   412  			validator: unbrokenCheck,
   413  		},
   414  		{
   415  			name: "SymlinkEmptyFile",
   416  			w: tartest.TarAll(
   417  				tc.Dir("etc", 0755),
   418  				tc.File("etc/emptied", []byte("notempty"), 0644),
   419  				tc.Symlink("/etc", "localetc"),
   420  				tc.File("/localetc/emptied", []byte{}, 0644),
   421  			),
   422  			validator: func(root string) error {
   423  				b, err := ioutil.ReadFile(filepath.Join(root, "etc", "emptied"))
   424  				if err != nil {
   425  					return errors.Wrap(err, "failed to read unbroken")
   426  				}
   427  				if len(b) > 0 {
   428  					return errors.Errorf("/etc/emptied: non-empty")
   429  				}
   430  				return nil
   431  			},
   432  		},
   433  		{
   434  			name: "HardlinkRelative",
   435  			w: tartest.TarAll(
   436  				tc.Dir("etc", 0770),
   437  				tc.File("/etc/passwd", []byte("inside"), 0644),
   438  				tc.Dir("breakouts", 0755),
   439  				tc.Symlink("../../etc", "breakouts/d1"),
   440  				tc.Link("/breakouts/d1/passwd", "breakouts/mypasswd"),
   441  			),
   442  			validator: sameFile("/breakouts/mypasswd", "/etc/passwd"),
   443  		},
   444  		{
   445  			name: "HardlinkDownAndOut",
   446  			w: tartest.TarAll(
   447  				tc.Dir("etc", 0770),
   448  				tc.File("/etc/passwd", []byte("inside"), 0644),
   449  				tc.Dir("breakouts", 0755),
   450  				tc.Dir("downandout", 0755),
   451  				tc.Symlink("../downandout/../../etc", "breakouts/d1"),
   452  				tc.Link("/breakouts/d1/passwd", "breakouts/mypasswd"),
   453  			),
   454  			validator: sameFile("/breakouts/mypasswd", "/etc/passwd"),
   455  		},
   456  		{
   457  			name: "HardlinkAbsolute",
   458  			w: tartest.TarAll(
   459  				tc.Dir("etc", 0770),
   460  				tc.File("/etc/passwd", []byte("inside"), 0644),
   461  				tc.Symlink("/etc", "localetc"),
   462  				tc.Link("/localetc/passwd", "localpasswd"),
   463  			),
   464  			validator: sameFile("localpasswd", "/etc/passwd"),
   465  		},
   466  		{
   467  			name: "HardlinkRelativeLong",
   468  			w: tartest.TarAll(
   469  				tc.Dir("etc", 0770),
   470  				tc.File("/etc/passwd", []byte("inside"), 0644),
   471  				tc.Symlink("../../../../../../../etc", "localetc"),
   472  				tc.Link("/localetc/passwd", "localpasswd"),
   473  			),
   474  			validator: sameFile("localpasswd", "/etc/passwd"),
   475  		},
   476  		{
   477  			name: "HardlinkRelativeUpAndOut",
   478  			w: tartest.TarAll(
   479  				tc.Dir("etc", 0770),
   480  				tc.File("/etc/passwd", []byte("inside"), 0644),
   481  				tc.Symlink("upandout/../../../etc", "localetc"),
   482  				tc.Link("/localetc/passwd", "localpasswd"),
   483  			),
   484  			validator: sameFile("localpasswd", "/etc/passwd"),
   485  		},
   486  		{
   487  			name: "HardlinkDirectRelative",
   488  			w: tartest.TarAll(
   489  				tc.Dir("etc", 0770),
   490  				tc.File("/etc/passwd", []byte("inside"), 0644),
   491  				tc.Link("../../../../../etc/passwd", "localpasswd"),
   492  			),
   493  			validator: sameFile("localpasswd", "/etc/passwd"),
   494  		},
   495  		{
   496  			name: "HardlinkDirectAbsolute",
   497  			w: tartest.TarAll(
   498  				tc.Dir("etc", 0770),
   499  				tc.File("/etc/passwd", []byte("inside"), 0644),
   500  				tc.Link("/etc/passwd", "localpasswd"),
   501  			),
   502  			validator: sameFile("localpasswd", "/etc/passwd"),
   503  		},
   504  		{
   505  			name: "HardlinkSymlinkBeforeCreateTarget",
   506  			w: tartest.TarAll(
   507  				tc.Dir("etc", 0770),
   508  				tc.Symlink("/etc/passwd", "localpasswd"),
   509  				tc.Link("localpasswd", "localpasswd-dup"),
   510  				tc.File("/etc/passwd", []byte("after"), 0644),
   511  			),
   512  			validator: sameFile("localpasswd-dup", "/etc/passwd"),
   513  		},
   514  		{
   515  			name: "HardlinkSymlinkRelative",
   516  			w: tartest.TarAll(
   517  				tc.Dir("etc", 0770),
   518  				tc.File("/etc/passwd", []byte("inside"), 0644),
   519  				tc.Symlink("../../../../../etc/passwd", "passwdlink"),
   520  				tc.Link("/passwdlink", "localpasswd"),
   521  			),
   522  			validator: all(
   523  				sameSymlinkFile("/localpasswd", "/passwdlink"),
   524  				sameFile("/localpasswd", "/etc/passwd"),
   525  			),
   526  		},
   527  		{
   528  			name: "HardlinkSymlinkAbsolute",
   529  			w: tartest.TarAll(
   530  				tc.Dir("etc", 0770),
   531  				tc.File("/etc/passwd", []byte("inside"), 0644),
   532  				tc.Symlink("/etc/passwd", "passwdlink"),
   533  				tc.Link("/passwdlink", "localpasswd"),
   534  			),
   535  			validator: all(
   536  				sameSymlinkFile("/localpasswd", "/passwdlink"),
   537  				sameFile("/localpasswd", "/etc/passwd"),
   538  			),
   539  		},
   540  		{
   541  			name: "SymlinkParentDirectory",
   542  			w: tartest.TarAll(
   543  				tc.Dir("etc", 0770),
   544  				tc.File("/etc/passwd", []byte("inside"), 0644),
   545  				tc.Symlink("/etc/", ".."),
   546  				tc.Link("/etc/passwd", "localpasswd"),
   547  			),
   548  			validator: sameFile("/localpasswd", "/etc/passwd"),
   549  		},
   550  		{
   551  			name: "SymlinkEmptyFilename",
   552  			w: tartest.TarAll(
   553  				tc.Dir("etc", 0770),
   554  				tc.File("/etc/passwd", []byte("inside"), 0644),
   555  				tc.Symlink("/etc/", ""),
   556  				tc.Link("/etc/passwd", "localpasswd"),
   557  			),
   558  			validator: sameFile("/localpasswd", "/etc/passwd"),
   559  		},
   560  		{
   561  			name: "SymlinkParentRelative",
   562  			w: tartest.TarAll(
   563  				tc.Dir("etc", 0770),
   564  				tc.File("/etc/passwd", []byte("inside"), 0644),
   565  				tc.Symlink("/etc/", "localetc/sub/.."),
   566  				tc.Link("/etc/passwd", "/localetc/localpasswd"),
   567  			),
   568  			validator: sameFile("/localetc/localpasswd", "/etc/passwd"),
   569  		},
   570  		{
   571  			name: "SymlinkSlashEnded",
   572  			w: tartest.TarAll(
   573  				tc.Dir("etc", 0770),
   574  				tc.File("/etc/passwd", []byte("inside"), 0644),
   575  				tc.Dir("localetc/", 0770),
   576  				tc.Link("/etc/passwd", "/localetc/localpasswd"),
   577  			),
   578  			validator: sameFile("/localetc/localpasswd", "/etc/passwd"),
   579  		},
   580  		{
   581  			name: "SymlinkOverrideDirectory",
   582  			apply: fstest.Apply(
   583  				fstest.CreateDir("/etc/", 0755),
   584  				fstest.CreateFile("/etc/passwd", []byte("inside"), 0644),
   585  				fstest.CreateDir("/localetc/", 0755),
   586  			),
   587  			w: tartest.TarAll(
   588  				tc.Symlink("/etc", "localetc"),
   589  				tc.Link("/etc/passwd", "/localetc/localpasswd"),
   590  			),
   591  			validator: sameFile("/localetc/localpasswd", "/etc/passwd"),
   592  		},
   593  		{
   594  			name: "SymlinkOverrideDirectoryRelative",
   595  			apply: fstest.Apply(
   596  				fstest.CreateDir("/etc/", 0755),
   597  				fstest.CreateFile("/etc/passwd", []byte("inside"), 0644),
   598  				fstest.CreateDir("/localetc/", 0755),
   599  			),
   600  			w: tartest.TarAll(
   601  				tc.Symlink("../../etc", "localetc"),
   602  				tc.Link("/etc/passwd", "/localetc/localpasswd"),
   603  			),
   604  			validator: sameFile("/localetc/localpasswd", "/etc/passwd"),
   605  		},
   606  		{
   607  			name: "DirectoryOverrideSymlink",
   608  			apply: fstest.Apply(
   609  				fstest.CreateDir("/etc/", 0755),
   610  				fstest.CreateFile("/etc/passwd", []byte("inside"), 0644),
   611  				fstest.Symlink("/etc", "localetc"),
   612  			),
   613  			w: tartest.TarAll(
   614  				tc.Dir("/localetc/", 0755),
   615  				tc.Link("/etc/passwd", "/localetc/localpasswd"),
   616  			),
   617  			validator: sameFile("/localetc/localpasswd", "/etc/passwd"),
   618  		},
   619  		{
   620  			name: "DirectoryOverrideSymlinkAndHardlink",
   621  			apply: fstest.Apply(
   622  				fstest.CreateDir("/etc/", 0755),
   623  				fstest.CreateFile("/etc/passwd", []byte("inside"), 0644),
   624  				fstest.Symlink("etc", "localetc"),
   625  				fstest.Link("/etc/passwd", "/localetc/localpasswd"),
   626  			),
   627  			w: tartest.TarAll(
   628  				tc.Dir("/localetc/", 0755),
   629  				tc.File("/localetc/localpasswd", []byte("different"), 0644),
   630  			),
   631  			validator: notSameFile("/localetc/localpasswd", "/etc/passwd"),
   632  		},
   633  		{
   634  			name: "WhiteoutRootParent",
   635  			apply: fstest.Apply(
   636  				fstest.CreateDir("/etc/", 0755),
   637  				fstest.CreateFile("/etc/passwd", []byte("inside"), 0644),
   638  			),
   639  			w: tartest.TarAll(
   640  				tc.File(".wh...", []byte{}, 0644), // Should wipe out whole directory
   641  			),
   642  			err: errInvalidArchive,
   643  		},
   644  		{
   645  			name: "WhiteoutParent",
   646  			apply: fstest.Apply(
   647  				fstest.CreateDir("/etc/", 0755),
   648  				fstest.CreateFile("/etc/passwd", []byte("inside"), 0644),
   649  			),
   650  			w: tartest.TarAll(
   651  				tc.File("etc/.wh...", []byte{}, 0644),
   652  			),
   653  			err: errInvalidArchive,
   654  		},
   655  		{
   656  			name: "WhiteoutRoot",
   657  			apply: fstest.Apply(
   658  				fstest.CreateDir("/etc/", 0755),
   659  				fstest.CreateFile("/etc/passwd", []byte("inside"), 0644),
   660  			),
   661  			w: tartest.TarAll(
   662  				tc.File(".wh..", []byte{}, 0644),
   663  			),
   664  			err: errInvalidArchive,
   665  		},
   666  		{
   667  			name: "WhiteoutCurrentDirectory",
   668  			apply: fstest.Apply(
   669  				fstest.CreateDir("/etc/", 0755),
   670  				fstest.CreateFile("/etc/passwd", []byte("inside"), 0644),
   671  			),
   672  			w: tartest.TarAll(
   673  				tc.File("etc/.wh..", []byte{}, 0644), // Should wipe out whole directory
   674  			),
   675  			err: errInvalidArchive,
   676  		},
   677  		{
   678  			name: "WhiteoutSymlink",
   679  			apply: fstest.Apply(
   680  				fstest.CreateDir("/etc/", 0755),
   681  				fstest.CreateFile("/etc/passwd", []byte("all users"), 0644),
   682  				fstest.Symlink("/etc", "localetc"),
   683  			),
   684  			w: tartest.TarAll(
   685  				tc.File(".wh.localetc", []byte{}, 0644), // Should wipe out whole directory
   686  			),
   687  			validator: all(
   688  				fileValue("etc/passwd", []byte("all users")),
   689  				fileNotExists("localetc"),
   690  			),
   691  		},
   692  		{
   693  			// TODO: This test should change once archive apply is disallowing
   694  			// symlinks as parents in the name
   695  			name: "WhiteoutSymlinkPath",
   696  			apply: fstest.Apply(
   697  				fstest.CreateDir("/etc/", 0755),
   698  				fstest.CreateFile("/etc/passwd", []byte("all users"), 0644),
   699  				fstest.CreateFile("/etc/whitedout", []byte("ahhhh whiteout"), 0644),
   700  				fstest.Symlink("/etc", "localetc"),
   701  			),
   702  			w: tartest.TarAll(
   703  				tc.File("localetc/.wh.whitedout", []byte{}, 0644),
   704  			),
   705  			validator: all(
   706  				fileValue("etc/passwd", []byte("all users")),
   707  				fileNotExists("etc/whitedout"),
   708  			),
   709  		},
   710  		{
   711  			name: "WhiteoutDirectoryName",
   712  			apply: fstest.Apply(
   713  				fstest.CreateDir("/etc/", 0755),
   714  				fstest.CreateFile("/etc/passwd", []byte("all users"), 0644),
   715  				fstest.CreateFile("/etc/whitedout", []byte("ahhhh whiteout"), 0644),
   716  				fstest.Symlink("/etc", "localetc"),
   717  			),
   718  			w: tartest.TarAll(
   719  				tc.File(".wh.etc/somefile", []byte("non-empty"), 0644),
   720  			),
   721  			validator: all(
   722  				fileValue("etc/passwd", []byte("all users")),
   723  				fileValue(".wh.etc/somefile", []byte("non-empty")),
   724  			),
   725  		},
   726  		{
   727  			name: "WhiteoutDeadSymlinkParent",
   728  			apply: fstest.Apply(
   729  				fstest.CreateDir("/etc/", 0755),
   730  				fstest.CreateFile("/etc/passwd", []byte("all users"), 0644),
   731  				fstest.Symlink("/dne", "localetc"),
   732  			),
   733  			w: tartest.TarAll(
   734  				tc.File("localetc/.wh.etc", []byte{}, 0644),
   735  			),
   736  			// no-op, remove does not
   737  			validator: fileValue("etc/passwd", []byte("all users")),
   738  		},
   739  		{
   740  			name: "WhiteoutRelativePath",
   741  			apply: fstest.Apply(
   742  				fstest.CreateDir("/etc/", 0755),
   743  				fstest.CreateFile("/etc/passwd", []byte("all users"), 0644),
   744  				fstest.Symlink("/dne", "localetc"),
   745  			),
   746  			w: tartest.TarAll(
   747  				tc.File("dne/../.wh.etc", []byte{}, 0644),
   748  			),
   749  			// resolution ends up just removing etc
   750  			validator: fileNotExists("etc/passwd"),
   751  		},
   752  		{
   753  
   754  			name: "HardlinkSymlinkChmod",
   755  			w: func() tartest.WriterToTar {
   756  				p := filepath.Join(td, "perm400")
   757  				if err := ioutil.WriteFile(p, []byte("..."), 0400); err != nil {
   758  					t.Fatal(err)
   759  				}
   760  				ep := filepath.Join(td, "also-exists-outside-root")
   761  				if err := ioutil.WriteFile(ep, []byte("..."), 0640); err != nil {
   762  					t.Fatal(err)
   763  				}
   764  
   765  				return tartest.TarAll(
   766  					tc.Symlink(p, ep),
   767  					tc.Link(ep, "sketchylink"),
   768  				)
   769  			}(),
   770  			validator: func(string) error {
   771  				p := filepath.Join(td, "perm400")
   772  				fi, err := os.Lstat(p)
   773  				if err != nil {
   774  					return err
   775  				}
   776  				if perm := fi.Mode() & os.ModePerm; perm != 0400 {
   777  					return errors.Errorf("%s perm changed from 0400 to %04o", p, perm)
   778  				}
   779  				return nil
   780  			},
   781  		},
   782  	}
   783  
   784  	for _, bo := range breakouts {
   785  		t.Run(bo.name, makeWriterToTarTest(bo.w, bo.apply, bo.validator, bo.err))
   786  	}
   787  }
   788  
   789  func TestDiffApply(t *testing.T) {
   790  	fstest.FSSuite(t, diffApplier{})
   791  }
   792  
   793  func TestApplyTar(t *testing.T) {
   794  	tc := tartest.TarContext{}.WithUIDGID(os.Getuid(), os.Getgid()).WithModTime(time.Now().UTC())
   795  	directoriesExist := func(dirs ...string) func(string) error {
   796  		return func(root string) error {
   797  			for _, d := range dirs {
   798  				p, err := fs.RootPath(root, d)
   799  				if err != nil {
   800  					return err
   801  				}
   802  				if _, err := os.Stat(p); err != nil {
   803  					return errors.Wrapf(err, "failure checking existence for %v", d)
   804  				}
   805  			}
   806  			return nil
   807  		}
   808  	}
   809  
   810  	tests := []struct {
   811  		name      string
   812  		w         tartest.WriterToTar
   813  		apply     fstest.Applier
   814  		validator func(string) error
   815  		err       error
   816  	}{
   817  		{
   818  			name: "DirectoryCreation",
   819  			apply: fstest.Apply(
   820  				fstest.CreateDir("/etc/", 0755),
   821  			),
   822  			w: tartest.TarAll(
   823  				tc.Dir("/etc/subdir", 0755),
   824  				tc.Dir("/etc/subdir2/", 0755),
   825  				tc.Dir("/etc/subdir2/more", 0755),
   826  				tc.Dir("/other/noparent-1/1", 0755),
   827  				tc.Dir("/other/noparent-2/2/", 0755),
   828  			),
   829  			validator: directoriesExist(
   830  				"etc/subdir",
   831  				"etc/subdir2",
   832  				"etc/subdir2/more",
   833  				"other/noparent-1/1",
   834  				"other/noparent-2/2",
   835  			),
   836  		},
   837  	}
   838  
   839  	for _, at := range tests {
   840  		t.Run(at.name, makeWriterToTarTest(at.w, at.apply, at.validator, at.err))
   841  	}
   842  }
   843  
   844  func testApply(a fstest.Applier) error {
   845  	td, err := ioutil.TempDir("", "test-apply-")
   846  	if err != nil {
   847  		return errors.Wrap(err, "failed to create temp dir")
   848  	}
   849  	defer os.RemoveAll(td)
   850  	dest, err := ioutil.TempDir("", "test-apply-dest-")
   851  	if err != nil {
   852  		return errors.Wrap(err, "failed to create temp dir")
   853  	}
   854  	defer os.RemoveAll(dest)
   855  
   856  	if err := a.Apply(td); err != nil {
   857  		return errors.Wrap(err, "failed to apply filesystem changes")
   858  	}
   859  
   860  	tarArgs := []string{"c", "-C", td}
   861  	names, err := readDirNames(td)
   862  	if err != nil {
   863  		return errors.Wrap(err, "failed to read directory names")
   864  	}
   865  	tarArgs = append(tarArgs, names...)
   866  
   867  	cmd := exec.Command(tarCmd, tarArgs...)
   868  
   869  	arch, err := cmd.StdoutPipe()
   870  	if err != nil {
   871  		return errors.Wrap(err, "failed to create stdout pipe")
   872  	}
   873  
   874  	if err := cmd.Start(); err != nil {
   875  		return errors.Wrap(err, "failed to start command")
   876  	}
   877  
   878  	if _, err := Apply(context.Background(), dest, arch); err != nil {
   879  		return errors.Wrap(err, "failed to apply tar stream")
   880  	}
   881  
   882  	return fstest.CheckDirectoryEqual(td, dest)
   883  }
   884  
   885  func testBaseDiff(a fstest.Applier) error {
   886  	td, err := ioutil.TempDir("", "test-base-diff-")
   887  	if err != nil {
   888  		return errors.Wrap(err, "failed to create temp dir")
   889  	}
   890  	defer os.RemoveAll(td)
   891  	dest, err := ioutil.TempDir("", "test-base-diff-dest-")
   892  	if err != nil {
   893  		return errors.Wrap(err, "failed to create temp dir")
   894  	}
   895  	defer os.RemoveAll(dest)
   896  
   897  	if err := a.Apply(td); err != nil {
   898  		return errors.Wrap(err, "failed to apply filesystem changes")
   899  	}
   900  
   901  	arch := Diff(context.Background(), "", td)
   902  
   903  	cmd := exec.Command(tarCmd, "x", "-C", dest)
   904  	cmd.Stdin = arch
   905  	if err := cmd.Run(); err != nil {
   906  		return errors.Wrap(err, "tar command failed")
   907  	}
   908  
   909  	return fstest.CheckDirectoryEqual(td, dest)
   910  }
   911  
   912  func testDiffApply(appliers ...fstest.Applier) error {
   913  	td, err := ioutil.TempDir("", "test-diff-apply-")
   914  	if err != nil {
   915  		return errors.Wrap(err, "failed to create temp dir")
   916  	}
   917  	defer os.RemoveAll(td)
   918  	dest, err := ioutil.TempDir("", "test-diff-apply-dest-")
   919  	if err != nil {
   920  		return errors.Wrap(err, "failed to create temp dir")
   921  	}
   922  	defer os.RemoveAll(dest)
   923  
   924  	for _, a := range appliers {
   925  		if err := a.Apply(td); err != nil {
   926  			return errors.Wrap(err, "failed to apply filesystem changes")
   927  		}
   928  	}
   929  
   930  	// Apply base changes before diff
   931  	if len(appliers) > 1 {
   932  		for _, a := range appliers[:len(appliers)-1] {
   933  			if err := a.Apply(dest); err != nil {
   934  				return errors.Wrap(err, "failed to apply base filesystem changes")
   935  			}
   936  		}
   937  	}
   938  
   939  	diffBytes, err := ioutil.ReadAll(Diff(context.Background(), dest, td))
   940  	if err != nil {
   941  		return errors.Wrap(err, "failed to create diff")
   942  	}
   943  
   944  	if _, err := Apply(context.Background(), dest, bytes.NewReader(diffBytes)); err != nil {
   945  		return errors.Wrap(err, "failed to apply tar stream")
   946  	}
   947  
   948  	return fstest.CheckDirectoryEqual(td, dest)
   949  }
   950  
   951  func makeWriterToTarTest(wt tartest.WriterToTar, a fstest.Applier, validate func(string) error, applyErr error) func(*testing.T) {
   952  	return func(t *testing.T) {
   953  		td, err := ioutil.TempDir("", "test-writer-to-tar-")
   954  		if err != nil {
   955  			t.Fatalf("Failed to create temp dir: %v", err)
   956  		}
   957  		defer os.RemoveAll(td)
   958  
   959  		if a != nil {
   960  			if err := a.Apply(td); err != nil {
   961  				t.Fatalf("Failed to apply filesystem to directory: %v", err)
   962  			}
   963  		}
   964  
   965  		tr := tartest.TarFromWriterTo(wt)
   966  
   967  		if _, err := Apply(context.Background(), td, tr); err != nil {
   968  			if applyErr == nil {
   969  				t.Fatalf("Failed to apply tar: %v", err)
   970  			} else if !errors.Is(err, applyErr) {
   971  				t.Fatalf("Unexpected apply error: %v, expected %v", err, applyErr)
   972  			}
   973  			return
   974  		} else if applyErr != nil {
   975  			t.Fatalf("Expected apply error, got none: %v", applyErr)
   976  		}
   977  
   978  		if validate != nil {
   979  			if err := validate(td); err != nil {
   980  				t.Errorf("Validation failed: %v", err)
   981  			}
   982  
   983  		}
   984  
   985  	}
   986  }
   987  
   988  func TestDiffTar(t *testing.T) {
   989  	tests := []struct {
   990  		name       string
   991  		validators []tarEntryValidator
   992  		a          fstest.Applier
   993  		b          fstest.Applier
   994  	}{
   995  		{
   996  			name:       "EmptyDiff",
   997  			validators: []tarEntryValidator{},
   998  			a: fstest.Apply(
   999  				fstest.CreateDir("/etc/", 0755),
  1000  			),
  1001  			b: fstest.Apply(),
  1002  		},
  1003  		{
  1004  			name: "ParentInclusion",
  1005  			validators: []tarEntryValidator{
  1006  				dirEntry("d1/", 0755),
  1007  				dirEntry("d1/d/", 0700),
  1008  				dirEntry("d2/", 0770),
  1009  				fileEntry("d2/f", []byte("ok"), 0644),
  1010  			},
  1011  			a: fstest.Apply(
  1012  				fstest.CreateDir("/d1/", 0755),
  1013  				fstest.CreateDir("/d2/", 0770),
  1014  			),
  1015  			b: fstest.Apply(
  1016  				fstest.CreateDir("/d1/d", 0700),
  1017  				fstest.CreateFile("/d2/f", []byte("ok"), 0644),
  1018  			),
  1019  		},
  1020  		{
  1021  			name: "HardlinkParentInclusion",
  1022  			validators: []tarEntryValidator{
  1023  				dirEntry("d2/", 0755),
  1024  				fileEntry("d2/l1", []byte("link me"), 0644),
  1025  				// d1/f1 and its parent is included after the new link,
  1026  				// before the new link was included, these files would
  1027  				// not have been needed
  1028  				dirEntry("d1/", 0755),
  1029  				linkEntry("d1/f1", "d2/l1"),
  1030  				dirEntry("d3/", 0755),
  1031  				fileEntry("d3/l1", []byte("link me"), 0644),
  1032  				dirEntry("d4/", 0755),
  1033  				linkEntry("d4/f1", "d3/l1"),
  1034  				dirEntry("d6/", 0755),
  1035  				whiteoutEntry("d6/l1"),
  1036  				whiteoutEntry("d6/l2"),
  1037  			},
  1038  			a: fstest.Apply(
  1039  				fstest.CreateDir("/d1/", 0755),
  1040  				fstest.CreateFile("/d1/f1", []byte("link me"), 0644),
  1041  				fstest.CreateDir("/d2/", 0755),
  1042  				fstest.CreateFile("/d2/f1", []byte("link me"), 0644),
  1043  				fstest.CreateDir("/d3/", 0755),
  1044  				fstest.CreateDir("/d4/", 0755),
  1045  				fstest.CreateFile("/d4/f1", []byte("link me"), 0644),
  1046  				fstest.CreateDir("/d5/", 0755),
  1047  				fstest.CreateFile("/d5/f1", []byte("link me"), 0644),
  1048  				fstest.CreateDir("/d6/", 0755),
  1049  				fstest.Link("/d1/f1", "/d6/l1"),
  1050  				fstest.Link("/d5/f1", "/d6/l2"),
  1051  			),
  1052  			b: fstest.Apply(
  1053  				fstest.Link("/d1/f1", "/d2/l1"),
  1054  				fstest.Link("/d4/f1", "/d3/l1"),
  1055  				fstest.Remove("/d6/l1"),
  1056  				fstest.Remove("/d6/l2"),
  1057  			),
  1058  		},
  1059  		{
  1060  			name: "UpdateDirectoryPermission",
  1061  			validators: []tarEntryValidator{
  1062  				dirEntry("d1/", 0777),
  1063  				dirEntry("d1/d/", 0700),
  1064  				dirEntry("d2/", 0770),
  1065  				fileEntry("d2/f", []byte("ok"), 0644),
  1066  			},
  1067  			a: fstest.Apply(
  1068  				fstest.CreateDir("/d1/", 0755),
  1069  				fstest.CreateDir("/d2/", 0770),
  1070  			),
  1071  			b: fstest.Apply(
  1072  				fstest.Chmod("/d1", 0777),
  1073  				fstest.CreateDir("/d1/d", 0700),
  1074  				fstest.CreateFile("/d2/f", []byte("ok"), 0644),
  1075  			),
  1076  		},
  1077  		{
  1078  			name: "HardlinkUpdatedParent",
  1079  			validators: []tarEntryValidator{
  1080  				dirEntry("d1/", 0777),
  1081  				dirEntry("d2/", 0755),
  1082  				fileEntry("d2/l1", []byte("link me"), 0644),
  1083  				// d1/f1 is included after the new link, its
  1084  				// parent has already changed and therefore
  1085  				// only the linked file is included
  1086  				linkEntry("d1/f1", "d2/l1"),
  1087  				dirEntry("d4/", 0777),
  1088  				fileEntry("d4/l1", []byte("link me"), 0644),
  1089  				dirEntry("d3/", 0755),
  1090  				linkEntry("d3/f1", "d4/l1"),
  1091  			},
  1092  			a: fstest.Apply(
  1093  				fstest.CreateDir("/d1/", 0755),
  1094  				fstest.CreateFile("/d1/f1", []byte("link me"), 0644),
  1095  				fstest.CreateDir("/d2/", 0755),
  1096  				fstest.CreateFile("/d2/f1", []byte("link me"), 0644),
  1097  				fstest.CreateDir("/d3/", 0755),
  1098  				fstest.CreateFile("/d3/f1", []byte("link me"), 0644),
  1099  				fstest.CreateDir("/d4/", 0755),
  1100  			),
  1101  			b: fstest.Apply(
  1102  				fstest.Chmod("/d1", 0777),
  1103  				fstest.Link("/d1/f1", "/d2/l1"),
  1104  				fstest.Chmod("/d4", 0777),
  1105  				fstest.Link("/d3/f1", "/d4/l1"),
  1106  			),
  1107  		},
  1108  		{
  1109  			name: "WhiteoutIncludesParents",
  1110  			validators: []tarEntryValidator{
  1111  				dirEntry("d1/", 0755),
  1112  				whiteoutEntry("d1/f1"),
  1113  				dirEntry("d2/", 0755),
  1114  				whiteoutEntry("d2/f1"),
  1115  				fileEntry("d2/f2", []byte("content"), 0777),
  1116  				dirEntry("d3/", 0755),
  1117  				whiteoutEntry("d3/f1"),
  1118  				fileEntry("d3/f2", []byte("content"), 0644),
  1119  				dirEntry("d4/", 0755),
  1120  				fileEntry("d4/f0", []byte("content"), 0644),
  1121  				whiteoutEntry("d4/f1"),
  1122  				whiteoutEntry("d5"),
  1123  			},
  1124  			a: fstest.Apply(
  1125  				fstest.CreateDir("/d1/", 0755),
  1126  				fstest.CreateFile("/d1/f1", []byte("content"), 0644),
  1127  				fstest.CreateDir("/d2/", 0755),
  1128  				fstest.CreateFile("/d2/f1", []byte("content"), 0644),
  1129  				fstest.CreateFile("/d2/f2", []byte("content"), 0644),
  1130  				fstest.CreateDir("/d3/", 0755),
  1131  				fstest.CreateFile("/d3/f1", []byte("content"), 0644),
  1132  				fstest.CreateDir("/d4/", 0755),
  1133  				fstest.CreateFile("/d4/f1", []byte("content"), 0644),
  1134  				fstest.CreateDir("/d5/", 0755),
  1135  				fstest.CreateFile("/d5/f1", []byte("content"), 0644),
  1136  			),
  1137  			b: fstest.Apply(
  1138  				fstest.Remove("/d1/f1"),
  1139  				fstest.Remove("/d2/f1"),
  1140  				fstest.Chmod("/d2/f2", 0777),
  1141  				fstest.Remove("/d3/f1"),
  1142  				fstest.CreateFile("/d3/f2", []byte("content"), 0644),
  1143  				fstest.Remove("/d4/f1"),
  1144  				fstest.CreateFile("/d4/f0", []byte("content"), 0644),
  1145  				fstest.RemoveAll("/d5"),
  1146  			),
  1147  		},
  1148  		{
  1149  			name: "WhiteoutParentRemoval",
  1150  			validators: []tarEntryValidator{
  1151  				whiteoutEntry("d1"),
  1152  				whiteoutEntry("d2"),
  1153  				dirEntry("d3/", 0755),
  1154  			},
  1155  			a: fstest.Apply(
  1156  				fstest.CreateDir("/d1/", 0755),
  1157  				fstest.CreateDir("/d2/", 0755),
  1158  				fstest.CreateFile("/d2/f1", []byte("content"), 0644),
  1159  			),
  1160  			b: fstest.Apply(
  1161  				fstest.RemoveAll("/d1"),
  1162  				fstest.RemoveAll("/d2"),
  1163  				fstest.CreateDir("/d3/", 0755),
  1164  			),
  1165  		},
  1166  		{
  1167  			name: "IgnoreSockets",
  1168  			validators: []tarEntryValidator{
  1169  				fileEntry("f2", []byte("content"), 0644),
  1170  				// There should be _no_ socket here, despite the fstest.CreateSocket below
  1171  				fileEntry("f3", []byte("content"), 0644),
  1172  			},
  1173  			a: fstest.Apply(
  1174  				fstest.CreateFile("/f1", []byte("content"), 0644),
  1175  			),
  1176  			b: fstest.Apply(
  1177  				fstest.CreateFile("/f2", []byte("content"), 0644),
  1178  				fstest.CreateSocket("/s0", 0644),
  1179  				fstest.CreateFile("/f3", []byte("content"), 0644),
  1180  			),
  1181  		},
  1182  	}
  1183  
  1184  	for _, at := range tests {
  1185  		t.Run(at.name, makeDiffTarTest(at.validators, at.a, at.b))
  1186  	}
  1187  }
  1188  
  1189  type tarEntryValidator func(*tar.Header, []byte) error
  1190  
  1191  func dirEntry(name string, mode int) tarEntryValidator {
  1192  	return func(hdr *tar.Header, b []byte) error {
  1193  		if hdr.Typeflag != tar.TypeDir {
  1194  			return errors.New("not directory type")
  1195  		}
  1196  		if hdr.Name != name {
  1197  			return errors.Errorf("wrong name %q, expected %q", hdr.Name, name)
  1198  		}
  1199  		if hdr.Mode != int64(mode) {
  1200  			return errors.Errorf("wrong mode %o, expected %o", hdr.Mode, mode)
  1201  		}
  1202  		return nil
  1203  	}
  1204  }
  1205  
  1206  func fileEntry(name string, expected []byte, mode int) tarEntryValidator {
  1207  	return func(hdr *tar.Header, b []byte) error {
  1208  		if hdr.Typeflag != tar.TypeReg {
  1209  			return errors.New("not file type")
  1210  		}
  1211  		if hdr.Name != name {
  1212  			return errors.Errorf("wrong name %q, expected %q", hdr.Name, name)
  1213  		}
  1214  		if hdr.Mode != int64(mode) {
  1215  			return errors.Errorf("wrong mode %o, expected %o", hdr.Mode, mode)
  1216  		}
  1217  		if !bytes.Equal(b, expected) {
  1218  			return errors.Errorf("different file content")
  1219  		}
  1220  		return nil
  1221  	}
  1222  }
  1223  
  1224  func linkEntry(name, link string) tarEntryValidator {
  1225  	return func(hdr *tar.Header, b []byte) error {
  1226  		if hdr.Typeflag != tar.TypeLink {
  1227  			return errors.New("not link type")
  1228  		}
  1229  		if hdr.Name != name {
  1230  			return errors.Errorf("wrong name %q, expected %q", hdr.Name, name)
  1231  		}
  1232  		if hdr.Linkname != link {
  1233  			return errors.Errorf("wrong link %q, expected %q", hdr.Linkname, link)
  1234  		}
  1235  		return nil
  1236  	}
  1237  }
  1238  
  1239  func whiteoutEntry(name string) tarEntryValidator {
  1240  	whiteOutDir := filepath.Dir(name)
  1241  	whiteOutBase := filepath.Base(name)
  1242  	whiteOut := filepath.Join(whiteOutDir, whiteoutPrefix+whiteOutBase)
  1243  
  1244  	return func(hdr *tar.Header, b []byte) error {
  1245  		if hdr.Typeflag != tar.TypeReg {
  1246  			return errors.Errorf("not file type: %q", hdr.Typeflag)
  1247  		}
  1248  		if hdr.Name != whiteOut {
  1249  			return errors.Errorf("wrong name %q, expected whiteout %q", hdr.Name, name)
  1250  		}
  1251  		return nil
  1252  	}
  1253  }
  1254  
  1255  func makeDiffTarTest(validators []tarEntryValidator, a, b fstest.Applier) func(*testing.T) {
  1256  	return func(t *testing.T) {
  1257  		ad, err := ioutil.TempDir("", "test-make-diff-tar-")
  1258  		if err != nil {
  1259  			t.Fatalf("failed to create temp dir: %v", err)
  1260  		}
  1261  		defer os.RemoveAll(ad)
  1262  		if err := a.Apply(ad); err != nil {
  1263  			t.Fatalf("failed to apply a: %v", err)
  1264  		}
  1265  
  1266  		bd, err := ioutil.TempDir("", "test-make-diff-tar-")
  1267  		if err != nil {
  1268  			t.Fatalf("failed to create temp dir: %v", err)
  1269  		}
  1270  		defer os.RemoveAll(bd)
  1271  		if err := fs.CopyDir(bd, ad); err != nil {
  1272  			t.Fatalf("failed to copy dir: %v", err)
  1273  		}
  1274  		if err := b.Apply(bd); err != nil {
  1275  			t.Fatalf("failed to apply b: %v", err)
  1276  		}
  1277  
  1278  		rc := Diff(context.Background(), ad, bd)
  1279  		defer rc.Close()
  1280  
  1281  		tr := tar.NewReader(rc)
  1282  		for i := 0; ; i++ {
  1283  			hdr, err := tr.Next()
  1284  			if err != nil {
  1285  				if err == io.EOF {
  1286  					break
  1287  				}
  1288  				t.Fatalf("tar read error: %v", err)
  1289  			}
  1290  			var b []byte
  1291  			if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 {
  1292  				b, err = ioutil.ReadAll(tr)
  1293  				if err != nil {
  1294  					t.Fatalf("tar read file error: %v", err)
  1295  				}
  1296  			}
  1297  			if i >= len(validators) {
  1298  				t.Fatal("no validator for entry")
  1299  			}
  1300  			if err := validators[i](hdr, b); err != nil {
  1301  				t.Fatalf("tar entry[%d] validation fail: %#v", i, err)
  1302  			}
  1303  		}
  1304  	}
  1305  }
  1306  
  1307  type diffApplier struct{}
  1308  
  1309  func (d diffApplier) TestContext(ctx context.Context) (context.Context, func(), error) {
  1310  	base, err := ioutil.TempDir("", "test-diff-apply-")
  1311  	if err != nil {
  1312  		return ctx, nil, errors.Wrap(err, "failed to create temp dir")
  1313  	}
  1314  	return context.WithValue(ctx, d, base), func() {
  1315  		os.RemoveAll(base)
  1316  	}, nil
  1317  }
  1318  
  1319  func (d diffApplier) Apply(ctx context.Context, a fstest.Applier) (string, func(), error) {
  1320  	base := ctx.Value(d).(string)
  1321  
  1322  	applyCopy, err := ioutil.TempDir("", "test-diffapply-apply-copy-")
  1323  	if err != nil {
  1324  		return "", nil, errors.Wrap(err, "failed to create temp dir")
  1325  	}
  1326  	defer os.RemoveAll(applyCopy)
  1327  	if err = fs.CopyDir(applyCopy, base); err != nil {
  1328  		return "", nil, errors.Wrap(err, "failed to copy base")
  1329  	}
  1330  	if err := a.Apply(applyCopy); err != nil {
  1331  		return "", nil, errors.Wrap(err, "failed to apply changes to copy of base")
  1332  	}
  1333  
  1334  	diffBytes, err := ioutil.ReadAll(Diff(ctx, base, applyCopy))
  1335  	if err != nil {
  1336  		return "", nil, errors.Wrap(err, "failed to create diff")
  1337  	}
  1338  
  1339  	if _, err = Apply(ctx, base, bytes.NewReader(diffBytes)); err != nil {
  1340  		return "", nil, errors.Wrap(err, "failed to apply tar stream")
  1341  	}
  1342  
  1343  	return base, nil, nil
  1344  }
  1345  
  1346  func readDirNames(p string) ([]string, error) {
  1347  	fis, err := ioutil.ReadDir(p)
  1348  	if err != nil {
  1349  		return nil, err
  1350  	}
  1351  	names := make([]string, len(fis))
  1352  	for i, fi := range fis {
  1353  		names[i] = fi.Name()
  1354  	}
  1355  	return names, nil
  1356  }
  1357  
  1358  func requireTar(t *testing.T) {
  1359  	if _, err := exec.LookPath(tarCmd); err != nil {
  1360  		t.Skipf("%s not found, skipping", tarCmd)
  1361  	}
  1362  }