Skip to content

Commit b75c0d4

Browse files
committed
std.os.windows.GetFinalPathNameByHandle: address non-structural review comments
1 parent 74963e8 commit b75c0d4

File tree

5 files changed

+80
-87
lines changed

5 files changed

+80
-87
lines changed

lib/std/os/windows.zig

Lines changed: 59 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ pub const gdi32 = @import("windows/gdi32.zig");
2929
pub usingnamespace @import("windows/bits.zig");
3030

3131
//version detection
32-
usingnamespace std.zig.system.windows;
32+
const version = std.zig.system.windows;
33+
const WindowsVersion = version.WindowsVersion;
3334

3435
pub const self_process_handle = @intToPtr(HANDLE, maxInt(usize));
3536

@@ -962,42 +963,44 @@ pub fn QueryObjectName(
962963
var full_buffer: [@sizeOf(OBJECT_NAME_INFORMATION) + PATH_MAX_WIDE * 2]u8 align(@alignOf(OBJECT_NAME_INFORMATION)) = undefined;
963964
var info = @ptrCast(*OBJECT_NAME_INFORMATION, &full_buffer);
964965
//buffer size is specified in bytes
965-
const full_buffer_length = @intCast(ULONG, @sizeOf(OBJECT_NAME_INFORMATION) + std.math.min(PATH_MAX_WIDE, (out_buffer.len + 1) * 2));
966966
//last argument would return the length required for full_buffer, not exposed here
967-
const rc = ntdll.NtQueryObject(handle, .ObjectNameInformation, full_buffer[0..], full_buffer_length, null);
968-
return switch (rc) {
969-
.SUCCESS => if (@ptrCast(?[*]WCHAR, info.Name.Buffer)) |buffer| blk: {
967+
const rc = ntdll.NtQueryObject(handle, .ObjectNameInformation, &full_buffer, full_buffer.len, null);
968+
switch (rc) {
969+
.SUCCESS => {
970+
// info.Name.Buffer from ObQueryNameString is documented to be null (and MaximumLength == 0)
971+
// if the object was "unnamed", not sure if this can happen for file handles
972+
if (info.Name.MaximumLength == 0) return error.Unexpected;
970973
//resulting string length is specified in bytes
971974
const path_length_unterminated = @divExact(info.Name.Length, 2);
972975
if (out_buffer.len < path_length_unterminated) {
973976
return error.NameTooLong;
974977
}
975-
std.mem.copy(WCHAR, out_buffer[0..path_length_unterminated], buffer[0..path_length_unterminated :0]);
976-
break :blk out_buffer[0..path_length_unterminated];
977-
} else error.Unexpected,
978-
.ACCESS_DENIED => error.AccessDenied,
979-
.INVALID_HANDLE => error.InvalidHandle,
980-
.BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => error.NameTooLong,
978+
mem.copy(WCHAR, out_buffer[0..path_length_unterminated], info.Name.Buffer[0..path_length_unterminated]);
979+
return out_buffer[0..path_length_unterminated];
980+
},
981+
.ACCESS_DENIED => return error.AccessDenied,
982+
.INVALID_HANDLE => return error.InvalidHandle,
983+
.BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => return error.NameTooLong,
981984
//name_buffer.len >= @sizeOf(OBJECT_NAME_INFORMATION) holds statically
982985
.INFO_LENGTH_MISMATCH => unreachable,
983-
else => |e| unexpectedStatus(e),
984-
};
986+
else => |e| return unexpectedStatus(e),
987+
}
985988
}
986989
test "QueryObjectName" {
987990
if (comptime builtin.os.tag != .windows)
988991
return;
989992

990993
//any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths.
991-
const file = try std.fs.openSelfExe(.{});
992-
defer file.close();
993-
//make this large enough for the test runner exe path
994-
var out_buffer align(16) = std.mem.zeroes([1 << 10]u16);
994+
var tmp = std.testing.tmpDir(.{});
995+
defer tmp.cleanup();
996+
const handle = tmp.dir.fd;
997+
var out_buffer: [PATH_MAX_WIDE]u16 = undefined;
995998

996-
var result_path = try QueryObjectName(file.handle, out_buffer[0..]);
999+
var result_path = try QueryObjectName(handle, &out_buffer);
9971000
//insufficient size
998-
std.testing.expectError(error.NameTooLong, QueryObjectName(file.handle, out_buffer[0 .. result_path.len - 1]));
1001+
std.testing.expectError(error.NameTooLong, QueryObjectName(handle, out_buffer[0 .. result_path.len - 1]));
9991002
//exactly-sufficient size
1000-
_ = try QueryObjectName(file.handle, out_buffer[0..result_path.len]);
1003+
_ = try QueryObjectName(handle, out_buffer[0..result_path.len]);
10011004
}
10021005

10031006
pub const GetFinalPathNameByHandleError = error{
@@ -1029,56 +1032,57 @@ pub fn GetFinalPathNameByHandle(
10291032
fmt: GetFinalPathNameByHandleFormat,
10301033
out_buffer: []u16,
10311034
) GetFinalPathNameByHandleError![]u16 {
1032-
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;
1035+
var path_buffer: [math.max(@sizeOf(FILE_NAME_INFORMATION), @sizeOf(OBJECT_NAME_INFORMATION)) + PATH_MAX_WIDE * 2]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined;
10331036
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
10341037

10351038
var file_name_u16: []const u16 = undefined;
10361039
var volume_name_u16: []const u16 = undefined;
1037-
if ((comptime (targetVersionIsAtLeast(WindowsVersion.win10_rs4) != true)) //need explicit comptime, because error returns affect return type
1038-
and !runtimeVersionIsAtLeast(WindowsVersion.win10_rs4))
1039-
{
1040-
const final_path = QueryObjectName(hFile, std.mem.bytesAsSlice(u16, path_buffer[0..])) catch |err| return switch (err) {
1041-
error.InvalidHandle => error.FileNotFound, //close enough?
1040+
if ((comptime (std.builtin.os.version_range.windows.isAtLeast(WindowsVersion.win10_rs4) != true)) and !version.detectRuntimeVersion().isAtLeast(WindowsVersion.win10_rs4)) {
1041+
const final_path = QueryObjectName(hFile, mem.bytesAsSlice(u16, &path_buffer)) catch |err| return switch (err) {
1042+
// we assume InvalidHandle is close enough to FileNotFound in semantics
1043+
// to not further complicate the error set
1044+
error.InvalidHandle => error.FileNotFound,
10421045
else => |e| e,
10431046
};
10441047

10451048
if (fmt.volume_name == .Nt) {
10461049
if (out_buffer.len < final_path.len) {
10471050
return error.NameTooLong;
10481051
}
1049-
std.mem.copy(u16, out_buffer[0..], final_path[0..]);
1050-
return final_path; //we can directly return the slice we received
1052+
mem.copy(u16, out_buffer, final_path);
1053+
return out_buffer[0..final_path.len];
10511054
}
10521055

10531056
//otherwise we need to parse the string for volume path for the .Dos logic below to work
10541057
const expected_prefix = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\");
1055-
if (!std.mem.eql(u16, expected_prefix, final_path[0..expected_prefix.len])) {
1056-
//TODO find out if this can occur, and if we need to handle it differently
1057-
//(i.e. how to determine the end of a volume name)
1058-
return error.BadPathName;
1059-
}
1060-
const index = std.mem.indexOfPos(u16, final_path, expected_prefix.len, &[_]u16{'\\'}) orelse unreachable;
1058+
1059+
// TODO find out if a path can start with something besides `\Device\<volume name>`,
1060+
// and if we need to handle it differently
1061+
// (i.e. how to determine the start and end of the volume name in that case)
1062+
if (!mem.eql(u16, expected_prefix, final_path[0..expected_prefix.len])) return error.Unexpected;
1063+
1064+
const index = mem.indexOfPos(u16, final_path, expected_prefix.len, &[_]u16{'\\'}) orelse unreachable;
10611065
volume_name_u16 = final_path[0..index];
10621066
file_name_u16 = final_path[index..];
10631067

10641068
//fallthrough for fmt.volume_name != .Nt
10651069
} else {
10661070
// Get normalized path; doesn't include volume name though.
1067-
try QueryInformationFile(hFile, .FileNormalizedNameInformation, path_buffer[0..]);
1068-
const file_name = @ptrCast(*const FILE_NAME_INFORMATION, &path_buffer[0]);
1069-
file_name_u16 = @ptrCast([*]const u16, &file_name.FileName[0])[0..@divExact(file_name.FileNameLength, 2)];
1071+
try QueryInformationFile(hFile, .FileNormalizedNameInformation, &path_buffer);
1072+
const file_name = @ptrCast(*const FILE_NAME_INFORMATION, &path_buffer);
1073+
file_name_u16 = @ptrCast([*]const u16, &file_name.FileName)[0..@divExact(file_name.FileNameLength, 2)];
10701074

10711075
// Get NT volume name.
1072-
try QueryInformationFile(hFile, .FileVolumeNameInformation, volume_buffer[0..]);
1073-
const volume_name_info = @ptrCast(*const FILE_NAME_INFORMATION, &volume_buffer[0]);
1074-
volume_name_u16 = @ptrCast([*]const u16, &volume_name_info.FileName[0])[0..@divExact(volume_name_info.FileNameLength, 2)];
1076+
try QueryInformationFile(hFile, .FileVolumeNameInformation, &volume_buffer);
1077+
const volume_name_info = @ptrCast(*const FILE_NAME_INFORMATION, &volume_buffer);
1078+
volume_name_u16 = @ptrCast([*]const u16, &volume_name_info.FileName)[0..@divExact(volume_name_info.FileNameLength, 2)];
10751079

10761080
if (fmt.volume_name == .Nt) {
10771081
// Nothing to do, we simply copy the bytes to the user-provided buffer.
10781082
if (out_buffer.len < volume_name_u16.len + file_name_u16.len) return error.NameTooLong;
10791083

1080-
std.mem.copy(u16, out_buffer[0..], volume_name_u16);
1081-
std.mem.copy(u16, out_buffer[volume_name_u16.len..], file_name_u16);
1084+
mem.copy(u16, out_buffer, volume_name_u16);
1085+
mem.copy(u16, out_buffer[volume_name_u16.len..], file_name_u16);
10821086

10831087
return out_buffer[0 .. volume_name_u16.len + file_name_u16.len];
10841088
}
@@ -1123,7 +1127,7 @@ pub fn GetFinalPathNameByHandle(
11231127
input_struct.DeviceNameLength = @intCast(USHORT, volume_name_u16.len * 2);
11241128
@memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..], @ptrCast([*]const u8, volume_name_u16.ptr), volume_name_u16.len * 2);
11251129

1126-
DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, input_buf[0..], output_buf[0..]) catch |err| switch (err) {
1130+
DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, &input_buf, &output_buf) catch |err| switch (err) {
11271131
error.AccessDenied => unreachable,
11281132
else => |e| return e,
11291133
};
@@ -1143,22 +1147,20 @@ pub fn GetFinalPathNameByHandle(
11431147

11441148
// Look for `\DosDevices\` prefix. We don't really care if there are more than one symlinks
11451149
// with traditional DOS drive letters, so pick the first one available.
1146-
const prefix_u8 = "\\DosDevices\\";
1147-
var prefix_buf_u16: [prefix_u8.len]u16 = undefined;
1148-
const prefix_len_u16 = std.unicode.utf8ToUtf16Le(prefix_buf_u16[0..], prefix_u8[0..]) catch unreachable;
1149-
const prefix = prefix_buf_u16[0..prefix_len_u16];
1150+
var prefix_buf = std.unicode.utf8ToUtf16LeStringLiteral("\\DosDevices\\");
1151+
const prefix = prefix_buf[0..prefix_buf.len];
11501152

1151-
if (std.mem.startsWith(u16, symlink, prefix)) {
1153+
if (mem.startsWith(u16, symlink, prefix)) {
11521154
const drive_letter = symlink[prefix.len..];
11531155

11541156
if (out_buffer.len < drive_letter.len + file_name_u16.len) return error.NameTooLong;
11551157

1156-
std.mem.copy(u16, out_buffer[0..], drive_letter);
1157-
std.mem.copy(u16, out_buffer[drive_letter.len..], file_name_u16);
1158+
mem.copy(u16, out_buffer, drive_letter);
1159+
mem.copy(u16, out_buffer[drive_letter.len..], file_name_u16);
11581160
const total_len = drive_letter.len + file_name_u16.len;
11591161

11601162
// Validate that DOS does not contain any spurious nul bytes.
1161-
if (std.mem.indexOfScalar(u16, out_buffer[0..total_len], 0)) |_| {
1163+
if (mem.indexOfScalar(u16, out_buffer[0..total_len], 0)) |_| {
11621164
return error.BadPathName;
11631165
}
11641166

@@ -1178,15 +1180,14 @@ test "GetFinalPathNameByHandle" {
11781180
return;
11791181

11801182
//any file will do
1181-
const file = try std.fs.openSelfExe(.{});
1182-
defer file.close();
1183-
const handle = file.handle;
1184-
//make this large enough for the test runner exe path
1185-
var buffer = std.mem.zeroes([1 << 10]u16);
1183+
var tmp = std.testing.tmpDir(.{});
1184+
defer tmp.cleanup();
1185+
const handle = tmp.dir.fd;
1186+
var buffer: [PATH_MAX_WIDE]u16 = undefined;
11861187

11871188
//check with sufficient size
1188-
const nt_length = (try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0..])).len;
1189-
const dos_length = (try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0..])).len;
1189+
const nt_length = (try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, &buffer)).len;
1190+
const dos_length = (try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, &buffer)).len;
11901191

11911192
//check with insufficient size
11921193
std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0 .. nt_length - 1]));

lib/std/os/windows/bits.zig

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ pub const SIZE_T = usize;
6565
pub const TCHAR = if (UNICODE) WCHAR else u8;
6666
pub const UINT = c_uint;
6767
pub const ULONG_PTR = usize;
68-
pub const PULONG = *ULONG;
6968
pub const LONG_PTR = isize;
7069
pub const DWORD_PTR = ULONG_PTR;
7170
pub const UNICODE = false;

lib/std/os/windows/ntdll.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,5 +119,5 @@ pub extern "NtDll" fn NtQueryObject(
119119
ObjectInformationClass: OBJECT_INFORMATION_CLASS,
120120
ObjectInformation: PVOID,
121121
ObjectInformationLength: ULONG,
122-
ReturnLength: ?PULONG,
122+
ReturnLength: ?*ULONG,
123123
) callconv(WINAPI) NTSTATUS;

lib/std/target.zig

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,34 +95,43 @@ 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;
111113

112114
/// Compared against build numbers reported by the runtime to distinguish win10 versions,
113115
/// where 0x0A000000 + index corresponds to the WindowsVersion u32 value.
114116
pub const known_win10_build_numbers = [_]u32{
115-
10240, //win10
117+
10240, //win10 aka win10_th1
116118
10586, //win10_th2
117-
14393, //win_rs1
118-
15063, //win_rs2
119-
16299, //win_rs3
120-
17134, //win_rs4
121-
17763, //win_rs5
122-
18362, //win_19h1
123-
19041, //win_20h1
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
124128
};
125129

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+
}
134+
126135
pub const Range = struct {
127136
min: WindowsVersion,
128137
max: WindowsVersion,

lib/std/zig/system/windows.zig

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,3 @@ pub fn detectRuntimeVersion() WindowsVersion {
4343

4444
return @intToEnum(WindowsVersion, version);
4545
}
46-
47-
/// Returns whether the target os versions are uniformly at least as new as the argument:
48-
/// true/false if this holds for the entire target range, null if it only holds for some.
49-
pub fn targetVersionIsAtLeast(requested_version: WindowsVersion) ?bool {
50-
const requested = @enumToInt(requested_version);
51-
const version_range = std.builtin.os.version_range.windows;
52-
const target_min = @enumToInt(version_range.min);
53-
const target_max = @enumToInt(version_range.max);
54-
return if (target_max < requested) false else if (target_min >= requested) true else null;
55-
}
56-
57-
/// Returns whether the runtime os version is at least as new as the argument.
58-
pub fn runtimeVersionIsAtLeast(requested_version: WindowsVersion) bool {
59-
return targetVersionIsAtLeast(requested_version) orelse
60-
(@enumToInt(detectRuntimeVersion()) >= @enumToInt(requested_version));
61-
}

0 commit comments

Comments
 (0)