Commit a0f1825c53
Changed files (1)
lib
std
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,