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