Skip to content

Commit 0217461

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 52ddc15 commit 0217461

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
@@ -1030,67 +1030,53 @@ pub fn GetFinalPathNameByHandle(
10301030
out_buffer: []u16,
10311031
) GetFinalPathNameByHandleError![]u16 {
10321032

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.
10701033
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.
10741034
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..]);
10761035

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

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

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){
10841066
// 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];
10861067

10871068
if (out_buffer.len < volume_name_u16.len + file_name_u16.len) return error.NameTooLong;
10881069

10891070
std.mem.copy(u16, out_buffer[0..], volume_name_u16);
10901071
std.mem.copy(u16, out_buffer[volume_name_u16.len..], file_name_u16);
10911072

10921073
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
10941080
.Dos => {
10951081
// Get DOS volume name. DOS volume names are actually symbolic link objects to the
10961082
// actual NT volume. For example:
@@ -1124,8 +1110,8 @@ pub fn GetFinalPathNameByHandle(
11241110

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

11301116
DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, input_buf[0..], output_buf[0..]) catch |err| switch (err) {
11311117
error.AccessDenied => unreachable,
@@ -1179,42 +1165,36 @@ pub fn GetFinalPathNameByHandle(
11791165

11801166
test "GetFinalPathNameByHandle" {
11811167
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);
11871170
return real.len + offset;
11881171
}};
11891172

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

11941177
//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});
11971180

11981181
//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}));
12141193

12151194
//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});
12181198
}
12191199

12201200
pub const QueryInformationFileError = error{Unexpected};

0 commit comments

Comments
 (0)