295 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			295 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// +build !windows
 | 
						|
 | 
						|
package fs
 | 
						|
 | 
						|
import (
 | 
						|
	"io/ioutil"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"testing"
 | 
						|
 | 
						|
	"github.com/containerd/containerd/fs/fstest"
 | 
						|
	"github.com/pkg/errors"
 | 
						|
)
 | 
						|
 | 
						|
type RootCheck struct {
 | 
						|
	unresolved string
 | 
						|
	expected   string
 | 
						|
	scope      func(string) string
 | 
						|
	cause      error
 | 
						|
}
 | 
						|
 | 
						|
func TestRootPath(t *testing.T) {
 | 
						|
	tests := []struct {
 | 
						|
		name   string
 | 
						|
		apply  fstest.Applier
 | 
						|
		checks []RootCheck
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name:   "SymlinkAbsolute",
 | 
						|
			apply:  Symlink("/b", "fs/a/d"),
 | 
						|
			checks: Check("fs/a/d/c/data", "b/c/data"),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:   "SymlinkRelativePath",
 | 
						|
			apply:  Symlink("a", "fs/i"),
 | 
						|
			checks: Check("fs/i", "fs/a"),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:   "SymlinkSkipSymlinksOutsideScope",
 | 
						|
			apply:  Symlink("realdir", "linkdir"),
 | 
						|
			checks: CheckWithScope("foo/bar", "foo/bar", "linkdir"),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:   "SymlinkLastLink",
 | 
						|
			apply:  Symlink("/b", "fs/a/d"),
 | 
						|
			checks: Check("fs/a/d", "b"),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:  "SymlinkRelativeLinkChangeScope",
 | 
						|
			apply: Symlink("../b", "fs/a/e"),
 | 
						|
			checks: CheckAll(
 | 
						|
				Check("fs/a/e/c/data", "fs/b/c/data"),
 | 
						|
				CheckWithScope("e", "b", "fs/a"), // Original return
 | 
						|
			),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:  "SymlinkDeepRelativeLinkChangeScope",
 | 
						|
			apply: Symlink("../../../../test", "fs/a/f"),
 | 
						|
			checks: CheckAll(
 | 
						|
				Check("fs/a/f", "test"),             // Original return
 | 
						|
				CheckWithScope("a/f", "test", "fs"), // Original return
 | 
						|
			),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "SymlinkRelativeLinkChain",
 | 
						|
			apply: fstest.Apply(
 | 
						|
				Symlink("../g", "fs/b/h"),
 | 
						|
				fstest.Symlink("../../../../../../../../../../../../root", "fs/g"),
 | 
						|
			),
 | 
						|
			checks: Check("fs/b/h", "root"),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:   "SymlinkBreakoutPath",
 | 
						|
			apply:  Symlink("../i/a", "fs/j/k"),
 | 
						|
			checks: CheckWithScope("k", "i/a", "fs/j"),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:   "SymlinkToRoot",
 | 
						|
			apply:  Symlink("/", "foo"),
 | 
						|
			checks: Check("foo", ""),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:   "SymlinkSlashDotdot",
 | 
						|
			apply:  Symlink("/../../", "foo"),
 | 
						|
			checks: Check("foo", ""),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:   "SymlinkDotdot",
 | 
						|
			apply:  Symlink("../../", "foo"),
 | 
						|
			checks: Check("foo", ""),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:   "SymlinkRelativePath2",
 | 
						|
			apply:  Symlink("baz/target", "bar/foo"),
 | 
						|
			checks: Check("bar/foo", "bar/baz/target"),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "SymlinkScopeLink",
 | 
						|
			apply: fstest.Apply(
 | 
						|
				Symlink("root2", "root"),
 | 
						|
				Symlink("../bar", "root2/foo"),
 | 
						|
			),
 | 
						|
			checks: CheckWithScope("foo", "bar", "root"),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "SymlinkSelf",
 | 
						|
			apply: fstest.Apply(
 | 
						|
				Symlink("foo", "root/foo"),
 | 
						|
			),
 | 
						|
			checks: ErrorWithScope("foo", "root", errTooManyLinks),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "SymlinkCircular",
 | 
						|
			apply: fstest.Apply(
 | 
						|
				Symlink("foo", "bar"),
 | 
						|
				Symlink("bar", "foo"),
 | 
						|
			),
 | 
						|
			checks: ErrorWithScope("foo", "", errTooManyLinks), //TODO: Test for circular error
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "SymlinkCircularUnderRoot",
 | 
						|
			apply: fstest.Apply(
 | 
						|
				Symlink("baz", "root/bar"),
 | 
						|
				Symlink("../bak", "root/baz"),
 | 
						|
				Symlink("/bar", "root/bak"),
 | 
						|
			),
 | 
						|
			checks: ErrorWithScope("bar", "root", errTooManyLinks), // TODO: Test for circular error
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "SymlinkComplexChain",
 | 
						|
			apply: fstest.Apply(
 | 
						|
				fstest.CreateDir("root2", 0777),
 | 
						|
				Symlink("root2", "root"),
 | 
						|
				Symlink("r/s", "root/a"),
 | 
						|
				Symlink("../root/t", "root/r"),
 | 
						|
				Symlink("/../u", "root/root/t/s/b"),
 | 
						|
				Symlink(".", "root/u/c"),
 | 
						|
				Symlink("../v", "root/u/x/y"),
 | 
						|
				Symlink("/../w", "root/u/v"),
 | 
						|
			),
 | 
						|
			checks: CheckWithScope("a/b/c/x/y/z", "w/z", "root"), // Original return
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "SymlinkBreakoutNonExistent",
 | 
						|
			apply: fstest.Apply(
 | 
						|
				Symlink("/", "root/slash"),
 | 
						|
				Symlink("/idontexist/../slash", "root/sym"),
 | 
						|
			),
 | 
						|
			checks: CheckWithScope("sym/file", "file", "root"),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "SymlinkNoLexicalCleaning",
 | 
						|
			apply: fstest.Apply(
 | 
						|
				Symlink("/foo/bar", "root/sym"),
 | 
						|
				Symlink("/sym/../baz", "root/hello"),
 | 
						|
			),
 | 
						|
			checks: CheckWithScope("hello", "foo/baz", "root"),
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, test := range tests {
 | 
						|
		t.Run(test.name, makeRootPathTest(t, test.apply, test.checks))
 | 
						|
	}
 | 
						|
 | 
						|
	// Add related tests which are unable to follow same pattern
 | 
						|
	t.Run("SymlinkRootScope", testRootPathSymlinkRootScope)
 | 
						|
	t.Run("SymlinkEmpty", testRootPathSymlinkEmpty)
 | 
						|
}
 | 
						|
 | 
						|
func testRootPathSymlinkRootScope(t *testing.T) {
 | 
						|
	tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRootScope")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	defer os.RemoveAll(tmpdir)
 | 
						|
 | 
						|
	expected, err := filepath.EvalSymlinks(tmpdir)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	rewrite, err := RootPath("/", tmpdir)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if rewrite != expected {
 | 
						|
		t.Fatalf("expected %q got %q", expected, rewrite)
 | 
						|
	}
 | 
						|
}
 | 
						|
func testRootPathSymlinkEmpty(t *testing.T) {
 | 
						|
	wd, err := os.Getwd()
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	res, err := RootPath(wd, "")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if res != wd {
 | 
						|
		t.Fatalf("expected %q got %q", wd, res)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func makeRootPathTest(t *testing.T, apply fstest.Applier, checks []RootCheck) func(t *testing.T) {
 | 
						|
	return func(t *testing.T) {
 | 
						|
		applyDir, err := ioutil.TempDir("", "test-root-path-")
 | 
						|
		if err != nil {
 | 
						|
			t.Fatalf("Unable to make temp directory: %+v", err)
 | 
						|
		}
 | 
						|
		defer os.RemoveAll(applyDir)
 | 
						|
 | 
						|
		if apply != nil {
 | 
						|
			if err := apply.Apply(applyDir); err != nil {
 | 
						|
				t.Fatalf("Apply failed: %+v", err)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		for i, check := range checks {
 | 
						|
			root := applyDir
 | 
						|
			if check.scope != nil {
 | 
						|
				root = check.scope(root)
 | 
						|
			}
 | 
						|
 | 
						|
			actual, err := RootPath(root, check.unresolved)
 | 
						|
			if check.cause != nil {
 | 
						|
				if err == nil {
 | 
						|
					t.Errorf("(Check %d) Expected error %q, %q evaluated as %q", i+1, check.cause.Error(), check.unresolved, actual)
 | 
						|
				}
 | 
						|
				if errors.Cause(err) != check.cause {
 | 
						|
					t.Fatalf("(Check %d) Failed to evaluate root path: %+v", i+1, err)
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				expected := filepath.Join(root, check.expected)
 | 
						|
				if err != nil {
 | 
						|
					t.Fatalf("(Check %d) Failed to evaluate root path: %+v", i+1, err)
 | 
						|
				}
 | 
						|
				if actual != expected {
 | 
						|
					t.Errorf("(Check %d) Unexpected evaluated path %q, expected %q", i+1, actual, expected)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func Check(unresolved, expected string) []RootCheck {
 | 
						|
	return []RootCheck{
 | 
						|
		{
 | 
						|
			unresolved: unresolved,
 | 
						|
			expected:   expected,
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func CheckWithScope(unresolved, expected, scope string) []RootCheck {
 | 
						|
	return []RootCheck{
 | 
						|
		{
 | 
						|
			unresolved: unresolved,
 | 
						|
			expected:   expected,
 | 
						|
			scope: func(root string) string {
 | 
						|
				return filepath.Join(root, scope)
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func ErrorWithScope(unresolved, scope string, cause error) []RootCheck {
 | 
						|
	return []RootCheck{
 | 
						|
		{
 | 
						|
			unresolved: unresolved,
 | 
						|
			cause:      cause,
 | 
						|
			scope: func(root string) string {
 | 
						|
				return filepath.Join(root, scope)
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func CheckAll(checks ...[]RootCheck) []RootCheck {
 | 
						|
	all := make([]RootCheck, 0, len(checks))
 | 
						|
	for _, c := range checks {
 | 
						|
		all = append(all, c...)
 | 
						|
	}
 | 
						|
	return all
 | 
						|
}
 | 
						|
 | 
						|
func Symlink(oldname, newname string) fstest.Applier {
 | 
						|
	dir := filepath.Dir(newname)
 | 
						|
	if dir != "" {
 | 
						|
		return fstest.Apply(
 | 
						|
			fstest.CreateDir(dir, 0755),
 | 
						|
			fstest.Symlink(oldname, newname),
 | 
						|
		)
 | 
						|
	}
 | 
						|
	return fstest.Symlink(oldname, newname)
 | 
						|
}
 |