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 == testFileSymlink && 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 testFileMax
2392
2393
2394
2395 testFileError
2396 )
2397
2398 func (kind testFileKind) String() string {
2399 switch kind {
2400 case testFileUnused:
2401 return "unused"
2402 case testFileAbsent:
2403 return "absent"
2404 case testFileFile:
2405 return "file"
2406 case testFileDir:
2407 return "dir"
2408 case testFileSymlink:
2409 return "symlink"
2410 case testFileError:
2411 return "error"
2412 default:
2413 return fmt.Sprintf("testFileKind(%d)", kind)
2414 }
2415 }
2416
2417
2418
2419
2420
2421 type testFileRef struct {
2422
2423
2424
2425 name string
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435 template string
2436
2437
2438 escapes bool
2439 }
2440
2441 var testFileRefs = []testFileRef{
2442 {escapes: false, name: "b", template: "BASE"},
2443 {escapes: false, name: "bs", template: "BASE/"},
2444 {escapes: false, name: "dpb", template: "DIR/../BASE"},
2445 {escapes: false, name: "dpbs", template: "DIR/../BASE/"},
2446 {escapes: true, name: "prb", template: "../ROOT/BASE"},
2447 {escapes: true, name: "prbs", template: "../ROOT/BASE/"},
2448 {escapes: true, name: "srb", template: "/ROOT/BASE"},
2449 {escapes: true, name: "srbs", template: "/ROOT/BASE/"},
2450 }
2451
2452
2453
2454 var testFileLimitedRefs = testFileRefs[0:2]
2455
2456
2457
2458
2459
2460 func (ref testFileRef) path(dir, base string) string {
2461 p := ref.template
2462 p = strings.ReplaceAll(p, "BASE", base)
2463 if trim, ok := strings.CutPrefix(p, "/ROOT"); ok {
2464 p = dir + trim
2465 }
2466 return p
2467 }
2468
2469
2470 func (ref testFileRef) hasSlashSuffix() bool {
2471 return strings.HasSuffix(ref.template, "/")
2472 }
2473
2474
2475
2476
2477
2478
2479
2480 type testFileDesc struct {
2481 kind testFileKind
2482 ref testFileRef
2483 target *testFileDesc
2484 }
2485
2486 var rootComprehensive = flag.Bool("root_comprehensive", false,
2487 "run many more os.Root test variations (slow, uncertain value)")
2488
2489
2490 func allTestFileDescs() iter.Seq[testFileDesc] {
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517 return func(yield func(testFileDesc) bool) {
2518
2519 for _, ref := range testFileRefs {
2520 for kind := range testFileMax {
2521 if kind == testFileUnused || kind == testFileSymlink {
2522 continue
2523 }
2524 desc := testFileDesc{
2525 kind: kind,
2526 ref: ref,
2527 }
2528 if !yield(desc) {
2529 return
2530 }
2531 }
2532 }
2533
2534
2535 refs := testFileRefs
2536 if !*rootComprehensive {
2537 refs = testFileLimitedRefs
2538 }
2539 for _, ref := range refs {
2540 for linkKind := range testFileMax {
2541 if linkKind == testFileUnused || linkKind == testFileSymlink {
2542 continue
2543 }
2544
2545 linkRefs := testFileRefs
2546 if !*rootComprehensive && linkKind != testFileFile && linkKind != testFileDir {
2547 linkRefs = testFileLimitedRefs
2548 }
2549 for _, linkRef := range linkRefs {
2550 desc := testFileDesc{
2551 kind: testFileSymlink,
2552 ref: ref,
2553 target: &testFileDesc{
2554 kind: linkKind,
2555 ref: linkRef,
2556 },
2557 }
2558 if !yield(desc) {
2559 return
2560 }
2561 }
2562 }
2563 }
2564 }
2565 }
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578 func (desc testFileDesc) String() string {
2579 s := desc.ref.name + strings.ToUpper(desc.kind.String()[:1])
2580 if desc.kind == testFileSymlink {
2581 s += desc.target.String()
2582 }
2583 return s
2584 }
2585
2586
2587
2588 func (desc testFileDesc) escapes() bool {
2589 if desc.ref.escapes {
2590 return true
2591 }
2592 if desc.kind == testFileSymlink {
2593 return desc.target.escapes()
2594 }
2595 return false
2596 }
2597
2598 func (desc testFileDesc) lescapes() bool {
2599 if desc.ref.escapes {
2600 return true
2601 }
2602 if runtime.GOOS == "windows" {
2603
2604
2605
2606 return false
2607 }
2608 if desc.ref.hasSlashSuffix() && desc.kind == testFileSymlink {
2609 return desc.target.escapes()
2610 }
2611 return false
2612 }
2613
2614
2615 func (desc testFileDesc) finalKind() testFileKind {
2616 if desc.kind == testFileSymlink {
2617 return desc.target.finalKind()
2618 }
2619 return desc.kind
2620 }
2621
2622 func (desc testFileDesc) lfinalKind() testFileKind {
2623 switch runtime.GOOS {
2624 case "windows":
2625 if desc.ref.hasSlashSuffix() && desc.kind == testFileSymlink && desc.target.kind != testFileDir {
2626 return testFileError
2627 }
2628 default:
2629 if desc.ref.hasSlashSuffix() && desc.kind == testFileSymlink {
2630 return desc.target.finalKind()
2631 }
2632 }
2633 return desc.kind
2634 }
2635
2636 func (desc testFileDesc) isError() bool {
2637 if runtime.GOOS == "js" {
2638 return false
2639 }
2640 var isError func(desc testFileDesc, hasSuffix bool) bool
2641 isError = func(desc testFileDesc, hasSuffix bool) bool {
2642 if desc.ref.escapes {
2643 return false
2644 }
2645 if desc.ref.hasSlashSuffix() {
2646 hasSuffix = true
2647 }
2648 switch desc.kind {
2649 case testFileDir:
2650 return false
2651 case testFileSymlink:
2652 if runtime.GOOS == "windows" && hasSuffix && desc.target.kind != testFileDir {
2653 return true
2654 }
2655 return isError(*desc.target, hasSuffix)
2656 default:
2657 return hasSuffix
2658 }
2659 }
2660 return isError(desc, false)
2661 }
2662
2663 func (desc testFileDesc) isSymlinkToDir() bool {
2664 if desc.kind != testFileSymlink {
2665 return false
2666 }
2667 if desc.ref.escapes {
2668 return false
2669 }
2670 if desc.finalKind() == testFileDir {
2671 return true
2672 }
2673 return false
2674 }
2675
2676
2677
2678
2679 func (desc testFileDesc) anySlashSuffix() bool {
2680 name := desc.ref.template
2681 if len(name) > 0 && os.IsPathSeparator(name[len(name)-1]) {
2682 return true
2683 }
2684 if desc.kind == testFileSymlink {
2685 return desc.target.anySlashSuffix()
2686 }
2687 return false
2688 }
2689
2690
2691 func (desc testFileDesc) slashSuffix() bool {
2692 name := desc.ref.template
2693 if len(name) > 0 && os.IsPathSeparator(name[len(name)-1]) {
2694 return true
2695 }
2696 return false
2697 }
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709 func (desc testFileDesc) create(t *testing.T, dir, base, token string) (fi os.FileInfo) {
2710 path := filepath.Join(dir, base)
2711 switch desc.kind {
2712 case testFileAbsent:
2713
2714 case testFileFile:
2715
2716 if err := os.WriteFile(path, []byte(token), 0o666); err != nil {
2717 t.Fatal(err)
2718 }
2719 case testFileDir:
2720
2721
2722
2723 if err := os.Mkdir(path, 0o777); err != nil {
2724 t.Fatal(err)
2725 }
2726 case testFileSymlink:
2727
2728 if runtime.GOOS == "plan9" {
2729 t.Skip("symlinks not supported on " + runtime.GOOS)
2730 }
2731 linktarget := desc.target.ref.path(dir, "s_"+base)
2732 if runtime.GOOS == "wasip1" && filepath.IsAbs(linktarget) {
2733 t.Skip("absolute link targets not supported on " + runtime.GOOS)
2734 }
2735 fi = desc.target.create(t, dir, "s_"+base, token)
2736 if err := os.Symlink(linktarget, path); err != nil {
2737 t.Fatal(err)
2738 }
2739 default:
2740 t.Fatalf("can't create file of kind: %v", desc.kind)
2741 }
2742 if desc.kind == testFileFile || desc.kind == testFileDir {
2743 var err error
2744 fi, err = os.Lstat(path)
2745 if err != nil {
2746 t.Fatal(err)
2747 }
2748 }
2749 return fi
2750 }
2751
2752
2753
2754
2755
2756
2757 func (test *rootMultiTest) describeFile(t *testing.T, f *os.File) string {
2758 if f == nil {
2759 return ""
2760 }
2761 fi, err := f.Stat()
2762 if err != nil {
2763 t.Fatal(err)
2764 }
2765 switch {
2766 case os.SameFile(fi, test.sourceInfo):
2767 return "source"
2768 case os.SameFile(fi, test.targetInfo):
2769 return "target"
2770 default:
2771 return "unknown file"
2772 }
2773 }
2774
2775
2776
2777
2778
2779
2780
2781 func dirTreeContents(t *testing.T, dir string) (contents []string) {
2782 root, err := os.OpenRoot(dir)
2783 if err != nil {
2784 t.Fatal(err)
2785 }
2786 defer root.Close()
2787 fs.WalkDir(root.FS(), ".", func(path string, d fs.DirEntry, err error) error {
2788 if path == "." {
2789 return nil
2790 }
2791 info, err := d.Info()
2792 if err != nil {
2793 t.Fatal(err)
2794 }
2795 ent := info.Mode().String() + " " + path
2796 switch d.Type() {
2797 case fs.ModeDir:
2798 ent += "/"
2799 case fs.ModeSymlink:
2800 target, err := root.Readlink(path)
2801 if err != nil {
2802 t.Fatal(err)
2803 }
2804 if filepath.IsAbs(target) {
2805 relPath, err := filepath.Rel(dir, target)
2806 if err == nil && filepath.IsLocal(relPath) {
2807 target = "/.../" + relPath
2808 }
2809 }
2810 ent += " => " + target
2811 default:
2812 f, err := root.Open(path)
2813 if err != nil {
2814 ent += " (unreadable)"
2815 } else {
2816 content, err := io.ReadAll(f)
2817 if err != nil {
2818 t.Fatal(err)
2819 }
2820 ent += fmt.Sprintf(" %q", content)
2821 }
2822 }
2823 contents = append(contents, ent)
2824 return nil
2825 })
2826 return contents
2827 }
2828
2829
2830
2831
2832
2833 func TestRootMultiOpen(t *testing.T) {
2834 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847 var open = os.Open
2848 if test.root != nil {
2849 open = test.root.Open
2850 }
2851
2852 test.setOp("Open(%q)", test.targetPath)
2853 f, gotErr := open(test.targetPath)
2854 if gotErr == nil {
2855 defer f.Close()
2856 }
2857
2858
2859
2860
2861
2862
2863 got := test.describeFile(t, f)
2864
2865 switch {
2866 case test.root != nil && test.target.escapes():
2867
2868 test.wantError(t, gotErr, os.ErrPathEscapes)
2869 case test.target.finalKind() == testFileAbsent:
2870
2871 test.wantError(t, gotErr, errAny)
2872 case test.target.anySlashSuffix():
2873
2874
2875
2876
2877
2878 default:
2879
2880 test.wantError(t, gotErr, nil)
2881 if want := "target"; got != want {
2882 t.Fatalf("opened file %q, want %q", got, want)
2883 }
2884 }
2885
2886
2887
2888
2889 return got, gotErr
2890 })
2891 }
2892
2893 func TestRootMultiChmod(t *testing.T) {
2894 if runtime.GOOS == "wasip1" {
2895 t.Skip("Chmod not supported on " + runtime.GOOS)
2896 }
2897 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
2898 var (
2899 chmod = os.Chmod
2900 stat = os.Stat
2901 lstat = os.Lstat
2902 )
2903 if test.root != nil {
2904 chmod = test.root.Chmod
2905 stat = test.root.Stat
2906 lstat = test.root.Lstat
2907 }
2908
2909
2910
2911
2912
2913
2914
2915 wantMode := os.FileMode(0o500)
2916 if runtime.GOOS == "windows" {
2917
2918
2919
2920 wantMode = 0o444
2921 }
2922 t.Cleanup(func() {
2923 chmod(test.targetPath, 0o700)
2924 })
2925
2926 test.setOp("Chmod(%q, %o)", test.targetPath, wantMode)
2927 gotErr := chmod(test.targetPath, wantMode)
2928
2929 escapes := test.target.escapes()
2930 targetKind := test.target.finalKind()
2931 if runtime.GOOS == "windows" {
2932
2933
2934 stat = lstat
2935 escapes = test.target.ref.escapes
2936 targetKind = test.target.kind
2937 }
2938
2939 var gotMode fs.FileMode
2940 switch {
2941 case test.root != nil && escapes:
2942 test.wantError(t, gotErr, os.ErrPathEscapes)
2943 case targetKind == testFileAbsent:
2944 test.wantError(t, gotErr, errAny)
2945 case test.target.anySlashSuffix():
2946
2947 default:
2948 test.wantError(t, gotErr, nil)
2949
2950 fi, err := stat(test.targetPath)
2951 if err != nil {
2952 t.Fatalf("could not stat target: %v", err)
2953 }
2954 if runtime.GOOS == "windows" && !fi.Mode().IsRegular() {
2955
2956 break
2957 }
2958
2959 gotMode = fi.Mode() & fs.ModePerm
2960 if gotMode != wantMode {
2961 t.Fatalf("file %q:\ngot mode: %v\nwant mode: %v", test.targetPath, gotMode, wantMode)
2962 }
2963 }
2964
2965 if runtime.GOOS == "windows" && test.root == nil && gotErr != nil {
2966
2967
2968
2969
2970 return "", errSkipRootConsistencyCheck
2971 }
2972
2973 return gotMode.String(), gotErr
2974 })
2975 }
2976
2977 func TestRootMultiCreate(t *testing.T) {
2978 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
2979 var create = os.Create
2980 if test.root != nil {
2981 create = test.root.Create
2982 }
2983
2984 test.setOp("Create(%q)", test.targetPath)
2985 f, gotErr := create(test.targetPath)
2986 if gotErr == nil {
2987 defer f.Close()
2988 }
2989
2990 switch {
2991 case test.target.isError():
2992 test.wantError(t, gotErr, errAny)
2993 case runtime.GOOS == "windows" && test.target.isSymlinkToDir():
2994
2995
2996 test.wantError(t, gotErr, errAny)
2997 case test.root != nil && test.target.escapes():
2998
2999 test.wantError(t, gotErr, os.ErrPathEscapes)
3000 default:
3001 }
3002
3003 return "", gotErr
3004 })
3005 }
3006
3007 func TestRootMultiLink(t *testing.T) {
3008 if runtime.GOOS == "wasip1" {
3009 switch os.Getenv("GOWASIRUNTIME") {
3010 case "", "wasmtime":
3011
3012
3013 t.Skip("test seems to tickle a wasmtime bug")
3014 }
3015 }
3016 testenv.MustHaveLink(t)
3017 runRootMultiTest2(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3018 var (
3019 rename = os.Link
3020 )
3021 if test.root != nil {
3022 rename = test.root.Link
3023 }
3024
3025 test.setOp("Link(%q, %q)", test.sourcePath, test.targetPath)
3026 gotErr := rename(test.sourcePath, test.targetPath)
3027
3028 switch {
3029 case test.root != nil && test.source.lescapes():
3030 test.wantError(t, gotErr, os.ErrPathEscapes)
3031 case test.source.lfinalKind() == testFileAbsent:
3032 test.wantError(t, gotErr, errAny)
3033 case test.source.kind == testFileSymlink:
3034
3035
3036
3037 return "", errSkipRootConsistencyCheck
3038 case test.source.slashSuffix() && test.source.lfinalKind() != testFileDir:
3039 test.wantError(t, gotErr, errAny)
3040 }
3041 return "", gotErr
3042 })
3043 }
3044
3045 func TestRootMultiLstat(t *testing.T) {
3046 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3047 var (
3048 lstat = os.Lstat
3049 )
3050 if test.root != nil {
3051 lstat = test.root.Lstat
3052 }
3053
3054 test.setOp("Lstat(%q)", test.targetPath)
3055 gotStat, gotErr := lstat(test.targetPath)
3056
3057 result := ""
3058 if gotStat != nil {
3059 result = gotStat.Mode().String()
3060 }
3061
3062 escapes := test.target.lescapes()
3063 finalKind := test.target.lfinalKind()
3064 if runtime.GOOS == "windows" && test.target.ref.hasSlashSuffix() {
3065
3066
3067 escapes = test.target.escapes()
3068 finalKind = test.target.finalKind()
3069 }
3070
3071 switch {
3072 case test.root != nil && escapes:
3073 test.wantError(t, gotErr, os.ErrPathEscapes)
3074 case test.target.kind == testFileAbsent:
3075
3076 test.wantError(t, gotErr, errAny)
3077 case finalKind == testFileSymlink:
3078 test.wantError(t, gotErr, nil)
3079 if got, want := gotStat.Mode().Type(), fs.ModeSymlink; got != want {
3080 test.errorf(t, "got mode %v, want %v", got, want)
3081 }
3082 case gotErr != nil:
3083 default:
3084 if !os.SameFile(gotStat, test.targetInfo) {
3085 test.errorf(t, "stat result is not for target file; want it to be")
3086 }
3087 }
3088
3089 return result, gotErr
3090 })
3091 }
3092
3093 func TestRootMultiMkdir(t *testing.T) {
3094 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3095 var (
3096 mkdir = os.Mkdir
3097 stat = os.Stat
3098 )
3099 if test.root != nil {
3100 mkdir = test.root.Mkdir
3101 stat = test.root.Stat
3102 }
3103
3104 test.setOp("Mkdir(%q, 0o777)", test.targetPath)
3105 gotErr := mkdir(test.targetPath, 0o777)
3106
3107 switch {
3108 case test.root != nil && test.target.ref.escapes:
3109
3110 test.wantError(t, gotErr, os.ErrPathEscapes)
3111 case test.target.slashSuffix() && test.target.kind == testFileSymlink:
3112
3113
3114
3115
3116 if test.root != nil && test.target.escapes() {
3117 test.wantError(t, gotErr, errAny)
3118 }
3119 if runtime.GOOS == "openbsd" {
3120
3121
3122 return "", errSkipRootConsistencyCheck
3123 }
3124 case test.target.kind != testFileAbsent:
3125
3126 test.wantError(t, gotErr, errAny)
3127 default:
3128 test.wantError(t, gotErr, nil)
3129 fi, err := stat(test.targetPath)
3130 if err != nil {
3131 t.Fatalf("could not stat target: %v", err)
3132 }
3133 if !fi.IsDir() {
3134 t.Fatalf("%q: not a directory, expected it to be", test.targetPath)
3135 }
3136 }
3137 return "", gotErr
3138 })
3139 }
3140
3141 func TestRootMultiMkdirAllShallow(t *testing.T) {
3142 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3143 return testRootMultiMkdirAll(t, test, test.targetPath)
3144 })
3145 }
3146
3147 func TestRootMultiMkdirAllDeep(t *testing.T) {
3148 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3149 targetPath := test.targetPath
3150 if len(targetPath) > 0 && os.IsPathSeparator(targetPath[len(targetPath)-1]) {
3151 targetPath += "a/b/"
3152 } else {
3153 targetPath += "/a/b"
3154 }
3155 return testRootMultiMkdirAll(t, test, targetPath)
3156 })
3157 }
3158
3159 func testRootMultiMkdirAll(t *testing.T, test *rootMultiTest, targetPath string) (string, error) {
3160 var mkdirAll = os.MkdirAll
3161 if test.root != nil {
3162 mkdirAll = test.root.MkdirAll
3163 }
3164
3165 test.setOp("MkdirAll(%q, 0o777)", targetPath)
3166 gotErr := mkdirAll(targetPath, 0o777)
3167
3168 switch {
3169 case test.root != nil && test.target.lescapes():
3170
3171 test.wantError(t, gotErr, os.ErrPathEscapes)
3172 case test.root != nil && test.target.escapes():
3173
3174 test.wantError(t, gotErr, errAny)
3175 return "", errSkipRootConsistencyCheck
3176 case test.root != nil && test.target.kind == testFileSymlink && test.target.target.kind == testFileAbsent && targetPath != test.targetPath:
3177
3178
3179
3180
3181 return "", errSkipRootConsistencyCheck
3182 default:
3183 }
3184 return "", gotErr
3185 }
3186
3187 func TestRootMultiRename(t *testing.T) {
3188 if runtime.GOOS == "wasip1" {
3189 switch os.Getenv("GOWASIRUNTIME") {
3190 case "", "wasmtime":
3191
3192
3193 t.Skip("test seems to tickle a wasmtime bug")
3194 }
3195 }
3196 runRootMultiTest2(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3197 var (
3198 rename = os.Rename
3199 )
3200 if test.root != nil {
3201 rename = test.root.Rename
3202 }
3203
3204
3205
3206 test.setOp("Rename(%q, %q)", test.sourcePath, test.targetPath)
3207 gotErr := rename(test.sourcePath, test.targetPath)
3208
3209 if runtime.GOOS == "windows" &&
3210 (test.source.finalKind() != test.target.finalKind() || test.source.kind == testFileSymlink || test.target.kind == testFileSymlink) {
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220 return "", errSkipRootConsistencyCheck
3221 }
3222
3223 switch {
3224 case test.root != nil && test.source.lescapes():
3225 test.wantError(t, gotErr, os.ErrPathEscapes)
3226 case test.source.lfinalKind() == testFileAbsent:
3227 test.wantError(t, gotErr, errAny)
3228 case test.source.slashSuffix() && test.source.lfinalKind() != testFileDir && runtime.GOOS != "js":
3229 test.wantError(t, gotErr, errAny)
3230 case test.root != nil && test.target.lescapes():
3231 test.wantError(t, gotErr, os.ErrPathEscapes)
3232 case runtime.GOOS == "plan9":
3233
3234
3235 case test.target.lfinalKind() == testFileDir:
3236
3237
3238 test.wantError(t, gotErr, errAny)
3239 case test.source.lfinalKind() == testFileDir && test.target.lfinalKind() != testFileAbsent:
3240 test.wantError(t, gotErr, errAny)
3241 case test.source.anySlashSuffix() || test.target.anySlashSuffix():
3242 if runtime.GOOS == "openbsd" {
3243
3244
3245 return "", errSkipRootConsistencyCheck
3246 }
3247 default:
3248 test.wantError(t, gotErr, nil)
3249
3250 }
3251
3252 if runtime.GOOS == "linux" && (test.source.slashSuffix() || test.target.slashSuffix()) {
3253 return "", errSkipRootConsistencyCheck
3254 }
3255
3256 return "", gotErr
3257 })
3258 }
3259
3260 func TestRootMultiReadFile(t *testing.T) {
3261 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3262 var readFile = os.ReadFile
3263 if test.root != nil {
3264 readFile = test.root.ReadFile
3265 }
3266
3267 test.setOp("ReadFile(%q)", test.targetPath)
3268 data, gotErr := readFile(test.targetPath)
3269 var got string
3270 if gotErr == nil {
3271 got = string(data)
3272 }
3273
3274 switch {
3275 case test.root != nil && test.target.escapes():
3276 test.wantError(t, gotErr, os.ErrPathEscapes)
3277 case test.target.finalKind() == testFileAbsent:
3278 test.wantError(t, gotErr, errAny)
3279 case runtime.GOOS == "plan9":
3280
3281
3282 case runtime.GOOS == "netbsd":
3283
3284
3285 return "", gotErr
3286 case test.target.finalKind() == testFileDir:
3287 test.wantError(t, gotErr, errAny)
3288 case test.target.anySlashSuffix():
3289
3290
3291
3292
3293 default:
3294 test.wantError(t, gotErr, nil)
3295 if want := "target"; got != want {
3296 t.Fatalf("read file content %q, want %q", got, want)
3297 }
3298 }
3299
3300 return got, gotErr
3301 })
3302 }
3303
3304 func TestRootMultiStat(t *testing.T) {
3305 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3306 var stat = os.Stat
3307 if test.root != nil {
3308 stat = test.root.Stat
3309 }
3310
3311 test.setOp("Stat(%q)", test.targetPath)
3312 gotStat, gotErr := stat(test.targetPath)
3313
3314 switch {
3315 case test.target.isError():
3316 test.wantError(t, gotErr, errAny)
3317 case test.root != nil && test.target.escapes():
3318 test.wantError(t, gotErr, os.ErrPathEscapes)
3319 case test.target.finalKind() == testFileAbsent:
3320 test.wantError(t, gotErr, errAny)
3321 case test.target.anySlashSuffix():
3322 default:
3323 test.wantError(t, gotErr, nil)
3324 if !os.SameFile(gotStat, test.targetInfo) {
3325 test.errorf(t, "stat result is not for target file; want it to be")
3326 }
3327 }
3328 return "", gotErr
3329 })
3330 }
3331
3332 func TestRootMultiRemove(t *testing.T) {
3333 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3334 var remove = os.Remove
3335 if test.root != nil {
3336 remove = test.root.Remove
3337 }
3338
3339 test.setOp("Remove(%q)", test.targetPath)
3340 gotErr := remove(test.targetPath)
3341
3342 switch {
3343 case test.root != nil && test.target.lescapes():
3344 test.wantError(t, gotErr, os.ErrPathEscapes)
3345 case test.target.kind == testFileAbsent:
3346 test.wantError(t, gotErr, errAny)
3347 case test.target.anySlashSuffix():
3348 if runtime.GOOS == "linux" {
3349
3350
3351
3352
3353 return "", errSkipRootConsistencyCheck
3354 }
3355 default:
3356 test.wantError(t, gotErr, nil)
3357 }
3358 return "", gotErr
3359 })
3360 }
3361
3362 func TestRootMultiRemoveAll(t *testing.T) {
3363 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3364 var removeAll = os.RemoveAll
3365 if test.root != nil {
3366 removeAll = test.root.RemoveAll
3367 }
3368
3369 test.setOp("RemoveAll(%q)", test.targetPath)
3370 gotErr := removeAll(test.targetPath)
3371
3372 switch {
3373 case test.root != nil && test.target.ref.escapes:
3374
3375
3376
3377 test.wantError(t, gotErr, os.ErrPathEscapes)
3378 case test.target.anySlashSuffix():
3379
3380
3381
3382 return "", errSkipRootConsistencyCheck
3383 default:
3384 test.wantError(t, gotErr, nil)
3385 }
3386 return "", gotErr
3387 })
3388 }
3389
3390 func TestRootMultiChtimes(t *testing.T) {
3391 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3392 var chtimes = os.Chtimes
3393 if test.root != nil {
3394 chtimes = test.root.Chtimes
3395 }
3396
3397 now := time.Now()
3398 test.setOp("Chtimes(%q, %v, %v)", test.targetPath, now, now)
3399 gotErr := chtimes(test.targetPath, now, now)
3400
3401 switch {
3402 case test.target.isError():
3403 test.wantError(t, gotErr, errAny)
3404 case test.root != nil && test.target.escapes():
3405 test.wantError(t, gotErr, os.ErrPathEscapes)
3406 case test.target.finalKind() == testFileAbsent:
3407 test.wantError(t, gotErr, errAny)
3408 case test.target.anySlashSuffix():
3409 default:
3410 test.wantError(t, gotErr, nil)
3411 }
3412 return "", gotErr
3413 })
3414 }
3415
3416 func TestRootMultiReadlink(t *testing.T) {
3417 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3418 var readlink = os.Readlink
3419 if test.root != nil {
3420 readlink = test.root.Readlink
3421 }
3422
3423 test.setOp("Readlink(%q)", test.targetPath)
3424 got, gotErr := readlink(test.targetPath)
3425 if suffix, ok := strings.CutPrefix(got, test.dir); ok {
3426
3427 got = "/..." + suffix
3428 }
3429
3430 switch {
3431 case test.root != nil && test.target.lescapes():
3432 test.wantError(t, gotErr, os.ErrPathEscapes)
3433 case test.target.kind != testFileSymlink:
3434 test.wantError(t, gotErr, errAny)
3435 case test.target.anySlashSuffix():
3436 default:
3437 test.wantError(t, gotErr, nil)
3438 }
3439 return got, gotErr
3440 })
3441 }
3442
3443 func TestRootMultiWriteFile(t *testing.T) {
3444 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3445 var writeFile = os.WriteFile
3446 if test.root != nil {
3447 writeFile = test.root.WriteFile
3448 }
3449
3450 test.setOp("WriteFile(%q, ...)", test.targetPath)
3451 gotErr := writeFile(test.targetPath, []byte("data"), 0o666)
3452
3453 switch {
3454 case test.target.isError():
3455 test.wantError(t, gotErr, errAny)
3456 case runtime.GOOS == "windows" && test.target.isSymlinkToDir():
3457 test.wantError(t, gotErr, errAny)
3458 case test.root != nil && test.target.escapes():
3459 test.wantError(t, gotErr, os.ErrPathEscapes)
3460 case test.target.finalKind() == testFileDir:
3461 test.wantError(t, gotErr, errAny)
3462 case test.target.anySlashSuffix():
3463 default:
3464 test.wantError(t, gotErr, nil)
3465 }
3466 return "", gotErr
3467 })
3468 }
3469
3470 func TestRootMultiOpenFile(t *testing.T) {
3471 runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
3472 var openFile = os.OpenFile
3473 if test.root != nil {
3474 openFile = test.root.OpenFile
3475 }
3476
3477 test.setOp("OpenFile(%q, O_RDONLY, 0)", test.targetPath)
3478 f, gotErr := openFile(test.targetPath, os.O_RDONLY, 0)
3479 if gotErr == nil {
3480 defer f.Close()
3481 }
3482
3483 got := test.describeFile(t, f)
3484
3485 switch {
3486 case test.root != nil && test.target.escapes():
3487 test.wantError(t, gotErr, os.ErrPathEscapes)
3488 case test.target.finalKind() == testFileAbsent:
3489 test.wantError(t, gotErr, errAny)
3490 case test.target.anySlashSuffix():
3491 default:
3492 test.wantError(t, gotErr, nil)
3493 if want := "target"; got != want {
3494 t.Fatalf("opened file %q, want %q", got, want)
3495 }
3496 }
3497
3498 return got, gotErr
3499 })
3500 }
3501
View as plain text