pkg os, method (*Root) Lchown(string, int, int) error #67002
pkg os, method (*Root) Link(string, string) error #67002
pkg os, method (*Root) Readlink(string) (string, error) #67002
+pkg os, method (*Root) RemoveAll(string) error #67002
pkg os, method (*Root) Rename(string, string) error #67002
pkg os, method (*Root) Symlink(string, string) error #67002
* [os.Root.Lchown]
* [os.Root.Link]
* [os.Root.Readlink]
+ * [os.Root.RemoveAll]
* [os.Root.Rename]
* [os.Root.Symlink]
}
defer parent.Close()
- if err := removeAllFrom(parent, base); err != nil {
+ if err := removeAllFrom(sysfdType(parent.Fd()), base); err != nil {
if pathErr, ok := err.(*PathError); ok {
pathErr.Path = parentDir + string(PathSeparator) + pathErr.Path
err = pathErr
return nil
}
-func removeAllFrom(parent *File, base string) error {
- parentFd := sysfdType(parent.Fd())
-
+func removeAllFrom(parentFd sysfdType, base string) error {
// Simple case: if Unlink (aka remove) works, we're done.
err := removefileat(parentFd, base)
if err == nil || IsNotExist(err) {
respSize = len(names)
for _, name := range names {
- err := removeAllFrom(file, name)
+ err := removeAllFrom(sysfdType(file.Fd()), name)
if err != nil {
if pathErr, ok := err.(*PathError); ok {
pathErr.Path = base + string(PathSeparator) + pathErr.Path
return rootRemove(r, name)
}
+// RemoveAll removes the named file or directory and any children that it contains.
+// See [RemoveAll] for more details.
+func (r *Root) RemoveAll(name string) error {
+ return rootRemoveAll(r, name)
+}
+
// Stat returns a [FileInfo] describing the named file in the root.
// See [Stat] for more details.
func (r *Root) Stat(name string) (FileInfo, error) {
import (
"errors"
"sync/atomic"
+ "syscall"
"time"
)
return nil
}
+func rootRemoveAll(r *Root, name string) error {
+ if endsWithDot(name) {
+ // Consistency with os.RemoveAll: Return EINVAL when trying to remove .
+ return &PathError{Op: "RemoveAll", Path: name, Err: syscall.EINVAL}
+ }
+ if err := checkPathEscapesLstat(r, name); err != nil {
+ if err == syscall.ENOTDIR {
+ // Some intermediate path component is not a directory.
+ // RemoveAll treats this as success (since the target doesn't exist).
+ return nil
+ }
+ return &PathError{Op: "RemoveAll", Path: name, Err: err}
+ }
+ if err := RemoveAll(joinPath(r.root.name, name)); err != nil {
+ return &PathError{Op: "RemoveAll", Path: name, Err: underlyingError(err)}
+ }
+ return nil
+}
+
func rootReadlink(r *Root, name string) (string, error) {
if err := checkPathEscapesLstat(r, name); err != nil {
return "", &PathError{Op: "readlinkat", Path: name, Err: err}
return nil
}
+func rootRemoveAll(r *Root, name string) error {
+ // Consistency with os.RemoveAll: Strip trailing /s from the name,
+ // so RemoveAll("not_a_directory/") succeeds.
+ for len(name) > 0 && IsPathSeparator(name[len(name)-1]) {
+ name = name[:len(name)-1]
+ }
+ if endsWithDot(name) {
+ // Consistency with os.RemoveAll: Return EINVAL when trying to remove .
+ return &PathError{Op: "RemoveAll", Path: name, Err: syscall.EINVAL}
+ }
+ _, err := doInRoot(r, name, func(parent sysfdType, name string) (struct{}, error) {
+ return struct{}{}, removeAllFrom(parent, name)
+ })
+ if IsNotExist(err) {
+ return nil
+ }
+ if err != nil {
+ return &PathError{Op: "RemoveAll", Path: name, Err: underlyingError(err)}
+ }
+ return err
+}
+
func rootRename(r *Root, oldname, newname string) error {
_, err := doInRoot(r, oldname, func(oldparent sysfdType, oldname string) (struct{}, error) {
_, err := doInRoot(r, newname, func(newparent sysfdType, newname string) (struct{}, error) {
}
}
+func TestRootRemoveAll(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)
+ }
+ if err := os.WriteFile(filepath.Join(target, "file"), nil, 0o666); err != nil {
+ t.Fatal(err)
+ }
+ }
+ targetExists := true
+ if _, err := root.Lstat(test.open); errors.Is(err, os.ErrNotExist) {
+ // If the target doesn't exist, RemoveAll succeeds rather
+ // than returning ErrNotExist.
+ targetExists = false
+ wantError = false
+ }
+
+ err := root.RemoveAll(test.open)
+ if errEndsTest(t, err, wantError, "root.RemoveAll(%q)", test.open) {
+ return
+ }
+ if !targetExists {
+ 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 TestRootOpenFileAsRoot(t *testing.T) {
dir := t.TempDir()
target := filepath.Join(dir, "target")
// detailedErrorMismatch indicates that os.Root and the corresponding non-Root
// function return different errors for this test.
detailedErrorMismatch func(t *testing.T) bool
+
+ // check is called before the test starts, and may t.Skip if necessary.
+ check func(t *testing.T)
}
var rootConsistencyTestCases = []rootConsistencyTest{{
// and os.Open returns "The file cannot be accessed by the system.".
return runtime.GOOS == "windows"
},
+ check: func(t *testing.T) {
+ if runtime.GOOS == "windows" && strings.HasPrefix(t.Name(), "TestRootConsistencyRemoveAll/") {
+ // Root.RemoveAll notices that a/ is not a directory,
+ // and returns success.
+ // os.RemoveAll tries to open a/ and fails because
+ // it is not a regular file.
+ // The inconsistency here isn't worth fixing, so just skip this test.
+ t.Skip("known inconsistency on windows")
+ }
+ },
}, {
name: "question mark",
open: "?",
}
t.Run(test.name, func(t *testing.T) {
+ if test.check != nil {
+ test.check(t)
+ }
+
dir1 := makefs(t, test.fs)
dir2 := makefs(t, test.fs)
if test.fsFunc != nil {
}
}
+func TestRootConsistencyRemoveAll(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.RemoveAll(path)
+ } else {
+ err = r.RemoveAll(path)
+ }
+ return "", err
+ })
+ }
+}
+
func TestRootConsistencyStat(t *testing.T) {
for _, test := range rootConsistencyTestCases {
test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
}
}
}
+
+func TestRootRemoveDot(t *testing.T) {
+ dir := t.TempDir()
+ root, err := os.OpenRoot(dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer root.Close()
+ if err := root.Remove("."); err == nil {
+ t.Errorf(`root.Remove(".") = %v, want error`, err)
+ }
+ if err := root.RemoveAll("."); err == nil {
+ t.Errorf(`root.RemoveAll(".") = %v, want error`, err)
+ }
+ if _, err := os.Stat(dir); err != nil {
+ t.Error(`root.Remove(All)?(".") removed the root`)
+ }
+}
"time"
)
+// sysfdType is the native type of a file handle
+// (int on Unix, syscall.Handle on Windows),
+// permitting helper functions to be written portably.
type sysfdType = int
// openRootNolog is OpenRoot.
return s, nil
}
+// sysfdType is the native type of a file handle
+// (int on Unix, syscall.Handle on Windows),
+// permitting helper functions to be written portably.
type sysfdType = syscall.Handle
// openRootNolog is OpenRoot.