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