Skip to content

Commit

Permalink
os: make MkdirAll support volume names
Browse files Browse the repository at this point in the history
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 <quimmuntal@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Bryan Mills <bcmills@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
  • Loading branch information
qmuntal committed Aug 9, 2023
1 parent f617a6c commit cd589c8
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 64 deletions.
1 change: 1 addition & 0 deletions src/internal/syscall/windows/syscall_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ type FILE_ID_BOTH_DIR_INFO struct {
}

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

//sys RtlLookupFunctionEntry(pc uintptr, baseAddress *uintptr, table *byte) (ret uintptr) = kernel32.RtlLookupFunctionEntry
//sys RtlVirtualUnwind(handlerType uint32, baseAddress uintptr, pc uintptr, entry uintptr, ctxt uintptr, data *uintptr, frame *uintptr, ctxptrs *byte) (ret uintptr) = kernel32.RtlVirtualUnwind
Expand Down
89 changes: 49 additions & 40 deletions src/internal/syscall/windows/zsyscall_windows.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 15 additions & 9 deletions src/os/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,25 @@ func MkdirAll(path string, perm FileMode) error {
}

// Slow path: make sure parent exists and then call Mkdir for path.
i := len(path)
for i > 0 && IsPathSeparator(path[i-1]) { // Skip trailing path separator.

// Extract the parent folder from path by first removing any trailing
// path separator and then scanning backward until finding a path
// separator or reaching the beginning of the string.
i := len(path) - 1
for i >= 0 && IsPathSeparator(path[i]) {
i--
}

j := i
for j > 0 && !IsPathSeparator(path[j-1]) { // Scan backward over element.
j--
for i >= 0 && !IsPathSeparator(path[i]) {
i--
}
if i < 0 {
i = 0
}

if j > 1 {
// Create parent.
err = MkdirAll(fixRootDirectory(path[:j-1]), perm)
// If there is a parent directory, and it is not the volume name,
// recurse to ensure parent directory exists.
if parent := path[:i]; len(parent) > len(volumeName(path)) {
err = MkdirAll(parent, perm)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions src/os/path_plan9.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ func IsPathSeparator(c uint8) bool {
return PathSeparator == c
}

func fixRootDirectory(p string) string {
return p
func volumeName(p string) string {
return ""
}
4 changes: 2 additions & 2 deletions src/os/path_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,6 @@ func splitPath(path string) (string, string) {
return dirname, basename
}

func fixRootDirectory(p string) string {
return p
func volumeName(p string) string {
return ""
}
11 changes: 0 additions & 11 deletions src/os/path_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,14 +214,3 @@ func fixLongPath(path string) string {
}
return string(pathbuf[:w])
}

// fixRootDirectory fixes a reference to a drive's root directory to
// have the required trailing slash.
func fixRootDirectory(p string) string {
if len(p) == len(`\\?\c:`) {
if IsPathSeparator(p[0]) && IsPathSeparator(p[1]) && p[2] == '?' && IsPathSeparator(p[3]) && p[5] == ':' {
return p + `\`
}
}
return p
}
49 changes: 49 additions & 0 deletions src/os/path_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
package os_test

import (
"fmt"
"internal/syscall/windows"
"internal/testenv"
"os"
"path/filepath"
"strings"
"syscall"
"testing"
Expand Down Expand Up @@ -106,3 +110,48 @@ func TestOpenRootSlash(t *testing.T) {
dir.Close()
}
}

func testMkdirAllAtRoot(t *testing.T, root string) {
// Create a unique-enough directory name in root.
base := fmt.Sprintf("%s-%d", t.Name(), os.Getpid())
path := filepath.Join(root, base)
if err := os.MkdirAll(path, 0777); err != nil {
t.Fatalf("MkdirAll(%q) failed: %v", path, err)
}
// Clean up
if err := os.RemoveAll(path); err != nil {
t.Fatal(err)
}
}

func TestMkdirAllExtendedLengthAtRoot(t *testing.T) {
if testenv.Builder() == "" {
t.Skipf("skipping non-hermetic test outside of Go builders")
}

const prefix = `\\?\`
vol := filepath.VolumeName(t.TempDir()) + `\`
if len(vol) < 4 || vol[:4] != prefix {
vol = prefix + vol
}
testMkdirAllAtRoot(t, vol)
}

func TestMkdirAllVolumeNameAtRoot(t *testing.T) {
if testenv.Builder() == "" {
t.Skipf("skipping non-hermetic test outside of Go builders")
}

vol, err := syscall.UTF16PtrFromString(filepath.VolumeName(t.TempDir()) + `\`)
if err != nil {
t.Fatal(err)
}
const maxVolNameLen = 50
var buf [maxVolNameLen]uint16
err = windows.GetVolumeNameForVolumeMountPoint(vol, &buf[0], maxVolNameLen)
if err != nil {
t.Fatal(err)
}
volName := syscall.UTF16ToString(buf[:])
testMkdirAllAtRoot(t, volName)
}

0 comments on commit cd589c8

Please sign in to comment.