os: add Root.MkdirAll

For #67002

Change-Id: Idd74b5b59e787e89bdfad82171b6a7719465f501
Reviewed-on: https://go-review.googlesource.com/c/go/+/674116
Reviewed-by: Alan Donovan <adonovan@google.com>
Auto-Submit: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Damien Neil 2025-05-19 13:09:06 -07:00 committed by Gopher Robot
parent 63dcc7b906
commit e59e128f90
11 changed files with 300 additions and 88 deletions

View File

@ -3,6 +3,7 @@ pkg os, method (*Root) Chown(string, int, int) error #67002
pkg os, method (*Root) Chtimes(string, time.Time, time.Time) error #67002 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) Lchown(string, int, int) error #67002
pkg os, method (*Root) Link(string, string) 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) Readlink(string) (string, error) #67002
pkg os, method (*Root) RemoveAll(string) error #67002 pkg os, method (*Root) RemoveAll(string) error #67002
pkg os, method (*Root) Rename(string, string) error #67002 pkg os, method (*Root) Rename(string, string) error #67002

View File

@ -5,6 +5,7 @@ The [os.Root] type supports the following additional methods:
* [os.Root.Chtimes] * [os.Root.Chtimes]
* [os.Root.Lchown] * [os.Root.Lchown]
* [os.Root.Link] * [os.Root.Link]
* [os.Root.MkdirAll]
* [os.Root.Readlink] * [os.Root.Readlink]
* [os.Root.RemoveAll] * [os.Root.RemoveAll]
* [os.Root.Rename] * [os.Root.Rename]

View File

@ -159,6 +159,8 @@ func ntCreateFileError(err error, flag uint64) error {
} }
case STATUS_FILE_IS_A_DIRECTORY: case STATUS_FILE_IS_A_DIRECTORY:
return syscall.EISDIR return syscall.EISDIR
case STATUS_OBJECT_NAME_COLLISION:
return syscall.EEXIST
} }
return s.Errno() return s.Errno()
} }

View File

@ -548,6 +548,7 @@ func (s NTStatus) Error() string {
// At the moment, we only need a couple, so just put them here manually. // 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. // If this list starts getting long, we should consider generating the full set.
const ( const (
STATUS_OBJECT_NAME_COLLISION NTStatus = 0xC0000035
STATUS_FILE_IS_A_DIRECTORY NTStatus = 0xC00000BA STATUS_FILE_IS_A_DIRECTORY NTStatus = 0xC00000BA
STATUS_DIRECTORY_NOT_EMPTY NTStatus = 0xC0000101 STATUS_DIRECTORY_NOT_EMPTY NTStatus = 0xC0000101
STATUS_NOT_A_DIRECTORY NTStatus = 0xC0000103 STATUS_NOT_A_DIRECTORY NTStatus = 0xC0000103

View File

@ -16,63 +16,84 @@ import (
var isReadonlyError = func(error) bool { return false } var isReadonlyError = func(error) bool { return false }
func TestMkdirAll(t *testing.T) { 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 := "_TestMkdirAll_/dir/./dir2"
path := tmpDir + "/_TestMkdirAll_/dir/./dir2" err := mkdirAll(path, 0777)
err := MkdirAll(path, 0777)
if err != nil {
t.Fatalf("MkdirAll %q: %s", path, err)
}
defer RemoveAll(tmpDir + "/_TestMkdirAll_")
// 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()
// 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))
}
if runtime.GOOS == "windows" {
path := tmpDir + `\_TestMkdirAll_\dir\.\dir2\`
err := MkdirAll(path, 0777)
if err != nil { if err != nil {
t.Fatalf("MkdirAll %q: %s", path, err) 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)
}
// 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 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 := `_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)
} }
} }

View File

@ -145,7 +145,7 @@ func (r *Root) Chmod(name string, mode FileMode) error {
// See [Mkdir] for more details. // See [Mkdir] for more details.
// //
// If perm contains bits other than the nine least-significant bits (0o777), // 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 { func (r *Root) Mkdir(name string, perm FileMode) error {
if perm&0o777 != perm { if perm&0o777 != perm {
return &PathError{Op: "mkdirat", Path: name, Err: errors.New("unsupported file mode")} return &PathError{Op: "mkdirat", Path: name, Err: errors.New("unsupported file mode")}
@ -153,6 +153,18 @@ func (r *Root) Mkdir(name string, perm FileMode) error {
return rootMkdir(r, name, perm) 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. // Chown changes the numeric uid and gid of the named file in the root.
// See [Chown] for more details. // See [Chown] for more details.
func (r *Root) Chown(name string, uid, gid int) error { func (r *Root) Chown(name string, uid, gid int) error {

View File

@ -8,6 +8,7 @@ package os
import ( import (
"errors" "errors"
"internal/stringslite"
"sync/atomic" "sync/atomic"
"syscall" "syscall"
"time" "time"
@ -147,6 +148,27 @@ func rootMkdir(r *Root, name string, perm FileMode) error {
return nil 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 { func rootRemove(r *Root, name string) error {
if err := checkPathEscapesLstat(r, name); err != nil { if err := checkPathEscapesLstat(r, name); err != nil {
return &PathError{Op: "removeat", Path: name, Err: err} return &PathError{Op: "removeat", Path: name, Err: err}

View File

@ -69,7 +69,7 @@ func (r *root) Name() string {
} }
func rootChmod(r *Root, name string, mode FileMode) error { 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) return struct{}{}, chmodat(parent, name, mode)
}) })
if err != nil { if err != nil {
@ -79,7 +79,7 @@ func rootChmod(r *Root, name string, mode FileMode) error {
} }
func rootChown(r *Root, name string, uid, gid int) error { 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) return struct{}{}, chownat(parent, name, uid, gid)
}) })
if err != nil { if err != nil {
@ -89,7 +89,7 @@ func rootChown(r *Root, name string, uid, gid int) error {
} }
func rootLchown(r *Root, name string, uid, gid int) error { 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) return struct{}{}, lchownat(parent, name, uid, gid)
}) })
if err != nil { if err != nil {
@ -99,7 +99,7 @@ func rootLchown(r *Root, name string, uid, gid int) error {
} }
func rootChtimes(r *Root, name string, atime time.Time, mtime time.Time) error { 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) return struct{}{}, chtimesat(parent, name, atime, mtime)
}) })
if err != nil { if err != nil {
@ -109,7 +109,7 @@ func rootChtimes(r *Root, name string, atime time.Time, mtime time.Time) error {
} }
func rootMkdir(r *Root, name string, perm FileMode) error { 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) return struct{}{}, mkdirat(parent, name, perm)
}) })
if err != nil { if err != nil {
@ -118,8 +118,67 @@ func rootMkdir(r *Root, name string, perm FileMode) error {
return 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) { 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) return readlinkat(parent, name)
}) })
if err != nil { if err != nil {
@ -129,7 +188,7 @@ func rootReadlink(r *Root, name string) (string, error) {
} }
func rootRemove(r *Root, name string) error { 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) return struct{}{}, removeat(parent, name)
}) })
if err != nil { if err != nil {
@ -148,7 +207,7 @@ func rootRemoveAll(r *Root, name string) error {
// Consistency with os.RemoveAll: Return EINVAL when trying to remove . // Consistency with os.RemoveAll: Return EINVAL when trying to remove .
return &PathError{Op: "RemoveAll", Path: name, Err: syscall.EINVAL} 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) return struct{}{}, removeAllFrom(parent, name)
}) })
if IsNotExist(err) { if IsNotExist(err) {
@ -161,8 +220,8 @@ func rootRemoveAll(r *Root, name string) error {
} }
func rootRename(r *Root, oldname, newname string) error { func rootRename(r *Root, oldname, newname string) error {
_, err := doInRoot(r, oldname, func(oldparent sysfdType, oldname string) (struct{}, error) { _, err := doInRoot(r, oldname, nil, func(oldparent sysfdType, oldname string) (struct{}, error) {
_, err := doInRoot(r, newname, func(newparent sysfdType, newname string) (struct{}, error) { _, err := doInRoot(r, newname, nil, func(newparent sysfdType, newname string) (struct{}, error) {
return struct{}{}, renameat(oldparent, oldname, newparent, newname) return struct{}{}, renameat(oldparent, oldname, newparent, newname)
}) })
return struct{}{}, err return struct{}{}, err
@ -174,8 +233,8 @@ func rootRename(r *Root, oldname, newname string) error {
} }
func rootLink(r *Root, oldname, newname string) error { func rootLink(r *Root, oldname, newname string) error {
_, err := doInRoot(r, oldname, func(oldparent sysfdType, oldname string) (struct{}, error) { _, err := doInRoot(r, oldname, nil, func(oldparent sysfdType, oldname string) (struct{}, error) {
_, err := doInRoot(r, newname, func(newparent sysfdType, newname string) (struct{}, error) { _, err := doInRoot(r, newname, nil, func(newparent sysfdType, newname string) (struct{}, error) {
return struct{}{}, linkat(oldparent, oldname, newparent, newname) return struct{}{}, linkat(oldparent, oldname, newparent, newname)
}) })
return struct{}{}, err return struct{}{}, err
@ -188,13 +247,23 @@ func rootLink(r *Root, oldname, newname string) error {
// doInRoot performs an operation on a path in a Root. // doInRoot performs an operation on a path in a Root.
// //
// It opens the directory containing the final element of the path, // It calls f with the FD or handle for the directory containing the last
// and calls f with the directory FD and name of the final element. // path element, and the name of the last path element.
// //
// If the path refers to a symlink which should be followed, // For example, given the path a/b/c it calls f with the FD for a/b and the name "c".
// then f must return errSymlink. //
// doInRoot will follow the symlink and call f again. // If openDirFunc is non-nil, it is called to open intermediate path elements.
func doInRoot[T any](r *Root, name string, f func(parent sysfdType, name string) (T, error)) (ret T, err error) { // 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 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 { if err := r.root.incref(); err != nil {
return ret, err return ret, err
} }
@ -204,6 +273,9 @@ func doInRoot[T any](r *Root, name string, f func(parent sysfdType, name string)
if err != nil { if err != nil {
return ret, err return ret, err
} }
if openDirFunc == nil {
openDirFunc = rootOpenDir
}
rootfd := r.root.fd rootfd := r.root.fd
dirfd := rootfd dirfd := rootfd
@ -226,6 +298,7 @@ func doInRoot[T any](r *Root, name string, f func(parent sysfdType, name string)
steps := 0 steps := 0
restarts := 0 restarts := 0
symlinks := 0 symlinks := 0
Loop:
for { for {
steps++ steps++
if steps > maxSteps && restarts > maxRestarts { if steps > maxSteps && restarts > maxRestarts {
@ -267,23 +340,23 @@ func doInRoot[T any](r *Root, name string, f func(parent sysfdType, name string)
// suffixSep contains any trailing separator characters // suffixSep contains any trailing separator characters
// which we rejoin to the final part at this time. // which we rejoin to the final part at this time.
ret, err = f(dirfd, parts[i]+suffixSep) ret, err = f(dirfd, parts[i]+suffixSep)
if _, ok := err.(errSymlink); !ok { if err == nil {
return ret, err return
} }
} else { } else {
var fd sysfdType var fd sysfdType
fd, err = rootOpenDir(dirfd, parts[i]) fd, err = openDirFunc(dirfd, parts[i])
if err == nil { if err == nil {
if dirfd != rootfd { if dirfd != rootfd {
syscall.Close(dirfd) syscall.Close(dirfd)
} }
dirfd = fd 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++ symlinks++
if symlinks > rootMaxSymlinks { if symlinks > rootMaxSymlinks {
return ret, syscall.ELOOP return ret, syscall.ELOOP
@ -311,7 +384,16 @@ func doInRoot[T any](r *Root, name string, f func(parent sysfdType, name string)
dirfd = rootfd dirfd = rootfd
} }
parts = newparts 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++ i++

View File

@ -551,6 +551,40 @@ func TestRootMkdir(t *testing.T) {
} }
} }
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) { func TestRootOpenRoot(t *testing.T) {
for _, test := range rootTestCases { for _, test := range rootTestCases {
test.run(t, func(t *testing.T, target string, root *os.Root) { test.run(t, func(t *testing.T, target string, root *os.Root) {
@ -1115,7 +1149,7 @@ var rootConsistencyTestCases = []rootConsistencyTest{{
name: "symlink to dir ends in slash", name: "symlink to dir ends in slash",
fs: []string{ fs: []string{
"dir/", "dir/",
"link => dir", "link => dir/",
}, },
open: "link", open: "link",
}, { }, {
@ -1361,6 +1395,20 @@ func TestRootConsistencyMkdir(t *testing.T) {
} }
} }
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) { func TestRootConsistencyRemove(t *testing.T) {
for _, test := range rootConsistencyTestCases { for _, test := range rootConsistencyTestCases {
if test.open == "." || test.open == "./" { if test.open == "." || test.open == "./" {

View File

@ -62,7 +62,7 @@ func newRoot(fd int, name string) (*Root, error) {
// openRootInRoot is Root.OpenRoot. // openRootInRoot is Root.OpenRoot.
func openRootInRoot(r *Root, name string) (*Root, error) { 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 { ignoringEINTR(func() error {
fd, err = unix.Openat(parent, name, syscall.O_NOFOLLOW|syscall.O_CLOEXEC, 0) fd, err = unix.Openat(parent, name, syscall.O_NOFOLLOW|syscall.O_CLOEXEC, 0)
if isNoFollowErr(err) { if isNoFollowErr(err) {
@ -80,7 +80,7 @@ func openRootInRoot(r *Root, name string) (*Root, error) {
// rootOpenFileNolog is Root.OpenFile. // rootOpenFileNolog is Root.OpenFile.
func rootOpenFileNolog(root *Root, name string, flag int, perm FileMode) (*File, error) { 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 { ignoringEINTR(func() error {
fd, err = unix.Openat(parent, name, syscall.O_NOFOLLOW|syscall.O_CLOEXEC|flag, uint32(perm)) fd, err = unix.Openat(parent, name, syscall.O_NOFOLLOW|syscall.O_CLOEXEC|flag, uint32(perm))
if isNoFollowErr(err) || err == syscall.ENOTDIR { if isNoFollowErr(err) || err == syscall.ENOTDIR {
@ -118,7 +118,7 @@ func rootOpenDir(parent int, name string) (int, error) {
} }
func rootStat(r *Root, name string, lstat bool) (FileInfo, error) { 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 var fs fileStat
if err := unix.Fstatat(parent, n, &fs.sys, unix.AT_SYMLINK_NOFOLLOW); err != nil { if err := unix.Fstatat(parent, n, &fs.sys, unix.AT_SYMLINK_NOFOLLOW); err != nil {
return nil, err return nil, err
@ -136,7 +136,7 @@ func rootStat(r *Root, name string, lstat bool) (FileInfo, error) {
} }
func rootSymlink(r *Root, oldname, newname string) error { 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) return struct{}{}, symlinkat(oldname, parent, name)
}) })
if err != nil { if err != nil {
@ -246,6 +246,15 @@ func symlinkat(oldname string, newfd int, newname string) error {
return unix.Symlinkat(oldname, newfd, newname) 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, // checkSymlink resolves the symlink name in parent,
// and returns errSymlink with the link contents. // and returns errSymlink with the link contents.
// //

View File

@ -119,7 +119,7 @@ func newRoot(fd syscall.Handle, name string) (*Root, error) {
// openRootInRoot is Root.OpenRoot. // openRootInRoot is Root.OpenRoot.
func openRootInRoot(r *Root, name string) (*Root, error) { func openRootInRoot(r *Root, name string) (*Root, error) {
fd, err := doInRoot(r, name, rootOpenDir) fd, err := doInRoot(r, name, nil, rootOpenDir)
if err != nil { if err != nil {
return nil, &PathError{Op: "openat", Path: name, Err: err} return nil, &PathError{Op: "openat", Path: name, Err: err}
} }
@ -128,7 +128,7 @@ func openRootInRoot(r *Root, name string) (*Root, error) {
// rootOpenFileNolog is Root.OpenFile. // rootOpenFileNolog is Root.OpenFile.
func rootOpenFileNolog(root *Root, name string, flag int, perm FileMode) (*File, error) { 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) return openat(parent, name, flag, perm)
}) })
if err != nil { if err != nil {
@ -212,7 +212,7 @@ func rootStat(r *Root, name string, lstat bool) (FileInfo, error) {
// merely the empirical evidence that Lstat behaves this way. // merely the empirical evidence that Lstat behaves this way.
lstat = false 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) fd, err := openat(parent, n, windows.O_OPEN_REPARSE, 0)
if err != nil { if err != nil {
return nil, err return nil, err
@ -274,7 +274,7 @@ func rootSymlink(r *Root, oldname, newname string) error {
flags |= windows.SYMLINKAT_RELATIVE 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) return struct{}{}, windows.Symlinkat(oldname, parent, name, flags)
}) })
if err != nil { if err != nil {
@ -389,3 +389,16 @@ func readlinkat(dirfd syscall.Handle, name string) (string, error) {
defer syscall.CloseHandle(fd) defer syscall.CloseHandle(fd)
return readReparseLinkHandle(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
}