diff options
| author | Jakub Konka <kubkon@jakubkonka.com> | 2020-08-06 18:51:17 +0200 |
|---|---|---|
| committer | Jakub Konka <kubkon@jakubkonka.com> | 2020-08-07 11:33:05 +0200 |
| commit | bdda8fa7a818c90803fb64f9784040bf3b9f0a5e (patch) | |
| tree | a4df7ab11a30dd02d1593d74b6576f53ebbb1517 /lib | |
| parent | e8abfef2aa8579121fbd13b8df84e8f68b533fa4 (diff) | |
| download | zig-bdda8fa7a818c90803fb64f9784040bf3b9f0a5e.tar.gz zig-bdda8fa7a818c90803fb64f9784040bf3b9f0a5e.zip | |
Redo GetFinalPathNameByHandle using DeviceIoControl
This commit reimagines `std.os.windows.GetFinalPathNameByHandle`
using `DeviceIoControl` to query the OS mount manager for the DOS
(symlink) paths for the given NT volume name. In particular,
it uses `IOCTL_MOUNTMGR_QUERY_POINTS` ioctl opcode to query the
manager for the available moount points.
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/std/os.zig | 2 | ||||
| -rw-r--r-- | lib/std/os/windows.zig | 243 | ||||
| -rw-r--r-- | lib/std/os/windows/bits.zig | 38 | ||||
| -rw-r--r-- | lib/std/os/windows/ntdll.zig | 34 |
4 files changed, 141 insertions, 176 deletions
diff --git a/lib/std/os.zig b/lib/std/os.zig index 88ce77dc56..04c2340cad 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -4094,7 +4094,7 @@ pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPat defer w.CloseHandle(h_file); var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined; - const wide_slice = try w.GetFinalPathNameByHandle(h_file, wide_buf[0..]); + const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, wide_buf[0..]); // Trust that Windows gives us valid UTF-16LE. const end_index = std.unicode.utf16leToUtf8(out_buffer, wide_slice) catch unreachable; diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 03dbcdbd02..c45f1c1fd8 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -902,135 +902,142 @@ pub const GetFinalPathNameByHandleError = error{ Unexpected, }; -/// Returns canonical (normalized) path of handle. The output path assumes -/// Win32 namespace, however, '\\?\' prefix is *not* prepended to the result. -/// TODO support other namespaces/volume names. -pub fn GetFinalPathNameByHandle(hFile: HANDLE, out_buffer: []u16) GetFinalPathNameByHandleError![]u16 { - // The implementation is based on implementation found in Wine sources, however, - // we make the following tweaks. First of all, if `NtQueryInformationFile` supports - // `FILE_INFORMATION_CLASS.FileNormalizedNameInformation` and `FileVolumeNameInformation`, - // we use those two calls to generate a valid Win32/DOS path. Otherwise, we fallback to - // more widely supported `NtQueryObject` generic routine. - // Wine source: https://source.winehq.com/git/wine.git/blob/HEAD:/dlls/kernelbase/file.c#l1708 - var buffer: [PATH_MAX_WIDE]u16 = undefined; - const object_path = blk: { - var path_buffer: [PATH_MAX_WIDE * 2]u8 = undefined; - var io: IO_STATUS_BLOCK = undefined; - var rc = ntdll.NtQueryInformationFile(hFile, &io, &path_buffer, path_buffer.len, FILE_INFORMATION_CLASS.FileNormalizedNameInformation); - switch (rc) { - .SUCCESS => { - var nt_volume_buffer: [MAX_PATH]u8 = undefined; - rc = ntdll.NtQueryInformationFile(hFile, &io, &nt_volume_buffer, nt_volume_buffer.len, FILE_INFORMATION_CLASS.FileVolumeNameInformation); - switch (rc) { - .SUCCESS => { - const file_name = @ptrCast(*const FILE_NAME_INFORMATION, @alignCast(@alignOf(FILE_NAME_INFORMATION), &path_buffer[0])); - const file_name_u16 = @ptrCast([*]const u16, &file_name.FileName[0])[0..file_name.FileNameLength / 2]; - - if (file_name_u16.len > PATH_MAX_WIDE) return error.NameTooLong; - - const volume_name = @ptrCast(*const FILE_NAME_INFORMATION, @alignCast(@alignOf(FILE_NAME_INFORMATION), &nt_volume_buffer[0])); - const volume_name_u16 = @ptrCast([*]const u16, &volume_name.FileName[0])[0..volume_name.FileNameLength / 2]; - - std.mem.copy(u16, buffer[0..], volume_name_u16); - std.mem.copy(u16, buffer[volume_name_u16.len..], file_name_u16); - - break :blk buffer[0..volume_name_u16.len + file_name_u16.len]; - }, - .INVALID_PARAMETER => {}, // fall through - else => return unexpectedStatus(rc), - } - }, - .INVALID_PARAMETER => {}, // fall through - else => return unexpectedStatus(rc), - } +/// Specifies how to format volume path in the result of `GetFinalPathNameByHandle`. +/// Defaults to DOS volume names. +pub const GetFinalPathNameByHandleFormat = struct { + volume_name: enum { + /// Format as DOS volume name + Dos, + /// Format as NT volume name + Nt, + } = .Dos, +}; - var dummy: ULONG = undefined; - rc = ntdll.NtQueryObject(hFile, OBJECT_INFORMATION_CLASS.ObjectNameInformation, &path_buffer, path_buffer.len, &dummy); - switch (rc) { - .SUCCESS => {}, - else => return unexpectedStatus(rc), - } +/// Returns canonical (normalized) path of handle. +/// Use `GetFinalPathNameByHandleFormat` to specify whether the path is meant to include +/// NT or DOS volume name (e.g., `\Device\HarddiskVolume0\foo.txt` versus `C:\foo.txt`). +/// If DOS volume name format is selected, note that this function does *not* prepend +/// `\\?\` prefix to the resultant path. +pub fn GetFinalPathNameByHandle( + hFile: HANDLE, + fmt: GetFinalPathNameByHandleFormat, + out_buffer: []u16, +) GetFinalPathNameByHandleError![]u16 { + // Get normalized path; doesn't include volume name though. + var path_buffer: [@sizeOf(FILE_NAME_INFORMATION) + PATH_MAX_WIDE * 2 + 2]u8 = undefined; + var io: IO_STATUS_BLOCK = undefined; + var rc = ntdll.NtQueryInformationFile(hFile, &io, &path_buffer, path_buffer.len, FILE_INFORMATION_CLASS.FileNormalizedNameInformation); + switch (rc) { + .SUCCESS => {}, + .INVALID_PARAMETER => unreachable, + else => return unexpectedStatus(rc), + } - const object_name = @ptrCast(*const OBJECT_NAME_INFORMATION, @alignCast(@alignOf(OBJECT_NAME_INFORMATION), &buffer)); + // Get NT volume name. + var volume_buffer: [MAX_PATH]u8 = undefined; // MAX_PATH bytes should be enough since it's Windows-defined name + rc = ntdll.NtQueryInformationFile(hFile, &io, &volume_buffer, volume_buffer.len, FILE_INFORMATION_CLASS.FileVolumeNameInformation); + switch (rc) { + .SUCCESS => {}, + .INVALID_PARAMETER => unreachable, + else => return unexpectedStatus(rc), + } - // Apparently, `NtQueryObject` has a bug when signalling an error condition. - // To check for error, i.e., FileNotFound, we check if the result Buffer is non null - // and Length is greater than zero. - // Source: https://stackoverflow.com/questions/65170/how-to-get-name-associated-with-open-handle - if (object_name.Name.Length == 0) return error.FileNotFound; + const file_name = @ptrCast(*const FILE_NAME_INFORMATION, @alignCast(@alignOf(FILE_NAME_INFORMATION), &path_buffer[0])); + const file_name_u16 = @ptrCast([*]const u16, &file_name.FileName[0])[0 .. file_name.FileNameLength / 2]; - break :blk @as([*]const u16, object_name.Name.Buffer)[0..object_name.Name.Length / 2]; - }; + const volume_name = @ptrCast(*const FILE_NAME_INFORMATION, @alignCast(@alignOf(FILE_NAME_INFORMATION), &volume_buffer[0])); - // By now, we got a a fully-qualified NT path, which we need to translate - // into a Win32/DOS path, for instance: - // \Device\HarddiskVolume4\foo => C:\foo - // - // NOTE: - // I couldn't figure out a better way of doing this unfortunately... - // This snippet below is in part based around `QueryDosDeviceW` implementation - // found in Wine. The trick with `NtQueryDirectoryObject` for some reason - // only lists `\DosDevices\Global` as the only SymblinkObject available, which - // means it's not possible to query the kernel for all available DOS volume name - // symlinks. - // TODO investigate! - // Wine source: https://source.winehq.com/git/wine.git/blob/HEAD:/dlls/kernelbase/volume.c#l1009 - const dos_drive_letters = &[_]u16{ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; - var query_path = [_]u16{ '\\', 'D', 'o', 's', 'D', 'e', 'v', 'i', 'c', 'e', 's', '\\', 'C', ':' }; - for (dos_drive_letters) |drive_letter| { - const drive = &[_]u16{ drive_letter, ':' }; - std.mem.copy(u16, query_path[query_path.len - 2..], drive[0..]); - - var sym_handle: HANDLE = undefined; - const len_bytes = @intCast(u16, query_path.len) * 2; - var nt_name = UNICODE_STRING{ - .Length = len_bytes, - .MaximumLength = len_bytes, - .Buffer = @intToPtr([*]u16, @ptrToInt(&query_path)), - }; - var attr = OBJECT_ATTRIBUTES{ - .Length = @sizeOf(OBJECT_ATTRIBUTES), - .RootDirectory = null, - .Attributes = 0, - .ObjectName = &nt_name, - .SecurityDescriptor = null, - .SecurityQualityOfService = null, - }; - var rc = ntdll.NtOpenSymbolicLinkObject(&sym_handle, SYMBOLIC_LINK_QUERY, attr); - switch (rc) { - .SUCCESS => {}, - .OBJECT_NAME_NOT_FOUND => continue, - else => return unexpectedStatus(rc), - } + switch (fmt.volume_name) { + .Nt => { + // Nothing to do, we simply copy the bytes to the user-provided buffer. + const volume_name_u16 = @ptrCast([*]const u16, &volume_name.FileName[0])[0 .. volume_name.FileNameLength / 2]; - var link_buffer: [MAX_PATH]u8 = undefined; - var link = UNICODE_STRING{ - .Length = 0, - .MaximumLength = MAX_PATH, - .Buffer = @intToPtr([*]u16, @ptrToInt(&link_buffer[0])), - }; - rc = ntdll.NtQuerySymbolicLinkObject(sym_handle, &link, null); - CloseHandle(sym_handle); - switch (rc) { - .SUCCESS => {}, - else => return unexpectedStatus(rc), - } + if (out_buffer.len < volume_name_u16.len + file_name_u16.len) return error.NameTooLong; + + std.mem.copy(u16, out_buffer[0..], volume_name_u16); + std.mem.copy(u16, out_buffer[volume_name_u16.len..], file_name_u16); + + return out_buffer[0 .. volume_name_u16.len + file_name_u16.len]; + }, + .Dos => { + // Get DOS volume name. DOS volume names are actually symbolic link objects to the + // actual NT volume. For example: + // (NT) \Device\HarddiskVolume4 => (DOS) \DosDevices\C: == (DOS) C: + const MIN_SIZE = @sizeOf(MOUNTMGR_MOUNT_POINT) + MAX_PATH; + // We initialize the input buffer to all zeros for convenience since + // `DeviceIoControl` with `IOCTL_MOUNTMGR_QUERY_POINTS` expects this. + var input_buf = [_]u8{0} ** MIN_SIZE; + var output_buf: [MIN_SIZE * 4]u8 = undefined; + + // This surprising path is a filesystem path to the mount manager on Windows. + // Source: https://stackoverflow.com/questions/3012828/using-ioctl-mountmgr-query-points + const mgmt_path = "\\MountPointManager"; + const mgmt_path_u16 = sliceToPrefixedFileW(mgmt_path) catch unreachable; + const mgmt_handle = OpenFile(mgmt_path_u16.span(), .{ + .access_mask = SYNCHRONIZE, + .share_access = FILE_SHARE_READ | FILE_SHARE_WRITE, + .creation = FILE_OPEN, + .io_mode = .blocking, + }) catch |err| switch (err) { + error.IsDir => unreachable, + error.NotDir => unreachable, + error.NoDevice => unreachable, + error.AccessDenied => unreachable, + error.PipeBusy => unreachable, + error.PathAlreadyExists => unreachable, + error.WouldBlock => unreachable, + else => |e| return e, + }; + defer CloseHandle(mgmt_handle); + + var input_struct = @ptrCast(*MOUNTMGR_MOUNT_POINT, @alignCast(@alignOf(MOUNTMGR_MOUNT_POINT), &input_buf[0])); + input_struct.DeviceNameOffset = @sizeOf(MOUNTMGR_MOUNT_POINT); + input_struct.DeviceNameLength = @intCast(USHORT, volume_name.FileNameLength); + @memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..], @ptrCast([*]const u8, &volume_name.FileName[0]), volume_name.FileNameLength); + + try DeviceIoControl( + mgmt_handle, + IOCTL_MOUNTMGR_QUERY_POINTS, + input_buf[0 .. @sizeOf(MOUNTMGR_MOUNT_POINT) + volume_name.FileNameLength], + output_buf[0..], + ); + const mount_points_struct = @ptrCast(*const MOUNTMGR_MOUNT_POINTS, @alignCast(@alignOf(MOUNTMGR_MOUNT_POINTS), &output_buf[0])); - const link_path = @as([*]const u16, link.Buffer)[0..link.Length / 2]; - const idx = std.mem.indexOf(u16, object_path, link_path) orelse continue; + const mount_points = @ptrCast( + [*]const MOUNTMGR_MOUNT_POINT, + @alignCast(@alignOf(MOUNTMGR_MOUNT_POINT), &mount_points_struct.MountPoints[0]), + )[0..mount_points_struct.NumberOfMountPoints]; - // TODO is this the most appropriate error here? - if (out_buffer.len < drive.len + object_path.len) return error.NameTooLong; + var found: bool = false; + for (mount_points) |mount_point| { + const symlink = @ptrCast( + [*]const u16, + @alignCast(@alignOf(u16), &output_buf[mount_point.SymbolicLinkNameOffset]), + )[0 .. mount_point.SymbolicLinkNameLength / 2]; - std.mem.copy(u16, out_buffer[0..], drive[0..]); - std.mem.copy(u16, out_buffer[2..], object_path[link_path.len..]); + // Look for `\DosDevices\` prefix. We don't really care if there are more than one symlinks + // with traditional DOS drive letters, so pick the first one available. + const prefix = &[_]u16{ '\\', 'D', 'o', 's', 'D', 'e', 'v', 'i', 'c', 'e', 's', '\\' }; - return out_buffer[0..object_path.len - link_path.len + 2]; - } + if (std.mem.indexOf(u16, symlink, prefix)) |idx| { + if (idx != 0) continue; + + const drive_letter = symlink[prefix.len..]; + + if (out_buffer.len < drive_letter.len + file_name_u16.len) return error.NameTooLong; + + std.mem.copy(u16, out_buffer[0..], drive_letter); + std.mem.copy(u16, out_buffer[drive_letter.len..], file_name_u16); - // If we're here, that means there was no match so we panic! - // TODO should we actually panic here or return an error instead? - unreachable; + return out_buffer[0 .. drive_letter.len + file_name_u16.len]; + } + } + + // If we've ended up here, then something went wrong/is corrupted in the OS, + // so error out! + return error.FileNotFound; + }, + } } pub const GetFileSizeError = error{Unexpected}; diff --git a/lib/std/os/windows/bits.zig b/lib/std/os/windows/bits.zig index 74fe7040ab..516af6d4fc 100644 --- a/lib/std/os/windows/bits.zig +++ b/lib/std/os/windows/bits.zig @@ -1574,28 +1574,20 @@ pub const SYMLINK_FLAG_RELATIVE: ULONG = 0x1; pub const SYMBOLIC_LINK_FLAG_DIRECTORY: DWORD = 0x1; pub const SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE: DWORD = 0x2; -pub const OBJECT_INFORMATION_CLASS = extern enum { - ObjectBasicInformation, - ObjectNameInformation, - ObjectTypeInformation, - ObjectAllInformation, - ObjectDataInformation, +pub const MOUNTMGR_MOUNT_POINT = extern struct { + SymbolicLinkNameOffset: ULONG, + SymbolicLinkNameLength: USHORT, + Reserved1: USHORT, + UniqueIdOffset: ULONG, + UniqueIdLength: USHORT, + Reserved2: USHORT, + DeviceNameOffset: ULONG, + DeviceNameLength: USHORT, + Reserved3: USHORT, }; -pub const OBJECT_NAME_INFORMATION = extern struct { - Name: UNICODE_STRING, -}; - -pub const DIRECTORY_QUERY: DWORD = 0x0001; -pub const DIRECTORY_TRAVERSE: DWORD = 0x0002; -pub const DIRECTORY_CREATE_OBJECT: DWORD = 0x0004; -pub const DIRECTORY_CREATE_SUBDIRECTORY: DWORD = 0x0008; -pub const DIRECTORY_ALL_ACCESS: DWORD = STANDARD_RIGHTS_REQUIRED | 0xF; - -pub const OBJDIR_INFORMATION = extern struct { - ObjectName: UNICODE_STRING, - ObjectTypeName: UNICODE_STRING, - Data: [1]BYTE, +pub const MOUNTMGR_MOUNT_POINTS = extern struct { + Size: ULONG, + NumberOfMountPoints: ULONG, + MountPoints: [1]MOUNTMGR_MOUNT_POINT, }; - -pub const SYMBOLIC_LINK_QUERY: DWORD = 0x0001; -pub const SYMBOLIC_LINK_ALL_ACCESS: DWORD = STANDARD_RIGHTS_REQUIRED | 0x1;
\ No newline at end of file +pub const IOCTL_MOUNTMGR_QUERY_POINTS: ULONG = 0x6d0008;
\ No newline at end of file diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index c41e613abb..5edad85c20 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -106,37 +106,3 @@ pub extern "NtDll" fn NtWaitForKeyedEvent( Alertable: BOOLEAN, Timeout: ?*LARGE_INTEGER, ) callconv(.Stdcall) NTSTATUS; - -pub extern "NtDll" fn NtQueryObject( - Handle: HANDLE, - ObjectInformationClass: OBJECT_INFORMATION_CLASS, - ObjectInformation: *c_void, - ObjectInformationLength: ULONG, - ReturnLength: *ULONG, -) callconv(.Stdcall) NTSTATUS; - -pub extern "NtDll" fn NtOpenSymbolicLinkObject( - pHandle: *HANDLE, - DesiredAccess: DWORD, - ObjectAttributes: OBJECT_ATTRIBUTES, -) callconv(.Stdcall) NTSTATUS; -pub extern "NtDll" fn NtQuerySymbolicLinkObject( - SymbolicLinkHandle: HANDLE, - pLinkName: *UNICODE_STRING, - pDataWritten: ?*ULONG, -) callconv(.Stdcall) NTSTATUS; - -pub extern "NtDll" fn NtOpenDirectoryObject( - DirectoryObjectHandle: *HANDLE, - DesiredAccess: DWORD, - ObjectAttributes: OBJECT_ATTRIBUTES, -) callconv(.Stdcall) NTSTATUS; -pub extern "NtDll" fn NtQueryDirectoryObject( - DirectoryHandle: HANDLE, - Buffer: ?*c_void, - Length: ULONG, - ReturnSingleEntry: BOOLEAN, - RestartScan: BOOLEAN, - Context: *ULONG, - ReturnLength: *ULONG, -) callconv(.Stdcall) NTSTATUS;
\ No newline at end of file |
