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 "io"
12 "io/fs"
13 "net"
14 "os"
15 "path"
16 "path/filepath"
17 "runtime"
18 "slices"
19 "strings"
20 "testing"
21 "time"
22 )
23
24
25
26 func testMaybeRooted(t *testing.T, f func(t *testing.T, r *os.Root)) {
27 t.Run("NoRoot", func(t *testing.T) {
28 t.Chdir(t.TempDir())
29 f(t, nil)
30 })
31 t.Run("InRoot", func(t *testing.T) {
32 t.Chdir(t.TempDir())
33 r, err := os.OpenRoot(".")
34 if err != nil {
35 t.Fatal(err)
36 }
37 defer r.Close()
38 f(t, r)
39 })
40 }
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56 func makefs(t *testing.T, fs []string) string {
57 root := path.Join(t.TempDir(), "ROOT")
58 if err := os.Mkdir(root, 0o777); err != nil {
59 t.Fatal(err)
60 }
61 for _, ent := range fs {
62 ent = strings.ReplaceAll(ent, "$ABS", root)
63 base, link, isLink := strings.Cut(ent, " => ")
64 if isLink {
65 if runtime.GOOS == "wasip1" && path.IsAbs(link) {
66 t.Skip("absolute link targets not supported on " + runtime.GOOS)
67 }
68 if runtime.GOOS == "plan9" {
69 t.Skip("symlinks not supported on " + runtime.GOOS)
70 }
71 ent = base
72 }
73 if err := os.MkdirAll(path.Join(root, path.Dir(base)), 0o777); err != nil {
74 t.Fatal(err)
75 }
76 if isLink {
77 if err := os.Symlink(link, path.Join(root, base)); err != nil {
78 t.Fatal(err)
79 }
80 } else if strings.HasSuffix(ent, "/") {
81 if err := os.MkdirAll(path.Join(root, ent), 0o777); err != nil {
82 t.Fatal(err)
83 }
84 } else {
85 if err := os.WriteFile(path.Join(root, ent), []byte(ent), 0o666); err != nil {
86 t.Fatal(err)
87 }
88 }
89 }
90 return root
91 }
92
93
94 type rootTest struct {
95 name string
96
97
98 fs []string
99
100
101 open string
102
103
104
105
106 target string
107
108
109
110
111
112
113 ltarget string
114
115
116 wantError bool
117
118
119
120
121
122
123
124 alwaysFails bool
125 }
126
127
128 func (test *rootTest) run(t *testing.T, f func(t *testing.T, target string, d *os.Root)) {
129 t.Run(test.name, func(t *testing.T) {
130 root := makefs(t, test.fs)
131 d, err := os.OpenRoot(root)
132 if err != nil {
133 t.Fatal(err)
134 }
135 defer d.Close()
136
137
138
139 target := test.target
140 if test.target != "" {
141 target = filepath.Join(root, test.target)
142 }
143 f(t, target, d)
144 })
145 }
146
147
148
149
150
151
152 func errEndsTest(t *testing.T, err error, wantError bool, format string, args ...any) bool {
153 t.Helper()
154 if wantError {
155 if err == nil {
156 op := fmt.Sprintf(format, args...)
157 t.Fatalf("%v = nil; want error", op)
158 }
159 return true
160 } else {
161 if err != nil {
162 op := fmt.Sprintf(format, args...)
163 t.Fatalf("%v = %v; want success", op, err)
164 }
165 return false
166 }
167 }
168
169 var rootTestCases = []rootTest{{
170 name: "plain path",
171 fs: []string{},
172 open: "target",
173 target: "target",
174 }, {
175 name: "path in directory",
176 fs: []string{
177 "a/b/c/",
178 },
179 open: "a/b/c/target",
180 target: "a/b/c/target",
181 }, {
182 name: "symlink",
183 fs: []string{
184 "link => target",
185 },
186 open: "link",
187 target: "target",
188 ltarget: "link",
189 }, {
190 name: "symlink dotdot slash",
191 fs: []string{
192 "link => ../",
193 },
194 open: "link",
195 ltarget: "link",
196 wantError: true,
197 }, {
198 name: "symlink ending in slash",
199 fs: []string{
200 "dir/",
201 "link => dir/",
202 },
203 open: "link/target",
204 target: "dir/target",
205 }, {
206 name: "symlink dotdot dotdot slash",
207 fs: []string{
208 "dir/link => ../../",
209 },
210 open: "dir/link",
211 ltarget: "dir/link",
212 wantError: true,
213 }, {
214 name: "symlink chain",
215 fs: []string{
216 "link => a/b/c/target",
217 "a/b => e",
218 "a/e => ../f",
219 "f => g/h/i",
220 "g/h/i => ..",
221 "g/c/",
222 },
223 open: "link",
224 target: "g/c/target",
225 ltarget: "link",
226 }, {
227 name: "path with dot",
228 fs: []string{
229 "a/b/",
230 },
231 open: "./a/./b/./target",
232 target: "a/b/target",
233 }, {
234 name: "path with dotdot",
235 fs: []string{
236 "a/b/",
237 },
238 open: "a/../a/b/../../a/b/../b/target",
239 target: "a/b/target",
240 }, {
241 name: "path with dotdot slash",
242 fs: []string{},
243 open: "../",
244 wantError: true,
245 }, {
246 name: "path with dotdot dotdot slash",
247 fs: []string{},
248 open: "a/../../",
249 wantError: true,
250 }, {
251 name: "dotdot no symlink",
252 fs: []string{
253 "a/",
254 },
255 open: "a/../target",
256 target: "target",
257 }, {
258 name: "dotdot after symlink",
259 fs: []string{
260 "a => b/c",
261 "b/c/",
262 },
263 open: "a/../target",
264 target: func() string {
265 if runtime.GOOS == "windows" {
266
267 return "target"
268 }
269 return "b/target"
270 }(),
271 }, {
272 name: "dotdot before symlink",
273 fs: []string{
274 "a => b/c",
275 "b/c/",
276 },
277 open: "b/../a/target",
278 target: "b/c/target",
279 }, {
280 name: "symlink ends in dot",
281 fs: []string{
282 "a => b/.",
283 "b/",
284 },
285 open: "a/target",
286 target: "b/target",
287 }, {
288 name: "directory does not exist",
289 fs: []string{},
290 open: "a/file",
291 wantError: true,
292 alwaysFails: true,
293 }, {
294 name: "empty path",
295 fs: []string{},
296 open: "",
297 wantError: true,
298 alwaysFails: true,
299 }, {
300 name: "symlink cycle",
301 fs: []string{
302 "a => a",
303 },
304 open: "a",
305 ltarget: "a",
306 wantError: true,
307 alwaysFails: true,
308 }, {
309 name: "path escapes",
310 fs: []string{},
311 open: "../ROOT/target",
312 target: "target",
313 wantError: true,
314 }, {
315 name: "long path escapes",
316 fs: []string{
317 "a/",
318 },
319 open: "a/../../ROOT/target",
320 target: "target",
321 wantError: true,
322 }, {
323 name: "absolute symlink",
324 fs: []string{
325 "link => $ABS/target",
326 },
327 open: "link",
328 ltarget: "link",
329 target: "target",
330 wantError: true,
331 }, {
332 name: "relative symlink",
333 fs: []string{
334 "link => ../ROOT/target",
335 },
336 open: "link",
337 target: "target",
338 ltarget: "link",
339 wantError: true,
340 }, {
341 name: "symlink chain escapes",
342 fs: []string{
343 "link => a/b/c/target",
344 "a/b => e",
345 "a/e => ../../ROOT",
346 "c/",
347 },
348 open: "link",
349 target: "c/target",
350 ltarget: "link",
351 wantError: true,
352 }}
353
354 func TestRootOpen_File(t *testing.T) {
355 want := []byte("target")
356 for _, test := range rootTestCases {
357 test.run(t, func(t *testing.T, target string, root *os.Root) {
358 if target != "" {
359 if err := os.WriteFile(target, want, 0o666); err != nil {
360 t.Fatal(err)
361 }
362 }
363 f, err := root.Open(test.open)
364 if errEndsTest(t, err, test.wantError, "root.Open(%q)", test.open) {
365 return
366 }
367 defer f.Close()
368 got, err := io.ReadAll(f)
369 if err != nil || !bytes.Equal(got, want) {
370 t.Errorf(`Dir.Open(%q): read content %q, %v; want %q`, test.open, string(got), err, string(want))
371 }
372 })
373 }
374 }
375
376 func TestRootOpen_Directory(t *testing.T) {
377 for _, test := range rootTestCases {
378 test.run(t, func(t *testing.T, target string, root *os.Root) {
379 if target != "" {
380 if err := os.Mkdir(target, 0o777); err != nil {
381 t.Fatal(err)
382 }
383 if err := os.WriteFile(target+"/found", nil, 0o666); err != nil {
384 t.Fatal(err)
385 }
386 }
387 f, err := root.Open(test.open)
388 if errEndsTest(t, err, test.wantError, "root.Open(%q)", test.open) {
389 return
390 }
391 defer f.Close()
392 got, err := f.Readdirnames(-1)
393 if err != nil {
394 t.Errorf(`Dir.Open(%q).Readdirnames: %v`, test.open, err)
395 }
396 if want := []string{"found"}; !slices.Equal(got, want) {
397 t.Errorf(`Dir.Open(%q).Readdirnames: %q, want %q`, test.open, got, want)
398 }
399 })
400 }
401 }
402
403 func TestRootCreate(t *testing.T) {
404 want := []byte("target")
405 for _, test := range rootTestCases {
406 test.run(t, func(t *testing.T, target string, root *os.Root) {
407 f, err := root.Create(test.open)
408 if errEndsTest(t, err, test.wantError, "root.Create(%q)", test.open) {
409 return
410 }
411 if _, err := f.Write(want); err != nil {
412 t.Fatal(err)
413 }
414 f.Close()
415 got, err := os.ReadFile(target)
416 if err != nil {
417 t.Fatalf(`reading file created with root.Create(%q): %v`, test.open, err)
418 }
419 if !bytes.Equal(got, want) {
420 t.Fatalf(`reading file created with root.Create(%q): got %q; want %q`, test.open, got, want)
421 }
422 })
423 }
424 }
425
426 func TestRootMkdir(t *testing.T) {
427 for _, test := range rootTestCases {
428 test.run(t, func(t *testing.T, target string, root *os.Root) {
429 wantError := test.wantError
430 if !wantError {
431 fi, err := os.Lstat(filepath.Join(root.Name(), test.open))
432 if err == nil && fi.Mode().Type() == fs.ModeSymlink {
433
434
435 wantError = true
436 }
437 }
438
439 err := root.Mkdir(test.open, 0o777)
440 if errEndsTest(t, err, wantError, "root.Create(%q)", test.open) {
441 return
442 }
443 fi, err := os.Lstat(target)
444 if err != nil {
445 t.Fatalf(`stat file created with Root.Mkdir(%q): %v`, test.open, err)
446 }
447 if !fi.IsDir() {
448 t.Fatalf(`stat file created with Root.Mkdir(%q): not a directory`, test.open)
449 }
450 })
451 }
452 }
453
454 func TestRootOpenRoot(t *testing.T) {
455 for _, test := range rootTestCases {
456 test.run(t, func(t *testing.T, target string, root *os.Root) {
457 if target != "" {
458 if err := os.Mkdir(target, 0o777); err != nil {
459 t.Fatal(err)
460 }
461 if err := os.WriteFile(target+"/f", nil, 0o666); err != nil {
462 t.Fatal(err)
463 }
464 }
465 rr, err := root.OpenRoot(test.open)
466 if errEndsTest(t, err, test.wantError, "root.OpenRoot(%q)", test.open) {
467 return
468 }
469 defer rr.Close()
470 f, err := rr.Open("f")
471 if err != nil {
472 t.Fatalf(`root.OpenRoot(%q).Open("f") = %v`, test.open, err)
473 }
474 f.Close()
475 })
476 }
477 }
478
479 func TestRootRemoveFile(t *testing.T) {
480 for _, test := range rootTestCases {
481 test.run(t, func(t *testing.T, target string, root *os.Root) {
482 wantError := test.wantError
483 if test.ltarget != "" {
484
485
486 wantError = false
487 target = filepath.Join(root.Name(), test.ltarget)
488 } else if target != "" {
489 if err := os.WriteFile(target, nil, 0o666); err != nil {
490 t.Fatal(err)
491 }
492 }
493
494 err := root.Remove(test.open)
495 if errEndsTest(t, err, wantError, "root.Remove(%q)", test.open) {
496 return
497 }
498 _, err = os.Lstat(target)
499 if !errors.Is(err, os.ErrNotExist) {
500 t.Fatalf(`stat file removed with Root.Remove(%q): %v, want ErrNotExist`, test.open, err)
501 }
502 })
503 }
504 }
505
506 func TestRootRemoveDirectory(t *testing.T) {
507 for _, test := range rootTestCases {
508 test.run(t, func(t *testing.T, target string, root *os.Root) {
509 wantError := test.wantError
510 if test.ltarget != "" {
511
512
513 wantError = false
514 target = filepath.Join(root.Name(), test.ltarget)
515 } else if target != "" {
516 if err := os.Mkdir(target, 0o777); err != nil {
517 t.Fatal(err)
518 }
519 }
520
521 err := root.Remove(test.open)
522 if errEndsTest(t, err, wantError, "root.Remove(%q)", test.open) {
523 return
524 }
525 _, err = os.Lstat(target)
526 if !errors.Is(err, os.ErrNotExist) {
527 t.Fatalf(`stat file removed with Root.Remove(%q): %v, want ErrNotExist`, test.open, err)
528 }
529 })
530 }
531 }
532
533 func TestRootOpenFileAsRoot(t *testing.T) {
534 dir := t.TempDir()
535 target := filepath.Join(dir, "target")
536 if err := os.WriteFile(target, nil, 0o666); err != nil {
537 t.Fatal(err)
538 }
539 _, err := os.OpenRoot(target)
540 if err == nil {
541 t.Fatal("os.OpenRoot(file) succeeded; want failure")
542 }
543 r, err := os.OpenRoot(dir)
544 if err != nil {
545 t.Fatal(err)
546 }
547 defer r.Close()
548 _, err = r.OpenRoot("target")
549 if err == nil {
550 t.Fatal("Root.OpenRoot(file) succeeded; want failure")
551 }
552 }
553
554 func TestRootStat(t *testing.T) {
555 for _, test := range rootTestCases {
556 test.run(t, func(t *testing.T, target string, root *os.Root) {
557 const content = "content"
558 if target != "" {
559 if err := os.WriteFile(target, []byte(content), 0o666); err != nil {
560 t.Fatal(err)
561 }
562 }
563
564 fi, err := root.Stat(test.open)
565 if errEndsTest(t, err, test.wantError, "root.Stat(%q)", test.open) {
566 return
567 }
568 if got, want := fi.Name(), filepath.Base(test.open); got != want {
569 t.Errorf("root.Stat(%q).Name() = %q, want %q", test.open, got, want)
570 }
571 if got, want := fi.Size(), int64(len(content)); got != want {
572 t.Errorf("root.Stat(%q).Size() = %v, want %v", test.open, got, want)
573 }
574 })
575 }
576 }
577
578 func TestRootLstat(t *testing.T) {
579 for _, test := range rootTestCases {
580 test.run(t, func(t *testing.T, target string, root *os.Root) {
581 const content = "content"
582 wantError := test.wantError
583 if test.ltarget != "" {
584
585 wantError = false
586 } else if target != "" {
587 if err := os.WriteFile(target, []byte(content), 0o666); err != nil {
588 t.Fatal(err)
589 }
590 }
591
592 fi, err := root.Lstat(test.open)
593 if errEndsTest(t, err, wantError, "root.Stat(%q)", test.open) {
594 return
595 }
596 if got, want := fi.Name(), filepath.Base(test.open); got != want {
597 t.Errorf("root.Stat(%q).Name() = %q, want %q", test.open, got, want)
598 }
599 if test.ltarget == "" {
600 if got := fi.Mode(); got&os.ModeSymlink != 0 {
601 t.Errorf("root.Stat(%q).Mode() = %v, want non-symlink", test.open, got)
602 }
603 if got, want := fi.Size(), int64(len(content)); got != want {
604 t.Errorf("root.Stat(%q).Size() = %v, want %v", test.open, got, want)
605 }
606 } else {
607 if got := fi.Mode(); got&os.ModeSymlink == 0 {
608 t.Errorf("root.Stat(%q).Mode() = %v, want symlink", test.open, got)
609 }
610 }
611 })
612 }
613 }
614
615
616
617
618
619
620 type rootConsistencyTest struct {
621 name string
622
623
624
625 fs []string
626 fsFunc func(t *testing.T, dir string) string
627
628
629 open string
630
631
632
633 detailedErrorMismatch func(t *testing.T) bool
634 }
635
636 var rootConsistencyTestCases = []rootConsistencyTest{{
637 name: "file",
638 fs: []string{
639 "target",
640 },
641 open: "target",
642 }, {
643 name: "dir slash dot",
644 fs: []string{
645 "target/file",
646 },
647 open: "target/.",
648 }, {
649 name: "dot",
650 fs: []string{
651 "file",
652 },
653 open: ".",
654 }, {
655 name: "file slash dot",
656 fs: []string{
657 "target",
658 },
659 open: "target/.",
660 detailedErrorMismatch: func(t *testing.T) bool {
661
662 return runtime.GOOS == "freebsd" && strings.HasPrefix(t.Name(), "TestRootConsistencyRemove")
663 },
664 }, {
665 name: "dir slash",
666 fs: []string{
667 "target/file",
668 },
669 open: "target/",
670 }, {
671 name: "dot slash",
672 fs: []string{
673 "file",
674 },
675 open: "./",
676 }, {
677 name: "file slash",
678 fs: []string{
679 "target",
680 },
681 open: "target/",
682 detailedErrorMismatch: func(t *testing.T) bool {
683
684 return runtime.GOOS == "js"
685 },
686 }, {
687 name: "file in path",
688 fs: []string{
689 "file",
690 },
691 open: "file/target",
692 }, {
693 name: "directory in path missing",
694 open: "dir/target",
695 }, {
696 name: "target does not exist",
697 open: "target",
698 }, {
699 name: "symlink slash",
700 fs: []string{
701 "target/file",
702 "link => target",
703 },
704 open: "link/",
705 }, {
706 name: "symlink slash dot",
707 fs: []string{
708 "target/file",
709 "link => target",
710 },
711 open: "link/.",
712 }, {
713 name: "file symlink slash",
714 fs: []string{
715 "target",
716 "link => target",
717 },
718 open: "link/",
719 detailedErrorMismatch: func(t *testing.T) bool {
720
721 return runtime.GOOS == "js"
722 },
723 }, {
724 name: "unresolved symlink",
725 fs: []string{
726 "link => target",
727 },
728 open: "link",
729 }, {
730 name: "resolved symlink",
731 fs: []string{
732 "link => target",
733 "target",
734 },
735 open: "link",
736 }, {
737 name: "dotdot in path after symlink",
738 fs: []string{
739 "a => b/c",
740 "b/c/",
741 "b/target",
742 },
743 open: "a/../target",
744 }, {
745 name: "long file name",
746 open: strings.Repeat("a", 500),
747 }, {
748 name: "unreadable directory",
749 fs: []string{
750 "dir/target",
751 },
752 fsFunc: func(t *testing.T, dir string) string {
753 os.Chmod(filepath.Join(dir, "dir"), 0)
754 t.Cleanup(func() {
755 os.Chmod(filepath.Join(dir, "dir"), 0o700)
756 })
757 return dir
758 },
759 open: "dir/target",
760 }, {
761 name: "unix domain socket target",
762 fsFunc: func(t *testing.T, dir string) string {
763 return tempDirWithUnixSocket(t, "a")
764 },
765 open: "a",
766 }, {
767 name: "unix domain socket in path",
768 fsFunc: func(t *testing.T, dir string) string {
769 return tempDirWithUnixSocket(t, "a")
770 },
771 open: "a/b",
772 detailedErrorMismatch: func(t *testing.T) bool {
773
774
775 return runtime.GOOS == "windows"
776 },
777 }, {
778 name: "question mark",
779 open: "?",
780 }, {
781 name: "nul byte",
782 open: "\x00",
783 }}
784
785 func tempDirWithUnixSocket(t *testing.T, name string) string {
786 dir, err := os.MkdirTemp("", "")
787 if err != nil {
788 t.Fatal(err)
789 }
790 t.Cleanup(func() {
791 if err := os.RemoveAll(dir); err != nil {
792 t.Error(err)
793 }
794 })
795 addr, err := net.ResolveUnixAddr("unix", filepath.Join(dir, name))
796 if err != nil {
797 t.Skipf("net.ResolveUnixAddr: %v", err)
798 }
799 conn, err := net.ListenUnix("unix", addr)
800 if err != nil {
801 t.Skipf("net.ListenUnix: %v", err)
802 }
803 t.Cleanup(func() {
804 conn.Close()
805 })
806 return dir
807 }
808
809 func (test rootConsistencyTest) run(t *testing.T, f func(t *testing.T, path string, r *os.Root) (string, error)) {
810 if runtime.GOOS == "wasip1" {
811
812
813
814 t.Skip("#69509: inconsistent results on wasip1")
815 }
816
817 t.Run(test.name, func(t *testing.T) {
818 dir1 := makefs(t, test.fs)
819 dir2 := makefs(t, test.fs)
820 if test.fsFunc != nil {
821 dir1 = test.fsFunc(t, dir1)
822 dir2 = test.fsFunc(t, dir2)
823 }
824
825 r, err := os.OpenRoot(dir1)
826 if err != nil {
827 t.Fatal(err)
828 }
829 defer r.Close()
830
831 res1, err1 := f(t, test.open, r)
832 res2, err2 := f(t, dir2+"/"+test.open, nil)
833
834 if res1 != res2 || ((err1 == nil) != (err2 == nil)) {
835 t.Errorf("with root: res=%v", res1)
836 t.Errorf(" err=%v", err1)
837 t.Errorf("without root: res=%v", res2)
838 t.Errorf(" err=%v", err2)
839 t.Errorf("want consistent results, got mismatch")
840 }
841
842 if err1 != nil || err2 != nil {
843 e1, ok := err1.(*os.PathError)
844 if !ok {
845 t.Fatalf("with root, expected PathError; got: %v", err1)
846 }
847 e2, ok := err2.(*os.PathError)
848 if !ok {
849 t.Fatalf("without root, expected PathError; got: %v", err1)
850 }
851 detailedErrorMismatch := false
852 if f := test.detailedErrorMismatch; f != nil {
853 detailedErrorMismatch = f(t)
854 }
855 if runtime.GOOS == "plan9" {
856
857 detailedErrorMismatch = true
858 }
859 if !detailedErrorMismatch && e1.Err != e2.Err {
860 t.Errorf("with root: err=%v", e1.Err)
861 t.Errorf("without root: err=%v", e2.Err)
862 t.Errorf("want consistent results, got mismatch")
863 }
864 }
865 })
866 }
867
868 func TestRootConsistencyOpen(t *testing.T) {
869 for _, test := range rootConsistencyTestCases {
870 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
871 var f *os.File
872 var err error
873 if r == nil {
874 f, err = os.Open(path)
875 } else {
876 f, err = r.Open(path)
877 }
878 if err != nil {
879 return "", err
880 }
881 defer f.Close()
882 fi, err := f.Stat()
883 if err == nil && !fi.IsDir() {
884 b, err := io.ReadAll(f)
885 return string(b), err
886 } else {
887 names, err := f.Readdirnames(-1)
888 slices.Sort(names)
889 return fmt.Sprintf("%q", names), err
890 }
891 })
892 }
893 }
894
895 func TestRootConsistencyCreate(t *testing.T) {
896 for _, test := range rootConsistencyTestCases {
897 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
898 var f *os.File
899 var err error
900 if r == nil {
901 f, err = os.Create(path)
902 } else {
903 f, err = r.Create(path)
904 }
905 if err == nil {
906 f.Write([]byte("file contents"))
907 f.Close()
908 }
909 return "", err
910 })
911 }
912 }
913
914 func TestRootConsistencyMkdir(t *testing.T) {
915 for _, test := range rootConsistencyTestCases {
916 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
917 var err error
918 if r == nil {
919 err = os.Mkdir(path, 0o777)
920 } else {
921 err = r.Mkdir(path, 0o777)
922 }
923 return "", err
924 })
925 }
926 }
927
928 func TestRootConsistencyRemove(t *testing.T) {
929 for _, test := range rootConsistencyTestCases {
930 if test.open == "." || test.open == "./" {
931 continue
932 }
933 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
934 var err error
935 if r == nil {
936 err = os.Remove(path)
937 } else {
938 err = r.Remove(path)
939 }
940 return "", err
941 })
942 }
943 }
944
945 func TestRootConsistencyStat(t *testing.T) {
946 for _, test := range rootConsistencyTestCases {
947 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
948 var fi os.FileInfo
949 var err error
950 if r == nil {
951 fi, err = os.Stat(path)
952 } else {
953 fi, err = r.Stat(path)
954 }
955 if err != nil {
956 return "", err
957 }
958 return fmt.Sprintf("name:%q size:%v mode:%v isdir:%v", fi.Name(), fi.Size(), fi.Mode(), fi.IsDir()), nil
959 })
960 }
961 }
962
963 func TestRootConsistencyLstat(t *testing.T) {
964 for _, test := range rootConsistencyTestCases {
965 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
966 var fi os.FileInfo
967 var err error
968 if r == nil {
969 fi, err = os.Lstat(path)
970 } else {
971 fi, err = r.Lstat(path)
972 }
973 if err != nil {
974 return "", err
975 }
976 return fmt.Sprintf("name:%q size:%v mode:%v isdir:%v", fi.Name(), fi.Size(), fi.Mode(), fi.IsDir()), nil
977 })
978 }
979 }
980
981 func TestRootRenameAfterOpen(t *testing.T) {
982 switch runtime.GOOS {
983 case "windows":
984 t.Skip("renaming open files not supported on " + runtime.GOOS)
985 case "js", "plan9":
986 t.Skip("openat not supported on " + runtime.GOOS)
987 case "wasip1":
988 if os.Getenv("GOWASIRUNTIME") == "wazero" {
989 t.Skip("wazero does not track renamed directories")
990 }
991 }
992
993 dir := t.TempDir()
994
995
996 if err := os.Mkdir(filepath.Join(dir, "a"), 0o777); err != nil {
997 t.Fatal(err)
998 }
999 dirf, err := os.OpenRoot(filepath.Join(dir, "a"))
1000 if err != nil {
1001 t.Fatal(err)
1002 }
1003 defer dirf.Close()
1004
1005
1006 if err := os.Rename(filepath.Join(dir, "a"), filepath.Join(dir, "b")); err != nil {
1007 t.Fatal(err)
1008 }
1009 if err := os.WriteFile(filepath.Join(dir, "b/f"), []byte("hello"), 0o666); err != nil {
1010 t.Fatal(err)
1011 }
1012
1013
1014 f, err := dirf.OpenFile("f", os.O_RDONLY, 0)
1015 if err != nil {
1016 t.Fatalf("reading file after renaming parent: %v", err)
1017 }
1018 defer f.Close()
1019 b, err := io.ReadAll(f)
1020 if err != nil {
1021 t.Fatal(err)
1022 }
1023 if got, want := string(b), "hello"; got != want {
1024 t.Fatalf("file contents: %q, want %q", got, want)
1025 }
1026
1027
1028 if got, want := f.Name(), dirf.Name()+string(os.PathSeparator)+"f"; got != want {
1029 t.Errorf("f.Name() = %q, want %q", got, want)
1030 }
1031 }
1032
1033 func TestRootNonPermissionMode(t *testing.T) {
1034 r, err := os.OpenRoot(t.TempDir())
1035 if err != nil {
1036 t.Fatal(err)
1037 }
1038 defer r.Close()
1039 if _, err := r.OpenFile("file", os.O_RDWR|os.O_CREATE, 0o1777); err == nil {
1040 t.Errorf("r.OpenFile(file, O_RDWR|O_CREATE, 0o1777) succeeded; want error")
1041 }
1042 if err := r.Mkdir("file", 0o1777); err == nil {
1043 t.Errorf("r.Mkdir(file, 0o1777) succeeded; want error")
1044 }
1045 }
1046
1047 func TestRootUseAfterClose(t *testing.T) {
1048 r, err := os.OpenRoot(t.TempDir())
1049 if err != nil {
1050 t.Fatal(err)
1051 }
1052 r.Close()
1053 for _, test := range []struct {
1054 name string
1055 f func(r *os.Root, filename string) error
1056 }{{
1057 name: "Open",
1058 f: func(r *os.Root, filename string) error {
1059 _, err := r.Open(filename)
1060 return err
1061 },
1062 }, {
1063 name: "Create",
1064 f: func(r *os.Root, filename string) error {
1065 _, err := r.Create(filename)
1066 return err
1067 },
1068 }, {
1069 name: "OpenFile",
1070 f: func(r *os.Root, filename string) error {
1071 _, err := r.OpenFile(filename, os.O_RDWR, 0o666)
1072 return err
1073 },
1074 }, {
1075 name: "OpenRoot",
1076 f: func(r *os.Root, filename string) error {
1077 _, err := r.OpenRoot(filename)
1078 return err
1079 },
1080 }, {
1081 name: "Mkdir",
1082 f: func(r *os.Root, filename string) error {
1083 return r.Mkdir(filename, 0o777)
1084 },
1085 }} {
1086 err := test.f(r, "target")
1087 pe, ok := err.(*os.PathError)
1088 if !ok || pe.Path != "target" || pe.Err != os.ErrClosed {
1089 t.Errorf(`r.%v = %v; want &PathError{Path: "target", Err: ErrClosed}`, test.name, err)
1090 }
1091 }
1092 }
1093
1094 func TestRootConcurrentClose(t *testing.T) {
1095 r, err := os.OpenRoot(t.TempDir())
1096 if err != nil {
1097 t.Fatal(err)
1098 }
1099 ch := make(chan error, 1)
1100 go func() {
1101 defer close(ch)
1102 first := true
1103 for {
1104 f, err := r.OpenFile("file", os.O_RDWR|os.O_CREATE, 0o666)
1105 if err != nil {
1106 ch <- err
1107 return
1108 }
1109 if first {
1110 ch <- nil
1111 first = false
1112 }
1113 f.Close()
1114 if runtime.GOARCH == "wasm" {
1115
1116 runtime.Gosched()
1117 }
1118 }
1119 }()
1120 if err := <-ch; err != nil {
1121 t.Errorf("OpenFile: %v, want success", err)
1122 }
1123 r.Close()
1124 if err := <-ch; !errors.Is(err, os.ErrClosed) {
1125 t.Errorf("OpenFile: %v, want ErrClosed", err)
1126 }
1127 }
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141 func TestRootRaceRenameDir(t *testing.T) {
1142 dir := t.TempDir()
1143 r, err := os.OpenRoot(dir)
1144 if err != nil {
1145 t.Fatal(err)
1146 }
1147 defer r.Close()
1148
1149 const depth = 4
1150
1151 os.MkdirAll(dir+"/base/"+strings.Repeat("/a", depth), 0o777)
1152
1153 path := "base/" + strings.Repeat("a/", depth) + strings.Repeat("../", depth) + "a/f"
1154 os.WriteFile(dir+"/f", []byte("secret"), 0o666)
1155 os.WriteFile(dir+"/base/a/f", []byte("public"), 0o666)
1156
1157
1158 const tries = 10
1159 var total time.Duration
1160 for range tries {
1161 start := time.Now()
1162 f, err := r.Open(path)
1163 if err != nil {
1164 t.Fatal(err)
1165 }
1166 b, err := io.ReadAll(f)
1167 if err != nil {
1168 t.Fatal(err)
1169 }
1170 if string(b) != "public" {
1171 t.Fatalf("read %q, want %q", b, "public")
1172 }
1173 f.Close()
1174 total += time.Since(start)
1175 }
1176 avg := total / tries
1177
1178
1179 for range 100 {
1180
1181 gotc := make(chan []byte)
1182 go func() {
1183 f, err := r.Open(path)
1184 if err != nil {
1185 gotc <- nil
1186 }
1187 defer f.Close()
1188 b, _ := io.ReadAll(f)
1189 gotc <- b
1190 }()
1191
1192
1193
1194 time.Sleep(avg / 4)
1195 if err := os.Rename(dir+"/base/a", dir+"/b"); err != nil {
1196
1197
1198 switch runtime.GOOS {
1199 case "windows", "plan9":
1200 default:
1201 t.Fatal(err)
1202 }
1203 }
1204
1205 got := <-gotc
1206 os.Rename(dir+"/b", dir+"/base/a")
1207 if len(got) > 0 && string(got) != "public" {
1208 t.Errorf("read file: %q; want error or 'public'", got)
1209 }
1210 }
1211 }
1212
1213 func TestRootSymlinkToRoot(t *testing.T) {
1214 dir := makefs(t, []string{
1215 "d/d => ..",
1216 })
1217 root, err := os.OpenRoot(dir)
1218 if err != nil {
1219 t.Fatal(err)
1220 }
1221 defer root.Close()
1222 if err := root.Mkdir("d/d/new", 0777); err != nil {
1223 t.Fatal(err)
1224 }
1225 f, err := root.Open("d/d")
1226 if err != nil {
1227 t.Fatal(err)
1228 }
1229 defer f.Close()
1230 names, err := f.Readdirnames(-1)
1231 if err != nil {
1232 t.Fatal(err)
1233 }
1234 slices.Sort(names)
1235 if got, want := names, []string{"d", "new"}; !slices.Equal(got, want) {
1236 t.Errorf("root contains: %q, want %q", got, want)
1237 }
1238 }
1239
1240 func TestOpenInRoot(t *testing.T) {
1241 dir := makefs(t, []string{
1242 "file",
1243 "link => ../ROOT/file",
1244 })
1245 f, err := os.OpenInRoot(dir, "file")
1246 if err != nil {
1247 t.Fatalf("OpenInRoot(`file`) = %v, want success", err)
1248 }
1249 f.Close()
1250 for _, name := range []string{
1251 "link",
1252 "../ROOT/file",
1253 dir + "/file",
1254 } {
1255 f, err := os.OpenInRoot(dir, name)
1256 if err == nil {
1257 f.Close()
1258 t.Fatalf("OpenInRoot(%q) = nil, want error", name)
1259 }
1260 }
1261 }
1262
View as plain text