authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2024-04-22 07:46:09-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-04-24 13:45:36-07:00
loge36bf2baff64ebc5c26215e98836454e906448cc
tree19b0ca1e0e23870c3cdf1babfc0824e9eb02644d
parent4babedf1bee40dbfaa0675c43e460125ba75ad82

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

1 files changed, 94 insertions(+), 1 deletions(-)

lib/std/os/windows.zig+94-1
...@@ -1368,6 +1368,61 @@ pub fn GetFinalPathNameByHandle(...@@ -1368,6 +1368,61 @@ pub fn GetFinalPathNameByHandle(
1368 return error.BadPathName;1368 return error.BadPathName;
1369 }1369 }
13701370
1371 return out_buffer[0..total_len];
1372 } else if (mountmgrIsVolumeName(symlink)) {
1373 // If the symlink is a volume GUID like \??\Volume{383da0b0-717f-41b6-8c36-00500992b58d},
1374 // then it is a volume mounted as a path rather than a drive letter. We need to
1375 // query the mount manager again to get the DOS path for the volume.
1376
1377 // 49 is the maximum length accepted by mountmgrIsVolumeName
1378 const vol_input_size = @sizeOf(MOUNTMGR_TARGET_NAME) + (49 * 2);
1379 var vol_input_buf: [vol_input_size]u8 align(@alignOf(MOUNTMGR_TARGET_NAME)) = [_]u8{0} ** vol_input_size;
1380 // Note: If the path exceeds MAX_PATH, the Disk Management GUI doesn't accept the full path,
1381 // and instead if must be specified using a shortened form (e.g. C:\FOO~1\BAR~1\<...>).
1382 // However, just to be sure we can handle any path length, we use PATH_MAX_WIDE here.
1383 const min_output_size = @sizeOf(MOUNTMGR_VOLUME_PATHS) + (PATH_MAX_WIDE * 2);
1384 var vol_output_buf: [min_output_size]u8 align(@alignOf(MOUNTMGR_VOLUME_PATHS)) = undefined;
1385
1386 var vol_input_struct: *MOUNTMGR_TARGET_NAME = @ptrCast(&vol_input_buf[0]);
1387 vol_input_struct.DeviceNameLength = @intCast(symlink.len * 2);
1388 @memcpy(@as([*]WCHAR, &vol_input_struct.DeviceName)[0..symlink.len], symlink);
1389
1390 DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH, &vol_input_buf, &vol_output_buf) catch |err| switch (err) {
1391 error.AccessDenied => return error.Unexpected,
1392 else => |e| return e,
1393 };
1394 const volume_paths_struct: *const MOUNTMGR_VOLUME_PATHS = @ptrCast(&vol_output_buf[0]);
1395 const volume_path = std.mem.sliceTo(@as(
1396 [*]const u16,
1397 &volume_paths_struct.MultiSz,
1398 )[0 .. volume_paths_struct.MultiSzLength / 2], 0);
1399
1400 if (out_buffer.len < volume_path.len + file_name_u16.len) return error.NameTooLong;
1401
1402 // `out_buffer` currently contains the memory of `file_name_u16`, so it can overlap with where
1403 // we want to place the filename before returning. Here are the possible overlapping cases:
1404 //
1405 // out_buffer: [filename]
1406 // dest: [___(a)___] [___(b)___]
1407 //
1408 // In the case of (a), we need to copy forwards, and in the case of (b) we need
1409 // to copy backwards. We also need to do this before copying the volume path because
1410 // it could overwrite the file_name_u16 memory.
1411 const file_name_dest = out_buffer[volume_path.len..][0..file_name_u16.len];
1412 const file_name_byte_offset = @intFromPtr(file_name_u16.ptr) - @intFromPtr(out_buffer.ptr);
1413 const file_name_index = file_name_byte_offset / @sizeOf(u16);
1414 if (volume_path.len > file_name_index)
1415 mem.copyBackwards(u16, file_name_dest, file_name_u16)
1416 else
1417 mem.copyForwards(u16, file_name_dest, file_name_u16);
1418 @memcpy(out_buffer[0..volume_path.len], volume_path);
1419 const total_len = volume_path.len + file_name_u16.len;
1420
1421 // Validate that DOS does not contain any spurious nul bytes.
1422 if (mem.indexOfScalar(u16, out_buffer[0..total_len], 0)) |_| {
1423 return error.BadPathName;
1424 }
1425
1371 return out_buffer[0..total_len];1426 return out_buffer[0..total_len];
1372 }1427 }
1373 }1428 }
...@@ -1379,6 +1434,32 @@ pub fn GetFinalPathNameByHandle(...@@ -1379,6 +1434,32 @@ pub fn GetFinalPathNameByHandle(
1379 }1434 }
1380}1435}
13811436
1437/// Equivalent to the MOUNTMGR_IS_VOLUME_NAME macro in mountmgr.h
1438fn mountmgrIsVolumeName(name: []const u16) bool {
1439 return (name.len == 48 or (name.len == 49 and name[48] == mem.nativeToLittle(u16, '\\'))) and
1440 name[0] == mem.nativeToLittle(u16, '\\') and
1441 (name[1] == mem.nativeToLittle(u16, '?') or name[1] == mem.nativeToLittle(u16, '\\')) and
1442 name[2] == mem.nativeToLittle(u16, '?') and
1443 name[3] == mem.nativeToLittle(u16, '\\') and
1444 mem.startsWith(u16, name[4..], std.unicode.utf8ToUtf16LeStringLiteral("Volume{")) and
1445 name[19] == mem.nativeToLittle(u16, '-') and
1446 name[24] == mem.nativeToLittle(u16, '-') and
1447 name[29] == mem.nativeToLittle(u16, '-') and
1448 name[34] == mem.nativeToLittle(u16, '-') and
1449 name[47] == mem.nativeToLittle(u16, '}');
1450}
1451
1452test mountmgrIsVolumeName {
1453 const L = std.unicode.utf8ToUtf16LeStringLiteral;
1454 try std.testing.expect(mountmgrIsVolumeName(L("\\\\?\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}")));
1455 try std.testing.expect(mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}")));
1456 try std.testing.expect(mountmgrIsVolumeName(L("\\\\?\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\")));
1457 try std.testing.expect(mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\")));
1458 try std.testing.expect(!mountmgrIsVolumeName(L("\\\\.\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}")));
1459 try std.testing.expect(!mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\foo")));
1460 try std.testing.expect(!mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58}")));
1461}
1462
1382test GetFinalPathNameByHandle {1463test GetFinalPathNameByHandle {
1383 if (builtin.os.tag != .windows)1464 if (builtin.os.tag != .windows)
1384 return;1465 return;
...@@ -4845,6 +4926,8 @@ pub const SYMLINK_FLAG_RELATIVE: ULONG = 0x1;...@@ -4845,6 +4926,8 @@ pub const SYMLINK_FLAG_RELATIVE: ULONG = 0x1;
4845pub const SYMBOLIC_LINK_FLAG_DIRECTORY: DWORD = 0x1;4926pub const SYMBOLIC_LINK_FLAG_DIRECTORY: DWORD = 0x1;
4846pub const SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE: DWORD = 0x2;4927pub const SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE: DWORD = 0x2;
48474928
4929pub const MOUNTMGRCONTROLTYPE = 0x0000006D;
4930
4848pub const MOUNTMGR_MOUNT_POINT = extern struct {4931pub const MOUNTMGR_MOUNT_POINT = extern struct {
4849 SymbolicLinkNameOffset: ULONG,4932 SymbolicLinkNameOffset: ULONG,
4850 SymbolicLinkNameLength: USHORT,4933 SymbolicLinkNameLength: USHORT,
...@@ -4861,7 +4944,17 @@ pub const MOUNTMGR_MOUNT_POINTS = extern struct {...@@ -4861,7 +4944,17 @@ pub const MOUNTMGR_MOUNT_POINTS = extern struct {
4861 NumberOfMountPoints: ULONG,4944 NumberOfMountPoints: ULONG,
4862 MountPoints: [1]MOUNTMGR_MOUNT_POINT,4945 MountPoints: [1]MOUNTMGR_MOUNT_POINT,
4863};4946};
4864pub const IOCTL_MOUNTMGR_QUERY_POINTS: ULONG = 0x6d0008;4947pub const IOCTL_MOUNTMGR_QUERY_POINTS = CTL_CODE(MOUNTMGRCONTROLTYPE, 2, .METHOD_BUFFERED, FILE_ANY_ACCESS);
4948
4949pub const MOUNTMGR_TARGET_NAME = extern struct {
4950 DeviceNameLength: USHORT,
4951 DeviceName: [1]WCHAR,
4952};
4953pub const MOUNTMGR_VOLUME_PATHS = extern struct {
4954 MultiSzLength: ULONG,
4955 MultiSz: [1]WCHAR,
4956};
4957pub const IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH = CTL_CODE(MOUNTMGRCONTROLTYPE, 12, .METHOD_BUFFERED, FILE_ANY_ACCESS);
48654958
4866pub const OBJECT_INFORMATION_CLASS = enum(c_int) {4959pub const OBJECT_INFORMATION_CLASS = enum(c_int) {
4867 ObjectBasicInformation = 0,4960 ObjectBasicInformation = 0,