Skip to content

Commit 7539116

Browse files
committed
std.os.windows.GetFinalPathNameByHandle: replace kernel32 by ntdll call
Removes the call to kernel32.GetFinalPathNameByHandleW in favor of NtQueryObject, which means we can reuse the other codepath's logic for DOS naming. Also cleans up the test case.
1 parent f2de663 commit 7539116

File tree

1 file changed

+56
-76
lines changed

1 file changed

+56
-76
lines changed

lib/std/os/windows.zig

Lines changed: 56 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,67 +1033,53 @@ pub fn GetFinalPathNameByHandle(
10331033
out_buffer: []u16,
10341034
) GetFinalPathNameByHandleError![]u16 {
10351035

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.
10731036
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.
10771037
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..]);
10791038

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);
10821044

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+
}
10841048

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){
10871069
// 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];
10891070

10901071
if (out_buffer.len < volume_name_u16.len + file_name_u16.len) return error.NameTooLong;
10911072

10921073
std.mem.copy(u16, out_buffer[0..], volume_name_u16);
10931074
std.mem.copy(u16, out_buffer[volume_name_u16.len..], file_name_u16);
10941075

10951076
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
10971083
.Dos => {
10981084
// Get DOS volume name. DOS volume names are actually symbolic link objects to the
10991085
// actual NT volume. For example:
@@ -1127,8 +1113,8 @@ pub fn GetFinalPathNameByHandle(
11271113

11281114
var input_struct = @ptrCast(*MOUNTMGR_MOUNT_POINT, &input_buf[0]);
11291115
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);
11321118

11331119
DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, input_buf[0..], output_buf[0..]) catch |err| switch (err) {
11341120
error.AccessDenied => unreachable,
@@ -1185,42 +1171,36 @@ test "GetFinalPathNameByHandle" {
11851171
return;
11861172

11871173
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);
11931176
return real.len + offset;
11941177
}};
11951178

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();
11981181
var buffer = std.mem.zeroes([1<<10]u16); //make this big enough for the test runner exe path
11991182

12001183
//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});
12031186

12041187
//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}));
12201199

12211200
//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});
12241204
}
12251205

12261206
pub const QueryInformationFileError = error{Unexpected};

0 commit comments

Comments
 (0)