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