authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-01-11 17:48:33-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-01-11 17:48:33-07:00
log56c03881ebd8617e6506bfad01bf9cfdd4d3df7e
tree25da58e774494f28c44463921ba2f2387d8e3ae3
parentfc10c9c4ce40570d0abeda764f1bd76b3e79af61
parentc96272f6186a5e843c0586513e87ffd771c5b00c

Merge branch 'rohlem-fix-GetFinalPathNameByHandle-before-win10_rs4'

Merges #7379

7 files changed, 242 insertions(+), 71 deletions(-)

lib/std/mem.zig+43
......@@ -2436,3 +2436,46 @@ test "freeing empty string with null-terminated sentinel" {
24362436 const empty_string = try dupeZ(testing.allocator, u8, "");
24372437 testing.allocator.free(empty_string);
24382438}
2439
2440/// Returns a slice with the given new alignment,
2441/// all other pointer attributes copied from `AttributeSource`.
2442fn AlignedSlice(comptime AttributeSource: type, comptime new_alignment: u29) type {
2443 const info = @typeInfo(AttributeSource).Pointer;
2444 return @Type(.{
2445 .Pointer = .{
2446 .size = .Slice,
2447 .is_const = info.is_const,
2448 .is_volatile = info.is_volatile,
2449 .is_allowzero = info.is_allowzero,
2450 .alignment = new_alignment,
2451 .child = info.child,
2452 .sentinel = null,
2453 },
2454 });
2455}
2456
2457/// Returns the largest slice in the given bytes that conforms to the new alignment,
2458/// or `null` if the given bytes contain no conforming address.
2459pub fn alignInBytes(bytes: []u8, comptime new_alignment: usize) ?[]align(new_alignment) u8 {
2460 const begin_address = @ptrToInt(bytes.ptr);
2461 const end_address = begin_address + bytes.len;
2462
2463 const begin_address_aligned = mem.alignForward(begin_address, new_alignment);
2464 const new_length = std.math.sub(usize, end_address, begin_address_aligned) catch |e| switch (e) {
2465 error.Overflow => return null,
2466 };
2467 const alignment_offset = begin_address_aligned - begin_address;
2468 return @alignCast(new_alignment, bytes[alignment_offset .. alignment_offset + new_length]);
2469}
2470
2471/// Returns the largest sub-slice within the given slice that conforms to the new alignment,
2472/// or `null` if the given slice contains no conforming address.
2473pub fn alignInSlice(slice: anytype, comptime new_alignment: usize) ?AlignedSlice(@TypeOf(slice), new_alignment) {
2474 const bytes = sliceAsBytes(slice);
2475 const aligned_bytes = alignInBytes(bytes, new_alignment) orelse return null;
2476
2477 const Element = @TypeOf(slice[0]);
2478 const slice_length_bytes = aligned_bytes.len - (aligned_bytes.len % @sizeOf(Element));
2479 const aligned_slice = bytesAsSlice(Element, aligned_bytes[0..slice_length_bytes]);
2480 return @alignCast(new_alignment, aligned_slice);
2481}
lib/std/os/windows.zig+101-31
......@@ -953,7 +953,56 @@ pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 {
953953 return @bitCast(u64, result);
954954}
955955
956pub fn QueryObjectName(
957 handle: HANDLE,
958 out_buffer: []u16,
959) ![]u16 {
960 const out_buffer_aligned = mem.alignInSlice(out_buffer, @alignOf(OBJECT_NAME_INFORMATION)) orelse return error.NameTooLong;
961
962 const info = @ptrCast(*OBJECT_NAME_INFORMATION, out_buffer_aligned);
963 //buffer size is specified in bytes
964 const out_buffer_len = std.math.cast(ULONG, out_buffer_aligned.len * 2) catch |e| switch (e) {
965 error.Overflow => std.math.maxInt(ULONG),
966 };
967 //last argument would return the length required for full_buffer, not exposed here
968 const rc = ntdll.NtQueryObject(handle, .ObjectNameInformation, info, out_buffer_len, null);
969 switch (rc) {
970 .SUCCESS => {
971 // info.Name.Buffer from ObQueryNameString is documented to be null (and MaximumLength == 0)
972 // if the object was "unnamed", not sure if this can happen for file handles
973 if (info.Name.MaximumLength == 0) return error.Unexpected;
974 // resulting string length is specified in bytes
975 const path_length_unterminated = @divExact(info.Name.Length, 2);
976 return info.Name.Buffer[0..path_length_unterminated];
977 },
978 .ACCESS_DENIED => return error.AccessDenied,
979 .INVALID_HANDLE => return error.InvalidHandle,
980 // triggered when the buffer is too small for the OBJECT_NAME_INFORMATION object (.INFO_LENGTH_MISMATCH),
981 // or if the buffer is too small for the file path returned (.BUFFER_OVERFLOW, .BUFFER_TOO_SMALL)
982 .INFO_LENGTH_MISMATCH, .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => return error.NameTooLong,
983 else => |e| return unexpectedStatus(e),
984 }
985}
986test "QueryObjectName" {
987 if (comptime builtin.os.tag != .windows)
988 return;
989
990 //any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths.
991 var tmp = std.testing.tmpDir(.{});
992 defer tmp.cleanup();
993 const handle = tmp.dir.fd;
994 var out_buffer: [PATH_MAX_WIDE]u16 = undefined;
995
996 var result_path = try QueryObjectName(handle, &out_buffer);
997 const required_len_in_u16 = result_path.len + @divExact(@ptrToInt(result_path.ptr) - @ptrToInt(&out_buffer), 2) + 1;
998 //insufficient size
999 std.testing.expectError(error.NameTooLong, QueryObjectName(handle, out_buffer[0 .. required_len_in_u16 - 1]));
1000 //exactly-sufficient size
1001 _ = try QueryObjectName(handle, out_buffer[0..required_len_in_u16]);
1002}
1003
9561004pub const GetFinalPathNameByHandleError = error{
1005 AccessDenied,
9571006 BadPathName,
9581007 FileNotFound,
9591008 NameTooLong,
......@@ -981,32 +1030,31 @@ pub fn GetFinalPathNameByHandle(
9811030 fmt: GetFinalPathNameByHandleFormat,
9821031 out_buffer: []u16,
9831032) GetFinalPathNameByHandleError![]u16 {
984 // Get normalized path; doesn't include volume name though.
985 var path_buffer: [@sizeOf(FILE_NAME_INFORMATION) + PATH_MAX_WIDE * 2]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined;
986 try QueryInformationFile(hFile, .FileNormalizedNameInformation, path_buffer[0..]);
987
988 // Get NT volume name.
989 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
990 try QueryInformationFile(hFile, .FileVolumeNameInformation, volume_buffer[0..]);
991
992 const file_name = @ptrCast(*const FILE_NAME_INFORMATION, &path_buffer[0]);
993 const file_name_u16 = @ptrCast([*]const u16, &file_name.FileName[0])[0 .. file_name.FileNameLength / 2];
994
995 const volume_name = @ptrCast(*const FILE_NAME_INFORMATION, &volume_buffer[0]);
1033 const final_path = QueryObjectName(hFile, out_buffer) catch |err| switch (err) {
1034 // we assume InvalidHandle is close enough to FileNotFound in semantics
1035 // to not further complicate the error set
1036 error.InvalidHandle => return error.FileNotFound,
1037 else => |e| return e,
1038 };
9961039
9971040 switch (fmt.volume_name) {
9981041 .Nt => {
999 // Nothing to do, we simply copy the bytes to the user-provided buffer.
1000 const volume_name_u16 = @ptrCast([*]const u16, &volume_name.FileName[0])[0 .. volume_name.FileNameLength / 2];
1042 // the returned path is already in .Nt format
1043 return final_path;
1044 },
1045 .Dos => {
1046 // parse the string to separate volume path from file path
1047 const expected_prefix = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\");
10011048
1002 if (out_buffer.len < volume_name_u16.len + file_name_u16.len) return error.NameTooLong;
1049 // TODO find out if a path can start with something besides `\Device\<volume name>`,
1050 // and if we need to handle it differently
1051 // (i.e. how to determine the start and end of the volume name in that case)
1052 if (!mem.eql(u16, expected_prefix, final_path[0..expected_prefix.len])) return error.Unexpected;
10031053
1004 std.mem.copy(u16, out_buffer[0..], volume_name_u16);
1005 std.mem.copy(u16, out_buffer[volume_name_u16.len..], file_name_u16);
1054 const file_path_begin_index = mem.indexOfPos(u16, final_path, expected_prefix.len, &[_]u16{'\\'}) orelse unreachable;
1055 const volume_name_u16 = final_path[0..file_path_begin_index];
1056 const file_name_u16 = final_path[file_path_begin_index..];
10061057
1007 return out_buffer[0 .. volume_name_u16.len + file_name_u16.len];
1008 },
1009 .Dos => {
10101058 // Get DOS volume name. DOS volume names are actually symbolic link objects to the
10111059 // actual NT volume. For example:
10121060 // (NT) \Device\HarddiskVolume4 => (DOS) \DosDevices\C: == (DOS) C:
......@@ -1039,10 +1087,10 @@ pub fn GetFinalPathNameByHandle(
10391087
10401088 var input_struct = @ptrCast(*MOUNTMGR_MOUNT_POINT, &input_buf[0]);
10411089 input_struct.DeviceNameOffset = @sizeOf(MOUNTMGR_MOUNT_POINT);
1042 input_struct.DeviceNameLength = @intCast(USHORT, volume_name.FileNameLength);
1043 @memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..], @ptrCast([*]const u8, &volume_name.FileName[0]), volume_name.FileNameLength);
1090 input_struct.DeviceNameLength = @intCast(USHORT, volume_name_u16.len * 2);
1091 @memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..], @ptrCast([*]const u8, volume_name_u16.ptr), volume_name_u16.len * 2);
10441092
1045 DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, input_buf[0..], output_buf[0..]) catch |err| switch (err) {
1093 DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, &input_buf, &output_buf) catch |err| switch (err) {
10461094 error.AccessDenied => unreachable,
10471095 else => |e| return e,
10481096 };
......@@ -1062,22 +1110,20 @@ pub fn GetFinalPathNameByHandle(
10621110
10631111 // Look for `\DosDevices\` prefix. We don't really care if there are more than one symlinks
10641112 // with traditional DOS drive letters, so pick the first one available.
1065 const prefix_u8 = "\\DosDevices\\";
1066 var prefix_buf_u16: [prefix_u8.len]u16 = undefined;
1067 const prefix_len_u16 = std.unicode.utf8ToUtf16Le(prefix_buf_u16[0..], prefix_u8[0..]) catch unreachable;
1068 const prefix = prefix_buf_u16[0..prefix_len_u16];
1113 var prefix_buf = std.unicode.utf8ToUtf16LeStringLiteral("\\DosDevices\\");
1114 const prefix = prefix_buf[0..prefix_buf.len];
10691115
1070 if (std.mem.startsWith(u16, symlink, prefix)) {
1116 if (mem.startsWith(u16, symlink, prefix)) {
10711117 const drive_letter = symlink[prefix.len..];
10721118
10731119 if (out_buffer.len < drive_letter.len + file_name_u16.len) return error.NameTooLong;
10741120
1075 std.mem.copy(u16, out_buffer[0..], drive_letter);
1076 std.mem.copy(u16, out_buffer[drive_letter.len..], file_name_u16);
1121 mem.copy(u16, out_buffer, drive_letter);
1122 mem.copy(u16, out_buffer[drive_letter.len..], file_name_u16);
10771123 const total_len = drive_letter.len + file_name_u16.len;
10781124
10791125 // Validate that DOS does not contain any spurious nul bytes.
1080 if (std.mem.indexOfScalar(u16, out_buffer[0..total_len], 0)) |_| {
1126 if (mem.indexOfScalar(u16, out_buffer[0..total_len], 0)) |_| {
10811127 return error.BadPathName;
10821128 }
10831129
......@@ -1092,6 +1138,30 @@ pub fn GetFinalPathNameByHandle(
10921138 }
10931139}
10941140
1141test "GetFinalPathNameByHandle" {
1142 if (comptime builtin.os.tag != .windows)
1143 return;
1144
1145 //any file will do
1146 var tmp = std.testing.tmpDir(.{});
1147 defer tmp.cleanup();
1148 const handle = tmp.dir.fd;
1149 var buffer: [PATH_MAX_WIDE]u16 = undefined;
1150
1151 //check with sufficient size
1152 const nt_path = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, &buffer);
1153 _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, &buffer);
1154
1155 const required_len_in_u16 = nt_path.len + @divExact(@ptrToInt(nt_path.ptr) - @ptrToInt(&buffer), 2) + 1;
1156 //check with insufficient size
1157 std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0 .. required_len_in_u16 - 1]));
1158 std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0 .. required_len_in_u16 - 1]));
1159
1160 //check with exactly-sufficient size
1161 _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0..required_len_in_u16]);
1162 _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0..required_len_in_u16]);
1163}
1164
10951165pub const QueryInformationFileError = error{Unexpected};
10961166
10971167pub fn QueryInformationFile(
lib/std/os/windows/bits.zig+15
......@@ -1620,3 +1620,18 @@ pub const IOCTL_MOUNTMGR_QUERY_POINTS: ULONG = 0x6d0008;
16201620pub const SD_RECEIVE = 0;
16211621pub const SD_SEND = 1;
16221622pub const SD_BOTH = 2;
1623
1624pub const OBJECT_INFORMATION_CLASS = extern enum {
1625 ObjectBasicInformation = 0,
1626 ObjectNameInformation = 1,
1627 ObjectTypeInformation = 2,
1628 ObjectTypesInformation = 3,
1629 ObjectHandleFlagInformation = 4,
1630 ObjectSessionInformation = 5,
1631 MaxObjectInfoClass,
1632};
1633
1634pub const OBJECT_NAME_INFORMATION = extern struct {
1635 Name: UNICODE_STRING,
1636};
1637pub const POBJECT_NAME_INFORMATION = *OBJECT_NAME_INFORMATION;
lib/std/os/windows/ntdll.zig+8
......@@ -113,3 +113,11 @@ pub extern "NtDll" fn NtWaitForKeyedEvent(
113113) callconv(WINAPI) NTSTATUS;
114114
115115pub extern "NtDll" fn RtlSetCurrentDirectory_U(PathName: *UNICODE_STRING) callconv(WINAPI) NTSTATUS;
116
117pub extern "NtDll" fn NtQueryObject(
118 Handle: HANDLE,
119 ObjectInformationClass: OBJECT_INFORMATION_CLASS,
120 ObjectInformation: PVOID,
121 ObjectInformationLength: ULONG,
122 ReturnLength: ?*ULONG,
123) callconv(WINAPI) NTSTATUS;
lib/std/target.zig+26-3
......@@ -95,7 +95,7 @@ pub const Target = struct {
9595 win7 = 0x06010000,
9696 win8 = 0x06020000,
9797 win8_1 = 0x06030000,
98 win10 = 0x0A000000,
98 win10 = 0x0A000000, //aka win10_th1
9999 win10_th2 = 0x0A000001,
100100 win10_rs1 = 0x0A000002,
101101 win10_rs2 = 0x0A000003,
......@@ -103,11 +103,34 @@ pub const Target = struct {
103103 win10_rs4 = 0x0A000005,
104104 win10_rs5 = 0x0A000006,
105105 win10_19h1 = 0x0A000007,
106 win10_20h1 = 0x0A000008,
106 win10_vb = 0x0A000008, //aka win10_19h2
107 win10_mn = 0x0A000009, //aka win10_20h1
108 win10_fe = 0x0A00000A, //aka win10_20h2
107109 _,
108110
109111 /// Latest Windows version that the Zig Standard Library is aware of
110 pub const latest = WindowsVersion.win10_20h1;
112 pub const latest = WindowsVersion.win10_fe;
113
114 /// Compared against build numbers reported by the runtime to distinguish win10 versions,
115 /// where 0x0A000000 + index corresponds to the WindowsVersion u32 value.
116 pub const known_win10_build_numbers = [_]u32{
117 10240, //win10 aka win10_th1
118 10586, //win10_th2
119 14393, //win10_rs1
120 15063, //win10_rs2
121 16299, //win10_rs3
122 17134, //win10_rs4
123 17763, //win10_rs5
124 18362, //win10_19h1
125 18363, //win10_vb aka win10_19h2
126 19041, //win10_mn aka win10_20h1
127 19042, //win10_fe aka win10_20h2
128 };
129
130 /// Returns whether the first version `self` is newer (greater) than or equal to the second version `ver`.
131 pub fn isAtLeast(self: WindowsVersion, ver: WindowsVersion) bool {
132 return @enumToInt(self) >= @enumToInt(ver);
133 }
111134
112135 pub const Range = struct {
113136 min: WindowsVersion,
lib/std/zig/system.zig+4-37
......@@ -14,6 +14,7 @@ const process = std.process;
1414const Target = std.Target;
1515const CrossTarget = std.zig.CrossTarget;
1616const macos = @import("system/macos.zig");
17pub const windows = @import("system/windows.zig");
1718
1819pub const getSDKPath = macos.getSDKPath;
1920
......@@ -249,43 +250,9 @@ pub const NativeTargetInfo = struct {
249250 }
250251 },
251252 .windows => {
252 var version_info: std.os.windows.RTL_OSVERSIONINFOW = undefined;
253 version_info.dwOSVersionInfoSize = @sizeOf(@TypeOf(version_info));
254
255 switch (std.os.windows.ntdll.RtlGetVersion(&version_info)) {
256 .SUCCESS => {},
257 else => unreachable,
258 }
259
260 // Starting from the system infos build a NTDDI-like version
261 // constant whose format is:
262 // B0 B1 B2 B3
263 // `---` `` ``--> Sub-version (Starting from Windows 10 onwards)
264 // \ `--> Service pack (Always zero in the constants defined)
265 // `--> OS version (Major & minor)
266 const os_ver: u16 = @intCast(u16, version_info.dwMajorVersion & 0xff) << 8 |
267 @intCast(u16, version_info.dwMinorVersion & 0xff);
268 const sp_ver: u8 = 0;
269 const sub_ver: u8 = if (os_ver >= 0x0A00) subver: {
270 // There's no other way to obtain this info beside
271 // checking the build number against a known set of
272 // values
273 const known_build_numbers = [_]u32{
274 10240, 10586, 14393, 15063, 16299, 17134, 17763,
275 18362, 19041,
276 };
277 var last_idx: usize = 0;
278 for (known_build_numbers) |build, i| {
279 if (version_info.dwBuildNumber >= build)
280 last_idx = i;
281 }
282 break :subver @truncate(u8, last_idx);
283 } else 0;
284
285 const version: u32 = @as(u32, os_ver) << 16 | @as(u32, sp_ver) << 8 | sub_ver;
286
287 os.version_range.windows.max = @intToEnum(Target.Os.WindowsVersion, version);
288 os.version_range.windows.min = @intToEnum(Target.Os.WindowsVersion, version);
253 const detected_version = windows.detectRuntimeVersion();
254 os.version_range.windows.min = detected_version;
255 os.version_range.windows.max = detected_version;
289256 },
290257 .macos => {
291258 var scbuf: [32]u8 = undefined;
lib/std/zig/system/windows.zig created+45
......@@ -0,0 +1,45 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2020 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6const std = @import("std");
7
8pub const WindowsVersion = std.Target.Os.WindowsVersion;
9
10/// Returns the highest known WindowsVersion deduced from reported runtime information.
11/// Discards information about in-between versions we don't differentiate.
12pub fn detectRuntimeVersion() WindowsVersion {
13 var version_info: std.os.windows.RTL_OSVERSIONINFOW = undefined;
14 version_info.dwOSVersionInfoSize = @sizeOf(@TypeOf(version_info));
15
16 switch (std.os.windows.ntdll.RtlGetVersion(&version_info)) {
17 .SUCCESS => {},
18 else => unreachable,
19 }
20
21 // Starting from the system infos build a NTDDI-like version
22 // constant whose format is:
23 // B0 B1 B2 B3
24 // `---` `` ``--> Sub-version (Starting from Windows 10 onwards)
25 // \ `--> Service pack (Always zero in the constants defined)
26 // `--> OS version (Major & minor)
27 const os_ver: u16 = @intCast(u16, version_info.dwMajorVersion & 0xff) << 8 |
28 @intCast(u16, version_info.dwMinorVersion & 0xff);
29 const sp_ver: u8 = 0;
30 const sub_ver: u8 = if (os_ver >= 0x0A00) subver: {
31 // There's no other way to obtain this info beside
32 // checking the build number against a known set of
33 // values
34 var last_idx: usize = 0;
35 for (WindowsVersion.known_win10_build_numbers) |build, i| {
36 if (version_info.dwBuildNumber >= build)
37 last_idx = i;
38 }
39 break :subver @truncate(u8, last_idx);
40 } else 0;
41
42 const version: u32 = @as(u32, os_ver) << 16 | @as(u16, sp_ver) << 8 | sub_ver;
43
44 return @intToEnum(WindowsVersion, version);
45}