@@ -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,