Browse code

Move Follow symlink to pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby <michael@crosbymichael.com> (github: crosbymichael)

Michael Crosby authored on 2014/05/14 02:34:30
Showing 15 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,72 @@
0
+package symlink
1
+
2
+import (
3
+	"fmt"
4
+	"os"
5
+	"path/filepath"
6
+	"strings"
7
+)
8
+
9
+// FollowSymlink will follow an existing link and scope it to the root
10
+// path provided.
11
+func FollowSymlinkInScope(link, root string) (string, error) {
12
+	prev := "/"
13
+
14
+	root, err := filepath.Abs(root)
15
+	if err != nil {
16
+		return "", err
17
+	}
18
+
19
+	link, err = filepath.Abs(link)
20
+	if err != nil {
21
+		return "", err
22
+	}
23
+
24
+	if !strings.HasPrefix(filepath.Dir(link), root) {
25
+		return "", fmt.Errorf("%s is not within %s", link, root)
26
+	}
27
+
28
+	for _, p := range strings.Split(link, "/") {
29
+		prev = filepath.Join(prev, p)
30
+		prev = filepath.Clean(prev)
31
+
32
+		for {
33
+			if !strings.HasPrefix(prev, root) {
34
+				// Don't resolve symlinks outside of root. For example,
35
+				// we don't have to check /home in the below.
36
+				//
37
+				//   /home -> usr/home
38
+				//   FollowSymlinkInScope("/home/bob/foo/bar", "/home/bob/foo")
39
+				break
40
+			}
41
+
42
+			stat, err := os.Lstat(prev)
43
+			if err != nil {
44
+				if os.IsNotExist(err) {
45
+					break
46
+				}
47
+				return "", err
48
+			}
49
+			if stat.Mode()&os.ModeSymlink == os.ModeSymlink {
50
+				dest, err := os.Readlink(prev)
51
+				if err != nil {
52
+					return "", err
53
+				}
54
+
55
+				switch dest[0] {
56
+				case '/':
57
+					prev = filepath.Join(root, dest)
58
+				case '.':
59
+					prev, _ = filepath.Abs(prev)
60
+
61
+					if prev = filepath.Clean(filepath.Join(filepath.Dir(prev), dest)); len(prev) < len(root) {
62
+						prev = filepath.Join(root, filepath.Base(dest))
63
+					}
64
+				}
65
+			} else {
66
+				break
67
+			}
68
+		}
69
+	}
70
+	return prev, nil
71
+}
0 72
new file mode 100644
... ...
@@ -0,0 +1,108 @@
0
+package symlink
1
+
2
+import (
3
+	"io/ioutil"
4
+	"os"
5
+	"path/filepath"
6
+	"testing"
7
+)
8
+
9
+func abs(t *testing.T, p string) string {
10
+	o, err := filepath.Abs(p)
11
+	if err != nil {
12
+		t.Fatal(err)
13
+	}
14
+	return o
15
+}
16
+
17
+func TestFollowSymLinkNormal(t *testing.T) {
18
+	link := "testdata/fs/a/d/c/data"
19
+
20
+	rewrite, err := FollowSymlinkInScope(link, "testdata")
21
+	if err != nil {
22
+		t.Fatal(err)
23
+	}
24
+
25
+	if expected := abs(t, "testdata/b/c/data"); expected != rewrite {
26
+		t.Fatalf("Expected %s got %s", expected, rewrite)
27
+	}
28
+}
29
+
30
+func TestFollowSymLinkUnderLinkedDir(t *testing.T) {
31
+	dir, err := ioutil.TempDir("", "docker-fs-test")
32
+	if err != nil {
33
+		t.Fatal(err)
34
+	}
35
+
36
+	os.Mkdir(filepath.Join(dir, "realdir"), 0700)
37
+	os.Symlink("realdir", filepath.Join(dir, "linkdir"))
38
+
39
+	linkDir := filepath.Join(dir, "linkdir", "foo")
40
+	dirUnderLinkDir := filepath.Join(dir, "linkdir", "foo", "bar")
41
+	os.MkdirAll(dirUnderLinkDir, 0700)
42
+
43
+	rewrite, err := FollowSymlinkInScope(dirUnderLinkDir, linkDir)
44
+	if err != nil {
45
+		t.Fatal(err)
46
+	}
47
+
48
+	if rewrite != dirUnderLinkDir {
49
+		t.Fatalf("Expected %s got %s", dirUnderLinkDir, rewrite)
50
+	}
51
+}
52
+
53
+func TestFollowSymLinkRandomString(t *testing.T) {
54
+	if _, err := FollowSymlinkInScope("toto", "testdata"); err == nil {
55
+		t.Fatal("Random string should fail but didn't")
56
+	}
57
+}
58
+
59
+func TestFollowSymLinkLastLink(t *testing.T) {
60
+	link := "testdata/fs/a/d"
61
+
62
+	rewrite, err := FollowSymlinkInScope(link, "testdata")
63
+	if err != nil {
64
+		t.Fatal(err)
65
+	}
66
+
67
+	if expected := abs(t, "testdata/b"); expected != rewrite {
68
+		t.Fatalf("Expected %s got %s", expected, rewrite)
69
+	}
70
+}
71
+
72
+func TestFollowSymLinkRelativeLink(t *testing.T) {
73
+	link := "testdata/fs/a/e/c/data"
74
+
75
+	rewrite, err := FollowSymlinkInScope(link, "testdata")
76
+	if err != nil {
77
+		t.Fatal(err)
78
+	}
79
+
80
+	if expected := abs(t, "testdata/fs/b/c/data"); expected != rewrite {
81
+		t.Fatalf("Expected %s got %s", expected, rewrite)
82
+	}
83
+}
84
+
85
+func TestFollowSymLinkRelativeLinkScope(t *testing.T) {
86
+	link := "testdata/fs/a/f"
87
+
88
+	rewrite, err := FollowSymlinkInScope(link, "testdata")
89
+	if err != nil {
90
+		t.Fatal(err)
91
+	}
92
+
93
+	if expected := abs(t, "testdata/test"); expected != rewrite {
94
+		t.Fatalf("Expected %s got %s", expected, rewrite)
95
+	}
96
+
97
+	link = "testdata/fs/b/h"
98
+
99
+	rewrite, err = FollowSymlinkInScope(link, "testdata")
100
+	if err != nil {
101
+		t.Fatal(err)
102
+	}
103
+
104
+	if expected := abs(t, "testdata/root"); expected != rewrite {
105
+		t.Fatalf("Expected %s got %s", expected, rewrite)
106
+	}
107
+}
0 108
new file mode 120000
... ...
@@ -0,0 +1 @@
0
+/b
0 1
\ No newline at end of file
1 2
new file mode 120000
... ...
@@ -0,0 +1 @@
0
+../b
0 1
\ No newline at end of file
1 2
new file mode 120000
... ...
@@ -0,0 +1 @@
0
+../../../../test
0 1
\ No newline at end of file
1 2
new file mode 120000
... ...
@@ -0,0 +1 @@
0
+../g
0 1
\ No newline at end of file
1 2
new file mode 120000
... ...
@@ -0,0 +1 @@
0
+../../../../../../../../../../../../root
0 1
\ No newline at end of file
1 2
deleted file mode 100644
... ...
@@ -1,103 +0,0 @@
1
-package utils
2
-
3
-import (
4
-	"fmt"
5
-	"os"
6
-	"path/filepath"
7
-	"strings"
8
-	"syscall"
9
-)
10
-
11
-// TreeSize walks a directory tree and returns its total size in bytes.
12
-func TreeSize(dir string) (size int64, err error) {
13
-	data := make(map[uint64]struct{})
14
-	err = filepath.Walk(dir, func(d string, fileInfo os.FileInfo, e error) error {
15
-		// Ignore directory sizes
16
-		if fileInfo == nil {
17
-			return nil
18
-		}
19
-
20
-		s := fileInfo.Size()
21
-		if fileInfo.IsDir() || s == 0 {
22
-			return nil
23
-		}
24
-
25
-		// Check inode to handle hard links correctly
26
-		inode := fileInfo.Sys().(*syscall.Stat_t).Ino
27
-		// inode is not a uint64 on all platforms. Cast it to avoid issues.
28
-		if _, exists := data[uint64(inode)]; exists {
29
-			return nil
30
-		}
31
-		// inode is not a uint64 on all platforms. Cast it to avoid issues.
32
-		data[uint64(inode)] = struct{}{}
33
-
34
-		size += s
35
-
36
-		return nil
37
-	})
38
-	return
39
-}
40
-
41
-// FollowSymlink will follow an existing link and scope it to the root
42
-// path provided.
43
-func FollowSymlinkInScope(link, root string) (string, error) {
44
-	prev := "/"
45
-
46
-	root, err := filepath.Abs(root)
47
-	if err != nil {
48
-		return "", err
49
-	}
50
-
51
-	link, err = filepath.Abs(link)
52
-	if err != nil {
53
-		return "", err
54
-	}
55
-
56
-	if !strings.HasPrefix(filepath.Dir(link), root) {
57
-		return "", fmt.Errorf("%s is not within %s", link, root)
58
-	}
59
-
60
-	for _, p := range strings.Split(link, "/") {
61
-		prev = filepath.Join(prev, p)
62
-		prev = filepath.Clean(prev)
63
-
64
-		for {
65
-			if !strings.HasPrefix(prev, root) {
66
-				// Don't resolve symlinks outside of root. For example,
67
-				// we don't have to check /home in the below.
68
-				//
69
-				//   /home -> usr/home
70
-				//   FollowSymlinkInScope("/home/bob/foo/bar", "/home/bob/foo")
71
-				break
72
-			}
73
-
74
-			stat, err := os.Lstat(prev)
75
-			if err != nil {
76
-				if os.IsNotExist(err) {
77
-					break
78
-				}
79
-				return "", err
80
-			}
81
-			if stat.Mode()&os.ModeSymlink == os.ModeSymlink {
82
-				dest, err := os.Readlink(prev)
83
-				if err != nil {
84
-					return "", err
85
-				}
86
-
87
-				switch dest[0] {
88
-				case '/':
89
-					prev = filepath.Join(root, dest)
90
-				case '.':
91
-					prev, _ = filepath.Abs(prev)
92
-
93
-					if prev = filepath.Clean(filepath.Join(filepath.Dir(prev), dest)); len(prev) < len(root) {
94
-						prev = filepath.Join(root, filepath.Base(dest))
95
-					}
96
-				}
97
-			} else {
98
-				break
99
-			}
100
-		}
101
-	}
102
-	return prev, nil
103
-}
104 1
deleted file mode 100644
... ...
@@ -1,108 +0,0 @@
1
-package utils
2
-
3
-import (
4
-	"io/ioutil"
5
-	"os"
6
-	"path/filepath"
7
-	"testing"
8
-)
9
-
10
-func abs(t *testing.T, p string) string {
11
-	o, err := filepath.Abs(p)
12
-	if err != nil {
13
-		t.Fatal(err)
14
-	}
15
-	return o
16
-}
17
-
18
-func TestFollowSymLinkNormal(t *testing.T) {
19
-	link := "testdata/fs/a/d/c/data"
20
-
21
-	rewrite, err := FollowSymlinkInScope(link, "testdata")
22
-	if err != nil {
23
-		t.Fatal(err)
24
-	}
25
-
26
-	if expected := abs(t, "testdata/b/c/data"); expected != rewrite {
27
-		t.Fatalf("Expected %s got %s", expected, rewrite)
28
-	}
29
-}
30
-
31
-func TestFollowSymLinkUnderLinkedDir(t *testing.T) {
32
-	dir, err := ioutil.TempDir("", "docker-fs-test")
33
-	if err != nil {
34
-		t.Fatal(err)
35
-	}
36
-
37
-	os.Mkdir(filepath.Join(dir, "realdir"), 0700)
38
-	os.Symlink("realdir", filepath.Join(dir, "linkdir"))
39
-
40
-	linkDir := filepath.Join(dir, "linkdir", "foo")
41
-	dirUnderLinkDir := filepath.Join(dir, "linkdir", "foo", "bar")
42
-	os.MkdirAll(dirUnderLinkDir, 0700)
43
-
44
-	rewrite, err := FollowSymlinkInScope(dirUnderLinkDir, linkDir)
45
-	if err != nil {
46
-		t.Fatal(err)
47
-	}
48
-
49
-	if rewrite != dirUnderLinkDir {
50
-		t.Fatalf("Expected %s got %s", dirUnderLinkDir, rewrite)
51
-	}
52
-}
53
-
54
-func TestFollowSymLinkRandomString(t *testing.T) {
55
-	if _, err := FollowSymlinkInScope("toto", "testdata"); err == nil {
56
-		t.Fatal("Random string should fail but didn't")
57
-	}
58
-}
59
-
60
-func TestFollowSymLinkLastLink(t *testing.T) {
61
-	link := "testdata/fs/a/d"
62
-
63
-	rewrite, err := FollowSymlinkInScope(link, "testdata")
64
-	if err != nil {
65
-		t.Fatal(err)
66
-	}
67
-
68
-	if expected := abs(t, "testdata/b"); expected != rewrite {
69
-		t.Fatalf("Expected %s got %s", expected, rewrite)
70
-	}
71
-}
72
-
73
-func TestFollowSymLinkRelativeLink(t *testing.T) {
74
-	link := "testdata/fs/a/e/c/data"
75
-
76
-	rewrite, err := FollowSymlinkInScope(link, "testdata")
77
-	if err != nil {
78
-		t.Fatal(err)
79
-	}
80
-
81
-	if expected := abs(t, "testdata/fs/b/c/data"); expected != rewrite {
82
-		t.Fatalf("Expected %s got %s", expected, rewrite)
83
-	}
84
-}
85
-
86
-func TestFollowSymLinkRelativeLinkScope(t *testing.T) {
87
-	link := "testdata/fs/a/f"
88
-
89
-	rewrite, err := FollowSymlinkInScope(link, "testdata")
90
-	if err != nil {
91
-		t.Fatal(err)
92
-	}
93
-
94
-	if expected := abs(t, "testdata/test"); expected != rewrite {
95
-		t.Fatalf("Expected %s got %s", expected, rewrite)
96
-	}
97
-
98
-	link = "testdata/fs/b/h"
99
-
100
-	rewrite, err = FollowSymlinkInScope(link, "testdata")
101
-	if err != nil {
102
-		t.Fatal(err)
103
-	}
104
-
105
-	if expected := abs(t, "testdata/root"); expected != rewrite {
106
-		t.Fatalf("Expected %s got %s", expected, rewrite)
107
-	}
108
-}
109 1
deleted file mode 120000
... ...
@@ -1 +0,0 @@
1
-/b
2 1
\ No newline at end of file
3 2
deleted file mode 120000
... ...
@@ -1 +0,0 @@
1
-../b
2 1
\ No newline at end of file
3 2
deleted file mode 120000
... ...
@@ -1 +0,0 @@
1
-../../../../test
2 1
\ No newline at end of file
3 2
deleted file mode 120000
... ...
@@ -1 +0,0 @@
1
-../g
2 1
\ No newline at end of file
3 2
deleted file mode 120000
... ...
@@ -1 +0,0 @@
1
-../../../../../../../../../../../../root
2 1
\ No newline at end of file
... ...
@@ -21,6 +21,7 @@ import (
21 21
 	"strconv"
22 22
 	"strings"
23 23
 	"sync"
24
+	"syscall"
24 25
 	"time"
25 26
 
26 27
 	"github.com/dotcloud/docker/dockerversion"
... ...
@@ -1091,3 +1092,33 @@ func ParseKeyValueOpt(opt string) (string, string, error) {
1091 1091
 	}
1092 1092
 	return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil
1093 1093
 }
1094
+
1095
+// TreeSize walks a directory tree and returns its total size in bytes.
1096
+func TreeSize(dir string) (size int64, err error) {
1097
+	data := make(map[uint64]struct{})
1098
+	err = filepath.Walk(dir, func(d string, fileInfo os.FileInfo, e error) error {
1099
+		// Ignore directory sizes
1100
+		if fileInfo == nil {
1101
+			return nil
1102
+		}
1103
+
1104
+		s := fileInfo.Size()
1105
+		if fileInfo.IsDir() || s == 0 {
1106
+			return nil
1107
+		}
1108
+
1109
+		// Check inode to handle hard links correctly
1110
+		inode := fileInfo.Sys().(*syscall.Stat_t).Ino
1111
+		// inode is not a uint64 on all platforms. Cast it to avoid issues.
1112
+		if _, exists := data[uint64(inode)]; exists {
1113
+			return nil
1114
+		}
1115
+		// inode is not a uint64 on all platforms. Cast it to avoid issues.
1116
+		data[uint64(inode)] = struct{}{}
1117
+
1118
+		size += s
1119
+
1120
+		return nil
1121
+	})
1122
+	return
1123
+}