@@ -1033,67 +1033,53 @@ pub fn GetFinalPathNameByHandle(
1033
1033
out_buffer : []u16 ,
1034
1034
) GetFinalPathNameByHandleError ! []u16 {
1035
1035
1036
- if ((comptime (targetVersionIsAtLeast (WindowsVersion .win10_rs4 ) != true )) //need explicit comptime, because error returns affect return type
1037
- and ! runtimeVersionIsAtLeast (WindowsVersion .win10_rs4 )){
1038
- // TODO: directly replace/emulate QueryInformationFile of .FileNormalizedNameInformation
1039
- // with ntdll instead of calling into kernel32
1040
- // (probably using some less-powerful query and looping over path segments)
1041
- const flags : DWORD = FILE_NAME_NORMALIZED | switch (fmt .volume_name ){
1042
- .Dos = > @as (DWORD , VOLUME_NAME_DOS ),
1043
- .Nt = > @as (DWORD , VOLUME_NAME_NT ),
1044
- };
1045
- const rc = kernel32 .GetFinalPathNameByHandleW (hFile , out_buffer .ptr , @intCast (u32 , out_buffer .len ), flags );
1046
- if (rc == 0 ) {
1047
- switch (kernel32 .GetLastError ()) {
1048
- .FILE_NOT_FOUND = > return error .FileNotFound ,
1049
- .PATH_NOT_FOUND = > return error .FileNotFound ,
1050
- .NOT_ENOUGH_MEMORY = > return error .SystemResources ,
1051
- .FILENAME_EXCED_RANGE = > return error .NameTooLong ,
1052
- .ACCESS_DENIED = > return error .AccessDenied , //can happen in SMB sub-queries for parent path segments
1053
- .INVALID_PARAMETER = > unreachable ,
1054
- else = > | err | return unexpectedError (err ),
1055
- }
1056
- }
1057
-
1058
- if (rc > out_buffer .len ) return error .NameTooLong ; //rc = length of string INCLUDING null terminator (unlike in case of success)
1059
- //in case of success, rc = length of string EXCLUDING null terminator
1060
- std .debug .assert (rc < out_buffer .len ); //this would mean the length we provided was enough, but GetFinalPathNameByHandleW thinks it wasn't
1061
-
1062
- switch (fmt .volume_name ){
1063
- .Dos = > {
1064
- const expected_prefix = [_ ]u16 {'\\ ' , '\\ ' , '?' , '\\ ' };
1065
- assert (std .mem .eql (u16 , expected_prefix [0.. ], out_buffer [0.. expected_prefix .len ]));
1066
- return out_buffer [expected_prefix .len .. rc :0 ];
1067
- },
1068
- .Nt = > return out_buffer [0.. rc :0 ], //no prefix here, which isn't mentioned in documentation
1069
- }
1070
- }
1071
-
1072
- // Get normalized path; doesn't include volume name though.
1073
1036
var path_buffer : [@sizeOf (FILE_NAME_INFORMATION ) + PATH_MAX_WIDE * 2 ]u8 align (@alignOf (FILE_NAME_INFORMATION )) = undefined ;
1074
- try QueryInformationFile (hFile , .FileNormalizedNameInformation , path_buffer [0.. ]);
1075
-
1076
- // Get NT volume name.
1077
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
1078
- try QueryInformationFile (hFile , .FileVolumeNameInformation , volume_buffer [0.. ]);
1079
1038
1080
- const file_name = @ptrCast (* const FILE_NAME_INFORMATION , & path_buffer [0 ]);
1081
- const file_name_u16 = @ptrCast ([* ]const u16 , & file_name .FileName [0 ])[0 .. file_name .FileNameLength / 2 ];
1039
+ var file_name_u16 : []const u16 = undefined ;
1040
+ var volume_name_u16 : []const u16 = undefined ;
1041
+ if ((comptime (targetVersionIsAtLeast (WindowsVersion .win10_rs4 ) != true )) //need explicit comptime, because error returns affect return type
1042
+ and ! runtimeVersionIsAtLeast (WindowsVersion .win10_rs4 )){
1043
+ const final_path = try QueryObjectName (hFile , out_buffer , null );
1082
1044
1083
- const volume_name = @ptrCast (* const FILE_NAME_INFORMATION , & volume_buffer [0 ]);
1045
+ if (fmt .volume_name == .Nt ){
1046
+ return final_path ; //we can directly return the slice we received
1047
+ }
1084
1048
1085
- switch (fmt .volume_name ) {
1086
- .Nt = > {
1049
+ //otherwise we need to parse the string for volume path for the .Dos logic below to work
1050
+ const expected_prefix = std .unicode .utf8ToUtf16LeStringLiteral ("\\ Device\\ " );
1051
+ std .debug .assert (std .mem .eql (u16 , expected_prefix , final_path [0.. expected_prefix .len ]));
1052
+ const index = std .mem .indexOfPos (u16 , final_path , expected_prefix .len , &[_ ]u16 {'\\ ' }) orelse unreachable ;
1053
+ volume_name_u16 = final_path [0.. index ];
1054
+ file_name_u16 = final_path [index .. ];
1055
+
1056
+ //fallthrough for fmt.volume_name != .Nt
1057
+ }else {
1058
+ // Get normalized path; doesn't include volume name though.
1059
+ try QueryInformationFile (hFile , .FileNormalizedNameInformation , path_buffer [0.. ]);
1060
+ const file_name = @ptrCast (* const FILE_NAME_INFORMATION , & path_buffer [0 ]);
1061
+ file_name_u16 = @ptrCast ([* ]const u16 , & file_name .FileName [0 ])[0 .. @divExact (file_name .FileNameLength , 2 )];
1062
+
1063
+ // Get NT volume name.
1064
+ try QueryInformationFile (hFile , .FileVolumeNameInformation , volume_buffer [0.. ]);
1065
+ const volume_name_info = @ptrCast (* const FILE_NAME_INFORMATION , & volume_buffer [0 ]);
1066
+ volume_name_u16 = @ptrCast ([* ]const u16 , & volume_name_info .FileName [0 ])[0 .. @divExact (volume_name_info .FileNameLength , 2 )];
1067
+
1068
+ if (fmt .volume_name == .Nt ){
1087
1069
// Nothing to do, we simply copy the bytes to the user-provided buffer.
1088
- const volume_name_u16 = @ptrCast ([* ]const u16 , & volume_name .FileName [0 ])[0 .. volume_name .FileNameLength / 2 ];
1089
1070
1090
1071
if (out_buffer .len < volume_name_u16 .len + file_name_u16 .len ) return error .NameTooLong ;
1091
1072
1092
1073
std .mem .copy (u16 , out_buffer [0.. ], volume_name_u16 );
1093
1074
std .mem .copy (u16 , out_buffer [volume_name_u16 .len .. ], file_name_u16 );
1094
1075
1095
1076
return out_buffer [0 .. volume_name_u16 .len + file_name_u16 .len ];
1096
- },
1077
+ }
1078
+ //fallthrough for fmt.volume_name != .Nt
1079
+ }
1080
+
1081
+ switch (fmt .volume_name ) {
1082
+ .Nt = > unreachable , //handled above
1097
1083
.Dos = > {
1098
1084
// Get DOS volume name. DOS volume names are actually symbolic link objects to the
1099
1085
// actual NT volume. For example:
@@ -1127,8 +1113,8 @@ pub fn GetFinalPathNameByHandle(
1127
1113
1128
1114
var input_struct = @ptrCast (* MOUNTMGR_MOUNT_POINT , & input_buf [0 ]);
1129
1115
input_struct .DeviceNameOffset = @sizeOf (MOUNTMGR_MOUNT_POINT );
1130
- input_struct .DeviceNameLength = @intCast (USHORT , volume_name . FileNameLength );
1131
- @memcpy (input_buf [@sizeOf (MOUNTMGR_MOUNT_POINT ).. ], @ptrCast ([* ]const u8 , & volume_name . FileName [ 0 ] ), volume_name . FileNameLength );
1116
+ input_struct .DeviceNameLength = @intCast (USHORT , volume_name_u16 . len * 2 );
1117
+ @memcpy (input_buf [@sizeOf (MOUNTMGR_MOUNT_POINT ).. ], @ptrCast ([* ]const u8 , volume_name_u16 . ptr ), volume_name_u16 . len * 2 );
1132
1118
1133
1119
DeviceIoControl (mgmt_handle , IOCTL_MOUNTMGR_QUERY_POINTS , input_buf [0.. ], output_buf [0.. ]) catch | err | switch (err ) {
1134
1120
error .AccessDenied = > unreachable ,
@@ -1185,42 +1171,36 @@ test "GetFinalPathNameByHandle" {
1185
1171
return ;
1186
1172
1187
1173
const S = struct {fn check (file : std.fs.File , buffer : anytype , fmt : std.os.windows.GetFinalPathNameByHandleFormat ) ! usize {
1188
- const real = try std .os .windows .GetFinalPathNameByHandle (file .handle , .{.volume_name = .Dos }, buffer [0.. ]);
1189
- //const utf8_real = try std.unicode.utf16leToUtf8Alloc(std.testing.allocator, real);
1190
- //defer std.testing.allocator.free(utf8_real);
1191
- //std.debug.print("real path: {}\n", .{utf8_real});
1192
- const offset = @divExact ((@ptrToInt (real .ptr ) - @ptrToInt (@ptrCast ([* ]u16 ,buffer ))), 2 );
1174
+ const real = try std .os .windows .GetFinalPathNameByHandle (file .handle , fmt , buffer [0.. ]);
1175
+ const offset = @divExact ((@ptrToInt (real .ptr ) - @ptrToInt (@ptrCast ([* ]u16 , buffer ))), 2 );
1193
1176
return real .len + offset ;
1194
1177
}};
1195
1178
1196
- const selfExeFile : std.fs.File = try std .fs .openSelfExe (.{}); //any file would do
1197
-
1179
+ const file : std.fs.File = try std .fs .openSelfExe (.{}); //any file would do
1180
+ defer file . close ();
1198
1181
var buffer = std .mem .zeroes ([1 << 10 ]u16 ); //make this big enough for the test runner exe path
1199
1182
1200
1183
//check with sufficient size
1201
- const dos_length = try S .check (selfExeFile , buffer [0.. ], .{.volume_name = .Dos });
1202
- const nt_length = try S .check (selfExeFile , buffer [0.. ], .{.volume_name = .Nt });
1184
+ const nt_length = try S .check (file , buffer [0.. ], .{.volume_name = .Nt });
1185
+ const dos_length = try S .check (file , buffer [0.. ], .{.volume_name = .Dos });
1203
1186
1204
1187
//check with size insufficient by more than 1 character: always errors
1205
- var err_count : u2 = 0 ;
1206
- _ = S .check (selfExeFile , buffer [0.. dos_length -1 ], .{.volume_name = .Dos })
1207
- catch | e | switch (e ) {error .NameTooLong = > {err_count += 1 ;}, else = > unreachable };
1208
- _ = S .check (selfExeFile , buffer [0.. nt_length -1 ], .{.volume_name = .Nt })
1209
- catch | e | switch (e ) {error .NameTooLong = > {err_count += 1 ;}, else = > unreachable };
1210
- std .testing .expectEqual (@as (u2 , 2 ), err_count );
1211
-
1212
- //check with size insufficient for null terminator: errors only on kernel32 path
1213
- err_count = 0 ;
1214
- _ = S .check (selfExeFile , buffer [0.. dos_length ], .{.volume_name = .Dos })
1215
- catch | e | switch (e ) {error .NameTooLong = > {err_count += 1 ;}, else = > unreachable };
1216
- _ = S .check (selfExeFile , buffer [0.. nt_length ], .{.volume_name = .Nt })
1217
- catch | e | switch (e ) {error .NameTooLong = > {err_count += 1 ;}, else = > unreachable };
1218
- const expect_no_errors = std .os .windows .runtimeVersionIsAtLeast (std .os .windows .WindowsVersion .win10_rs4 );
1219
- std .testing .expectEqual (if (expect_no_errors ) @as (u2 , 0 ) else @as (u2 , 2 ), err_count );
1188
+ std .testing .expect (null == S .check (file , buffer [0.. nt_length -1 ], .{.volume_name = .Nt })
1189
+ catch | e | switch (e ) {error .NameTooLong = > null , else = > unreachable });
1190
+ std .testing .expect (null == S .check (file , buffer [0.. dos_length -1 ], .{.volume_name = .Dos })
1191
+ catch | e | switch (e ) {error .NameTooLong = > null , else = > unreachable });
1192
+
1193
+ //check with size insufficient for null terminator: errors only on NtQueryObject path
1194
+ const queryObjectPath = ! std .os .windows .runtimeVersionIsAtLeast (std .os .windows .WindowsVersion .win10_rs4 );
1195
+ std .testing .expect (queryObjectPath == (null == S .check (file , buffer [0.. nt_length ], .{.volume_name = .Nt })
1196
+ catch | e | switch (e ) {error .NameTooLong = > null , else = > unreachable }));
1197
+ std .testing .expect (queryObjectPath == (null == S .check (file , buffer [0.. dos_length ], .{.volume_name = .Dos })
1198
+ catch | e | switch (e ) {error .NameTooLong = > null , else = > unreachable }));
1220
1199
1221
1200
//check with exactly-sufficient size
1222
- _ = try S .check (selfExeFile , buffer [0.. dos_length + 1 ], .{.volume_name = .Dos });
1223
- _ = try S .check (selfExeFile , buffer [0.. nt_length + 1 ], .{.volume_name = .Nt });
1201
+ const sufficient_dos_length = if (queryObjectPath ) nt_length + 1 else dos_length + 1 ;
1202
+ _ = try S .check (file , buffer [0.. nt_length + 1 ], .{.volume_name = .Nt });
1203
+ _ = try S .check (file , buffer [0.. sufficient_dos_length ], .{.volume_name = .Dos });
1224
1204
}
1225
1205
1226
1206
pub const QueryInformationFileError = error {Unexpected };
0 commit comments