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