From 6e0a81ac6149e28f75f4e61bd375c4fc426280df Mon Sep 17 00:00:00 2001 From: qmuntal Date: Wed, 19 Feb 2025 16:05:48 +0100 Subject: [PATCH] path/filepath: use RtlIsDosDeviceName_U to detect Windows devices MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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 Reviewed-by: Michael Knyszek Reviewed-by: Damien Neil --- src/internal/filepathlite/path_windows.go | 19 ++++++++----------- .../syscall/windows/syscall_windows.go | 1 + .../syscall/windows/zsyscall_windows.go | 7 +++++++ src/path/filepath/path_test.go | 15 +++++++++++++++ 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/internal/filepathlite/path_windows.go b/src/internal/filepathlite/path_windows.go index 8f34838a98..011baa96f0 100644 --- a/src/internal/filepathlite/path_windows.go +++ b/src/internal/filepathlite/path_windows.go @@ -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) { diff --git a/src/internal/syscall/windows/syscall_windows.go b/src/internal/syscall/windows/syscall_windows.go index c848f92d1f..e4d42f3dae 100644 --- a/src/internal/syscall/windows/syscall_windows.go +++ b/src/internal/syscall/windows/syscall_windows.go @@ -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 diff --git a/src/internal/syscall/windows/zsyscall_windows.go b/src/internal/syscall/windows/zsyscall_windows.go index 6a6ea7bdc0..f7b89e9ca3 100644 --- a/src/internal/syscall/windows/zsyscall_windows.go +++ b/src/internal/syscall/windows/zsyscall_windows.go @@ -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) diff --git a/src/path/filepath/path_test.go b/src/path/filepath/path_test.go index e9cd82d6c5..7ea02a7c28 100644 --- a/src/path/filepath/path_test.go +++ b/src/path/filepath/path_test.go @@ -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) + } + } +} -- 2.50.0