1
2
3
4
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"),
70 ),
71 },
72 {
73 name: "SymlinkDeepRelativeLinkChangeScope",
74 apply: Symlink("../../../../test", "fs/a/f"),
75 checks: CheckAll(
76 Check("fs/a/f", "test"),
77 CheckWithScope("a/f", "test", "fs"),
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),
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),
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"),
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
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