path/filepath: use RtlIsDosDeviceName_U to detect Windows devices

RtlIsDosDeviceName_U is specifically designed to detect Windows devices.
We were using GetFullPathName to do this, but it's not the right API
for the job, as it is slower and allocates more memory.

goos: windows
goarch: amd64
pkg: path/filepath
cpu: Intel(R) Core(TM) i7-10850H CPU @ 2.70GHz
           │   old.txt    │               new.txt                │
           │    sec/op    │    sec/op     vs base                │
IsLocal-12   5.685µ ± 59%   1.853µ ± 12%  -67.41% (p=0.000 n=10)

           │   old.txt   │              new.txt               │
           │    B/op     │    B/op     vs base                │
IsLocal-12   496.00 ± 0%   48.00 ± 0%  -90.32% (p=0.000 n=10)

           │   old.txt   │              new.txt               │
           │  allocs/op  │ allocs/op   vs base                │
IsLocal-12   10.000 ± 0%   6.000 ± 0%  -40.00% (p=0.000 n=10)

Change-Id: Ib40ad7a90ab93cf7051c8d6becbce4d287f10f4e
Reviewed-on: https://go-review.googlesource.com/c/go/+/650578
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
qmuntal 2025-02-19 16:05:48 +01:00 committed by Quim Muntal
parent 9ddeac30b5
commit 6e0a81ac61
4 changed files with 31 additions and 11 deletions

View File

@ -7,6 +7,7 @@ package filepathlite
import (
"internal/bytealg"
"internal/stringslite"
"internal/syscall/windows"
"syscall"
)
@ -114,13 +115,14 @@ func isReservedName(name string) bool {
return true
}
// The path element is a reserved name with an extension.
// Some Windows versions consider this a reserved name,
// while others do not. Use FullPath to see if the name is
// reserved.
if p, _ := syscall.FullPath(name); len(p) >= 4 && p[:4] == `\\.\` {
return true
// Since Windows 11, reserved names with extensions are no
// longer reserved. For example, "CON.txt" is a valid file
// name. Use RtlIsDosDeviceName_U to see if the name is reserved.
p, err := syscall.UTF16PtrFromString(name)
if err != nil {
return false
}
return false
return windows.RtlIsDosDeviceName_U(p) > 0
}
func isReservedBaseName(name string) bool {
@ -297,11 +299,6 @@ func cutPath(path string) (before, after string, found bool) {
return path, "", false
}
// isUNC reports whether path is a UNC path.
func isUNC(path string) bool {
return len(path) > 1 && IsPathSeparator(path[0]) && IsPathSeparator(path[1])
}
// postClean adjusts the results of Clean to avoid turning a relative path
// into an absolute or rooted one.
func postClean(out *lazybuf) {

View File

@ -535,3 +535,4 @@ const (
//sys NtOpenFile(handle *syscall.Handle, access uint32, oa *OBJECT_ATTRIBUTES, iosb *IO_STATUS_BLOCK, share uint32, options uint32) (ntstatus error) = ntdll.NtOpenFile
//sys rtlNtStatusToDosErrorNoTeb(ntstatus NTStatus) (ret syscall.Errno) = ntdll.RtlNtStatusToDosErrorNoTeb
//sys NtSetInformationFile(handle syscall.Handle, iosb *IO_STATUS_BLOCK, inBuffer uintptr, inBufferLen uint32, class uint32) (ntstatus error) = ntdll.NtSetInformationFile
//sys RtlIsDosDeviceName_U(name *uint16) (ret uint32) = ntdll.RtlIsDosDeviceName_U

View File

@ -96,6 +96,7 @@ var (
procNtOpenFile = modntdll.NewProc("NtOpenFile")
procNtSetInformationFile = modntdll.NewProc("NtSetInformationFile")
procRtlGetVersion = modntdll.NewProc("RtlGetVersion")
procRtlIsDosDeviceName_U = modntdll.NewProc("RtlIsDosDeviceName_U")
procRtlNtStatusToDosErrorNoTeb = modntdll.NewProc("RtlNtStatusToDosErrorNoTeb")
procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo")
procCreateEnvironmentBlock = moduserenv.NewProc("CreateEnvironmentBlock")
@ -500,6 +501,12 @@ func rtlGetVersion(info *_OSVERSIONINFOW) {
return
}
func RtlIsDosDeviceName_U(name *uint16) (ret uint32) {
r0, _, _ := syscall.Syscall(procRtlIsDosDeviceName_U.Addr(), 1, uintptr(unsafe.Pointer(name)), 0, 0)
ret = uint32(r0)
return
}
func rtlNtStatusToDosErrorNoTeb(ntstatus NTStatus) (ret syscall.Errno) {
r0, _, _ := syscall.Syscall(procRtlNtStatusToDosErrorNoTeb.Addr(), 1, uintptr(ntstatus), 0, 0)
ret = syscall.Errno(r0)

View File

@ -1889,3 +1889,18 @@ func TestEvalSymlinksTooManyLinks(t *testing.T) {
t.Fatal("expected error, got nil")
}
}
func BenchmarkIsLocal(b *testing.B) {
tests := islocaltests
if runtime.GOOS == "windows" {
tests = append(tests, winislocaltests...)
}
if runtime.GOOS == "plan9" {
tests = append(tests, plan9islocaltests...)
}
for b.Loop() {
for _, test := range tests {
filepath.IsLocal(test.path)
}
}
}