| ... | @@ -29,7 +29,8 @@ pub const gdi32 = @import("windows/gdi32.zig"); | ... | @@ -29,7 +29,8 @@ pub const gdi32 = @import("windows/gdi32.zig"); |
| 29 | pub usingnamespace @import("windows/bits.zig"); | 29 | pub usingnamespace @import("windows/bits.zig"); |
| 30 | | 30 | |
| 31 | //version detection | 31 | //version detection |
| 32 | usingnamespace std.zig.system.windows; | 32 | const version = std.zig.system.windows; |
| | 33 | const WindowsVersion = version.WindowsVersion; |
| 33 | | 34 | |
| 34 | pub const self_process_handle = @intToPtr(HANDLE, maxInt(usize)); | 35 | pub const self_process_handle = @intToPtr(HANDLE, maxInt(usize)); |
| 35 | | 36 | |
| ... | @@ -963,42 +964,44 @@ pub fn QueryObjectName( | ... | @@ -963,42 +964,44 @@ pub fn QueryObjectName( |
| 963 | var full_buffer: [@sizeOf(OBJECT_NAME_INFORMATION) + PATH_MAX_WIDE * 2]u8 align(@alignOf(OBJECT_NAME_INFORMATION)) = undefined; | 964 | var full_buffer: [@sizeOf(OBJECT_NAME_INFORMATION) + PATH_MAX_WIDE * 2]u8 align(@alignOf(OBJECT_NAME_INFORMATION)) = undefined; |
| 964 | var info = @ptrCast(*OBJECT_NAME_INFORMATION, &full_buffer); | 965 | var info = @ptrCast(*OBJECT_NAME_INFORMATION, &full_buffer); |
| 965 | //buffer size is specified in bytes | 966 | //buffer size is specified in bytes |
| 966 | const full_buffer_length = @intCast(ULONG, @sizeOf(OBJECT_NAME_INFORMATION) + std.math.min(PATH_MAX_WIDE, (out_buffer.len + 1) * 2)); | | |
| 967 | //last argument would return the length required for full_buffer, not exposed here | 967 | //last argument would return the length required for full_buffer, not exposed here |
| 968 | const rc = ntdll.NtQueryObject(handle, .ObjectNameInformation, full_buffer[0..], full_buffer_length, null); | 968 | const rc = ntdll.NtQueryObject(handle, .ObjectNameInformation, &full_buffer, full_buffer.len, null); |
| 969 | return switch (rc) { | 969 | switch (rc) { |
| 970 | .SUCCESS => if (@ptrCast(?[*]WCHAR, info.Name.Buffer)) |buffer| blk: { | 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; |
| 971 | //resulting string length is specified in bytes | 974 | //resulting string length is specified in bytes |
| 972 | const path_length_unterminated = @divExact(info.Name.Length, 2); | 975 | const path_length_unterminated = @divExact(info.Name.Length, 2); |
| 973 | if (out_buffer.len < path_length_unterminated) { | 976 | if (out_buffer.len < path_length_unterminated) { |
| 974 | return error.NameTooLong; | 977 | return error.NameTooLong; |
| 975 | } | 978 | } |
| 976 | std.mem.copy(WCHAR, out_buffer[0..path_length_unterminated], buffer[0..path_length_unterminated :0]); | 979 | mem.copy(WCHAR, out_buffer[0..path_length_unterminated], info.Name.Buffer[0..path_length_unterminated]); |
| 977 | break :blk out_buffer[0..path_length_unterminated]; | 980 | return out_buffer[0..path_length_unterminated]; |
| 978 | } else error.Unexpected, | 981 | }, |
| 979 | .ACCESS_DENIED => error.AccessDenied, | 982 | .ACCESS_DENIED => return error.AccessDenied, |
| 980 | .INVALID_HANDLE => error.InvalidHandle, | 983 | .INVALID_HANDLE => return error.InvalidHandle, |
| 981 | .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => error.NameTooLong, | 984 | .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => return error.NameTooLong, |
| 982 | //name_buffer.len >= @sizeOf(OBJECT_NAME_INFORMATION) holds statically | 985 | //name_buffer.len >= @sizeOf(OBJECT_NAME_INFORMATION) holds statically |
| 983 | .INFO_LENGTH_MISMATCH => unreachable, | 986 | .INFO_LENGTH_MISMATCH => unreachable, |
| 984 | else => |e| unexpectedStatus(e), | 987 | else => |e| return unexpectedStatus(e), |
| 985 | }; | 988 | } |
| 986 | } | 989 | } |
| 987 | test "QueryObjectName" { | 990 | test "QueryObjectName" { |
| 988 | if (comptime builtin.os.tag != .windows) | 991 | if (comptime builtin.os.tag != .windows) |
| 989 | return; | 992 | return; |
| 990 | | 993 | |
| 991 | //any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths. | 994 | //any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths. |
| 992 | const file = try std.fs.openSelfExe(.{}); | 995 | var tmp = std.testing.tmpDir(.{}); |
| 993 | defer file.close(); | 996 | defer tmp.cleanup(); |
| 994 | //make this large enough for the test runner exe path | 997 | const handle = tmp.dir.fd; |
| 995 | var out_buffer align(16) = std.mem.zeroes([1 << 10]u16); | 998 | var out_buffer: [PATH_MAX_WIDE]u16 = undefined; |
| 996 | | 999 | |
| 997 | var result_path = try QueryObjectName(file.handle, out_buffer[0..]); | 1000 | var result_path = try QueryObjectName(handle, &out_buffer); |
| 998 | //insufficient size | 1001 | //insufficient size |
| 999 | std.testing.expectError(error.NameTooLong, QueryObjectName(file.handle, out_buffer[0 .. result_path.len - 1])); | 1002 | std.testing.expectError(error.NameTooLong, QueryObjectName(handle, out_buffer[0 .. result_path.len - 1])); |
| 1000 | //exactly-sufficient size | 1003 | //exactly-sufficient size |
| 1001 | _ = try QueryObjectName(file.handle, out_buffer[0..result_path.len]); | 1004 | _ = try QueryObjectName(handle, out_buffer[0..result_path.len]); |
| 1002 | } | 1005 | } |
| 1003 | | 1006 | |
| 1004 | pub const GetFinalPathNameByHandleError = error{ | 1007 | pub const GetFinalPathNameByHandleError = error{ |
| ... | @@ -1030,16 +1033,16 @@ pub fn GetFinalPathNameByHandle( | ... | @@ -1030,16 +1033,16 @@ pub fn GetFinalPathNameByHandle( |
| 1030 | fmt: GetFinalPathNameByHandleFormat, | 1033 | fmt: GetFinalPathNameByHandleFormat, |
| 1031 | out_buffer: []u16, | 1034 | out_buffer: []u16, |
| 1032 | ) GetFinalPathNameByHandleError![]u16 { | 1035 | ) GetFinalPathNameByHandleError![]u16 { |
| 1033 | 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; | 1036 | var path_buffer: [math.max(@sizeOf(FILE_NAME_INFORMATION), @sizeOf(OBJECT_NAME_INFORMATION)) + PATH_MAX_WIDE * 2]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; |
| 1034 | 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 | 1037 | 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 |
| 1035 | | 1038 | |
| 1036 | var file_name_u16: []const u16 = undefined; | 1039 | var file_name_u16: []const u16 = undefined; |
| 1037 | var volume_name_u16: []const u16 = undefined; | 1040 | var volume_name_u16: []const u16 = undefined; |
| 1038 | if ((comptime (targetVersionIsAtLeast(WindowsVersion.win10_rs4) != true)) //need explicit comptime, because error returns affect return type | 1041 | if ((comptime (std.builtin.os.version_range.windows.isAtLeast(WindowsVersion.win10_rs4) != true)) and !version.detectRuntimeVersion().isAtLeast(WindowsVersion.win10_rs4)) { |
| 1039 | and !runtimeVersionIsAtLeast(WindowsVersion.win10_rs4)) | 1042 | const final_path = QueryObjectName(hFile, mem.bytesAsSlice(u16, &path_buffer)) catch |err| return switch (err) { |
| 1040 | { | 1043 | // we assume InvalidHandle is close enough to FileNotFound in semantics |
| 1041 | const final_path = QueryObjectName(hFile, std.mem.bytesAsSlice(u16, path_buffer[0..])) catch |err| return switch (err) { | 1044 | // to not further complicate the error set |
| 1042 | error.InvalidHandle => error.FileNotFound, //close enough? | 1045 | error.InvalidHandle => error.FileNotFound, |
| 1043 | else => |e| e, | 1046 | else => |e| e, |
| 1044 | }; | 1047 | }; |
| 1045 | | 1048 | |
| ... | @@ -1047,39 +1050,40 @@ pub fn GetFinalPathNameByHandle( | ... | @@ -1047,39 +1050,40 @@ pub fn GetFinalPathNameByHandle( |
| 1047 | if (out_buffer.len < final_path.len) { | 1050 | if (out_buffer.len < final_path.len) { |
| 1048 | return error.NameTooLong; | 1051 | return error.NameTooLong; |
| 1049 | } | 1052 | } |
| 1050 | std.mem.copy(u16, out_buffer[0..], final_path[0..]); | 1053 | mem.copy(u16, out_buffer, final_path); |
| 1051 | return final_path; //we can directly return the slice we received | 1054 | return out_buffer[0..final_path.len]; |
| 1052 | } | 1055 | } |
| 1053 | | 1056 | |
| 1054 | //otherwise we need to parse the string for volume path for the .Dos logic below to work | 1057 | //otherwise we need to parse the string for volume path for the .Dos logic below to work |
| 1055 | const expected_prefix = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\"); | 1058 | const expected_prefix = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\"); |
| 1056 | if (!std.mem.eql(u16, expected_prefix, final_path[0..expected_prefix.len])) { | 1059 | |
| 1057 | //TODO find out if this can occur, and if we need to handle it differently | 1060 | // TODO find out if a path can start with something besides `\Device\<volume name>`, |
| 1058 | //(i.e. how to determine the end of a volume name) | 1061 | // and if we need to handle it differently |
| 1059 | return error.BadPathName; | 1062 | // (i.e. how to determine the start and end of the volume name in that case) |
| 1060 | } | 1063 | if (!mem.eql(u16, expected_prefix, final_path[0..expected_prefix.len])) return error.Unexpected; |
| 1061 | const index = std.mem.indexOfPos(u16, final_path, expected_prefix.len, &[_]u16{'\\'}) orelse unreachable; | 1064 | |
| | 1065 | const index = mem.indexOfPos(u16, final_path, expected_prefix.len, &[_]u16{'\\'}) orelse unreachable; |
| 1062 | volume_name_u16 = final_path[0..index]; | 1066 | volume_name_u16 = final_path[0..index]; |
| 1063 | file_name_u16 = final_path[index..]; | 1067 | file_name_u16 = final_path[index..]; |
| 1064 | | 1068 | |
| 1065 | //fallthrough for fmt.volume_name != .Nt | 1069 | //fallthrough for fmt.volume_name != .Nt |
| 1066 | } else { | 1070 | } else { |
| 1067 | // Get normalized path; doesn't include volume name though. | 1071 | // Get normalized path; doesn't include volume name though. |
| 1068 | try QueryInformationFile(hFile, .FileNormalizedNameInformation, path_buffer[0..]); | 1072 | try QueryInformationFile(hFile, .FileNormalizedNameInformation, &path_buffer); |
| 1069 | const file_name = @ptrCast(*const FILE_NAME_INFORMATION, &path_buffer[0]); | 1073 | const file_name = @ptrCast(*const FILE_NAME_INFORMATION, &path_buffer); |
| 1070 | file_name_u16 = @ptrCast([*]const u16, &file_name.FileName[0])[0..@divExact(file_name.FileNameLength, 2)]; | 1074 | file_name_u16 = @ptrCast([*]const u16, &file_name.FileName)[0..@divExact(file_name.FileNameLength, 2)]; |
| 1071 | | 1075 | |
| 1072 | // Get NT volume name. | 1076 | // Get NT volume name. |
| 1073 | try QueryInformationFile(hFile, .FileVolumeNameInformation, volume_buffer[0..]); | 1077 | try QueryInformationFile(hFile, .FileVolumeNameInformation, &volume_buffer); |
| 1074 | const volume_name_info = @ptrCast(*const FILE_NAME_INFORMATION, &volume_buffer[0]); | 1078 | const volume_name_info = @ptrCast(*const FILE_NAME_INFORMATION, &volume_buffer); |
| 1075 | volume_name_u16 = @ptrCast([*]const u16, &volume_name_info.FileName[0])[0..@divExact(volume_name_info.FileNameLength, 2)]; | 1079 | volume_name_u16 = @ptrCast([*]const u16, &volume_name_info.FileName)[0..@divExact(volume_name_info.FileNameLength, 2)]; |
| 1076 | | 1080 | |
| 1077 | if (fmt.volume_name == .Nt) { | 1081 | if (fmt.volume_name == .Nt) { |
| 1078 | // Nothing to do, we simply copy the bytes to the user-provided buffer. | 1082 | // Nothing to do, we simply copy the bytes to the user-provided buffer. |
| 1079 | if (out_buffer.len < volume_name_u16.len + file_name_u16.len) return error.NameTooLong; | 1083 | if (out_buffer.len < volume_name_u16.len + file_name_u16.len) return error.NameTooLong; |
| 1080 | | 1084 | |
| 1081 | std.mem.copy(u16, out_buffer[0..], volume_name_u16); | 1085 | mem.copy(u16, out_buffer, volume_name_u16); |
| 1082 | std.mem.copy(u16, out_buffer[volume_name_u16.len..], file_name_u16); | 1086 | mem.copy(u16, out_buffer[volume_name_u16.len..], file_name_u16); |
| 1083 | | 1087 | |
| 1084 | return out_buffer[0 .. volume_name_u16.len + file_name_u16.len]; | 1088 | return out_buffer[0 .. volume_name_u16.len + file_name_u16.len]; |
| 1085 | } | 1089 | } |
| ... | @@ -1124,7 +1128,7 @@ pub fn GetFinalPathNameByHandle( | ... | @@ -1124,7 +1128,7 @@ pub fn GetFinalPathNameByHandle( |
| 1124 | input_struct.DeviceNameLength = @intCast(USHORT, volume_name_u16.len * 2); | 1128 | input_struct.DeviceNameLength = @intCast(USHORT, volume_name_u16.len * 2); |
| 1125 | @memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..], @ptrCast([*]const u8, volume_name_u16.ptr), volume_name_u16.len * 2); | 1129 | @memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..], @ptrCast([*]const u8, volume_name_u16.ptr), volume_name_u16.len * 2); |
| 1126 | | 1130 | |
| 1127 | DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, input_buf[0..], output_buf[0..]) catch |err| switch (err) { | 1131 | DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, &input_buf, &output_buf) catch |err| switch (err) { |
| 1128 | error.AccessDenied => unreachable, | 1132 | error.AccessDenied => unreachable, |
| 1129 | else => |e| return e, | 1133 | else => |e| return e, |
| 1130 | }; | 1134 | }; |
| ... | @@ -1144,22 +1148,20 @@ pub fn GetFinalPathNameByHandle( | ... | @@ -1144,22 +1148,20 @@ pub fn GetFinalPathNameByHandle( |
| 1144 | | 1148 | |
| 1145 | // Look for `\DosDevices\` prefix. We don't really care if there are more than one symlinks | 1149 | // Look for `\DosDevices\` prefix. We don't really care if there are more than one symlinks |
| 1146 | // with traditional DOS drive letters, so pick the first one available. | 1150 | // with traditional DOS drive letters, so pick the first one available. |
| 1147 | const prefix_u8 = "\\DosDevices\\"; | 1151 | var prefix_buf = std.unicode.utf8ToUtf16LeStringLiteral("\\DosDevices\\"); |
| 1148 | var prefix_buf_u16: [prefix_u8.len]u16 = undefined; | 1152 | const prefix = prefix_buf[0..prefix_buf.len]; |
| 1149 | const prefix_len_u16 = std.unicode.utf8ToUtf16Le(prefix_buf_u16[0..], prefix_u8[0..]) catch unreachable; | | |
| 1150 | const prefix = prefix_buf_u16[0..prefix_len_u16]; | | |
| 1151 | | 1153 | |
| 1152 | if (std.mem.startsWith(u16, symlink, prefix)) { | 1154 | if (mem.startsWith(u16, symlink, prefix)) { |
| 1153 | const drive_letter = symlink[prefix.len..]; | 1155 | const drive_letter = symlink[prefix.len..]; |
| 1154 | | 1156 | |
| 1155 | if (out_buffer.len < drive_letter.len + file_name_u16.len) return error.NameTooLong; | 1157 | if (out_buffer.len < drive_letter.len + file_name_u16.len) return error.NameTooLong; |
| 1156 | | 1158 | |
| 1157 | std.mem.copy(u16, out_buffer[0..], drive_letter); | 1159 | mem.copy(u16, out_buffer, drive_letter); |
| 1158 | std.mem.copy(u16, out_buffer[drive_letter.len..], file_name_u16); | 1160 | mem.copy(u16, out_buffer[drive_letter.len..], file_name_u16); |
| 1159 | const total_len = drive_letter.len + file_name_u16.len; | 1161 | const total_len = drive_letter.len + file_name_u16.len; |
| 1160 | | 1162 | |
| 1161 | // Validate that DOS does not contain any spurious nul bytes. | 1163 | // Validate that DOS does not contain any spurious nul bytes. |
| 1162 | if (std.mem.indexOfScalar(u16, out_buffer[0..total_len], 0)) |_| { | 1164 | if (mem.indexOfScalar(u16, out_buffer[0..total_len], 0)) |_| { |
| 1163 | return error.BadPathName; | 1165 | return error.BadPathName; |
| 1164 | } | 1166 | } |
| 1165 | | 1167 | |
| ... | @@ -1179,15 +1181,14 @@ test "GetFinalPathNameByHandle" { | ... | @@ -1179,15 +1181,14 @@ test "GetFinalPathNameByHandle" { |
| 1179 | return; | 1181 | return; |
| 1180 | | 1182 | |
| 1181 | //any file will do | 1183 | //any file will do |
| 1182 | const file = try std.fs.openSelfExe(.{}); | 1184 | var tmp = std.testing.tmpDir(.{}); |
| 1183 | defer file.close(); | 1185 | defer tmp.cleanup(); |
| 1184 | const handle = file.handle; | 1186 | const handle = tmp.dir.fd; |
| 1185 | //make this large enough for the test runner exe path | 1187 | var buffer: [PATH_MAX_WIDE]u16 = undefined; |
| 1186 | var buffer = std.mem.zeroes([1 << 10]u16); | | |
| 1187 | | 1188 | |
| 1188 | //check with sufficient size | 1189 | //check with sufficient size |
| 1189 | const nt_length = (try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0..])).len; | 1190 | const nt_length = (try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, &buffer)).len; |
| 1190 | const dos_length = (try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0..])).len; | 1191 | const dos_length = (try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, &buffer)).len; |
| 1191 | | 1192 | |
| 1192 | //check with insufficient size | 1193 | //check with insufficient size |
| 1193 | std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0 .. nt_length - 1])); | 1194 | std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0 .. nt_length - 1])); |