@@ -953,7 +953,56 @@ pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 {
953
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
1004
pub const GetFinalPathNameByHandleError = error {
1005
+ AccessDenied ,
957
1006
BadPathName ,
958
1007
FileNotFound ,
959
1008
NameTooLong ,
@@ -981,32 +1030,31 @@ pub fn GetFinalPathNameByHandle(
981
1030
fmt : GetFinalPathNameByHandleFormat ,
982
1031
out_buffer : []u16 ,
983
1032
) 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
+ };
996
1039
997
1040
switch (fmt .volume_name ) {
998
1041
.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\\ " );
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 );
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 .. ];
1006
1057
1007
- return out_buffer [0 .. volume_name_u16 .len + file_name_u16 .len ];
1008
- },
1009
- .Dos = > {
1010
1058
// Get DOS volume name. DOS volume names are actually symbolic link objects to the
1011
1059
// actual NT volume. For example:
1012
1060
// (NT) \Device\HarddiskVolume4 => (DOS) \DosDevices\C: == (DOS) C:
@@ -1039,10 +1087,10 @@ pub fn GetFinalPathNameByHandle(
1039
1087
1040
1088
var input_struct = @ptrCast (* MOUNTMGR_MOUNT_POINT , & input_buf [0 ]);
1041
1089
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 );
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
1094
error .AccessDenied = > unreachable ,
1047
1095
else = > | e | return e ,
1048
1096
};
@@ -1062,22 +1110,20 @@ pub fn GetFinalPathNameByHandle(
1062
1110
1063
1111
// Look for `\DosDevices\` prefix. We don't really care if there are more than one symlinks
1064
1112
// 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 ];
1069
1115
1070
- if (std . mem .startsWith (u16 , symlink , prefix )) {
1116
+ if (mem .startsWith (u16 , symlink , prefix )) {
1071
1117
const drive_letter = symlink [prefix .len .. ];
1072
1118
1073
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 );
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 );
1077
1123
const total_len = drive_letter .len + file_name_u16 .len ;
1078
1124
1079
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
1127
return error .BadPathName ;
1082
1128
}
1083
1129
@@ -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
1165
pub const QueryInformationFileError = error {Unexpected };
1096
1166
1097
1167
pub fn QueryInformationFile (
0 commit comments