pkg os, method (*Root) Chtimes(string, time.Time, time.Time) error #67002
pkg os, method (*Root) Lchown(string, int, int) error #67002
pkg os, method (*Root) Link(string, string) error #67002
+pkg os, method (*Root) MkdirAll(string, fs.FileMode) 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
* [os.Root.Chtimes]
* [os.Root.Lchown]
* [os.Root.Link]
+ * [os.Root.MkdirAll]
* [os.Root.Readlink]
* [os.Root.RemoveAll]
* [os.Root.Rename]
}
case STATUS_FILE_IS_A_DIRECTORY:
return syscall.EISDIR
+ case STATUS_OBJECT_NAME_COLLISION:
+ return syscall.EEXIST
}
return s.Errno()
}
// At the moment, we only need a couple, so just put them here manually.
// If this list starts getting long, we should consider generating the full set.
const (
+ STATUS_OBJECT_NAME_COLLISION NTStatus = 0xC0000035
STATUS_FILE_IS_A_DIRECTORY NTStatus = 0xC00000BA
STATUS_DIRECTORY_NOT_EMPTY NTStatus = 0xC0000101
STATUS_NOT_A_DIRECTORY NTStatus = 0xC0000103
var isReadonlyError = func(error) bool { return false }
func TestMkdirAll(t *testing.T) {
- t.Parallel()
+ testMaybeRooted(t, func(t *testing.T, r *Root) {
+ mkdirAll := MkdirAll
+ create := Create
+ if r != nil {
+ mkdirAll = r.MkdirAll
+ create = r.Create
+ }
- tmpDir := TempDir()
- path := tmpDir + "/_TestMkdirAll_/dir/./dir2"
- err := MkdirAll(path, 0777)
- if err != nil {
- t.Fatalf("MkdirAll %q: %s", path, err)
- }
- defer RemoveAll(tmpDir + "/_TestMkdirAll_")
+ path := "_TestMkdirAll_/dir/./dir2"
+ err := mkdirAll(path, 0777)
+ if err != nil {
+ t.Fatalf("MkdirAll %q: %s", path, err)
+ }
- // Already exists, should succeed.
- err = MkdirAll(path, 0777)
- if err != nil {
- t.Fatalf("MkdirAll %q (second time): %s", path, err)
- }
+ // Already exists, should succeed.
+ err = mkdirAll(path, 0777)
+ if err != nil {
+ t.Fatalf("MkdirAll %q (second time): %s", path, err)
+ }
- // Make file.
- fpath := path + "/file"
- f, err := Create(fpath)
- if err != nil {
- t.Fatalf("create %q: %s", fpath, err)
- }
- defer f.Close()
+ // Make file.
+ fpath := path + "/file"
+ f, err := create(fpath)
+ if err != nil {
+ t.Fatalf("create %q: %s", fpath, err)
+ }
+ defer f.Close()
- // Can't make directory named after file.
- err = MkdirAll(fpath, 0777)
- if err == nil {
- t.Fatalf("MkdirAll %q: no error", fpath)
- }
- perr, ok := err.(*PathError)
- if !ok {
- t.Fatalf("MkdirAll %q returned %T, not *PathError", fpath, err)
- }
- if filepath.Clean(perr.Path) != filepath.Clean(fpath) {
- t.Fatalf("MkdirAll %q returned wrong error path: %q not %q", fpath, filepath.Clean(perr.Path), filepath.Clean(fpath))
- }
+ // Can't make directory named after file.
+ err = mkdirAll(fpath, 0777)
+ if err == nil {
+ t.Fatalf("MkdirAll %q: no error", fpath)
+ }
+ perr, ok := err.(*PathError)
+ if !ok {
+ t.Fatalf("MkdirAll %q returned %T, not *PathError", fpath, err)
+ }
+ if filepath.Clean(perr.Path) != filepath.Clean(fpath) {
+ t.Fatalf("MkdirAll %q returned wrong error path: %q not %q", fpath, filepath.Clean(perr.Path), filepath.Clean(fpath))
+ }
- // Can't make subdirectory of file.
- ffpath := fpath + "/subdir"
- err = MkdirAll(ffpath, 0777)
- if err == nil {
- t.Fatalf("MkdirAll %q: no error", ffpath)
- }
- perr, ok = err.(*PathError)
- if !ok {
- t.Fatalf("MkdirAll %q returned %T, not *PathError", ffpath, err)
- }
- if filepath.Clean(perr.Path) != filepath.Clean(fpath) {
- t.Fatalf("MkdirAll %q returned wrong error path: %q not %q", ffpath, filepath.Clean(perr.Path), filepath.Clean(fpath))
- }
+ // Can't make subdirectory of file.
+ ffpath := fpath + "/subdir"
+ err = mkdirAll(ffpath, 0777)
+ if err == nil {
+ t.Fatalf("MkdirAll %q: no error", ffpath)
+ }
+ perr, ok = err.(*PathError)
+ if !ok {
+ t.Fatalf("MkdirAll %q returned %T, not *PathError", ffpath, err)
+ }
+ if filepath.Clean(perr.Path) != filepath.Clean(fpath) {
+ t.Fatalf("MkdirAll %q returned wrong error path: %q not %q", ffpath, filepath.Clean(perr.Path), filepath.Clean(fpath))
+ }
- if runtime.GOOS == "windows" {
- path := tmpDir + `\_TestMkdirAll_\dir\.\dir2\`
- err := MkdirAll(path, 0777)
- if err != nil {
- t.Fatalf("MkdirAll %q: %s", path, err)
+ if runtime.GOOS == "windows" {
+ path := `_TestMkdirAll_\dir\.\dir2\`
+ err := mkdirAll(path, 0777)
+ if err != nil {
+ t.Fatalf("MkdirAll %q: %s", path, err)
+ }
}
+ })
+}
+
+func TestMkdirAllAbsPath(t *testing.T) {
+ t.Parallel()
+ tmpDir := t.TempDir()
+ path := filepath.Join(tmpDir, "/a/b/c")
+ if err := MkdirAll(path, 0o777); err != nil {
+ t.Fatal(err)
+ }
+ st, err := Stat(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !st.IsDir() {
+ t.Fatalf("after MkdirAll(%q, 0o777), %q is not a directory", path, path)
}
}
// See [Mkdir] for more details.
//
// If perm contains bits other than the nine least-significant bits (0o777),
-// OpenFile returns an error.
+// Mkdir returns an error.
func (r *Root) Mkdir(name string, perm FileMode) error {
if perm&0o777 != perm {
return &PathError{Op: "mkdirat", Path: name, Err: errors.New("unsupported file mode")}
return rootMkdir(r, name, perm)
}
+// MkdirAll creates a new directory in the root, along with any necessary parents.
+// See [MkdirAll] for more details.
+//
+// If perm contains bits other than the nine least-significant bits (0o777),
+// MkdirAll returns an error.
+func (r *Root) MkdirAll(name string, perm FileMode) error {
+ if perm&0o777 != perm {
+ return &PathError{Op: "mkdirat", Path: name, Err: errors.New("unsupported file mode")}
+ }
+ return rootMkdirAll(r, name, perm)
+}
+
// Chown changes the numeric uid and gid of the named file in the root.
// See [Chown] for more details.
func (r *Root) Chown(name string, uid, gid int) error {
import (
"errors"
+ "internal/stringslite"
"sync/atomic"
"syscall"
"time"
return nil
}
+func rootMkdirAll(r *Root, name string, perm FileMode) error {
+ // We only check for errPathEscapes here.
+ // For errors such as ENOTDIR (a non-directory file appeared somewhere along the path),
+ // we let MkdirAll generate the error.
+ // MkdirAll will return a PathError referencing the exact location of the error,
+ // and we want to preserve that property.
+ if err := checkPathEscapes(r, name); err == errPathEscapes {
+ return &PathError{Op: "mkdirat", Path: name, Err: err}
+ }
+ prefix := r.root.name + string(PathSeparator)
+ if err := MkdirAll(prefix+name, perm); err != nil {
+ if pe, ok := err.(*PathError); ok {
+ pe.Op = "mkdirat"
+ pe.Path = stringslite.TrimPrefix(pe.Path, prefix)
+ return pe
+ }
+ return &PathError{Op: "mkdirat", Path: name, Err: underlyingError(err)}
+ }
+ return nil
+}
+
func rootRemove(r *Root, name string) error {
if err := checkPathEscapesLstat(r, name); err != nil {
return &PathError{Op: "removeat", Path: name, Err: err}
}
func rootChmod(r *Root, name string, mode FileMode) error {
- _, err := doInRoot(r, name, func(parent sysfdType, name string) (struct{}, error) {
+ _, err := doInRoot(r, name, nil, func(parent sysfdType, name string) (struct{}, error) {
return struct{}{}, chmodat(parent, name, mode)
})
if err != nil {
}
func rootChown(r *Root, name string, uid, gid int) error {
- _, err := doInRoot(r, name, func(parent sysfdType, name string) (struct{}, error) {
+ _, err := doInRoot(r, name, nil, func(parent sysfdType, name string) (struct{}, error) {
return struct{}{}, chownat(parent, name, uid, gid)
})
if err != nil {
}
func rootLchown(r *Root, name string, uid, gid int) error {
- _, err := doInRoot(r, name, func(parent sysfdType, name string) (struct{}, error) {
+ _, err := doInRoot(r, name, nil, func(parent sysfdType, name string) (struct{}, error) {
return struct{}{}, lchownat(parent, name, uid, gid)
})
if err != nil {
}
func rootChtimes(r *Root, name string, atime time.Time, mtime time.Time) error {
- _, err := doInRoot(r, name, func(parent sysfdType, name string) (struct{}, error) {
+ _, err := doInRoot(r, name, nil, func(parent sysfdType, name string) (struct{}, error) {
return struct{}{}, chtimesat(parent, name, atime, mtime)
})
if err != nil {
}
func rootMkdir(r *Root, name string, perm FileMode) error {
- _, err := doInRoot(r, name, func(parent sysfdType, name string) (struct{}, error) {
+ _, err := doInRoot(r, name, nil, func(parent sysfdType, name string) (struct{}, error) {
return struct{}{}, mkdirat(parent, name, perm)
})
if err != nil {
return nil
}
+func rootMkdirAll(r *Root, fullname string, perm FileMode) error {
+ // doInRoot opens each path element in turn.
+ //
+ // openDirFunc opens all but the last path component.
+ // The usual default openDirFunc just opens directories with O_DIRECTORY.
+ // We replace it here with one that creates missing directories along the way.
+ openDirFunc := func(parent sysfdType, name string) (sysfdType, error) {
+ for try := range 2 {
+ fd, err := rootOpenDir(parent, name)
+ switch err.(type) {
+ case nil, errSymlink:
+ return fd, err
+ }
+ if try > 0 || !IsNotExist(err) {
+ return 0, &PathError{Op: "openat", Err: err}
+ }
+ if err := mkdirat(parent, name, perm); err != nil {
+ return 0, &PathError{Op: "mkdirat", Err: err}
+ }
+ }
+ panic("unreachable")
+ }
+ // openLastComponentFunc opens the last path component.
+ openLastComponentFunc := func(parent sysfdType, name string) (struct{}, error) {
+ err := mkdirat(parent, name, perm)
+ if err == syscall.EEXIST {
+ mode, e := modeAt(parent, name)
+ if e == nil {
+ if mode.IsDir() {
+ // The target of MkdirAll is an existing directory.
+ err = nil
+ } else if mode&ModeSymlink != 0 {
+ // The target of MkdirAll is a symlink.
+ // For consistency with os.MkdirAll,
+ // succeed if the link resolves to a directory.
+ // We don't return errSymlink here, because we don't
+ // want to create the link target if it doesn't exist.
+ fi, e := r.Stat(fullname)
+ if e == nil && fi.Mode().IsDir() {
+ err = nil
+ }
+ }
+ }
+ }
+ switch err.(type) {
+ case nil, errSymlink:
+ return struct{}{}, err
+ }
+ return struct{}{}, &PathError{Op: "mkdirat", Err: err}
+ }
+ _, err := doInRoot(r, fullname, openDirFunc, openLastComponentFunc)
+ if err != nil {
+ if _, ok := err.(*PathError); !ok {
+ err = &PathError{Op: "mkdirat", Path: fullname, Err: err}
+ }
+ }
+ return err
+}
+
func rootReadlink(r *Root, name string) (string, error) {
- target, err := doInRoot(r, name, func(parent sysfdType, name string) (string, error) {
+ target, err := doInRoot(r, name, nil, func(parent sysfdType, name string) (string, error) {
return readlinkat(parent, name)
})
if err != nil {
}
func rootRemove(r *Root, name string) error {
- _, err := doInRoot(r, name, func(parent sysfdType, name string) (struct{}, error) {
+ _, err := doInRoot(r, name, nil, func(parent sysfdType, name string) (struct{}, error) {
return struct{}{}, removeat(parent, name)
})
if err != nil {
// 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) {
+ _, err := doInRoot(r, name, nil, func(parent sysfdType, name string) (struct{}, error) {
return struct{}{}, removeAllFrom(parent, name)
})
if IsNotExist(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) {
+ _, err := doInRoot(r, oldname, nil, func(oldparent sysfdType, oldname string) (struct{}, error) {
+ _, err := doInRoot(r, newname, nil, func(newparent sysfdType, newname string) (struct{}, error) {
return struct{}{}, renameat(oldparent, oldname, newparent, newname)
})
return struct{}{}, err
}
func rootLink(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) {
+ _, err := doInRoot(r, oldname, nil, func(oldparent sysfdType, oldname string) (struct{}, error) {
+ _, err := doInRoot(r, newname, nil, func(newparent sysfdType, newname string) (struct{}, error) {
return struct{}{}, linkat(oldparent, oldname, newparent, newname)
})
return struct{}{}, err
// doInRoot performs an operation on a path in a Root.
//
-// It opens the directory containing the final element of the path,
-// and calls f with the directory FD and name of the final element.
+// It calls f with the FD or handle for the directory containing the last
+// path element, and the name of the last path element.
+//
+// For example, given the path a/b/c it calls f with the FD for a/b and the name "c".
+//
+// If openDirFunc is non-nil, it is called to open intermediate path elements.
+// For example, given the path a/b/c openDirFunc will be called to open a and a/b in turn.
+//
+// f or openDirFunc may return errSymlink to indicate that the path element is a symlink
+// which should be followed. Note that this can result in f being called multiple times
+// with different names. For example, give the path "link" which is a symlink to "target",
+// f is called with the path "link", returns errSymlink("target"), and is called again with
+// the path "target".
//
-// If the path refers to a symlink which should be followed,
-// then f must return errSymlink.
-// doInRoot will follow the symlink and call f again.
-func doInRoot[T any](r *Root, name string, f func(parent sysfdType, name string) (T, error)) (ret T, err error) {
+// If f or openDirFunc return a *PathError, doInRoot will set PathError.Path to the
+// full path which caused the error.
+func doInRoot[T any](r *Root, name string, openDirFunc func(parent sysfdType, name string) (sysfdType, error), f func(parent sysfdType, name string) (T, error)) (ret T, err error) {
if err := r.root.incref(); err != nil {
return ret, err
}
if err != nil {
return ret, err
}
+ if openDirFunc == nil {
+ openDirFunc = rootOpenDir
+ }
rootfd := r.root.fd
dirfd := rootfd
steps := 0
restarts := 0
symlinks := 0
+Loop:
for {
steps++
if steps > maxSteps && restarts > maxRestarts {
// suffixSep contains any trailing separator characters
// which we rejoin to the final part at this time.
ret, err = f(dirfd, parts[i]+suffixSep)
- if _, ok := err.(errSymlink); !ok {
- return ret, err
+ if err == nil {
+ return
}
} else {
var fd sysfdType
- fd, err = rootOpenDir(dirfd, parts[i])
+ fd, err = openDirFunc(dirfd, parts[i])
if err == nil {
if dirfd != rootfd {
syscall.Close(dirfd)
}
dirfd = fd
- } else if _, ok := err.(errSymlink); !ok {
- return ret, err
}
}
- if e, ok := err.(errSymlink); ok {
+ switch e := err.(type) {
+ case nil:
+ case errSymlink:
symlinks++
if symlinks > rootMaxSymlinks {
return ret, syscall.ELOOP
dirfd = rootfd
}
parts = newparts
- continue
+ continue Loop
+ case *PathError:
+ // This is strings.Join(parts[:i+1], PathSeparator).
+ e.Path = parts[0]
+ for _, part := range parts[1 : i+1] {
+ e.Path += string(PathSeparator) + part
+ }
+ return ret, e
+ default:
+ return ret, err
}
i++
}
}
+func TestRootMkdirAll(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.MkdirAll(%q)", test.open) {
+ return
+ }
+ fi, err := os.Lstat(target)
+ if err != nil {
+ t.Fatalf(`stat file created with Root.MkdirAll(%q): %v`, test.open, err)
+ }
+ if !fi.IsDir() {
+ t.Fatalf(`stat file created with Root.MkdirAll(%q): not a directory`, test.open)
+ }
+ if mode := fi.Mode(); mode&0o777 == 0 {
+ // Issue #73559: We're not going to worry about the exact
+ // mode bits (which will have been modified by umask),
+ // but there should be mode bits.
+ t.Fatalf(`stat file created with Root.MkdirAll(%q): mode=%v, want non-zero`, test.open, mode)
+ }
+ })
+ }
+}
+
func TestRootOpenRoot(t *testing.T) {
for _, test := range rootTestCases {
test.run(t, func(t *testing.T, target string, root *os.Root) {
name: "symlink to dir ends in slash",
fs: []string{
"dir/",
- "link => dir",
+ "link => dir/",
},
open: "link",
}, {
}
}
+func TestRootConsistencyMkdirAll(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.MkdirAll(path, 0o777)
+ } else {
+ err = r.MkdirAll(path, 0o777)
+ }
+ return "", err
+ })
+ }
+}
+
func TestRootConsistencyRemove(t *testing.T) {
for _, test := range rootConsistencyTestCases {
if test.open == "." || test.open == "./" {
// openRootInRoot is Root.OpenRoot.
func openRootInRoot(r *Root, name string) (*Root, error) {
- fd, err := doInRoot(r, name, func(parent int, name string) (fd int, err error) {
+ fd, err := doInRoot(r, name, nil, func(parent int, name string) (fd int, err error) {
ignoringEINTR(func() error {
fd, err = unix.Openat(parent, name, syscall.O_NOFOLLOW|syscall.O_CLOEXEC, 0)
if isNoFollowErr(err) {
// rootOpenFileNolog is Root.OpenFile.
func rootOpenFileNolog(root *Root, name string, flag int, perm FileMode) (*File, error) {
- fd, err := doInRoot(root, name, func(parent int, name string) (fd int, err error) {
+ fd, err := doInRoot(root, name, nil, func(parent int, name string) (fd int, err error) {
ignoringEINTR(func() error {
fd, err = unix.Openat(parent, name, syscall.O_NOFOLLOW|syscall.O_CLOEXEC|flag, uint32(perm))
if isNoFollowErr(err) || err == syscall.ENOTDIR {
}
func rootStat(r *Root, name string, lstat bool) (FileInfo, error) {
- fi, err := doInRoot(r, name, func(parent sysfdType, n string) (FileInfo, error) {
+ fi, err := doInRoot(r, name, nil, func(parent sysfdType, n string) (FileInfo, error) {
var fs fileStat
if err := unix.Fstatat(parent, n, &fs.sys, unix.AT_SYMLINK_NOFOLLOW); err != nil {
return nil, err
}
func rootSymlink(r *Root, oldname, newname string) error {
- _, err := doInRoot(r, newname, func(parent sysfdType, name string) (struct{}, error) {
+ _, err := doInRoot(r, newname, nil, func(parent sysfdType, name string) (struct{}, error) {
return struct{}{}, symlinkat(oldname, parent, name)
})
if err != nil {
return unix.Symlinkat(oldname, newfd, newname)
}
+func modeAt(parent int, name string) (FileMode, error) {
+ var fs fileStat
+ if err := unix.Fstatat(parent, name, &fs.sys, unix.AT_SYMLINK_NOFOLLOW); err != nil {
+ return 0, err
+ }
+ fillFileStatFromSys(&fs, name)
+ return fs.mode, nil
+}
+
// checkSymlink resolves the symlink name in parent,
// and returns errSymlink with the link contents.
//
// openRootInRoot is Root.OpenRoot.
func openRootInRoot(r *Root, name string) (*Root, error) {
- fd, err := doInRoot(r, name, rootOpenDir)
+ fd, err := doInRoot(r, name, nil, rootOpenDir)
if err != nil {
return nil, &PathError{Op: "openat", Path: name, Err: err}
}
// rootOpenFileNolog is Root.OpenFile.
func rootOpenFileNolog(root *Root, name string, flag int, perm FileMode) (*File, error) {
- fd, err := doInRoot(root, name, func(parent syscall.Handle, name string) (syscall.Handle, error) {
+ fd, err := doInRoot(root, name, nil, func(parent syscall.Handle, name string) (syscall.Handle, error) {
return openat(parent, name, flag, perm)
})
if err != nil {
// merely the empirical evidence that Lstat behaves this way.
lstat = false
}
- fi, err := doInRoot(r, name, func(parent syscall.Handle, n string) (FileInfo, error) {
+ fi, err := doInRoot(r, name, nil, func(parent syscall.Handle, n string) (FileInfo, error) {
fd, err := openat(parent, n, windows.O_OPEN_REPARSE, 0)
if err != nil {
return nil, err
flags |= windows.SYMLINKAT_RELATIVE
}
- _, err := doInRoot(r, newname, func(parent sysfdType, name string) (struct{}, error) {
+ _, err := doInRoot(r, newname, nil, func(parent sysfdType, name string) (struct{}, error) {
return struct{}{}, windows.Symlinkat(oldname, parent, name, flags)
})
if err != nil {
defer syscall.CloseHandle(fd)
return readReparseLinkHandle(fd)
}
+
+func modeAt(parent syscall.Handle, name string) (FileMode, error) {
+ fd, err := openat(parent, name, windows.O_OPEN_REPARSE|windows.O_DIRECTORY, 0)
+ if err != nil {
+ return 0, err
+ }
+ defer syscall.CloseHandle(fd)
+ fi, err := statHandle(name, fd)
+ if err != nil {
+ return 0, err
+ }
+ return fi.Mode(), nil
+}