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