From e36bf2baff64ebc5c26215e98836454e906448cc Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Mon, 22 Apr 2024 07:46:09 -0700 Subject: [PATCH] windows.GetFinalPathNameByHandle: Support volumes mounted as paths A volume can be mounted as a NTFS path, e.g. as C:\Mnt\Foo. In that case, IOCTL_MOUNTMGR_QUERY_POINTS gives us a mount point with a symlink value something like `\??\Volume{383da0b0-717f-41b6-8c36-00500992b58d}`. In order to get the `C:\Mnt\Foo` path, we can query the mountmgr again using IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH. Fixes #19731 --- lib/std/os/windows.zig | 95 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 27a7b8a351..2e32e4676e 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -1368,6 +1368,61 @@ pub fn GetFinalPathNameByHandle( return error.BadPathName; } + return out_buffer[0..total_len]; + } else if (mountmgrIsVolumeName(symlink)) { + // If the symlink is a volume GUID like \??\Volume{383da0b0-717f-41b6-8c36-00500992b58d}, + // then it is a volume mounted as a path rather than a drive letter. We need to + // query the mount manager again to get the DOS path for the volume. + + // 49 is the maximum length accepted by mountmgrIsVolumeName + const vol_input_size = @sizeOf(MOUNTMGR_TARGET_NAME) + (49 * 2); + var vol_input_buf: [vol_input_size]u8 align(@alignOf(MOUNTMGR_TARGET_NAME)) = [_]u8{0} ** vol_input_size; + // Note: If the path exceeds MAX_PATH, the Disk Management GUI doesn't accept the full path, + // and instead if must be specified using a shortened form (e.g. C:\FOO~1\BAR~1\<...>). + // However, just to be sure we can handle any path length, we use PATH_MAX_WIDE here. + const min_output_size = @sizeOf(MOUNTMGR_VOLUME_PATHS) + (PATH_MAX_WIDE * 2); + var vol_output_buf: [min_output_size]u8 align(@alignOf(MOUNTMGR_VOLUME_PATHS)) = undefined; + + var vol_input_struct: *MOUNTMGR_TARGET_NAME = @ptrCast(&vol_input_buf[0]); + vol_input_struct.DeviceNameLength = @intCast(symlink.len * 2); + @memcpy(@as([*]WCHAR, &vol_input_struct.DeviceName)[0..symlink.len], symlink); + + DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH, &vol_input_buf, &vol_output_buf) catch |err| switch (err) { + error.AccessDenied => return error.Unexpected, + else => |e| return e, + }; + const volume_paths_struct: *const MOUNTMGR_VOLUME_PATHS = @ptrCast(&vol_output_buf[0]); + const volume_path = std.mem.sliceTo(@as( + [*]const u16, + &volume_paths_struct.MultiSz, + )[0 .. volume_paths_struct.MultiSzLength / 2], 0); + + if (out_buffer.len < volume_path.len + file_name_u16.len) return error.NameTooLong; + + // `out_buffer` currently contains the memory of `file_name_u16`, so it can overlap with where + // we want to place the filename before returning. Here are the possible overlapping cases: + // + // out_buffer: [filename] + // dest: [___(a)___] [___(b)___] + // + // In the case of (a), we need to copy forwards, and in the case of (b) we need + // to copy backwards. We also need to do this before copying the volume path because + // it could overwrite the file_name_u16 memory. + const file_name_dest = out_buffer[volume_path.len..][0..file_name_u16.len]; + const file_name_byte_offset = @intFromPtr(file_name_u16.ptr) - @intFromPtr(out_buffer.ptr); + const file_name_index = file_name_byte_offset / @sizeOf(u16); + if (volume_path.len > file_name_index) + mem.copyBackwards(u16, file_name_dest, file_name_u16) + else + mem.copyForwards(u16, file_name_dest, file_name_u16); + @memcpy(out_buffer[0..volume_path.len], volume_path); + const total_len = volume_path.len + file_name_u16.len; + + // Validate that DOS does not contain any spurious nul bytes. + if (mem.indexOfScalar(u16, out_buffer[0..total_len], 0)) |_| { + return error.BadPathName; + } + return out_buffer[0..total_len]; } } @@ -1379,6 +1434,32 @@ pub fn GetFinalPathNameByHandle( } } +/// Equivalent to the MOUNTMGR_IS_VOLUME_NAME macro in mountmgr.h +fn mountmgrIsVolumeName(name: []const u16) bool { + return (name.len == 48 or (name.len == 49 and name[48] == mem.nativeToLittle(u16, '\\'))) and + name[0] == mem.nativeToLittle(u16, '\\') and + (name[1] == mem.nativeToLittle(u16, '?') or name[1] == mem.nativeToLittle(u16, '\\')) and + name[2] == mem.nativeToLittle(u16, '?') and + name[3] == mem.nativeToLittle(u16, '\\') and + mem.startsWith(u16, name[4..], std.unicode.utf8ToUtf16LeStringLiteral("Volume{")) and + name[19] == mem.nativeToLittle(u16, '-') and + name[24] == mem.nativeToLittle(u16, '-') and + name[29] == mem.nativeToLittle(u16, '-') and + name[34] == mem.nativeToLittle(u16, '-') and + name[47] == mem.nativeToLittle(u16, '}'); +} + +test mountmgrIsVolumeName { + const L = std.unicode.utf8ToUtf16LeStringLiteral; + try std.testing.expect(mountmgrIsVolumeName(L("\\\\?\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}"))); + try std.testing.expect(mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}"))); + try std.testing.expect(mountmgrIsVolumeName(L("\\\\?\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\"))); + try std.testing.expect(mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\"))); + try std.testing.expect(!mountmgrIsVolumeName(L("\\\\.\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}"))); + try std.testing.expect(!mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\foo"))); + try std.testing.expect(!mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58}"))); +} + test GetFinalPathNameByHandle { if (builtin.os.tag != .windows) return; @@ -4845,6 +4926,8 @@ 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 MOUNTMGRCONTROLTYPE = 0x0000006D; + pub const MOUNTMGR_MOUNT_POINT = extern struct { SymbolicLinkNameOffset: ULONG, SymbolicLinkNameLength: USHORT, @@ -4861,7 +4944,17 @@ pub const MOUNTMGR_MOUNT_POINTS = extern struct { NumberOfMountPoints: ULONG, MountPoints: [1]MOUNTMGR_MOUNT_POINT, }; -pub const IOCTL_MOUNTMGR_QUERY_POINTS: ULONG = 0x6d0008; +pub const IOCTL_MOUNTMGR_QUERY_POINTS = CTL_CODE(MOUNTMGRCONTROLTYPE, 2, .METHOD_BUFFERED, FILE_ANY_ACCESS); + +pub const MOUNTMGR_TARGET_NAME = extern struct { + DeviceNameLength: USHORT, + DeviceName: [1]WCHAR, +}; +pub const MOUNTMGR_VOLUME_PATHS = extern struct { + MultiSzLength: ULONG, + MultiSz: [1]WCHAR, +}; +pub const IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH = CTL_CODE(MOUNTMGRCONTROLTYPE, 12, .METHOD_BUFFERED, FILE_ANY_ACCESS); pub const OBJECT_INFORMATION_CLASS = enum(c_int) { ObjectBasicInformation = 0,