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" {...@@ -2436,3 +2436,46 @@ test "freeing empty string with null-terminated sentinel" {
2436 const empty_string = try dupeZ(testing.allocator, u8, "");2436 const empty_string = try dupeZ(testing.allocator, u8, "");
2437 testing.allocator.free(empty_string);2437 testing.allocator.free(empty_string);
2438}2438}
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 {...@@ -953,7 +953,56 @@ pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 {
953 return @bitCast(u64, result);953 return @bitCast(u64, result);
954}954}
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
956pub const GetFinalPathNameByHandleError = error{1004pub const GetFinalPathNameByHandleError = error{
1005 AccessDenied,
957 BadPathName,1006 BadPathName,
958 FileNotFound,1007 FileNotFound,
959 NameTooLong,1008 NameTooLong,
...@@ -981,32 +1030,31 @@ pub fn GetFinalPathNameByHandle(...@@ -981,32 +1030,31 @@ pub fn GetFinalPathNameByHandle(
981 fmt: GetFinalPathNameByHandleFormat,1030 fmt: GetFinalPathNameByHandleFormat,
982 out_buffer: []u16,1031 out_buffer: []u16,
983) GetFinalPathNameByHandleError![]u16 {1032) GetFinalPathNameByHandleError![]u16 {
984 // Get normalized path; doesn't include volume name though.1033 const final_path = QueryObjectName(hFile, out_buffer) catch |err| switch (err) {
985 var path_buffer: [@sizeOf(FILE_NAME_INFORMATION) + PATH_MAX_WIDE * 2]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined;1034 // we assume InvalidHandle is close enough to FileNotFound in semantics
986 try QueryInformationFile(hFile, .FileNormalizedNameInformation, path_buffer[0..]);1035 // to not further complicate the error set
9871036 error.InvalidHandle => return error.FileNotFound,
988 // Get NT volume name.1037 else => |e| return e,
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 name1038 };
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]);
9961039
997 switch (fmt.volume_name) {1040 switch (fmt.volume_name) {
998 .Nt => {1041 .Nt => {
999 // Nothing to do, we simply copy the bytes to the user-provided buffer.1042 // the returned path is already in .Nt format
1000 const volume_name_u16 = @ptrCast([*]const u16, &volume_name.FileName[0])[0 .. volume_name.FileNameLength / 2];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);1054 const file_path_begin_index = mem.indexOfPos(u16, final_path, expected_prefix.len, &[_]u16{'\\'}) orelse unreachable;
1005 std.mem.copy(u16, out_buffer[volume_name_u16.len..], file_name_u16);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 => {
1010 // Get DOS volume name. DOS volume names are actually symbolic link objects to the1058 // Get DOS volume name. DOS volume names are actually symbolic link objects to the
1011 // actual NT volume. For example:1059 // actual NT volume. For example:
1012 // (NT) \Device\HarddiskVolume4 => (DOS) \DosDevices\C: == (DOS) C:1060 // (NT) \Device\HarddiskVolume4 => (DOS) \DosDevices\C: == (DOS) C:
...@@ -1039,10 +1087,10 @@ pub fn GetFinalPathNameByHandle(...@@ -1039,10 +1087,10 @@ pub fn GetFinalPathNameByHandle(
10391087
1040 var input_struct = @ptrCast(*MOUNTMGR_MOUNT_POINT, &input_buf[0]);1088 var input_struct = @ptrCast(*MOUNTMGR_MOUNT_POINT, &input_buf[0]);
1041 input_struct.DeviceNameOffset = @sizeOf(MOUNTMGR_MOUNT_POINT);1089 input_struct.DeviceNameOffset = @sizeOf(MOUNTMGR_MOUNT_POINT);
1042 input_struct.DeviceNameLength = @intCast(USHORT, volume_name.FileNameLength);1090 input_struct.DeviceNameLength = @intCast(USHORT, volume_name_u16.len * 2);
1043 @memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..], @ptrCast([*]const u8, &volume_name.FileName[0]), volume_name.FileNameLength);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) {
1046 error.AccessDenied => unreachable,1094 error.AccessDenied => unreachable,
1047 else => |e| return e,1095 else => |e| return e,
1048 };1096 };
...@@ -1062,22 +1110,20 @@ pub fn GetFinalPathNameByHandle(...@@ -1062,22 +1110,20 @@ pub fn GetFinalPathNameByHandle(
10621110
1063 // Look for `\DosDevices\` prefix. We don't really care if there are more than one symlinks1111 // Look for `\DosDevices\` prefix. We don't really care if there are more than one symlinks
1064 // with traditional DOS drive letters, so pick the first one available.1112 // with traditional DOS drive letters, so pick the first one available.
1065 const prefix_u8 = "\\DosDevices\\";1113 var prefix_buf = std.unicode.utf8ToUtf16LeStringLiteral("\\DosDevices\\");
1066 var prefix_buf_u16: [prefix_u8.len]u16 = undefined;1114 const prefix = prefix_buf[0..prefix_buf.len];
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];
10691115
1070 if (std.mem.startsWith(u16, symlink, prefix)) {1116 if (mem.startsWith(u16, symlink, prefix)) {
1071 const drive_letter = symlink[prefix.len..];1117 const drive_letter = symlink[prefix.len..];
10721118
1073 if (out_buffer.len < drive_letter.len + file_name_u16.len) return error.NameTooLong;1119 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);1121 mem.copy(u16, out_buffer, drive_letter);
1076 std.mem.copy(u16, out_buffer[drive_letter.len..], file_name_u16);1122 mem.copy(u16, out_buffer[drive_letter.len..], file_name_u16);
1077 const total_len = drive_letter.len + file_name_u16.len;1123 const total_len = drive_letter.len + file_name_u16.len;
10781124
1079 // Validate that DOS does not contain any spurious nul bytes.1125 // 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)) |_| {
1081 return error.BadPathName;1127 return error.BadPathName;
1082 }1128 }
10831129
...@@ -1092,6 +1138,30 @@ pub fn GetFinalPathNameByHandle(...@@ -1092,6 +1138,30 @@ pub fn GetFinalPathNameByHandle(
1092 }1138 }
1093}1139}
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
1095pub const QueryInformationFileError = error{Unexpected};1165pub const QueryInformationFileError = error{Unexpected};
10961166
1097pub fn QueryInformationFile(1167pub fn QueryInformationFile(
lib/std/os/windows/bits.zig+15
...@@ -1620,3 +1620,18 @@ pub const IOCTL_MOUNTMGR_QUERY_POINTS: ULONG = 0x6d0008;...@@ -1620,3 +1620,18 @@ pub const IOCTL_MOUNTMGR_QUERY_POINTS: ULONG = 0x6d0008;
1620pub const SD_RECEIVE = 0;1620pub const SD_RECEIVE = 0;
1621pub const SD_SEND = 1;1621pub const SD_SEND = 1;
1622pub const SD_BOTH = 2;1622pub 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(...@@ -113,3 +113,11 @@ pub extern "NtDll" fn NtWaitForKeyedEvent(
113) callconv(WINAPI) NTSTATUS;113) callconv(WINAPI) NTSTATUS;
114114
115pub extern "NtDll" fn RtlSetCurrentDirectory_U(PathName: *UNICODE_STRING) callconv(WINAPI) NTSTATUS;115pub 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 {...@@ -95,7 +95,7 @@ pub const Target = struct {
95 win7 = 0x06010000,95 win7 = 0x06010000,
96 win8 = 0x06020000,96 win8 = 0x06020000,
97 win8_1 = 0x06030000,97 win8_1 = 0x06030000,
98 win10 = 0x0A000000,98 win10 = 0x0A000000, //aka win10_th1
99 win10_th2 = 0x0A000001,99 win10_th2 = 0x0A000001,
100 win10_rs1 = 0x0A000002,100 win10_rs1 = 0x0A000002,
101 win10_rs2 = 0x0A000003,101 win10_rs2 = 0x0A000003,
...@@ -103,11 +103,34 @@ pub const Target = struct {...@@ -103,11 +103,34 @@ pub const Target = struct {
103 win10_rs4 = 0x0A000005,103 win10_rs4 = 0x0A000005,
104 win10_rs5 = 0x0A000006,104 win10_rs5 = 0x0A000006,
105 win10_19h1 = 0x0A000007,105 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
107 _,109 _,
108110
109 /// Latest Windows version that the Zig Standard Library is aware of111 /// 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
112 pub const Range = struct {135 pub const Range = struct {
113 min: WindowsVersion,136 min: WindowsVersion,
lib/std/zig/system.zig+4-37
...@@ -14,6 +14,7 @@ const process = std.process;...@@ -14,6 +14,7 @@ const process = std.process;
14const Target = std.Target;14const Target = std.Target;
15const CrossTarget = std.zig.CrossTarget;15const CrossTarget = std.zig.CrossTarget;
16const macos = @import("system/macos.zig");16const macos = @import("system/macos.zig");
17pub const windows = @import("system/windows.zig");
1718
18pub const getSDKPath = macos.getSDKPath;19pub const getSDKPath = macos.getSDKPath;
1920
...@@ -249,43 +250,9 @@ pub const NativeTargetInfo = struct {...@@ -249,43 +250,9 @@ pub const NativeTargetInfo = struct {
249 }250 }
250 },251 },
251 .windows => {252 .windows => {
252 var version_info: std.os.windows.RTL_OSVERSIONINFOW = undefined;253 const detected_version = windows.detectRuntimeVersion();
253 version_info.dwOSVersionInfoSize = @sizeOf(@TypeOf(version_info));254 os.version_range.windows.min = detected_version;
254255 os.version_range.windows.max = detected_version;
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);
289 },256 },
290 .macos => {257 .macos => {
291 var scbuf: [32]u8 = undefined;258 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}