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