Source file
src/os/root_test.go
1
2
3
4
5 package os_test
6
7 import (
8 "bytes"
9 "errors"
10 "fmt"
11 "internal/testenv"
12 "io"
13 "io/fs"
14 "net"
15 "os"
16 "path"
17 "path/filepath"
18 "runtime"
19 "slices"
20 "strings"
21 "testing"
22 "time"
23 )
24
25
26
27 func testMaybeRooted(t *testing.T, f func(t *testing.T, r *os.Root)) {
28 t.Run("NoRoot", func(t *testing.T) {
29 t.Chdir(t.TempDir())
30 f(t, nil)
31 })
32 t.Run("InRoot", func(t *testing.T) {
33 t.Chdir(t.TempDir())
34 r, err := os.OpenRoot(".")
35 if err != nil {
36 t.Fatal(err)
37 }
38 defer r.Close()
39 f(t, r)
40 })
41 }
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 func makefs(t *testing.T, fs []string) string {
58 root := path.Join(t.TempDir(), "ROOT")
59 if err := os.Mkdir(root, 0o777); err != nil {
60 t.Fatal(err)
61 }
62 for _, ent := range fs {
63 ent = strings.ReplaceAll(ent, "$ABS", root)
64 base, link, isLink := strings.Cut(ent, " => ")
65 if isLink {
66 if runtime.GOOS == "wasip1" && path.IsAbs(link) {
67 t.Skip("absolute link targets not supported on " + runtime.GOOS)
68 }
69 if runtime.GOOS == "plan9" {
70 t.Skip("symlinks not supported on " + runtime.GOOS)
71 }
72 ent = base
73 }
74 if err := os.MkdirAll(path.Join(root, path.Dir(base)), 0o777); err != nil {
75 t.Fatal(err)
76 }
77 if isLink {
78 if err := os.Symlink(link, path.Join(root, base)); err != nil {
79 t.Fatal(err)
80 }
81 } else if strings.HasSuffix(ent, "/") {
82 if err := os.MkdirAll(path.Join(root, ent), 0o777); err != nil {
83 t.Fatal(err)
84 }
85 } else {
86 if err := os.WriteFile(path.Join(root, ent), []byte(ent), 0o666); err != nil {
87 t.Fatal(err)
88 }
89 }
90 }
91 return root
92 }
93
94
95 type rootTest struct {
96 name string
97
98
99 fs []string
100
101
102 open string
103
104
105
106
107 target string
108
109
110
111
112
113
114 ltarget string
115
116
117 wantError bool
118
119
120
121
122
123
124
125 alwaysFails bool
126 }
127
128
129 func (test *rootTest) run(t *testing.T, f func(t *testing.T, target string, d *os.Root)) {
130 t.Run(test.name, func(t *testing.T) {
131 root := makefs(t, test.fs)
132 d, err := os.OpenRoot(root)
133 if err != nil {
134 t.Fatal(err)
135 }
136 defer d.Close()
137
138
139
140 target := test.target
141 if test.target != "" {
142 target = filepath.Join(root, test.target)
143 }
144 f(t, target, d)
145 })
146 }
147
148
149
150
151
152
153 func errEndsTest(t *testing.T, err error, wantError bool, format string, args ...any) bool {
154 t.Helper()
155 if wantError {
156 if err == nil {
157 op := fmt.Sprintf(format, args...)
158 t.Fatalf("%v = nil; want error", op)
159 }
160 return true
161 } else {
162 if err != nil {
163 op := fmt.Sprintf(format, args...)
164 t.Fatalf("%v = %v; want success", op, err)
165 }
166 return false
167 }
168 }
169
170 var rootTestCases = []rootTest{{
171 name: "plain path",
172 fs: []string{},
173 open: "target",
174 target: "target",
175 }, {
176 name: "path in directory",
177 fs: []string{
178 "a/b/c/",
179 },
180 open: "a/b/c/target",
181 target: "a/b/c/target",
182 }, {
183 name: "symlink",
184 fs: []string{
185 "link => target",
186 },
187 open: "link",
188 target: "target",
189 ltarget: "link",
190 }, {
191 name: "symlink chain",
192 fs: []string{
193 "link => a/b/c/target",
194 "a/b => e",
195 "a/e => ../f",
196 "f => g/h/i",
197 "g/h/i => ..",
198 "g/c/",
199 },
200 open: "link",
201 target: "g/c/target",
202 ltarget: "link",
203 }, {
204 name: "path with dot",
205 fs: []string{
206 "a/b/",
207 },
208 open: "./a/./b/./target",
209 target: "a/b/target",
210 }, {
211 name: "path with dotdot",
212 fs: []string{
213 "a/b/",
214 },
215 open: "a/../a/b/../../a/b/../b/target",
216 target: "a/b/target",
217 }, {
218 name: "dotdot no symlink",
219 fs: []string{
220 "a/",
221 },
222 open: "a/../target",
223 target: "target",
224 }, {
225 name: "dotdot after symlink",
226 fs: []string{
227 "a => b/c",
228 "b/c/",
229 },
230 open: "a/../target",
231 target: func() string {
232 if runtime.GOOS == "windows" {
233
234 return "target"
235 }
236 return "b/target"
237 }(),
238 }, {
239 name: "dotdot before symlink",
240 fs: []string{
241 "a => b/c",
242 "b/c/",
243 },
244 open: "b/../a/target",
245 target: "b/c/target",
246 }, {
247 name: "symlink ends in dot",
248 fs: []string{
249 "a => b/.",
250 "b/",
251 },
252 open: "a/target",
253 target: "b/target",
254 }, {
255 name: "directory does not exist",
256 fs: []string{},
257 open: "a/file",
258 wantError: true,
259 alwaysFails: true,
260 }, {
261 name: "empty path",
262 fs: []string{},
263 open: "",
264 wantError: true,
265 alwaysFails: true,
266 }, {
267 name: "symlink cycle",
268 fs: []string{
269 "a => a",
270 },
271 open: "a",
272 ltarget: "a",
273 wantError: true,
274 alwaysFails: true,
275 }, {
276 name: "path escapes",
277 fs: []string{},
278 open: "../ROOT/target",
279 target: "target",
280 wantError: true,
281 }, {
282 name: "long path escapes",
283 fs: []string{
284 "a/",
285 },
286 open: "a/../../ROOT/target",
287 target: "target",
288 wantError: true,
289 }, {
290 name: "absolute symlink",
291 fs: []string{
292 "link => $ABS/target",
293 },
294 open: "link",
295 ltarget: "link",
296 target: "target",
297 wantError: true,
298 }, {
299 name: "relative symlink",
300 fs: []string{
301 "link => ../ROOT/target",
302 },
303 open: "link",
304 target: "target",
305 ltarget: "link",
306 wantError: true,
307 }, {
308 name: "symlink chain escapes",
309 fs: []string{
310 "link => a/b/c/target",
311 "a/b => e",
312 "a/e => ../../ROOT",
313 "c/",
314 },
315 open: "link",
316 target: "c/target",
317 ltarget: "link",
318 wantError: true,
319 }}
320
321 func TestRootOpen_File(t *testing.T) {
322 want := []byte("target")
323 for _, test := range rootTestCases {
324 test.run(t, func(t *testing.T, target string, root *os.Root) {
325 if target != "" {
326 if err := os.WriteFile(target, want, 0o666); err != nil {
327 t.Fatal(err)
328 }
329 }
330 f, err := root.Open(test.open)
331 if errEndsTest(t, err, test.wantError, "root.Open(%q)", test.open) {
332 return
333 }
334 defer f.Close()
335 got, err := io.ReadAll(f)
336 if err != nil || !bytes.Equal(got, want) {
337 t.Errorf(`Dir.Open(%q): read content %q, %v; want %q`, test.open, string(got), err, string(want))
338 }
339 })
340 }
341 }
342
343 func TestRootOpen_Directory(t *testing.T) {
344 for _, test := range rootTestCases {
345 test.run(t, func(t *testing.T, target string, root *os.Root) {
346 if target != "" {
347 if err := os.Mkdir(target, 0o777); err != nil {
348 t.Fatal(err)
349 }
350 if err := os.WriteFile(target+"/found", nil, 0o666); err != nil {
351 t.Fatal(err)
352 }
353 }
354 f, err := root.Open(test.open)
355 if errEndsTest(t, err, test.wantError, "root.Open(%q)", test.open) {
356 return
357 }
358 defer f.Close()
359 got, err := f.Readdirnames(-1)
360 if err != nil {
361 t.Errorf(`Dir.Open(%q).Readdirnames: %v`, test.open, err)
362 }
363 if want := []string{"found"}; !slices.Equal(got, want) {
364 t.Errorf(`Dir.Open(%q).Readdirnames: %q, want %q`, test.open, got, want)
365 }
366 })
367 }
368 }
369
370 func TestRootCreate(t *testing.T) {
371 want := []byte("target")
372 for _, test := range rootTestCases {
373 test.run(t, func(t *testing.T, target string, root *os.Root) {
374 f, err := root.Create(test.open)
375 if errEndsTest(t, err, test.wantError, "root.Create(%q)", test.open) {
376 return
377 }
378 if _, err := f.Write(want); err != nil {
379 t.Fatal(err)
380 }
381 f.Close()
382 got, err := os.ReadFile(target)
383 if err != nil {
384 t.Fatalf(`reading file created with root.Create(%q): %v`, test.open, err)
385 }
386 if !bytes.Equal(got, want) {
387 t.Fatalf(`reading file created with root.Create(%q): got %q; want %q`, test.open, got, want)
388 }
389 })
390 }
391 }
392
393 func TestRootChmod(t *testing.T) {
394 if runtime.GOOS == "wasip1" {
395 t.Skip("Chmod not supported on " + runtime.GOOS)
396 }
397 for _, test := range rootTestCases {
398 test.run(t, func(t *testing.T, target string, root *os.Root) {
399 if target != "" {
400
401
402 if err := os.WriteFile(target, nil, 0o000); err != nil {
403 t.Fatal(err)
404 }
405 }
406 if runtime.GOOS == "windows" {
407
408
409 fi, err := root.Lstat(test.open)
410 if err == nil && !fi.Mode().IsRegular() {
411 t.Skip("https://go.dev/issue/71492")
412 }
413 }
414 want := os.FileMode(0o666)
415 err := root.Chmod(test.open, want)
416 if errEndsTest(t, err, test.wantError, "root.Chmod(%q)", test.open) {
417 return
418 }
419 st, err := os.Stat(target)
420 if err != nil {
421 t.Fatalf("os.Stat(%q) = %v", target, err)
422 }
423 if got := st.Mode(); got != want {
424 t.Errorf("after root.Chmod(%q, %v): file mode = %v, want %v", test.open, want, got, want)
425 }
426 })
427 }
428 }
429
430 func TestRootChtimes(t *testing.T) {
431
432
433 checkAtimes := !hasNoatime() && runtime.GOOS != "plan9"
434 for _, test := range rootTestCases {
435 test.run(t, func(t *testing.T, target string, root *os.Root) {
436 if target != "" {
437 if err := os.WriteFile(target, nil, 0o666); err != nil {
438 t.Fatal(err)
439 }
440 }
441 for _, times := range []struct {
442 atime, mtime time.Time
443 }{{
444 atime: time.Now().Add(-1 * time.Minute),
445 mtime: time.Now().Add(-1 * time.Minute),
446 }, {
447 atime: time.Now().Add(1 * time.Minute),
448 mtime: time.Now().Add(1 * time.Minute),
449 }, {
450 atime: time.Time{},
451 mtime: time.Now(),
452 }, {
453 atime: time.Now(),
454 mtime: time.Time{},
455 }} {
456 switch runtime.GOOS {
457 case "js", "plan9":
458 times.atime = times.atime.Truncate(1 * time.Second)
459 times.mtime = times.mtime.Truncate(1 * time.Second)
460 }
461
462 err := root.Chtimes(test.open, times.atime, times.mtime)
463 if errEndsTest(t, err, test.wantError, "root.Chtimes(%q)", test.open) {
464 return
465 }
466 st, err := os.Stat(target)
467 if err != nil {
468 t.Fatalf("os.Stat(%q) = %v", target, err)
469 }
470 if got := st.ModTime(); !times.mtime.IsZero() && !got.Equal(times.mtime) {
471 t.Errorf("after root.Chtimes(%q, %v, %v): got mtime=%v, want %v", test.open, times.atime, times.mtime, got, times.mtime)
472 }
473 if checkAtimes {
474 if got := os.Atime(st); !times.atime.IsZero() && !got.Equal(times.atime) {
475 t.Errorf("after root.Chtimes(%q, %v, %v): got atime=%v, want %v", test.open, times.atime, times.mtime, got, times.atime)
476 }
477 }
478 }
479 })
480 }
481 }
482
483 func TestRootMkdir(t *testing.T) {
484 for _, test := range rootTestCases {
485 test.run(t, func(t *testing.T, target string, root *os.Root) {
486 wantError := test.wantError
487 if !wantError {
488 fi, err := os.Lstat(filepath.Join(root.Name(), test.open))
489 if err == nil && fi.Mode().Type() == fs.ModeSymlink {
490
491
492 wantError = true
493 }
494 }
495
496 err := root.Mkdir(test.open, 0o777)
497 if errEndsTest(t, err, wantError, "root.Create(%q)", test.open) {
498 return
499 }
500 fi, err := os.Lstat(target)
501 if err != nil {
502 t.Fatalf(`stat file created with Root.Mkdir(%q): %v`, test.open, err)
503 }
504 if !fi.IsDir() {
505 t.Fatalf(`stat file created with Root.Mkdir(%q): not a directory`, test.open)
506 }
507 })
508 }
509 }
510
511 func TestRootOpenRoot(t *testing.T) {
512 for _, test := range rootTestCases {
513 test.run(t, func(t *testing.T, target string, root *os.Root) {
514 if target != "" {
515 if err := os.Mkdir(target, 0o777); err != nil {
516 t.Fatal(err)
517 }
518 if err := os.WriteFile(target+"/f", nil, 0o666); err != nil {
519 t.Fatal(err)
520 }
521 }
522 rr, err := root.OpenRoot(test.open)
523 if errEndsTest(t, err, test.wantError, "root.OpenRoot(%q)", test.open) {
524 return
525 }
526 defer rr.Close()
527 f, err := rr.Open("f")
528 if err != nil {
529 t.Fatalf(`root.OpenRoot(%q).Open("f") = %v`, test.open, err)
530 }
531 f.Close()
532 })
533 }
534 }
535
536 func TestRootRemoveFile(t *testing.T) {
537 for _, test := range rootTestCases {
538 test.run(t, func(t *testing.T, target string, root *os.Root) {
539 wantError := test.wantError
540 if test.ltarget != "" {
541
542
543 wantError = false
544 target = filepath.Join(root.Name(), test.ltarget)
545 } else if target != "" {
546 if err := os.WriteFile(target, nil, 0o666); err != nil {
547 t.Fatal(err)
548 }
549 }
550
551 err := root.Remove(test.open)
552 if errEndsTest(t, err, wantError, "root.Remove(%q)", test.open) {
553 return
554 }
555 _, err = os.Lstat(target)
556 if !errors.Is(err, os.ErrNotExist) {
557 t.Fatalf(`stat file removed with Root.Remove(%q): %v, want ErrNotExist`, test.open, err)
558 }
559 })
560 }
561 }
562
563 func TestRootRemoveDirectory(t *testing.T) {
564 for _, test := range rootTestCases {
565 test.run(t, func(t *testing.T, target string, root *os.Root) {
566 wantError := test.wantError
567 if test.ltarget != "" {
568
569
570 wantError = false
571 target = filepath.Join(root.Name(), test.ltarget)
572 } else if target != "" {
573 if err := os.Mkdir(target, 0o777); err != nil {
574 t.Fatal(err)
575 }
576 }
577
578 err := root.Remove(test.open)
579 if errEndsTest(t, err, wantError, "root.Remove(%q)", test.open) {
580 return
581 }
582 _, err = os.Lstat(target)
583 if !errors.Is(err, os.ErrNotExist) {
584 t.Fatalf(`stat file removed with Root.Remove(%q): %v, want ErrNotExist`, test.open, err)
585 }
586 })
587 }
588 }
589
590 func TestRootOpenFileAsRoot(t *testing.T) {
591 dir := t.TempDir()
592 target := filepath.Join(dir, "target")
593 if err := os.WriteFile(target, nil, 0o666); err != nil {
594 t.Fatal(err)
595 }
596 r, err := os.OpenRoot(target)
597 if err == nil {
598 r.Close()
599 t.Fatal("os.OpenRoot(file) succeeded; want failure")
600 }
601 r, err = os.OpenRoot(dir)
602 if err != nil {
603 t.Fatal(err)
604 }
605 defer r.Close()
606 rr, err := r.OpenRoot("target")
607 if err == nil {
608 rr.Close()
609 t.Fatal("Root.OpenRoot(file) succeeded; want failure")
610 }
611 }
612
613 func TestRootStat(t *testing.T) {
614 for _, test := range rootTestCases {
615 test.run(t, func(t *testing.T, target string, root *os.Root) {
616 const content = "content"
617 if target != "" {
618 if err := os.WriteFile(target, []byte(content), 0o666); err != nil {
619 t.Fatal(err)
620 }
621 }
622
623 fi, err := root.Stat(test.open)
624 if errEndsTest(t, err, test.wantError, "root.Stat(%q)", test.open) {
625 return
626 }
627 if got, want := fi.Name(), filepath.Base(test.open); got != want {
628 t.Errorf("root.Stat(%q).Name() = %q, want %q", test.open, got, want)
629 }
630 if got, want := fi.Size(), int64(len(content)); got != want {
631 t.Errorf("root.Stat(%q).Size() = %v, want %v", test.open, got, want)
632 }
633 })
634 }
635 }
636
637 func TestRootLstat(t *testing.T) {
638 for _, test := range rootTestCases {
639 test.run(t, func(t *testing.T, target string, root *os.Root) {
640 const content = "content"
641 wantError := test.wantError
642 if test.ltarget != "" {
643
644 wantError = false
645 } else if target != "" {
646 if err := os.WriteFile(target, []byte(content), 0o666); err != nil {
647 t.Fatal(err)
648 }
649 }
650
651 fi, err := root.Lstat(test.open)
652 if errEndsTest(t, err, wantError, "root.Stat(%q)", test.open) {
653 return
654 }
655 if got, want := fi.Name(), filepath.Base(test.open); got != want {
656 t.Errorf("root.Stat(%q).Name() = %q, want %q", test.open, got, want)
657 }
658 if test.ltarget == "" {
659 if got := fi.Mode(); got&os.ModeSymlink != 0 {
660 t.Errorf("root.Stat(%q).Mode() = %v, want non-symlink", test.open, got)
661 }
662 if got, want := fi.Size(), int64(len(content)); got != want {
663 t.Errorf("root.Stat(%q).Size() = %v, want %v", test.open, got, want)
664 }
665 } else {
666 if got := fi.Mode(); got&os.ModeSymlink == 0 {
667 t.Errorf("root.Stat(%q).Mode() = %v, want symlink", test.open, got)
668 }
669 }
670 })
671 }
672 }
673
674 func TestRootReadlink(t *testing.T) {
675 for _, test := range rootTestCases {
676 test.run(t, func(t *testing.T, target string, root *os.Root) {
677 const content = "content"
678 wantError := test.wantError
679 if test.ltarget != "" {
680
681 wantError = false
682 } else {
683
684 wantError = true
685 }
686
687 got, err := root.Readlink(test.open)
688 if errEndsTest(t, err, wantError, "root.Readlink(%q)", test.open) {
689 return
690 }
691
692 want, err := os.Readlink(filepath.Join(root.Name(), test.ltarget))
693 if err != nil {
694 t.Fatalf("os.Readlink(%q) = %v, want success", test.ltarget, err)
695 }
696 if got != want {
697 t.Errorf("root.Readlink(%q) = %q, want %q", test.open, got, want)
698 }
699 })
700 }
701 }
702
703
704 func TestRootRenameFrom(t *testing.T) {
705 testRootMoveFrom(t, true)
706 }
707
708
709 func TestRootLinkFrom(t *testing.T) {
710 testenv.MustHaveLink(t)
711 testRootMoveFrom(t, false)
712 }
713
714 func testRootMoveFrom(t *testing.T, rename bool) {
715 want := []byte("target")
716 for _, test := range rootTestCases {
717 test.run(t, func(t *testing.T, target string, root *os.Root) {
718 if target != "" {
719 if err := os.WriteFile(target, want, 0o666); err != nil {
720 t.Fatal(err)
721 }
722 }
723 wantError := test.wantError
724 var linkTarget string
725 if test.ltarget != "" {
726
727 wantError = false
728 var err error
729 linkTarget, err = root.Readlink(test.ltarget)
730 if err != nil {
731 t.Fatalf("root.Readlink(%q) = %v, want success", test.ltarget, err)
732 }
733
734
735 if !rename && runtime.GOOS == "js" {
736 wantError = true
737 }
738 }
739
740 const dstPath = "destination"
741
742
743 if runtime.GOOS == "plan9" && strings.Contains(test.open, "/") {
744 wantError = true
745 }
746
747 var op string
748 var err error
749 if rename {
750 op = "Rename"
751 err = root.Rename(test.open, dstPath)
752 } else {
753 op = "Link"
754 err = root.Link(test.open, dstPath)
755 }
756 if errEndsTest(t, err, wantError, "root.%v(%q, %q)", op, test.open, dstPath) {
757 return
758 }
759
760 origPath := target
761 if test.ltarget != "" {
762 origPath = filepath.Join(root.Name(), test.ltarget)
763 }
764 _, err = os.Lstat(origPath)
765 if rename {
766 if !errors.Is(err, os.ErrNotExist) {
767 t.Errorf("after renaming file, Lstat(%q) = %v, want ErrNotExist", origPath, err)
768 }
769 } else {
770 if err != nil {
771 t.Errorf("after linking file, error accessing original: %v", err)
772 }
773 }
774
775 dstFullPath := filepath.Join(root.Name(), dstPath)
776 if test.ltarget != "" {
777 got, err := os.Readlink(dstFullPath)
778 if err != nil || got != linkTarget {
779 t.Errorf("os.Readlink(%q) = %q, %v, want %q", dstFullPath, got, err, linkTarget)
780 }
781 } else {
782 got, err := os.ReadFile(dstFullPath)
783 if err != nil || !bytes.Equal(got, want) {
784 t.Errorf(`os.ReadFile(%q): read content %q, %v; want %q`, dstFullPath, string(got), err, string(want))
785 }
786 st, err := os.Lstat(dstFullPath)
787 if err != nil || st.Mode()&fs.ModeSymlink != 0 {
788 t.Errorf(`os.Lstat(%q) = %v, %v; want non-symlink`, dstFullPath, st.Mode(), err)
789 }
790
791 }
792 })
793 }
794 }
795
796
797 func TestRootRenameTo(t *testing.T) {
798 testRootMoveTo(t, true)
799 }
800
801
802 func TestRootLinkTo(t *testing.T) {
803 testenv.MustHaveLink(t)
804 testRootMoveTo(t, true)
805 }
806
807 func testRootMoveTo(t *testing.T, rename bool) {
808 want := []byte("target")
809 for _, test := range rootTestCases {
810 test.run(t, func(t *testing.T, target string, root *os.Root) {
811 const srcPath = "source"
812 if err := os.WriteFile(filepath.Join(root.Name(), srcPath), want, 0o666); err != nil {
813 t.Fatal(err)
814 }
815
816 target = test.target
817 wantError := test.wantError
818 if test.ltarget != "" {
819
820 target = test.ltarget
821 wantError = false
822 }
823
824
825 if runtime.GOOS == "plan9" && strings.Contains(test.open, "/") {
826 wantError = true
827 }
828
829 var err error
830 var op string
831 if rename {
832 op = "Rename"
833 err = root.Rename(srcPath, test.open)
834 } else {
835 op = "Link"
836 err = root.Link(srcPath, test.open)
837 }
838 if errEndsTest(t, err, wantError, "root.%v(%q, %q)", op, srcPath, test.open) {
839 return
840 }
841
842 _, err = os.Lstat(filepath.Join(root.Name(), srcPath))
843 if rename {
844 if !errors.Is(err, os.ErrNotExist) {
845 t.Errorf("after renaming file, Lstat(%q) = %v, want ErrNotExist", srcPath, err)
846 }
847 } else {
848 if err != nil {
849 t.Errorf("after linking file, error accessing original: %v", err)
850 }
851 }
852
853 got, err := os.ReadFile(filepath.Join(root.Name(), target))
854 if err != nil || !bytes.Equal(got, want) {
855 t.Errorf(`os.ReadFile(%q): read content %q, %v; want %q`, target, string(got), err, string(want))
856 }
857 })
858 }
859 }
860
861 func TestRootSymlink(t *testing.T) {
862 testenv.MustHaveSymlink(t)
863 for _, test := range rootTestCases {
864 test.run(t, func(t *testing.T, target string, root *os.Root) {
865 wantError := test.wantError
866 if test.ltarget != "" {
867
868 wantError = true
869 }
870
871 const wantTarget = "linktarget"
872 err := root.Symlink(wantTarget, test.open)
873 if errEndsTest(t, err, wantError, "root.Symlink(%q)", test.open) {
874 return
875 }
876 got, err := os.Readlink(target)
877 if err != nil || got != wantTarget {
878 t.Fatalf("ReadLink(%q) = %q, %v; want %q, nil", target, got, err, wantTarget)
879 }
880 })
881 }
882 }
883
884
885
886
887
888
889 type rootConsistencyTest struct {
890 name string
891
892
893
894 fs []string
895 fsFunc func(t *testing.T, dir string) string
896
897
898 open string
899
900
901
902 detailedErrorMismatch func(t *testing.T) bool
903 }
904
905 var rootConsistencyTestCases = []rootConsistencyTest{{
906 name: "file",
907 fs: []string{
908 "target",
909 },
910 open: "target",
911 }, {
912 name: "dir slash dot",
913 fs: []string{
914 "target/file",
915 },
916 open: "target/.",
917 }, {
918 name: "dot",
919 fs: []string{
920 "file",
921 },
922 open: ".",
923 }, {
924 name: "file slash dot",
925 fs: []string{
926 "target",
927 },
928 open: "target/.",
929 detailedErrorMismatch: func(t *testing.T) bool {
930
931 return runtime.GOOS == "freebsd" && strings.HasPrefix(t.Name(), "TestRootConsistencyRemove")
932 },
933 }, {
934 name: "dir slash",
935 fs: []string{
936 "target/file",
937 },
938 open: "target/",
939 }, {
940 name: "dot slash",
941 fs: []string{
942 "file",
943 },
944 open: "./",
945 }, {
946 name: "file slash",
947 fs: []string{
948 "target",
949 },
950 open: "target/",
951 detailedErrorMismatch: func(t *testing.T) bool {
952
953 return runtime.GOOS == "js"
954 },
955 }, {
956 name: "file in path",
957 fs: []string{
958 "file",
959 },
960 open: "file/target",
961 }, {
962 name: "directory in path missing",
963 open: "dir/target",
964 }, {
965 name: "target does not exist",
966 open: "target",
967 }, {
968 name: "symlink slash",
969 fs: []string{
970 "target/file",
971 "link => target",
972 },
973 open: "link/",
974 }, {
975 name: "symlink slash dot",
976 fs: []string{
977 "target/file",
978 "link => target",
979 },
980 open: "link/.",
981 }, {
982 name: "file symlink slash",
983 fs: []string{
984 "target",
985 "link => target",
986 },
987 open: "link/",
988 detailedErrorMismatch: func(t *testing.T) bool {
989
990 return runtime.GOOS == "js"
991 },
992 }, {
993 name: "unresolved symlink",
994 fs: []string{
995 "link => target",
996 },
997 open: "link",
998 }, {
999 name: "resolved symlink",
1000 fs: []string{
1001 "link => target",
1002 "target",
1003 },
1004 open: "link",
1005 }, {
1006 name: "dotdot in path after symlink",
1007 fs: []string{
1008 "a => b/c",
1009 "b/c/",
1010 "b/target",
1011 },
1012 open: "a/../target",
1013 }, {
1014 name: "long file name",
1015 open: strings.Repeat("a", 500),
1016 }, {
1017 name: "unreadable directory",
1018 fs: []string{
1019 "dir/target",
1020 },
1021 fsFunc: func(t *testing.T, dir string) string {
1022 os.Chmod(filepath.Join(dir, "dir"), 0)
1023 t.Cleanup(func() {
1024 os.Chmod(filepath.Join(dir, "dir"), 0o700)
1025 })
1026 return dir
1027 },
1028 open: "dir/target",
1029 }, {
1030 name: "unix domain socket target",
1031 fsFunc: func(t *testing.T, dir string) string {
1032 return tempDirWithUnixSocket(t, "a")
1033 },
1034 open: "a",
1035 }, {
1036 name: "unix domain socket in path",
1037 fsFunc: func(t *testing.T, dir string) string {
1038 return tempDirWithUnixSocket(t, "a")
1039 },
1040 open: "a/b",
1041 detailedErrorMismatch: func(t *testing.T) bool {
1042
1043
1044 return runtime.GOOS == "windows"
1045 },
1046 }, {
1047 name: "question mark",
1048 open: "?",
1049 }, {
1050 name: "nul byte",
1051 open: "\x00",
1052 }}
1053
1054 func tempDirWithUnixSocket(t *testing.T, name string) string {
1055 dir, err := os.MkdirTemp("", "")
1056 if err != nil {
1057 t.Fatal(err)
1058 }
1059 t.Cleanup(func() {
1060 if err := os.RemoveAll(dir); err != nil {
1061 t.Error(err)
1062 }
1063 })
1064 addr, err := net.ResolveUnixAddr("unix", filepath.Join(dir, name))
1065 if err != nil {
1066 t.Skipf("net.ResolveUnixAddr: %v", err)
1067 }
1068 conn, err := net.ListenUnix("unix", addr)
1069 if err != nil {
1070 t.Skipf("net.ListenUnix: %v", err)
1071 }
1072 t.Cleanup(func() {
1073 conn.Close()
1074 })
1075 return dir
1076 }
1077
1078 func (test rootConsistencyTest) run(t *testing.T, f func(t *testing.T, path string, r *os.Root) (string, error)) {
1079 if runtime.GOOS == "wasip1" {
1080
1081
1082
1083 t.Skip("#69509: inconsistent results on wasip1")
1084 }
1085
1086 t.Run(test.name, func(t *testing.T) {
1087 dir1 := makefs(t, test.fs)
1088 dir2 := makefs(t, test.fs)
1089 if test.fsFunc != nil {
1090 dir1 = test.fsFunc(t, dir1)
1091 dir2 = test.fsFunc(t, dir2)
1092 }
1093
1094 r, err := os.OpenRoot(dir1)
1095 if err != nil {
1096 t.Fatal(err)
1097 }
1098 defer r.Close()
1099
1100 res1, err1 := f(t, test.open, r)
1101 res2, err2 := f(t, dir2+"/"+test.open, nil)
1102
1103 if res1 != res2 || ((err1 == nil) != (err2 == nil)) {
1104 t.Errorf("with root: res=%v", res1)
1105 t.Errorf(" err=%v", err1)
1106 t.Errorf("without root: res=%v", res2)
1107 t.Errorf(" err=%v", err2)
1108 t.Errorf("want consistent results, got mismatch")
1109 }
1110
1111 if err1 != nil || err2 != nil {
1112 underlyingError := func(how string, err error) error {
1113 switch e := err1.(type) {
1114 case *os.PathError:
1115 return e.Err
1116 case *os.LinkError:
1117 return e.Err
1118 default:
1119 t.Fatalf("%v, expected PathError or LinkError; got: %v", how, err)
1120 }
1121 return nil
1122 }
1123 e1 := underlyingError("with root", err1)
1124 e2 := underlyingError("without root", err1)
1125 detailedErrorMismatch := false
1126 if f := test.detailedErrorMismatch; f != nil {
1127 detailedErrorMismatch = f(t)
1128 }
1129 if runtime.GOOS == "plan9" {
1130
1131 detailedErrorMismatch = true
1132 }
1133 if !detailedErrorMismatch && e1 != e2 {
1134 t.Errorf("with root: err=%v", e1)
1135 t.Errorf("without root: err=%v", e2)
1136 t.Errorf("want consistent results, got mismatch")
1137 }
1138 }
1139 })
1140 }
1141
1142 func TestRootConsistencyOpen(t *testing.T) {
1143 for _, test := range rootConsistencyTestCases {
1144 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
1145 var f *os.File
1146 var err error
1147 if r == nil {
1148 f, err = os.Open(path)
1149 } else {
1150 f, err = r.Open(path)
1151 }
1152 if err != nil {
1153 return "", err
1154 }
1155 defer f.Close()
1156 fi, err := f.Stat()
1157 if err == nil && !fi.IsDir() {
1158 b, err := io.ReadAll(f)
1159 return string(b), err
1160 } else {
1161 names, err := f.Readdirnames(-1)
1162 slices.Sort(names)
1163 return fmt.Sprintf("%q", names), err
1164 }
1165 })
1166 }
1167 }
1168
1169 func TestRootConsistencyCreate(t *testing.T) {
1170 for _, test := range rootConsistencyTestCases {
1171 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
1172 var f *os.File
1173 var err error
1174 if r == nil {
1175 f, err = os.Create(path)
1176 } else {
1177 f, err = r.Create(path)
1178 }
1179 if err == nil {
1180 f.Write([]byte("file contents"))
1181 f.Close()
1182 }
1183 return "", err
1184 })
1185 }
1186 }
1187
1188 func TestRootConsistencyChmod(t *testing.T) {
1189 if runtime.GOOS == "wasip1" {
1190 t.Skip("Chmod not supported on " + runtime.GOOS)
1191 }
1192 for _, test := range rootConsistencyTestCases {
1193 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
1194 chmod := os.Chmod
1195 lstat := os.Lstat
1196 if r != nil {
1197 chmod = r.Chmod
1198 lstat = r.Lstat
1199 }
1200
1201 var m1, m2 os.FileMode
1202 if err := chmod(path, 0o555); err != nil {
1203 return "chmod 0o555", err
1204 }
1205 fi, err := lstat(path)
1206 if err == nil {
1207 m1 = fi.Mode()
1208 }
1209 if err = chmod(path, 0o777); err != nil {
1210 return "chmod 0o777", err
1211 }
1212 fi, err = lstat(path)
1213 if err == nil {
1214 m2 = fi.Mode()
1215 }
1216 return fmt.Sprintf("%v %v", m1, m2), err
1217 })
1218 }
1219 }
1220
1221 func TestRootConsistencyMkdir(t *testing.T) {
1222 for _, test := range rootConsistencyTestCases {
1223 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
1224 var err error
1225 if r == nil {
1226 err = os.Mkdir(path, 0o777)
1227 } else {
1228 err = r.Mkdir(path, 0o777)
1229 }
1230 return "", err
1231 })
1232 }
1233 }
1234
1235 func TestRootConsistencyRemove(t *testing.T) {
1236 for _, test := range rootConsistencyTestCases {
1237 if test.open == "." || test.open == "./" {
1238 continue
1239 }
1240 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
1241 var err error
1242 if r == nil {
1243 err = os.Remove(path)
1244 } else {
1245 err = r.Remove(path)
1246 }
1247 return "", err
1248 })
1249 }
1250 }
1251
1252 func TestRootConsistencyStat(t *testing.T) {
1253 for _, test := range rootConsistencyTestCases {
1254 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
1255 var fi os.FileInfo
1256 var err error
1257 if r == nil {
1258 fi, err = os.Stat(path)
1259 } else {
1260 fi, err = r.Stat(path)
1261 }
1262 if err != nil {
1263 return "", err
1264 }
1265 return fmt.Sprintf("name:%q size:%v mode:%v isdir:%v", fi.Name(), fi.Size(), fi.Mode(), fi.IsDir()), nil
1266 })
1267 }
1268 }
1269
1270 func TestRootConsistencyLstat(t *testing.T) {
1271 for _, test := range rootConsistencyTestCases {
1272 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
1273 var fi os.FileInfo
1274 var err error
1275 if r == nil {
1276 fi, err = os.Lstat(path)
1277 } else {
1278 fi, err = r.Lstat(path)
1279 }
1280 if err != nil {
1281 return "", err
1282 }
1283 return fmt.Sprintf("name:%q size:%v mode:%v isdir:%v", fi.Name(), fi.Size(), fi.Mode(), fi.IsDir()), nil
1284 })
1285 }
1286 }
1287
1288 func TestRootConsistencyReadlink(t *testing.T) {
1289 for _, test := range rootConsistencyTestCases {
1290 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
1291 if r == nil {
1292 return os.Readlink(path)
1293 } else {
1294 return r.Readlink(path)
1295 }
1296 })
1297 }
1298 }
1299
1300 func TestRootConsistencyRename(t *testing.T) {
1301 testRootConsistencyMove(t, true)
1302 }
1303
1304 func TestRootConsistencyLink(t *testing.T) {
1305 testenv.MustHaveLink(t)
1306 testRootConsistencyMove(t, false)
1307 }
1308
1309 func testRootConsistencyMove(t *testing.T, rename bool) {
1310 if runtime.GOOS == "plan9" {
1311
1312 t.Skip("Plan 9 does not support cross-directory renames")
1313 }
1314
1315
1316
1317 for _, name := range []string{"from", "to"} {
1318 t.Run(name, func(t *testing.T) {
1319 for _, test := range rootConsistencyTestCases {
1320 if runtime.GOOS == "windows" {
1321
1322
1323
1324
1325 if test.open == "." || test.open == "./" {
1326 continue
1327 }
1328 }
1329
1330 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
1331 var move func(oldname, newname string) error
1332 switch {
1333 case rename && r == nil:
1334 move = os.Rename
1335 case rename && r != nil:
1336 move = r.Rename
1337 case !rename && r == nil:
1338 move = os.Link
1339 case !rename && r != nil:
1340 move = r.Link
1341 }
1342 lstat := os.Lstat
1343 if r != nil {
1344 lstat = r.Lstat
1345 }
1346
1347 otherPath := "other"
1348 if r == nil {
1349 otherPath = filepath.Join(t.TempDir(), otherPath)
1350 }
1351
1352 var srcPath, dstPath string
1353 if name == "from" {
1354 srcPath = path
1355 dstPath = otherPath
1356 } else {
1357 srcPath = otherPath
1358 dstPath = path
1359 }
1360
1361 if !rename {
1362
1363
1364
1365
1366
1367
1368
1369 fi, err := lstat(srcPath)
1370 if err == nil && fi.Mode()&os.ModeSymlink != 0 {
1371 return "", nil
1372 }
1373 }
1374
1375 if err := move(srcPath, dstPath); err != nil {
1376 return "", err
1377 }
1378 fi, err := lstat(dstPath)
1379 if err != nil {
1380 t.Errorf("stat(%q) after successful copy: %v", dstPath, err)
1381 return "stat error", err
1382 }
1383 return fmt.Sprintf("name:%q size:%v mode:%v isdir:%v", fi.Name(), fi.Size(), fi.Mode(), fi.IsDir()), nil
1384 })
1385 }
1386 })
1387 }
1388 }
1389
1390 func TestRootConsistencySymlink(t *testing.T) {
1391 testenv.MustHaveSymlink(t)
1392 for _, test := range rootConsistencyTestCases {
1393 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
1394 const target = "linktarget"
1395 var err error
1396 var got string
1397 if r == nil {
1398 err = os.Symlink(target, path)
1399 got, _ = os.Readlink(target)
1400 } else {
1401 err = r.Symlink(target, path)
1402 got, _ = r.Readlink(target)
1403 }
1404 return got, err
1405 })
1406 }
1407 }
1408
1409 func TestRootRenameAfterOpen(t *testing.T) {
1410 switch runtime.GOOS {
1411 case "windows":
1412 t.Skip("renaming open files not supported on " + runtime.GOOS)
1413 case "js", "plan9":
1414 t.Skip("openat not supported on " + runtime.GOOS)
1415 case "wasip1":
1416 if os.Getenv("GOWASIRUNTIME") == "wazero" {
1417 t.Skip("wazero does not track renamed directories")
1418 }
1419 }
1420
1421 dir := t.TempDir()
1422
1423
1424 if err := os.Mkdir(filepath.Join(dir, "a"), 0o777); err != nil {
1425 t.Fatal(err)
1426 }
1427 dirf, err := os.OpenRoot(filepath.Join(dir, "a"))
1428 if err != nil {
1429 t.Fatal(err)
1430 }
1431 defer dirf.Close()
1432
1433
1434 if err := os.Rename(filepath.Join(dir, "a"), filepath.Join(dir, "b")); err != nil {
1435 t.Fatal(err)
1436 }
1437 if err := os.WriteFile(filepath.Join(dir, "b/f"), []byte("hello"), 0o666); err != nil {
1438 t.Fatal(err)
1439 }
1440
1441
1442 f, err := dirf.OpenFile("f", os.O_RDONLY, 0)
1443 if err != nil {
1444 t.Fatalf("reading file after renaming parent: %v", err)
1445 }
1446 defer f.Close()
1447 b, err := io.ReadAll(f)
1448 if err != nil {
1449 t.Fatal(err)
1450 }
1451 if got, want := string(b), "hello"; got != want {
1452 t.Fatalf("file contents: %q, want %q", got, want)
1453 }
1454
1455
1456 if got, want := f.Name(), dirf.Name()+string(os.PathSeparator)+"f"; got != want {
1457 t.Errorf("f.Name() = %q, want %q", got, want)
1458 }
1459 }
1460
1461 func TestRootNonPermissionMode(t *testing.T) {
1462 r, err := os.OpenRoot(t.TempDir())
1463 if err != nil {
1464 t.Fatal(err)
1465 }
1466 defer r.Close()
1467 if _, err := r.OpenFile("file", os.O_RDWR|os.O_CREATE, 0o1777); err == nil {
1468 t.Errorf("r.OpenFile(file, O_RDWR|O_CREATE, 0o1777) succeeded; want error")
1469 }
1470 if err := r.Mkdir("file", 0o1777); err == nil {
1471 t.Errorf("r.Mkdir(file, 0o1777) succeeded; want error")
1472 }
1473 }
1474
1475 func TestRootUseAfterClose(t *testing.T) {
1476 r, err := os.OpenRoot(t.TempDir())
1477 if err != nil {
1478 t.Fatal(err)
1479 }
1480 r.Close()
1481 for _, test := range []struct {
1482 name string
1483 f func(r *os.Root, filename string) error
1484 }{{
1485 name: "Open",
1486 f: func(r *os.Root, filename string) error {
1487 _, err := r.Open(filename)
1488 return err
1489 },
1490 }, {
1491 name: "Create",
1492 f: func(r *os.Root, filename string) error {
1493 _, err := r.Create(filename)
1494 return err
1495 },
1496 }, {
1497 name: "OpenFile",
1498 f: func(r *os.Root, filename string) error {
1499 _, err := r.OpenFile(filename, os.O_RDWR, 0o666)
1500 return err
1501 },
1502 }, {
1503 name: "OpenRoot",
1504 f: func(r *os.Root, filename string) error {
1505 _, err := r.OpenRoot(filename)
1506 return err
1507 },
1508 }, {
1509 name: "Mkdir",
1510 f: func(r *os.Root, filename string) error {
1511 return r.Mkdir(filename, 0o777)
1512 },
1513 }} {
1514 err := test.f(r, "target")
1515 pe, ok := err.(*os.PathError)
1516 if !ok || pe.Path != "target" || pe.Err != os.ErrClosed {
1517 t.Errorf(`r.%v = %v; want &PathError{Path: "target", Err: ErrClosed}`, test.name, err)
1518 }
1519 }
1520 }
1521
1522 func TestRootConcurrentClose(t *testing.T) {
1523 r, err := os.OpenRoot(t.TempDir())
1524 if err != nil {
1525 t.Fatal(err)
1526 }
1527 ch := make(chan error, 1)
1528 go func() {
1529 defer close(ch)
1530 first := true
1531 for {
1532 f, err := r.OpenFile("file", os.O_RDWR|os.O_CREATE, 0o666)
1533 if err != nil {
1534 ch <- err
1535 return
1536 }
1537 if first {
1538 ch <- nil
1539 first = false
1540 }
1541 f.Close()
1542 if runtime.GOARCH == "wasm" {
1543
1544 runtime.Gosched()
1545 }
1546 }
1547 }()
1548 if err := <-ch; err != nil {
1549 t.Errorf("OpenFile: %v, want success", err)
1550 }
1551 r.Close()
1552 if err := <-ch; !errors.Is(err, os.ErrClosed) {
1553 t.Errorf("OpenFile: %v, want ErrClosed", err)
1554 }
1555 }
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569 func TestRootRaceRenameDir(t *testing.T) {
1570 dir := t.TempDir()
1571 r, err := os.OpenRoot(dir)
1572 if err != nil {
1573 t.Fatal(err)
1574 }
1575 defer r.Close()
1576
1577 const depth = 4
1578
1579 os.MkdirAll(dir+"/base/"+strings.Repeat("/a", depth), 0o777)
1580
1581 path := "base/" + strings.Repeat("a/", depth) + strings.Repeat("../", depth) + "a/f"
1582 os.WriteFile(dir+"/f", []byte("secret"), 0o666)
1583 os.WriteFile(dir+"/base/a/f", []byte("public"), 0o666)
1584
1585
1586 const tries = 10
1587 var total time.Duration
1588 for range tries {
1589 start := time.Now()
1590 f, err := r.Open(path)
1591 if err != nil {
1592 t.Fatal(err)
1593 }
1594 b, err := io.ReadAll(f)
1595 if err != nil {
1596 t.Fatal(err)
1597 }
1598 if string(b) != "public" {
1599 t.Fatalf("read %q, want %q", b, "public")
1600 }
1601 f.Close()
1602 total += time.Since(start)
1603 }
1604 avg := total / tries
1605
1606
1607 for range 100 {
1608
1609 gotc := make(chan []byte)
1610 go func() {
1611 f, err := r.Open(path)
1612 if err != nil {
1613 gotc <- nil
1614 }
1615 defer f.Close()
1616 b, _ := io.ReadAll(f)
1617 gotc <- b
1618 }()
1619
1620
1621
1622 time.Sleep(avg / 4)
1623 if err := os.Rename(dir+"/base/a", dir+"/b"); err != nil {
1624
1625
1626 switch runtime.GOOS {
1627 case "windows", "plan9":
1628 default:
1629 t.Fatal(err)
1630 }
1631 }
1632
1633 got := <-gotc
1634 os.Rename(dir+"/b", dir+"/base/a")
1635 if len(got) > 0 && string(got) != "public" {
1636 t.Errorf("read file: %q; want error or 'public'", got)
1637 }
1638 }
1639 }
1640
1641 func TestRootSymlinkToRoot(t *testing.T) {
1642 dir := makefs(t, []string{
1643 "d/d => ..",
1644 })
1645 root, err := os.OpenRoot(dir)
1646 if err != nil {
1647 t.Fatal(err)
1648 }
1649 defer root.Close()
1650 if err := root.Mkdir("d/d/new", 0777); err != nil {
1651 t.Fatal(err)
1652 }
1653 f, err := root.Open("d/d")
1654 if err != nil {
1655 t.Fatal(err)
1656 }
1657 defer f.Close()
1658 names, err := f.Readdirnames(-1)
1659 if err != nil {
1660 t.Fatal(err)
1661 }
1662 slices.Sort(names)
1663 if got, want := names, []string{"d", "new"}; !slices.Equal(got, want) {
1664 t.Errorf("root contains: %q, want %q", got, want)
1665 }
1666 }
1667
1668 func TestOpenInRoot(t *testing.T) {
1669 dir := makefs(t, []string{
1670 "file",
1671 "link => ../ROOT/file",
1672 })
1673 f, err := os.OpenInRoot(dir, "file")
1674 if err != nil {
1675 t.Fatalf("OpenInRoot(`file`) = %v, want success", err)
1676 }
1677 f.Close()
1678 for _, name := range []string{
1679 "link",
1680 "../ROOT/file",
1681 dir + "/file",
1682 } {
1683 f, err := os.OpenInRoot(dir, name)
1684 if err == nil {
1685 f.Close()
1686 t.Fatalf("OpenInRoot(%q) = nil, want error", name)
1687 }
1688 }
1689 }
1690
View as plain text