Skip to content

Commit e59e128

Browse files
neildgopherbot
authored andcommitted
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>
1 parent 63dcc7b commit e59e128

File tree

11 files changed

+297
-85
lines changed

11 files changed

+297
-85
lines changed

api/next/67002.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pkg os, method (*Root) Chown(string, int, int) error #67002
33
pkg os, method (*Root) Chtimes(string, time.Time, time.Time) error #67002
44
pkg os, method (*Root) Lchown(string, int, int) error #67002
55
pkg os, method (*Root) Link(string, string) error #67002
6+
pkg os, method (*Root) MkdirAll(string, fs.FileMode) error #67002
67
pkg os, method (*Root) Readlink(string) (string, error) #67002
78
pkg os, method (*Root) RemoveAll(string) error #67002
89
pkg os, method (*Root) Rename(string, string) error #67002

doc/next/6-stdlib/99-minor/os/67002.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ The [os.Root] type supports the following additional methods:
55
* [os.Root.Chtimes]
66
* [os.Root.Lchown]
77
* [os.Root.Link]
8+
* [os.Root.MkdirAll]
89
* [os.Root.Readlink]
910
* [os.Root.RemoveAll]
1011
* [os.Root.Rename]

src/internal/syscall/windows/at_windows.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ func ntCreateFileError(err error, flag uint64) error {
159159
}
160160
case STATUS_FILE_IS_A_DIRECTORY:
161161
return syscall.EISDIR
162+
case STATUS_OBJECT_NAME_COLLISION:
163+
return syscall.EEXIST
162164
}
163165
return s.Errno()
164166
}

src/internal/syscall/windows/syscall_windows.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,7 @@ func (s NTStatus) Error() string {
548548
// At the moment, we only need a couple, so just put them here manually.
549549
// If this list starts getting long, we should consider generating the full set.
550550
const (
551+
STATUS_OBJECT_NAME_COLLISION NTStatus = 0xC0000035
551552
STATUS_FILE_IS_A_DIRECTORY NTStatus = 0xC00000BA
552553
STATUS_DIRECTORY_NOT_EMPTY NTStatus = 0xC0000101
553554
STATUS_NOT_A_DIRECTORY NTStatus = 0xC0000103

src/os/path_test.go

Lines changed: 71 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -16,63 +16,84 @@ import (
1616
var isReadonlyError = func(error) bool { return false }
1717

1818
func TestMkdirAll(t *testing.T) {
19-
t.Parallel()
19+
testMaybeRooted(t, func(t *testing.T, r *Root) {
20+
mkdirAll := MkdirAll
21+
create := Create
22+
if r != nil {
23+
mkdirAll = r.MkdirAll
24+
create = r.Create
25+
}
2026

21-
tmpDir := TempDir()
22-
path := tmpDir + "/_TestMkdirAll_/dir/./dir2"
23-
err := MkdirAll(path, 0777)
24-
if err != nil {
25-
t.Fatalf("MkdirAll %q: %s", path, err)
26-
}
27-
defer RemoveAll(tmpDir + "/_TestMkdirAll_")
27+
path := "_TestMkdirAll_/dir/./dir2"
28+
err := mkdirAll(path, 0777)
29+
if err != nil {
30+
t.Fatalf("MkdirAll %q: %s", path, err)
31+
}
2832

29-
// Already exists, should succeed.
30-
err = MkdirAll(path, 0777)
31-
if err != nil {
32-
t.Fatalf("MkdirAll %q (second time): %s", path, err)
33-
}
33+
// Already exists, should succeed.
34+
err = mkdirAll(path, 0777)
35+
if err != nil {
36+
t.Fatalf("MkdirAll %q (second time): %s", path, err)
37+
}
3438

35-
// Make file.
36-
fpath := path + "/file"
37-
f, err := Create(fpath)
38-
if err != nil {
39-
t.Fatalf("create %q: %s", fpath, err)
40-
}
41-
defer f.Close()
39+
// Make file.
40+
fpath := path + "/file"
41+
f, err := create(fpath)
42+
if err != nil {
43+
t.Fatalf("create %q: %s", fpath, err)
44+
}
45+
defer f.Close()
4246

43-
// Can't make directory named after file.
44-
err = MkdirAll(fpath, 0777)
45-
if err == nil {
46-
t.Fatalf("MkdirAll %q: no error", fpath)
47-
}
48-
perr, ok := err.(*PathError)
49-
if !ok {
50-
t.Fatalf("MkdirAll %q returned %T, not *PathError", fpath, err)
51-
}
52-
if filepath.Clean(perr.Path) != filepath.Clean(fpath) {
53-
t.Fatalf("MkdirAll %q returned wrong error path: %q not %q", fpath, filepath.Clean(perr.Path), filepath.Clean(fpath))
54-
}
47+
// Can't make directory named after file.
48+
err = mkdirAll(fpath, 0777)
49+
if err == nil {
50+
t.Fatalf("MkdirAll %q: no error", fpath)
51+
}
52+
perr, ok := err.(*PathError)
53+
if !ok {
54+
t.Fatalf("MkdirAll %q returned %T, not *PathError", fpath, err)
55+
}
56+
if filepath.Clean(perr.Path) != filepath.Clean(fpath) {
57+
t.Fatalf("MkdirAll %q returned wrong error path: %q not %q", fpath, filepath.Clean(perr.Path), filepath.Clean(fpath))
58+
}
5559

56-
// Can't make subdirectory of file.
57-
ffpath := fpath + "/subdir"
58-
err = MkdirAll(ffpath, 0777)
59-
if err == nil {
60-
t.Fatalf("MkdirAll %q: no error", ffpath)
61-
}
62-
perr, ok = err.(*PathError)
63-
if !ok {
64-
t.Fatalf("MkdirAll %q returned %T, not *PathError", ffpath, err)
65-
}
66-
if filepath.Clean(perr.Path) != filepath.Clean(fpath) {
67-
t.Fatalf("MkdirAll %q returned wrong error path: %q not %q", ffpath, filepath.Clean(perr.Path), filepath.Clean(fpath))
68-
}
60+
// Can't make subdirectory of file.
61+
ffpath := fpath + "/subdir"
62+
err = mkdirAll(ffpath, 0777)
63+
if err == nil {
64+
t.Fatalf("MkdirAll %q: no error", ffpath)
65+
}
66+
perr, ok = err.(*PathError)
67+
if !ok {
68+
t.Fatalf("MkdirAll %q returned %T, not *PathError", ffpath, err)
69+
}
70+
if filepath.Clean(perr.Path) != filepath.Clean(fpath) {
71+
t.Fatalf("MkdirAll %q returned wrong error path: %q not %q", ffpath, filepath.Clean(perr.Path), filepath.Clean(fpath))
72+
}
6973

70-
if runtime.GOOS == "windows" {
71-
path := tmpDir + `\_TestMkdirAll_\dir\.\dir2\`
72-
err := MkdirAll(path, 0777)
73-
if err != nil {
74-
t.Fatalf("MkdirAll %q: %s", path, err)
74+
if runtime.GOOS == "windows" {
75+
path := `_TestMkdirAll_\dir\.\dir2\`
76+
err := mkdirAll(path, 0777)
77+
if err != nil {
78+
t.Fatalf("MkdirAll %q: %s", path, err)
79+
}
7580
}
81+
})
82+
}
83+
84+
func TestMkdirAllAbsPath(t *testing.T) {
85+
t.Parallel()
86+
tmpDir := t.TempDir()
87+
path := filepath.Join(tmpDir, "/a/b/c")
88+
if err := MkdirAll(path, 0o777); err != nil {
89+
t.Fatal(err)
90+
}
91+
st, err := Stat(path)
92+
if err != nil {
93+
t.Fatal(err)
94+
}
95+
if !st.IsDir() {
96+
t.Fatalf("after MkdirAll(%q, 0o777), %q is not a directory", path, path)
7697
}
7798
}
7899

src/os/root.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,14 +145,26 @@ func (r *Root) Chmod(name string, mode FileMode) error {
145145
// See [Mkdir] for more details.
146146
//
147147
// If perm contains bits other than the nine least-significant bits (0o777),
148-
// OpenFile returns an error.
148+
// Mkdir returns an error.
149149
func (r *Root) Mkdir(name string, perm FileMode) error {
150150
if perm&0o777 != perm {
151151
return &PathError{Op: "mkdirat", Path: name, Err: errors.New("unsupported file mode")}
152152
}
153153
return rootMkdir(r, name, perm)
154154
}
155155

156+
// MkdirAll creates a new directory in the root, along with any necessary parents.
157+
// See [MkdirAll] for more details.
158+
//
159+
// If perm contains bits other than the nine least-significant bits (0o777),
160+
// MkdirAll returns an error.
161+
func (r *Root) MkdirAll(name string, perm FileMode) error {
162+
if perm&0o777 != perm {
163+
return &PathError{Op: "mkdirat", Path: name, Err: errors.New("unsupported file mode")}
164+
}
165+
return rootMkdirAll(r, name, perm)
166+
}
167+
156168
// Chown changes the numeric uid and gid of the named file in the root.
157169
// See [Chown] for more details.
158170
func (r *Root) Chown(name string, uid, gid int) error {

src/os/root_noopenat.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package os
88

99
import (
1010
"errors"
11+
"internal/stringslite"
1112
"sync/atomic"
1213
"syscall"
1314
"time"
@@ -147,6 +148,27 @@ func rootMkdir(r *Root, name string, perm FileMode) error {
147148
return nil
148149
}
149150

151+
func rootMkdirAll(r *Root, name string, perm FileMode) error {
152+
// We only check for errPathEscapes here.
153+
// For errors such as ENOTDIR (a non-directory file appeared somewhere along the path),
154+
// we let MkdirAll generate the error.
155+
// MkdirAll will return a PathError referencing the exact location of the error,
156+
// and we want to preserve that property.
157+
if err := checkPathEscapes(r, name); err == errPathEscapes {
158+
return &PathError{Op: "mkdirat", Path: name, Err: err}
159+
}
160+
prefix := r.root.name + string(PathSeparator)
161+
if err := MkdirAll(prefix+name, perm); err != nil {
162+
if pe, ok := err.(*PathError); ok {
163+
pe.Op = "mkdirat"
164+
pe.Path = stringslite.TrimPrefix(pe.Path, prefix)
165+
return pe
166+
}
167+
return &PathError{Op: "mkdirat", Path: name, Err: underlyingError(err)}
168+
}
169+
return nil
170+
}
171+
150172
func rootRemove(r *Root, name string) error {
151173
if err := checkPathEscapesLstat(r, name); err != nil {
152174
return &PathError{Op: "removeat", Path: name, Err: err}

0 commit comments

Comments
 (0)