From 00d74e5952c3d3dc40b9723d89449e2f1fb8b6de Mon Sep 17 00:00:00 2001 From: Rohlem Date: Wed, 9 Dec 2020 10:23:27 +0100 Subject: [PATCH 1/9] move windows runtime version detection into std.zig.system.windows --- lib/std/target.zig | 14 +++++++++++ lib/std/zig/system.zig | 42 +++---------------------------- lib/std/zig/system/windows.zig | 45 ++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 38 deletions(-) create mode 100644 lib/std/zig/system/windows.zig diff --git a/lib/std/target.zig b/lib/std/target.zig index 9a006a95fa51..b2e2e8cbbf4c 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -109,6 +109,20 @@ pub const Target = struct { /// Latest Windows version that the Zig Standard Library is aware of pub const latest = WindowsVersion.win10_20h1; + /// Compared against build numbers reported by the runtime to distinguish win10 versions, + /// where 0x0A000000 + index corresponds to the WindowsVersion u32 value. + pub const known_win10_build_numbers = [_]u32{ + 10240, //win10 + 10586, //win10_th2 + 14393, //win_rs1 + 15063, //win_rs2 + 16299, //win_rs3 + 17134, //win_rs4 + 17763, //win_rs5 + 18362, //win_19h1 + 19041, //win_20h1 + }; + pub const Range = struct { min: WindowsVersion, max: WindowsVersion, diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index af0b00032819..5d8f2f4b1ebc 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -14,6 +14,7 @@ const process = std.process; const Target = std.Target; const CrossTarget = std.zig.CrossTarget; const macos = @import("system/macos.zig"); +pub const windows = @import("system/windows.zig"); const is_windows = Target.current.os.tag == .windows; @@ -223,44 +224,9 @@ pub const NativeTargetInfo = struct { } }, .windows => { - var version_info: std.os.windows.RTL_OSVERSIONINFOW = undefined; - version_info.dwOSVersionInfoSize = @sizeOf(@TypeOf(version_info)); - - switch (std.os.windows.ntdll.RtlGetVersion(&version_info)) { - .SUCCESS => {}, - else => unreachable, - } - - // Starting from the system infos build a NTDDI-like version - // constant whose format is: - // B0 B1 B2 B3 - // `---` `` ``--> Sub-version (Starting from Windows 10 onwards) - // \ `--> Service pack (Always zero in the constants defined) - // `--> OS version (Major & minor) - const os_ver: u16 = - @intCast(u16, version_info.dwMajorVersion & 0xff) << 8 | - @intCast(u16, version_info.dwMinorVersion & 0xff); - const sp_ver: u8 = 0; - const sub_ver: u8 = if (os_ver >= 0x0A00) subver: { - // There's no other way to obtain this info beside - // checking the build number against a known set of - // values - const known_build_numbers = [_]u32{ - 10240, 10586, 14393, 15063, 16299, 17134, 17763, - 18362, 19041, - }; - var last_idx: usize = 0; - for (known_build_numbers) |build, i| { - if (version_info.dwBuildNumber >= build) - last_idx = i; - } - break :subver @truncate(u8, last_idx); - } else 0; - - const version: u32 = @as(u32, os_ver) << 16 | @as(u32, sp_ver) << 8 | sub_ver; - - os.version_range.windows.max = @intToEnum(Target.Os.WindowsVersion, version); - os.version_range.windows.min = @intToEnum(Target.Os.WindowsVersion, version); + const detected_version = windows.detectRuntimeVersion(); + os.version_range.windows.min = detected_version; + os.version_range.windows.max = detected_version; }, .macos => { var scbuf: [32]u8 = undefined; diff --git a/lib/std/zig/system/windows.zig b/lib/std/zig/system/windows.zig new file mode 100644 index 000000000000..d32b28f60719 --- /dev/null +++ b/lib/std/zig/system/windows.zig @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2020 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. +const std = @import("std"); + +pub const WindowsVersion = std.Target.Os.WindowsVersion; + +/// Returns the highest known WindowsVersion deduced from reported runtime information. +/// Discards information about in-between versions we don't differentiate. +pub fn detectRuntimeVersion() WindowsVersion { + var version_info: std.os.windows.RTL_OSVERSIONINFOW = undefined; + version_info.dwOSVersionInfoSize = @sizeOf(@TypeOf(version_info)); + + switch (std.os.windows.ntdll.RtlGetVersion(&version_info)) { + .SUCCESS => {}, + else => unreachable, + } + + // Starting from the system infos build a NTDDI-like version + // constant whose format is: + // B0 B1 B2 B3 + // `---` `` ``--> Sub-version (Starting from Windows 10 onwards) + // \ `--> Service pack (Always zero in the constants defined) + // `--> OS version (Major & minor) + const os_ver: u16 = @intCast(u16, version_info.dwMajorVersion & 0xff) << 8 | + @intCast(u16, version_info.dwMinorVersion & 0xff); + const sp_ver: u8 = 0; + const sub_ver: u8 = if (os_ver >= 0x0A00) subver: { + // There's no other way to obtain this info beside + // checking the build number against a known set of + // values + var last_idx: usize = 0; + for (WindowsVersion.known_win10_build_numbers) |build, i| { + if (version_info.dwBuildNumber >= build) + last_idx = i; + } + break :subver @truncate(u8, last_idx); + } else 0; + + const version: u32 = @as(u32, os_ver) << 16 | @as(u16, sp_ver) << 8 | sub_ver; + + return @intToEnum(WindowsVersion, version); +} From 95045a533d055bca41b1f8381023af4f5e287f49 Mon Sep 17 00:00:00 2001 From: Rohlem Date: Thu, 17 Dec 2020 15:55:05 +0100 Subject: [PATCH 2/9] add std.zig.system.windows version check utility functions --- lib/std/zig/system/windows.zig | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/std/zig/system/windows.zig b/lib/std/zig/system/windows.zig index d32b28f60719..d352703e40ff 100644 --- a/lib/std/zig/system/windows.zig +++ b/lib/std/zig/system/windows.zig @@ -43,3 +43,19 @@ pub fn detectRuntimeVersion() WindowsVersion { return @intToEnum(WindowsVersion, version); } + +/// Returns whether the target os versions are uniformly at least as new as the argument: +/// true/false if this holds for the entire target range, null if it only holds for some. +pub fn targetVersionIsAtLeast(requested_version: WindowsVersion) ?bool { + const requested = @enumToInt(requested_version); + const version_range = std.builtin.os.version_range.windows; + const target_min = @enumToInt(version_range.min); + const target_max = @enumToInt(version_range.max); + return if (target_max < requested) false else if (target_min >= requested) true else null; +} + +/// Returns whether the runtime os version is at least as new as the argument. +pub fn runtimeVersionIsAtLeast(requested_version: WindowsVersion) bool { + return targetVersionIsAtLeast(requested_version) orelse + (@enumToInt(detectRuntimeVersion()) >= @enumToInt(requested_version)); +} From 096047d2bad92d9b086f90e965cba4ee84ad2782 Mon Sep 17 00:00:00 2001 From: Rohlem Date: Wed, 9 Dec 2020 13:04:26 +0100 Subject: [PATCH 3/9] std.os.windows.GetFinalPathNameByHandle: reintroduce kernel32 for compatibility The NtQueryInformationFile with .FileNormalizedNameInformation is only available in Windows 10 1803 (rs4) and later, however there is probably still another route we can go via ntdll. --- lib/std/os/windows.zig | 68 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 7 deletions(-) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index b994720ce995..887deef48999 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -28,6 +28,9 @@ pub const gdi32 = @import("windows/gdi32.zig"); pub usingnamespace @import("windows/bits.zig"); +//version detection +usingnamespace std.zig.system.windows; + pub const self_process_handle = @intToPtr(HANDLE, maxInt(usize)); pub const OpenError = error{ @@ -952,12 +955,19 @@ pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 { return @bitCast(u64, result); } -pub const GetFinalPathNameByHandleError = error{ - BadPathName, - FileNotFound, - NameTooLong, - Unexpected, -}; +pub const GetFinalPathNameByHandleError = error { + BadPathName, + FileNotFound, + NameTooLong, + Unexpected, + } + || if((comptime builtin.os.tag != .windows) or (targetVersionIsAtLeast(WindowsVersion.win10_rs4) == true)) + error {} + else + error { + AccessDenied, + SystemResources, + }; /// Specifies how to format volume path in the result of `GetFinalPathNameByHandle`. /// Defaults to DOS volume names. @@ -980,8 +990,52 @@ pub fn GetFinalPathNameByHandle( fmt: GetFinalPathNameByHandleFormat, out_buffer: []u16, ) GetFinalPathNameByHandleError![]u16 { - // Get normalized path; doesn't include volume name though. + var path_buffer: [@sizeOf(FILE_NAME_INFORMATION) + PATH_MAX_WIDE * 2]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; + + if ((comptime (targetVersionIsAtLeast(WindowsVersion.win10_rs4) != true)) //need explicit comptime, because error returns affect return type + and !runtimeVersionIsAtLeast(WindowsVersion.win10_rs4)) { + // TODO: directly replace/emulate QueryInformationFile of .FileNormalizedNameInformation + // with ntdll instead of calling into kernel32 + // (probably using some less-powerful query and looping over path segments) + const flags: DWORD = FILE_NAME_NORMALIZED | switch(fmt.volume_name) { + .Dos => @as(DWORD, VOLUME_NAME_DOS), + .Nt => @as(DWORD, VOLUME_NAME_NT), + }; + const wide_path_buffer = std.mem.bytesAsSlice(u16, path_buffer[0..]); + const rc = kernel32.GetFinalPathNameByHandleW(hFile, wide_path_buffer.ptr, @intCast(u32, wide_path_buffer.len), flags); + if (rc == 0) { + switch (kernel32.GetLastError()) { + .FILE_NOT_FOUND => return error.FileNotFound, + .PATH_NOT_FOUND => return error.FileNotFound, + .NOT_ENOUGH_MEMORY => return error.SystemResources, + .FILENAME_EXCED_RANGE => return error.NameTooLong, + .ACCESS_DENIED => return error.AccessDenied, //can happen in SMB sub-queries for parent path segments + .INVALID_PARAMETER => unreachable, + else => |err| return unexpectedError(err), + } + } + + //in case of failure, rc == length of string INCLUDING null terminator, + if (rc > wide_path_buffer.len) return error.NameTooLong; + //in case of success, rc == length of string EXCLUDING null terminator + const result_slice = switch(fmt.volume_name) { + .Dos => blk: { + const expected_prefix = [_]u16{'\\', '\\', '?', '\\'}; + if (!std.mem.eql(u16, expected_prefix[0..], wide_path_buffer[0..expected_prefix.len])) { + return error.BadPathName; + } + break :blk wide_path_buffer[expected_prefix.len..rc:0]; + }, + //no prefix here + .Nt => wide_path_buffer[0..rc:0], + }; + if(result_slice.len > out_buffer.len) return error.NameTooLong; + std.mem.copy(u16, out_buffer[0..], result_slice); + return out_buffer[0..result_slice.len]; + } + + // Get normalized path; doesn't include volume name though. try QueryInformationFile(hFile, .FileNormalizedNameInformation, path_buffer[0..]); // Get NT volume name. From 61fcef46e281cb181c2a68440266f89033243d06 Mon Sep 17 00:00:00 2001 From: Rohlem Date: Thu, 17 Dec 2020 16:26:30 +0100 Subject: [PATCH 4/9] std.os.windows.GetFinalPathNameByHandle: add test --- lib/std/os/windows.zig | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 887deef48999..a8f77b09d2fa 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -1145,6 +1145,30 @@ pub fn GetFinalPathNameByHandle( } } +test "GetFinalPathNameByHandle" { + if (comptime builtin.os.tag != .windows) + return; + + //any file will do + const file = try std.fs.openSelfExe(.{}); + defer file.close(); + const handle = file.handle; + //make this large enough for the test runner exe path + var buffer = std.mem.zeroes([1 << 10]u16); + + //check with sufficient size + const nt_length = (try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0..])).len; + const dos_length = (try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0..])).len; + + //check with insufficient size + std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0 .. nt_length - 1])); + std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0 .. dos_length - 1])); + + //check with exactly-sufficient size + _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0..nt_length]); + _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0..dos_length]); +} + pub const QueryInformationFileError = error{Unexpected}; pub fn QueryInformationFile( From 6feccbdbd6680236490ca448e2463fc75390e8e4 Mon Sep 17 00:00:00 2001 From: Rohlem Date: Thu, 10 Dec 2020 12:52:00 +0100 Subject: [PATCH 5/9] introduce std.os.windows.QueryObjectName --- lib/std/os/windows.zig | 45 ++++++++++++++++++++++++++++++++++++ lib/std/os/windows/bits.zig | 16 +++++++++++++ lib/std/os/windows/ntdll.zig | 8 +++++++ 3 files changed, 69 insertions(+) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index a8f77b09d2fa..76f9e13c9a48 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -955,6 +955,51 @@ pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 { return @bitCast(u64, result); } +pub fn QueryObjectName( + handle: HANDLE, + out_buffer: []u16, +) ![]u16 { + var full_buffer: [@sizeOf(OBJECT_NAME_INFORMATION) + PATH_MAX_WIDE * 2]u8 align(@alignOf(OBJECT_NAME_INFORMATION)) = undefined; + var info = @ptrCast(*OBJECT_NAME_INFORMATION, &full_buffer); + //buffer size is specified in bytes + const full_buffer_length = @intCast(ULONG, @sizeOf(OBJECT_NAME_INFORMATION) + std.math.min(PATH_MAX_WIDE, (out_buffer.len + 1) * 2)); + //last argument would return the length required for full_buffer, not exposed here + const rc = ntdll.NtQueryObject(handle, .ObjectNameInformation, full_buffer[0..], full_buffer_length, null); + return switch (rc) { + .SUCCESS => if (@ptrCast(?[*]WCHAR, info.Name.Buffer)) |buffer| blk: { + //resulting string length is specified in bytes + const path_length_unterminated = @divExact(info.Name.Length, 2); + if (out_buffer.len < path_length_unterminated) { + return error.NameTooLong; + } + std.mem.copy(WCHAR, out_buffer[0..path_length_unterminated], buffer[0..path_length_unterminated :0]); + break :blk out_buffer[0..path_length_unterminated]; + } else error.Unexpected, + .ACCESS_DENIED => error.AccessDenied, + .INVALID_HANDLE => error.InvalidHandle, + .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => error.NameTooLong, + //name_buffer.len >= @sizeOf(OBJECT_NAME_INFORMATION) holds statically + .INFO_LENGTH_MISMATCH => unreachable, + else => |e| unexpectedStatus(e), + }; +} +test "QueryObjectName" { + if (comptime builtin.os.tag != .windows) + return; + + //any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths. + const file = try std.fs.openSelfExe(.{}); + defer file.close(); + //make this large enough for the test runner exe path + var out_buffer align(16) = std.mem.zeroes([1 << 10]u16); + + var result_path = try QueryObjectName(file.handle, out_buffer[0..]); + //insufficient size + std.testing.expectError(error.NameTooLong, QueryObjectName(file.handle, out_buffer[0 .. result_path.len - 1])); + //exactly-sufficient size + _ = try QueryObjectName(file.handle, out_buffer[0..result_path.len]); +} + pub const GetFinalPathNameByHandleError = error { BadPathName, FileNotFound, diff --git a/lib/std/os/windows/bits.zig b/lib/std/os/windows/bits.zig index f5d520c5807d..6bfc6625bda9 100644 --- a/lib/std/os/windows/bits.zig +++ b/lib/std/os/windows/bits.zig @@ -65,6 +65,7 @@ pub const SIZE_T = usize; pub const TCHAR = if (UNICODE) WCHAR else u8; pub const UINT = c_uint; pub const ULONG_PTR = usize; +pub const PULONG = *ULONG; pub const LONG_PTR = isize; pub const DWORD_PTR = ULONG_PTR; pub const UNICODE = false; @@ -1606,3 +1607,18 @@ pub const IOCTL_MOUNTMGR_QUERY_POINTS: ULONG = 0x6d0008; pub const SD_RECEIVE = 0; pub const SD_SEND = 1; pub const SD_BOTH = 2; + +pub const OBJECT_INFORMATION_CLASS = extern enum { + ObjectBasicInformation = 0, + ObjectNameInformation = 1, + ObjectTypeInformation = 2, + ObjectTypesInformation = 3, + ObjectHandleFlagInformation = 4, + ObjectSessionInformation = 5, + MaxObjectInfoClass, +}; + +pub const OBJECT_NAME_INFORMATION = extern struct { + Name: UNICODE_STRING, +}; +pub const POBJECT_NAME_INFORMATION = *OBJECT_NAME_INFORMATION; diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index fc485f87f299..82e1b8a59f1f 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -113,3 +113,11 @@ pub extern "NtDll" fn NtWaitForKeyedEvent( ) callconv(WINAPI) NTSTATUS; pub extern "NtDll" fn RtlSetCurrentDirectory_U(PathName: *UNICODE_STRING) callconv(WINAPI) NTSTATUS; + +pub extern "NtDll" fn NtQueryObject( + Handle: HANDLE, + ObjectInformationClass: OBJECT_INFORMATION_CLASS, + ObjectInformation: PVOID, + ObjectInformationLength: ULONG, + ReturnLength: ?PULONG, +) callconv(WINAPI) NTSTATUS; From 74963e89ee90fc52a4673911c36f4473e77a5121 Mon Sep 17 00:00:00 2001 From: Rohlem Date: Thu, 17 Dec 2020 16:37:40 +0100 Subject: [PATCH 6/9] std.os.windows.GetFinalPathNameByHandle: replace kernel32 by ntdll call Removes the call to kernel32.GetFinalPathNameByHandleW in favor of NtQueryObject, which means we can reuse the other codepath's logic for DOS naming. --- lib/std/os/windows.zig | 119 ++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 68 deletions(-) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 76f9e13c9a48..57bf676f8271 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -1000,19 +1000,13 @@ test "QueryObjectName" { _ = try QueryObjectName(file.handle, out_buffer[0..result_path.len]); } -pub const GetFinalPathNameByHandleError = error { - BadPathName, - FileNotFound, - NameTooLong, - Unexpected, - } - || if((comptime builtin.os.tag != .windows) or (targetVersionIsAtLeast(WindowsVersion.win10_rs4) == true)) - error {} - else - error { - AccessDenied, - SystemResources, - }; +pub const GetFinalPathNameByHandleError = error{ + AccessDenied, + BadPathName, + FileNotFound, + NameTooLong, + Unexpected, +}; /// Specifies how to format volume path in the result of `GetFinalPathNameByHandle`. /// Defaults to DOS volume names. @@ -1035,75 +1029,64 @@ pub fn GetFinalPathNameByHandle( fmt: GetFinalPathNameByHandleFormat, out_buffer: []u16, ) GetFinalPathNameByHandleError![]u16 { + var path_buffer: [std.math.max(@sizeOf(FILE_NAME_INFORMATION), @sizeOf(OBJECT_NAME_INFORMATION)) + PATH_MAX_WIDE * 2]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; + var volume_buffer: [@sizeOf(FILE_NAME_INFORMATION) + MAX_PATH]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; // MAX_PATH bytes should be enough since it's Windows-defined name - var path_buffer: [@sizeOf(FILE_NAME_INFORMATION) + PATH_MAX_WIDE * 2]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; - + var file_name_u16: []const u16 = undefined; + var volume_name_u16: []const u16 = undefined; if ((comptime (targetVersionIsAtLeast(WindowsVersion.win10_rs4) != true)) //need explicit comptime, because error returns affect return type - and !runtimeVersionIsAtLeast(WindowsVersion.win10_rs4)) { - // TODO: directly replace/emulate QueryInformationFile of .FileNormalizedNameInformation - // with ntdll instead of calling into kernel32 - // (probably using some less-powerful query and looping over path segments) - const flags: DWORD = FILE_NAME_NORMALIZED | switch(fmt.volume_name) { - .Dos => @as(DWORD, VOLUME_NAME_DOS), - .Nt => @as(DWORD, VOLUME_NAME_NT), + and !runtimeVersionIsAtLeast(WindowsVersion.win10_rs4)) + { + const final_path = QueryObjectName(hFile, std.mem.bytesAsSlice(u16, path_buffer[0..])) catch |err| return switch (err) { + error.InvalidHandle => error.FileNotFound, //close enough? + else => |e| e, }; - const wide_path_buffer = std.mem.bytesAsSlice(u16, path_buffer[0..]); - const rc = kernel32.GetFinalPathNameByHandleW(hFile, wide_path_buffer.ptr, @intCast(u32, wide_path_buffer.len), flags); - if (rc == 0) { - switch (kernel32.GetLastError()) { - .FILE_NOT_FOUND => return error.FileNotFound, - .PATH_NOT_FOUND => return error.FileNotFound, - .NOT_ENOUGH_MEMORY => return error.SystemResources, - .FILENAME_EXCED_RANGE => return error.NameTooLong, - .ACCESS_DENIED => return error.AccessDenied, //can happen in SMB sub-queries for parent path segments - .INVALID_PARAMETER => unreachable, - else => |err| return unexpectedError(err), + + if (fmt.volume_name == .Nt) { + if (out_buffer.len < final_path.len) { + return error.NameTooLong; } + std.mem.copy(u16, out_buffer[0..], final_path[0..]); + return final_path; //we can directly return the slice we received } - //in case of failure, rc == length of string INCLUDING null terminator, - if (rc > wide_path_buffer.len) return error.NameTooLong; - //in case of success, rc == length of string EXCLUDING null terminator - const result_slice = switch(fmt.volume_name) { - .Dos => blk: { - const expected_prefix = [_]u16{'\\', '\\', '?', '\\'}; - if (!std.mem.eql(u16, expected_prefix[0..], wide_path_buffer[0..expected_prefix.len])) { - return error.BadPathName; - } - break :blk wide_path_buffer[expected_prefix.len..rc:0]; - }, - //no prefix here - .Nt => wide_path_buffer[0..rc:0], - }; - if(result_slice.len > out_buffer.len) return error.NameTooLong; - std.mem.copy(u16, out_buffer[0..], result_slice); - return out_buffer[0..result_slice.len]; - } - - // Get normalized path; doesn't include volume name though. - try QueryInformationFile(hFile, .FileNormalizedNameInformation, path_buffer[0..]); - - // Get NT volume name. - var volume_buffer: [@sizeOf(FILE_NAME_INFORMATION) + MAX_PATH]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; // MAX_PATH bytes should be enough since it's Windows-defined name - try QueryInformationFile(hFile, .FileVolumeNameInformation, volume_buffer[0..]); + //otherwise we need to parse the string for volume path for the .Dos logic below to work + const expected_prefix = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\"); + if (!std.mem.eql(u16, expected_prefix, final_path[0..expected_prefix.len])) { + //TODO find out if this can occur, and if we need to handle it differently + //(i.e. how to determine the end of a volume name) + return error.BadPathName; + } + const index = std.mem.indexOfPos(u16, final_path, expected_prefix.len, &[_]u16{'\\'}) orelse unreachable; + volume_name_u16 = final_path[0..index]; + file_name_u16 = final_path[index..]; - const file_name = @ptrCast(*const FILE_NAME_INFORMATION, &path_buffer[0]); - const file_name_u16 = @ptrCast([*]const u16, &file_name.FileName[0])[0 .. file_name.FileNameLength / 2]; + //fallthrough for fmt.volume_name != .Nt + } else { + // Get normalized path; doesn't include volume name though. + try QueryInformationFile(hFile, .FileNormalizedNameInformation, path_buffer[0..]); + const file_name = @ptrCast(*const FILE_NAME_INFORMATION, &path_buffer[0]); + file_name_u16 = @ptrCast([*]const u16, &file_name.FileName[0])[0..@divExact(file_name.FileNameLength, 2)]; - const volume_name = @ptrCast(*const FILE_NAME_INFORMATION, &volume_buffer[0]); + // Get NT volume name. + try QueryInformationFile(hFile, .FileVolumeNameInformation, volume_buffer[0..]); + const volume_name_info = @ptrCast(*const FILE_NAME_INFORMATION, &volume_buffer[0]); + volume_name_u16 = @ptrCast([*]const u16, &volume_name_info.FileName[0])[0..@divExact(volume_name_info.FileNameLength, 2)]; - switch (fmt.volume_name) { - .Nt => { + if (fmt.volume_name == .Nt) { // Nothing to do, we simply copy the bytes to the user-provided buffer. - const volume_name_u16 = @ptrCast([*]const u16, &volume_name.FileName[0])[0 .. volume_name.FileNameLength / 2]; - if (out_buffer.len < volume_name_u16.len + file_name_u16.len) return error.NameTooLong; std.mem.copy(u16, out_buffer[0..], volume_name_u16); std.mem.copy(u16, out_buffer[volume_name_u16.len..], file_name_u16); return out_buffer[0 .. volume_name_u16.len + file_name_u16.len]; - }, + } + //fallthrough for fmt.volume_name != .Nt + } + + switch (fmt.volume_name) { + .Nt => unreachable, //handled above .Dos => { // Get DOS volume name. DOS volume names are actually symbolic link objects to the // actual NT volume. For example: @@ -1137,8 +1120,8 @@ pub fn GetFinalPathNameByHandle( var input_struct = @ptrCast(*MOUNTMGR_MOUNT_POINT, &input_buf[0]); input_struct.DeviceNameOffset = @sizeOf(MOUNTMGR_MOUNT_POINT); - input_struct.DeviceNameLength = @intCast(USHORT, volume_name.FileNameLength); - @memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..], @ptrCast([*]const u8, &volume_name.FileName[0]), volume_name.FileNameLength); + input_struct.DeviceNameLength = @intCast(USHORT, volume_name_u16.len * 2); + @memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..], @ptrCast([*]const u8, volume_name_u16.ptr), volume_name_u16.len * 2); DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, input_buf[0..], output_buf[0..]) catch |err| switch (err) { error.AccessDenied => unreachable, From b75c0d408ea404770e560c2507f4b39cc0e7a206 Mon Sep 17 00:00:00 2001 From: Rohlem Date: Wed, 23 Dec 2020 02:39:02 +0100 Subject: [PATCH 7/9] std.os.windows.GetFinalPathNameByHandle: address non-structural review comments --- lib/std/os/windows.zig | 117 +++++++++++++++++---------------- lib/std/os/windows/bits.zig | 1 - lib/std/os/windows/ntdll.zig | 2 +- lib/std/target.zig | 31 +++++---- lib/std/zig/system/windows.zig | 16 ----- 5 files changed, 80 insertions(+), 87 deletions(-) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 57bf676f8271..28d666e94e7e 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -29,7 +29,8 @@ pub const gdi32 = @import("windows/gdi32.zig"); pub usingnamespace @import("windows/bits.zig"); //version detection -usingnamespace std.zig.system.windows; +const version = std.zig.system.windows; +const WindowsVersion = version.WindowsVersion; pub const self_process_handle = @intToPtr(HANDLE, maxInt(usize)); @@ -962,42 +963,44 @@ pub fn QueryObjectName( var full_buffer: [@sizeOf(OBJECT_NAME_INFORMATION) + PATH_MAX_WIDE * 2]u8 align(@alignOf(OBJECT_NAME_INFORMATION)) = undefined; var info = @ptrCast(*OBJECT_NAME_INFORMATION, &full_buffer); //buffer size is specified in bytes - const full_buffer_length = @intCast(ULONG, @sizeOf(OBJECT_NAME_INFORMATION) + std.math.min(PATH_MAX_WIDE, (out_buffer.len + 1) * 2)); //last argument would return the length required for full_buffer, not exposed here - const rc = ntdll.NtQueryObject(handle, .ObjectNameInformation, full_buffer[0..], full_buffer_length, null); - return switch (rc) { - .SUCCESS => if (@ptrCast(?[*]WCHAR, info.Name.Buffer)) |buffer| blk: { + const rc = ntdll.NtQueryObject(handle, .ObjectNameInformation, &full_buffer, full_buffer.len, null); + switch (rc) { + .SUCCESS => { + // info.Name.Buffer from ObQueryNameString is documented to be null (and MaximumLength == 0) + // if the object was "unnamed", not sure if this can happen for file handles + if (info.Name.MaximumLength == 0) return error.Unexpected; //resulting string length is specified in bytes const path_length_unterminated = @divExact(info.Name.Length, 2); if (out_buffer.len < path_length_unterminated) { return error.NameTooLong; } - std.mem.copy(WCHAR, out_buffer[0..path_length_unterminated], buffer[0..path_length_unterminated :0]); - break :blk out_buffer[0..path_length_unterminated]; - } else error.Unexpected, - .ACCESS_DENIED => error.AccessDenied, - .INVALID_HANDLE => error.InvalidHandle, - .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => error.NameTooLong, + mem.copy(WCHAR, out_buffer[0..path_length_unterminated], info.Name.Buffer[0..path_length_unterminated]); + return out_buffer[0..path_length_unterminated]; + }, + .ACCESS_DENIED => return error.AccessDenied, + .INVALID_HANDLE => return error.InvalidHandle, + .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => return error.NameTooLong, //name_buffer.len >= @sizeOf(OBJECT_NAME_INFORMATION) holds statically .INFO_LENGTH_MISMATCH => unreachable, - else => |e| unexpectedStatus(e), - }; + else => |e| return unexpectedStatus(e), + } } test "QueryObjectName" { if (comptime builtin.os.tag != .windows) return; //any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths. - const file = try std.fs.openSelfExe(.{}); - defer file.close(); - //make this large enough for the test runner exe path - var out_buffer align(16) = std.mem.zeroes([1 << 10]u16); + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const handle = tmp.dir.fd; + var out_buffer: [PATH_MAX_WIDE]u16 = undefined; - var result_path = try QueryObjectName(file.handle, out_buffer[0..]); + var result_path = try QueryObjectName(handle, &out_buffer); //insufficient size - std.testing.expectError(error.NameTooLong, QueryObjectName(file.handle, out_buffer[0 .. result_path.len - 1])); + std.testing.expectError(error.NameTooLong, QueryObjectName(handle, out_buffer[0 .. result_path.len - 1])); //exactly-sufficient size - _ = try QueryObjectName(file.handle, out_buffer[0..result_path.len]); + _ = try QueryObjectName(handle, out_buffer[0..result_path.len]); } pub const GetFinalPathNameByHandleError = error{ @@ -1029,16 +1032,16 @@ pub fn GetFinalPathNameByHandle( fmt: GetFinalPathNameByHandleFormat, out_buffer: []u16, ) GetFinalPathNameByHandleError![]u16 { - var path_buffer: [std.math.max(@sizeOf(FILE_NAME_INFORMATION), @sizeOf(OBJECT_NAME_INFORMATION)) + PATH_MAX_WIDE * 2]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; + var path_buffer: [math.max(@sizeOf(FILE_NAME_INFORMATION), @sizeOf(OBJECT_NAME_INFORMATION)) + PATH_MAX_WIDE * 2]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; var volume_buffer: [@sizeOf(FILE_NAME_INFORMATION) + MAX_PATH]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; // MAX_PATH bytes should be enough since it's Windows-defined name var file_name_u16: []const u16 = undefined; var volume_name_u16: []const u16 = undefined; - if ((comptime (targetVersionIsAtLeast(WindowsVersion.win10_rs4) != true)) //need explicit comptime, because error returns affect return type - and !runtimeVersionIsAtLeast(WindowsVersion.win10_rs4)) - { - const final_path = QueryObjectName(hFile, std.mem.bytesAsSlice(u16, path_buffer[0..])) catch |err| return switch (err) { - error.InvalidHandle => error.FileNotFound, //close enough? + if ((comptime (std.builtin.os.version_range.windows.isAtLeast(WindowsVersion.win10_rs4) != true)) and !version.detectRuntimeVersion().isAtLeast(WindowsVersion.win10_rs4)) { + const final_path = QueryObjectName(hFile, mem.bytesAsSlice(u16, &path_buffer)) catch |err| return switch (err) { + // we assume InvalidHandle is close enough to FileNotFound in semantics + // to not further complicate the error set + error.InvalidHandle => error.FileNotFound, else => |e| e, }; @@ -1046,39 +1049,40 @@ pub fn GetFinalPathNameByHandle( if (out_buffer.len < final_path.len) { return error.NameTooLong; } - std.mem.copy(u16, out_buffer[0..], final_path[0..]); - return final_path; //we can directly return the slice we received + mem.copy(u16, out_buffer, final_path); + return out_buffer[0..final_path.len]; } //otherwise we need to parse the string for volume path for the .Dos logic below to work const expected_prefix = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\"); - if (!std.mem.eql(u16, expected_prefix, final_path[0..expected_prefix.len])) { - //TODO find out if this can occur, and if we need to handle it differently - //(i.e. how to determine the end of a volume name) - return error.BadPathName; - } - const index = std.mem.indexOfPos(u16, final_path, expected_prefix.len, &[_]u16{'\\'}) orelse unreachable; + + // TODO find out if a path can start with something besides `\Device\`, + // and if we need to handle it differently + // (i.e. how to determine the start and end of the volume name in that case) + if (!mem.eql(u16, expected_prefix, final_path[0..expected_prefix.len])) return error.Unexpected; + + const index = mem.indexOfPos(u16, final_path, expected_prefix.len, &[_]u16{'\\'}) orelse unreachable; volume_name_u16 = final_path[0..index]; file_name_u16 = final_path[index..]; //fallthrough for fmt.volume_name != .Nt } else { // Get normalized path; doesn't include volume name though. - try QueryInformationFile(hFile, .FileNormalizedNameInformation, path_buffer[0..]); - const file_name = @ptrCast(*const FILE_NAME_INFORMATION, &path_buffer[0]); - file_name_u16 = @ptrCast([*]const u16, &file_name.FileName[0])[0..@divExact(file_name.FileNameLength, 2)]; + try QueryInformationFile(hFile, .FileNormalizedNameInformation, &path_buffer); + const file_name = @ptrCast(*const FILE_NAME_INFORMATION, &path_buffer); + file_name_u16 = @ptrCast([*]const u16, &file_name.FileName)[0..@divExact(file_name.FileNameLength, 2)]; // Get NT volume name. - try QueryInformationFile(hFile, .FileVolumeNameInformation, volume_buffer[0..]); - const volume_name_info = @ptrCast(*const FILE_NAME_INFORMATION, &volume_buffer[0]); - volume_name_u16 = @ptrCast([*]const u16, &volume_name_info.FileName[0])[0..@divExact(volume_name_info.FileNameLength, 2)]; + try QueryInformationFile(hFile, .FileVolumeNameInformation, &volume_buffer); + const volume_name_info = @ptrCast(*const FILE_NAME_INFORMATION, &volume_buffer); + volume_name_u16 = @ptrCast([*]const u16, &volume_name_info.FileName)[0..@divExact(volume_name_info.FileNameLength, 2)]; if (fmt.volume_name == .Nt) { // Nothing to do, we simply copy the bytes to the user-provided buffer. if (out_buffer.len < volume_name_u16.len + file_name_u16.len) return error.NameTooLong; - std.mem.copy(u16, out_buffer[0..], volume_name_u16); - std.mem.copy(u16, out_buffer[volume_name_u16.len..], file_name_u16); + mem.copy(u16, out_buffer, volume_name_u16); + mem.copy(u16, out_buffer[volume_name_u16.len..], file_name_u16); return out_buffer[0 .. volume_name_u16.len + file_name_u16.len]; } @@ -1123,7 +1127,7 @@ pub fn GetFinalPathNameByHandle( input_struct.DeviceNameLength = @intCast(USHORT, volume_name_u16.len * 2); @memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..], @ptrCast([*]const u8, volume_name_u16.ptr), volume_name_u16.len * 2); - DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, input_buf[0..], output_buf[0..]) catch |err| switch (err) { + DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, &input_buf, &output_buf) catch |err| switch (err) { error.AccessDenied => unreachable, else => |e| return e, }; @@ -1143,22 +1147,20 @@ pub fn GetFinalPathNameByHandle( // Look for `\DosDevices\` prefix. We don't really care if there are more than one symlinks // with traditional DOS drive letters, so pick the first one available. - const prefix_u8 = "\\DosDevices\\"; - var prefix_buf_u16: [prefix_u8.len]u16 = undefined; - const prefix_len_u16 = std.unicode.utf8ToUtf16Le(prefix_buf_u16[0..], prefix_u8[0..]) catch unreachable; - const prefix = prefix_buf_u16[0..prefix_len_u16]; + var prefix_buf = std.unicode.utf8ToUtf16LeStringLiteral("\\DosDevices\\"); + const prefix = prefix_buf[0..prefix_buf.len]; - if (std.mem.startsWith(u16, symlink, prefix)) { + if (mem.startsWith(u16, symlink, prefix)) { const drive_letter = symlink[prefix.len..]; if (out_buffer.len < drive_letter.len + file_name_u16.len) return error.NameTooLong; - std.mem.copy(u16, out_buffer[0..], drive_letter); - std.mem.copy(u16, out_buffer[drive_letter.len..], file_name_u16); + mem.copy(u16, out_buffer, drive_letter); + mem.copy(u16, out_buffer[drive_letter.len..], file_name_u16); const total_len = drive_letter.len + file_name_u16.len; // Validate that DOS does not contain any spurious nul bytes. - if (std.mem.indexOfScalar(u16, out_buffer[0..total_len], 0)) |_| { + if (mem.indexOfScalar(u16, out_buffer[0..total_len], 0)) |_| { return error.BadPathName; } @@ -1178,15 +1180,14 @@ test "GetFinalPathNameByHandle" { return; //any file will do - const file = try std.fs.openSelfExe(.{}); - defer file.close(); - const handle = file.handle; - //make this large enough for the test runner exe path - var buffer = std.mem.zeroes([1 << 10]u16); + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const handle = tmp.dir.fd; + var buffer: [PATH_MAX_WIDE]u16 = undefined; //check with sufficient size - const nt_length = (try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0..])).len; - const dos_length = (try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0..])).len; + const nt_length = (try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, &buffer)).len; + const dos_length = (try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, &buffer)).len; //check with insufficient size std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0 .. nt_length - 1])); diff --git a/lib/std/os/windows/bits.zig b/lib/std/os/windows/bits.zig index 6bfc6625bda9..6e2a4e3819a2 100644 --- a/lib/std/os/windows/bits.zig +++ b/lib/std/os/windows/bits.zig @@ -65,7 +65,6 @@ pub const SIZE_T = usize; pub const TCHAR = if (UNICODE) WCHAR else u8; pub const UINT = c_uint; pub const ULONG_PTR = usize; -pub const PULONG = *ULONG; pub const LONG_PTR = isize; pub const DWORD_PTR = ULONG_PTR; pub const UNICODE = false; diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index 82e1b8a59f1f..818db4759516 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -119,5 +119,5 @@ pub extern "NtDll" fn NtQueryObject( ObjectInformationClass: OBJECT_INFORMATION_CLASS, ObjectInformation: PVOID, ObjectInformationLength: ULONG, - ReturnLength: ?PULONG, + ReturnLength: ?*ULONG, ) callconv(WINAPI) NTSTATUS; diff --git a/lib/std/target.zig b/lib/std/target.zig index b2e2e8cbbf4c..1ddadefbccae 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -95,7 +95,7 @@ pub const Target = struct { win7 = 0x06010000, win8 = 0x06020000, win8_1 = 0x06030000, - win10 = 0x0A000000, + win10 = 0x0A000000, //aka win10_th1 win10_th2 = 0x0A000001, win10_rs1 = 0x0A000002, win10_rs2 = 0x0A000003, @@ -103,26 +103,35 @@ pub const Target = struct { win10_rs4 = 0x0A000005, win10_rs5 = 0x0A000006, win10_19h1 = 0x0A000007, - win10_20h1 = 0x0A000008, + win10_vb = 0x0A000008, //aka win10_19h2 + win10_mn = 0x0A000009, //aka win10_20h1 + win10_fe = 0x0A00000A, //aka win10_20h2 _, /// Latest Windows version that the Zig Standard Library is aware of - pub const latest = WindowsVersion.win10_20h1; + pub const latest = WindowsVersion.win10_fe; /// Compared against build numbers reported by the runtime to distinguish win10 versions, /// where 0x0A000000 + index corresponds to the WindowsVersion u32 value. pub const known_win10_build_numbers = [_]u32{ - 10240, //win10 + 10240, //win10 aka win10_th1 10586, //win10_th2 - 14393, //win_rs1 - 15063, //win_rs2 - 16299, //win_rs3 - 17134, //win_rs4 - 17763, //win_rs5 - 18362, //win_19h1 - 19041, //win_20h1 + 14393, //win10_rs1 + 15063, //win10_rs2 + 16299, //win10_rs3 + 17134, //win10_rs4 + 17763, //win10_rs5 + 18362, //win10_19h1 + 18363, //win10_vb aka win10_19h2 + 19041, //win10_mn aka win10_20h1 + 19042, //win10_fe aka win10_20h2 }; + /// Returns whether the first version `self` is newer (greater) than or equal to the second version `ver`. + pub fn isAtLeast(self: WindowsVersion, ver: WindowsVersion) bool { + return @enumToInt(self) >= @enumToInt(ver); + } + pub const Range = struct { min: WindowsVersion, max: WindowsVersion, diff --git a/lib/std/zig/system/windows.zig b/lib/std/zig/system/windows.zig index d352703e40ff..d32b28f60719 100644 --- a/lib/std/zig/system/windows.zig +++ b/lib/std/zig/system/windows.zig @@ -43,19 +43,3 @@ pub fn detectRuntimeVersion() WindowsVersion { return @intToEnum(WindowsVersion, version); } - -/// Returns whether the target os versions are uniformly at least as new as the argument: -/// true/false if this holds for the entire target range, null if it only holds for some. -pub fn targetVersionIsAtLeast(requested_version: WindowsVersion) ?bool { - const requested = @enumToInt(requested_version); - const version_range = std.builtin.os.version_range.windows; - const target_min = @enumToInt(version_range.min); - const target_max = @enumToInt(version_range.max); - return if (target_max < requested) false else if (target_min >= requested) true else null; -} - -/// Returns whether the runtime os version is at least as new as the argument. -pub fn runtimeVersionIsAtLeast(requested_version: WindowsVersion) bool { - return targetVersionIsAtLeast(requested_version) orelse - (@enumToInt(detectRuntimeVersion()) >= @enumToInt(requested_version)); -} From 796f312ecd19b9c877009381f67bbe031daa2659 Mon Sep 17 00:00:00 2001 From: Rohlem Date: Wed, 23 Dec 2020 21:50:08 +0100 Subject: [PATCH 8/9] std.os.windows.GetFinalPathNameByHandle: remove QueryInformationFile code path --- lib/std/os/windows.zig | 74 ++++++++++++------------------------------ 1 file changed, 20 insertions(+), 54 deletions(-) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 28d666e94e7e..93a3c404f592 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -28,10 +28,6 @@ pub const gdi32 = @import("windows/gdi32.zig"); pub usingnamespace @import("windows/bits.zig"); -//version detection -const version = std.zig.system.windows; -const WindowsVersion = version.WindowsVersion; - pub const self_process_handle = @intToPtr(HANDLE, maxInt(usize)); pub const OpenError = error{ @@ -1033,65 +1029,35 @@ pub fn GetFinalPathNameByHandle( out_buffer: []u16, ) GetFinalPathNameByHandleError![]u16 { var path_buffer: [math.max(@sizeOf(FILE_NAME_INFORMATION), @sizeOf(OBJECT_NAME_INFORMATION)) + PATH_MAX_WIDE * 2]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; - var volume_buffer: [@sizeOf(FILE_NAME_INFORMATION) + MAX_PATH]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; // MAX_PATH bytes should be enough since it's Windows-defined name - - var file_name_u16: []const u16 = undefined; - var volume_name_u16: []const u16 = undefined; - if ((comptime (std.builtin.os.version_range.windows.isAtLeast(WindowsVersion.win10_rs4) != true)) and !version.detectRuntimeVersion().isAtLeast(WindowsVersion.win10_rs4)) { - const final_path = QueryObjectName(hFile, mem.bytesAsSlice(u16, &path_buffer)) catch |err| return switch (err) { - // we assume InvalidHandle is close enough to FileNotFound in semantics - // to not further complicate the error set - error.InvalidHandle => error.FileNotFound, - else => |e| e, - }; + const final_path = QueryObjectName(hFile, mem.bytesAsSlice(u16, &path_buffer)) catch |err| switch (err) { + // we assume InvalidHandle is close enough to FileNotFound in semantics + // to not further complicate the error set + error.InvalidHandle => return error.FileNotFound, + else => |e| return e, + }; - if (fmt.volume_name == .Nt) { + switch (fmt.volume_name) { + .Nt => { + // the returned path is already in .Nt format if (out_buffer.len < final_path.len) { return error.NameTooLong; } mem.copy(u16, out_buffer, final_path); return out_buffer[0..final_path.len]; - } - - //otherwise we need to parse the string for volume path for the .Dos logic below to work - const expected_prefix = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\"); - - // TODO find out if a path can start with something besides `\Device\`, - // and if we need to handle it differently - // (i.e. how to determine the start and end of the volume name in that case) - if (!mem.eql(u16, expected_prefix, final_path[0..expected_prefix.len])) return error.Unexpected; - - const index = mem.indexOfPos(u16, final_path, expected_prefix.len, &[_]u16{'\\'}) orelse unreachable; - volume_name_u16 = final_path[0..index]; - file_name_u16 = final_path[index..]; - - //fallthrough for fmt.volume_name != .Nt - } else { - // Get normalized path; doesn't include volume name though. - try QueryInformationFile(hFile, .FileNormalizedNameInformation, &path_buffer); - const file_name = @ptrCast(*const FILE_NAME_INFORMATION, &path_buffer); - file_name_u16 = @ptrCast([*]const u16, &file_name.FileName)[0..@divExact(file_name.FileNameLength, 2)]; - - // Get NT volume name. - try QueryInformationFile(hFile, .FileVolumeNameInformation, &volume_buffer); - const volume_name_info = @ptrCast(*const FILE_NAME_INFORMATION, &volume_buffer); - volume_name_u16 = @ptrCast([*]const u16, &volume_name_info.FileName)[0..@divExact(volume_name_info.FileNameLength, 2)]; + }, + .Dos => { + // parse the string to separate volume path from file path + const expected_prefix = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\"); - if (fmt.volume_name == .Nt) { - // Nothing to do, we simply copy the bytes to the user-provided buffer. - if (out_buffer.len < volume_name_u16.len + file_name_u16.len) return error.NameTooLong; + // TODO find out if a path can start with something besides `\Device\`, + // and if we need to handle it differently + // (i.e. how to determine the start and end of the volume name in that case) + if (!mem.eql(u16, expected_prefix, final_path[0..expected_prefix.len])) return error.Unexpected; - mem.copy(u16, out_buffer, volume_name_u16); - mem.copy(u16, out_buffer[volume_name_u16.len..], file_name_u16); + const file_path_begin_index = mem.indexOfPos(u16, final_path, expected_prefix.len, &[_]u16{'\\'}) orelse unreachable; + const volume_name_u16 = final_path[0..file_path_begin_index]; + const file_name_u16 = final_path[file_path_begin_index..]; - return out_buffer[0 .. volume_name_u16.len + file_name_u16.len]; - } - //fallthrough for fmt.volume_name != .Nt - } - - switch (fmt.volume_name) { - .Nt => unreachable, //handled above - .Dos => { // Get DOS volume name. DOS volume names are actually symbolic link objects to the // actual NT volume. For example: // (NT) \Device\HarddiskVolume4 => (DOS) \DosDevices\C: == (DOS) C: From befaeb305812185b88b5be9d21efecbea90d138a Mon Sep 17 00:00:00 2001 From: Rohlem Date: Wed, 23 Dec 2020 22:10:28 +0100 Subject: [PATCH 9/9] std.os.windows.GetFinalPathNameByHandle: remove intermediate buffers ... and mem.copy operations. Requires slightly larger input buffers than result length. Add helper functions std.mem.alignInBytes and std.mem.alignInSlice. --- lib/std/mem.zig | 43 +++++++++++++++++++++++++++++++++++ lib/std/os/windows.zig | 51 ++++++++++++++++++++---------------------- 2 files changed, 67 insertions(+), 27 deletions(-) diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 22e340810e29..aa1be530bf4e 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -2402,3 +2402,46 @@ test "freeing empty string with null-terminated sentinel" { const empty_string = try dupeZ(testing.allocator, u8, ""); testing.allocator.free(empty_string); } + +/// Returns a slice with the given new alignment, +/// all other pointer attributes copied from `AttributeSource`. +fn AlignedSlice(comptime AttributeSource: type, comptime new_alignment: u29) type { + const info = @typeInfo(AttributeSource).Pointer; + return @Type(.{ + .Pointer = .{ + .size = .Slice, + .is_const = info.is_const, + .is_volatile = info.is_volatile, + .is_allowzero = info.is_allowzero, + .alignment = new_alignment, + .child = info.child, + .sentinel = null, + }, + }); +} + +/// Returns the largest slice in the given bytes that conforms to the new alignment, +/// or `null` if the given bytes contain no conforming address. +pub fn alignInBytes(bytes: []u8, comptime new_alignment: usize) ?[]align(new_alignment) u8 { + const begin_address = @ptrToInt(bytes.ptr); + const end_address = begin_address + bytes.len; + + const begin_address_aligned = mem.alignForward(begin_address, new_alignment); + const new_length = std.math.sub(usize, end_address, begin_address_aligned) catch |e| switch (e) { + error.Overflow => return null, + }; + const alignment_offset = begin_address_aligned - begin_address; + return @alignCast(new_alignment, bytes[alignment_offset .. alignment_offset + new_length]); +} + +/// Returns the largest sub-slice within the given slice that conforms to the new alignment, +/// or `null` if the given slice contains no conforming address. +pub fn alignInSlice(slice: anytype, comptime new_alignment: usize) ?AlignedSlice(@TypeOf(slice), new_alignment) { + const bytes = sliceAsBytes(slice); + const aligned_bytes = alignInBytes(bytes, new_alignment) orelse return null; + + const Element = @TypeOf(slice[0]); + const slice_length_bytes = aligned_bytes.len - (aligned_bytes.len % @sizeOf(Element)); + const aligned_slice = bytesAsSlice(Element, aligned_bytes[0..slice_length_bytes]); + return @alignCast(new_alignment, aligned_slice); +} diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 93a3c404f592..83f0dcb52f05 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -956,29 +956,29 @@ pub fn QueryObjectName( handle: HANDLE, out_buffer: []u16, ) ![]u16 { - var full_buffer: [@sizeOf(OBJECT_NAME_INFORMATION) + PATH_MAX_WIDE * 2]u8 align(@alignOf(OBJECT_NAME_INFORMATION)) = undefined; - var info = @ptrCast(*OBJECT_NAME_INFORMATION, &full_buffer); + const out_buffer_aligned = mem.alignInSlice(out_buffer, @alignOf(OBJECT_NAME_INFORMATION)) orelse return error.NameTooLong; + + const info = @ptrCast(*OBJECT_NAME_INFORMATION, out_buffer_aligned); //buffer size is specified in bytes + const out_buffer_len = std.math.cast(ULONG, out_buffer_aligned.len * 2) catch |e| switch (e) { + error.Overflow => std.math.maxInt(ULONG), + }; //last argument would return the length required for full_buffer, not exposed here - const rc = ntdll.NtQueryObject(handle, .ObjectNameInformation, &full_buffer, full_buffer.len, null); + const rc = ntdll.NtQueryObject(handle, .ObjectNameInformation, info, out_buffer_len, null); switch (rc) { .SUCCESS => { // info.Name.Buffer from ObQueryNameString is documented to be null (and MaximumLength == 0) // if the object was "unnamed", not sure if this can happen for file handles if (info.Name.MaximumLength == 0) return error.Unexpected; - //resulting string length is specified in bytes + // resulting string length is specified in bytes const path_length_unterminated = @divExact(info.Name.Length, 2); - if (out_buffer.len < path_length_unterminated) { - return error.NameTooLong; - } - mem.copy(WCHAR, out_buffer[0..path_length_unterminated], info.Name.Buffer[0..path_length_unterminated]); - return out_buffer[0..path_length_unterminated]; + return info.Name.Buffer[0..path_length_unterminated]; }, .ACCESS_DENIED => return error.AccessDenied, .INVALID_HANDLE => return error.InvalidHandle, - .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => return error.NameTooLong, - //name_buffer.len >= @sizeOf(OBJECT_NAME_INFORMATION) holds statically - .INFO_LENGTH_MISMATCH => unreachable, + // triggered when the buffer is too small for the OBJECT_NAME_INFORMATION object (.INFO_LENGTH_MISMATCH), + // or if the buffer is too small for the file path returned (.BUFFER_OVERFLOW, .BUFFER_TOO_SMALL) + .INFO_LENGTH_MISMATCH, .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => return error.NameTooLong, else => |e| return unexpectedStatus(e), } } @@ -993,10 +993,11 @@ test "QueryObjectName" { var out_buffer: [PATH_MAX_WIDE]u16 = undefined; var result_path = try QueryObjectName(handle, &out_buffer); + const required_len_in_u16 = result_path.len + @divExact(@ptrToInt(result_path.ptr) - @ptrToInt(&out_buffer), 2) + 1; //insufficient size - std.testing.expectError(error.NameTooLong, QueryObjectName(handle, out_buffer[0 .. result_path.len - 1])); + std.testing.expectError(error.NameTooLong, QueryObjectName(handle, out_buffer[0 .. required_len_in_u16 - 1])); //exactly-sufficient size - _ = try QueryObjectName(handle, out_buffer[0..result_path.len]); + _ = try QueryObjectName(handle, out_buffer[0..required_len_in_u16]); } pub const GetFinalPathNameByHandleError = error{ @@ -1028,8 +1029,7 @@ pub fn GetFinalPathNameByHandle( fmt: GetFinalPathNameByHandleFormat, out_buffer: []u16, ) GetFinalPathNameByHandleError![]u16 { - var path_buffer: [math.max(@sizeOf(FILE_NAME_INFORMATION), @sizeOf(OBJECT_NAME_INFORMATION)) + PATH_MAX_WIDE * 2]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; - const final_path = QueryObjectName(hFile, mem.bytesAsSlice(u16, &path_buffer)) catch |err| switch (err) { + const final_path = QueryObjectName(hFile, out_buffer) catch |err| switch (err) { // we assume InvalidHandle is close enough to FileNotFound in semantics // to not further complicate the error set error.InvalidHandle => return error.FileNotFound, @@ -1039,11 +1039,7 @@ pub fn GetFinalPathNameByHandle( switch (fmt.volume_name) { .Nt => { // the returned path is already in .Nt format - if (out_buffer.len < final_path.len) { - return error.NameTooLong; - } - mem.copy(u16, out_buffer, final_path); - return out_buffer[0..final_path.len]; + return final_path; }, .Dos => { // parse the string to separate volume path from file path @@ -1152,16 +1148,17 @@ test "GetFinalPathNameByHandle" { var buffer: [PATH_MAX_WIDE]u16 = undefined; //check with sufficient size - const nt_length = (try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, &buffer)).len; - const dos_length = (try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, &buffer)).len; + const nt_path = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, &buffer); + _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, &buffer); + const required_len_in_u16 = nt_path.len + @divExact(@ptrToInt(nt_path.ptr) - @ptrToInt(&buffer), 2) + 1; //check with insufficient size - std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0 .. nt_length - 1])); - std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0 .. dos_length - 1])); + std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0 .. required_len_in_u16 - 1])); + std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0 .. required_len_in_u16 - 1])); //check with exactly-sufficient size - _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0..nt_length]); - _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0..dos_length]); + _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0..required_len_in_u16]); + _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0..required_len_in_u16]); } pub const QueryInformationFileError = error{Unexpected};