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