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 if mode := fi.Mode(); mode&0o777 == 0 {
451
452
453
454 t.Fatalf(`stat file created with Root.Mkdir(%q): mode=%v, want non-zero`, test.open, mode)
455 }
456 })
457 }
458 }
459
460 func TestRootOpenRoot(t *testing.T) {
461 for _, test := range rootTestCases {
462 test.run(t, func(t *testing.T, target string, root *os.Root) {
463 if target != "" {
464 if err := os.Mkdir(target, 0o777); err != nil {
465 t.Fatal(err)
466 }
467 if err := os.WriteFile(target+"/f", nil, 0o666); err != nil {
468 t.Fatal(err)
469 }
470 }
471 rr, err := root.OpenRoot(test.open)
472 if errEndsTest(t, err, test.wantError, "root.OpenRoot(%q)", test.open) {
473 return
474 }
475 defer rr.Close()
476 f, err := rr.Open("f")
477 if err != nil {
478 t.Fatalf(`root.OpenRoot(%q).Open("f") = %v`, test.open, err)
479 }
480 f.Close()
481 })
482 }
483 }
484
485 func TestRootRemoveFile(t *testing.T) {
486 for _, test := range rootTestCases {
487 test.run(t, func(t *testing.T, target string, root *os.Root) {
488 wantError := test.wantError
489 if test.ltarget != "" {
490
491
492 wantError = false
493 target = filepath.Join(root.Name(), test.ltarget)
494 } else if target != "" {
495 if err := os.WriteFile(target, nil, 0o666); err != nil {
496 t.Fatal(err)
497 }
498 }
499
500 err := root.Remove(test.open)
501 if errEndsTest(t, err, wantError, "root.Remove(%q)", test.open) {
502 return
503 }
504 _, err = os.Lstat(target)
505 if !errors.Is(err, os.ErrNotExist) {
506 t.Fatalf(`stat file removed with Root.Remove(%q): %v, want ErrNotExist`, test.open, err)
507 }
508 })
509 }
510 }
511
512 func TestRootRemoveDirectory(t *testing.T) {
513 for _, test := range rootTestCases {
514 test.run(t, func(t *testing.T, target string, root *os.Root) {
515 wantError := test.wantError
516 if test.ltarget != "" {
517
518
519 wantError = false
520 target = filepath.Join(root.Name(), test.ltarget)
521 } else if target != "" {
522 if err := os.Mkdir(target, 0o777); err != nil {
523 t.Fatal(err)
524 }
525 }
526
527 err := root.Remove(test.open)
528 if errEndsTest(t, err, wantError, "root.Remove(%q)", test.open) {
529 return
530 }
531 _, err = os.Lstat(target)
532 if !errors.Is(err, os.ErrNotExist) {
533 t.Fatalf(`stat file removed with Root.Remove(%q): %v, want ErrNotExist`, test.open, err)
534 }
535 })
536 }
537 }
538
539 func TestRootOpenFileAsRoot(t *testing.T) {
540 dir := t.TempDir()
541 target := filepath.Join(dir, "target")
542 if err := os.WriteFile(target, nil, 0o666); err != nil {
543 t.Fatal(err)
544 }
545 _, err := os.OpenRoot(target)
546 if err == nil {
547 t.Fatal("os.OpenRoot(file) succeeded; want failure")
548 }
549 r, err := os.OpenRoot(dir)
550 if err != nil {
551 t.Fatal(err)
552 }
553 defer r.Close()
554 _, err = r.OpenRoot("target")
555 if err == nil {
556 t.Fatal("Root.OpenRoot(file) succeeded; want failure")
557 }
558 }
559
560 func TestRootStat(t *testing.T) {
561 for _, test := range rootTestCases {
562 test.run(t, func(t *testing.T, target string, root *os.Root) {
563 const content = "content"
564 if target != "" {
565 if err := os.WriteFile(target, []byte(content), 0o666); err != nil {
566 t.Fatal(err)
567 }
568 }
569
570 fi, err := root.Stat(test.open)
571 if errEndsTest(t, err, test.wantError, "root.Stat(%q)", test.open) {
572 return
573 }
574 if got, want := fi.Name(), filepath.Base(test.open); got != want {
575 t.Errorf("root.Stat(%q).Name() = %q, want %q", test.open, got, want)
576 }
577 if got, want := fi.Size(), int64(len(content)); got != want {
578 t.Errorf("root.Stat(%q).Size() = %v, want %v", test.open, got, want)
579 }
580 })
581 }
582 }
583
584 func TestRootLstat(t *testing.T) {
585 for _, test := range rootTestCases {
586 test.run(t, func(t *testing.T, target string, root *os.Root) {
587 const content = "content"
588 wantError := test.wantError
589 if test.ltarget != "" {
590
591 wantError = false
592 } else if target != "" {
593 if err := os.WriteFile(target, []byte(content), 0o666); err != nil {
594 t.Fatal(err)
595 }
596 }
597
598 fi, err := root.Lstat(test.open)
599 if errEndsTest(t, err, wantError, "root.Stat(%q)", test.open) {
600 return
601 }
602 if got, want := fi.Name(), filepath.Base(test.open); got != want {
603 t.Errorf("root.Stat(%q).Name() = %q, want %q", test.open, got, want)
604 }
605 if test.ltarget == "" {
606 if got := fi.Mode(); got&os.ModeSymlink != 0 {
607 t.Errorf("root.Stat(%q).Mode() = %v, want non-symlink", test.open, got)
608 }
609 if got, want := fi.Size(), int64(len(content)); got != want {
610 t.Errorf("root.Stat(%q).Size() = %v, want %v", test.open, got, want)
611 }
612 } else {
613 if got := fi.Mode(); got&os.ModeSymlink == 0 {
614 t.Errorf("root.Stat(%q).Mode() = %v, want symlink", test.open, got)
615 }
616 }
617 })
618 }
619 }
620
621
622
623
624
625
626 type rootConsistencyTest struct {
627 name string
628
629
630
631 fs []string
632 fsFunc func(t *testing.T, dir string) string
633
634
635 open string
636
637
638
639 detailedErrorMismatch func(t *testing.T) bool
640 }
641
642 var rootConsistencyTestCases = []rootConsistencyTest{{
643 name: "file",
644 fs: []string{
645 "target",
646 },
647 open: "target",
648 }, {
649 name: "dir slash dot",
650 fs: []string{
651 "target/file",
652 },
653 open: "target/.",
654 }, {
655 name: "dot",
656 fs: []string{
657 "file",
658 },
659 open: ".",
660 }, {
661 name: "file slash dot",
662 fs: []string{
663 "target",
664 },
665 open: "target/.",
666 detailedErrorMismatch: func(t *testing.T) bool {
667
668 return runtime.GOOS == "freebsd" && strings.HasPrefix(t.Name(), "TestRootConsistencyRemove")
669 },
670 }, {
671 name: "dir slash",
672 fs: []string{
673 "target/file",
674 },
675 open: "target/",
676 }, {
677 name: "dot slash",
678 fs: []string{
679 "file",
680 },
681 open: "./",
682 }, {
683 name: "file slash",
684 fs: []string{
685 "target",
686 },
687 open: "target/",
688 detailedErrorMismatch: func(t *testing.T) bool {
689
690 return runtime.GOOS == "js"
691 },
692 }, {
693 name: "file in path",
694 fs: []string{
695 "file",
696 },
697 open: "file/target",
698 }, {
699 name: "directory in path missing",
700 open: "dir/target",
701 }, {
702 name: "target does not exist",
703 open: "target",
704 }, {
705 name: "symlink slash",
706 fs: []string{
707 "target/file",
708 "link => target",
709 },
710 open: "link/",
711 }, {
712 name: "symlink slash dot",
713 fs: []string{
714 "target/file",
715 "link => target",
716 },
717 open: "link/.",
718 }, {
719 name: "file symlink slash",
720 fs: []string{
721 "target",
722 "link => target",
723 },
724 open: "link/",
725 detailedErrorMismatch: func(t *testing.T) bool {
726
727 return runtime.GOOS == "js"
728 },
729 }, {
730 name: "unresolved symlink",
731 fs: []string{
732 "link => target",
733 },
734 open: "link",
735 }, {
736 name: "resolved symlink",
737 fs: []string{
738 "link => target",
739 "target",
740 },
741 open: "link",
742 }, {
743 name: "dotdot in path after symlink",
744 fs: []string{
745 "a => b/c",
746 "b/c/",
747 "b/target",
748 },
749 open: "a/../target",
750 }, {
751 name: "long file name",
752 open: strings.Repeat("a", 500),
753 }, {
754 name: "unreadable directory",
755 fs: []string{
756 "dir/target",
757 },
758 fsFunc: func(t *testing.T, dir string) string {
759 os.Chmod(filepath.Join(dir, "dir"), 0)
760 t.Cleanup(func() {
761 os.Chmod(filepath.Join(dir, "dir"), 0o700)
762 })
763 return dir
764 },
765 open: "dir/target",
766 }, {
767 name: "unix domain socket target",
768 fsFunc: func(t *testing.T, dir string) string {
769 return tempDirWithUnixSocket(t, "a")
770 },
771 open: "a",
772 }, {
773 name: "unix domain socket in path",
774 fsFunc: func(t *testing.T, dir string) string {
775 return tempDirWithUnixSocket(t, "a")
776 },
777 open: "a/b",
778 detailedErrorMismatch: func(t *testing.T) bool {
779
780
781 return runtime.GOOS == "windows"
782 },
783 }, {
784 name: "question mark",
785 open: "?",
786 }, {
787 name: "nul byte",
788 open: "\x00",
789 }}
790
791 func tempDirWithUnixSocket(t *testing.T, name string) string {
792 dir, err := os.MkdirTemp("", "")
793 if err != nil {
794 t.Fatal(err)
795 }
796 t.Cleanup(func() {
797 if err := os.RemoveAll(dir); err != nil {
798 t.Error(err)
799 }
800 })
801 addr, err := net.ResolveUnixAddr("unix", filepath.Join(dir, name))
802 if err != nil {
803 t.Skipf("net.ResolveUnixAddr: %v", err)
804 }
805 conn, err := net.ListenUnix("unix", addr)
806 if err != nil {
807 t.Skipf("net.ListenUnix: %v", err)
808 }
809 t.Cleanup(func() {
810 conn.Close()
811 })
812 return dir
813 }
814
815 func (test rootConsistencyTest) run(t *testing.T, f func(t *testing.T, path string, r *os.Root) (string, error)) {
816 if runtime.GOOS == "wasip1" {
817
818
819
820 t.Skip("#69509: inconsistent results on wasip1")
821 }
822
823 t.Run(test.name, func(t *testing.T) {
824 dir1 := makefs(t, test.fs)
825 dir2 := makefs(t, test.fs)
826 if test.fsFunc != nil {
827 dir1 = test.fsFunc(t, dir1)
828 dir2 = test.fsFunc(t, dir2)
829 }
830
831 r, err := os.OpenRoot(dir1)
832 if err != nil {
833 t.Fatal(err)
834 }
835 defer r.Close()
836
837 res1, err1 := f(t, test.open, r)
838 res2, err2 := f(t, dir2+"/"+test.open, nil)
839
840 if res1 != res2 || ((err1 == nil) != (err2 == nil)) {
841 t.Errorf("with root: res=%v", res1)
842 t.Errorf(" err=%v", err1)
843 t.Errorf("without root: res=%v", res2)
844 t.Errorf(" err=%v", err2)
845 t.Errorf("want consistent results, got mismatch")
846 }
847
848 if err1 != nil || err2 != nil {
849 e1, ok := err1.(*os.PathError)
850 if !ok {
851 t.Fatalf("with root, expected PathError; got: %v", err1)
852 }
853 e2, ok := err2.(*os.PathError)
854 if !ok {
855 t.Fatalf("without root, expected PathError; got: %v", err1)
856 }
857 detailedErrorMismatch := false
858 if f := test.detailedErrorMismatch; f != nil {
859 detailedErrorMismatch = f(t)
860 }
861 if runtime.GOOS == "plan9" {
862
863 detailedErrorMismatch = true
864 }
865 if !detailedErrorMismatch && e1.Err != e2.Err {
866 t.Errorf("with root: err=%v", e1.Err)
867 t.Errorf("without root: err=%v", e2.Err)
868 t.Errorf("want consistent results, got mismatch")
869 }
870 }
871 })
872 }
873
874 func TestRootConsistencyOpen(t *testing.T) {
875 for _, test := range rootConsistencyTestCases {
876 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
877 var f *os.File
878 var err error
879 if r == nil {
880 f, err = os.Open(path)
881 } else {
882 f, err = r.Open(path)
883 }
884 if err != nil {
885 return "", err
886 }
887 defer f.Close()
888 fi, err := f.Stat()
889 if err == nil && !fi.IsDir() {
890 b, err := io.ReadAll(f)
891 return string(b), err
892 } else {
893 names, err := f.Readdirnames(-1)
894 slices.Sort(names)
895 return fmt.Sprintf("%q", names), err
896 }
897 })
898 }
899 }
900
901 func TestRootConsistencyCreate(t *testing.T) {
902 for _, test := range rootConsistencyTestCases {
903 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
904 var f *os.File
905 var err error
906 if r == nil {
907 f, err = os.Create(path)
908 } else {
909 f, err = r.Create(path)
910 }
911 if err == nil {
912 f.Write([]byte("file contents"))
913 f.Close()
914 }
915 return "", err
916 })
917 }
918 }
919
920 func TestRootConsistencyMkdir(t *testing.T) {
921 for _, test := range rootConsistencyTestCases {
922 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
923 var err error
924 if r == nil {
925 err = os.Mkdir(path, 0o777)
926 } else {
927 err = r.Mkdir(path, 0o777)
928 }
929 return "", err
930 })
931 }
932 }
933
934 func TestRootConsistencyRemove(t *testing.T) {
935 for _, test := range rootConsistencyTestCases {
936 if test.open == "." || test.open == "./" {
937 continue
938 }
939 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
940 var err error
941 if r == nil {
942 err = os.Remove(path)
943 } else {
944 err = r.Remove(path)
945 }
946 return "", err
947 })
948 }
949 }
950
951 func TestRootConsistencyStat(t *testing.T) {
952 for _, test := range rootConsistencyTestCases {
953 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
954 var fi os.FileInfo
955 var err error
956 if r == nil {
957 fi, err = os.Stat(path)
958 } else {
959 fi, err = r.Stat(path)
960 }
961 if err != nil {
962 return "", err
963 }
964 return fmt.Sprintf("name:%q size:%v mode:%v isdir:%v", fi.Name(), fi.Size(), fi.Mode(), fi.IsDir()), nil
965 })
966 }
967 }
968
969 func TestRootConsistencyLstat(t *testing.T) {
970 for _, test := range rootConsistencyTestCases {
971 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
972 var fi os.FileInfo
973 var err error
974 if r == nil {
975 fi, err = os.Lstat(path)
976 } else {
977 fi, err = r.Lstat(path)
978 }
979 if err != nil {
980 return "", err
981 }
982 return fmt.Sprintf("name:%q size:%v mode:%v isdir:%v", fi.Name(), fi.Size(), fi.Mode(), fi.IsDir()), nil
983 })
984 }
985 }
986
987 func TestRootRenameAfterOpen(t *testing.T) {
988 switch runtime.GOOS {
989 case "windows":
990 t.Skip("renaming open files not supported on " + runtime.GOOS)
991 case "js", "plan9":
992 t.Skip("openat not supported on " + runtime.GOOS)
993 case "wasip1":
994 if os.Getenv("GOWASIRUNTIME") == "wazero" {
995 t.Skip("wazero does not track renamed directories")
996 }
997 }
998
999 dir := t.TempDir()
1000
1001
1002 if err := os.Mkdir(filepath.Join(dir, "a"), 0o777); err != nil {
1003 t.Fatal(err)
1004 }
1005 dirf, err := os.OpenRoot(filepath.Join(dir, "a"))
1006 if err != nil {
1007 t.Fatal(err)
1008 }
1009 defer dirf.Close()
1010
1011
1012 if err := os.Rename(filepath.Join(dir, "a"), filepath.Join(dir, "b")); err != nil {
1013 t.Fatal(err)
1014 }
1015 if err := os.WriteFile(filepath.Join(dir, "b/f"), []byte("hello"), 0o666); err != nil {
1016 t.Fatal(err)
1017 }
1018
1019
1020 f, err := dirf.OpenFile("f", os.O_RDONLY, 0)
1021 if err != nil {
1022 t.Fatalf("reading file after renaming parent: %v", err)
1023 }
1024 defer f.Close()
1025 b, err := io.ReadAll(f)
1026 if err != nil {
1027 t.Fatal(err)
1028 }
1029 if got, want := string(b), "hello"; got != want {
1030 t.Fatalf("file contents: %q, want %q", got, want)
1031 }
1032
1033
1034 if got, want := f.Name(), dirf.Name()+string(os.PathSeparator)+"f"; got != want {
1035 t.Errorf("f.Name() = %q, want %q", got, want)
1036 }
1037 }
1038
1039 func TestRootNonPermissionMode(t *testing.T) {
1040 r, err := os.OpenRoot(t.TempDir())
1041 if err != nil {
1042 t.Fatal(err)
1043 }
1044 defer r.Close()
1045 if _, err := r.OpenFile("file", os.O_RDWR|os.O_CREATE, 0o1777); err == nil {
1046 t.Errorf("r.OpenFile(file, O_RDWR|O_CREATE, 0o1777) succeeded; want error")
1047 }
1048 if err := r.Mkdir("file", 0o1777); err == nil {
1049 t.Errorf("r.Mkdir(file, 0o1777) succeeded; want error")
1050 }
1051 }
1052
1053 func TestRootUseAfterClose(t *testing.T) {
1054 r, err := os.OpenRoot(t.TempDir())
1055 if err != nil {
1056 t.Fatal(err)
1057 }
1058 r.Close()
1059 for _, test := range []struct {
1060 name string
1061 f func(r *os.Root, filename string) error
1062 }{{
1063 name: "Open",
1064 f: func(r *os.Root, filename string) error {
1065 _, err := r.Open(filename)
1066 return err
1067 },
1068 }, {
1069 name: "Create",
1070 f: func(r *os.Root, filename string) error {
1071 _, err := r.Create(filename)
1072 return err
1073 },
1074 }, {
1075 name: "OpenFile",
1076 f: func(r *os.Root, filename string) error {
1077 _, err := r.OpenFile(filename, os.O_RDWR, 0o666)
1078 return err
1079 },
1080 }, {
1081 name: "OpenRoot",
1082 f: func(r *os.Root, filename string) error {
1083 _, err := r.OpenRoot(filename)
1084 return err
1085 },
1086 }, {
1087 name: "Mkdir",
1088 f: func(r *os.Root, filename string) error {
1089 return r.Mkdir(filename, 0o777)
1090 },
1091 }} {
1092 err := test.f(r, "target")
1093 pe, ok := err.(*os.PathError)
1094 if !ok || pe.Path != "target" || pe.Err != os.ErrClosed {
1095 t.Errorf(`r.%v = %v; want &PathError{Path: "target", Err: ErrClosed}`, test.name, err)
1096 }
1097 }
1098 }
1099
1100 func TestRootConcurrentClose(t *testing.T) {
1101 r, err := os.OpenRoot(t.TempDir())
1102 if err != nil {
1103 t.Fatal(err)
1104 }
1105 ch := make(chan error, 1)
1106 go func() {
1107 defer close(ch)
1108 first := true
1109 for {
1110 f, err := r.OpenFile("file", os.O_RDWR|os.O_CREATE, 0o666)
1111 if err != nil {
1112 ch <- err
1113 return
1114 }
1115 if first {
1116 ch <- nil
1117 first = false
1118 }
1119 f.Close()
1120 if runtime.GOARCH == "wasm" {
1121
1122 runtime.Gosched()
1123 }
1124 }
1125 }()
1126 if err := <-ch; err != nil {
1127 t.Errorf("OpenFile: %v, want success", err)
1128 }
1129 r.Close()
1130 if err := <-ch; !errors.Is(err, os.ErrClosed) {
1131 t.Errorf("OpenFile: %v, want ErrClosed", err)
1132 }
1133 }
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147 func TestRootRaceRenameDir(t *testing.T) {
1148 dir := t.TempDir()
1149 r, err := os.OpenRoot(dir)
1150 if err != nil {
1151 t.Fatal(err)
1152 }
1153 defer r.Close()
1154
1155 const depth = 4
1156
1157 os.MkdirAll(dir+"/base/"+strings.Repeat("/a", depth), 0o777)
1158
1159 path := "base/" + strings.Repeat("a/", depth) + strings.Repeat("../", depth) + "a/f"
1160 os.WriteFile(dir+"/f", []byte("secret"), 0o666)
1161 os.WriteFile(dir+"/base/a/f", []byte("public"), 0o666)
1162
1163
1164 const tries = 10
1165 var total time.Duration
1166 for range tries {
1167 start := time.Now()
1168 f, err := r.Open(path)
1169 if err != nil {
1170 t.Fatal(err)
1171 }
1172 b, err := io.ReadAll(f)
1173 if err != nil {
1174 t.Fatal(err)
1175 }
1176 if string(b) != "public" {
1177 t.Fatalf("read %q, want %q", b, "public")
1178 }
1179 f.Close()
1180 total += time.Since(start)
1181 }
1182 avg := total / tries
1183
1184
1185 for range 100 {
1186
1187 gotc := make(chan []byte)
1188 go func() {
1189 f, err := r.Open(path)
1190 if err != nil {
1191 gotc <- nil
1192 }
1193 defer f.Close()
1194 b, _ := io.ReadAll(f)
1195 gotc <- b
1196 }()
1197
1198
1199
1200 time.Sleep(avg / 4)
1201 if err := os.Rename(dir+"/base/a", dir+"/b"); err != nil {
1202
1203
1204 switch runtime.GOOS {
1205 case "windows", "plan9":
1206 default:
1207 t.Fatal(err)
1208 }
1209 }
1210
1211 got := <-gotc
1212 os.Rename(dir+"/b", dir+"/base/a")
1213 if len(got) > 0 && string(got) != "public" {
1214 t.Errorf("read file: %q; want error or 'public'", got)
1215 }
1216 }
1217 }
1218
1219 func TestRootSymlinkToRoot(t *testing.T) {
1220 dir := makefs(t, []string{
1221 "d/d => ..",
1222 })
1223 root, err := os.OpenRoot(dir)
1224 if err != nil {
1225 t.Fatal(err)
1226 }
1227 defer root.Close()
1228 if err := root.Mkdir("d/d/new", 0777); err != nil {
1229 t.Fatal(err)
1230 }
1231 f, err := root.Open("d/d")
1232 if err != nil {
1233 t.Fatal(err)
1234 }
1235 defer f.Close()
1236 names, err := f.Readdirnames(-1)
1237 if err != nil {
1238 t.Fatal(err)
1239 }
1240 slices.Sort(names)
1241 if got, want := names, []string{"d", "new"}; !slices.Equal(got, want) {
1242 t.Errorf("root contains: %q, want %q", got, want)
1243 }
1244 }
1245
1246 func TestOpenInRoot(t *testing.T) {
1247 dir := makefs(t, []string{
1248 "file",
1249 "link => ../ROOT/file",
1250 })
1251 f, err := os.OpenInRoot(dir, "file")
1252 if err != nil {
1253 t.Fatalf("OpenInRoot(`file`) = %v, want success", err)
1254 }
1255 f.Close()
1256 for _, name := range []string{
1257 "link",
1258 "../ROOT/file",
1259 dir + "/file",
1260 } {
1261 f, err := os.OpenInRoot(dir, name)
1262 if err == nil {
1263 f.Close()
1264 t.Fatalf("OpenInRoot(%q) = nil, want error", name)
1265 }
1266 }
1267 }
1268
View as plain text