[NTOS:IO] Implement IoGetDeviceInterfaceAlias

Required by XP/2003 audio stack to be loaded properly.
CORE-17361
This commit is contained in:
Oleg Dubinskiy
2025-02-02 23:30:38 +01:00
committed by Oleg Dubinskiy
parent 6b2f00e70a
commit d617c8cf32
+420 -33
View File
@@ -7,6 +7,7 @@
* PROGRAMMERS: Filip Navara ([email protected])
* Matthew Brace ([email protected])
* Hervé Poussineau ([email protected])
* Oleg Dubinskiy ([email protected])
*/
/* INCLUDES ******************************************************************/
@@ -26,7 +27,265 @@ C_ASSERT(sizeof(L"{01234567-89ab-cdef-0123-456789abcdef}") == GUID_STRING_BYTES
PDEVICE_OBJECT
IopGetDeviceObjectFromDeviceInstance(PUNICODE_STRING DeviceInstance);
static PWCHAR BaseKeyString = L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\DeviceClasses\\";
static PCWSTR BaseKeyString = L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\DeviceClasses\\";
/**
* @brief
* Creates a new symbolic link from the specified format of the prefix, device string,
* class GUID and reference string (if any).
*
* @param[in] DeviceString
* Device string, placed after prefix and before GUID, for example ACPI#PNP0501#1#.
*
* @param[in] GuidString
* Device interface class GUID represented by a string. Placed in curly brackets {},
* after device string, should always be 38 characters long. For example,
* {01234567-89ab-cdef-0123-456789abcdef}.
*
* @param[in] ReferenceString
* Optional reference string, if any. Placed after GUID, at the end of symbolic link.
* Usually contains human-readable subdevice name or class GUID.
*
* @param[in] UserModePrefixFormat
* Specifies whether a new symbolic link should have either a kernel mode or user mode prefix.
* TRUE for user mode prefix, FALSE for kernel mode.
*
* @param[out] SymbolicLinkName
* Pointer to unicode string which receives created symbolic link.
*
* @return
* STATUS_SUCCESS in case of success, or an NTSTATUS error code otherwise.
**/
static
NTSTATUS
IopBuildSymbolicLink(
_In_ PCUNICODE_STRING DeviceString,
_In_ PCUNICODE_STRING GuidString,
_In_opt_ PCUNICODE_STRING ReferenceString,
_In_ BOOLEAN UserModePrefixFormat,
_Out_ PUNICODE_STRING SymbolicLinkName)
{
static const UNICODE_STRING KernelModePrefix = RTL_CONSTANT_STRING(L"\\??\\");
static const UNICODE_STRING UserModePrefix = RTL_CONSTANT_STRING(L"\\\\?\\");
static const UNICODE_STRING PathSep = RTL_CONSTANT_STRING(L"\\");
UNICODE_STRING MungedDeviceString, SymbolicLink;
NTSTATUS Status;
ULONG Length;
USHORT i;
/* Use a backslash if reference string is not specified */
if (!ReferenceString)
ReferenceString = &PathSep;
/* Duplicate the device string (to "munge" it) */
Status = RtlDuplicateUnicodeString(0, DeviceString, &MungedDeviceString);
if (!NT_SUCCESS(Status))
{
DPRINT1("RtlDuplicateUnicodeString() failed, Status 0x%08lx\n", Status);
return Status;
}
/* Replace all '\' by '#' in device string */
for (i = 0; i < MungedDeviceString.Length / sizeof(WCHAR); i++)
{
if (MungedDeviceString.Buffer[i] == L'\\')
MungedDeviceString.Buffer[i] = L'#';
}
/* Calculate total length */
Length = KernelModePrefix.Length // Same as UserModePrefix.Length
+ MungedDeviceString.Length
+ sizeof(L"#") + GuidString->Length
+ ReferenceString->Length;
ASSERT(Length <= MAXUSHORT);
/* Build up new symbolic link */
SymbolicLink.Length = 0;
SymbolicLink.MaximumLength = Length;
SymbolicLink.Buffer = ExAllocatePoolWithTag(PagedPool, SymbolicLink.MaximumLength, TAG_IO);
if (!SymbolicLink.Buffer)
{
DPRINT1("ExAllocatePoolWithTag() failed\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
Status = RtlUnicodeStringPrintf(&SymbolicLink,
L"%wZ%wZ#%wZ%wZ",
UserModePrefixFormat ?
&UserModePrefix : &KernelModePrefix,
&MungedDeviceString,
GuidString,
ReferenceString);
NT_VERIFY(NT_SUCCESS(Status));
DPRINT("New symbolic link is %wZ\n", &SymbolicLink);
*SymbolicLinkName = SymbolicLink;
return STATUS_SUCCESS;
}
/**
* @brief
* Parses the specified symbolic link onto the 4 parts: prefix, device string,
* class GUID and reference string.
*
* @param[in] SymbolicLinkName
* Pointer to a symbolic link string to parse.
*
* @param[out] PrefixString
* Receives prefix of symbolic link. Can be '\??\' for Kernel mode or '\\?\' for User mode.
*
* @param[out] MungedString
* Receives device string. For example, ##?#ACPI#PNP0501#1#.
*
* @param[out] GuidString
* Receives device interface class GUID string represented by device interface.
* For example, {01234567-89ab-cdef-0123-456789abcdef}.
*
* @param[out] ReferenceString
* Receives reference string, if any. Usually contains a human-readable
* subdevice name or class GUID.
*
* @param[out] ReferenceStringPresent
* Pointer to variable that indicates whether the reference string exists in symbolic link.
* TRUE if it does, FALSE otherwise.
*
* @param[out] InterfaceClassGuid
* Receives the interface class GUID to which specified symbolic link belongs to.
*
* @return
* STATUS_SUCCESS in case of success, or an NTSTATUS error code otherwise.
**/
static
NTSTATUS
IopSeparateSymbolicLink(
_In_ PCUNICODE_STRING SymbolicLinkName,
_Out_opt_ PUNICODE_STRING PrefixString,
_Out_opt_ PUNICODE_STRING MungedString,
_Out_opt_ PUNICODE_STRING GuidString,
_Out_opt_ PUNICODE_STRING ReferenceString,
_Out_opt_ PBOOLEAN ReferenceStringPresent,
_Out_opt_ LPGUID InterfaceClassGuid)
{
static const UNICODE_STRING KernelModePrefix = RTL_CONSTANT_STRING(L"\\??\\");
static const UNICODE_STRING UserModePrefix = RTL_CONSTANT_STRING(L"\\\\?\\");
UNICODE_STRING MungedStringReal, GuidStringReal, ReferenceStringReal;
UNICODE_STRING LinkNameNoPrefix;
USHORT i, ReferenceStringOffset;
NTSTATUS Status = STATUS_SUCCESS;
DPRINT("Symbolic link is %wZ\n", SymbolicLinkName);
/* The symbolic link name looks like \??\ACPI#PNP0501#1#{GUID}\ReferenceString
* Make sure it starts with the expected prefix. */
if (!RtlPrefixUnicodeString(&KernelModePrefix, SymbolicLinkName, FALSE) &&
!RtlPrefixUnicodeString(&UserModePrefix, SymbolicLinkName, FALSE))
{
DPRINT1("Invalid link name %wZ\n", SymbolicLinkName);
return STATUS_INVALID_PARAMETER;
}
/* Sanity checks */
ASSERT(KernelModePrefix.Length == UserModePrefix.Length);
ASSERT(SymbolicLinkName->Length >= KernelModePrefix.Length);
/* Make a version without the prefix for further processing */
LinkNameNoPrefix.Buffer = SymbolicLinkName->Buffer + KernelModePrefix.Length / sizeof(WCHAR);
LinkNameNoPrefix.Length = SymbolicLinkName->Length - KernelModePrefix.Length;
LinkNameNoPrefix.MaximumLength = LinkNameNoPrefix.Length;
DPRINT("Symbolic link without prefix is %wZ\n", &LinkNameNoPrefix);
/* Find the reference string, if any */
for (i = 0; i < LinkNameNoPrefix.Length / sizeof(WCHAR); i++)
{
if (LinkNameNoPrefix.Buffer[i] == L'\\')
break;
}
ReferenceStringOffset = i * sizeof(WCHAR);
/* The GUID is before the reference string or at the end */
ASSERT(LinkNameNoPrefix.Length >= ReferenceStringOffset);
if (ReferenceStringOffset < GUID_STRING_BYTES + sizeof(WCHAR))
{
DPRINT1("Invalid link name %wZ\n", SymbolicLinkName);
return STATUS_INVALID_PARAMETER;
}
/* Get reference string (starts with \ after {GUID}) from link without prefix */
ReferenceStringReal.Buffer = LinkNameNoPrefix.Buffer + ReferenceStringOffset / sizeof(WCHAR);
ReferenceStringReal.Length = LinkNameNoPrefix.Length - ReferenceStringOffset;
ReferenceStringReal.MaximumLength = ReferenceStringReal.Length;
DPRINT("Reference string is %wZ\n", &ReferenceStringReal);
/* Get GUID string (device class GUID in {} brackets) */
GuidStringReal.Buffer = LinkNameNoPrefix.Buffer + (ReferenceStringOffset - GUID_STRING_BYTES) / sizeof(WCHAR);
GuidStringReal.Length = GUID_STRING_BYTES;
GuidStringReal.MaximumLength = GuidStringReal.Length;
DPRINT("GUID string is %wZ\n", &GuidStringReal);
/* Validate GUID string for:
* 1) {} brackets at the start and the end;
* 2) - separators in the appropriate places. */
ASSERT(GuidStringReal.Buffer[0] == L'{');
ASSERT(GuidStringReal.Buffer[GUID_STRING_CHARS - 1] == L'}');
ASSERT(GuidStringReal.Buffer[9] == L'-');
ASSERT(GuidStringReal.Buffer[14] == L'-');
ASSERT(GuidStringReal.Buffer[19] == L'-');
ASSERT(GuidStringReal.Buffer[24] == L'-');
if (MungedString)
{
/* Create a munged path string (looks like ACPI#PNP0501#1#) */
MungedStringReal.Buffer = LinkNameNoPrefix.Buffer;
MungedStringReal.Length = LinkNameNoPrefix.Length - ReferenceStringReal.Length - GUID_STRING_BYTES - sizeof(WCHAR);
MungedStringReal.MaximumLength = MungedStringReal.Length;
DPRINT("Munged string is %wZ\n", &MungedStringReal);
}
/* Store received parts if the parameters are not null */
if (PrefixString)
{
PrefixString->Buffer = SymbolicLinkName->Buffer;
PrefixString->Length = KernelModePrefix.Length; // Same as UserModePrefix.Length
PrefixString->MaximumLength = PrefixString->Length;
DPRINT("Prefix string is %wZ\n", PrefixString);
}
if (MungedString)
*MungedString = MungedStringReal;
if (GuidString)
*GuidString = GuidStringReal;
if (ReferenceString)
{
if (ReferenceStringReal.Length > sizeof(WCHAR))
*ReferenceString = ReferenceStringReal;
else
RtlInitEmptyUnicodeString(ReferenceString, NULL, 0);
}
if (ReferenceStringPresent)
*ReferenceStringPresent = ReferenceStringReal.Length > sizeof(WCHAR);
if (InterfaceClassGuid)
{
/* Convert GUID string into a GUID and store it also */
Status = RtlGUIDFromString(&GuidStringReal, InterfaceClassGuid);
if (!NT_SUCCESS(Status))
{
DPRINT1("RtlGUIDFromString() failed, Status 0x%08lx\n", Status);
}
}
/* We're done */
return Status;
}
static
NTSTATUS
@@ -275,38 +534,6 @@ IoOpenDeviceInterfaceRegistryKey(IN PUNICODE_STRING SymbolicLinkName,
return Status;
}
/*++
* @name IoGetDeviceInterfaceAlias
* @unimplemented
*
* Returns the alias device interface of the specified device interface
* instance, if the alias exists.
* Documented in WDK.
*
* @param SymbolicLinkName
* Pointer to a string which identifies the device interface instance
*
* @param AliasInterfaceClassGuid
* See WDK
*
* @param AliasSymbolicLinkName
* See WDK
*
* @return Three different NTSTATUS values in case of errors, and STATUS_SUCCESS
* otherwise (see WDK for details)
*
* @remarks Must be called at IRQL = PASSIVE_LEVEL in the context of a system thread
*
*--*/
NTSTATUS
NTAPI
IoGetDeviceInterfaceAlias(IN PUNICODE_STRING SymbolicLinkName,
IN CONST GUID *AliasInterfaceClassGuid,
OUT PUNICODE_STRING AliasSymbolicLinkName)
{
return STATUS_NOT_IMPLEMENTED;
}
/*++
* @name IopOpenInterfaceKey
*
@@ -412,6 +639,166 @@ cleanup:
return Status;
}
/**
* @brief
* Returns the alias device interface of the specified device interface
* instance, if the alias exists.
*
* @param[in] SymbolicLinkName
* Pointer to a symbolic link string which identifies the device interface instance.
*
* @param[in] AliasInterfaceClassGuid
* Pointer to a device interface class GUID.
*
* @param[out] AliasSymbolicLinkName
* Pointer to unicode string which receives the alias symbolic link upon success.
* Must be freed with RtlFreeUnicodeString after using.
*
* @return NTSTATUS values in case of errors, STATUS_SUCCESS otherwise.
*
* @remarks Must be called at IRQL = PASSIVE_LEVEL in the context of a system thread
**/
NTSTATUS
NTAPI
IoGetDeviceInterfaceAlias(
_In_ PUNICODE_STRING SymbolicLinkName,
_In_ CONST GUID *AliasInterfaceClassGuid,
_Out_ PUNICODE_STRING AliasSymbolicLinkName)
{
static const UNICODE_STRING UserModePrefix = RTL_CONSTANT_STRING(L"\\\\?\\");
UNICODE_STRING AliasSymbolicLink = {0};
UNICODE_STRING AliasGuidString = {0};
UNICODE_STRING DeviceString = {0};
UNICODE_STRING ReferenceString = {0};
PKEY_VALUE_FULL_INFORMATION kvInfo;
HANDLE DeviceKey, AliasInstanceKey;
BOOLEAN UserModePrefixFormat;
BOOLEAN ReferenceStringPresent = FALSE; /* Assuming no ref string by default */
PVOID Buffer;
NTSTATUS Status;
DPRINT("IoGetDeviceInterfaceAlias(%wZ, 0x%p)\n", SymbolicLinkName, AliasInterfaceClassGuid);
/* Sanity check */
if (!SymbolicLinkName || !AliasInterfaceClassGuid)
{
DPRINT1("IoGetDeviceInterfaceAlias() invalid symbolic link or alias class GUID\n");
return STATUS_INVALID_PARAMETER;
}
/* Convert alias GUID to a string */
Status = RtlStringFromGUID(AliasInterfaceClassGuid, &AliasGuidString);
if (!NT_SUCCESS(Status))
{
DPRINT1("RtlStringFromGUID() failed, Status 0x%08lx\n", Status);
goto Quit;
}
DPRINT("Alias GUID is %wZ\n", &AliasGuidString);
/* Get the device instance string of existing symbolic link */
Status = OpenRegistryHandlesFromSymbolicLink(SymbolicLinkName,
KEY_QUERY_VALUE,
NULL,
&DeviceKey,
NULL);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed to open device instance key for %wZ, Status 0x%08lx\n", SymbolicLinkName, Status);
goto Quit;
}
Status = IopGetRegistryValue(DeviceKey, L"DeviceInstance", &kvInfo);
ZwClose(DeviceKey);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed get device instance value, Status 0x%08lx\n", Status);
goto Quit;
}
if (kvInfo->Type != REG_SZ || kvInfo->DataLength == 0 || kvInfo->DataLength > MAXUSHORT)
{
DPRINT1("Wrong or empty instance value\n");
Status = STATUS_INVALID_PARAMETER;
goto Quit;
}
/* Convert received data to unicode string */
Buffer = (PVOID)((ULONG_PTR)kvInfo + kvInfo->DataOffset);
PnpRegSzToString(Buffer, kvInfo->DataLength, &DeviceString.Length);
DeviceString.MaximumLength = DeviceString.Length;
DeviceString.Buffer = Buffer;
/*
* Separate symbolic link into 4 parts:
* 1) prefix string (\??\ for kernel mode or \\?\ for user mode),
* 2) munged path string (like ##?#ACPI#PNP0501#1#{GUID}),
* 3) GUID string (the current GUID),
* 4) reference string (goes after GUID, starts with '\').
*
* We need only reference string.
*/
Status = IopSeparateSymbolicLink(SymbolicLinkName,
NULL,
NULL,
NULL,
&ReferenceString,
&ReferenceStringPresent,
NULL);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed to separate symbolic link %wZ, Status 0x%08lx\n", SymbolicLinkName, Status);
goto Quit;
}
DPRINT("Device string is '%wZ'\n", &DeviceString);
/* Does symbolic link have kernel mode "\??\" or user mode "\\?\" prefix format? */
UserModePrefixFormat = RtlPrefixUnicodeString(&UserModePrefix, SymbolicLinkName, FALSE);
/* Build up new symbolic link with alias GUID */
Status = IopBuildSymbolicLink(&DeviceString,
&AliasGuidString,
ReferenceStringPresent ? &ReferenceString : NULL,
UserModePrefixFormat,
&AliasSymbolicLink);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed to build alias symbolic link, Status 0x%08lx\n", Status);
goto Quit;
}
/* Make sure that alias symbolic link key exists in registry */
Status = OpenRegistryHandlesFromSymbolicLink(&AliasSymbolicLink,
KEY_READ,
NULL,
NULL,
&AliasInstanceKey);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed to open alias symbolic link key, Status 0x%08lx\n", Status);
goto Quit;
}
ZwClose(AliasInstanceKey);
/* We're done */
DPRINT("IoGetDeviceInterfaceAlias(): alias symbolic link %wZ\n", &AliasSymbolicLink);
*AliasSymbolicLinkName = AliasSymbolicLink;
Status = STATUS_SUCCESS;
Quit:
if (!NT_SUCCESS(Status))
{
if (AliasSymbolicLink.Buffer)
RtlFreeUnicodeString(&AliasSymbolicLink);
}
if (AliasGuidString.Buffer)
RtlFreeUnicodeString(&AliasGuidString);
return Status;
}
/*++
* @name IoGetDeviceInterfaces
* @implemented