| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | // Copyright 2024 The Go Authors. All rights reserved. | 
					
						
							|  |  |  | // Use of this source code is governed by a BSD-style | 
					
						
							|  |  |  | // license that can be found in the LICENSE file. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package os_test | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"bytes" | 
					
						
							|  |  |  | 	"errors" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2025-03-20 12:41:21 -07:00
										 |  |  | 	"internal/testenv" | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | 	"io" | 
					
						
							|  |  |  | 	"io/fs" | 
					
						
							|  |  |  | 	"net" | 
					
						
							|  |  |  | 	"os" | 
					
						
							|  |  |  | 	"path" | 
					
						
							|  |  |  | 	"path/filepath" | 
					
						
							|  |  |  | 	"runtime" | 
					
						
							|  |  |  | 	"slices" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 	"testing" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // testMaybeRooted calls f in two subtests, | 
					
						
							|  |  |  | // one with a Root and one with a nil r. | 
					
						
							|  |  |  | func testMaybeRooted(t *testing.T, f func(t *testing.T, r *os.Root)) { | 
					
						
							|  |  |  | 	t.Run("NoRoot", func(t *testing.T) { | 
					
						
							|  |  |  | 		t.Chdir(t.TempDir()) | 
					
						
							|  |  |  | 		f(t, nil) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	t.Run("InRoot", func(t *testing.T) { | 
					
						
							|  |  |  | 		t.Chdir(t.TempDir()) | 
					
						
							|  |  |  | 		r, err := os.OpenRoot(".") | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatal(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		defer r.Close() | 
					
						
							|  |  |  | 		f(t, r) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // makefs creates a test filesystem layout and returns the path to its root. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // Each entry in the slice is a file, directory, or symbolic link to create: | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | //   - "d/": directory d | 
					
						
							|  |  |  | //   - "f": file f with contents f | 
					
						
							|  |  |  | //   - "a => b": symlink a with target b | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // The directory containing the filesystem is always named ROOT. | 
					
						
							|  |  |  | // $ABS is replaced with the absolute path of the directory containing the filesystem. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // Parent directories are automatically created as needed. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // makefs calls t.Skip if the layout contains features not supported by the current GOOS. | 
					
						
							|  |  |  | func makefs(t *testing.T, fs []string) string { | 
					
						
							|  |  |  | 	root := path.Join(t.TempDir(), "ROOT") | 
					
						
							|  |  |  | 	if err := os.Mkdir(root, 0o777); err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for _, ent := range fs { | 
					
						
							|  |  |  | 		ent = strings.ReplaceAll(ent, "$ABS", root) | 
					
						
							|  |  |  | 		base, link, isLink := strings.Cut(ent, " => ") | 
					
						
							|  |  |  | 		if isLink { | 
					
						
							|  |  |  | 			if runtime.GOOS == "wasip1" && path.IsAbs(link) { | 
					
						
							|  |  |  | 				t.Skip("absolute link targets not supported on " + runtime.GOOS) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if runtime.GOOS == "plan9" { | 
					
						
							|  |  |  | 				t.Skip("symlinks not supported on " + runtime.GOOS) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			ent = base | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if err := os.MkdirAll(path.Join(root, path.Dir(base)), 0o777); err != nil { | 
					
						
							|  |  |  | 			t.Fatal(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if isLink { | 
					
						
							|  |  |  | 			if err := os.Symlink(link, path.Join(root, base)); err != nil { | 
					
						
							|  |  |  | 				t.Fatal(err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else if strings.HasSuffix(ent, "/") { | 
					
						
							|  |  |  | 			if err := os.MkdirAll(path.Join(root, ent), 0o777); err != nil { | 
					
						
							|  |  |  | 				t.Fatal(err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			if err := os.WriteFile(path.Join(root, ent), []byte(ent), 0o666); err != nil { | 
					
						
							|  |  |  | 				t.Fatal(err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return root | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // A rootTest is a test case for os.Root. | 
					
						
							|  |  |  | type rootTest struct { | 
					
						
							|  |  |  | 	name string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// fs is the test filesystem layout. See makefs above. | 
					
						
							|  |  |  | 	fs []string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// open is the filename to access in the test. | 
					
						
							|  |  |  | 	open string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// target is the filename that we expect to be accessed, after resolving all symlinks. | 
					
						
							|  |  |  | 	// For test cases where the operation fails due to an escaping path such as ../ROOT/x, | 
					
						
							|  |  |  | 	// the target is the filename that should not have been opened. | 
					
						
							|  |  |  | 	target string | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-12 17:16:10 +01:00
										 |  |  | 	// ltarget is the filename that we expect to accessed, after resolving all symlinks | 
					
						
							|  |  |  | 	// except the last one. This is the file we expect to be removed by Remove or statted | 
					
						
							|  |  |  | 	// by Lstat. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// If the last path component in open is not a symlink, ltarget should be "". | 
					
						
							|  |  |  | 	ltarget string | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | 	// wantError is true if accessing the file should fail. | 
					
						
							|  |  |  | 	wantError bool | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// alwaysFails is true if the open operation is expected to fail | 
					
						
							|  |  |  | 	// even when using non-openat operations. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// This lets us check that tests that are expected to fail because (for example) | 
					
						
							|  |  |  | 	// a path escapes the directory root will succeed when the escaping checks are not | 
					
						
							|  |  |  | 	// performed. | 
					
						
							|  |  |  | 	alwaysFails bool | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // run sets up the test filesystem layout, os.OpenDirs the root, and calls f. | 
					
						
							|  |  |  | func (test *rootTest) run(t *testing.T, f func(t *testing.T, target string, d *os.Root)) { | 
					
						
							|  |  |  | 	t.Run(test.name, func(t *testing.T) { | 
					
						
							|  |  |  | 		root := makefs(t, test.fs) | 
					
						
							|  |  |  | 		d, err := os.OpenRoot(root) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatal(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		defer d.Close() | 
					
						
							|  |  |  | 		// The target is a file that will be accessed, | 
					
						
							|  |  |  | 		// or a file that should not be accessed | 
					
						
							|  |  |  | 		// (because doing so escapes the root). | 
					
						
							|  |  |  | 		target := test.target | 
					
						
							|  |  |  | 		if test.target != "" { | 
					
						
							|  |  |  | 			target = filepath.Join(root, test.target) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		f(t, target, d) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // errEndsTest checks the error result of a test, | 
					
						
							|  |  |  | // verifying that it succeeded or failed as expected. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // It returns true if the test is done due to encountering an expected error. | 
					
						
							|  |  |  | // false if the test should continue. | 
					
						
							|  |  |  | func errEndsTest(t *testing.T, err error, wantError bool, format string, args ...any) bool { | 
					
						
							|  |  |  | 	t.Helper() | 
					
						
							|  |  |  | 	if wantError { | 
					
						
							|  |  |  | 		if err == nil { | 
					
						
							|  |  |  | 			op := fmt.Sprintf(format, args...) | 
					
						
							|  |  |  | 			t.Fatalf("%v = nil; want error", op) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			op := fmt.Sprintf(format, args...) | 
					
						
							|  |  |  | 			t.Fatalf("%v = %v; want success", op, err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var rootTestCases = []rootTest{{ | 
					
						
							|  |  |  | 	name:   "plain path", | 
					
						
							|  |  |  | 	fs:     []string{}, | 
					
						
							|  |  |  | 	open:   "target", | 
					
						
							|  |  |  | 	target: "target", | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "path in directory", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"a/b/c/", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open:   "a/b/c/target", | 
					
						
							|  |  |  | 	target: "a/b/c/target", | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "symlink", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"link => target", | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2024-11-12 17:16:10 +01:00
										 |  |  | 	open:    "link", | 
					
						
							|  |  |  | 	target:  "target", | 
					
						
							|  |  |  | 	ltarget: "link", | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | }, { | 
					
						
							|  |  |  | 	name: "symlink chain", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"link => a/b/c/target", | 
					
						
							|  |  |  | 		"a/b => e", | 
					
						
							|  |  |  | 		"a/e => ../f", | 
					
						
							|  |  |  | 		"f => g/h/i", | 
					
						
							|  |  |  | 		"g/h/i => ..", | 
					
						
							|  |  |  | 		"g/c/", | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2024-11-12 17:16:10 +01:00
										 |  |  | 	open:    "link", | 
					
						
							|  |  |  | 	target:  "g/c/target", | 
					
						
							|  |  |  | 	ltarget: "link", | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | }, { | 
					
						
							|  |  |  | 	name: "path with dot", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"a/b/", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open:   "./a/./b/./target", | 
					
						
							|  |  |  | 	target: "a/b/target", | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "path with dotdot", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"a/b/", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open:   "a/../a/b/../../a/b/../b/target", | 
					
						
							|  |  |  | 	target: "a/b/target", | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "dotdot no symlink", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"a/", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open:   "a/../target", | 
					
						
							|  |  |  | 	target: "target", | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "dotdot after symlink", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"a => b/c", | 
					
						
							|  |  |  | 		"b/c/", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open: "a/../target", | 
					
						
							|  |  |  | 	target: func() string { | 
					
						
							|  |  |  | 		if runtime.GOOS == "windows" { | 
					
						
							|  |  |  | 			// On Windows, the path is cleaned before symlink resolution. | 
					
						
							|  |  |  | 			return "target" | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return "b/target" | 
					
						
							|  |  |  | 	}(), | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "dotdot before symlink", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"a => b/c", | 
					
						
							|  |  |  | 		"b/c/", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open:   "b/../a/target", | 
					
						
							|  |  |  | 	target: "b/c/target", | 
					
						
							| 
									
										
										
										
											2024-11-21 09:20:46 -08:00
										 |  |  | }, { | 
					
						
							|  |  |  | 	name: "symlink ends in dot", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"a => b/.", | 
					
						
							|  |  |  | 		"b/", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open:   "a/target", | 
					
						
							|  |  |  | 	target: "b/target", | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | }, { | 
					
						
							|  |  |  | 	name:        "directory does not exist", | 
					
						
							|  |  |  | 	fs:          []string{}, | 
					
						
							|  |  |  | 	open:        "a/file", | 
					
						
							|  |  |  | 	wantError:   true, | 
					
						
							|  |  |  | 	alwaysFails: true, | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name:        "empty path", | 
					
						
							|  |  |  | 	fs:          []string{}, | 
					
						
							|  |  |  | 	open:        "", | 
					
						
							|  |  |  | 	wantError:   true, | 
					
						
							|  |  |  | 	alwaysFails: true, | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "symlink cycle", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"a => a", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open:        "a", | 
					
						
							| 
									
										
										
										
											2024-11-12 17:16:10 +01:00
										 |  |  | 	ltarget:     "a", | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | 	wantError:   true, | 
					
						
							|  |  |  | 	alwaysFails: true, | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name:      "path escapes", | 
					
						
							|  |  |  | 	fs:        []string{}, | 
					
						
							|  |  |  | 	open:      "../ROOT/target", | 
					
						
							|  |  |  | 	target:    "target", | 
					
						
							|  |  |  | 	wantError: true, | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "long path escapes", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"a/", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open:      "a/../../ROOT/target", | 
					
						
							|  |  |  | 	target:    "target", | 
					
						
							|  |  |  | 	wantError: true, | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "absolute symlink", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"link => $ABS/target", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open:      "link", | 
					
						
							| 
									
										
										
										
											2024-11-12 17:16:10 +01:00
										 |  |  | 	ltarget:   "link", | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | 	target:    "target", | 
					
						
							|  |  |  | 	wantError: true, | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "relative symlink", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"link => ../ROOT/target", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open:      "link", | 
					
						
							|  |  |  | 	target:    "target", | 
					
						
							| 
									
										
										
										
											2024-11-12 17:16:10 +01:00
										 |  |  | 	ltarget:   "link", | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | 	wantError: true, | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "symlink chain escapes", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"link => a/b/c/target", | 
					
						
							|  |  |  | 		"a/b => e", | 
					
						
							|  |  |  | 		"a/e => ../../ROOT", | 
					
						
							|  |  |  | 		"c/", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open:      "link", | 
					
						
							|  |  |  | 	target:    "c/target", | 
					
						
							| 
									
										
										
										
											2024-11-12 17:16:10 +01:00
										 |  |  | 	ltarget:   "link", | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | 	wantError: true, | 
					
						
							|  |  |  | }} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestRootOpen_File(t *testing.T) { | 
					
						
							|  |  |  | 	want := []byte("target") | 
					
						
							|  |  |  | 	for _, test := range rootTestCases { | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, target string, root *os.Root) { | 
					
						
							|  |  |  | 			if target != "" { | 
					
						
							|  |  |  | 				if err := os.WriteFile(target, want, 0o666); err != nil { | 
					
						
							|  |  |  | 					t.Fatal(err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			f, err := root.Open(test.open) | 
					
						
							|  |  |  | 			if errEndsTest(t, err, test.wantError, "root.Open(%q)", test.open) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			defer f.Close() | 
					
						
							|  |  |  | 			got, err := io.ReadAll(f) | 
					
						
							|  |  |  | 			if err != nil || !bytes.Equal(got, want) { | 
					
						
							|  |  |  | 				t.Errorf(`Dir.Open(%q): read content %q, %v; want %q`, test.open, string(got), err, string(want)) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestRootOpen_Directory(t *testing.T) { | 
					
						
							|  |  |  | 	for _, test := range rootTestCases { | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, target string, root *os.Root) { | 
					
						
							|  |  |  | 			if target != "" { | 
					
						
							|  |  |  | 				if err := os.Mkdir(target, 0o777); err != nil { | 
					
						
							|  |  |  | 					t.Fatal(err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				if err := os.WriteFile(target+"/found", nil, 0o666); err != nil { | 
					
						
							|  |  |  | 					t.Fatal(err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			f, err := root.Open(test.open) | 
					
						
							|  |  |  | 			if errEndsTest(t, err, test.wantError, "root.Open(%q)", test.open) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			defer f.Close() | 
					
						
							|  |  |  | 			got, err := f.Readdirnames(-1) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Errorf(`Dir.Open(%q).Readdirnames: %v`, test.open, err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if want := []string{"found"}; !slices.Equal(got, want) { | 
					
						
							|  |  |  | 				t.Errorf(`Dir.Open(%q).Readdirnames: %q, want %q`, test.open, got, want) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestRootCreate(t *testing.T) { | 
					
						
							|  |  |  | 	want := []byte("target") | 
					
						
							|  |  |  | 	for _, test := range rootTestCases { | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, target string, root *os.Root) { | 
					
						
							|  |  |  | 			f, err := root.Create(test.open) | 
					
						
							|  |  |  | 			if errEndsTest(t, err, test.wantError, "root.Create(%q)", test.open) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if _, err := f.Write(want); err != nil { | 
					
						
							|  |  |  | 				t.Fatal(err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			f.Close() | 
					
						
							|  |  |  | 			got, err := os.ReadFile(target) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatalf(`reading file created with root.Create(%q): %v`, test.open, err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if !bytes.Equal(got, want) { | 
					
						
							|  |  |  | 				t.Fatalf(`reading file created with root.Create(%q): got %q; want %q`, test.open, got, want) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-30 15:53:06 -08:00
										 |  |  | func TestRootChmod(t *testing.T) { | 
					
						
							|  |  |  | 	if runtime.GOOS == "wasip1" { | 
					
						
							|  |  |  | 		t.Skip("Chmod not supported on " + runtime.GOOS) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for _, test := range rootTestCases { | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, target string, root *os.Root) { | 
					
						
							|  |  |  | 			if target != "" { | 
					
						
							|  |  |  | 				// Create a file with no read/write permissions, | 
					
						
							|  |  |  | 				// to ensure we can use Chmod on an inaccessible file. | 
					
						
							|  |  |  | 				if err := os.WriteFile(target, nil, 0o000); err != nil { | 
					
						
							|  |  |  | 					t.Fatal(err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if runtime.GOOS == "windows" { | 
					
						
							|  |  |  | 				// On Windows, Chmod("symlink") affects the link, not its target. | 
					
						
							|  |  |  | 				// See issue 71492. | 
					
						
							|  |  |  | 				fi, err := root.Lstat(test.open) | 
					
						
							|  |  |  | 				if err == nil && !fi.Mode().IsRegular() { | 
					
						
							|  |  |  | 					t.Skip("https://go.dev/issue/71492") | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			want := os.FileMode(0o666) | 
					
						
							|  |  |  | 			err := root.Chmod(test.open, want) | 
					
						
							|  |  |  | 			if errEndsTest(t, err, test.wantError, "root.Chmod(%q)", test.open) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			st, err := os.Stat(target) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatalf("os.Stat(%q) = %v", target, err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if got := st.Mode(); got != want { | 
					
						
							|  |  |  | 				t.Errorf("after root.Chmod(%q, %v): file mode = %v, want %v", test.open, want, got, want) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-11 10:47:20 -08:00
										 |  |  | func TestRootChtimes(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2025-03-20 09:30:19 -07:00
										 |  |  | 	// Don't check atimes if the fs is mounted noatime, | 
					
						
							|  |  |  | 	// or on Plan 9 which does not permit changing atimes to arbitrary values. | 
					
						
							|  |  |  | 	checkAtimes := !hasNoatime() && runtime.GOOS != "plan9" | 
					
						
							| 
									
										
										
										
											2025-02-11 10:47:20 -08:00
										 |  |  | 	for _, test := range rootTestCases { | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, target string, root *os.Root) { | 
					
						
							|  |  |  | 			if target != "" { | 
					
						
							|  |  |  | 				if err := os.WriteFile(target, nil, 0o666); err != nil { | 
					
						
							|  |  |  | 					t.Fatal(err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			for _, times := range []struct { | 
					
						
							|  |  |  | 				atime, mtime time.Time | 
					
						
							|  |  |  | 			}{{ | 
					
						
							|  |  |  | 				atime: time.Now().Add(-1 * time.Minute), | 
					
						
							|  |  |  | 				mtime: time.Now().Add(-1 * time.Minute), | 
					
						
							|  |  |  | 			}, { | 
					
						
							|  |  |  | 				atime: time.Now().Add(1 * time.Minute), | 
					
						
							|  |  |  | 				mtime: time.Now().Add(1 * time.Minute), | 
					
						
							|  |  |  | 			}, { | 
					
						
							|  |  |  | 				atime: time.Time{}, | 
					
						
							|  |  |  | 				mtime: time.Now(), | 
					
						
							|  |  |  | 			}, { | 
					
						
							|  |  |  | 				atime: time.Now(), | 
					
						
							|  |  |  | 				mtime: time.Time{}, | 
					
						
							|  |  |  | 			}} { | 
					
						
							| 
									
										
										
										
											2025-03-19 16:37:16 -07:00
										 |  |  | 				switch runtime.GOOS { | 
					
						
							|  |  |  | 				case "js", "plan9": | 
					
						
							| 
									
										
										
										
											2025-02-11 10:47:20 -08:00
										 |  |  | 					times.atime = times.atime.Truncate(1 * time.Second) | 
					
						
							|  |  |  | 					times.mtime = times.mtime.Truncate(1 * time.Second) | 
					
						
							| 
									
										
										
										
											2025-04-11 14:11:37 +02:00
										 |  |  | 				case "illumos": | 
					
						
							|  |  |  | 					times.atime = times.atime.Truncate(1 * time.Microsecond) | 
					
						
							|  |  |  | 					times.mtime = times.mtime.Truncate(1 * time.Microsecond) | 
					
						
							| 
									
										
										
										
											2025-02-11 10:47:20 -08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				err := root.Chtimes(test.open, times.atime, times.mtime) | 
					
						
							|  |  |  | 				if errEndsTest(t, err, test.wantError, "root.Chtimes(%q)", test.open) { | 
					
						
							|  |  |  | 					return | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				st, err := os.Stat(target) | 
					
						
							|  |  |  | 				if err != nil { | 
					
						
							|  |  |  | 					t.Fatalf("os.Stat(%q) = %v", target, err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				if got := st.ModTime(); !times.mtime.IsZero() && !got.Equal(times.mtime) { | 
					
						
							|  |  |  | 					t.Errorf("after root.Chtimes(%q, %v, %v): got mtime=%v, want %v", test.open, times.atime, times.mtime, got, times.mtime) | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2025-03-20 09:30:19 -07:00
										 |  |  | 				if checkAtimes { | 
					
						
							| 
									
										
										
										
											2025-03-19 16:37:16 -07:00
										 |  |  | 					if got := os.Atime(st); !times.atime.IsZero() && !got.Equal(times.atime) { | 
					
						
							|  |  |  | 						t.Errorf("after root.Chtimes(%q, %v, %v): got atime=%v, want %v", test.open, times.atime, times.mtime, got, times.atime) | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2025-02-11 10:47:20 -08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | func TestRootMkdir(t *testing.T) { | 
					
						
							|  |  |  | 	for _, test := range rootTestCases { | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, target string, root *os.Root) { | 
					
						
							|  |  |  | 			wantError := test.wantError | 
					
						
							|  |  |  | 			if !wantError { | 
					
						
							|  |  |  | 				fi, err := os.Lstat(filepath.Join(root.Name(), test.open)) | 
					
						
							|  |  |  | 				if err == nil && fi.Mode().Type() == fs.ModeSymlink { | 
					
						
							|  |  |  | 					// This case is trying to mkdir("some symlink"), | 
					
						
							|  |  |  | 					// which is an error. | 
					
						
							|  |  |  | 					wantError = true | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			err := root.Mkdir(test.open, 0o777) | 
					
						
							|  |  |  | 			if errEndsTest(t, err, wantError, "root.Create(%q)", test.open) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			fi, err := os.Lstat(target) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatalf(`stat file created with Root.Mkdir(%q): %v`, test.open, err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if !fi.IsDir() { | 
					
						
							|  |  |  | 				t.Fatalf(`stat file created with Root.Mkdir(%q): not a directory`, test.open) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestRootOpenRoot(t *testing.T) { | 
					
						
							|  |  |  | 	for _, test := range rootTestCases { | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, target string, root *os.Root) { | 
					
						
							|  |  |  | 			if target != "" { | 
					
						
							|  |  |  | 				if err := os.Mkdir(target, 0o777); err != nil { | 
					
						
							|  |  |  | 					t.Fatal(err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				if err := os.WriteFile(target+"/f", nil, 0o666); err != nil { | 
					
						
							|  |  |  | 					t.Fatal(err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			rr, err := root.OpenRoot(test.open) | 
					
						
							|  |  |  | 			if errEndsTest(t, err, test.wantError, "root.OpenRoot(%q)", test.open) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			defer rr.Close() | 
					
						
							|  |  |  | 			f, err := rr.Open("f") | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatalf(`root.OpenRoot(%q).Open("f") = %v`, test.open, err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			f.Close() | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-12 17:16:10 +01:00
										 |  |  | func TestRootRemoveFile(t *testing.T) { | 
					
						
							|  |  |  | 	for _, test := range rootTestCases { | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, target string, root *os.Root) { | 
					
						
							|  |  |  | 			wantError := test.wantError | 
					
						
							|  |  |  | 			if test.ltarget != "" { | 
					
						
							|  |  |  | 				// Remove doesn't follow symlinks in the final path component, | 
					
						
							|  |  |  | 				// so it will successfully remove ltarget. | 
					
						
							|  |  |  | 				wantError = false | 
					
						
							|  |  |  | 				target = filepath.Join(root.Name(), test.ltarget) | 
					
						
							|  |  |  | 			} else if target != "" { | 
					
						
							|  |  |  | 				if err := os.WriteFile(target, nil, 0o666); err != nil { | 
					
						
							|  |  |  | 					t.Fatal(err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			err := root.Remove(test.open) | 
					
						
							|  |  |  | 			if errEndsTest(t, err, wantError, "root.Remove(%q)", test.open) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			_, err = os.Lstat(target) | 
					
						
							|  |  |  | 			if !errors.Is(err, os.ErrNotExist) { | 
					
						
							|  |  |  | 				t.Fatalf(`stat file removed with Root.Remove(%q): %v, want ErrNotExist`, test.open, err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestRootRemoveDirectory(t *testing.T) { | 
					
						
							|  |  |  | 	for _, test := range rootTestCases { | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, target string, root *os.Root) { | 
					
						
							|  |  |  | 			wantError := test.wantError | 
					
						
							|  |  |  | 			if test.ltarget != "" { | 
					
						
							|  |  |  | 				// Remove doesn't follow symlinks in the final path component, | 
					
						
							|  |  |  | 				// so it will successfully remove ltarget. | 
					
						
							|  |  |  | 				wantError = false | 
					
						
							|  |  |  | 				target = filepath.Join(root.Name(), test.ltarget) | 
					
						
							|  |  |  | 			} else if target != "" { | 
					
						
							|  |  |  | 				if err := os.Mkdir(target, 0o777); err != nil { | 
					
						
							|  |  |  | 					t.Fatal(err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			err := root.Remove(test.open) | 
					
						
							|  |  |  | 			if errEndsTest(t, err, wantError, "root.Remove(%q)", test.open) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			_, err = os.Lstat(target) | 
					
						
							|  |  |  | 			if !errors.Is(err, os.ErrNotExist) { | 
					
						
							|  |  |  | 				t.Fatalf(`stat file removed with Root.Remove(%q): %v, want ErrNotExist`, test.open, err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | func TestRootOpenFileAsRoot(t *testing.T) { | 
					
						
							|  |  |  | 	dir := t.TempDir() | 
					
						
							|  |  |  | 	target := filepath.Join(dir, "target") | 
					
						
							|  |  |  | 	if err := os.WriteFile(target, nil, 0o666); err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-03-06 16:25:51 +01:00
										 |  |  | 	r, err := os.OpenRoot(target) | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | 	if err == nil { | 
					
						
							| 
									
										
										
										
											2025-03-06 16:25:51 +01:00
										 |  |  | 		r.Close() | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | 		t.Fatal("os.OpenRoot(file) succeeded; want failure") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-03-06 16:25:51 +01:00
										 |  |  | 	r, err = os.OpenRoot(dir) | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer r.Close() | 
					
						
							| 
									
										
										
										
											2025-03-06 16:25:51 +01:00
										 |  |  | 	rr, err := r.OpenRoot("target") | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | 	if err == nil { | 
					
						
							| 
									
										
										
										
											2025-03-06 16:25:51 +01:00
										 |  |  | 		rr.Close() | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | 		t.Fatal("Root.OpenRoot(file) succeeded; want failure") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-13 14:13:57 +01:00
										 |  |  | func TestRootStat(t *testing.T) { | 
					
						
							|  |  |  | 	for _, test := range rootTestCases { | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, target string, root *os.Root) { | 
					
						
							|  |  |  | 			const content = "content" | 
					
						
							|  |  |  | 			if target != "" { | 
					
						
							|  |  |  | 				if err := os.WriteFile(target, []byte(content), 0o666); err != nil { | 
					
						
							|  |  |  | 					t.Fatal(err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			fi, err := root.Stat(test.open) | 
					
						
							|  |  |  | 			if errEndsTest(t, err, test.wantError, "root.Stat(%q)", test.open) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if got, want := fi.Name(), filepath.Base(test.open); got != want { | 
					
						
							|  |  |  | 				t.Errorf("root.Stat(%q).Name() = %q, want %q", test.open, got, want) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if got, want := fi.Size(), int64(len(content)); got != want { | 
					
						
							|  |  |  | 				t.Errorf("root.Stat(%q).Size() = %v, want %v", test.open, got, want) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestRootLstat(t *testing.T) { | 
					
						
							|  |  |  | 	for _, test := range rootTestCases { | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, target string, root *os.Root) { | 
					
						
							|  |  |  | 			const content = "content" | 
					
						
							|  |  |  | 			wantError := test.wantError | 
					
						
							|  |  |  | 			if test.ltarget != "" { | 
					
						
							|  |  |  | 				// Lstat will stat the final link, rather than following it. | 
					
						
							|  |  |  | 				wantError = false | 
					
						
							|  |  |  | 			} else if target != "" { | 
					
						
							|  |  |  | 				if err := os.WriteFile(target, []byte(content), 0o666); err != nil { | 
					
						
							|  |  |  | 					t.Fatal(err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			fi, err := root.Lstat(test.open) | 
					
						
							|  |  |  | 			if errEndsTest(t, err, wantError, "root.Stat(%q)", test.open) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if got, want := fi.Name(), filepath.Base(test.open); got != want { | 
					
						
							|  |  |  | 				t.Errorf("root.Stat(%q).Name() = %q, want %q", test.open, got, want) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if test.ltarget == "" { | 
					
						
							|  |  |  | 				if got := fi.Mode(); got&os.ModeSymlink != 0 { | 
					
						
							|  |  |  | 					t.Errorf("root.Stat(%q).Mode() = %v, want non-symlink", test.open, got) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				if got, want := fi.Size(), int64(len(content)); got != want { | 
					
						
							|  |  |  | 					t.Errorf("root.Stat(%q).Size() = %v, want %v", test.open, got, want) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				if got := fi.Mode(); got&os.ModeSymlink == 0 { | 
					
						
							|  |  |  | 					t.Errorf("root.Stat(%q).Mode() = %v, want symlink", test.open, got) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-18 15:12:31 -07:00
										 |  |  | func TestRootReadlink(t *testing.T) { | 
					
						
							|  |  |  | 	for _, test := range rootTestCases { | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, target string, root *os.Root) { | 
					
						
							|  |  |  | 			const content = "content" | 
					
						
							|  |  |  | 			wantError := test.wantError | 
					
						
							|  |  |  | 			if test.ltarget != "" { | 
					
						
							|  |  |  | 				// Readlink will read the final link, rather than following it. | 
					
						
							|  |  |  | 				wantError = false | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				// Readlink fails on non-link targets. | 
					
						
							|  |  |  | 				wantError = true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			got, err := root.Readlink(test.open) | 
					
						
							|  |  |  | 			if errEndsTest(t, err, wantError, "root.Readlink(%q)", test.open) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			want, err := os.Readlink(filepath.Join(root.Name(), test.ltarget)) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatalf("os.Readlink(%q) = %v, want success", test.ltarget, err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if got != want { | 
					
						
							|  |  |  | 				t.Errorf("root.Readlink(%q) = %q, want %q", test.open, got, want) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-19 11:15:06 -07:00
										 |  |  | // TestRootRenameFrom tests renaming the test case target to a known-good path. | 
					
						
							|  |  |  | func TestRootRenameFrom(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2025-03-20 12:41:21 -07:00
										 |  |  | 	testRootMoveFrom(t, true) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // TestRootRenameFrom tests linking the test case target to a known-good path. | 
					
						
							|  |  |  | func TestRootLinkFrom(t *testing.T) { | 
					
						
							|  |  |  | 	testenv.MustHaveLink(t) | 
					
						
							|  |  |  | 	testRootMoveFrom(t, false) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func testRootMoveFrom(t *testing.T, rename bool) { | 
					
						
							| 
									
										
										
										
											2025-03-19 11:15:06 -07:00
										 |  |  | 	want := []byte("target") | 
					
						
							|  |  |  | 	for _, test := range rootTestCases { | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, target string, root *os.Root) { | 
					
						
							|  |  |  | 			if target != "" { | 
					
						
							|  |  |  | 				if err := os.WriteFile(target, want, 0o666); err != nil { | 
					
						
							|  |  |  | 					t.Fatal(err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			wantError := test.wantError | 
					
						
							|  |  |  | 			var linkTarget string | 
					
						
							|  |  |  | 			if test.ltarget != "" { | 
					
						
							|  |  |  | 				// Rename will rename the link, not the file linked to. | 
					
						
							|  |  |  | 				wantError = false | 
					
						
							|  |  |  | 				var err error | 
					
						
							|  |  |  | 				linkTarget, err = root.Readlink(test.ltarget) | 
					
						
							|  |  |  | 				if err != nil { | 
					
						
							|  |  |  | 					t.Fatalf("root.Readlink(%q) = %v, want success", test.ltarget, err) | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2025-03-20 12:41:21 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				// When GOOS=js, creating a hard link to a symlink fails. | 
					
						
							|  |  |  | 				if !rename && runtime.GOOS == "js" { | 
					
						
							|  |  |  | 					wantError = true | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2025-03-19 11:15:06 -07:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const dstPath = "destination" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// Plan 9 doesn't allow cross-directory renames. | 
					
						
							|  |  |  | 			if runtime.GOOS == "plan9" && strings.Contains(test.open, "/") { | 
					
						
							|  |  |  | 				wantError = true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-20 12:41:21 -07:00
										 |  |  | 			var op string | 
					
						
							|  |  |  | 			var err error | 
					
						
							|  |  |  | 			if rename { | 
					
						
							|  |  |  | 				op = "Rename" | 
					
						
							|  |  |  | 				err = root.Rename(test.open, dstPath) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				op = "Link" | 
					
						
							|  |  |  | 				err = root.Link(test.open, dstPath) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if errEndsTest(t, err, wantError, "root.%v(%q, %q)", op, test.open, dstPath) { | 
					
						
							| 
									
										
										
										
											2025-03-19 11:15:06 -07:00
										 |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-20 12:41:21 -07:00
										 |  |  | 			origPath := target | 
					
						
							| 
									
										
										
										
											2025-03-19 11:15:06 -07:00
										 |  |  | 			if test.ltarget != "" { | 
					
						
							| 
									
										
										
										
											2025-03-20 12:41:21 -07:00
										 |  |  | 				origPath = filepath.Join(root.Name(), test.ltarget) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			_, err = os.Lstat(origPath) | 
					
						
							|  |  |  | 			if rename { | 
					
						
							|  |  |  | 				if !errors.Is(err, os.ErrNotExist) { | 
					
						
							|  |  |  | 					t.Errorf("after renaming file, Lstat(%q) = %v, want ErrNotExist", origPath, err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				if err != nil { | 
					
						
							|  |  |  | 					t.Errorf("after linking file, error accessing original: %v", err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			dstFullPath := filepath.Join(root.Name(), dstPath) | 
					
						
							|  |  |  | 			if test.ltarget != "" { | 
					
						
							|  |  |  | 				got, err := os.Readlink(dstFullPath) | 
					
						
							| 
									
										
										
										
											2025-03-19 11:15:06 -07:00
										 |  |  | 				if err != nil || got != linkTarget { | 
					
						
							| 
									
										
										
										
											2025-03-20 12:41:21 -07:00
										 |  |  | 					t.Errorf("os.Readlink(%q) = %q, %v, want %q", dstFullPath, got, err, linkTarget) | 
					
						
							| 
									
										
										
										
											2025-03-19 11:15:06 -07:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			} else { | 
					
						
							| 
									
										
										
										
											2025-03-20 12:41:21 -07:00
										 |  |  | 				got, err := os.ReadFile(dstFullPath) | 
					
						
							| 
									
										
										
										
											2025-03-19 11:15:06 -07:00
										 |  |  | 				if err != nil || !bytes.Equal(got, want) { | 
					
						
							| 
									
										
										
										
											2025-03-20 12:41:21 -07:00
										 |  |  | 					t.Errorf(`os.ReadFile(%q): read content %q, %v; want %q`, dstFullPath, string(got), err, string(want)) | 
					
						
							| 
									
										
										
										
											2025-03-19 11:15:06 -07:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2025-03-20 12:41:21 -07:00
										 |  |  | 				st, err := os.Lstat(dstFullPath) | 
					
						
							|  |  |  | 				if err != nil || st.Mode()&fs.ModeSymlink != 0 { | 
					
						
							|  |  |  | 					t.Errorf(`os.Lstat(%q) = %v, %v; want non-symlink`, dstFullPath, st.Mode(), err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-19 11:15:06 -07:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // TestRootRenameTo tests renaming a known-good path to the test case target. | 
					
						
							|  |  |  | func TestRootRenameTo(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2025-03-20 12:41:21 -07:00
										 |  |  | 	testRootMoveTo(t, true) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // TestRootLinkTo tests renaming a known-good path to the test case target. | 
					
						
							|  |  |  | func TestRootLinkTo(t *testing.T) { | 
					
						
							|  |  |  | 	testenv.MustHaveLink(t) | 
					
						
							|  |  |  | 	testRootMoveTo(t, true) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func testRootMoveTo(t *testing.T, rename bool) { | 
					
						
							| 
									
										
										
										
											2025-03-19 11:15:06 -07:00
										 |  |  | 	want := []byte("target") | 
					
						
							|  |  |  | 	for _, test := range rootTestCases { | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, target string, root *os.Root) { | 
					
						
							|  |  |  | 			const srcPath = "source" | 
					
						
							|  |  |  | 			if err := os.WriteFile(filepath.Join(root.Name(), srcPath), want, 0o666); err != nil { | 
					
						
							|  |  |  | 				t.Fatal(err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			target = test.target | 
					
						
							|  |  |  | 			wantError := test.wantError | 
					
						
							|  |  |  | 			if test.ltarget != "" { | 
					
						
							|  |  |  | 				// Rename will overwrite the final link rather than follow it. | 
					
						
							|  |  |  | 				target = test.ltarget | 
					
						
							|  |  |  | 				wantError = false | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// Plan 9 doesn't allow cross-directory renames. | 
					
						
							|  |  |  | 			if runtime.GOOS == "plan9" && strings.Contains(test.open, "/") { | 
					
						
							|  |  |  | 				wantError = true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-20 12:41:21 -07:00
										 |  |  | 			var err error | 
					
						
							|  |  |  | 			var op string | 
					
						
							|  |  |  | 			if rename { | 
					
						
							|  |  |  | 				op = "Rename" | 
					
						
							|  |  |  | 				err = root.Rename(srcPath, test.open) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				op = "Link" | 
					
						
							|  |  |  | 				err = root.Link(srcPath, test.open) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if errEndsTest(t, err, wantError, "root.%v(%q, %q)", op, srcPath, test.open) { | 
					
						
							| 
									
										
										
										
											2025-03-19 11:15:06 -07:00
										 |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-20 12:41:21 -07:00
										 |  |  | 			_, err = os.Lstat(filepath.Join(root.Name(), srcPath)) | 
					
						
							|  |  |  | 			if rename { | 
					
						
							|  |  |  | 				if !errors.Is(err, os.ErrNotExist) { | 
					
						
							|  |  |  | 					t.Errorf("after renaming file, Lstat(%q) = %v, want ErrNotExist", srcPath, err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				if err != nil { | 
					
						
							|  |  |  | 					t.Errorf("after linking file, error accessing original: %v", err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-19 11:15:06 -07:00
										 |  |  | 			got, err := os.ReadFile(filepath.Join(root.Name(), target)) | 
					
						
							|  |  |  | 			if err != nil || !bytes.Equal(got, want) { | 
					
						
							|  |  |  | 				t.Errorf(`os.ReadFile(%q): read content %q, %v; want %q`, target, string(got), err, string(want)) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-25 10:31:00 -07:00
										 |  |  | func TestRootSymlink(t *testing.T) { | 
					
						
							|  |  |  | 	testenv.MustHaveSymlink(t) | 
					
						
							|  |  |  | 	for _, test := range rootTestCases { | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, target string, root *os.Root) { | 
					
						
							|  |  |  | 			wantError := test.wantError | 
					
						
							|  |  |  | 			if test.ltarget != "" { | 
					
						
							|  |  |  | 				// We can't create a symlink over an existing symlink. | 
					
						
							|  |  |  | 				wantError = true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const wantTarget = "linktarget" | 
					
						
							|  |  |  | 			err := root.Symlink(wantTarget, test.open) | 
					
						
							|  |  |  | 			if errEndsTest(t, err, wantError, "root.Symlink(%q)", test.open) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			got, err := os.Readlink(target) | 
					
						
							|  |  |  | 			if err != nil || got != wantTarget { | 
					
						
							|  |  |  | 				t.Fatalf("ReadLink(%q) = %q, %v; want %q, nil", target, got, err, wantTarget) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | // A rootConsistencyTest is a test case comparing os.Root behavior with | 
					
						
							|  |  |  | // the corresponding non-Root function. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // These tests verify that, for example, Root.Open("file/./") and os.Open("file/./") | 
					
						
							|  |  |  | // have the same result, although the specific result may vary by platform. | 
					
						
							|  |  |  | type rootConsistencyTest struct { | 
					
						
							|  |  |  | 	name string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// fs is the test filesystem layout. See makefs above. | 
					
						
							|  |  |  | 	// fsFunc is called to modify the test filesystem, or replace it. | 
					
						
							|  |  |  | 	fs     []string | 
					
						
							|  |  |  | 	fsFunc func(t *testing.T, dir string) string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// open is the filename to access in the test. | 
					
						
							|  |  |  | 	open string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// detailedErrorMismatch indicates that os.Root and the corresponding non-Root | 
					
						
							|  |  |  | 	// function return different errors for this test. | 
					
						
							|  |  |  | 	detailedErrorMismatch func(t *testing.T) bool | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var rootConsistencyTestCases = []rootConsistencyTest{{ | 
					
						
							|  |  |  | 	name: "file", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"target", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open: "target", | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "dir slash dot", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"target/file", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open: "target/.", | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "dot", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"file", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open: ".", | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "file slash dot", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"target", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open: "target/.", | 
					
						
							|  |  |  | 	detailedErrorMismatch: func(t *testing.T) bool { | 
					
						
							|  |  |  | 		// FreeBSD returns EPERM in the non-Root case. | 
					
						
							|  |  |  | 		return runtime.GOOS == "freebsd" && strings.HasPrefix(t.Name(), "TestRootConsistencyRemove") | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "dir slash", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"target/file", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open: "target/", | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "dot slash", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"file", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open: "./", | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "file slash", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"target", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open: "target/", | 
					
						
							|  |  |  | 	detailedErrorMismatch: func(t *testing.T) bool { | 
					
						
							|  |  |  | 		// os.Create returns ENOTDIR or EISDIR depending on the platform. | 
					
						
							|  |  |  | 		return runtime.GOOS == "js" | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "file in path", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"file", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open: "file/target", | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "directory in path missing", | 
					
						
							|  |  |  | 	open: "dir/target", | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "target does not exist", | 
					
						
							|  |  |  | 	open: "target", | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "symlink slash", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"target/file", | 
					
						
							|  |  |  | 		"link => target", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open: "link/", | 
					
						
							| 
									
										
										
										
											2024-11-13 14:13:57 +01:00
										 |  |  | }, { | 
					
						
							|  |  |  | 	name: "symlink slash dot", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"target/file", | 
					
						
							|  |  |  | 		"link => target", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open: "link/.", | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "file symlink slash", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"target", | 
					
						
							|  |  |  | 		"link => target", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open: "link/", | 
					
						
							|  |  |  | 	detailedErrorMismatch: func(t *testing.T) bool { | 
					
						
							|  |  |  | 		// os.Create returns ENOTDIR or EISDIR depending on the platform. | 
					
						
							|  |  |  | 		return runtime.GOOS == "js" | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | }, { | 
					
						
							|  |  |  | 	name: "unresolved symlink", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"link => target", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open: "link", | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "resolved symlink", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"link => target", | 
					
						
							|  |  |  | 		"target", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open: "link", | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "dotdot in path after symlink", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"a => b/c", | 
					
						
							|  |  |  | 		"b/c/", | 
					
						
							|  |  |  | 		"b/target", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open: "a/../target", | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "long file name", | 
					
						
							|  |  |  | 	open: strings.Repeat("a", 500), | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "unreadable directory", | 
					
						
							|  |  |  | 	fs: []string{ | 
					
						
							|  |  |  | 		"dir/target", | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	fsFunc: func(t *testing.T, dir string) string { | 
					
						
							|  |  |  | 		os.Chmod(filepath.Join(dir, "dir"), 0) | 
					
						
							|  |  |  | 		t.Cleanup(func() { | 
					
						
							|  |  |  | 			os.Chmod(filepath.Join(dir, "dir"), 0o700) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		return dir | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open: "dir/target", | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "unix domain socket target", | 
					
						
							|  |  |  | 	fsFunc: func(t *testing.T, dir string) string { | 
					
						
							|  |  |  | 		return tempDirWithUnixSocket(t, "a") | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open: "a", | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "unix domain socket in path", | 
					
						
							|  |  |  | 	fsFunc: func(t *testing.T, dir string) string { | 
					
						
							|  |  |  | 		return tempDirWithUnixSocket(t, "a") | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	open: "a/b", | 
					
						
							|  |  |  | 	detailedErrorMismatch: func(t *testing.T) bool { | 
					
						
							|  |  |  | 		// On Windows, os.Root.Open returns "The directory name is invalid." | 
					
						
							|  |  |  | 		// and os.Open returns "The file cannot be accessed by the system.". | 
					
						
							|  |  |  | 		return runtime.GOOS == "windows" | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "question mark", | 
					
						
							|  |  |  | 	open: "?", | 
					
						
							|  |  |  | }, { | 
					
						
							|  |  |  | 	name: "nul byte", | 
					
						
							|  |  |  | 	open: "\x00", | 
					
						
							|  |  |  | }} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func tempDirWithUnixSocket(t *testing.T, name string) string { | 
					
						
							|  |  |  | 	dir, err := os.MkdirTemp("", "") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	t.Cleanup(func() { | 
					
						
							|  |  |  | 		if err := os.RemoveAll(dir); err != nil { | 
					
						
							|  |  |  | 			t.Error(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	addr, err := net.ResolveUnixAddr("unix", filepath.Join(dir, name)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Skipf("net.ResolveUnixAddr: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	conn, err := net.ListenUnix("unix", addr) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Skipf("net.ListenUnix: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	t.Cleanup(func() { | 
					
						
							|  |  |  | 		conn.Close() | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	return dir | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (test rootConsistencyTest) run(t *testing.T, f func(t *testing.T, path string, r *os.Root) (string, error)) { | 
					
						
							|  |  |  | 	if runtime.GOOS == "wasip1" { | 
					
						
							|  |  |  | 		// On wasip, non-Root functions clean paths before opening them, | 
					
						
							|  |  |  | 		// resulting in inconsistent behavior. | 
					
						
							|  |  |  | 		// https://go.dev/issue/69509 | 
					
						
							|  |  |  | 		t.Skip("#69509: inconsistent results on wasip1") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run(test.name, func(t *testing.T) { | 
					
						
							|  |  |  | 		dir1 := makefs(t, test.fs) | 
					
						
							|  |  |  | 		dir2 := makefs(t, test.fs) | 
					
						
							|  |  |  | 		if test.fsFunc != nil { | 
					
						
							|  |  |  | 			dir1 = test.fsFunc(t, dir1) | 
					
						
							|  |  |  | 			dir2 = test.fsFunc(t, dir2) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		r, err := os.OpenRoot(dir1) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatal(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		defer r.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		res1, err1 := f(t, test.open, r) | 
					
						
							|  |  |  | 		res2, err2 := f(t, dir2+"/"+test.open, nil) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if res1 != res2 || ((err1 == nil) != (err2 == nil)) { | 
					
						
							|  |  |  | 			t.Errorf("with root:    res=%v", res1) | 
					
						
							|  |  |  | 			t.Errorf("              err=%v", err1) | 
					
						
							|  |  |  | 			t.Errorf("without root: res=%v", res2) | 
					
						
							|  |  |  | 			t.Errorf("              err=%v", err2) | 
					
						
							|  |  |  | 			t.Errorf("want consistent results, got mismatch") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if err1 != nil || err2 != nil { | 
					
						
							| 
									
										
										
										
											2025-03-19 11:15:06 -07:00
										 |  |  | 			underlyingError := func(how string, err error) error { | 
					
						
							|  |  |  | 				switch e := err1.(type) { | 
					
						
							|  |  |  | 				case *os.PathError: | 
					
						
							|  |  |  | 					return e.Err | 
					
						
							|  |  |  | 				case *os.LinkError: | 
					
						
							|  |  |  | 					return e.Err | 
					
						
							|  |  |  | 				default: | 
					
						
							|  |  |  | 					t.Fatalf("%v, expected PathError or LinkError; got: %v", how, err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				return nil | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2025-03-19 11:15:06 -07:00
										 |  |  | 			e1 := underlyingError("with root", err1) | 
					
						
							|  |  |  | 			e2 := underlyingError("without root", err1) | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | 			detailedErrorMismatch := false | 
					
						
							|  |  |  | 			if f := test.detailedErrorMismatch; f != nil { | 
					
						
							|  |  |  | 				detailedErrorMismatch = f(t) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2024-11-21 08:43:23 -08:00
										 |  |  | 			if runtime.GOOS == "plan9" { | 
					
						
							|  |  |  | 				// Plan9 syscall errors aren't comparable. | 
					
						
							|  |  |  | 				detailedErrorMismatch = true | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2025-03-19 11:15:06 -07:00
										 |  |  | 			if !detailedErrorMismatch && e1 != e2 { | 
					
						
							|  |  |  | 				t.Errorf("with root:    err=%v", e1) | 
					
						
							|  |  |  | 				t.Errorf("without root: err=%v", e2) | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | 				t.Errorf("want consistent results, got mismatch") | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestRootConsistencyOpen(t *testing.T) { | 
					
						
							|  |  |  | 	for _, test := range rootConsistencyTestCases { | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) { | 
					
						
							|  |  |  | 			var f *os.File | 
					
						
							|  |  |  | 			var err error | 
					
						
							|  |  |  | 			if r == nil { | 
					
						
							|  |  |  | 				f, err = os.Open(path) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				f, err = r.Open(path) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return "", err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			defer f.Close() | 
					
						
							|  |  |  | 			fi, err := f.Stat() | 
					
						
							|  |  |  | 			if err == nil && !fi.IsDir() { | 
					
						
							|  |  |  | 				b, err := io.ReadAll(f) | 
					
						
							|  |  |  | 				return string(b), err | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				names, err := f.Readdirnames(-1) | 
					
						
							|  |  |  | 				slices.Sort(names) | 
					
						
							|  |  |  | 				return fmt.Sprintf("%q", names), err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestRootConsistencyCreate(t *testing.T) { | 
					
						
							|  |  |  | 	for _, test := range rootConsistencyTestCases { | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) { | 
					
						
							|  |  |  | 			var f *os.File | 
					
						
							|  |  |  | 			var err error | 
					
						
							|  |  |  | 			if r == nil { | 
					
						
							|  |  |  | 				f, err = os.Create(path) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				f, err = r.Create(path) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if err == nil { | 
					
						
							|  |  |  | 				f.Write([]byte("file contents")) | 
					
						
							|  |  |  | 				f.Close() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return "", err | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-30 15:53:06 -08:00
										 |  |  | func TestRootConsistencyChmod(t *testing.T) { | 
					
						
							|  |  |  | 	if runtime.GOOS == "wasip1" { | 
					
						
							|  |  |  | 		t.Skip("Chmod not supported on " + runtime.GOOS) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for _, test := range rootConsistencyTestCases { | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) { | 
					
						
							|  |  |  | 			chmod := os.Chmod | 
					
						
							|  |  |  | 			lstat := os.Lstat | 
					
						
							|  |  |  | 			if r != nil { | 
					
						
							|  |  |  | 				chmod = r.Chmod | 
					
						
							|  |  |  | 				lstat = r.Lstat | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var m1, m2 os.FileMode | 
					
						
							| 
									
										
										
										
											2025-02-17 11:45:51 +00:00
										 |  |  | 			if err := chmod(path, 0o555); err != nil { | 
					
						
							|  |  |  | 				return "chmod 0o555", err | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2025-01-30 15:53:06 -08:00
										 |  |  | 			fi, err := lstat(path) | 
					
						
							|  |  |  | 			if err == nil { | 
					
						
							|  |  |  | 				m1 = fi.Mode() | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2025-02-17 11:45:51 +00:00
										 |  |  | 			if err = chmod(path, 0o777); err != nil { | 
					
						
							|  |  |  | 				return "chmod 0o777", err | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2025-01-30 15:53:06 -08:00
										 |  |  | 			fi, err = lstat(path) | 
					
						
							|  |  |  | 			if err == nil { | 
					
						
							|  |  |  | 				m2 = fi.Mode() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return fmt.Sprintf("%v %v", m1, m2), err | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | func TestRootConsistencyMkdir(t *testing.T) { | 
					
						
							|  |  |  | 	for _, test := range rootConsistencyTestCases { | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) { | 
					
						
							|  |  |  | 			var err error | 
					
						
							|  |  |  | 			if r == nil { | 
					
						
							|  |  |  | 				err = os.Mkdir(path, 0o777) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				err = r.Mkdir(path, 0o777) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return "", err | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-12 17:16:10 +01:00
										 |  |  | func TestRootConsistencyRemove(t *testing.T) { | 
					
						
							|  |  |  | 	for _, test := range rootConsistencyTestCases { | 
					
						
							|  |  |  | 		if test.open == "." || test.open == "./" { | 
					
						
							|  |  |  | 			continue // can't remove the root itself | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) { | 
					
						
							|  |  |  | 			var err error | 
					
						
							|  |  |  | 			if r == nil { | 
					
						
							|  |  |  | 				err = os.Remove(path) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				err = r.Remove(path) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return "", err | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-13 14:13:57 +01:00
										 |  |  | func TestRootConsistencyStat(t *testing.T) { | 
					
						
							|  |  |  | 	for _, test := range rootConsistencyTestCases { | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) { | 
					
						
							|  |  |  | 			var fi os.FileInfo | 
					
						
							|  |  |  | 			var err error | 
					
						
							|  |  |  | 			if r == nil { | 
					
						
							|  |  |  | 				fi, err = os.Stat(path) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				fi, err = r.Stat(path) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return "", err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return fmt.Sprintf("name:%q size:%v mode:%v isdir:%v", fi.Name(), fi.Size(), fi.Mode(), fi.IsDir()), nil | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestRootConsistencyLstat(t *testing.T) { | 
					
						
							|  |  |  | 	for _, test := range rootConsistencyTestCases { | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) { | 
					
						
							|  |  |  | 			var fi os.FileInfo | 
					
						
							|  |  |  | 			var err error | 
					
						
							|  |  |  | 			if r == nil { | 
					
						
							|  |  |  | 				fi, err = os.Lstat(path) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				fi, err = r.Lstat(path) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return "", err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return fmt.Sprintf("name:%q size:%v mode:%v isdir:%v", fi.Name(), fi.Size(), fi.Mode(), fi.IsDir()), nil | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-18 15:12:31 -07:00
										 |  |  | func TestRootConsistencyReadlink(t *testing.T) { | 
					
						
							|  |  |  | 	for _, test := range rootConsistencyTestCases { | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) { | 
					
						
							|  |  |  | 			if r == nil { | 
					
						
							|  |  |  | 				return os.Readlink(path) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				return r.Readlink(path) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-19 11:15:06 -07:00
										 |  |  | func TestRootConsistencyRename(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2025-03-20 12:41:21 -07:00
										 |  |  | 	testRootConsistencyMove(t, true) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestRootConsistencyLink(t *testing.T) { | 
					
						
							|  |  |  | 	testenv.MustHaveLink(t) | 
					
						
							|  |  |  | 	testRootConsistencyMove(t, false) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func testRootConsistencyMove(t *testing.T, rename bool) { | 
					
						
							| 
									
										
										
										
											2025-03-19 11:15:06 -07:00
										 |  |  | 	if runtime.GOOS == "plan9" { | 
					
						
							|  |  |  | 		// This test depends on moving files between directories. | 
					
						
							|  |  |  | 		t.Skip("Plan 9 does not support cross-directory renames") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Run this test in two directions: | 
					
						
							|  |  |  | 	// Renaming the test path to a known-good path (from), | 
					
						
							|  |  |  | 	// and renaming a known-good path to the test path (to). | 
					
						
							|  |  |  | 	for _, name := range []string{"from", "to"} { | 
					
						
							|  |  |  | 		t.Run(name, func(t *testing.T) { | 
					
						
							|  |  |  | 			for _, test := range rootConsistencyTestCases { | 
					
						
							|  |  |  | 				if runtime.GOOS == "windows" { | 
					
						
							|  |  |  | 					// On Windows, Rename("/path/to/.", x) succeeds, | 
					
						
							|  |  |  | 					// because Windows cleans the path to just "/path/to". | 
					
						
							|  |  |  | 					// Root.Rename(".", x) fails as expected. | 
					
						
							|  |  |  | 					// Don't run this consistency test on Windows. | 
					
						
							|  |  |  | 					if test.open == "." || test.open == "./" { | 
					
						
							|  |  |  | 						continue | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) { | 
					
						
							| 
									
										
										
										
											2025-03-20 12:41:21 -07:00
										 |  |  | 					var move func(oldname, newname string) error | 
					
						
							|  |  |  | 					switch { | 
					
						
							|  |  |  | 					case rename && r == nil: | 
					
						
							|  |  |  | 						move = os.Rename | 
					
						
							|  |  |  | 					case rename && r != nil: | 
					
						
							|  |  |  | 						move = r.Rename | 
					
						
							|  |  |  | 					case !rename && r == nil: | 
					
						
							|  |  |  | 						move = os.Link | 
					
						
							|  |  |  | 					case !rename && r != nil: | 
					
						
							|  |  |  | 						move = r.Link | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2025-03-19 11:15:06 -07:00
										 |  |  | 					lstat := os.Lstat | 
					
						
							|  |  |  | 					if r != nil { | 
					
						
							|  |  |  | 						lstat = r.Lstat | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					otherPath := "other" | 
					
						
							|  |  |  | 					if r == nil { | 
					
						
							|  |  |  | 						otherPath = filepath.Join(t.TempDir(), otherPath) | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					var srcPath, dstPath string | 
					
						
							|  |  |  | 					if name == "from" { | 
					
						
							|  |  |  | 						srcPath = path | 
					
						
							|  |  |  | 						dstPath = otherPath | 
					
						
							|  |  |  | 					} else { | 
					
						
							|  |  |  | 						srcPath = otherPath | 
					
						
							|  |  |  | 						dstPath = path | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-20 12:41:21 -07:00
										 |  |  | 					if !rename { | 
					
						
							|  |  |  | 						// When the source is a symlink, Root.Link creates | 
					
						
							|  |  |  | 						// a hard link to the symlink. | 
					
						
							|  |  |  | 						// os.Link does whatever the link syscall does, | 
					
						
							|  |  |  | 						// which varies between operating systems and | 
					
						
							|  |  |  | 						// their versions. | 
					
						
							|  |  |  | 						// Skip running the consistency test when | 
					
						
							|  |  |  | 						// the source is a symlink. | 
					
						
							|  |  |  | 						fi, err := lstat(srcPath) | 
					
						
							|  |  |  | 						if err == nil && fi.Mode()&os.ModeSymlink != 0 { | 
					
						
							|  |  |  | 							return "", nil | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					if err := move(srcPath, dstPath); err != nil { | 
					
						
							| 
									
										
										
										
											2025-03-19 11:15:06 -07:00
										 |  |  | 						return "", err | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					fi, err := lstat(dstPath) | 
					
						
							|  |  |  | 					if err != nil { | 
					
						
							|  |  |  | 						t.Errorf("stat(%q) after successful copy: %v", dstPath, err) | 
					
						
							|  |  |  | 						return "stat error", err | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					return fmt.Sprintf("name:%q size:%v mode:%v isdir:%v", fi.Name(), fi.Size(), fi.Mode(), fi.IsDir()), nil | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-25 10:31:00 -07:00
										 |  |  | func TestRootConsistencySymlink(t *testing.T) { | 
					
						
							|  |  |  | 	testenv.MustHaveSymlink(t) | 
					
						
							|  |  |  | 	for _, test := range rootConsistencyTestCases { | 
					
						
							|  |  |  | 		test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) { | 
					
						
							|  |  |  | 			const target = "linktarget" | 
					
						
							|  |  |  | 			var err error | 
					
						
							|  |  |  | 			var got string | 
					
						
							|  |  |  | 			if r == nil { | 
					
						
							|  |  |  | 				err = os.Symlink(target, path) | 
					
						
							|  |  |  | 				got, _ = os.Readlink(target) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				err = r.Symlink(target, path) | 
					
						
							|  |  |  | 				got, _ = r.Readlink(target) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return got, err | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | func TestRootRenameAfterOpen(t *testing.T) { | 
					
						
							|  |  |  | 	switch runtime.GOOS { | 
					
						
							|  |  |  | 	case "windows": | 
					
						
							|  |  |  | 		t.Skip("renaming open files not supported on " + runtime.GOOS) | 
					
						
							|  |  |  | 	case "js", "plan9": | 
					
						
							|  |  |  | 		t.Skip("openat not supported on " + runtime.GOOS) | 
					
						
							|  |  |  | 	case "wasip1": | 
					
						
							|  |  |  | 		if os.Getenv("GOWASIRUNTIME") == "wazero" { | 
					
						
							|  |  |  | 			t.Skip("wazero does not track renamed directories") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dir := t.TempDir() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Create directory "a" and open it. | 
					
						
							|  |  |  | 	if err := os.Mkdir(filepath.Join(dir, "a"), 0o777); err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	dirf, err := os.OpenRoot(filepath.Join(dir, "a")) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer dirf.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Rename "a" => "b", and create "b/f". | 
					
						
							|  |  |  | 	if err := os.Rename(filepath.Join(dir, "a"), filepath.Join(dir, "b")); err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err := os.WriteFile(filepath.Join(dir, "b/f"), []byte("hello"), 0o666); err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Open "f", and confirm that we see it. | 
					
						
							|  |  |  | 	f, err := dirf.OpenFile("f", os.O_RDONLY, 0) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("reading file after renaming parent: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer f.Close() | 
					
						
							|  |  |  | 	b, err := io.ReadAll(f) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if got, want := string(b), "hello"; got != want { | 
					
						
							|  |  |  | 		t.Fatalf("file contents: %q, want %q", got, want) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// f.Name reflects the original path we opened the directory under (".../a"), not "b". | 
					
						
							|  |  |  | 	if got, want := f.Name(), dirf.Name()+string(os.PathSeparator)+"f"; got != want { | 
					
						
							|  |  |  | 		t.Errorf("f.Name() = %q, want %q", got, want) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestRootNonPermissionMode(t *testing.T) { | 
					
						
							|  |  |  | 	r, err := os.OpenRoot(t.TempDir()) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer r.Close() | 
					
						
							|  |  |  | 	if _, err := r.OpenFile("file", os.O_RDWR|os.O_CREATE, 0o1777); err == nil { | 
					
						
							|  |  |  | 		t.Errorf("r.OpenFile(file, O_RDWR|O_CREATE, 0o1777) succeeded; want error") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err := r.Mkdir("file", 0o1777); err == nil { | 
					
						
							|  |  |  | 		t.Errorf("r.Mkdir(file, 0o1777) succeeded; want error") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestRootUseAfterClose(t *testing.T) { | 
					
						
							|  |  |  | 	r, err := os.OpenRoot(t.TempDir()) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	r.Close() | 
					
						
							|  |  |  | 	for _, test := range []struct { | 
					
						
							|  |  |  | 		name string | 
					
						
							|  |  |  | 		f    func(r *os.Root, filename string) error | 
					
						
							|  |  |  | 	}{{ | 
					
						
							|  |  |  | 		name: "Open", | 
					
						
							|  |  |  | 		f: func(r *os.Root, filename string) error { | 
					
						
							|  |  |  | 			_, err := r.Open(filename) | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	}, { | 
					
						
							|  |  |  | 		name: "Create", | 
					
						
							|  |  |  | 		f: func(r *os.Root, filename string) error { | 
					
						
							|  |  |  | 			_, err := r.Create(filename) | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	}, { | 
					
						
							|  |  |  | 		name: "OpenFile", | 
					
						
							|  |  |  | 		f: func(r *os.Root, filename string) error { | 
					
						
							|  |  |  | 			_, err := r.OpenFile(filename, os.O_RDWR, 0o666) | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	}, { | 
					
						
							|  |  |  | 		name: "OpenRoot", | 
					
						
							|  |  |  | 		f: func(r *os.Root, filename string) error { | 
					
						
							|  |  |  | 			_, err := r.OpenRoot(filename) | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	}, { | 
					
						
							|  |  |  | 		name: "Mkdir", | 
					
						
							|  |  |  | 		f: func(r *os.Root, filename string) error { | 
					
						
							|  |  |  | 			return r.Mkdir(filename, 0o777) | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	}} { | 
					
						
							|  |  |  | 		err := test.f(r, "target") | 
					
						
							|  |  |  | 		pe, ok := err.(*os.PathError) | 
					
						
							|  |  |  | 		if !ok || pe.Path != "target" || pe.Err != os.ErrClosed { | 
					
						
							|  |  |  | 			t.Errorf(`r.%v = %v; want &PathError{Path: "target", Err: ErrClosed}`, test.name, err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestRootConcurrentClose(t *testing.T) { | 
					
						
							|  |  |  | 	r, err := os.OpenRoot(t.TempDir()) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	ch := make(chan error, 1) | 
					
						
							|  |  |  | 	go func() { | 
					
						
							|  |  |  | 		defer close(ch) | 
					
						
							|  |  |  | 		first := true | 
					
						
							|  |  |  | 		for { | 
					
						
							|  |  |  | 			f, err := r.OpenFile("file", os.O_RDWR|os.O_CREATE, 0o666) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				ch <- err | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if first { | 
					
						
							|  |  |  | 				ch <- nil | 
					
						
							|  |  |  | 				first = false | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			f.Close() | 
					
						
							| 
									
										
										
										
											2025-01-03 16:53:32 -05:00
										 |  |  | 			if runtime.GOARCH == "wasm" { | 
					
						
							|  |  |  | 				// TODO(go.dev/issue/71134) can lead to goroutine starvation. | 
					
						
							|  |  |  | 				runtime.Gosched() | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 	if err := <-ch; err != nil { | 
					
						
							|  |  |  | 		t.Errorf("OpenFile: %v, want success", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	r.Close() | 
					
						
							|  |  |  | 	if err := <-ch; !errors.Is(err, os.ErrClosed) { | 
					
						
							|  |  |  | 		t.Errorf("OpenFile: %v, want ErrClosed", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // TestRootRaceRenameDir attempts to escape a Root by renaming a path component mid-parse. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // We create a deeply nested directory: | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | //	base/a/a/a/a/ [...] /a | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // And a path that descends into the tree, then returns to the top using ..: | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | //	base/a/a/a/a/ [...] /a/../../../ [..] /../a/f | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // While opening this file, we rename base/a/a to base/b. | 
					
						
							|  |  |  | // A naive lookup operation will resolve the path to base/f. | 
					
						
							|  |  |  | func TestRootRaceRenameDir(t *testing.T) { | 
					
						
							|  |  |  | 	dir := t.TempDir() | 
					
						
							|  |  |  | 	r, err := os.OpenRoot(dir) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer r.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const depth = 4 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	os.MkdirAll(dir+"/base/"+strings.Repeat("/a", depth), 0o777) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	path := "base/" + strings.Repeat("a/", depth) + strings.Repeat("../", depth) + "a/f" | 
					
						
							|  |  |  | 	os.WriteFile(dir+"/f", []byte("secret"), 0o666) | 
					
						
							|  |  |  | 	os.WriteFile(dir+"/base/a/f", []byte("public"), 0o666) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Compute how long it takes to open the path in the common case. | 
					
						
							|  |  |  | 	const tries = 10 | 
					
						
							|  |  |  | 	var total time.Duration | 
					
						
							|  |  |  | 	for range tries { | 
					
						
							|  |  |  | 		start := time.Now() | 
					
						
							|  |  |  | 		f, err := r.Open(path) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatal(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		b, err := io.ReadAll(f) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatal(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if string(b) != "public" { | 
					
						
							|  |  |  | 			t.Fatalf("read %q, want %q", b, "public") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		f.Close() | 
					
						
							|  |  |  | 		total += time.Since(start) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	avg := total / tries | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// We're trying to exploit a race, so try this a number of times. | 
					
						
							|  |  |  | 	for range 100 { | 
					
						
							|  |  |  | 		// Start a goroutine to open the file. | 
					
						
							|  |  |  | 		gotc := make(chan []byte) | 
					
						
							|  |  |  | 		go func() { | 
					
						
							|  |  |  | 			f, err := r.Open(path) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				gotc <- nil | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			defer f.Close() | 
					
						
							|  |  |  | 			b, _ := io.ReadAll(f) | 
					
						
							|  |  |  | 			gotc <- b | 
					
						
							|  |  |  | 		}() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Wait for the open operation to partially complete, | 
					
						
							|  |  |  | 		// and then rename a directory near the root. | 
					
						
							|  |  |  | 		time.Sleep(avg / 4) | 
					
						
							|  |  |  | 		if err := os.Rename(dir+"/base/a", dir+"/b"); err != nil { | 
					
						
							| 
									
										
										
										
											2024-11-21 08:43:23 -08:00
										 |  |  | 			// Windows and Plan9 won't let us rename a directory if we have | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | 			// an open handle for it, so an error here is expected. | 
					
						
							| 
									
										
										
										
											2024-11-21 08:43:23 -08:00
										 |  |  | 			switch runtime.GOOS { | 
					
						
							|  |  |  | 			case "windows", "plan9": | 
					
						
							|  |  |  | 			default: | 
					
						
							| 
									
										
										
										
											2024-04-23 11:14:19 -07:00
										 |  |  | 				t.Fatal(err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		got := <-gotc | 
					
						
							|  |  |  | 		os.Rename(dir+"/b", dir+"/base/a") | 
					
						
							|  |  |  | 		if len(got) > 0 && string(got) != "public" { | 
					
						
							|  |  |  | 			t.Errorf("read file: %q; want error or 'public'", got) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-11-18 18:09:17 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-27 16:22:38 -07:00
										 |  |  | func TestRootSymlinkToRoot(t *testing.T) { | 
					
						
							|  |  |  | 	dir := makefs(t, []string{ | 
					
						
							|  |  |  | 		"d/d => ..", | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	root, err := os.OpenRoot(dir) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer root.Close() | 
					
						
							|  |  |  | 	if err := root.Mkdir("d/d/new", 0777); err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	f, err := root.Open("d/d") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer f.Close() | 
					
						
							|  |  |  | 	names, err := f.Readdirnames(-1) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	slices.Sort(names) | 
					
						
							|  |  |  | 	if got, want := names, []string{"d", "new"}; !slices.Equal(got, want) { | 
					
						
							|  |  |  | 		t.Errorf("root contains: %q, want %q", got, want) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-18 18:09:17 -08:00
										 |  |  | func TestOpenInRoot(t *testing.T) { | 
					
						
							|  |  |  | 	dir := makefs(t, []string{ | 
					
						
							|  |  |  | 		"file", | 
					
						
							|  |  |  | 		"link => ../ROOT/file", | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	f, err := os.OpenInRoot(dir, "file") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("OpenInRoot(`file`) = %v, want success", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	f.Close() | 
					
						
							|  |  |  | 	for _, name := range []string{ | 
					
						
							|  |  |  | 		"link", | 
					
						
							|  |  |  | 		"../ROOT/file", | 
					
						
							|  |  |  | 		dir + "/file", | 
					
						
							|  |  |  | 	} { | 
					
						
							|  |  |  | 		f, err := os.OpenInRoot(dir, name) | 
					
						
							|  |  |  | 		if err == nil { | 
					
						
							|  |  |  | 			f.Close() | 
					
						
							|  |  |  | 			t.Fatalf("OpenInRoot(%q) = nil, want error", name) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |