Skip to content

Commit 6325d6a

Browse files
authored
Merge pull request #5993 from kubkon/getpathnamebyhandle
Implement std.os.windows.GetPathNameByHandle using NT routines only
2 parents bc176fb + 901bf0a commit 6325d6a

File tree

3 files changed

+163
-25
lines changed

3 files changed

+163
-25
lines changed

lib/std/os.zig

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4060,7 +4060,6 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
40604060
}
40614061

40624062
/// Same as `realpath` except `pathname` is UTF16LE-encoded.
4063-
/// TODO use ntdll to emulate `GetFinalPathNameByHandleW` routine
40644063
pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
40654064
const w = windows;
40664065

@@ -4095,15 +4094,10 @@ pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPat
40954094
defer w.CloseHandle(h_file);
40964095

40974096
var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined;
4098-
const wide_slice = try w.GetFinalPathNameByHandleW(h_file, &wide_buf, wide_buf.len, w.VOLUME_NAME_DOS);
4099-
4100-
// Windows returns \\?\ prepended to the path.
4101-
// We strip it to make this function consistent across platforms.
4102-
const prefix = [_]u16{ '\\', '\\', '?', '\\' };
4103-
const start_index = if (mem.startsWith(u16, wide_slice, &prefix)) prefix.len else 0;
4097+
const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, wide_buf[0..]);
41044098

41054099
// Trust that Windows gives us valid UTF-16LE.
4106-
const end_index = std.unicode.utf16leToUtf8(out_buffer, wide_slice[start_index..]) catch unreachable;
4100+
const end_index = std.unicode.utf16leToUtf8(out_buffer, wide_slice) catch unreachable;
41074101
return out_buffer[0..end_index];
41084102
}
41094103

lib/std/os/windows.zig

Lines changed: 143 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -897,30 +897,156 @@ pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 {
897897
}
898898

899899
pub const GetFinalPathNameByHandleError = error{
900+
BadPathName,
900901
FileNotFound,
901-
SystemResources,
902902
NameTooLong,
903903
Unexpected,
904904
};
905905

906-
pub fn GetFinalPathNameByHandleW(
906+
/// Specifies how to format volume path in the result of `GetFinalPathNameByHandle`.
907+
/// Defaults to DOS volume names.
908+
pub const GetFinalPathNameByHandleFormat = struct {
909+
volume_name: enum {
910+
/// Format as DOS volume name
911+
Dos,
912+
/// Format as NT volume name
913+
Nt,
914+
} = .Dos,
915+
};
916+
917+
/// Returns canonical (normalized) path of handle.
918+
/// Use `GetFinalPathNameByHandleFormat` to specify whether the path is meant to include
919+
/// NT or DOS volume name (e.g., `\Device\HarddiskVolume0\foo.txt` versus `C:\foo.txt`).
920+
/// If DOS volume name format is selected, note that this function does *not* prepend
921+
/// `\\?\` prefix to the resultant path.
922+
pub fn GetFinalPathNameByHandle(
907923
hFile: HANDLE,
908-
buf_ptr: [*]u16,
909-
buf_len: DWORD,
910-
flags: DWORD,
911-
) GetFinalPathNameByHandleError![:0]u16 {
912-
const rc = kernel32.GetFinalPathNameByHandleW(hFile, buf_ptr, buf_len, flags);
913-
if (rc == 0) {
914-
switch (kernel32.GetLastError()) {
915-
.FILE_NOT_FOUND => return error.FileNotFound,
916-
.PATH_NOT_FOUND => return error.FileNotFound,
917-
.NOT_ENOUGH_MEMORY => return error.SystemResources,
918-
.FILENAME_EXCED_RANGE => return error.NameTooLong,
919-
.INVALID_PARAMETER => unreachable,
920-
else => |err| return unexpectedError(err),
921-
}
924+
fmt: GetFinalPathNameByHandleFormat,
925+
out_buffer: []u16,
926+
) GetFinalPathNameByHandleError![]u16 {
927+
// Get normalized path; doesn't include volume name though.
928+
var path_buffer: [@sizeOf(FILE_NAME_INFORMATION) + PATH_MAX_WIDE * 2]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined;
929+
try QueryInformationFile(hFile, .FileNormalizedNameInformation, path_buffer[0..]);
930+
931+
// Get NT volume name.
932+
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
933+
try QueryInformationFile(hFile, .FileVolumeNameInformation, volume_buffer[0..]);
934+
935+
const file_name = @ptrCast(*const FILE_NAME_INFORMATION, &path_buffer[0]);
936+
const file_name_u16 = @ptrCast([*]const u16, &file_name.FileName[0])[0 .. file_name.FileNameLength / 2];
937+
938+
const volume_name = @ptrCast(*const FILE_NAME_INFORMATION, &volume_buffer[0]);
939+
940+
switch (fmt.volume_name) {
941+
.Nt => {
942+
// Nothing to do, we simply copy the bytes to the user-provided buffer.
943+
const volume_name_u16 = @ptrCast([*]const u16, &volume_name.FileName[0])[0 .. volume_name.FileNameLength / 2];
944+
945+
if (out_buffer.len < volume_name_u16.len + file_name_u16.len) return error.NameTooLong;
946+
947+
std.mem.copy(u16, out_buffer[0..], volume_name_u16);
948+
std.mem.copy(u16, out_buffer[volume_name_u16.len..], file_name_u16);
949+
950+
return out_buffer[0 .. volume_name_u16.len + file_name_u16.len];
951+
},
952+
.Dos => {
953+
// Get DOS volume name. DOS volume names are actually symbolic link objects to the
954+
// actual NT volume. For example:
955+
// (NT) \Device\HarddiskVolume4 => (DOS) \DosDevices\C: == (DOS) C:
956+
const MIN_SIZE = @sizeOf(MOUNTMGR_MOUNT_POINT) + MAX_PATH;
957+
// We initialize the input buffer to all zeros for convenience since
958+
// `DeviceIoControl` with `IOCTL_MOUNTMGR_QUERY_POINTS` expects this.
959+
var input_buf: [MIN_SIZE]u8 align(@alignOf(MOUNTMGR_MOUNT_POINT)) = [_]u8{0} ** MIN_SIZE;
960+
var output_buf: [MIN_SIZE * 4]u8 align(@alignOf(MOUNTMGR_MOUNT_POINTS)) = undefined;
961+
962+
// This surprising path is a filesystem path to the mount manager on Windows.
963+
// Source: https://stackoverflow.com/questions/3012828/using-ioctl-mountmgr-query-points
964+
const mgmt_path = "\\MountPointManager";
965+
const mgmt_path_u16 = sliceToPrefixedFileW(mgmt_path) catch unreachable;
966+
const mgmt_handle = OpenFile(mgmt_path_u16.span(), .{
967+
.access_mask = SYNCHRONIZE,
968+
.share_access = FILE_SHARE_READ | FILE_SHARE_WRITE,
969+
.creation = FILE_OPEN,
970+
.io_mode = .blocking,
971+
}) catch |err| switch (err) {
972+
error.IsDir => unreachable,
973+
error.NotDir => unreachable,
974+
error.NoDevice => unreachable,
975+
error.AccessDenied => unreachable,
976+
error.PipeBusy => unreachable,
977+
error.PathAlreadyExists => unreachable,
978+
error.WouldBlock => unreachable,
979+
else => |e| return e,
980+
};
981+
defer CloseHandle(mgmt_handle);
982+
983+
var input_struct = @ptrCast(*MOUNTMGR_MOUNT_POINT, &input_buf[0]);
984+
input_struct.DeviceNameOffset = @sizeOf(MOUNTMGR_MOUNT_POINT);
985+
input_struct.DeviceNameLength = @intCast(USHORT, volume_name.FileNameLength);
986+
@memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..], @ptrCast([*]const u8, &volume_name.FileName[0]), volume_name.FileNameLength);
987+
988+
try DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, input_buf[0..], output_buf[0..]);
989+
const mount_points_struct = @ptrCast(*const MOUNTMGR_MOUNT_POINTS, &output_buf[0]);
990+
991+
const mount_points = @ptrCast(
992+
[*]const MOUNTMGR_MOUNT_POINT,
993+
&mount_points_struct.MountPoints[0],
994+
)[0..mount_points_struct.NumberOfMountPoints];
995+
996+
var found: bool = false;
997+
for (mount_points) |mount_point| {
998+
const symlink = @ptrCast(
999+
[*]const u16,
1000+
@alignCast(@alignOf(u16), &output_buf[mount_point.SymbolicLinkNameOffset]),
1001+
)[0 .. mount_point.SymbolicLinkNameLength / 2];
1002+
1003+
// Look for `\DosDevices\` prefix. We don't really care if there are more than one symlinks
1004+
// with traditional DOS drive letters, so pick the first one available.
1005+
const prefix_u8 = "\\DosDevices\\";
1006+
var prefix_buf_u16: [prefix_u8.len]u16 = undefined;
1007+
const prefix_len_u16 = std.unicode.utf8ToUtf16Le(prefix_buf_u16[0..], prefix_u8[0..]) catch unreachable;
1008+
const prefix = prefix_buf_u16[0..prefix_len_u16];
1009+
1010+
if (std.mem.startsWith(u16, symlink, prefix)) {
1011+
const drive_letter = symlink[prefix.len..];
1012+
1013+
if (out_buffer.len < drive_letter.len + file_name_u16.len) return error.NameTooLong;
1014+
1015+
std.mem.copy(u16, out_buffer[0..], drive_letter);
1016+
std.mem.copy(u16, out_buffer[drive_letter.len..], file_name_u16);
1017+
const total_len = drive_letter.len + file_name_u16.len;
1018+
1019+
// Validate that DOS does not contain any spurious nul bytes.
1020+
if (std.mem.indexOfScalar(u16, out_buffer[0..total_len], 0)) |_| {
1021+
return error.BadPathName;
1022+
}
1023+
1024+
return out_buffer[0..total_len];
1025+
}
1026+
}
1027+
1028+
// If we've ended up here, then something went wrong/is corrupted in the OS,
1029+
// so error out!
1030+
return error.FileNotFound;
1031+
},
1032+
}
1033+
}
1034+
1035+
pub const QueryInformationFileError = error{Unexpected};
1036+
1037+
pub fn QueryInformationFile(
1038+
handle: HANDLE,
1039+
info_class: FILE_INFORMATION_CLASS,
1040+
out_buffer: []u8,
1041+
) QueryInformationFileError!void {
1042+
var io: IO_STATUS_BLOCK = undefined;
1043+
const len_bytes = std.math.cast(u32, out_buffer.len) catch unreachable;
1044+
const rc = ntdll.NtQueryInformationFile(handle, &io, out_buffer.ptr, len_bytes, info_class);
1045+
switch (rc) {
1046+
.SUCCESS => {},
1047+
.INVALID_PARAMETER => unreachable,
1048+
else => return unexpectedStatus(rc),
9221049
}
923-
return buf_ptr[0..rc :0];
9241050
}
9251051

9261052
pub const GetFileSizeError = error{Unexpected};

lib/std/os/windows/bits.zig

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1573,3 +1573,21 @@ pub const SYMLINK_FLAG_RELATIVE: ULONG = 0x1;
15731573

15741574
pub const SYMBOLIC_LINK_FLAG_DIRECTORY: DWORD = 0x1;
15751575
pub const SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE: DWORD = 0x2;
1576+
1577+
pub const MOUNTMGR_MOUNT_POINT = extern struct {
1578+
SymbolicLinkNameOffset: ULONG,
1579+
SymbolicLinkNameLength: USHORT,
1580+
Reserved1: USHORT,
1581+
UniqueIdOffset: ULONG,
1582+
UniqueIdLength: USHORT,
1583+
Reserved2: USHORT,
1584+
DeviceNameOffset: ULONG,
1585+
DeviceNameLength: USHORT,
1586+
Reserved3: USHORT,
1587+
};
1588+
pub const MOUNTMGR_MOUNT_POINTS = extern struct {
1589+
Size: ULONG,
1590+
NumberOfMountPoints: ULONG,
1591+
MountPoints: [1]MOUNTMGR_MOUNT_POINT,
1592+
};
1593+
pub const IOCTL_MOUNTMGR_QUERY_POINTS: ULONG = 0x6d0008;

0 commit comments

Comments
 (0)