| ... | ... | @@ -897,30 +897,156 @@ pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 { |
| 897 | 897 | } |
| 898 | 898 | |
| 899 | 899 | pub const GetFinalPathNameByHandleError = error{ |
| 900 | BadPathName, |
| 900 | 901 | FileNotFound, |
| 901 | | SystemResources, |
| 902 | 902 | NameTooLong, |
| 903 | 903 | Unexpected, |
| 904 | 904 | }; |
| 905 | 905 | |
| 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( |
| 907 | 923 | 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), |
| 922 | 1049 | } |
| 923 | | return buf_ptr[0..rc :0]; |
| 924 | 1050 | } |
| 925 | 1051 | |
| 926 | 1052 | pub const GetFileSizeError = error{Unexpected}; |