| author | |
| committer | |
| log | 56c03881ebd8617e6506bfad01bf9cfdd4d3df7e |
| tree | 25da58e774494f28c44463921ba2f2387d8e3ae3 |
| parent | fc10c9c4ce40570d0abeda764f1bd76b3e79af61 |
| parent | c96272f6186a5e843c0586513e87ffd771c5b00c |
Merges #73797 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`. | ||
| 2442 | fn 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. | ||
| 2459 | pub 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. | ||
| 2473 | pub 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 | } |
| 955 | 955 | ||
| 956 | pub 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 | } | ||
| 986 | test "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 | |||
| 956 | pub const GetFinalPathNameByHandleError = error{ | 1004 | pub 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 |
| 987 | 1036 | 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 name | 1038 | }; |
| 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]); | ||
| 996 | 1039 | ||
| 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\\"); | ||
| 1001 | 1048 | ||
| 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; | ||
| 1003 | 1053 | ||
| 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..]; | ||
| 1006 | 1057 | ||
| 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 the | 1058 | // 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( |
| 1039 | 1087 | ||
| 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); |
| 1044 | 1092 | ||
| 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( |
| 1062 | 1110 | ||
| 1063 | // Look for `\DosDevices\` prefix. We don't really care if there are more than one symlinks | 1111 | // 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]; | ||
| 1069 | 1115 | ||
| 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..]; |
| 1072 | 1118 | ||
| 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; |
| 1074 | 1120 | ||
| 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; |
| 1078 | 1124 | ||
| 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 | } |
| 1083 | 1129 | ||
| ... | @@ -1092,6 +1138,30 @@ pub fn GetFinalPathNameByHandle( | ... | @@ -1092,6 +1138,30 @@ pub fn GetFinalPathNameByHandle( |
| 1092 | } | 1138 | } |
| 1093 | } | 1139 | } |
| 1094 | 1140 | ||
| 1141 | test "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 | |||
| 1095 | pub const QueryInformationFileError = error{Unexpected}; | 1165 | pub const QueryInformationFileError = error{Unexpected}; |
| 1096 | 1166 | ||
| 1097 | pub fn QueryInformationFile( | 1167 | pub 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; |
| 1620 | pub const SD_RECEIVE = 0; | 1620 | pub const SD_RECEIVE = 0; |
| 1621 | pub const SD_SEND = 1; | 1621 | pub const SD_SEND = 1; |
| 1622 | pub const SD_BOTH = 2; | 1622 | pub const SD_BOTH = 2; |
| 1623 | |||
| 1624 | pub 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 | |||
| 1634 | pub const OBJECT_NAME_INFORMATION = extern struct { | ||
| 1635 | Name: UNICODE_STRING, | ||
| 1636 | }; | ||
| 1637 | pub 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; |
| 114 | 114 | ||
| 115 | pub extern "NtDll" fn RtlSetCurrentDirectory_U(PathName: *UNICODE_STRING) callconv(WINAPI) NTSTATUS; | 115 | pub extern "NtDll" fn RtlSetCurrentDirectory_U(PathName: *UNICODE_STRING) callconv(WINAPI) NTSTATUS; |
| 116 | |||
| 117 | pub 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 | _, |
| 108 | 110 | ||
| 109 | /// Latest Windows version that the Zig Standard Library is aware of | 111 | /// 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 | } | ||
| 111 | 134 | ||
| 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; |
| 14 | const Target = std.Target; | 14 | const Target = std.Target; |
| 15 | const CrossTarget = std.zig.CrossTarget; | 15 | const CrossTarget = std.zig.CrossTarget; |
| 16 | const macos = @import("system/macos.zig"); | 16 | const macos = @import("system/macos.zig"); |
| 17 | pub const windows = @import("system/windows.zig"); | ||
| 17 | 18 | ||
| 18 | pub const getSDKPath = macos.getSDKPath; | 19 | pub const getSDKPath = macos.getSDKPath; |
| 19 | 20 | ||
| ... | @@ -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; |
| 254 | 255 | 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. | ||
| 6 | const std = @import("std"); | ||
| 7 | |||
| 8 | pub 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. | ||
| 12 | pub 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 | } | ||