mirror of
https://github.com/golang/go.git
synced 2025-05-29 03:11:26 +00:00
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:
parent
63dcc7b906
commit
e59e128f90
@ -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
|
||||||
|
@ -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]
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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}
|
||||||
|
@ -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++
|
||||||
|
@ -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 == "./" {
|
||||||
|
@ -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.
|
||||||
//
|
//
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user