authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-08-10 20:09:45-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-08-10 20:09:45-04:00
log6325d6a48628bff89b2e243e0a1b6eedf39b0ec6
tree54f6965f18db1a1f55e318238d15f6095bff7c23
parentbc176fb7390b4d03b786bbc567448836140a67fc
parent901bf0a2e2821caa1087b03ed308700ec9135c87
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #5993 from kubkon/getpathnamebyhandle

Implement std.os.windows.GetPathNameByHandle using NT routines only

3 files changed, 163 insertions(+), 25 deletions(-)

lib/std/os.zig+2-8
...@@ -4060,7 +4060,6 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP...@@ -4060,7 +4060,6 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
4060}4060}
40614061
4062/// Same as `realpath` except `pathname` is UTF16LE-encoded.4062/// Same as `realpath` except `pathname` is UTF16LE-encoded.
4063/// TODO use ntdll to emulate `GetFinalPathNameByHandleW` routine
4064pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {4063pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
4065 const w = windows;4064 const w = windows;
40664065
...@@ -4095,15 +4094,10 @@ pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPat...@@ -4095,15 +4094,10 @@ pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPat
4095 defer w.CloseHandle(h_file);4094 defer w.CloseHandle(h_file);
40964095
4097 var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined;4096 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);4097 const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, wide_buf[0..]);
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;
41044098
4105 // Trust that Windows gives us valid UTF-16LE.4099 // 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;
4107 return out_buffer[0..end_index];4101 return out_buffer[0..end_index];
4108}4102}
41094103
lib/std/os/windows.zig+143-17
...@@ -897,30 +897,156 @@ pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 {...@@ -897,30 +897,156 @@ pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 {
897}897}
898898
899pub const GetFinalPathNameByHandleError = error{899pub const GetFinalPathNameByHandleError = error{
900 BadPathName,
900 FileNotFound,901 FileNotFound,
901 SystemResources,
902 NameTooLong,902 NameTooLong,
903 Unexpected,903 Unexpected,
904};904};
905905
906pub fn GetFinalPathNameByHandleW(906/// Specifies how to format volume path in the result of `GetFinalPathNameByHandle`.
907/// Defaults to DOS volume names.
908pub 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.
922pub fn GetFinalPathNameByHandle(
907 hFile: HANDLE,923 hFile: HANDLE,
908 buf_ptr: [*]u16,924 fmt: GetFinalPathNameByHandleFormat,
909 buf_len: DWORD,925 out_buffer: []u16,
910 flags: DWORD,926) GetFinalPathNameByHandleError![]u16 {
911) GetFinalPathNameByHandleError![:0]u16 {927 // Get normalized path; doesn't include volume name though.
912 const rc = kernel32.GetFinalPathNameByHandleW(hFile, buf_ptr, buf_len, flags);928 var path_buffer: [@sizeOf(FILE_NAME_INFORMATION) + PATH_MAX_WIDE * 2]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined;
913 if (rc == 0) {929 try QueryInformationFile(hFile, .FileNormalizedNameInformation, path_buffer[0..]);
914 switch (kernel32.GetLastError()) {930
915 .FILE_NOT_FOUND => return error.FileNotFound,931 // Get NT volume name.
916 .PATH_NOT_FOUND => return error.FileNotFound,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
917 .NOT_ENOUGH_MEMORY => return error.SystemResources,933 try QueryInformationFile(hFile, .FileVolumeNameInformation, volume_buffer[0..]);
918 .FILENAME_EXCED_RANGE => return error.NameTooLong,934
919 .INVALID_PARAMETER => unreachable,935 const file_name = @ptrCast(*const FILE_NAME_INFORMATION, &path_buffer[0]);
920 else => |err| return unexpectedError(err),936 const file_name_u16 = @ptrCast([*]const u16, &file_name.FileName[0])[0 .. file_name.FileNameLength / 2];
921 }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
1035pub const QueryInformationFileError = error{Unexpected};
1036
1037pub 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),
922 }1049 }
923 return buf_ptr[0..rc :0];
924}1050}
9251051
926pub const GetFileSizeError = error{Unexpected};1052pub const GetFileSizeError = error{Unexpected};
lib/std/os/windows/bits.zig+18
...@@ -1573,3 +1573,21 @@ pub const SYMLINK_FLAG_RELATIVE: ULONG = 0x1;...@@ -1573,3 +1573,21 @@ pub const SYMLINK_FLAG_RELATIVE: ULONG = 0x1;
15731573
1574pub const SYMBOLIC_LINK_FLAG_DIRECTORY: DWORD = 0x1;1574pub const SYMBOLIC_LINK_FLAG_DIRECTORY: DWORD = 0x1;
1575pub const SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE: DWORD = 0x2;1575pub const SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE: DWORD = 0x2;
1576
1577pub 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};
1588pub const MOUNTMGR_MOUNT_POINTS = extern struct {
1589 Size: ULONG,
1590 NumberOfMountPoints: ULONG,
1591 MountPoints: [1]MOUNTMGR_MOUNT_POINT,
1592};
1593pub const IOCTL_MOUNTMGR_QUERY_POINTS: ULONG = 0x6d0008;