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