Source file src/os/root_test.go

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     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  // testMaybeRooted calls f in two subtests,
    28  // one with a Root and one with a nil r.
    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  // makefs creates a test filesystem layout and returns the path to its root.
    46  //
    47  // Each entry in the slice is a file, directory, or symbolic link to create:
    48  //
    49  //   - "d/": directory d
    50  //   - "f": file f with contents f
    51  //   - "a => b": symlink a with target b
    52  //
    53  // The directory containing the filesystem is always named ROOT.
    54  // $ABS is replaced with the absolute path of the directory containing the filesystem.
    55  //
    56  // Parent directories are automatically created as needed.
    57  //
    58  // makefs calls t.Skip if the layout contains features not supported by the current GOOS.
    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  // hasLink reports whether the test filesystem layout fs
    97  // contains at least a single symlink.
    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  // A rootTest is a test case for os.Root.
   109  type rootTest struct {
   110  	name string
   111  
   112  	// fs is the test filesystem layout. See makefs above.
   113  	fs []string
   114  
   115  	// open is the filename to access in the test.
   116  	open string
   117  
   118  	// target is the filename that we expect to be accessed, after resolving all symlinks.
   119  	// For test cases where the operation fails due to an escaping path such as ../ROOT/x,
   120  	// the target is the filename that should not have been opened.
   121  	target string
   122  
   123  	// ltarget is the filename that we expect to accessed, after resolving all symlinks
   124  	// except the last one. This is the file we expect to be removed by Remove or statted
   125  	// by Lstat.
   126  	//
   127  	// If the last path component in open is not a symlink, ltarget should be "".
   128  	ltarget string
   129  
   130  	// wantError is true if accessing the file should fail.
   131  	wantError bool
   132  
   133  	// alwaysFails is true if the open operation is expected to fail
   134  	// even when using non-openat operations.
   135  	//
   136  	// This lets us check that tests that are expected to fail because (for example)
   137  	// a path escapes the directory root will succeed when the escaping checks are not
   138  	// performed.
   139  	alwaysFails bool
   140  }
   141  
   142  // run sets up the test filesystem layout, os.OpenDirs the root, and calls f.
   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  		// The target is a file that will be accessed,
   155  		// or a file that should not be accessed
   156  		// (because doing so escapes the root).
   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  // errEndsTest checks the error result of a test,
   166  // verifying that it succeeded or failed as expected.
   167  //
   168  // It returns true if the test is done due to encountering an expected error.
   169  // false if the test should continue.
   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  			// On Windows, the path is cleaned before symlink resolution.
   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  				// Create a file with no read/write permissions,
   470  				// to ensure we can use Chmod on an inaccessible file.
   471  				if err := os.WriteFile(target, nil, 0o000); err != nil {
   472  					t.Fatal(err)
   473  				}
   474  			}
   475  			if runtime.GOOS == "windows" {
   476  				// On Windows, Chmod("symlink") affects the link, not its target.
   477  				// See issue 71492.
   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  	// Don't check atimes if the fs is mounted noatime,
   501  	// or on Plan 9 which does not permit changing atimes to arbitrary values.
   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  				// This case is trying to mkdir("some symlink"),
   561  				// which is an error (but not an escape).
   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  				// Issue #73559: We're not going to worry about the exact
   578  				// mode bits (which will have been modified by umask),
   579  				// but there should be mode bits.
   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  			// Test expects error, mkdirall creates the missing directory.
   590  			// TestRootMultiMkdirAll covers this case better anyway, just skip.
   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  				// This case is trying to mkdir("some symlink"),
   597  				// which is an error (but not an escape).
   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  				// Issue #73559: We're not going to worry about the exact
   614  				// mode bits (which will have been modified by umask),
   615  				// but there should be mode bits.
   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  				// Remove doesn't follow symlinks in the final path component,
   653  				// so it will successfully remove ltarget.
   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  				// Remove doesn't follow symlinks in the final path component,
   680  				// so it will successfully remove ltarget.
   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  				// The test is removing a file with a trailing /.
   706  				// RemoveAll ignores trailing /s
   707  				// If the file is a symlink, it will remove the symlink.
   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  				// Remove doesn't follow symlinks in the final path component,
   716  				// so it will successfully remove ltarget.
   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  				// If the target doesn't exist, RemoveAll succeeds rather
   730  				// than returning ErrNotExist.
   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  				// Lstat will stat the final link, rather than following it.
   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  				// Readlink will read the final link, rather than following it.
   841  				wantError = false
   842  			} else {
   843  				// Readlink fails on non-link targets.
   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  // TestRootRenameFrom tests renaming the test case target to a known-good path.
   864  func TestRootRenameFrom(t *testing.T) {
   865  	testRootMoveFrom(t, true)
   866  }
   867  
   868  // TestRootRenameFrom tests linking the test case target to a known-good path.
   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  				// Rename will rename the link, not the file linked to.
   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  				// When GOOS=js, creating a hard link to a symlink fails.
   895  				if !rename && runtime.GOOS == "js" {
   896  					wantError = true
   897  				}
   898  
   899  				// Windows allows creating a hard link to a file symlink,
   900  				// but not to a directory symlink.
   901  				//
   902  				// This uses os.Stat to check the link target, because this
   903  				// is easier than figuring out whether the link itself is a
   904  				// directory link. The link was created with os.Symlink,
   905  				// which creates directory links when the target is a directory,
   906  				// so this is good enough for a test.
   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  			// Plan 9 doesn't allow cross-directory renames.
   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  // TestRootRenameTo tests renaming a known-good path to the test case target.
   972  func TestRootRenameTo(t *testing.T) {
   973  	testRootMoveTo(t, true)
   974  }
   975  
   976  // TestRootLinkTo tests renaming a known-good path to the test case target.
   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  				// Windows will ignore trailing slashes in the rename/link target.
   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  				// Rename will overwrite the final link rather than follow it.
  1004  				target = test.ltarget
  1005  				wantError = false
  1006  			}
  1007  
  1008  			// Plan 9 doesn't allow cross-directory renames.
  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  				// We can't create a symlink over an existing symlink.
  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  // A rootConsistencyTest is a test case comparing os.Root behavior with
  1069  // the corresponding non-Root function.
  1070  //
  1071  // These tests verify that, for example, Root.Open("file/./") and os.Open("file/./")
  1072  // have the same result, although the specific result may vary by platform.
  1073  type rootConsistencyTest struct {
  1074  	name string
  1075  
  1076  	// fs is the test filesystem layout. See makefs above.
  1077  	// fsFunc is called to modify the test filesystem, or replace it.
  1078  	fs     []string
  1079  	fsFunc func(t *testing.T, dir string) string
  1080  
  1081  	// open is the filename to access in the test.
  1082  	open string
  1083  
  1084  	// detailedErrorMismatch indicates that os.Root and the corresponding non-Root
  1085  	// function return different errors for this test.
  1086  	detailedErrorMismatch func(t *testing.T) bool
  1087  
  1088  	// check is called before the test starts, and may t.Skip if necessary.
  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  		// FreeBSD returns EPERM in the non-Root case.
  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  		// os.Create returns ENOTDIR or EISDIR depending on the platform.
  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  			// Linux does not resolve "symlink" in rename("symlink/", "target").
  1164  			t.Skip("known inconsistency on linux")
  1165  		}
  1166  		if strings.HasPrefix(t.Name(), "TestRootConsistencyRemoveAll/") {
  1167  			// Root.RemoveAll and os.RemoveAll are not always consistent here.
  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  		// On Windows, os.Root.Open returns "The directory name is invalid."
  1243  		// and os.Open returns "The file cannot be accessed by the system.".
  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  				// Root.RemoveAll notices that a/ is not a directory,
  1251  				// and returns success.
  1252  				// os.RemoveAll tries to open a/ and fails because
  1253  				// it is not a regular file.
  1254  				// The inconsistency here isn't worth fixing, so just skip this test.
  1255  				t.Skip("known inconsistency on windows")
  1256  			case "js":
  1257  				// GOOS=js behavior varies with what the underlying OS is.
  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  		// On wasip, non-Root functions clean paths before opening them,
  1289  		// resulting in inconsistent behavior.
  1290  		// https://go.dev/issue/69509
  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  				// Plan9 syscall errors aren't comparable.
  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 // can't remove the root itself
  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 // can't remove the root itself
  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  		// This test depends on moving files between directories.
  1559  		t.Skip("Plan 9 does not support cross-directory renames")
  1560  	}
  1561  	// Run this test in two directions:
  1562  	// Renaming the test path to a known-good path (from),
  1563  	// and renaming a known-good path to the test path (to).
  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  					// On Windows, Rename("/path/to/.", x) succeeds,
  1569  					// because Windows cleans the path to just "/path/to".
  1570  					// Root.Rename(".", x) fails as expected.
  1571  					// Don't run this consistency test on Windows.
  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  						// When the source is a symlink, Root.Link creates
  1610  						// a hard link to the symlink.
  1611  						// os.Link does whatever the link syscall does,
  1612  						// which varies between operating systems and
  1613  						// their versions.
  1614  						// Skip running the consistency test when
  1615  						// the source is a symlink.
  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  	// Create directory "a" and open it.
  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  	// Rename "a" => "b", and create "b/f".
  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  	// Open "f", and confirm that we see it.
  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  	// f.Name reflects the original path we opened the directory under (".../a"), not "b".
  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  				// TODO(go.dev/issue/71134) can lead to goroutine starvation.
  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  // TestRootRaceRenameDir attempts to escape a Root by renaming a path component mid-parse.
  1805  //
  1806  // We create a deeply nested directory:
  1807  //
  1808  //	base/a/a/a/a/ [...] /a
  1809  //
  1810  // And a path that descends into the tree, then returns to the top using ..:
  1811  //
  1812  //	base/a/a/a/a/ [...] /a/../../../ [..] /../a/f
  1813  //
  1814  // While opening this file, we rename base/a/a to base/b.
  1815  // A naive lookup operation will resolve the path to base/f.
  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  	// Compute how long it takes to open the path in the common case.
  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  	// We're trying to exploit a race, so try this a number of times.
  1854  	for range 100 {
  1855  		// Start a goroutine to open the file.
  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  		// Wait for the open operation to partially complete,
  1868  		// and then rename a directory near the root.
  1869  		time.Sleep(avg / 4)
  1870  		if err := os.Rename(dir+"/base/a", dir+"/b"); err != nil {
  1871  			// Windows and Plan9 won't let us rename a directory if we have
  1872  			// an open handle for it, so an error here is expected.
  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  // TestRootNoLstat verifies that we do not use lstat (possibly escaping the root)
  2012  // when reading directories in a Root.
  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  	// File.ReadDir, returning []DirEntry
  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  	// File.Readdir, returning []FileInfo
  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  	// File.Readdirnames, returning []string
  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  // A rootMultiTest is state for testing an os.Root operation in one configuration among many.
  2117  // Each execution of a rootMultiTest varies in several ways:
  2118  //
  2119  //   - With or without an *os.Root, to check consistency between root/non-root operations.
  2120  //   - With a target that may be a file, directory, symlink, or entirely absent.
  2121  //   - With various paths referencing the target: "target", "DIR/../target", etc.
  2122  //   - When the target is a symlink, with various link target paths.
  2123  //
  2124  // For example, a single test execution might be:
  2125  // In an *os.Root, copy "source" to "DIR/../target".
  2126  // "source" is a file, and "target" is a symlink to "../ROOT/s_target". "s_target" is a directory.
  2127  // (In this case, we expect the test to fail due to the path escape in the symlink.)
  2128  type rootMultiTest struct {
  2129  	// dir is the directory containing the test.
  2130  	// dir will always contain a directory named "ROOT"
  2131  	// and a subdir named "ROOT/DIR".
  2132  	dir string
  2133  
  2134  	// root is the *Root for the test. May be nil.
  2135  	root *os.Root
  2136  
  2137  	// source and target are files acted on by the test.
  2138  	// target is always set; source is only set for tests which request two files.
  2139  	source testFileDesc
  2140  	target testFileDesc
  2141  
  2142  	// sourcePath and targetPath are the paths which should be used to acceess
  2143  	// the source/target.
  2144  	sourcePath string
  2145  	targetPath string
  2146  
  2147  	sourceInfo os.FileInfo
  2148  	targetInfo os.FileInfo
  2149  
  2150  	// op is the operation being performed, used for reporting errors.
  2151  	op string
  2152  }
  2153  
  2154  var testVerbose = flag.Bool("verbose", false, "verbose")
  2155  
  2156  // A rootMultiTest function may return this error to disable
  2157  // the check that in-root and out-of-root functions have the same outcome.
  2158  var errSkipRootConsistencyCheck = errors.New("skip root consistency check")
  2159  
  2160  // runRootMultiTest runs f in a variety of configurations.
  2161  // See above.
  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 // unused
  2166  			runRootMultiTestDescs(t, source, target, f)
  2167  		})
  2168  	}
  2169  }
  2170  
  2171  // runRootMultiTest2 runs f in a variety of configurations,
  2172  // with both source and target files.
  2173  // See above.
  2174  func runRootMultiTest2(t *testing.T, f func(*testing.T, *rootMultiTest) (string, error)) {
  2175  	// A "simple" desc is one which contains only direct references.
  2176  	// When not running the comprehensive (but slow) set of test variations,
  2177  	// we only test variations where at least one of source and target is simple.
  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  // setOp sets the operation performed by the test (logged in errors).
  2201  //
  2202  // This currently assumes the operation will be a method of os.Root and a function in os
  2203  // (e.g., root.Open/os.Open).
  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  // wantError tests whether got matches want.
  2221  // If want is errAny, got may be any non-nil error.
  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  		// Darwin appears to have a kernel bug which causes restrictions on paths
  2248  		// with a trailing / to not be applied during uncached path lookups.
  2249  		// These restrictions are applied during cached lookups, so the results
  2250  		// of operating on /-suffixed paths are inconsistent.
  2251  		//
  2252  		// An example of this Darwin behavior (as of 25.4.0) is:
  2253  		//   $ mkdir -p test/dir
  2254  		//   $ echo hello > test/file
  2255  		//   $ ln -s dir/../file test/link
  2256  		//   $ cat test/link/
  2257  		//   hello
  2258  		//   $ cat test/link/
  2259  		//   cat: test/link/: Not a directory
  2260  		//
  2261  		// Since Darwin isn't consistent with itself, we can't verify that we're
  2262  		// consistent with it.
  2263  		if rootTest.source.anySlashSuffix() || rootTest.target.anySlashSuffix() {
  2264  			return
  2265  		}
  2266  	}
  2267  
  2268  	if runtime.GOOS == "wasip1" || runtime.GOOS == "js" {
  2269  		// WASI runtimes don't have any consistent behavior for handling paths with
  2270  		// a trailing /, so skip consistency tests for these paths.
  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  		// os.Root forbids this operation (and is therefore not consistent with
  2304  		// the non-root version).
  2305  		return
  2306  	}
  2307  
  2308  	if rootErr == errSkipRootConsistencyCheck || osErr == errSkipRootConsistencyCheck {
  2309  		return
  2310  	}
  2311  
  2312  	// Consistency check: Performing the same operation in and out of a root
  2313  	// should produce the same results.
  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  	// Filesystem consistency check: Same files in the same places.
  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  // testFileKind is a kind of file.
  2383  type testFileKind int
  2384  
  2385  const (
  2386  	testFileUnused  = testFileKind(iota)
  2387  	testFileAbsent  // file does not exist
  2388  	testFileFile    // regular file
  2389  	testFileDir     // directory
  2390  	testFileSymlink // symlink
  2391  	testFileMax
  2392  
  2393  	// testFileError represents a path which fails during resolution,
  2394  	// such as "a/b" where "a" does not exist.
  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  // testFileRef is a kind of reference to a file.
  2418  //
  2419  // Many path names can refer to the same file: f, ./f, /abs/path/to/f, somedir/../f, etc.
  2420  // A testFileRef describes some form of reference.
  2421  type testFileRef struct {
  2422  	// name is the name of the reference (not the file name).
  2423  	// These are a bit cryptic to keep test names short:
  2424  	// s (/ slash), p (.. parent), b (base), d (directory), r (root)
  2425  	name string
  2426  
  2427  	// template is a template path.
  2428  	//
  2429  	// templates assume that the file is contained in a directory named "ROOT",
  2430  	// and that "ROOT/DIR" exists and is a directory.
  2431  	//
  2432  	// The string BASE in the template may be replaced with the file's basename.
  2433  	//
  2434  	// Absolute path templates start with /ROOT.
  2435  	template string
  2436  
  2437  	// escapes indicates whether the path escapes the current directory.
  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  // testFileLimitedRefs is a smaller set of references which do not exercise path escapes
  2453  // (see allTestFileDescs).
  2454  var testFileLimitedRefs = testFileRefs[0:2]
  2455  
  2456  // path creates a path using the template.
  2457  //
  2458  // dir is the absolute path to the root directory (which must be named "ROOT").
  2459  // base is the name of the target file within the root directory.
  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  // hasSlashSuffix reports whether the file reference ends in a /.
  2470  func (ref testFileRef) hasSlashSuffix() bool {
  2471  	return strings.HasSuffix(ref.template, "/")
  2472  }
  2473  
  2474  // testFileDesc is a description of a type of file, combining the kind and reference type.
  2475  //
  2476  // Some sample testFileDescs:
  2477  //   - "name", a plain file.
  2478  //   - "DIR/../name", a directory
  2479  //   - "name/", where name is a symlink to "DIR/../target/", where target is a plain file.
  2480  type testFileDesc struct {
  2481  	kind   testFileKind
  2482  	ref    testFileRef
  2483  	target *testFileDesc // symlink target, nil when kind is not testFileSymlink
  2484  }
  2485  
  2486  var rootComprehensive = flag.Bool("root_comprehensive", false,
  2487  	"run many more os.Root test variations (slow, uncertain value)")
  2488  
  2489  // allTestFileDescs returns an iterator over all the testFileDescs we use in tests.
  2490  func allTestFileDescs() iter.Seq[testFileDesc] {
  2491  	// A testFileDesc contains a reference type ("name", "d/../name", "../r/name", etc.) and
  2492  	// a file kind (file, directory, symlink, etc.).
  2493  	//
  2494  	// When the kind is symlink, the desc contains a reference type and file kind for
  2495  	// the link target as well. We only exercise one level of symlink (although we
  2496  	// could do more), so this means a testFileDesc effectively contains four axes of
  2497  	// variation: ref, kind, symlink ref, symlink kind.
  2498  	//
  2499  	// For example:
  2500  	//
  2501  	//   - "name" is a file
  2502  	//   - "d/../name" is a directory
  2503  	//   - "name" is a symlink to "name2" which is a file
  2504  	//   - "d/../name" is a symlink to "d/../name2" which is a directory
  2505  	//   - etc.
  2506  	//
  2507  	// It is feasible to test every possible variation of these four axes,
  2508  	// but this is quite a few tests and gets quite slow. So by default we exclude
  2509  	// some variations. We test:
  2510  	//
  2511  	//   - every reference to every kind, except symlink
  2512  	//   - direct and direct/ references to a symlink to every reference to a file
  2513  	//   - a direct reference to a symlink to a direct reference to every kind (except file)
  2514  	//
  2515  	// The full set of variations may be enabled with the -comprehensive_root_tests flag.
  2516  
  2517  	return func(yield func(testFileDesc) bool) {
  2518  		// Every type of reference to every type of file, except symlink.
  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  		// Unless we're being comprehensive, only direct references to symlinks.
  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  // String returns the target name.
  2568  //
  2569  // These are somewhat cryptic to keep test names short.
  2570  // For example, "bsSdpbD" is:
  2571  //
  2572  //	bs  - "BASE/"
  2573  //	S   - symlink
  2574  //	dpb - "DIR/../BASE"
  2575  //	D   - directory
  2576  //
  2577  // So, open "file1/", where file1 is a symlink to "DIR/../file2", where file2 is a directory.
  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  // escapes reports whether accessing this file escapes the root,
  2587  // either because the file name escapes or because some element of a symlink chain escapes.
  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  		// On POSIX filesystems, a trailing slash at the end of a path causes
  2604  		// symlinks in the last path component to be resolved.
  2605  		// On Windows, a trailing slash does not cause symlink resolution.
  2606  		return false
  2607  	}
  2608  	if desc.ref.hasSlashSuffix() && desc.kind == testFileSymlink {
  2609  		return desc.target.escapes()
  2610  	}
  2611  	return false
  2612  }
  2613  
  2614  // finalKind reports the kind of the file after following all symlinks.
  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  // anySlashSuffix reports whether any of the names in the file
  2677  // (either the initial name, or a symlink target)
  2678  // include a trailing /.
  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  // anySlashSuffix reports whether the name of the file includes a trailing /.
  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  // create creates the file(s) for this descriptor.
  2700  //
  2701  // dir is the test root directory.
  2702  // base is the base name of the file we will open within the root.
  2703  // (If there are symlinks, base is the start of the symlink chain.)
  2704  //
  2705  // Tests may create, delete, or move files, which makes it useful to have a way to identify
  2706  // and track the files that existed at the start of the test. The token parameter identifies
  2707  // which file we're creating. When symlinks are involved, the token is used in creating the
  2708  // final, non-symlink file.
  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  		// File does not exist.
  2714  	case testFileFile:
  2715  		// Regular file. We use the token as the file contents.
  2716  		if err := os.WriteFile(path, []byte(token), 0o666); err != nil {
  2717  			t.Fatal(err)
  2718  		}
  2719  	case testFileDir:
  2720  		// Directory. We create a subdir within the directory named "c_"+token.
  2721  		// (The "c_" prefix is to distinguish this subdir from any files that may
  2722  		// have the same name as the token.)
  2723  		if err := os.Mkdir(path, 0o777); err != nil {
  2724  			t.Fatal(err)
  2725  		}
  2726  	case testFileSymlink:
  2727  		// Symlink. We create a symlink target named "s_"+base.
  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  // testRootDescribeFile returns a string identifying a file.
  2753  //
  2754  // It returns "" if f is nil.
  2755  // It returns "source" or "target" if f is the source or target file in the test.
  2756  // Otherwise, it returns "unknown file".
  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  // dirTreeContents returns a description of the contents of directory.
  2776  // For example:
  2777  //
  2778  //	drwxrwxrwx dir/
  2779  //	-rw-rw-rw- dir/file "file contents"
  2780  //	Lrw-rw-rw- symlink => dir/file
  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  // TestRootMultiOpen tests os.Root.Open.
  2830  //
  2831  // This also serves as a prototypical example of using rootMultiTest
  2832  // (see also the doc comment on rootMultiTest above).
  2833  func TestRootMultiOpen(t *testing.T) {
  2834  	runRootMultiTest(t, func(t *testing.T, test *rootMultiTest) (string, error) {
  2835  		// This function will be run many times, with different inputs:
  2836  		//   - in and out of a Root
  2837  		//   - opening a file, directory, symlink, or nothing at all
  2838  		//   - opening various names: target, DIR/../target, /abs/path/to/target, etc.
  2839  		//
  2840  		// The test function should perform the requested operation
  2841  		// (for example: open "target" in a Root),
  2842  		// verify that the result is consistent with expectations,
  2843  		// and then return a description of the result.
  2844  		//
  2845  		// The returned description is used to validate consistent behavior
  2846  		// between operations in and out of a Root.
  2847  		var open = os.Open
  2848  		if test.root != nil {
  2849  			open = test.root.Open
  2850  		}
  2851  
  2852  		test.setOp("Open(%q)", test.targetPath) // test's operation, for errors
  2853  		f, gotErr := open(test.targetPath)
  2854  		if gotErr == nil {
  2855  			defer f.Close()
  2856  		}
  2857  
  2858  		// testRootDescribeFile returns a string identifying a file.
  2859  		//
  2860  		// This is always "source" or "target" for the source/target files in a test,
  2861  		// or "" if f is nil.
  2862  		// (Note that most tests use only a target file, no source.)
  2863  		got := test.describeFile(t, f)
  2864  
  2865  		switch {
  2866  		case test.root != nil && test.target.escapes():
  2867  			// The operation escapes the root.
  2868  			test.wantError(t, gotErr, os.ErrPathEscapes)
  2869  		case test.target.finalKind() == testFileAbsent:
  2870  			// The file does not exist ("absent").
  2871  			test.wantError(t, gotErr, errAny)
  2872  		case test.target.anySlashSuffix():
  2873  			// The file name or a symlink target contain a trailing slash.
  2874  			// Trailing slashes are handled differently on different platforms,
  2875  			// so we won't try to assert an outcome when they are present.
  2876  			// runRootMultiTest will verify that root.Open and os.Open
  2877  			// produce consistent results.
  2878  		default:
  2879  			// We should have successfully opened the file.
  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  		// Return the name of the file opened (possibly "" for nothing) and the error.
  2887  		// runRootMultiTest will compare the results for in-a-root and out-of-a-root
  2888  		// to validate that they are the same.
  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  		// Using the wrong mode here can cause problems during test cleanup,
  2910  		// if we leave a temp dir with a mode that prevents listing or removing
  2911  		// its contents.
  2912  		//
  2913  		// read+execute permissions let us list directory contents,
  2914  		// and we restore writability before deleting the temp dir.
  2915  		wantMode := os.FileMode(0o500) // readable, executable
  2916  		if runtime.GOOS == "windows" {
  2917  			// On Windows, the only modes we support are the default (777/rwx)
  2918  			// or read-only (444/r-x). Making a directory read-only doesn't prevent
  2919  			// listing its contents, so we can use 444 here.
  2920  			wantMode = 0o444 // readable
  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  			// On Windows, Chmod("symlink") affects the link, not its target.
  2933  			// See issue #71492.
  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  			// Don't expect anything, just be consistent with the OS.
  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  				// See issue #71492.
  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  			// On Windows, os.Chmod calls GetFileAttributes on the target.
  2967  			// This seems to fail in a number of situations where the os.Root
  2968  			// chmod path works. For now, just skip the consistency check
  2969  			// when os.Chmod fails.
  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) // test's operation, for errors
  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  			// The error here is because the link is a Windows directory link,
  2995  			// not because the link target is a directory.
  2996  			test.wantError(t, gotErr, errAny)
  2997  		case test.root != nil && test.target.escapes():
  2998  			// The operation escapes the root.
  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  			// This test fails when run with wasmtime, because os.RemoveAll fails
  3012  			// to remove the test tempdir.
  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  			// os.Link(old, new) may or may not deference old when it is a symlink.
  3035  			// POSIX says that link(2) should deference the source, but implementations
  3036  			// are inconsistent.
  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  			// When the target of lstat has a trailing slash,
  3066  			// Windows follows it.
  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  			// Target does not exist.
  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  			// "mkdir ../target", or equivalent escaping path.
  3110  			test.wantError(t, gotErr, os.ErrPathEscapes)
  3111  		case test.target.slashSuffix() && test.target.kind == testFileSymlink:
  3112  			// "mkdir symlink/", inconsistent behavior across platforms
  3113  			// as to whether this follows the symlink or not.
  3114  			//
  3115  			// If the symlink escapes, this needs to be some kind of error though.
  3116  			if test.root != nil && test.target.escapes() {
  3117  				test.wantError(t, gotErr, errAny)
  3118  			}
  3119  			if runtime.GOOS == "openbsd" {
  3120  				// Known inconsistency: OpenBSD doesn't resolve the final
  3121  				// symlink when creating a directory.
  3122  				return "", errSkipRootConsistencyCheck
  3123  			}
  3124  		case test.target.kind != testFileAbsent:
  3125  			// "mkdir target", where target exists.
  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  		// "mkdir ../target", or equivalent escaping path.
  3171  		test.wantError(t, gotErr, os.ErrPathEscapes)
  3172  	case test.root != nil && test.target.escapes():
  3173  		// "mkdir ../target", or equivalent escaping path.
  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  		// A minor inconsistency between Root.MkdirAll and os.MkdirAll:
  3178  		// When an intermediate component of the tree being constructed is a
  3179  		// dangling symlink, Root.MkdirAll will follow the symlink and create
  3180  		// its target directory, while os.MkdirAll will fail with an error.
  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  			// This test fails when run with wasmtime, because os.RemoveAll fails
  3192  			// to remove the test tempdir.
  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  		// TODO: target directory (if any) should be empty
  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  			// os.Rename on Windows is implemented using MoveFileEx,
  3212  			// while Root.Rename is implemented using NtSetInformationFileEx
  3213  			// with an explicit request for POSIX semantics.
  3214  			//
  3215  			// This means the two do not behave the same when renaming
  3216  			// a file onto a directory or vice-versa.
  3217  			//
  3218  			// We should make this consistent, but for now just skip
  3219  			// the consistency checks in this case.
  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  			// Plan9 rename behaves differently.
  3234  			// Just rely on consistency checks.
  3235  		case test.target.lfinalKind() == testFileDir:
  3236  			// POSIX rename() will replace an empty target directory,
  3237  			// but os.Rename will not.
  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  				// Known inconsistency: OpenBSD doesn't resolve the final
  3244  				// symlink when creating a directory.
  3245  				return "", errSkipRootConsistencyCheck
  3246  			}
  3247  		default:
  3248  			test.wantError(t, gotErr, nil)
  3249  			// TODO: check that the file is in its new location
  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  			// Plan9 lets you read from directories.
  3281  			// Just rely on consistency checks.
  3282  		case runtime.GOOS == "netbsd":
  3283  			// See https://go.dev/issue/80322:
  3284  			// NetBSD builder appears to be succeeding on read-from-dir as well.
  3285  			return "", gotErr
  3286  		case test.target.finalKind() == testFileDir:
  3287  			test.wantError(t, gotErr, errAny)
  3288  		case test.target.anySlashSuffix():
  3289  			// Trailing slashes are handled differently on different platforms,
  3290  			// so we won't try to assert an outcome when they are present.
  3291  			// runRootMultiTest will verify that root.ReadFile and os.ReadFile
  3292  			// produce consistent results.
  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  				// Linux treats rmdir("symlink/") as an error when
  3350  				// "symlink" is a symlink to a directory.
  3351  				// Root.Remove prefers the POSIX interpretation
  3352  				// of resolving the symlink.
  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  			// This is only checking target.ref.escapes,
  3375  			// not target.lescapes(), because RemoveAll strips
  3376  			// terminal slashes.
  3377  			test.wantError(t, gotErr, os.ErrPathEscapes)
  3378  		case test.target.anySlashSuffix():
  3379  			// We are inconsistent on some platforms on whether
  3380  			// RemoveAll("symlink/") removes the link or the link target.
  3381  			// Something worth addressing, but for now skip the check.
  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  			// Replace absolute path prefix with /.../
  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