...

Source file src/github.com/containerd/continuity/fs/path_test.go

Documentation: github.com/containerd/continuity/fs

     1  //go:build !windows
     2  // +build !windows
     3  
     4  /*
     5     Copyright The containerd Authors.
     6  
     7     Licensed under the Apache License, Version 2.0 (the "License");
     8     you may not use this file except in compliance with the License.
     9     You may obtain a copy of the License at
    10  
    11         http://www.apache.org/licenses/LICENSE-2.0
    12  
    13     Unless required by applicable law or agreed to in writing, software
    14     distributed under the License is distributed on an "AS IS" BASIS,
    15     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    16     See the License for the specific language governing permissions and
    17     limitations under the License.
    18  */
    19  
    20  package fs
    21  
    22  import (
    23  	"errors"
    24  	"os"
    25  	"path/filepath"
    26  	"testing"
    27  
    28  	"github.com/containerd/continuity/fs/fstest"
    29  )
    30  
    31  type RootCheck struct {
    32  	unresolved string
    33  	expected   string
    34  	scope      func(string) string
    35  	cause      error
    36  }
    37  
    38  func TestRootPath(t *testing.T) {
    39  	tests := []struct {
    40  		name   string
    41  		apply  fstest.Applier
    42  		checks []RootCheck
    43  	}{
    44  		{
    45  			name:   "SymlinkAbsolute",
    46  			apply:  Symlink("/b", "fs/a/d"),
    47  			checks: Check("fs/a/d/c/data", "b/c/data"),
    48  		},
    49  		{
    50  			name:   "SymlinkRelativePath",
    51  			apply:  Symlink("a", "fs/i"),
    52  			checks: Check("fs/i", "fs/a"),
    53  		},
    54  		{
    55  			name:   "SymlinkSkipSymlinksOutsideScope",
    56  			apply:  Symlink("realdir", "linkdir"),
    57  			checks: CheckWithScope("foo/bar", "foo/bar", "linkdir"),
    58  		},
    59  		{
    60  			name:   "SymlinkLastLink",
    61  			apply:  Symlink("/b", "fs/a/d"),
    62  			checks: Check("fs/a/d", "b"),
    63  		},
    64  		{
    65  			name:  "SymlinkRelativeLinkChangeScope",
    66  			apply: Symlink("../b", "fs/a/e"),
    67  			checks: CheckAll(
    68  				Check("fs/a/e/c/data", "fs/b/c/data"),
    69  				CheckWithScope("e", "b", "fs/a"), // Original return
    70  			),
    71  		},
    72  		{
    73  			name:  "SymlinkDeepRelativeLinkChangeScope",
    74  			apply: Symlink("../../../../test", "fs/a/f"),
    75  			checks: CheckAll(
    76  				Check("fs/a/f", "test"),             // Original return
    77  				CheckWithScope("a/f", "test", "fs"), // Original return
    78  			),
    79  		},
    80  		{
    81  			name: "SymlinkRelativeLinkChain",
    82  			apply: fstest.Apply(
    83  				Symlink("../g", "fs/b/h"),
    84  				fstest.Symlink("../../../../../../../../../../../../root", "fs/g"),
    85  			),
    86  			checks: Check("fs/b/h", "root"),
    87  		},
    88  		{
    89  			name:   "SymlinkBreakoutPath",
    90  			apply:  Symlink("../i/a", "fs/j/k"),
    91  			checks: CheckWithScope("k", "i/a", "fs/j"),
    92  		},
    93  		{
    94  			name:   "SymlinkToRoot",
    95  			apply:  Symlink("/", "foo"),
    96  			checks: Check("foo", ""),
    97  		},
    98  		{
    99  			name:   "SymlinkSlashDotdot",
   100  			apply:  Symlink("/../../", "foo"),
   101  			checks: Check("foo", ""),
   102  		},
   103  		{
   104  			name:   "SymlinkDotdot",
   105  			apply:  Symlink("../../", "foo"),
   106  			checks: Check("foo", ""),
   107  		},
   108  		{
   109  			name:   "SymlinkRelativePath2",
   110  			apply:  Symlink("baz/target", "bar/foo"),
   111  			checks: Check("bar/foo", "bar/baz/target"),
   112  		},
   113  		{
   114  			name: "SymlinkScopeLink",
   115  			apply: fstest.Apply(
   116  				Symlink("root2", "root"),
   117  				Symlink("../bar", "root2/foo"),
   118  			),
   119  			checks: CheckWithScope("foo", "bar", "root"),
   120  		},
   121  		{
   122  			name: "SymlinkSelf",
   123  			apply: fstest.Apply(
   124  				Symlink("foo", "root/foo"),
   125  			),
   126  			checks: ErrorWithScope("foo", "root", errTooManyLinks),
   127  		},
   128  		{
   129  			name: "SymlinkCircular",
   130  			apply: fstest.Apply(
   131  				Symlink("foo", "bar"),
   132  				Symlink("bar", "foo"),
   133  			),
   134  			checks: ErrorWithScope("foo", "", errTooManyLinks), // TODO: Test for circular error
   135  		},
   136  		{
   137  			name: "SymlinkCircularUnderRoot",
   138  			apply: fstest.Apply(
   139  				Symlink("baz", "root/bar"),
   140  				Symlink("../bak", "root/baz"),
   141  				Symlink("/bar", "root/bak"),
   142  			),
   143  			checks: ErrorWithScope("bar", "root", errTooManyLinks), // TODO: Test for circular error
   144  		},
   145  		{
   146  			name: "SymlinkComplexChain",
   147  			apply: fstest.Apply(
   148  				fstest.CreateDir("root2", 0o777),
   149  				Symlink("root2", "root"),
   150  				Symlink("r/s", "root/a"),
   151  				Symlink("../root/t", "root/r"),
   152  				Symlink("/../u", "root/root/t/s/b"),
   153  				Symlink(".", "root/u/c"),
   154  				Symlink("../v", "root/u/x/y"),
   155  				Symlink("/../w", "root/u/v"),
   156  			),
   157  			checks: CheckWithScope("a/b/c/x/y/z", "w/z", "root"), // Original return
   158  		},
   159  		{
   160  			name: "SymlinkBreakoutNonExistent",
   161  			apply: fstest.Apply(
   162  				Symlink("/", "root/slash"),
   163  				Symlink("/idontexist/../slash", "root/sym"),
   164  			),
   165  			checks: CheckWithScope("sym/file", "file", "root"),
   166  		},
   167  		{
   168  			name: "SymlinkNoLexicalCleaning",
   169  			apply: fstest.Apply(
   170  				Symlink("/foo/bar", "root/sym"),
   171  				Symlink("/sym/../baz", "root/hello"),
   172  			),
   173  			checks: CheckWithScope("hello", "foo/baz", "root"),
   174  		},
   175  	}
   176  
   177  	for _, test := range tests {
   178  		t.Run(test.name, makeRootPathTest(t, test.apply, test.checks))
   179  	}
   180  
   181  	// Add related tests which are unable to follow same pattern
   182  	t.Run("SymlinkRootScope", testRootPathSymlinkRootScope)
   183  	t.Run("SymlinkEmpty", testRootPathSymlinkEmpty)
   184  }
   185  
   186  func TestDirectoryCompare(t *testing.T) {
   187  	for i, tc := range []struct {
   188  		p1 string
   189  		p2 string
   190  		r  int
   191  	}{
   192  		{"", "", 0},
   193  		{"", "/", -1},
   194  		{"/", "", 1},
   195  		{"/", "/", 0},
   196  		{"", "", 0},
   197  		{"/dir1", "/dir1/", -1},
   198  		{"/dir1", "/dir1", 0},
   199  		{"/dir1/", "/dir1", 1},
   200  		{"/dir1", "/dir2", -1},
   201  		{"/dir2", "/dir1", 1},
   202  		{"/dir1/1", "/dir1-1", -1},
   203  		{"/dir1-1", "/dir1/1", 1},
   204  		{"/dir1/dir2", "/dir1/dir2", 0},
   205  		{"/dir1/dir2", "/dir1/dir2/", -1},
   206  		{"/dir1/dir2", "/dir1/dir2/f1", -1},
   207  		{"/dir1/dir2/", "/dir1/dir2", 1},
   208  		{"/dir1/dir2/f1", "/dir1/dir2", 1},
   209  		{"/dir1/dir2-f1", "/dir1/dir2", 1},
   210  		{"/dir1/dir2-f1", "/dir1/dir2/", 1},
   211  		{"/dir1/dir2/", "/dir1/dir2/", 0},
   212  		{"/dir1/dir2你", "/dir1/dir2a", 1},
   213  		{"/dir1/dir2你", "/dir1/dir2", 1},
   214  		{"/dir1/dir2你", "/dir1/dir2/", 1},
   215  		{"/dir1/dir2你/", "/dir1/dir2/", 1},
   216  		{"/dir1/dir2你/", "/dir1/dir2你/", 0},
   217  		{"/dir1/dir2你/", "/dir1/dir2你好/", -1},
   218  		{"/dir1/dir2你/", "/dir1/dir2你-好/", -1},
   219  		{"/dir1/dir2你/", "/dir1/dir2好/", -1},
   220  		{"/dir1/dir2/f1", "/dir1/dir2/f1", 0},
   221  		{"/d1/d2/d3/d4/d5/d6/d7/d8/d9/d10", "/d1/d2/d3/d4/d5/d6/d7/d8/d9/d10", 0},
   222  		{"/d1/d2/d3/d4/d5/d6/d7/d8/d9/d10", "/d1/d2/d3/d4/d5/d6/d7/d8/d9/d11", -1},
   223  	} {
   224  		r := directoryCompare(tc.p1, tc.p2)
   225  		if r != tc.r {
   226  			t.Errorf("[%d] Test case failed, %q <> %q = %d, expected %d", i, tc.p1, tc.p2, r, tc.r)
   227  		}
   228  	}
   229  }
   230  
   231  func testRootPathSymlinkRootScope(t *testing.T) {
   232  	tmpdir := t.TempDir()
   233  	expected, err := filepath.EvalSymlinks(tmpdir)
   234  	if err != nil {
   235  		t.Fatal(err)
   236  	}
   237  	rewrite, err := RootPath("/", tmpdir)
   238  	if err != nil {
   239  		t.Fatal(err)
   240  	}
   241  	if rewrite != expected {
   242  		t.Fatalf("expected %q got %q", expected, rewrite)
   243  	}
   244  }
   245  
   246  func testRootPathSymlinkEmpty(t *testing.T) {
   247  	wd, err := os.Getwd()
   248  	if err != nil {
   249  		t.Fatal(err)
   250  	}
   251  	res, err := RootPath(wd, "")
   252  	if err != nil {
   253  		t.Fatal(err)
   254  	}
   255  	if res != wd {
   256  		t.Fatalf("expected %q got %q", wd, res)
   257  	}
   258  }
   259  
   260  func makeRootPathTest(t *testing.T, apply fstest.Applier, checks []RootCheck) func(t *testing.T) {
   261  	return func(t *testing.T) {
   262  		applyDir := t.TempDir()
   263  		if apply != nil {
   264  			if err := apply.Apply(applyDir); err != nil {
   265  				t.Fatalf("Apply failed: %+v", err)
   266  			}
   267  		}
   268  
   269  		for i, check := range checks {
   270  			root := applyDir
   271  			if check.scope != nil {
   272  				root = check.scope(root)
   273  			}
   274  
   275  			actual, err := RootPath(root, check.unresolved)
   276  			if check.cause != nil {
   277  				if err == nil {
   278  					t.Errorf("(Check %d) Expected error %q, %q evaluated as %q", i+1, check.cause.Error(), check.unresolved, actual)
   279  				}
   280  				if !errors.Is(err, check.cause) {
   281  					t.Fatalf("(Check %d) Failed to evaluate root path: %+v", i+1, err)
   282  				}
   283  			} else {
   284  				expected := filepath.Join(root, check.expected)
   285  				if err != nil {
   286  					t.Fatalf("(Check %d) Failed to evaluate root path: %+v", i+1, err)
   287  				}
   288  				if actual != expected {
   289  					t.Errorf("(Check %d) Unexpected evaluated path %q, expected %q", i+1, actual, expected)
   290  				}
   291  			}
   292  		}
   293  	}
   294  }
   295  
   296  func Check(unresolved, expected string) []RootCheck {
   297  	return []RootCheck{
   298  		{
   299  			unresolved: unresolved,
   300  			expected:   expected,
   301  		},
   302  	}
   303  }
   304  
   305  func CheckWithScope(unresolved, expected, scope string) []RootCheck {
   306  	return []RootCheck{
   307  		{
   308  			unresolved: unresolved,
   309  			expected:   expected,
   310  			scope: func(root string) string {
   311  				return filepath.Join(root, scope)
   312  			},
   313  		},
   314  	}
   315  }
   316  
   317  func ErrorWithScope(unresolved, scope string, cause error) []RootCheck {
   318  	return []RootCheck{
   319  		{
   320  			unresolved: unresolved,
   321  			cause:      cause,
   322  			scope: func(root string) string {
   323  				return filepath.Join(root, scope)
   324  			},
   325  		},
   326  	}
   327  }
   328  
   329  func CheckAll(checks ...[]RootCheck) []RootCheck {
   330  	all := make([]RootCheck, 0, len(checks))
   331  	for _, c := range checks {
   332  		all = append(all, c...)
   333  	}
   334  	return all
   335  }
   336  
   337  func Symlink(oldname, newname string) fstest.Applier {
   338  	dir := filepath.Dir(newname)
   339  	if dir != "" {
   340  		return fstest.Apply(
   341  			fstest.CreateDir(dir, 0o755),
   342  			fstest.Symlink(oldname, newname),
   343  		)
   344  	}
   345  	return fstest.Symlink(oldname, newname)
   346  }
   347  

View as plain text