Source file
src/os/root_test.go
1
2
3
4
5 package os_test
6
7 import (
8 "bytes"
9 "errors"
10 "flag"
11 "fmt"
12 "internal/testenv"
13 "io"
14 "io/fs"
15 "iter"
16 "net"
17 "os"
18 "path"
19 "path/filepath"
20 "runtime"
21 "slices"
22 "strings"
23 "testing"
24 "time"
25 )
26
27
28
29 func testMaybeRooted(t *testing.T, f func(t *testing.T, r *os.Root)) {
30 t.Run("NoRoot", func(t *testing.T) {
31 t.Chdir(t.TempDir())
32 f(t, nil)
33 })
34 t.Run("InRoot", func(t *testing.T) {
35 t.Chdir(t.TempDir())
36 r, err := os.OpenRoot(".")
37 if err != nil {
38 t.Fatal(err)
39 }
40 defer r.Close()
41 f(t, r)
42 })
43 }
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59 func makefs(t *testing.T, fs []string) string {
60 root := filepath.Join(t.TempDir(), "ROOT")
61 if err := os.Mkdir(root, 0o777); err != nil {
62 t.Fatal(err)
63 }
64 for _, ent := range fs {
65 ent = strings.ReplaceAll(ent, "$ABS", root)
66 base, link, isLink := strings.Cut(ent, " => ")
67 if isLink {
68 if runtime.GOOS == "wasip1" && path.IsAbs(link) {
69 t.Skip("absolute link targets not supported on " + runtime.GOOS)
70 }
71 if runtime.GOOS == "plan9" {
72 t.Skip("symlinks not supported on " + runtime.GOOS)
73 }
74 ent = base
75 }
76 if err := os.MkdirAll(path.Join(root, path.Dir(base)), 0o777); err != nil {
77 t.Fatal(err)
78 }
79 if isLink {
80 if err := os.Symlink(link, path.Join(root, base)); err != nil {
81 t.Fatal(err)
82 }
83 } else if strings.HasSuffix(ent, "/") {
84 if err := os.MkdirAll(path.Join(root, ent), 0o777); err != nil {
85 t.Fatal(err)
86 }
87 } else {
88 if err := os.WriteFile(path.Join(root, ent), []byte(ent), 0o666); err != nil {
89 t.Fatal(err)
90 }
91 }
92 }
93 return root
94 }
95
96
97 type rootTest struct {
98 name string
99
100
101 fs []string
102
103
104 open string
105
106
107
108
109 target string
110
111
112
113
114
115
116 ltarget string
117
118
119 wantError bool
120
121
122
123
124
125
126
127 alwaysFails bool
128 }
129
130
131 func (test *rootTest) run(t *testing.T, f func(t *testing.T, target string, d *os.Root)) {
132 t.Run(test.name, func(t *testing.T) {
133 root := makefs(t, test.fs)
134 d, err := os.OpenRoot(root)
135 if err != nil {
136 t.Fatal(err)
137 }
138 defer d.Close()
139
140
141
142 target := test.target
143 if test.target != "" {
144 target = filepath.Join(root, test.target)
145 }
146 f(t, target, d)
147 })
148 }
149
150
151
152
153
154
155 func errEndsTest(t *testing.T, err error, wantError bool, format string, args ...any) bool {
156 t.Helper()
157 if wantError {
158 if err == nil {
159 op := fmt.Sprintf(format, args...)
160 t.Fatalf("%v = nil; want error", op)
161 }
162 return true
163 } else {
164 if err != nil {
165 op := fmt.Sprintf(format, args...)
166 t.Fatalf("%v = %v; want success", op, err)
167 }
168 return false
169 }
170 }
171
172 var rootTestCases = []rootTest{{
173 name: "plain path",
174 fs: []string{},
175 open: "target",
176 target: "target",
177 }, {
178 name: "path in directory",
179 fs: []string{
180 "a/b/c/",
181 },
182 open: "a/b/c/target",
183 target: "a/b/c/target",
184 }, {
185 name: "symlink",
186 fs: []string{
187 "link => target",
188 },
189 open: "link",
190 target: "target",
191 ltarget: "link",
192 }, {
193 name: "symlink dotdot slash",
194 fs: []string{
195 "link => ../",
196 },
197 open: "link",
198 ltarget: "link",
199 wantError: true,
200 }, {
201 name: "symlink ending in slash",
202 fs: []string{
203 "dir/",
204 "link => dir/",
205 },
206 open: "link/target",
207 target: "dir/target",
208 }, {
209 name: "slash after symlink to file",
210 fs: []string{
211 "link => ../ROOT/target",
212 },
213 open: "link/",
214 target: "target",
215 wantError: true,
216 }, {
217 name: "slash after symlink to dir",
218 fs: []string{
219 "link => ../ROOT/target",
220 "target/",
221 },
222 open: "link/",
223 wantError: true,
224 }, {
225 name: "symlink dotdot dotdot slash",
226 fs: []string{
227 "dir/link => ../../",
228 },
229 open: "dir/link",
230 ltarget: "dir/link",
231 wantError: true,
232 }, {
233 name: "symlink chain",
234 fs: []string{
235 "link => a/b/c/target",
236 "a/b => e",
237 "a/e => ../f",
238 "f => g/h/i",
239 "g/h/i => ..",
240 "g/c/",
241 },
242 open: "link",
243 target: "g/c/target",
244 ltarget: "link",
245 }, {
246 name: "path with dot",
247 fs: []string{
248 "a/b/",
249 },
250 open: "./a/./b/./target",
251 target: "a/b/target",
252 }, {
253 name: "path with dotdot",
254 fs: []string{
255 "a/b/",
256 },
257 open: "a/../a/b/../../a/b/../b/target",
258 target: "a/b/target",
259 }, {
260 name: "path with dotdot slash",
261 fs: []string{},
262 open: "../",
263 wantError: true,
264 }, {
265 name: "path with dotdot dotdot slash",
266 fs: []string{
267 "a/",
268 },
269 open: "a/../../",
270 wantError: true,
271 }, {
272 name: "dotdot no symlink",
273 fs: []string{
274 "a/",
275 },
276 open: "a/../target",
277 target: "target",
278 }, {
279 name: "dotdot after symlink",
280 fs: []string{
281 "a => b/c",
282 "b/c/",
283 },
284 open: "a/../target",
285 target: func() string {
286 if runtime.GOOS == "windows" {
287
288 return "target"
289 }
290 return "b/target"
291 }(),
292 }, {
293 name: "dotdot before symlink",
294 fs: []string{
295 "a => b/c",
296 "b/c/",
297 },
298 open: "b/../a/target",
299 target: "b/c/target",
300 }, {
301 name: "symlink ends in dot",
302 fs: []string{
303 "a => b/.",
304 "b/",
305 },
306 open: "a/target",
307 target: "b/target",
308 }, {
309 name: "directory does not exist",
310 fs: []string{},
311 open: "a/file",
312 wantError: true,
313 alwaysFails: true,
314 }, {
315 name: "empty path",
316 fs: []string{},
317 open: "",
318 wantError: true,
319 alwaysFails: true,
320 }, {
321 name: "symlink cycle",
322 fs: []string{
323 "a => a",
324 },
325 open: "a",
326 ltarget: "a",
327 wantError: true,
328 alwaysFails: true,
329 }, {
330 name: "path escapes",
331 fs: []string{},
332 open: "../ROOT/target",
333 target: "target",
334 wantError: true,
335 }, {
336 name: "long path escapes",
337 fs: []string{
338 "a/",
339 },
340 open: "a/../../ROOT/target",
341 target: "target",
342 wantError: true,
343 }, {
344 name: "absolute symlink",
345 fs: []string{
346 "link => $ABS/target",
347 },
348 open: "link",
349 ltarget: "link",
350 target: "target",
351 wantError: true,
352 }, {
353 name: "relative symlink",
354 fs: []string{
355 "link => ../ROOT/target",
356 },
357 open: "link",
358 target: "target",
359 ltarget: "link",
360 wantError: true,
361 }, {
362 name: "symlink chain escapes",
363 fs: []string{
364 "link => a/b/c/target",
365 "a/b => e",
366 "a/e => ../../ROOT",
367 "c/",
368 },
369 open: "link",
370 target: "c/target",
371 ltarget: "link",
372 wantError: true,
373 }}
374
375 func TestRootOpen_File(t *testing.T) {
376 want := []byte("target")
377 for _, test := range rootTestCases {
378 test.run(t, func(t *testing.T, target string, root *os.Root) {
379 if target != "" {
380 if err := os.WriteFile(target, want, 0o666); err != nil {
381 t.Fatal(err)
382 }
383 }
384 f, err := root.Open(test.open)
385 if errEndsTest(t, err, test.wantError, "root.Open(%q)", test.open) {
386 return
387 }
388 defer f.Close()
389 got, err := io.ReadAll(f)
390 if err != nil || !bytes.Equal(got, want) {
391 t.Errorf(`Dir.Open(%q): read content %q, %v; want %q`, test.open, string(got), err, string(want))
392 }
393 })
394 }
395 }
396
397 func TestRootOpen_Directory(t *testing.T) {
398 for _, test := range rootTestCases {
399 test.run(t, func(t *testing.T, target string, root *os.Root) {
400 if target != "" {
401 if err := os.Mkdir(target, 0o777); err != nil {
402 t.Fatal(err)
403 }
404 if err := os.WriteFile(target+"/found", nil, 0o666); err != nil {
405 t.Fatal(err)
406 }
407 }
408 f, err := root.Open(test.open)
409 if errEndsTest(t, err, test.wantError, "root.Open(%q)", test.open) {
410 return
411 }
412 defer f.Close()
413 got, err := f.Readdirnames(-1)
414 if err != nil {
415 t.Errorf(`Dir.Open(%q).Readdirnames: %v`, test.open, err)
416 }
417 if want := []string{"found"}; !slices.Equal(got, want) {
418 t.Errorf(`Dir.Open(%q).Readdirnames: %q, want %q`, test.open, got, want)
419 }
420 })
421 }
422 }
423
424 func TestRootCreate(t *testing.T) {
425 want := []byte("target")
426 for _, test := range rootTestCases {
427 test.run(t, func(t *testing.T, target string, root *os.Root) {
428 f, err := root.Create(test.open)
429 if errEndsTest(t, err, test.wantError, "root.Create(%q)", test.open) {
430 return
431 }
432 if _, err := f.Write(want); err != nil {
433 t.Fatal(err)
434 }
435 f.Close()
436 got, err := os.ReadFile(target)
437 if err != nil {
438 t.Fatalf(`reading file created with root.Create(%q): %v`, test.open, err)
439 }
440 if !bytes.Equal(got, want) {
441 t.Fatalf(`reading file created with root.Create(%q): got %q; want %q`, test.open, got, want)
442 }
443 })
444 }
445 }
446
447 func TestRootChmod(t *testing.T) {
448 if runtime.GOOS == "wasip1" {
449 t.Skip("Chmod not supported on " + runtime.GOOS)
450 }
451 for _, test := range rootTestCases {
452 test.run(t, func(t *testing.T, target string, root *os.Root) {
453 if target != "" {
454
455
456 if err := os.WriteFile(target, nil, 0o000); err != nil {
457 t.Fatal(err)
458 }
459 }
460 if runtime.GOOS == "windows" {
461
462
463 fi, err := root.Lstat(test.open)
464 if err == nil && !fi.Mode().IsRegular() {
465 t.Skip("https://go.dev/issue/71492")
466 }
467 }
468 want := os.FileMode(0o666)
469 err := root.Chmod(test.open, want)
470 if errEndsTest(t, err, test.wantError, "root.Chmod(%q)", test.open) {
471 return
472 }
473 st, err := os.Stat(target)
474 if err != nil {
475 t.Fatalf("os.Stat(%q) = %v", target, err)
476 }
477 if got := st.Mode(); got != want {
478 t.Errorf("after root.Chmod(%q, %v): file mode = %v, want %v", test.open, want, got, want)
479 }
480 })
481 }
482 }
483
484 func TestRootChtimes(t *testing.T) {
485
486
487 checkAtimes := !hasNoatime() && runtime.GOOS != "plan9"
488 for _, test := range rootTestCases {
489 test.run(t, func(t *testing.T, target string, root *os.Root) {
490 if target != "" {
491 if err := os.WriteFile(target, nil, 0o666); err != nil {
492 t.Fatal(err)
493 }
494 }
495 for _, times := range []struct {
496 atime, mtime time.Time
497 }{{
498 atime: time.Now().Add(-1 * time.Minute),
499 mtime: time.Now().Add(-1 * time.Minute),
500 }, {
501 atime: time.Now().Add(1 * time.Minute),
502 mtime: time.Now().Add(1 * time.Minute),
503 }, {
504 atime: time.Time{},
505 mtime: time.Now(),
506 }, {
507 atime: time.Now(),
508 mtime: time.Time{},
509 }} {
510 switch runtime.GOOS {
511 case "js", "plan9":
512 times.atime = times.atime.Truncate(1 * time.Second)
513 times.mtime = times.mtime.Truncate(1 * time.Second)
514 case "illumos":
515 times.atime = times.atime.Truncate(1 * time.Microsecond)
516 times.mtime = times.mtime.Truncate(1 * time.Microsecond)
517 }
518
519 err := root.Chtimes(test.open, times.atime, times.mtime)
520 if errEndsTest(t, err, test.wantError, "root.Chtimes(%q)", test.open) {
521 return
522 }
523 st, err := os.Stat(target)
524 if err != nil {
525 t.Fatalf("os.Stat(%q) = %v", target, err)
526 }
527 if got := st.ModTime(); !times.mtime.IsZero() && !got.Equal(times.mtime) {
528 t.Errorf("after root.Chtimes(%q, %v, %v): got mtime=%v, want %v", test.open, times.atime, times.mtime, got, times.mtime)
529 }
530 if checkAtimes {
531 if got := os.Atime(st); !times.atime.IsZero() && !got.Equal(times.atime) {
532 t.Errorf("after root.Chtimes(%q, %v, %v): got atime=%v, want %v", test.open, times.atime, times.mtime, got, times.atime)
533 }
534 }
535 }
536 })
537 }
538 }
539
540 func TestRootMkdir(t *testing.T) {
541 for _, test := range rootTestCases {
542 test.run(t, func(t *testing.T, target string, root *os.Root) {
543 wantError := test.wantError
544 if test.ltarget != "" {
545
546
547 wantError = true
548 }
549
550 err := root.Mkdir(test.open, 0o777)
551 if errEndsTest(t, err, wantError, "root.Create(%q)", test.open) {
552 return
553 }
554 fi, err := os.Lstat(target)
555 if err != nil {
556 t.Fatalf(`stat file created with Root.Mkdir(%q): %v`, test.open, err)
557 }
558 if !fi.IsDir() {
559 t.Fatalf(`stat file created with Root.Mkdir(%q): not a directory`, test.open)
560 }
561 if mode := fi.Mode(); mode&0o777 == 0 {
562
563
564
565 t.Fatalf(`stat file created with Root.Mkdir(%q): mode=%v, want non-zero`, test.open, mode)
566 }
567 })
568 }
569 }
570
571 func TestRootMkdirAll(t *testing.T) {
572 for _, test := range rootTestCases {
573 if test.name == "directory does not exist" {
574
575
576 continue
577 }
578 test.run(t, func(t *testing.T, target string, root *os.Root) {
579 wantError := test.wantError
580 if test.ltarget != "" {
581
582
583 wantError = true
584 }
585
586 err := root.MkdirAll(test.open, 0o777)
587 if errEndsTest(t, err, wantError, "root.MkdirAll(%q)", test.open) {
588 return
589 }
590 fi, err := os.Lstat(target)
591 if err != nil {
592 t.Fatalf(`stat file created with Root.MkdirAll(%q): %v`, test.open, err)
593 }
594 if !fi.IsDir() {
595 t.Fatalf(`stat file created with Root.MkdirAll(%q): not a directory`, test.open)
596 }
597 if mode := fi.Mode(); mode&0o777 == 0 {
598
599
600
601 t.Fatalf(`stat file created with Root.MkdirAll(%q): mode=%v, want non-zero`, test.open, mode)
602 }
603 })
604 }
605 }
606
607 func TestRootOpenRoot(t *testing.T) {
608 for _, test := range rootTestCases {
609 test.run(t, func(t *testing.T, target string, root *os.Root) {
610 if target != "" {
611 if err := os.Mkdir(target, 0o777); err != nil {
612 t.Fatal(err)
613 }
614 if err := os.WriteFile(target+"/f", nil, 0o666); err != nil {
615 t.Fatal(err)
616 }
617 }
618 rr, err := root.OpenRoot(test.open)
619 if errEndsTest(t, err, test.wantError, "root.OpenRoot(%q)", test.open) {
620 return
621 }
622 defer rr.Close()
623 f, err := rr.Open("f")
624 if err != nil {
625 t.Fatalf(`root.OpenRoot(%q).Open("f") = %v`, test.open, err)
626 }
627 f.Close()
628 })
629 }
630 }
631
632 func TestRootRemoveFile(t *testing.T) {
633 for _, test := range rootTestCases {
634 test.run(t, func(t *testing.T, target string, root *os.Root) {
635 wantError := test.wantError
636 if test.ltarget != "" {
637
638
639 wantError = false
640 target = filepath.Join(root.Name(), test.ltarget)
641 } else if target != "" {
642 if err := os.WriteFile(target, nil, 0o666); err != nil {
643 t.Fatal(err)
644 }
645 }
646
647 err := root.Remove(test.open)
648 if errEndsTest(t, err, wantError, "root.Remove(%q)", test.open) {
649 return
650 }
651 _, err = os.Lstat(target)
652 if !errors.Is(err, os.ErrNotExist) {
653 t.Fatalf(`stat file removed with Root.Remove(%q): %v, want ErrNotExist`, test.open, err)
654 }
655 })
656 }
657 }
658
659 func TestRootRemoveDirectory(t *testing.T) {
660 for _, test := range rootTestCases {
661 test.run(t, func(t *testing.T, target string, root *os.Root) {
662 wantError := test.wantError
663 if test.ltarget != "" {
664
665
666 wantError = false
667 target = filepath.Join(root.Name(), test.ltarget)
668 } else if target != "" {
669 if err := os.Mkdir(target, 0o777); err != nil {
670 t.Fatal(err)
671 }
672 }
673
674 err := root.Remove(test.open)
675 if errEndsTest(t, err, wantError, "root.Remove(%q)", test.open) {
676 return
677 }
678 _, err = os.Lstat(target)
679 if !errors.Is(err, os.ErrNotExist) {
680 t.Fatalf(`stat file removed with Root.Remove(%q): %v, want ErrNotExist`, test.open, err)
681 }
682 })
683 }
684 }
685
686 func TestRootRemoveAll(t *testing.T) {
687 for _, test := range rootTestCases {
688 test.run(t, func(t *testing.T, target string, root *os.Root) {
689 if strings.HasSuffix(test.open, "/") {
690
691
692
693 fullname := filepath.Join(root.Name(), test.open)
694 if st, err := os.Lstat(fullname); err == nil && st.Mode().Type() == fs.ModeSymlink {
695 test.ltarget = test.open
696 }
697 }
698 wantError := test.wantError
699 if test.ltarget != "" {
700
701
702 wantError = false
703 target = filepath.Join(root.Name(), test.ltarget)
704 } else if target != "" {
705 if err := os.Mkdir(target, 0o777); err != nil {
706 t.Fatal(err)
707 }
708 if err := os.WriteFile(filepath.Join(target, "file"), nil, 0o666); err != nil {
709 t.Fatal(err)
710 }
711 }
712 targetExists := true
713 if _, err := root.Lstat(test.open); errors.Is(err, os.ErrNotExist) {
714
715
716 targetExists = false
717 wantError = false
718 }
719
720 err := root.RemoveAll(test.open)
721 if errEndsTest(t, err, wantError, "root.RemoveAll(%q)", test.open) {
722 return
723 }
724 if !targetExists {
725 return
726 }
727 _, err = os.Lstat(target)
728 if !errors.Is(err, os.ErrNotExist) {
729 t.Fatalf(`stat file removed with Root.Remove(%q): %v, want ErrNotExist`, test.open, err)
730 }
731 })
732 }
733 }
734
735 func TestRootOpenFileAsRoot(t *testing.T) {
736 dir := t.TempDir()
737 target := filepath.Join(dir, "target")
738 if err := os.WriteFile(target, nil, 0o666); err != nil {
739 t.Fatal(err)
740 }
741 r, err := os.OpenRoot(target)
742 if err == nil {
743 r.Close()
744 t.Fatal("os.OpenRoot(file) succeeded; want failure")
745 }
746 r, err = os.OpenRoot(dir)
747 if err != nil {
748 t.Fatal(err)
749 }
750 defer r.Close()
751 rr, err := r.OpenRoot("target")
752 if err == nil {
753 rr.Close()
754 t.Fatal("Root.OpenRoot(file) succeeded; want failure")
755 }
756 }
757
758 func TestRootStat(t *testing.T) {
759 for _, test := range rootTestCases {
760 test.run(t, func(t *testing.T, target string, root *os.Root) {
761 const content = "content"
762 if target != "" {
763 if err := os.WriteFile(target, []byte(content), 0o666); err != nil {
764 t.Fatal(err)
765 }
766 }
767
768 fi, err := root.Stat(test.open)
769 if errEndsTest(t, err, test.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 got, want := fi.Size(), int64(len(content)); got != want {
776 t.Errorf("root.Stat(%q).Size() = %v, want %v", test.open, got, want)
777 }
778 })
779 }
780 }
781
782 func TestRootLstat(t *testing.T) {
783 for _, test := range rootTestCases {
784 test.run(t, func(t *testing.T, target string, root *os.Root) {
785 const content = "content"
786 wantError := test.wantError
787 if test.ltarget != "" {
788
789 wantError = false
790 } else if target != "" {
791 if err := os.WriteFile(target, []byte(content), 0o666); err != nil {
792 t.Fatal(err)
793 }
794 }
795
796 fi, err := root.Lstat(test.open)
797 if errEndsTest(t, err, wantError, "root.Stat(%q)", test.open) {
798 return
799 }
800 if got, want := fi.Name(), filepath.Base(test.open); got != want {
801 t.Errorf("root.Stat(%q).Name() = %q, want %q", test.open, got, want)
802 }
803 if test.ltarget == "" {
804 if got := fi.Mode(); got&os.ModeSymlink != 0 {
805 t.Errorf("root.Stat(%q).Mode() = %v, want non-symlink", test.open, got)
806 }
807 if got, want := fi.Size(), int64(len(content)); got != want {
808 t.Errorf("root.Stat(%q).Size() = %v, want %v", test.open, got, want)
809 }
810 } else {
811 if got := fi.Mode(); got&os.ModeSymlink == 0 {
812 t.Errorf("root.Stat(%q).Mode() = %v, want symlink", test.open, got)
813 }
814 }
815 })
816 }
817 }
818
819 func TestRootReadlink(t *testing.T) {
820 for _, test := range rootTestCases {
821 test.run(t, func(t *testing.T, target string, root *os.Root) {
822 const content = "content"
823 wantError := test.wantError
824 if test.ltarget != "" {
825
826 wantError = false
827 } else {
828
829 wantError = true
830 }
831
832 got, err := root.Readlink(test.open)
833 if errEndsTest(t, err, wantError, "root.Readlink(%q)", test.open) {
834 return
835 }
836
837 want, err := os.Readlink(filepath.Join(root.Name(), test.ltarget))
838 if err != nil {
839 t.Fatalf("os.Readlink(%q) = %v, want success", test.ltarget, err)
840 }
841 if got != want {
842 t.Errorf("root.Readlink(%q) = %q, want %q", test.open, got, want)
843 }
844 })
845 }
846 }
847
848
849 func TestRootRenameFrom(t *testing.T) {
850 testRootMoveFrom(t, true)
851 }
852
853
854 func TestRootLinkFrom(t *testing.T) {
855 testenv.MustHaveLink(t)
856 testRootMoveFrom(t, false)
857 }
858
859 func testRootMoveFrom(t *testing.T, rename bool) {
860 want := []byte("target")
861 for _, test := range rootTestCases {
862 test.run(t, func(t *testing.T, target string, root *os.Root) {
863 if target != "" {
864 if err := os.WriteFile(target, want, 0o666); err != nil {
865 t.Fatal(err)
866 }
867 }
868 wantError := test.wantError
869 var linkTarget string
870 if test.ltarget != "" {
871
872 wantError = false
873 var err error
874 linkTarget, err = root.Readlink(test.ltarget)
875 if err != nil {
876 t.Fatalf("root.Readlink(%q) = %v, want success", test.ltarget, err)
877 }
878
879
880 if !rename && runtime.GOOS == "js" {
881 wantError = true
882 }
883
884
885
886
887
888
889
890
891
892 if !rename && runtime.GOOS == "windows" {
893 st, err := os.Stat(filepath.Join(root.Name(), test.ltarget))
894 if err == nil && st.IsDir() {
895 wantError = true
896 }
897 }
898 }
899
900 const dstPath = "destination"
901
902
903 if runtime.GOOS == "plan9" && strings.Contains(test.open, "/") {
904 wantError = true
905 }
906
907 var op string
908 var err error
909 if rename {
910 op = "Rename"
911 err = root.Rename(test.open, dstPath)
912 } else {
913 op = "Link"
914 err = root.Link(test.open, dstPath)
915 }
916 if errEndsTest(t, err, wantError, "root.%v(%q, %q)", op, test.open, dstPath) {
917 return
918 }
919
920 origPath := target
921 if test.ltarget != "" {
922 origPath = filepath.Join(root.Name(), test.ltarget)
923 }
924 _, err = os.Lstat(origPath)
925 if rename {
926 if !errors.Is(err, os.ErrNotExist) {
927 t.Errorf("after renaming file, Lstat(%q) = %v, want ErrNotExist", origPath, err)
928 }
929 } else {
930 if err != nil {
931 t.Errorf("after linking file, error accessing original: %v", err)
932 }
933 }
934
935 dstFullPath := filepath.Join(root.Name(), dstPath)
936 if test.ltarget != "" {
937 got, err := os.Readlink(dstFullPath)
938 if err != nil || got != linkTarget {
939 t.Errorf("os.Readlink(%q) = %q, %v, want %q", dstFullPath, got, err, linkTarget)
940 }
941 } else {
942 got, err := os.ReadFile(dstFullPath)
943 if err != nil || !bytes.Equal(got, want) {
944 t.Errorf(`os.ReadFile(%q): read content %q, %v; want %q`, dstFullPath, string(got), err, string(want))
945 }
946 st, err := os.Lstat(dstFullPath)
947 if err != nil || st.Mode()&fs.ModeSymlink != 0 {
948 t.Errorf(`os.Lstat(%q) = %v, %v; want non-symlink`, dstFullPath, st.Mode(), err)
949 }
950
951 }
952 })
953 }
954 }
955
956
957 func TestRootRenameTo(t *testing.T) {
958 testRootMoveTo(t, true)
959 }
960
961
962 func TestRootLinkTo(t *testing.T) {
963 testenv.MustHaveLink(t)
964 testRootMoveTo(t, true)
965 }
966
967 func testRootMoveTo(t *testing.T, rename bool) {
968 want := []byte("target")
969 for _, test := range rootTestCases {
970 test.run(t, func(t *testing.T, target string, root *os.Root) {
971 const srcPath = "source"
972 if err := os.WriteFile(filepath.Join(root.Name(), srcPath), want, 0o666); err != nil {
973 t.Fatal(err)
974 }
975
976 if runtime.GOOS == "windows" && strings.HasSuffix(test.open, "/") {
977
978 p := strings.TrimSuffix(test.open, "/")
979 st, err := root.Lstat(p)
980 if err == nil && st.Mode().Type() == fs.ModeSymlink {
981 test.ltarget = p
982 }
983 }
984
985 target = test.target
986 wantError := test.wantError
987 if test.ltarget != "" {
988
989 target = test.ltarget
990 wantError = false
991 }
992
993
994 if runtime.GOOS == "plan9" && strings.Contains(test.open, "/") {
995 wantError = true
996 }
997
998 var err error
999 var op string
1000 if rename {
1001 op = "Rename"
1002 err = root.Rename(srcPath, test.open)
1003 } else {
1004 op = "Link"
1005 err = root.Link(srcPath, test.open)
1006 }
1007 if errEndsTest(t, err, wantError, "root.%v(%q, %q)", op, srcPath, test.open) {
1008 return
1009 }
1010
1011 _, err = os.Lstat(filepath.Join(root.Name(), srcPath))
1012 if rename {
1013 if !errors.Is(err, os.ErrNotExist) {
1014 t.Errorf("after renaming file, Lstat(%q) = %v, want ErrNotExist", srcPath, err)
1015 }
1016 } else {
1017 if err != nil {
1018 t.Errorf("after linking file, error accessing original: %v", err)
1019 }
1020 }
1021
1022 got, err := os.ReadFile(filepath.Join(root.Name(), target))
1023 if err != nil || !bytes.Equal(got, want) {
1024 t.Errorf(`os.ReadFile(%q): read content %q, %v; want %q`, target, string(got), err, string(want))
1025 }
1026 })
1027 }
1028 }
1029
1030 func TestRootSymlink(t *testing.T) {
1031 testenv.MustHaveSymlink(t)
1032 for _, test := range rootTestCases {
1033 test.run(t, func(t *testing.T, target string, root *os.Root) {
1034 wantError := test.wantError
1035 if test.ltarget != "" {
1036
1037 wantError = true
1038 }
1039
1040 const wantTarget = "linktarget"
1041 err := root.Symlink(wantTarget, test.open)
1042 if errEndsTest(t, err, wantError, "root.Symlink(%q)", test.open) {
1043 return
1044 }
1045 got, err := os.Readlink(target)
1046 if err != nil || got != wantTarget {
1047 t.Fatalf("ReadLink(%q) = %q, %v; want %q, nil", target, got, err, wantTarget)
1048 }
1049 })
1050 }
1051 }
1052
1053
1054
1055
1056
1057
1058 type rootConsistencyTest struct {
1059 name string
1060
1061
1062
1063 fs []string
1064 fsFunc func(t *testing.T, dir string) string
1065
1066
1067 open string
1068
1069
1070
1071 detailedErrorMismatch func(t *testing.T) bool
1072
1073
1074 check func(t *testing.T)
1075 }
1076
1077 var rootConsistencyTestCases = []rootConsistencyTest{{
1078 name: "file",
1079 fs: []string{
1080 "target",
1081 },
1082 open: "target",
1083 }, {
1084 name: "dir slash dot",
1085 fs: []string{
1086 "target/file",
1087 },
1088 open: "target/.",
1089 }, {
1090 name: "dot",
1091 fs: []string{
1092 "file",
1093 },
1094 open: ".",
1095 }, {
1096 name: "file slash dot",
1097 fs: []string{
1098 "target",
1099 },
1100 open: "target/.",
1101 detailedErrorMismatch: func(t *testing.T) bool {
1102
1103 return runtime.GOOS == "freebsd" && strings.HasPrefix(t.Name(), "TestRootConsistencyRemove")
1104 },
1105 }, {
1106 name: "dir slash",
1107 fs: []string{
1108 "target/file",
1109 },
1110 open: "target/",
1111 }, {
1112 name: "dot slash",
1113 fs: []string{
1114 "file",
1115 },
1116 open: "./",
1117 }, {
1118 name: "file slash",
1119 fs: []string{
1120 "target",
1121 },
1122 open: "target/",
1123 detailedErrorMismatch: func(t *testing.T) bool {
1124
1125 return runtime.GOOS == "js"
1126 },
1127 }, {
1128 name: "file in path",
1129 fs: []string{
1130 "file",
1131 },
1132 open: "file/target",
1133 }, {
1134 name: "directory in path missing",
1135 open: "dir/target",
1136 }, {
1137 name: "target does not exist",
1138 open: "target",
1139 }, {
1140 name: "symlink slash",
1141 fs: []string{
1142 "target/file",
1143 "link => target",
1144 },
1145 open: "link/",
1146 check: func(t *testing.T) {
1147 if runtime.GOOS == "linux" && strings.HasPrefix(t.Name(), "TestRootConsistencyRename/") {
1148
1149 t.Skip("known inconsistency on linux")
1150 }
1151 if strings.HasPrefix(t.Name(), "TestRootConsistencyRemoveAll/") {
1152
1153 t.Skip("known inconsistency in RemoveAll")
1154 }
1155 },
1156 }, {
1157 name: "symlink slash dot",
1158 fs: []string{
1159 "target/file",
1160 "link => target",
1161 },
1162 open: "link/.",
1163 }, {
1164 name: "unresolved symlink",
1165 fs: []string{
1166 "link => target",
1167 },
1168 open: "link",
1169 }, {
1170 name: "resolved symlink",
1171 fs: []string{
1172 "link => target",
1173 "target",
1174 },
1175 open: "link",
1176 }, {
1177 name: "dotdot in path after symlink",
1178 fs: []string{
1179 "a => b/c",
1180 "b/c/",
1181 "b/target",
1182 },
1183 open: "a/../target",
1184 }, {
1185 name: "symlink to dir ends in slash",
1186 fs: []string{
1187 "dir/",
1188 "link => dir/",
1189 },
1190 open: "link",
1191 }, {
1192 name: "symlink to file ends in slash",
1193 fs: []string{
1194 "file",
1195 "link => file/",
1196 },
1197 open: "link",
1198 }, {
1199 name: "long file name",
1200 open: strings.Repeat("a", 500),
1201 }, {
1202 name: "unreadable directory",
1203 fs: []string{
1204 "dir/target",
1205 },
1206 fsFunc: func(t *testing.T, dir string) string {
1207 os.Chmod(filepath.Join(dir, "dir"), 0)
1208 t.Cleanup(func() {
1209 os.Chmod(filepath.Join(dir, "dir"), 0o700)
1210 })
1211 return dir
1212 },
1213 open: "dir/target",
1214 }, {
1215 name: "unix domain socket target",
1216 fsFunc: func(t *testing.T, dir string) string {
1217 return tempDirWithUnixSocket(t, "a")
1218 },
1219 open: "a",
1220 }, {
1221 name: "unix domain socket in path",
1222 fsFunc: func(t *testing.T, dir string) string {
1223 return tempDirWithUnixSocket(t, "a")
1224 },
1225 open: "a/b",
1226 detailedErrorMismatch: func(t *testing.T) bool {
1227
1228
1229 return runtime.GOOS == "windows"
1230 },
1231 check: func(t *testing.T) {
1232 if strings.HasPrefix(t.Name(), "TestRootConsistencyRemoveAll/") {
1233 switch runtime.GOOS {
1234 case "windows":
1235
1236
1237
1238
1239
1240 t.Skip("known inconsistency on windows")
1241 case "js":
1242
1243 t.Skip("known inconsistency with GOOS=js")
1244 }
1245 }
1246 },
1247 }, {
1248 name: "question mark",
1249 open: "?",
1250 }, {
1251 name: "nul byte",
1252 open: "\x00",
1253 }}
1254
1255 func tempDirWithUnixSocket(t *testing.T, name string) string {
1256 dir, err := os.MkdirTemp("", "")
1257 if err != nil {
1258 t.Fatal(err)
1259 }
1260 t.Cleanup(func() {
1261 if err := os.RemoveAll(dir); err != nil {
1262 t.Error(err)
1263 }
1264 })
1265 addr, err := net.ResolveUnixAddr("unix", filepath.Join(dir, name))
1266 if err != nil {
1267 t.Skipf("net.ResolveUnixAddr: %v", err)
1268 }
1269 conn, err := net.ListenUnix("unix", addr)
1270 if err != nil {
1271 t.Skipf("net.ListenUnix: %v", err)
1272 }
1273 t.Cleanup(func() {
1274 conn.Close()
1275 })
1276 return dir
1277 }
1278
1279 func (test rootConsistencyTest) run(t *testing.T, f func(t *testing.T, path string, r *os.Root) (string, error)) {
1280 if runtime.GOOS == "wasip1" {
1281
1282
1283
1284 t.Skip("#69509: inconsistent results on wasip1")
1285 }
1286
1287 t.Run(test.name, func(t *testing.T) {
1288 if test.check != nil {
1289 test.check(t)
1290 }
1291
1292 dir1 := makefs(t, test.fs)
1293 dir2 := makefs(t, test.fs)
1294 if test.fsFunc != nil {
1295 dir1 = test.fsFunc(t, dir1)
1296 dir2 = test.fsFunc(t, dir2)
1297 }
1298
1299 r, err := os.OpenRoot(dir1)
1300 if err != nil {
1301 t.Fatal(err)
1302 }
1303 defer r.Close()
1304
1305 res1, err1 := f(t, test.open, r)
1306 res2, err2 := f(t, dir2+"/"+test.open, nil)
1307
1308 if res1 != res2 || ((err1 == nil) != (err2 == nil)) {
1309 t.Errorf("with root: res=%v", res1)
1310 t.Errorf(" err=%v", err1)
1311 t.Errorf("without root: res=%v", res2)
1312 t.Errorf(" err=%v", err2)
1313 t.Errorf("want consistent results, got mismatch")
1314 }
1315
1316 if err1 != nil || err2 != nil {
1317 underlyingError := func(how string, err error) error {
1318 switch e := err1.(type) {
1319 case *os.PathError:
1320 return e.Err
1321 case *os.LinkError:
1322 return e.Err
1323 default:
1324 t.Fatalf("%v, expected PathError or LinkError; got: %v", how, err)
1325 }
1326 return nil
1327 }
1328 e1 := underlyingError("with root", err1)
1329 e2 := underlyingError("without root", err1)
1330 detailedErrorMismatch := false
1331 if f := test.detailedErrorMismatch; f != nil {
1332 detailedErrorMismatch = f(t)
1333 }
1334 if runtime.GOOS == "plan9" {
1335
1336 detailedErrorMismatch = true
1337 }
1338 if !detailedErrorMismatch && e1 != e2 {
1339 t.Errorf("with root: err=%v", e1)
1340 t.Errorf("without root: err=%v", e2)
1341 t.Errorf("want consistent results, got mismatch")
1342 }
1343 }
1344 })
1345 }
1346
1347 func TestRootConsistencyOpen(t *testing.T) {
1348 for _, test := range rootConsistencyTestCases {
1349 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
1350 var f *os.File
1351 var err error
1352 if r == nil {
1353 f, err = os.Open(path)
1354 } else {
1355 f, err = r.Open(path)
1356 }
1357 if err != nil {
1358 return "", err
1359 }
1360 defer f.Close()
1361 fi, err := f.Stat()
1362 if err == nil && !fi.IsDir() {
1363 b, err := io.ReadAll(f)
1364 return string(b), err
1365 } else {
1366 names, err := f.Readdirnames(-1)
1367 slices.Sort(names)
1368 return fmt.Sprintf("%q", names), err
1369 }
1370 })
1371 }
1372 }
1373
1374 func TestRootConsistencyCreate(t *testing.T) {
1375 for _, test := range rootConsistencyTestCases {
1376 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
1377 var f *os.File
1378 var err error
1379 if r == nil {
1380 f, err = os.Create(path)
1381 } else {
1382 f, err = r.Create(path)
1383 }
1384 if err == nil {
1385 f.Write([]byte("file contents"))
1386 f.Close()
1387 }
1388 return "", err
1389 })
1390 }
1391 }
1392
1393 func TestRootConsistencyChmod(t *testing.T) {
1394 if runtime.GOOS == "wasip1" {
1395 t.Skip("Chmod not supported on " + runtime.GOOS)
1396 }
1397 for _, test := range rootConsistencyTestCases {
1398 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
1399 chmod := os.Chmod
1400 lstat := os.Lstat
1401 if r != nil {
1402 chmod = r.Chmod
1403 lstat = r.Lstat
1404 }
1405
1406 var m1, m2 os.FileMode
1407 if err := chmod(path, 0o555); err != nil {
1408 return "chmod 0o555", err
1409 }
1410 fi, err := lstat(path)
1411 if err == nil {
1412 m1 = fi.Mode()
1413 }
1414 if err = chmod(path, 0o777); err != nil {
1415 return "chmod 0o777", err
1416 }
1417 fi, err = lstat(path)
1418 if err == nil {
1419 m2 = fi.Mode()
1420 }
1421 return fmt.Sprintf("%v %v", m1, m2), err
1422 })
1423 }
1424 }
1425
1426 func TestRootConsistencyMkdir(t *testing.T) {
1427 for _, test := range rootConsistencyTestCases {
1428 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
1429 var err error
1430 if r == nil {
1431 err = os.Mkdir(path, 0o777)
1432 } else {
1433 err = r.Mkdir(path, 0o777)
1434 }
1435 return "", err
1436 })
1437 }
1438 }
1439
1440 func TestRootConsistencyMkdirAll(t *testing.T) {
1441 for _, test := range rootConsistencyTestCases {
1442 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
1443 var err error
1444 if r == nil {
1445 err = os.MkdirAll(path, 0o777)
1446 } else {
1447 err = r.MkdirAll(path, 0o777)
1448 }
1449 return "", err
1450 })
1451 }
1452 }
1453
1454 func TestRootConsistencyRemove(t *testing.T) {
1455 for _, test := range rootConsistencyTestCases {
1456 if test.open == "." || test.open == "./" {
1457 continue
1458 }
1459 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
1460 var err error
1461 if r == nil {
1462 err = os.Remove(path)
1463 } else {
1464 err = r.Remove(path)
1465 }
1466 return "", err
1467 })
1468 }
1469 }
1470
1471 func TestRootConsistencyRemoveAll(t *testing.T) {
1472 for _, test := range rootConsistencyTestCases {
1473 if test.open == "." || test.open == "./" {
1474 continue
1475 }
1476 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
1477 var err error
1478 if r == nil {
1479 err = os.RemoveAll(path)
1480 } else {
1481 err = r.RemoveAll(path)
1482 }
1483 return "", err
1484 })
1485 }
1486 }
1487
1488 func TestRootConsistencyStat(t *testing.T) {
1489 for _, test := range rootConsistencyTestCases {
1490 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
1491 var fi os.FileInfo
1492 var err error
1493 if r == nil {
1494 fi, err = os.Stat(path)
1495 } else {
1496 fi, err = r.Stat(path)
1497 }
1498 if err != nil {
1499 return "", err
1500 }
1501 return fmt.Sprintf("name:%q size:%v mode:%v isdir:%v", fi.Name(), fi.Size(), fi.Mode(), fi.IsDir()), nil
1502 })
1503 }
1504 }
1505
1506 func TestRootConsistencyLstat(t *testing.T) {
1507 for _, test := range rootConsistencyTestCases {
1508 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
1509 var fi os.FileInfo
1510 var err error
1511 if r == nil {
1512 fi, err = os.Lstat(path)
1513 } else {
1514 fi, err = r.Lstat(path)
1515 }
1516 if err != nil {
1517 return "", err
1518 }
1519 return fmt.Sprintf("name:%q size:%v mode:%v isdir:%v", fi.Name(), fi.Size(), fi.Mode(), fi.IsDir()), nil
1520 })
1521 }
1522 }
1523
1524 func TestRootConsistencyReadlink(t *testing.T) {
1525 for _, test := range rootConsistencyTestCases {
1526 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
1527 if r == nil {
1528 return os.Readlink(path)
1529 } else {
1530 return r.Readlink(path)
1531 }
1532 })
1533 }
1534 }
1535
1536 func TestRootConsistencyRename(t *testing.T) {
1537 testRootConsistencyMove(t, true)
1538 }
1539
1540 func TestRootConsistencyLink(t *testing.T) {
1541 testenv.MustHaveLink(t)
1542 testRootConsistencyMove(t, false)
1543 }
1544
1545 func testRootConsistencyMove(t *testing.T, rename bool) {
1546 if runtime.GOOS == "plan9" {
1547
1548 t.Skip("Plan 9 does not support cross-directory renames")
1549 }
1550
1551
1552
1553 for _, name := range []string{"from", "to"} {
1554 t.Run(name, func(t *testing.T) {
1555 for _, test := range rootConsistencyTestCases {
1556 if runtime.GOOS == "windows" {
1557
1558
1559
1560
1561 if test.open == "." || test.open == "./" {
1562 continue
1563 }
1564 }
1565
1566 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
1567 var move func(oldname, newname string) error
1568 switch {
1569 case rename && r == nil:
1570 move = os.Rename
1571 case rename && r != nil:
1572 move = r.Rename
1573 case !rename && r == nil:
1574 move = os.Link
1575 case !rename && r != nil:
1576 move = r.Link
1577 }
1578 lstat := os.Lstat
1579 if r != nil {
1580 lstat = r.Lstat
1581 }
1582
1583 otherPath := "other"
1584 if r == nil {
1585 otherPath = filepath.Join(t.TempDir(), otherPath)
1586 }
1587
1588 var srcPath, dstPath string
1589 if name == "from" {
1590 srcPath = path
1591 dstPath = otherPath
1592 } else {
1593 srcPath = otherPath
1594 dstPath = path
1595 }
1596
1597 if !rename {
1598
1599
1600
1601
1602
1603
1604
1605 fi, err := lstat(srcPath)
1606 if err == nil && fi.Mode()&os.ModeSymlink != 0 {
1607 return "", nil
1608 }
1609 }
1610
1611 if err := move(srcPath, dstPath); err != nil {
1612 return "", err
1613 }
1614 fi, err := lstat(dstPath)
1615 if err != nil {
1616 t.Errorf("stat(%q) after successful copy: %v", dstPath, err)
1617 return "stat error", err
1618 }
1619 return fmt.Sprintf("name:%q size:%v mode:%v isdir:%v", fi.Name(), fi.Size(), fi.Mode(), fi.IsDir()), nil
1620 })
1621 }
1622 })
1623 }
1624 }
1625
1626 func TestRootConsistencySymlink(t *testing.T) {
1627 testenv.MustHaveSymlink(t)
1628 for _, test := range rootConsistencyTestCases {
1629 test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
1630 const target = "linktarget"
1631 var err error
1632 var got string
1633 if r == nil {
1634 err = os.Symlink(target, path)
1635 got, _ = os.Readlink(target)
1636 } else {
1637 err = r.Symlink(target, path)
1638 got, _ = r.Readlink(target)
1639 }
1640 return got, err
1641 })
1642 }
1643 }
1644
1645 func TestRootRenameAfterOpen(t *testing.T) {
1646 switch runtime.GOOS {
1647 case "windows":
1648 t.Skip("renaming open files not supported on " + runtime.GOOS)
1649 case "js", "plan9":
1650 t.Skip("openat not supported on " + runtime.GOOS)
1651 case "wasip1":
1652 if os.Getenv("GOWASIRUNTIME") == "wazero" {
1653 t.Skip("wazero does not track renamed directories")
1654 }
1655 }
1656
1657 dir := t.TempDir()
1658
1659
1660 if err := os.Mkdir(filepath.Join(dir, "a"), 0o777); err != nil {
1661 t.Fatal(err)
1662 }
1663 dirf, err := os.OpenRoot(filepath.Join(dir, "a"))
1664 if err != nil {
1665 t.Fatal(err)
1666 }
1667 defer dirf.Close()
1668
1669
1670 if err := os.Rename(filepath.Join(dir, "a"), filepath.Join(dir, "b")); err != nil {
1671 t.Fatal(err)
1672 }
1673 if err := os.WriteFile(filepath.Join(dir, "b/f"), []byte("hello"), 0o666); err != nil {
1674 t.Fatal(err)
1675 }
1676
1677
1678 f, err := dirf.OpenFile("f", os.O_RDONLY, 0)
1679 if err != nil {
1680 t.Fatalf("reading file after renaming parent: %v", err)
1681 }
1682 defer f.Close()
1683 b, err := io.ReadAll(f)
1684 if err != nil {
1685 t.Fatal(err)
1686 }
1687 if got, want := string(b), "hello"; got != want {
1688 t.Fatalf("file contents: %q, want %q", got, want)
1689 }
1690
1691
1692 if got, want := f.Name(), dirf.Name()+string(os.PathSeparator)+"f"; got != want {
1693 t.Errorf("f.Name() = %q, want %q", got, want)
1694 }
1695 }
1696
1697 func TestRootNonPermissionMode(t *testing.T) {
1698 r, err := os.OpenRoot(t.TempDir())
1699 if err != nil {
1700 t.Fatal(err)
1701 }
1702 defer r.Close()
1703 if _, err := r.OpenFile("file", os.O_RDWR|os.O_CREATE, 0o1777); err == nil {
1704 t.Errorf("r.OpenFile(file, O_RDWR|O_CREATE, 0o1777) succeeded; want error")
1705 }
1706 if err := r.Mkdir("file", 0o1777); err == nil {
1707 t.Errorf("r.Mkdir(file, 0o1777) succeeded; want error")
1708 }
1709 }
1710
1711 func TestRootUseAfterClose(t *testing.T) {
1712 r, err := os.OpenRoot(t.TempDir())
1713 if err != nil {
1714 t.Fatal(err)
1715 }
1716 r.Close()
1717 for _, test := range []struct {
1718 name string
1719 f func(r *os.Root, filename string) error
1720 }{{
1721 name: "Open",
1722 f: func(r *os.Root, filename string) error {
1723 _, err := r.Open(filename)
1724 return err
1725 },
1726 }, {
1727 name: "Create",
1728 f: func(r *os.Root, filename string) error {
1729 _, err := r.Create(filename)
1730 return err
1731 },
1732 }, {
1733 name: "OpenFile",
1734 f: func(r *os.Root, filename string) error {
1735 _, err := r.OpenFile(filename, os.O_RDWR, 0o666)
1736 return err
1737 },
1738 }, {
1739 name: "OpenRoot",
1740 f: func(r *os.Root, filename string) error {
1741 _, err := r.OpenRoot(filename)
1742 return err
1743 },
1744 }, {
1745 name: "Mkdir",
1746 f: func(r *os.Root, filename string) error {
1747 return r.Mkdir(filename, 0o777)
1748 },
1749 }} {
1750 err := test.f(r, "target")
1751 pe, ok := err.(*os.PathError)
1752 if !ok || pe.Path != "target" || pe.Err != os.ErrClosed {
1753 t.Errorf(`r.%v = %v; want &PathError{Path: "target", Err: ErrClosed}`, test.name, err)
1754 }
1755 }
1756 }
1757
1758 func TestRootConcurrentClose(t *testing.T) {
1759 r, err := os.OpenRoot(t.TempDir())
1760 if err != nil {
1761 t.Fatal(err)
1762 }
1763 ch := make(chan error, 1)
1764 go func() {
1765 defer close(ch)
1766 first := true
1767 for {
1768 f, err := r.OpenFile("file", os.O_RDWR|os.O_CREATE, 0o666)
1769 if err != nil {
1770 ch <- err
1771 return
1772 }
1773 if first {
1774 ch <- nil
1775 first = false
1776 }
1777 f.Close()
1778 if runtime.GOARCH == "wasm" {
1779
1780 runtime.Gosched()
1781 }
1782 }
1783 }()
1784 if err := <-ch; err != nil {
1785 t.Errorf("OpenFile: %v, want success", err)
1786 }
1787 r.Close()
1788 if err := <-ch; !errors.Is(err, os.ErrClosed) {
1789 t.Errorf("OpenFile: %v, want ErrClosed", err)
1790 }
1791 }
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805 func TestRootRaceRenameDir(t *testing.T) {
1806 dir := t.TempDir()
1807 r, err := os.OpenRoot(dir)
1808 if err != nil {
1809 t.Fatal(err)
1810 }
1811 defer r.Close()
1812
1813 const depth = 4
1814
1815 os.MkdirAll(dir+"/base/"+strings.Repeat("/a", depth), 0o777)
1816
1817 path := "base/" + strings.Repeat("a/", depth) + strings.Repeat("../", depth) + "a/f"
1818 os.WriteFile(dir+"/f", []byte("secret"), 0o666)
1819 os.WriteFile(dir+"/base/a/f", []byte("public"), 0o666)
1820
1821
1822 const tries = 10
1823 var total time.Duration
1824 for range tries {
1825 start := time.Now()
1826 f, err := r.Open(path)
1827 if err != nil {
1828 t.Fatal(err)
1829 }
1830 b, err := io.ReadAll(f)
1831 if err != nil {
1832 t.Fatal(err)
1833 }
1834 if string(b) != "public" {
1835 t.Fatalf("read %q, want %q", b, "public")
1836 }
1837 f.Close()
1838 total += time.Since(start)
1839 }
1840 avg := total / tries
1841
1842
1843 for range 100 {
1844
1845 gotc := make(chan []byte)
1846 go func() {
1847 f, err := r.Open(path)
1848 if err != nil {
1849 gotc <- nil
1850 }
1851 defer f.Close()
1852 b, _ := io.ReadAll(f)
1853 gotc <- b
1854 }()
1855
1856
1857
1858 time.Sleep(avg / 4)
1859 if err := os.Rename(dir+"/base/a", dir+"/b"); err != nil {
1860
1861
1862 switch runtime.GOOS {
1863 case "windows", "plan9":
1864 default:
1865 t.Fatal(err)
1866 }
1867 }
1868
1869 got := <-gotc
1870 os.Rename(dir+"/b", dir+"/base/a")
1871 if len(got) > 0 && string(got) != "public" {
1872 t.Errorf("read file: %q; want error or 'public'", got)
1873 }
1874 }
1875 }
1876
1877 func TestRootSymlinkToRoot(t *testing.T) {
1878 dir := makefs(t, []string{
1879 "d/d => ..",
1880 })
1881 root, err := os.OpenRoot(dir)
1882 if err != nil {
1883 t.Fatal(err)
1884 }
1885 defer root.Close()
1886 if err := root.Mkdir("d/d/new", 0777); err != nil {
1887 t.Fatal(err)
1888 }
1889 f, err := root.Open("d/d")
1890 if err != nil {
1891 t.Fatal(err)
1892 }
1893 defer f.Close()
1894 names, err := f.Readdirnames(-1)
1895 if err != nil {
1896 t.Fatal(err)
1897 }
1898 slices.Sort(names)
1899 if got, want := names, []string{"d", "new"}; !slices.Equal(got, want) {
1900 t.Errorf("root contains: %q, want %q", got, want)
1901 }
1902 }
1903
1904 func TestOpenInRoot(t *testing.T) {
1905 dir := makefs(t, []string{
1906 "file",
1907 "link => ../ROOT/file",
1908 })
1909 f, err := os.OpenInRoot(dir, "file")
1910 if err != nil {
1911 t.Fatalf("OpenInRoot(`file`) = %v, want success", err)
1912 }
1913 f.Close()
1914 for _, name := range []string{
1915 "link",
1916 "../ROOT/file",
1917 dir + "/file",
1918 } {
1919 f, err := os.OpenInRoot(dir, name)
1920 if err == nil {
1921 f.Close()
1922 t.Fatalf("OpenInRoot(%q) = nil, want error", name)
1923 }
1924 }
1925 }
1926
1927 func TestRootRemoveDot(t *testing.T) {
1928 dir := t.TempDir()
1929 root, err := os.OpenRoot(dir)
1930 if err != nil {
1931 t.Fatal(err)
1932 }
1933 defer root.Close()
1934 if err := root.Remove("."); err == nil {
1935 t.Errorf(`root.Remove(".") = %v, want error`, err)
1936 }
1937 if err := root.RemoveAll("."); err == nil {
1938 t.Errorf(`root.RemoveAll(".") = %v, want error`, err)
1939 }
1940 if _, err := os.Stat(dir); err != nil {
1941 t.Error(`root.Remove(All)?(".") removed the root`)
1942 }
1943 }
1944
1945 func TestRootWriteReadFile(t *testing.T) {
1946 dir := t.TempDir()
1947 root, err := os.OpenRoot(dir)
1948 if err != nil {
1949 t.Fatal(err)
1950 }
1951 defer root.Close()
1952
1953 name := "filename"
1954 want := []byte("file contents")
1955 if err := root.WriteFile(name, want, 0o666); err != nil {
1956 t.Fatalf("root.WriteFile(%q, %q, 0o666) = %v; want nil", name, want, err)
1957 }
1958
1959 got, err := root.ReadFile(name)
1960 if err != nil {
1961 t.Fatalf("root.ReadFile(%q) = %q, %v; want %q, nil", name, got, err, want)
1962 }
1963 }
1964
1965 func TestRootName(t *testing.T) {
1966 dir := t.TempDir()
1967 root, err := os.OpenRoot(dir)
1968 if err != nil {
1969 t.Fatal(err)
1970 }
1971 defer root.Close()
1972 if got, want := root.Name(), dir; got != want {
1973 t.Errorf("root.Name() = %q, want %q", got, want)
1974 }
1975
1976 f, err := root.Create("file")
1977 if err != nil {
1978 t.Fatal(err)
1979 }
1980 defer f.Close()
1981 if got, want := f.Name(), filepath.Join(dir, "file"); got != want {
1982 t.Errorf(`root.Create("file").Name() = %q, want %q`, got, want)
1983 }
1984
1985 if err := root.Mkdir("dir", 0o777); err != nil {
1986 t.Fatal(err)
1987 }
1988 subroot, err := root.OpenRoot("dir")
1989 if err != nil {
1990 t.Fatal(err)
1991 }
1992 defer subroot.Close()
1993 if got, want := subroot.Name(), filepath.Join(dir, "dir"); got != want {
1994 t.Errorf(`root.OpenRoot("dir").Name() = %q, want %q`, got, want)
1995 }
1996 }
1997
1998
1999
2000 func TestRootNoLstat(t *testing.T) {
2001 if runtime.GOARCH == "wasm" {
2002 t.Skip("wasm lacks fstatat")
2003 }
2004
2005 dir := makefs(t, []string{
2006 "subdir/",
2007 })
2008 const size = 42
2009 contents := strings.Repeat("x", size)
2010 if err := os.WriteFile(dir+"/subdir/file", []byte(contents), 0666); err != nil {
2011 t.Fatal(err)
2012 }
2013 root, err := os.OpenRoot(dir)
2014 if err != nil {
2015 t.Fatal(err)
2016 }
2017 defer root.Close()
2018
2019 test := func(name string, fn func(t *testing.T, f *os.File)) {
2020 t.Run(name, func(t *testing.T) {
2021 os.SetStatHook(t, func(f *os.File, name string) (os.FileInfo, error) {
2022 if f == nil {
2023 t.Errorf("unexpected Lstat(%q)", name)
2024 }
2025 return nil, nil
2026 })
2027 f, err := root.Open("subdir")
2028 if err != nil {
2029 t.Fatal(err)
2030 }
2031 defer f.Close()
2032 fn(t, f)
2033 })
2034 }
2035
2036 checkFileInfo := func(t *testing.T, fi fs.FileInfo) {
2037 t.Helper()
2038 if got, want := fi.Name(), "file"; got != want {
2039 t.Errorf("FileInfo.Name() = %q, want %q", got, want)
2040 }
2041 if got, want := fi.Size(), int64(size); got != want {
2042 t.Errorf("FileInfo.Size() = %v, want %v", got, want)
2043 }
2044 }
2045 checkDirEntry := func(t *testing.T, d fs.DirEntry) {
2046 t.Helper()
2047 if got, want := d.Name(), "file"; got != want {
2048 t.Errorf("DirEntry.Name() = %q, want %q", got, want)
2049 }
2050 if got, want := d.IsDir(), false; got != want {
2051 t.Errorf("DirEntry.IsDir() = %v, want %v", got, want)
2052 }
2053 fi, err := d.Info()
2054 if err != nil {
2055 t.Fatalf("DirEntry.Info() = _, %v", err)
2056 }
2057 checkFileInfo(t, fi)
2058 }
2059
2060 test("Stat", func(t *testing.T, subdir *os.File) {
2061 fi, err := subdir.Stat()
2062 if err != nil {
2063 t.Fatal(err)
2064 }
2065 if !fi.IsDir() {
2066 t.Fatalf(`Open("subdir").Stat().IsDir() = false, want true`)
2067 }
2068 })
2069
2070 test("ReadDirEntry", func(t *testing.T, subdir *os.File) {
2071 dirents, err := subdir.ReadDir(-1)
2072 if err != nil {
2073 t.Fatal(err)
2074 }
2075 if len(dirents) != 1 {
2076 t.Fatalf(`Open("subdir").ReadDir(-1) = {%v}, want {file}`, dirents)
2077 }
2078 checkDirEntry(t, dirents[0])
2079 })
2080
2081 test("ReadFileInfo", func(t *testing.T, subdir *os.File) {
2082 fileinfos, err := subdir.Readdir(-1)
2083 if err != nil {
2084 t.Fatal(err)
2085 }
2086 if len(fileinfos) != 1 {
2087 t.Fatalf(`Open("subdir").Readdir(-1) = {%v}, want {file}`, fileinfos)
2088 }
2089 checkFileInfo(t, fileinfos[0])
2090 })
2091
2092 test("Readdirnames", func(t *testing.T, subdir *os.File) {
2093 names, err := subdir.Readdirnames(-1)
2094 if err != nil {
2095 t.Fatal(err)
2096 }
2097 if got, want := names, []string{"file"}; !slices.Equal(got, want) {
2098 t.Fatalf(`Open("subdir").Readdirnames(-1) = %q, want %q`, got, want)
2099 }
2100 })
2101 }
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115 type rootMultiTest struct {
2116
2117
2118
2119 dir string
2120
2121
2122 root *os.Root
2123
2124
2125
2126 source testFileDesc
2127 target testFileDesc
2128
2129
2130
2131 sourcePath string
2132 targetPath string
2133
2134 sourceInfo os.FileInfo
2135 targetInfo os.FileInfo
2136
2137
2138 op string
2139 }
2140
2141 var testVerbose = flag.Bool("verbose", false, "verbose")
2142
2143
2144
2145 var errSkipRootConsistencyCheck = errors.New("skip root consistency check")
2146
2147
2148
2149 func runRootMultiTest(t *testing.T, f func(*testing.T, *rootMultiTest) (string, error)) {
2150 for target := range allTestFileDescs() {
2151 t.Run(target.String(), func(t *testing.T) {
2152 var source testFileDesc
2153 runRootMultiTestDescs(t, source, target, f)
2154 })
2155 }
2156 }
2157
2158
2159
2160
2161 func runRootMultiTest2(t *testing.T, f func(*testing.T, *rootMultiTest) (string, error)) {
2162
2163
2164
2165 isSimple := func(desc testFileDesc) bool {
2166 if desc.ref.template != "BASE" {
2167 return false
2168 }
2169 if desc.kind == testFileSymlink && desc.target.ref.template != "BASE" {
2170 return false
2171 }
2172 return true
2173 }
2174 for source := range allTestFileDescs() {
2175 for target := range allTestFileDescs() {
2176 if !*rootComprehensive && !isSimple(source) && !isSimple(target) {
2177 continue
2178 }
2179 name := fmt.Sprintf("%s_to_%s", source, target)
2180 t.Run(name, func(t *testing.T) {
2181 runRootMultiTestDescs(t, source, target, f)
2182 })
2183 }
2184 }
2185 }
2186
2187
2188
2189
2190
2191 func (test *rootMultiTest) setOp(format string, a ...any) {
2192 if test.root != nil {
2193 test.op = "root."
2194 } else {
2195 test.op = "os."
2196 }
2197 test.op += fmt.Sprintf(format, a...)
2198 }
2199
2200 var errAny = errors.New("any error")
2201
2202 func (test *rootMultiTest) errorf(t *testing.T, format string, args ...any) {
2203 t.Errorf("%v:", test.op)
2204 t.Fatalf(" "+format, args...)
2205 }
2206
2207
2208
2209 func (test *rootMultiTest) wantError(t *testing.T, got, want error) {
2210 t.Helper()
2211 if errors.Is(got, want) || (got != nil && want == errAny) {
2212 return
2213 }
2214 t.Fatalf("%v:\ngot error: %v\nwant error: %v", test.op, got, want)
2215 }
2216
2217 func runRootMultiTestDescs(t *testing.T, source, target testFileDesc, f func(*testing.T, *rootMultiTest) (string, error)) {
2218 rootTest := newRootTest(t, source, target, true)
2219 osTest := newRootTest(t, source, target, false)
2220
2221 initialContent := dirTreeContents(t, rootTest.dir)
2222 t.Cleanup(func() {
2223 if t.Failed() {
2224 t.Log("Initial directory contents:")
2225 for _, line := range initialContent {
2226 t.Logf(" %v", line)
2227 }
2228 }
2229 })
2230
2231 rootResult, rootErr := f(t, rootTest)
2232
2233 if runtime.GOOS == "darwin" {
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250 if rootTest.source.anySlashSuffix() || rootTest.target.anySlashSuffix() {
2251 return
2252 }
2253 }
2254
2255 if runtime.GOOS == "wasip1" || runtime.GOOS == "js" {
2256
2257
2258 if rootTest.source.anySlashSuffix() || rootTest.target.anySlashSuffix() {
2259 return
2260 }
2261 }
2262
2263 osResult, osErr := f(t, osTest)
2264
2265 t.Cleanup(func() {
2266 if t.Failed() || !*testVerbose {
2267 return
2268 }
2269 rootContent := dirTreeContents(t, rootTest.dir)
2270 osContent := dirTreeContents(t, osTest.dir)
2271 t.Log("Initial directory contents:")
2272 for _, line := range initialContent {
2273 t.Logf(" %v", line)
2274 }
2275 t.Logf("%v:", rootTest.op)
2276 t.Logf(" result: %v", rootResult)
2277 t.Logf(" error: %v", rootErr)
2278 for _, line := range rootContent {
2279 t.Logf(" %v", line)
2280 }
2281 t.Logf("%v:", osTest.op)
2282 t.Logf(" result: %v", osResult)
2283 t.Logf(" error: %v", osErr)
2284 for _, line := range osContent {
2285 t.Logf(" %v", line)
2286 }
2287 })
2288
2289 if errors.Is(rootErr, os.ErrPathEscapes) {
2290
2291
2292 return
2293 }
2294
2295 if rootErr == errSkipRootConsistencyCheck || osErr == errSkipRootConsistencyCheck {
2296 return
2297 }
2298
2299
2300
2301 if rootResult != osResult {
2302 t.Errorf("inconsistent results in/out of root")
2303 t.Errorf("%v:", rootTest.op)
2304 t.Errorf(" result: %v", rootResult)
2305 t.Errorf("%v:", osTest.op)
2306 t.Errorf(" result: %v", osResult)
2307 }
2308 if (rootErr == nil) != (osErr == nil) {
2309 t.Errorf("inconsistent errors in/out of root")
2310 t.Errorf("%v:", rootTest.op)
2311 t.Errorf(" error: %v", rootErr)
2312 t.Errorf("%v:", osTest.op)
2313 t.Errorf(" error: %v", osErr)
2314 }
2315
2316
2317 rootContent := dirTreeContents(t, rootTest.dir)
2318 osContent := dirTreeContents(t, osTest.dir)
2319 if !slices.Equal(rootContent, osContent) {
2320 t.Errorf("inconsistent filesystem after running in/out of root")
2321 t.Errorf("%v:", rootTest.op)
2322 for _, line := range rootContent {
2323 t.Errorf(" %v", line)
2324 }
2325 t.Errorf("%v:", osTest.op)
2326 for _, line := range osContent {
2327 t.Errorf(" %v", line)
2328 }
2329 }
2330 }
2331
2332 func newRootTest(t *testing.T, source, target testFileDesc, inRoot bool) *rootMultiTest {
2333 dir := makefs(t, []string{
2334 "DIR/",
2335 })
2336 var root *os.Root
2337 if inRoot {
2338 var err error
2339 root, err = os.OpenRoot(dir)
2340 if err != nil {
2341 t.Fatal(err)
2342 }
2343 t.Cleanup(func() {
2344 root.Close()
2345 })
2346 }
2347 test := &rootMultiTest{
2348 dir: dir,
2349 root: root,
2350 source: source,
2351 target: target,
2352 }
2353 createFile := func(name string, desc testFileDesc) (path string, fi os.FileInfo) {
2354 if desc.kind == testFileUnused {
2355 return "", nil
2356 }
2357 fi = desc.create(t, dir, name, name)
2358 path = desc.ref.path(dir, name)
2359 if !inRoot && !filepath.IsAbs(path) {
2360 path = dir + "/" + path
2361 }
2362 return path, fi
2363 }
2364 test.sourcePath, test.sourceInfo = createFile("source", source)
2365 test.targetPath, test.targetInfo = createFile("target", target)
2366 return test
2367 }
2368
2369
2370 type testFileKind int
2371
2372 const (
2373 testFileUnused = testFileKind(iota)
2374 testFileAbsent
2375 testFileFile
2376 testFileDir
2377 testFileSymlink
2378 testFileMax
2379
2380
2381
2382 testFileError
2383 )
2384
2385 func (kind testFileKind) String() string {
2386 switch kind {
2387 case testFileUnused:
2388 return "unused"
2389 case testFileAbsent:
2390 return "absent"
2391 case testFileFile:
2392 return "file"
2393 case testFileDir:
2394 return "dir"
2395 case testFileSymlink:
2396 return "symlink"
2397 case testFileError:
2398 return "error"
2399 default:
2400 return fmt.Sprintf("testFileKind(%d)", kind)
2401 }
2402 }
2403
2404
2405
2406
2407
2408 type testFileRef struct {
2409
2410
2411
2412 name string
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422 template string
2423
2424
2425 escapes bool
2426 }
2427
2428 var testFileRefs = []testFileRef{
2429 {escapes: false, name: "b", template: "BASE"},
2430 {escapes: false, name: "bs", template: "BASE/"},
2431 {escapes: false, name: "dpb", template: "DIR/../BASE"},
2432 {escapes: false, name: "dpbs", template: "DIR/../BASE/"},
2433 {escapes: true, name: "prb", template: "../ROOT/BASE"},
2434 {escapes: true, name: "prbs", template: "../ROOT/BASE/"},
2435 {escapes: true, name: "srb", template: "/ROOT/BASE"},
2436 {escapes: true, name: "srbs", template: "/ROOT/BASE/"},
2437 }
2438
2439
2440
2441 var testFileLimitedRefs = testFileRefs[0:2]
2442
2443
2444
2445
2446
2447 func (ref testFileRef) path(dir, base string) string {
2448 p := ref.template
2449 p = strings.ReplaceAll(p, "BASE", base)
2450 if trim, ok := strings.CutPrefix(p, "/ROOT"); ok {
2451 p = dir + trim
2452 }
2453 return p
2454 }
2455
2456
2457 func (ref testFileRef) hasSlashSuffix() bool {
2458 return strings.HasSuffix(ref.template, "/")
2459 }
2460
2461
2462
2463
2464
2465
2466
2467 type testFileDesc struct {
2468 kind testFileKind
2469 ref testFileRef
2470 target *testFileDesc
2471 }
2472
2473 var rootComprehensive = flag.Bool("root_comprehensive", false,
2474 "run many more os.Root test variations (slow, uncertain value)")
2475
2476
2477 func allTestFileDescs() iter.Seq[testFileDesc] {
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504 return func(yield func(testFileDesc) bool) {
2505
2506 for _, ref := range testFileRefs {
2507 for kind := range testFileMax {
2508 if kind == testFileUnused || kind == testFileSymlink {
2509 continue
2510 }
2511 desc := testFileDesc{
2512 kind: kind,
2513 ref: ref,
2514 }
2515 if !yield(desc) {
2516 return
2517 }
2518 }
2519 }
2520
2521
2522 refs := testFileRefs
2523 if !*rootComprehensive {
2524 refs = testFileLimitedRefs
2525 }
2526 for _, ref := range refs {
2527 for linkKind := range testFileMax {
2528 if linkKind == testFileUnused || linkKind == testFileSymlink {
2529 continue
2530 }
2531
2532 linkRefs := testFileRefs
2533 if !*rootComprehensive && linkKind != testFileFile && linkKind != testFileDir {
2534 linkRefs = testFileLimitedRefs
2535 }
2536 for _, linkRef := range linkRefs {
2537 desc := testFileDesc{
2538 kind: testFileSymlink,
2539 ref: ref,
2540 target: &testFileDesc{
2541 kind: linkKind,
2542 ref: linkRef,
2543 },
2544 }
2545 if !yield(desc) {
2546 return
2547 }
2548 }
2549 }
2550 }
2551 }
2552 }
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565 func (desc testFileDesc) String() string {
2566 s := desc.ref.name + strings.ToUpper(desc.kind.String()[:1])
2567 if desc.kind == testFileSymlink {
2568 s += desc.target.String()
2569 }
2570 return s
2571 }
2572
2573
2574
2575 func (desc testFileDesc) escapes() bool {
2576 if desc.ref.escapes {
2577 return true
2578 }
2579 if desc.kind == testFileSymlink {
2580 return desc.target.escapes()
2581 }
2582 return false
2583 }
2584
2585 func (desc testFileDesc) lescapes() bool {
2586 if desc.ref.escapes {
2587 return true
2588 }
2589 if runtime.GOOS == "windows" {
2590
2591
2592
2593 return false
2594 }
2595 if desc.ref.hasSlashSuffix() && desc.kind == testFileSymlink {
2596 return desc.target.escapes()
2597 }
2598 return false
2599 }
2600
2601
2602 func (desc testFileDesc) finalKind() testFileKind {
2603 if desc.kind == testFileSymlink {
2604 return desc.target.finalKind()
2605 }
2606 return desc.kind
2607 }
2608
2609 func (desc testFileDesc) lfinalKind() testFileKind {
2610 switch runtime.GOOS {
2611 case "windows":
2612 if desc.ref.hasSlashSuffix() && desc.kind == testFileSymlink && desc.target.kind != testFileDir {
2613 return testFileError
2614 }
2615 default:
2616 if desc.ref.hasSlashSuffix() && desc.kind == testFileSymlink {
2617 return desc.target.finalKind()
2618 }
2619 }
2620 return desc.kind
2621 }
2622
2623 func (desc testFileDesc) isError() bool {
2624 if runtime.GOOS == "js" {
2625 return false
2626 }
2627 var isError func(desc testFileDesc, hasSuffix bool) bool
2628 isError = func(desc testFileDesc, hasSuffix bool) bool {
2629 if desc.ref.escapes {
2630 return false
2631 }
2632 if desc.ref.hasSlashSuffix() {
2633 hasSuffix = true
2634 }
2635 switch desc.kind {
2636 case testFileDir:
2637 return false
2638 case testFileSymlink:
2639 if runtime.GOOS == "windows" && hasSuffix && desc.target.kind != testFileDir {
2640 return true
2641 }
2642 return isError(*desc.target, hasSuffix)
2643 default:
2644 return hasSuffix
2645 }
2646 }
2647 return isError(desc, false)
2648 }
2649
2650 func (desc testFileDesc) isSymlinkToDir() bool {
2651 if desc.kind != testFileSymlink {
2652 return false
2653 }
2654 if desc.ref.escapes {
2655 return false
2656 }
2657 if desc.finalKind() == testFileDir {
2658 return true
2659 }
2660 return false
2661 }
2662
2663
2664
2665
2666 func (desc testFileDesc) anySlashSuffix() bool {
2667 name := desc.ref.template
2668 if len(name) > 0 && os.IsPathSeparator(name[len(name)-1]) {
2669 return true
2670 }
2671 if desc.kind == testFileSymlink {
2672 return desc.target.anySlashSuffix()
2673 }
2674 return false
2675 }
2676
2677
2678 func (desc testFileDesc) slashSuffix() bool {
2679 name := desc.ref.template
2680 if len(name) > 0 && os.IsPathSeparator(name[len(name)-1]) {
2681 return true
2682 }
2683 return false
2684 }
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696 func (desc testFileDesc) create(t *testing.T, dir, base, token string) (fi os.FileInfo) {
2697 path := filepath.Join(dir, base)
2698 switch desc.kind {
2699 case testFileAbsent:
2700
2701 case testFileFile:
2702
2703 if err := os.WriteFile(path, []byte(token), 0o666); err != nil {
2704 t.Fatal(err)
2705 }
2706 case testFileDir:
2707
2708
2709
2710 if err := os.Mkdir(path, 0o777); err != nil {
2711 t.Fatal(err)
2712 }
2713 case testFileSymlink:
2714
2715 if runtime.GOOS == "plan9" {
2716 t.Skip("symlinks not supported on " + runtime.GOOS)
2717 }
2718 linktarget := desc.target.ref.path(dir, "s_"+base)
2719 if runtime.GOOS == "wasip1" && filepath.IsAbs(linktarget) {
2720 t.Skip("absolute link targets not supported on " + runtime.GOOS)
2721 }
2722 fi = desc.target.create(t, dir, "s_"+base, token)
2723 if err := os.Symlink(linktarget, path); err != nil {
2724 t.Fatal(err)
2725 }
2726 default:
2727 t.Fatalf("can't create file of kind: %v", desc.kind)
2728 }
2729 if desc.kind == testFileFile || desc.kind == testFileDir {
2730 var err error
2731 fi, err = os.Lstat(path)
2732 if err != nil {
2733 t.Fatal(err)
2734 }
2735 }
2736 return fi
2737 }
2738
2739
2740
2741
2742
2743
2744 func (test *rootMultiTest) describeFile(t *testing.T, f *os.File) string {
2745 if f == nil {
2746 return ""
2747 }
2748 fi, err := f.Stat()
2749 if err != nil {
2750 t.Fatal(err)
2751 }
2752 switch {
2753 case os.SameFile(fi, test.sourceInfo):
2754 return "source"
2755 case os.SameFile(fi, test.targetInfo):
2756 return "target"
2757 default:
2758 return "unknown file"
2759 }
2760 }
2761
2762
2763
2764
2765
2766
2767
2768 func dirTreeContents(t *testing.T, dir string) (contents []string) {
2769 root, err := os.OpenRoot(dir)
2770 if err != nil {
2771 t.Fatal(err)
2772 }
2773 defer root.Close()
2774 fs.WalkDir(root.FS(), ".", func(path string, d fs.DirEntry, err error) error {
2775 if path == "." {
2776 return nil
2777 }
2778 info, err := d.Info()
2779 if err != nil {
2780 t.Fatal(err)
2781 }
2782 ent := info.Mode().String() + " " + path
2783 switch d.Type() {
2784 case fs.ModeDir:
2785 ent += "/"
2786 case fs.ModeSymlink:
2787 target, err := root.Readlink(path)
2788 if err != nil {
2789 t.Fatal(err)
2790 }
2791 if filepath.IsAbs(target) {
2792 relPath, err := filepath.Rel(dir, target)
2793 if err == nil && filepath.IsLocal(relPath) {
2794 target = "/.../" + relPath
2795 }
2796 }
2797 ent += " => " + target
2798 default:
2799 f, err := root.Open(path)
2800 if err != nil {
2801 ent += " (unreadable)"
2802 } else {
2803 content, err := io.ReadAll(f)
2804 if err != nil {
2805 t.Fatal(err)
2806 }
2807 ent += fmt.Sprintf(" %q", content)
2808 }
2809 }
2810 contents = append(contents, ent)
2811 return nil
2812 })
2813 return contents
2814 }
2815
2816
2817
2818
2819
2820 func TestRootMultiOpen(t *testing.T) {
2821 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834 var open = os.Open
2835 if test.root != nil {
2836 open = test.root.Open
2837 }
2838
2839 test.setOp("Open(%q)", test.targetPath)
2840 f, gotErr := open(test.targetPath)
2841 if gotErr == nil {
2842 defer f.Close()
2843 }
2844
2845
2846
2847
2848
2849
2850 got := test.describeFile(t, f)
2851
2852 switch {
2853 case test.root != nil && test.target.escapes():
2854
2855 test.wantError(t, gotErr, os.ErrPathEscapes)
2856 case test.target.finalKind() == testFileAbsent:
2857
2858 test.wantError(t, gotErr, errAny)
2859 case test.target.anySlashSuffix():
2860
2861
2862
2863
2864
2865 default:
2866
2867 test.wantError(t, gotErr, nil)
2868 if want := "target"; got != want {
2869 t.Fatalf("opened file %q, want %q", got, want)
2870 }
2871 }
2872
2873
2874
2875
2876 return got, gotErr
2877 })
2878 }
2879
2880 func TestRootMultiChmod(t *testing.T) {
2881 if runtime.GOOS == "wasip1" {
2882 t.Skip("Chmod not supported on " + runtime.GOOS)
2883 }
2884 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
2885 var (
2886 chmod = os.Chmod
2887 stat = os.Stat
2888 lstat = os.Lstat
2889 )
2890 if test.root != nil {
2891 chmod = test.root.Chmod
2892 stat = test.root.Stat
2893 lstat = test.root.Lstat
2894 }
2895
2896
2897
2898
2899
2900
2901
2902 wantMode := os.FileMode(0o500)
2903 if runtime.GOOS == "windows" {
2904
2905
2906
2907 wantMode = 0o444
2908 }
2909 t.Cleanup(func() {
2910 chmod(test.targetPath, 0o700)
2911 })
2912
2913 test.setOp("Chmod(%q, %o)", test.targetPath, wantMode)
2914 gotErr := chmod(test.targetPath, wantMode)
2915
2916 escapes := test.target.escapes()
2917 targetKind := test.target.finalKind()
2918 if runtime.GOOS == "windows" {
2919
2920
2921 stat = lstat
2922 escapes = test.target.ref.escapes
2923 targetKind = test.target.kind
2924 }
2925
2926 var gotMode fs.FileMode
2927 switch {
2928 case test.root != nil && escapes:
2929 test.wantError(t, gotErr, os.ErrPathEscapes)
2930 case targetKind == testFileAbsent:
2931 test.wantError(t, gotErr, errAny)
2932 case test.target.anySlashSuffix():
2933
2934 default:
2935 test.wantError(t, gotErr, nil)
2936
2937 fi, err := stat(test.targetPath)
2938 if err != nil {
2939 t.Fatalf("could not stat target: %v", err)
2940 }
2941 if runtime.GOOS == "windows" && !fi.Mode().IsRegular() {
2942
2943 break
2944 }
2945
2946 gotMode = fi.Mode() & fs.ModePerm
2947 if gotMode != wantMode {
2948 t.Fatalf("file %q:\ngot mode: %v\nwant mode: %v", test.targetPath, gotMode, wantMode)
2949 }
2950 }
2951
2952 if runtime.GOOS == "windows" && test.root == nil && gotErr != nil {
2953
2954
2955
2956
2957 return "", errSkipRootConsistencyCheck
2958 }
2959
2960 return gotMode.String(), gotErr
2961 })
2962 }
2963
2964 func TestRootMultiCreate(t *testing.T) {
2965 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
2966 var create = os.Create
2967 if test.root != nil {
2968 create = test.root.Create
2969 }
2970
2971 test.setOp("Create(%q)", test.targetPath)
2972 f, gotErr := create(test.targetPath)
2973 if gotErr == nil {
2974 defer f.Close()
2975 }
2976
2977 switch {
2978 case test.target.isError():
2979 test.wantError(t, gotErr, errAny)
2980 case runtime.GOOS == "windows" && test.target.isSymlinkToDir():
2981
2982
2983 test.wantError(t, gotErr, errAny)
2984 case test.root != nil && test.target.escapes():
2985
2986 test.wantError(t, gotErr, os.ErrPathEscapes)
2987 default:
2988 }
2989
2990 return "", gotErr
2991 })
2992 }
2993
2994 func TestRootMultiLink(t *testing.T) {
2995 if runtime.GOOS == "wasip1" {
2996 switch os.Getenv("GOWASIRUNTIME") {
2997 case "", "wasmtime":
2998
2999
3000 t.Skip("test seems to tickle a wasmtime bug")
3001 }
3002 }
3003 testenv.MustHaveLink(t)
3004 runRootMultiTest2(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3005 var (
3006 rename = os.Link
3007 )
3008 if test.root != nil {
3009 rename = test.root.Link
3010 }
3011
3012 test.setOp("Link(%q, %q)", test.sourcePath, test.targetPath)
3013 gotErr := rename(test.sourcePath, test.targetPath)
3014
3015 switch {
3016 case test.root != nil && test.source.lescapes():
3017 test.wantError(t, gotErr, os.ErrPathEscapes)
3018 case test.source.lfinalKind() == testFileAbsent:
3019 test.wantError(t, gotErr, errAny)
3020 case test.source.kind == testFileSymlink:
3021
3022
3023
3024 return "", errSkipRootConsistencyCheck
3025 case test.source.slashSuffix() && test.source.lfinalKind() != testFileDir:
3026 test.wantError(t, gotErr, errAny)
3027 }
3028 return "", gotErr
3029 })
3030 }
3031
3032 func TestRootMultiLstat(t *testing.T) {
3033 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3034 var (
3035 lstat = os.Lstat
3036 )
3037 if test.root != nil {
3038 lstat = test.root.Lstat
3039 }
3040
3041 test.setOp("Lstat(%q)", test.targetPath)
3042 gotStat, gotErr := lstat(test.targetPath)
3043
3044 result := ""
3045 if gotStat != nil {
3046 result = gotStat.Mode().String()
3047 }
3048
3049 escapes := test.target.lescapes()
3050 finalKind := test.target.lfinalKind()
3051 if runtime.GOOS == "windows" && test.target.ref.hasSlashSuffix() {
3052
3053
3054 escapes = test.target.escapes()
3055 finalKind = test.target.finalKind()
3056 }
3057
3058 switch {
3059 case test.root != nil && escapes:
3060 test.wantError(t, gotErr, os.ErrPathEscapes)
3061 case test.target.kind == testFileAbsent:
3062
3063 test.wantError(t, gotErr, errAny)
3064 case finalKind == testFileSymlink:
3065 test.wantError(t, gotErr, nil)
3066 if got, want := gotStat.Mode().Type(), fs.ModeSymlink; got != want {
3067 test.errorf(t, "got mode %v, want %v", got, want)
3068 }
3069 case gotErr != nil:
3070 default:
3071 if !os.SameFile(gotStat, test.targetInfo) {
3072 test.errorf(t, "stat result is not for target file; want it to be")
3073 }
3074 }
3075
3076 return result, gotErr
3077 })
3078 }
3079
3080 func TestRootMultiMkdir(t *testing.T) {
3081 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3082 var (
3083 mkdir = os.Mkdir
3084 stat = os.Stat
3085 )
3086 if test.root != nil {
3087 mkdir = test.root.Mkdir
3088 stat = test.root.Stat
3089 }
3090
3091 test.setOp("Mkdir(%q, 0o777)", test.targetPath)
3092 gotErr := mkdir(test.targetPath, 0o777)
3093
3094 switch {
3095 case test.root != nil && test.target.ref.escapes:
3096
3097 test.wantError(t, gotErr, os.ErrPathEscapes)
3098 case test.target.slashSuffix() && test.target.kind == testFileSymlink:
3099
3100
3101
3102
3103 if test.root != nil && test.target.escapes() {
3104 test.wantError(t, gotErr, errAny)
3105 }
3106 if runtime.GOOS == "openbsd" {
3107
3108
3109 return "", errSkipRootConsistencyCheck
3110 }
3111 case test.target.kind != testFileAbsent:
3112
3113 test.wantError(t, gotErr, errAny)
3114 default:
3115 test.wantError(t, gotErr, nil)
3116 fi, err := stat(test.targetPath)
3117 if err != nil {
3118 t.Fatalf("could not stat target: %v", err)
3119 }
3120 if !fi.IsDir() {
3121 t.Fatalf("%q: not a directory, expected it to be", test.targetPath)
3122 }
3123 }
3124 return "", gotErr
3125 })
3126 }
3127
3128 func TestRootMultiMkdirAllShallow(t *testing.T) {
3129 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3130 return testRootMultiMkdirAll(t, test, test.targetPath)
3131 })
3132 }
3133
3134 func TestRootMultiMkdirAllDeep(t *testing.T) {
3135 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3136 targetPath := test.targetPath
3137 if len(targetPath) > 0 && os.IsPathSeparator(targetPath[len(targetPath)-1]) {
3138 targetPath += "a/b/"
3139 } else {
3140 targetPath += "/a/b"
3141 }
3142 return testRootMultiMkdirAll(t, test, targetPath)
3143 })
3144 }
3145
3146 func testRootMultiMkdirAll(t *testing.T, test *rootMultiTest, targetPath string) (string, error) {
3147 var mkdirAll = os.MkdirAll
3148 if test.root != nil {
3149 mkdirAll = test.root.MkdirAll
3150 }
3151
3152 test.setOp("MkdirAll(%q, 0o777)", targetPath)
3153 gotErr := mkdirAll(targetPath, 0o777)
3154
3155 switch {
3156 case test.root != nil && test.target.lescapes():
3157
3158 test.wantError(t, gotErr, os.ErrPathEscapes)
3159 case test.root != nil && test.target.escapes():
3160
3161 test.wantError(t, gotErr, errAny)
3162 return "", errSkipRootConsistencyCheck
3163 case test.root != nil && test.target.kind == testFileSymlink && test.target.target.kind == testFileAbsent && targetPath != test.targetPath:
3164
3165
3166
3167
3168 return "", errSkipRootConsistencyCheck
3169 default:
3170 }
3171 return "", gotErr
3172 }
3173
3174 func TestRootMultiRename(t *testing.T) {
3175 if runtime.GOOS == "wasip1" {
3176 switch os.Getenv("GOWASIRUNTIME") {
3177 case "", "wasmtime":
3178
3179
3180 t.Skip("test seems to tickle a wasmtime bug")
3181 }
3182 }
3183 runRootMultiTest2(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3184 var (
3185 rename = os.Rename
3186 )
3187 if test.root != nil {
3188 rename = test.root.Rename
3189 }
3190
3191
3192
3193 test.setOp("Rename(%q, %q)", test.sourcePath, test.targetPath)
3194 gotErr := rename(test.sourcePath, test.targetPath)
3195
3196 if runtime.GOOS == "windows" &&
3197 (test.source.finalKind() != test.target.finalKind() || test.source.kind == testFileSymlink || test.target.kind == testFileSymlink) {
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207 return "", errSkipRootConsistencyCheck
3208 }
3209
3210 switch {
3211 case test.root != nil && test.source.lescapes():
3212 test.wantError(t, gotErr, os.ErrPathEscapes)
3213 case test.source.lfinalKind() == testFileAbsent:
3214 test.wantError(t, gotErr, errAny)
3215 case test.source.slashSuffix() && test.source.lfinalKind() != testFileDir && runtime.GOOS != "js":
3216 test.wantError(t, gotErr, errAny)
3217 case test.root != nil && test.target.lescapes():
3218 test.wantError(t, gotErr, os.ErrPathEscapes)
3219 case runtime.GOOS == "plan9":
3220
3221
3222 case test.target.lfinalKind() == testFileDir:
3223
3224
3225 test.wantError(t, gotErr, errAny)
3226 case test.source.lfinalKind() == testFileDir && test.target.lfinalKind() != testFileAbsent:
3227 test.wantError(t, gotErr, errAny)
3228 case test.source.anySlashSuffix() || test.target.anySlashSuffix():
3229 if runtime.GOOS == "openbsd" {
3230
3231
3232 return "", errSkipRootConsistencyCheck
3233 }
3234 default:
3235 test.wantError(t, gotErr, nil)
3236
3237 }
3238
3239 if runtime.GOOS == "linux" && (test.source.slashSuffix() || test.target.slashSuffix()) {
3240 return "", errSkipRootConsistencyCheck
3241 }
3242
3243 return "", gotErr
3244 })
3245 }
3246
3247 func TestRootMultiReadFile(t *testing.T) {
3248 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3249 var readFile = os.ReadFile
3250 if test.root != nil {
3251 readFile = test.root.ReadFile
3252 }
3253
3254 test.setOp("ReadFile(%q)", test.targetPath)
3255 data, gotErr := readFile(test.targetPath)
3256 var got string
3257 if gotErr == nil {
3258 got = string(data)
3259 }
3260
3261 switch {
3262 case test.root != nil && test.target.escapes():
3263 test.wantError(t, gotErr, os.ErrPathEscapes)
3264 case test.target.finalKind() == testFileAbsent:
3265 test.wantError(t, gotErr, errAny)
3266 case runtime.GOOS == "plan9":
3267
3268
3269 case runtime.GOOS == "netbsd":
3270
3271
3272 return "", gotErr
3273 case test.target.finalKind() == testFileDir:
3274 test.wantError(t, gotErr, errAny)
3275 case test.target.anySlashSuffix():
3276
3277
3278
3279
3280 default:
3281 test.wantError(t, gotErr, nil)
3282 if want := "target"; got != want {
3283 t.Fatalf("read file content %q, want %q", got, want)
3284 }
3285 }
3286
3287 return got, gotErr
3288 })
3289 }
3290
3291 func TestRootMultiStat(t *testing.T) {
3292 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3293 var stat = os.Stat
3294 if test.root != nil {
3295 stat = test.root.Stat
3296 }
3297
3298 test.setOp("Stat(%q)", test.targetPath)
3299 gotStat, gotErr := stat(test.targetPath)
3300
3301 switch {
3302 case test.target.isError():
3303 test.wantError(t, gotErr, errAny)
3304 case test.root != nil && test.target.escapes():
3305 test.wantError(t, gotErr, os.ErrPathEscapes)
3306 case test.target.finalKind() == testFileAbsent:
3307 test.wantError(t, gotErr, errAny)
3308 case test.target.anySlashSuffix():
3309 default:
3310 test.wantError(t, gotErr, nil)
3311 if !os.SameFile(gotStat, test.targetInfo) {
3312 test.errorf(t, "stat result is not for target file; want it to be")
3313 }
3314 }
3315 return "", gotErr
3316 })
3317 }
3318
3319 func TestRootMultiRemove(t *testing.T) {
3320 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3321 var remove = os.Remove
3322 if test.root != nil {
3323 remove = test.root.Remove
3324 }
3325
3326 test.setOp("Remove(%q)", test.targetPath)
3327 gotErr := remove(test.targetPath)
3328
3329 switch {
3330 case test.root != nil && test.target.lescapes():
3331 test.wantError(t, gotErr, os.ErrPathEscapes)
3332 case test.target.kind == testFileAbsent:
3333 test.wantError(t, gotErr, errAny)
3334 case test.target.anySlashSuffix():
3335 if runtime.GOOS == "linux" {
3336
3337
3338
3339
3340 return "", errSkipRootConsistencyCheck
3341 }
3342 default:
3343 test.wantError(t, gotErr, nil)
3344 }
3345 return "", gotErr
3346 })
3347 }
3348
3349 func TestRootMultiRemoveAll(t *testing.T) {
3350 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3351 var removeAll = os.RemoveAll
3352 if test.root != nil {
3353 removeAll = test.root.RemoveAll
3354 }
3355
3356 test.setOp("RemoveAll(%q)", test.targetPath)
3357 gotErr := removeAll(test.targetPath)
3358
3359 switch {
3360 case test.root != nil && test.target.ref.escapes:
3361
3362
3363
3364 test.wantError(t, gotErr, os.ErrPathEscapes)
3365 case test.target.anySlashSuffix():
3366
3367
3368
3369 return "", errSkipRootConsistencyCheck
3370 default:
3371 test.wantError(t, gotErr, nil)
3372 }
3373 return "", gotErr
3374 })
3375 }
3376
3377 func TestRootMultiChtimes(t *testing.T) {
3378 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3379 var chtimes = os.Chtimes
3380 if test.root != nil {
3381 chtimes = test.root.Chtimes
3382 }
3383
3384 now := time.Now()
3385 test.setOp("Chtimes(%q, %v, %v)", test.targetPath, now, now)
3386 gotErr := chtimes(test.targetPath, now, now)
3387
3388 switch {
3389 case test.target.isError():
3390 test.wantError(t, gotErr, errAny)
3391 case test.root != nil && test.target.escapes():
3392 test.wantError(t, gotErr, os.ErrPathEscapes)
3393 case test.target.finalKind() == testFileAbsent:
3394 test.wantError(t, gotErr, errAny)
3395 case test.target.anySlashSuffix():
3396 default:
3397 test.wantError(t, gotErr, nil)
3398 }
3399 return "", gotErr
3400 })
3401 }
3402
3403 func TestRootMultiReadlink(t *testing.T) {
3404 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3405 var readlink = os.Readlink
3406 if test.root != nil {
3407 readlink = test.root.Readlink
3408 }
3409
3410 test.setOp("Readlink(%q)", test.targetPath)
3411 got, gotErr := readlink(test.targetPath)
3412 if suffix, ok := strings.CutPrefix(got, test.dir); ok {
3413
3414 got = "/..." + suffix
3415 }
3416
3417 switch {
3418 case test.root != nil && test.target.lescapes():
3419 test.wantError(t, gotErr, os.ErrPathEscapes)
3420 case test.target.kind != testFileSymlink:
3421 test.wantError(t, gotErr, errAny)
3422 case test.target.anySlashSuffix():
3423 default:
3424 test.wantError(t, gotErr, nil)
3425 }
3426 return got, gotErr
3427 })
3428 }
3429
3430 func TestRootMultiWriteFile(t *testing.T) {
3431 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3432 var writeFile = os.WriteFile
3433 if test.root != nil {
3434 writeFile = test.root.WriteFile
3435 }
3436
3437 test.setOp("WriteFile(%q, ...)", test.targetPath)
3438 gotErr := writeFile(test.targetPath, []byte("data"), 0o666)
3439
3440 switch {
3441 case test.target.isError():
3442 test.wantError(t, gotErr, errAny)
3443 case runtime.GOOS == "windows" && test.target.isSymlinkToDir():
3444 test.wantError(t, gotErr, errAny)
3445 case test.root != nil && test.target.escapes():
3446 test.wantError(t, gotErr, os.ErrPathEscapes)
3447 case test.target.finalKind() == testFileDir:
3448 test.wantError(t, gotErr, errAny)
3449 case test.target.anySlashSuffix():
3450 default:
3451 test.wantError(t, gotErr, nil)
3452 }
3453 return "", gotErr
3454 })
3455 }
3456
3457 func TestRootMultiOpenFile(t *testing.T) {
3458 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3459 var openFile = os.OpenFile
3460 if test.root != nil {
3461 openFile = test.root.OpenFile
3462 }
3463
3464 test.setOp("OpenFile(%q, O_RDONLY, 0)", test.targetPath)
3465 f, gotErr := openFile(test.targetPath, os.O_RDONLY, 0)
3466 if gotErr == nil {
3467 defer f.Close()
3468 }
3469
3470 got := test.describeFile(t, f)
3471
3472 switch {
3473 case test.root != nil && test.target.escapes():
3474 test.wantError(t, gotErr, os.ErrPathEscapes)
3475 case test.target.finalKind() == testFileAbsent:
3476 test.wantError(t, gotErr, errAny)
3477 case test.target.anySlashSuffix():
3478 default:
3479 test.wantError(t, gotErr, nil)
3480 if want := "target"; got != want {
3481 t.Fatalf("opened file %q, want %q", got, want)
3482 }
3483 }
3484
3485 return got, gotErr
3486 })
3487 }
3488
View as plain text