Skip to content

Commit cd589c8

Browse files
committed
os: make MkdirAll support volume names
MkdirAll fails to create directories under root paths using volume names (e.g. //?/Volume{GUID}/foo). This is because fixRootDirectory only handle extended length paths using drive letters (e.g. //?/C:/foo). This CL fixes that issue by also detecting volume names without path separator. Updates #22230 Fixes #39785 Change-Id: I813fdc0b968ce71a4297f69245b935558e6cd789 Reviewed-on: https://go-review.googlesource.com/c/go/+/517015 Run-TryBot: Quim Muntal <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Bryan Mills <[email protected]> Reviewed-by: Michael Knyszek <[email protected]>
1 parent f617a6c commit cd589c8

7 files changed

Lines changed: 118 additions & 64 deletions

File tree

src/internal/syscall/windows/syscall_windows.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ type FILE_ID_BOTH_DIR_INFO struct {
398398
}
399399

400400
//sys GetVolumeInformationByHandle(file syscall.Handle, volumeNameBuffer *uint16, volumeNameSize uint32, volumeNameSerialNumber *uint32, maximumComponentLength *uint32, fileSystemFlags *uint32, fileSystemNameBuffer *uint16, fileSystemNameSize uint32) (err error) = GetVolumeInformationByHandleW
401+
//sys GetVolumeNameForVolumeMountPoint(volumeMountPoint *uint16, volumeName *uint16, bufferlength uint32) (err error) = GetVolumeNameForVolumeMountPointW
401402

402403
//sys RtlLookupFunctionEntry(pc uintptr, baseAddress *uintptr, table *byte) (ret uintptr) = kernel32.RtlLookupFunctionEntry
403404
//sys RtlVirtualUnwind(handlerType uint32, baseAddress uintptr, pc uintptr, entry uintptr, ctxt uintptr, data *uintptr, frame *uintptr, ctxptrs *byte) (ret uintptr) = kernel32.RtlVirtualUnwind

src/internal/syscall/windows/zsyscall_windows.go

Lines changed: 49 additions & 40 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/os/path.go

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,25 @@ func MkdirAll(path string, perm FileMode) error {
2626
}
2727

2828
// Slow path: make sure parent exists and then call Mkdir for path.
29-
i := len(path)
30-
for i > 0 && IsPathSeparator(path[i-1]) { // Skip trailing path separator.
29+
30+
// Extract the parent folder from path by first removing any trailing
31+
// path separator and then scanning backward until finding a path
32+
// separator or reaching the beginning of the string.
33+
i := len(path) - 1
34+
for i >= 0 && IsPathSeparator(path[i]) {
3135
i--
3236
}
33-
34-
j := i
35-
for j > 0 && !IsPathSeparator(path[j-1]) { // Scan backward over element.
36-
j--
37+
for i >= 0 && !IsPathSeparator(path[i]) {
38+
i--
39+
}
40+
if i < 0 {
41+
i = 0
3742
}
3843

39-
if j > 1 {
40-
// Create parent.
41-
err = MkdirAll(fixRootDirectory(path[:j-1]), perm)
44+
// If there is a parent directory, and it is not the volume name,
45+
// recurse to ensure parent directory exists.
46+
if parent := path[:i]; len(parent) > len(volumeName(path)) {
47+
err = MkdirAll(parent, perm)
4248
if err != nil {
4349
return err
4450
}

src/os/path_plan9.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ func IsPathSeparator(c uint8) bool {
1414
return PathSeparator == c
1515
}
1616

17-
func fixRootDirectory(p string) string {
18-
return p
17+
func volumeName(p string) string {
18+
return ""
1919
}

src/os/path_unix.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,6 @@ func splitPath(path string) (string, string) {
7070
return dirname, basename
7171
}
7272

73-
func fixRootDirectory(p string) string {
74-
return p
73+
func volumeName(p string) string {
74+
return ""
7575
}

src/os/path_windows.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -214,14 +214,3 @@ func fixLongPath(path string) string {
214214
}
215215
return string(pathbuf[:w])
216216
}
217-
218-
// fixRootDirectory fixes a reference to a drive's root directory to
219-
// have the required trailing slash.
220-
func fixRootDirectory(p string) string {
221-
if len(p) == len(`\\?\c:`) {
222-
if IsPathSeparator(p[0]) && IsPathSeparator(p[1]) && p[2] == '?' && IsPathSeparator(p[3]) && p[5] == ':' {
223-
return p + `\`
224-
}
225-
}
226-
return p
227-
}

src/os/path_windows_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
package os_test
66

77
import (
8+
"fmt"
9+
"internal/syscall/windows"
10+
"internal/testenv"
811
"os"
12+
"path/filepath"
913
"strings"
1014
"syscall"
1115
"testing"
@@ -106,3 +110,48 @@ func TestOpenRootSlash(t *testing.T) {
106110
dir.Close()
107111
}
108112
}
113+
114+
func testMkdirAllAtRoot(t *testing.T, root string) {
115+
// Create a unique-enough directory name in root.
116+
base := fmt.Sprintf("%s-%d", t.Name(), os.Getpid())
117+
path := filepath.Join(root, base)
118+
if err := os.MkdirAll(path, 0777); err != nil {
119+
t.Fatalf("MkdirAll(%q) failed: %v", path, err)
120+
}
121+
// Clean up
122+
if err := os.RemoveAll(path); err != nil {
123+
t.Fatal(err)
124+
}
125+
}
126+
127+
func TestMkdirAllExtendedLengthAtRoot(t *testing.T) {
128+
if testenv.Builder() == "" {
129+
t.Skipf("skipping non-hermetic test outside of Go builders")
130+
}
131+
132+
const prefix = `\\?\`
133+
vol := filepath.VolumeName(t.TempDir()) + `\`
134+
if len(vol) < 4 || vol[:4] != prefix {
135+
vol = prefix + vol
136+
}
137+
testMkdirAllAtRoot(t, vol)
138+
}
139+
140+
func TestMkdirAllVolumeNameAtRoot(t *testing.T) {
141+
if testenv.Builder() == "" {
142+
t.Skipf("skipping non-hermetic test outside of Go builders")
143+
}
144+
145+
vol, err := syscall.UTF16PtrFromString(filepath.VolumeName(t.TempDir()) + `\`)
146+
if err != nil {
147+
t.Fatal(err)
148+
}
149+
const maxVolNameLen = 50
150+
var buf [maxVolNameLen]uint16
151+
err = windows.GetVolumeNameForVolumeMountPoint(vol, &buf[0], maxVolNameLen)
152+
if err != nil {
153+
t.Fatal(err)
154+
}
155+
volName := syscall.UTF16ToString(buf[:])
156+
testMkdirAllAtRoot(t, volName)
157+
}

0 commit comments

Comments
 (0)