Move Follow symlink to pkg
Docker-DCO-1.1-Signed-off-by: Michael Crosby <michael@crosbymichael.com> (github: crosbymichael)
This commit is contained in:
parent
4086e37e27
commit
e8a8022ba3
7 changed files with 185 additions and 0 deletions
72
symlink/fs.go
Normal file
72
symlink/fs.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
package symlink
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// FollowSymlink will follow an existing link and scope it to the root
|
||||
// path provided.
|
||||
func FollowSymlinkInScope(link, root string) (string, error) {
|
||||
prev := "/"
|
||||
|
||||
root, err := filepath.Abs(root)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
link, err = filepath.Abs(link)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(filepath.Dir(link), root) {
|
||||
return "", fmt.Errorf("%s is not within %s", link, root)
|
||||
}
|
||||
|
||||
for _, p := range strings.Split(link, "/") {
|
||||
prev = filepath.Join(prev, p)
|
||||
prev = filepath.Clean(prev)
|
||||
|
||||
for {
|
||||
if !strings.HasPrefix(prev, root) {
|
||||
// Don't resolve symlinks outside of root. For example,
|
||||
// we don't have to check /home in the below.
|
||||
//
|
||||
// /home -> usr/home
|
||||
// FollowSymlinkInScope("/home/bob/foo/bar", "/home/bob/foo")
|
||||
break
|
||||
}
|
||||
|
||||
stat, err := os.Lstat(prev)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
break
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
if stat.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
dest, err := os.Readlink(prev)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch dest[0] {
|
||||
case '/':
|
||||
prev = filepath.Join(root, dest)
|
||||
case '.':
|
||||
prev, _ = filepath.Abs(prev)
|
||||
|
||||
if prev = filepath.Clean(filepath.Join(filepath.Dir(prev), dest)); len(prev) < len(root) {
|
||||
prev = filepath.Join(root, filepath.Base(dest))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return prev, nil
|
||||
}
|
108
symlink/fs_test.go
Normal file
108
symlink/fs_test.go
Normal file
|
@ -0,0 +1,108 @@
|
|||
package symlink
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func abs(t *testing.T, p string) string {
|
||||
o, err := filepath.Abs(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func TestFollowSymLinkNormal(t *testing.T) {
|
||||
link := "testdata/fs/a/d/c/data"
|
||||
|
||||
rewrite, err := FollowSymlinkInScope(link, "testdata")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := abs(t, "testdata/b/c/data"); expected != rewrite {
|
||||
t.Fatalf("Expected %s got %s", expected, rewrite)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFollowSymLinkUnderLinkedDir(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "docker-fs-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
os.Mkdir(filepath.Join(dir, "realdir"), 0700)
|
||||
os.Symlink("realdir", filepath.Join(dir, "linkdir"))
|
||||
|
||||
linkDir := filepath.Join(dir, "linkdir", "foo")
|
||||
dirUnderLinkDir := filepath.Join(dir, "linkdir", "foo", "bar")
|
||||
os.MkdirAll(dirUnderLinkDir, 0700)
|
||||
|
||||
rewrite, err := FollowSymlinkInScope(dirUnderLinkDir, linkDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if rewrite != dirUnderLinkDir {
|
||||
t.Fatalf("Expected %s got %s", dirUnderLinkDir, rewrite)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFollowSymLinkRandomString(t *testing.T) {
|
||||
if _, err := FollowSymlinkInScope("toto", "testdata"); err == nil {
|
||||
t.Fatal("Random string should fail but didn't")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFollowSymLinkLastLink(t *testing.T) {
|
||||
link := "testdata/fs/a/d"
|
||||
|
||||
rewrite, err := FollowSymlinkInScope(link, "testdata")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := abs(t, "testdata/b"); expected != rewrite {
|
||||
t.Fatalf("Expected %s got %s", expected, rewrite)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFollowSymLinkRelativeLink(t *testing.T) {
|
||||
link := "testdata/fs/a/e/c/data"
|
||||
|
||||
rewrite, err := FollowSymlinkInScope(link, "testdata")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := abs(t, "testdata/fs/b/c/data"); expected != rewrite {
|
||||
t.Fatalf("Expected %s got %s", expected, rewrite)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFollowSymLinkRelativeLinkScope(t *testing.T) {
|
||||
link := "testdata/fs/a/f"
|
||||
|
||||
rewrite, err := FollowSymlinkInScope(link, "testdata")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := abs(t, "testdata/test"); expected != rewrite {
|
||||
t.Fatalf("Expected %s got %s", expected, rewrite)
|
||||
}
|
||||
|
||||
link = "testdata/fs/b/h"
|
||||
|
||||
rewrite, err = FollowSymlinkInScope(link, "testdata")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := abs(t, "testdata/root"); expected != rewrite {
|
||||
t.Fatalf("Expected %s got %s", expected, rewrite)
|
||||
}
|
||||
}
|
1
symlink/testdata/fs/a/d
vendored
Symbolic link
1
symlink/testdata/fs/a/d
vendored
Symbolic link
|
@ -0,0 +1 @@
|
|||
/b
|
1
symlink/testdata/fs/a/e
vendored
Symbolic link
1
symlink/testdata/fs/a/e
vendored
Symbolic link
|
@ -0,0 +1 @@
|
|||
../b
|
1
symlink/testdata/fs/a/f
vendored
Symbolic link
1
symlink/testdata/fs/a/f
vendored
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../../../test
|
1
symlink/testdata/fs/b/h
vendored
Symbolic link
1
symlink/testdata/fs/b/h
vendored
Symbolic link
|
@ -0,0 +1 @@
|
|||
../g
|
1
symlink/testdata/fs/g
vendored
Symbolic link
1
symlink/testdata/fs/g
vendored
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../../../../../../../../../../../root
|
Loading…
Reference in a new issue