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