Skip to content

Commit 56c0388

Browse files
committed
Merge branch 'rohlem-fix-GetFinalPathNameByHandle-before-win10_rs4'
Merges #7379
2 parents fc10c9c + c96272f commit 56c0388

File tree

7 files changed

+242
-71
lines changed

7 files changed

+242
-71
lines changed

lib/std/mem.zig

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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`.
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

Lines changed: 101 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -953,7 +953,56 @@ pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 {
953953
return @bitCast(u64, result);
954954
}
955955

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+
9561004
pub 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

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+
10951165
pub const QueryInformationFileError = error{Unexpected};
10961166

10971167
pub fn QueryInformationFile(

lib/std/os/windows/bits.zig

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1620,3 +1620,18 @@ pub const IOCTL_MOUNTMGR_QUERY_POINTS: ULONG = 0x6d0008;
16201620
pub const SD_RECEIVE = 0;
16211621
pub const SD_SEND = 1;
16221622
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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,11 @@ pub extern "NtDll" fn NtWaitForKeyedEvent(
113113
) callconv(WINAPI) NTSTATUS;
114114

115115
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

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,19 +95,42 @@ 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,
102102
win10_rs3 = 0x0A000004,
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

Lines changed: 4 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const process = std.process;
1414
const Target = std.Target;
1515
const CrossTarget = std.zig.CrossTarget;
1616
const macos = @import("system/macos.zig");
17+
pub const windows = @import("system/windows.zig");
1718

1819
pub 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;

0 commit comments

Comments
 (0)