mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2265 lines
56 KiB
2265 lines
56 KiB
/*++
|
|
|
|
Copyright (c) 1995 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
rtravers.c
|
|
|
|
Abstract:
|
|
|
|
This module contains the server-side hardware tree traversal APIs.
|
|
PNP_ValidateDeviceInstance
|
|
PNP_GetRootDeviceInstance
|
|
PNP_GetRelatedDeviceInstance
|
|
PNP_EnumerateSubKeys
|
|
PNP_GetDeviceList
|
|
PNP_GetDeviceListSize
|
|
|
|
Author:
|
|
|
|
Paula Tomlinson (paulat) 6-19-1995
|
|
|
|
Environment:
|
|
|
|
User-mode only.
|
|
|
|
Revision History:
|
|
|
|
19-June-1995 paulat
|
|
|
|
Creation and initial implementation.
|
|
|
|
--*/
|
|
|
|
|
|
//
|
|
// includes
|
|
//
|
|
#include <nt.h>
|
|
#include <ntrtl.h>
|
|
#include <nturtl.h>
|
|
#include <ntpnpapi.h>
|
|
#include "precomp.h"
|
|
#include "umpnpdat.h"
|
|
|
|
|
|
|
|
//
|
|
// private prototypes
|
|
//
|
|
CONFIGRET
|
|
GetParentDevInst(
|
|
IN LPWSTR pDeviceID,
|
|
OUT LPWSTR pRelatedDeviceID,
|
|
IN OUT PULONG pulLength
|
|
);
|
|
|
|
CONFIGRET
|
|
GetChildDevInst(
|
|
IN LPWSTR pDeviceID,
|
|
OUT LPWSTR pRelatedDeviceID,
|
|
IN OUT PULONG pulLength
|
|
);
|
|
|
|
CONFIGRET
|
|
GetSiblingDevInst(
|
|
IN LPWSTR pDeviceID,
|
|
OUT LPWSTR pRelatedDeviceID,
|
|
IN OUT PULONG pulLength
|
|
);
|
|
|
|
//
|
|
// utility routines in rtravers.c that are used by rdevnode.c
|
|
//
|
|
#if 0
|
|
CONFIGRET
|
|
GetServiceDeviceListSize(
|
|
IN LPCWSTR pszService,
|
|
OUT PULONG pulLength
|
|
);
|
|
|
|
CONFIGRET
|
|
GetServiceDeviceList(
|
|
IN LPCWSTR pszService,
|
|
OUT LPWSTR pBuffer,
|
|
IN OUT PULONG pulLength,
|
|
IN ULONG ulFlags
|
|
);
|
|
#endif
|
|
|
|
|
|
CONFIGRET
|
|
GetInstanceListSize(
|
|
IN LPCWSTR pszDevice,
|
|
OUT PULONG pulLength
|
|
);
|
|
|
|
CONFIGRET
|
|
GetInstanceList(
|
|
IN LPCWSTR pszDevice,
|
|
IN OUT LPWSTR *pBuffer,
|
|
IN OUT PULONG pulLength
|
|
);
|
|
|
|
CONFIGRET
|
|
GetDeviceInstanceListSize(
|
|
IN LPCWSTR pszEnumerator,
|
|
OUT PULONG pulLength
|
|
);
|
|
|
|
CONFIGRET
|
|
GetDeviceInstanceList(
|
|
IN LPCWSTR pszEnumerator,
|
|
IN OUT LPWSTR *pBuffer,
|
|
IN OUT PULONG pulLength
|
|
);
|
|
|
|
|
|
|
|
//
|
|
// global data
|
|
//
|
|
extern HKEY ghEnumKey; // Key to HKLM\CCC\System\Enum - DO NOT MODIFY
|
|
extern HKEY ghServicesKey; // Key to HKLM\CCC\System\Services - DO NOT MODIFY
|
|
extern HKEY ghClassKey; // Key to HKLM\CCC\System\Class - NO NOT MODIFY
|
|
|
|
|
|
|
|
|
|
CONFIGRET
|
|
PNP_ValidateDeviceInstance(
|
|
IN handle_t hBinding,
|
|
IN LPWSTR pDeviceID,
|
|
IN ULONG ulFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This the server-side of an RPC remote call. This routine verifies whether
|
|
the specificed device instance is a valid device instance.
|
|
|
|
Arguments:
|
|
|
|
hBinding Not used.
|
|
|
|
DeviceInstance Null-terminated string that contains a device instance
|
|
to be validated.
|
|
|
|
ulFlags One of the CM_LOCATE_DEVNODE_* flags.
|
|
|
|
Return Value:
|
|
|
|
If the specified device instance is valid, it returns CR_SUCCESS,
|
|
otherwise it returns CR_ error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
LONG RegStatus = ERROR_SUCCESS;
|
|
HKEY hKey = NULL;
|
|
ULONG ulSize, ulValue;
|
|
|
|
|
|
UNREFERENCED_PARAMETER(hBinding);
|
|
|
|
//
|
|
// assume that the device instance string was checked for proper form
|
|
// before being added to the registry Enum tree
|
|
//
|
|
|
|
try {
|
|
//
|
|
// open a key to the specified device id
|
|
//
|
|
if (RegOpenKeyEx(ghEnumKey, pDeviceID, 0, KEY_READ,
|
|
&hKey) != ERROR_SUCCESS) {
|
|
Status = CR_NO_SUCH_DEVINST;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Will specify for now that a moved devinst cannot be located (we
|
|
// could allow this if we wanted to).
|
|
//
|
|
if (IsDeviceMoved(pDeviceID, hKey)) {
|
|
Status = CR_NO_SUCH_DEVINST;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// if we're locating a phantom devnode, it just has to exist
|
|
// in the registry (the above check) and not already be a
|
|
// phantom (private) devnode
|
|
//
|
|
if (ulFlags & CM_LOCATE_DEVNODE_PHANTOM) {
|
|
//
|
|
// verify that it's not a phantom
|
|
//
|
|
ulSize = sizeof(ULONG);
|
|
RegStatus = RegQueryValueEx(hKey, pszRegValuePhantom, NULL, NULL,
|
|
(LPBYTE)&ulValue, &ulSize);
|
|
|
|
if (RegStatus == ERROR_SUCCESS && ulValue == TRUE) {
|
|
Status = CR_NO_SUCH_DEVINST;
|
|
goto Clean0;
|
|
}
|
|
|
|
} else if (ulFlags & CM_LOCATE_DEVNODE_CANCELREMOVE) {
|
|
//
|
|
// In the CANCEL-REMOVE case, if the devnode has been removed,
|
|
// (made volatile) then convert it make to nonvoatile so it
|
|
// can be installed again without disappearing on the next
|
|
// boot. If it's not removed, then just verify that it is
|
|
// present.
|
|
//
|
|
|
|
//
|
|
// verify that the device id is actually present (FoundAtEnum)
|
|
//
|
|
ulSize = sizeof(ULONG);
|
|
RegStatus = RegQueryValueEx(hKey, pszRegValueFoundAtEnum, NULL,
|
|
NULL, (LPBYTE)&ulValue, &ulSize);
|
|
|
|
if (RegStatus != ERROR_SUCCESS || ulValue == FALSE) {
|
|
Status = CR_NO_SUCH_DEVINST;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Is this a device that is being removed on the next reboot?
|
|
//
|
|
ulSize = sizeof(ULONG);
|
|
if (RegQueryValueEx(hKey, pszRegValueStatusFlags, NULL, NULL,
|
|
(LPBYTE)&ulValue, &ulSize) != ERROR_SUCCESS) {
|
|
ulValue = 0;
|
|
}
|
|
|
|
if (ulValue & DN_WILL_BE_REMOVED) {
|
|
|
|
ULONG ulProfile = 0, ulCount = 0;
|
|
WCHAR RegStr[MAX_CM_PATH];
|
|
|
|
//
|
|
// This device will be removed on the next reboot,
|
|
// convert to nonvolatile.
|
|
//
|
|
Status = MakeKeyNonVolatile(pszRegPathEnum, pDeviceID);
|
|
if (Status != CR_SUCCESS) {
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Now make any keys that were "supposed" to be volatile
|
|
// back to volatile again!
|
|
//
|
|
wsprintf(RegStr, TEXT("%s\\%s"),
|
|
pszRegPathEnum,
|
|
pDeviceID);
|
|
|
|
MakeKeyVolatile(RegStr, pszRegKeyDeviceControl);
|
|
|
|
//
|
|
// Also, convert any profile specific keys to nonvolatile
|
|
//
|
|
Status = GetProfileCount(&ulCount);
|
|
if (Status != CR_SUCCESS) {
|
|
goto Clean0;
|
|
}
|
|
|
|
for (ulProfile = 1; ulProfile <= ulCount; ulProfile++) {
|
|
|
|
wsprintf(RegStr, TEXT("%s\\%04u\\%s"),
|
|
pszRegPathHwProfiles,
|
|
ulProfile,
|
|
pszRegPathEnum);
|
|
|
|
//
|
|
// Ignore the status for profile-specific keys since they may
|
|
// not exist.
|
|
//
|
|
MakeKeyNonVolatile(RegStr, pDeviceID);
|
|
}
|
|
|
|
//
|
|
// clean the DN_WILL_BE_REMOVED flag
|
|
//
|
|
CLEAR_FLAG(ulValue, DN_WILL_BE_REMOVED);
|
|
RegSetValueEx(hKey, pszRegValueStatusFlags, 0, REG_DWORD,
|
|
(LPBYTE)&ulValue, sizeof(ULONG));
|
|
}
|
|
}
|
|
|
|
//
|
|
// in the normal (non-phantom case), verify that the device id is
|
|
// actually present
|
|
//
|
|
else {
|
|
//
|
|
// verify that the device id is actually present (FoundAtEnum)
|
|
//
|
|
ulSize = sizeof(ULONG);
|
|
RegStatus = RegQueryValueEx(hKey, pszRegValueFoundAtEnum, NULL,
|
|
NULL, (LPBYTE)&ulValue, &ulSize);
|
|
|
|
if (RegStatus != ERROR_SUCCESS || ulValue == FALSE) {
|
|
Status = CR_NO_SUCH_DEVINST;
|
|
goto Clean0;
|
|
}
|
|
}
|
|
|
|
Clean0:
|
|
;
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
if (hKey != NULL) {
|
|
RegCloseKey(hKey);
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // PNP_ValidateDeviceInstance
|
|
|
|
|
|
|
|
|
|
CONFIGRET
|
|
PNP_GetRootDeviceInstance(
|
|
IN handle_t hBinding,
|
|
OUT LPWSTR pDeviceID,
|
|
IN ULONG ulLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This the server-side of an RPC remote call. This routine returns the
|
|
root device instance for the hardware tree.
|
|
|
|
Arguments:
|
|
|
|
hBinding Not used.
|
|
|
|
pDeviceID Pointer to a buffer that will hold the root device
|
|
instance ID string.
|
|
|
|
ulLength Size of pDeviceID buffer in characters.
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, it returns CR_SUCCESS, otherwise it returns
|
|
a CR_* error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
HKEY hKey = NULL;
|
|
|
|
|
|
UNREFERENCED_PARAMETER(hBinding);
|
|
|
|
try {
|
|
//
|
|
// first validate that the root device instance exists
|
|
//
|
|
if (RegOpenKeyEx(ghEnumKey, pszRegKeyRoot, 0, KEY_QUERY_VALUE,
|
|
&hKey) != ERROR_SUCCESS) {
|
|
//
|
|
// root doesn't exist, create root devinst
|
|
//
|
|
if (!CreateDeviceIDRegKey(ghEnumKey, pszRegKeyRoot)) {
|
|
Status = CR_REGISTRY_ERROR;
|
|
goto Clean0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// return the root device instance id
|
|
//
|
|
if (ulLength < (ULONG)lstrlen(pszRegKeyRoot)+1) {
|
|
Status = CR_BUFFER_SMALL;
|
|
goto Clean0;
|
|
}
|
|
|
|
lstrcpy(pDeviceID, pszRegKeyRoot);
|
|
|
|
Clean0:
|
|
;
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
if (hKey != NULL) {
|
|
RegCloseKey(hKey);
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // PNP_GetRootDeviceInstance
|
|
|
|
|
|
|
|
|
|
CONFIGRET
|
|
PNP_GetRelatedDeviceInstance(
|
|
IN handle_t hBinding,
|
|
IN ULONG ulRelationship,
|
|
IN LPWSTR pDeviceID,
|
|
OUT LPWSTR pRelatedDeviceID,
|
|
IN OUT PULONG pulLength,
|
|
IN ULONG ulFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This the server-side of an RPC remote call. This routine returns a
|
|
device instance that is related to the specified device instance.
|
|
|
|
Arguments:
|
|
|
|
hBinding Not used.
|
|
|
|
ulRelationship Specifies the relationship of the device instance to
|
|
be retrieved (can be PNP_GET_PARENT_DEVICE_INSTANCE,
|
|
PNP_GET_CHILD_DEVICE_INSTANCE, or
|
|
PNP_GET_SIBLING_DEVICE_INSTANCE).
|
|
|
|
pDeviceID Pointer to a buffer that contains the base device
|
|
instance string.
|
|
|
|
pRelatedDeviceID Pointer to a buffer that will receive the related
|
|
device instance string.
|
|
|
|
pulLength Length (in characters) of the RelatedDeviceInstance
|
|
buffer.
|
|
|
|
ulFlags Not used.
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, it returns CR_SUCCESS, otherwise it returns
|
|
a CR_* error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
|
|
|
|
UNREFERENCED_PARAMETER(hBinding);
|
|
UNREFERENCED_PARAMETER(ulFlags);
|
|
|
|
|
|
switch (ulRelationship) {
|
|
|
|
case PNP_GET_PARENT_DEVICE_INSTANCE:
|
|
Status = GetParentDevInst(
|
|
pDeviceID, pRelatedDeviceID, pulLength);
|
|
break;
|
|
|
|
case PNP_GET_CHILD_DEVICE_INSTANCE:
|
|
Status = GetChildDevInst(
|
|
pDeviceID, pRelatedDeviceID, pulLength);
|
|
break;
|
|
|
|
case PNP_GET_SIBLING_DEVICE_INSTANCE:
|
|
Status = GetSiblingDevInst(
|
|
pDeviceID, pRelatedDeviceID, pulLength);
|
|
break;
|
|
|
|
default:
|
|
Status = CR_FAILURE;
|
|
break;
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // PNP_GetRelatedDeviceInstance
|
|
|
|
|
|
|
|
|
|
CONFIGRET
|
|
PNP_EnumerateSubKeys(
|
|
IN handle_t hBinding,
|
|
IN ULONG ulBranch,
|
|
IN ULONG ulIndex,
|
|
OUT PWSTR Buffer,
|
|
IN ULONG ulLength,
|
|
OUT PULONG pulRequiredLen,
|
|
IN ULONG ulFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the RPC server entry point for the CM_Enumerate_Enumerators and
|
|
CM_Enumerate_Classes. It provides generic subkey enumeration based on
|
|
the specified registry branch.
|
|
|
|
Arguments:
|
|
|
|
hBinding Not used.
|
|
|
|
ulBranch Specifies which keys to enumerate.
|
|
|
|
ulIndex Index of the subkey key to retrieve.
|
|
|
|
Buffer Supplies the address of the buffer that receives the
|
|
subkey name.
|
|
|
|
ulLength Specifies the max size of the Buffer in characters.
|
|
|
|
pulRequired On output it contains the number of characters actually
|
|
copied to Buffer if it was successful, or the number of
|
|
characters required if the buffer was too small.
|
|
|
|
ulFlags Not used.
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, it returns CR_SUCCESS, otherwise it returns
|
|
a CR_* error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
LONG RegStatus = ERROR_SUCCESS;
|
|
HKEY hKey = NULL;
|
|
|
|
UNREFERENCED_PARAMETER(hBinding);
|
|
UNREFERENCED_PARAMETER(ulFlags);
|
|
|
|
|
|
try {
|
|
|
|
if (ulBranch == PNP_CLASS_SUBKEYS) {
|
|
//
|
|
// Use the global base CLASS registry key
|
|
//
|
|
hKey = ghClassKey;
|
|
}
|
|
else if (ulBranch == PNP_ENUMERATOR_SUBKEYS) {
|
|
//
|
|
// Use the global base ENUM registry key
|
|
//
|
|
hKey = ghEnumKey;
|
|
}
|
|
else {
|
|
Status = CR_FAILURE;
|
|
goto Clean0;
|
|
}
|
|
|
|
if (RegStatus != ERROR_SUCCESS) {
|
|
Status = CR_REGISTRY_ERROR;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// enumerate a subkey based on the passed in index value
|
|
//
|
|
*pulRequiredLen = ulLength;
|
|
|
|
RegStatus = RegEnumKeyEx(
|
|
hKey, ulIndex, Buffer, pulRequiredLen, NULL, NULL, NULL, NULL);
|
|
*pulRequiredLen += 1; // returned count doesn't include null terminator
|
|
|
|
if (RegStatus == ERROR_MORE_DATA) {
|
|
Status = CR_BUFFER_SMALL;
|
|
goto Clean0;
|
|
}
|
|
else if (RegStatus == ERROR_NO_MORE_ITEMS) {
|
|
*pulRequiredLen = 0;
|
|
Status = CR_NO_SUCH_VALUE;
|
|
goto Clean0;
|
|
}
|
|
else if (RegStatus != ERROR_SUCCESS) {
|
|
*pulRequiredLen = 0;
|
|
Status = CR_REGISTRY_ERROR;
|
|
goto Clean0;
|
|
}
|
|
|
|
Clean0:
|
|
; // do nothing
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // PNP_EnumerateSubKeys
|
|
|
|
|
|
|
|
|
|
CONFIGRET
|
|
PNP_GetDeviceList(
|
|
IN handle_t hBinding,
|
|
IN LPCWSTR pszFilter,
|
|
OUT LPWSTR Buffer,
|
|
IN OUT PULONG pulLength,
|
|
IN ULONG ulFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This the server-side of an RPC remote call. This routine returns a
|
|
list of device instances.
|
|
|
|
Arguments:
|
|
|
|
hBinding Not used.
|
|
|
|
pszFilter Optional parameter, controls which device ids are
|
|
returned.
|
|
|
|
Buffer Pointer to a buffer that will contain the multi_sz list
|
|
of device instance strings.
|
|
|
|
pulLength Size in characters of Buffer on input, size (in chars)
|
|
transferred on output
|
|
|
|
ulFlags Flag specifying which devices ids to return.
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, it returns CR_SUCCESS, otherwise it returns
|
|
a CR_* error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
LONG RegStatus = ERROR_SUCCESS;
|
|
ULONG ulBufferLen=0, ulSize=0, ulIndex=0, ulLen=0;
|
|
WCHAR RegStr[MAX_CM_PATH];
|
|
WCHAR szEnumerator[MAX_DEVICE_ID_LEN],
|
|
szDevice[MAX_DEVICE_ID_LEN],
|
|
szInstance[MAX_DEVICE_ID_LEN];
|
|
LPWSTR ptr = NULL;
|
|
|
|
UNREFERENCED_PARAMETER(hBinding); // used by rpc stubs to bind to server
|
|
|
|
|
|
try {
|
|
|
|
*Buffer = 0;
|
|
|
|
//---------------------------------------------------
|
|
// Service filter
|
|
//---------------------------------------------------
|
|
|
|
if (ulFlags & CM_GETIDLIST_FILTER_SERVICE) {
|
|
|
|
if (pszFilter == NULL) {
|
|
//
|
|
// the filter string is required for this flag
|
|
//
|
|
Status = CR_INVALID_POINTER;
|
|
goto Clean0;
|
|
}
|
|
|
|
Status = GetServiceDeviceList(pszFilter, Buffer, pulLength, ulFlags);
|
|
goto Clean0;
|
|
}
|
|
|
|
//---------------------------------------------------
|
|
// Enumerator filter
|
|
//---------------------------------------------------
|
|
|
|
else if (ulFlags & CM_GETIDLIST_FILTER_ENUMERATOR) {
|
|
|
|
if (pszFilter == NULL) {
|
|
//
|
|
// the filter string is required for this flag
|
|
//
|
|
Status = CR_INVALID_POINTER;
|
|
goto Clean0;
|
|
}
|
|
|
|
SplitDeviceInstanceString(
|
|
pszFilter, szEnumerator, szDevice, szInstance);
|
|
|
|
//
|
|
// if both the enumerator and device were specified, retrieve
|
|
// the device instances for this device
|
|
//
|
|
if (*szEnumerator != '\0' && *szDevice != '\0') {
|
|
|
|
ptr = Buffer;
|
|
Status = GetInstanceList(pszFilter, &ptr, pulLength);
|
|
}
|
|
|
|
//
|
|
// if just the enumerator was specified, retrieve all the device
|
|
// instances under this enumerator
|
|
//
|
|
else {
|
|
ptr = Buffer;
|
|
Status = GetDeviceInstanceList(pszFilter, &ptr, pulLength);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------
|
|
// No filtering
|
|
//-----------------------------------------------
|
|
|
|
else {
|
|
|
|
//
|
|
// return device instances for all enumerators (by enumerating
|
|
// the enumerators)
|
|
//
|
|
// Open a key to the Enum branch
|
|
//
|
|
ulSize = ulBufferLen = *pulLength; // total Buffer size
|
|
*pulLength = 0; // nothing copied yet
|
|
ptr = Buffer; // tail of the buffer
|
|
ulIndex = 0;
|
|
|
|
//
|
|
// Enumerate all the enumerators
|
|
//
|
|
while (RegStatus == ERROR_SUCCESS) {
|
|
|
|
ulLen = MAX_DEVICE_ID_LEN; // size in chars
|
|
RegStatus = RegEnumKeyEx(ghEnumKey, ulIndex, RegStr, &ulLen,
|
|
NULL, NULL, NULL, NULL);
|
|
|
|
ulIndex++;
|
|
|
|
if (RegStatus == ERROR_SUCCESS) {
|
|
|
|
Status = GetDeviceInstanceList(RegStr, &ptr, &ulSize);
|
|
|
|
if (Status != CR_SUCCESS) {
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
*pulLength += ulSize - 1; // length copied so far
|
|
ulSize = ulBufferLen - *pulLength; // buffer length left
|
|
}
|
|
}
|
|
*pulLength += 1; // now count the double-null
|
|
}
|
|
|
|
|
|
Clean0:
|
|
; // do nothing
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_SUCCESS;
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // PNP_GetDeviceList
|
|
|
|
|
|
|
|
|
|
CONFIGRET
|
|
PNP_GetDeviceListSize(
|
|
IN handle_t hBinding,
|
|
IN LPCWSTR pszFilter,
|
|
OUT PULONG pulLen,
|
|
IN ULONG ulFlags
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This the server-side of an RPC remote call. This routine returns the
|
|
size of a list of device instances.
|
|
|
|
Arguments:
|
|
|
|
hBinding Not used.
|
|
|
|
pszEnumerator Optional parameter, if specified the size will only
|
|
include device instances of this enumerator.
|
|
|
|
pulLen Returns the worst case estimate of the size of a
|
|
device instance list.
|
|
|
|
ulFlags Not used.
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, it returns CR_SUCCESS, otherwise it returns
|
|
a CR_* error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
ULONG ulSize = 0, ulIndex = 0;
|
|
WCHAR RegStr[MAX_CM_PATH];
|
|
ULONG RegStatus = ERROR_SUCCESS;
|
|
WCHAR szEnumerator[MAX_DEVICE_ID_LEN],
|
|
szDevice[MAX_DEVICE_ID_LEN],
|
|
szInstance[MAX_DEVICE_ID_LEN];
|
|
|
|
|
|
UNREFERENCED_PARAMETER(hBinding);
|
|
|
|
|
|
try {
|
|
//
|
|
// initialize output length param
|
|
//
|
|
*pulLen = 0;
|
|
|
|
//---------------------------------------------------
|
|
// Service filter
|
|
//---------------------------------------------------
|
|
|
|
if (ulFlags & CM_GETIDLIST_FILTER_SERVICE) {
|
|
|
|
if (pszFilter == NULL) {
|
|
//
|
|
// the filter string is required for this flag
|
|
//
|
|
Status = CR_INVALID_POINTER;
|
|
goto Clean0;
|
|
}
|
|
|
|
Status = GetServiceDeviceListSize(pszFilter, pulLen);
|
|
goto Clean0;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------
|
|
// Enumerator filter
|
|
//---------------------------------------------------
|
|
|
|
else if (ulFlags & CM_GETIDLIST_FILTER_ENUMERATOR) {
|
|
|
|
if (pszFilter == NULL) {
|
|
//
|
|
// the filter string is required for this flag
|
|
//
|
|
Status = CR_INVALID_POINTER;
|
|
goto Clean0;
|
|
}
|
|
|
|
SplitDeviceInstanceString(
|
|
pszFilter, szEnumerator, szDevice, szInstance);
|
|
|
|
//
|
|
// if both the enumerator and device were specified, retrieve
|
|
// the device instance list size for this device only
|
|
//
|
|
if (*szEnumerator != '\0' && *szDevice != '\0') {
|
|
|
|
Status = GetInstanceListSize(pszFilter, pulLen);
|
|
}
|
|
|
|
//
|
|
// if just the enumerator was specified, retrieve the size of
|
|
// all the device instances under this enumerator
|
|
//
|
|
else {
|
|
Status = GetDeviceInstanceListSize(pszFilter, pulLen);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------
|
|
// No filtering
|
|
//---------------------------------------------------
|
|
|
|
else {
|
|
|
|
//
|
|
// no enumerator was specified, return device instance size
|
|
// for all enumerators (by enumerating the enumerators)
|
|
//
|
|
//
|
|
ulIndex = 0;
|
|
|
|
while (RegStatus == ERROR_SUCCESS) {
|
|
|
|
ulSize = MAX_DEVICE_ID_LEN; // size in chars
|
|
|
|
RegStatus = RegEnumKeyEx(ghEnumKey, ulIndex, RegStr, &ulSize,
|
|
NULL, NULL, NULL, NULL);
|
|
ulIndex++;
|
|
|
|
if (RegStatus == ERROR_SUCCESS) {
|
|
|
|
Status = GetDeviceInstanceListSize(RegStr, &ulSize);
|
|
|
|
if (Status != CR_SUCCESS) {
|
|
goto Clean0;
|
|
}
|
|
*pulLen += ulSize;
|
|
}
|
|
}
|
|
}
|
|
|
|
*pulLen += 1; // add extra char for double null term
|
|
|
|
|
|
Clean0:
|
|
; // do nothing
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // PNP_GetDeviceListSize
|
|
|
|
|
|
|
|
|
|
CONFIGRET
|
|
PNP_GetDepth(
|
|
IN handle_t hBinding,
|
|
IN LPCWSTR pszDeviceID,
|
|
OUT PULONG pulDepth,
|
|
IN ULONG ulFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This the server-side of an RPC remote call. This routine returns the
|
|
depth of a device instance.
|
|
|
|
Arguments:
|
|
|
|
hBinding Not used.
|
|
|
|
pszDeviceID Device instance to find the depth of.
|
|
|
|
pulDepth Returns the depth of pszDeviceID.
|
|
|
|
ulFlags Not used.
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, it returns CR_SUCCESS, otherwise it returns
|
|
a CR_* error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
LONG RegStatus = ERROR_SUCCESS;
|
|
HKEY hKey = NULL;
|
|
WCHAR szParent[MAX_DEVICE_ID_LEN];
|
|
ULONG ulLength = 0;
|
|
|
|
|
|
UNREFERENCED_PARAMETER(hBinding);
|
|
UNREFERENCED_PARAMETER(ulFlags);
|
|
|
|
|
|
try {
|
|
//
|
|
// Starting with the device ID passed in, backtrack through
|
|
// each subsequent parent until we get to the root
|
|
//
|
|
lstrcpy(szParent, pszDeviceID);
|
|
*pulDepth = 0;
|
|
|
|
|
|
while (!IsRootDeviceID(szParent)) {
|
|
//
|
|
// Open a key to the device instance in the registry
|
|
//
|
|
RegStatus = RegOpenKeyEx(ghEnumKey, szParent, 0,
|
|
KEY_QUERY_VALUE, &hKey);
|
|
|
|
if (RegStatus != ERROR_SUCCESS) {
|
|
Status = CR_INVALID_DEVINST;
|
|
goto Clean0;
|
|
}
|
|
|
|
ulLength = MAX_DEVICE_ID_LEN * sizeof(WCHAR);
|
|
RegStatus = RegQueryValueEx(hKey, pszRegValueBaseDevicePath, NULL,
|
|
NULL, (LPBYTE)szParent, &ulLength);
|
|
|
|
if (RegStatus != ERROR_SUCCESS) {
|
|
Status = CR_REGISTRY_ERROR;
|
|
goto Clean0;
|
|
}
|
|
|
|
*pulDepth += 1;
|
|
|
|
RegCloseKey(hKey);
|
|
hKey = NULL;
|
|
}
|
|
|
|
|
|
Clean0:
|
|
;
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
if (hKey != NULL) {
|
|
RegCloseKey(hKey);
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // PNP_GetDepth
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Private functions
|
|
//-------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
CONFIGRET
|
|
GetParentDevInst(
|
|
IN LPWSTR pDeviceID,
|
|
OUT LPWSTR pRelatedDeviceID,
|
|
IN OUT PULONG pulLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns the parent device instance of the specified base
|
|
device instance.
|
|
|
|
Arguments:
|
|
|
|
pDeviceID Pointer to a buffer that contains the base device
|
|
instance string.
|
|
|
|
pRelatedDeviceID Pointer to a buffer that will receive the related
|
|
device instance string.
|
|
|
|
pulLength Length (in characters) of the RelatedDeviceInstance
|
|
buffer.
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, it returns CR_SUCCESS, otherwise it returns
|
|
a CR_* error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
LONG RegStatus = ERROR_SUCCESS;
|
|
HKEY hKey = NULL;
|
|
ULONG ulSize = 0;
|
|
|
|
|
|
try {
|
|
//
|
|
// first verify it isn't the root (which has no parent by definition)
|
|
//
|
|
if (IsRootDeviceID(pDeviceID)) {
|
|
Status = CR_NO_SUCH_DEVINST;
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// open a key to the specified device id
|
|
//
|
|
RegStatus = RegOpenKeyEx(ghEnumKey, pDeviceID, 0,
|
|
KEY_QUERY_VALUE, &hKey);
|
|
|
|
if (RegStatus != ERROR_SUCCESS) {
|
|
*pulLength = 0;
|
|
Status = CR_INVALID_DEVINST;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// get the parent device instance by querying the BaseDevicePath value
|
|
//
|
|
ulSize = *pulLength * sizeof(WCHAR);
|
|
RegStatus = RegQueryValueEx(
|
|
hKey, pszRegValueBaseDevicePath, NULL, NULL,
|
|
(LPBYTE)pRelatedDeviceID, &ulSize);
|
|
|
|
if (RegStatus != ERROR_SUCCESS || *pRelatedDeviceID == '\0') {
|
|
Status = CR_REGISTRY_ERROR;
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Validate the parent devnode, this could be a stale devnode
|
|
// for a child and parent that have gone away
|
|
//
|
|
if (!IsValidDeviceID(pRelatedDeviceID, NULL, PNP_PRESENT | PNP_NOT_MOVED)) {
|
|
Status = CR_NO_SUCH_DEVINST;
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
*pulLength = ulSize;
|
|
|
|
|
|
Clean0:
|
|
; // do nothing
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
if (hKey != NULL) {
|
|
RegCloseKey(hKey);
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // GetParentDevInst
|
|
|
|
|
|
|
|
|
|
CONFIGRET
|
|
GetChildDevInst(
|
|
IN LPWSTR pDeviceID,
|
|
OUT LPWSTR pRelatedDeviceID,
|
|
IN OUT PULONG pulLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns the first child device instance of the specified base
|
|
device instance.
|
|
|
|
Arguments:
|
|
|
|
pDeviceID Pointer to a buffer that contains the base device
|
|
instance string.
|
|
|
|
pRelatedDeviceID Pointer to a buffer that will receive the related
|
|
device instance string.
|
|
|
|
pulLength Length (in characters) of the pRelatedDeviceID buffer.
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, it returns CR_SUCCESS, otherwise it returns
|
|
a CR_* error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
LONG RegStatus = ERROR_SUCCESS;
|
|
HKEY hKey = NULL;
|
|
DWORD ulSize = 0;
|
|
PWSTR Buffer = NULL, Component = NULL;
|
|
|
|
|
|
try {
|
|
//
|
|
// open a key to the specified device id
|
|
//
|
|
RegStatus = RegOpenKeyEx(ghEnumKey, pDeviceID, 0,
|
|
KEY_QUERY_VALUE, &hKey);
|
|
|
|
if (RegStatus != ERROR_SUCCESS) {
|
|
*pulLength = 0;
|
|
Status = CR_INVALID_DEVINST;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// retrieve the list of attached components
|
|
//
|
|
ulSize = 0;
|
|
RegStatus = RegQueryValueEx(hKey, pszRegValueAttachedComponents,
|
|
NULL, NULL, NULL, &ulSize);
|
|
|
|
if (RegStatus != ERROR_SUCCESS) {
|
|
Status = CR_NO_SUCH_DEVINST;
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
Buffer = (PWSTR)malloc(ulSize); // allocate a buffer
|
|
if (Buffer == NULL) {
|
|
Status = CR_OUT_OF_MEMORY;
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
RegStatus = RegQueryValueEx(hKey, pszRegValueAttachedComponents,
|
|
NULL, NULL, (LPBYTE)Buffer, &ulSize);
|
|
|
|
if (RegStatus != ERROR_SUCCESS || *Buffer == '\0') {
|
|
Status = CR_NO_SUCH_DEVINST; // at the bottom, no children
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// By definition, GetChild returns the first (present) child in the
|
|
// list of attached components.
|
|
//
|
|
for (Component = Buffer;
|
|
*Component;
|
|
Component += lstrlen(Component) + 1) {
|
|
|
|
//
|
|
// Validate the presense of this child
|
|
//
|
|
if (IsValidDeviceID(Component, NULL, PNP_PRESENT | PNP_NOT_MOVED)) {
|
|
break; // valid child found, we're done
|
|
}
|
|
|
|
//
|
|
// this child isn't valid - if it just doesn't exist, then delete
|
|
// the orphaned device instance key
|
|
//
|
|
if (!IsValidDeviceID(Component, NULL, 0)) {
|
|
|
|
if (MultiSzDeleteStringW(Buffer, Component)) {
|
|
|
|
ulSize = MultiSzSizeW(Buffer) * sizeof(WCHAR);
|
|
RegStatus = RegSetValueEx(hKey,
|
|
pszRegValueAttachedComponents, 0,
|
|
REG_MULTI_SZ, (LPBYTE)Buffer,
|
|
ulSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (*Component == 0x0) {
|
|
//
|
|
// no present children were found
|
|
//
|
|
Status = CR_NO_SUCH_DEVINST;
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// we have the first child, make sure buffer is adequate
|
|
//
|
|
if (*pulLength < (ULONG)lstrlen(Component)+1) {
|
|
Status = CR_BUFFER_SMALL;
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// copy the child to caller's buffer
|
|
//
|
|
lstrcpy(pRelatedDeviceID, Component);
|
|
*pulLength = lstrlen(pRelatedDeviceID)+1;
|
|
|
|
|
|
Clean0:
|
|
; // do nothing
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
if (hKey != NULL) {
|
|
RegCloseKey(hKey);
|
|
}
|
|
if (Buffer != NULL) {
|
|
free(Buffer);
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // GetChildDevInst
|
|
|
|
|
|
|
|
|
|
|
|
CONFIGRET
|
|
GetSiblingDevInst(
|
|
IN LPWSTR pDeviceID,
|
|
OUT LPWSTR pRelatedDeviceID,
|
|
IN OUT PULONG pulLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns the sibling device instance of the specified base
|
|
device instance. GetChildDevInst must be called first.
|
|
|
|
Arguments:
|
|
|
|
pDeviceID Pointer to a buffer that contains the base device
|
|
instance string.
|
|
|
|
pRelatedDeviceID Pointer to a buffer that will receive the related
|
|
device instance string.
|
|
|
|
pulLength Length (in characters) of the pRelatedDeviceID buffer.
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, it returns CR_SUCCESS, otherwise it returns
|
|
a CR_* error code.
|
|
|
|
--*/
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
LONG RegStatus = ERROR_SUCCESS;
|
|
WCHAR RegStr[MAX_CM_PATH], pParentDeviceID[MAX_DEVICE_ID_LEN];
|
|
HKEY hKey = NULL;
|
|
DWORD ulSize = 0;
|
|
PWSTR Buffer = NULL, Component = NULL;
|
|
|
|
|
|
try {
|
|
//
|
|
// first verify it isn't the root (which has no siblings by definition)
|
|
//
|
|
if (IsRootDeviceID(pDeviceID)) {
|
|
Status = CR_NO_SUCH_DEVINST;
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// open a key to the specified device id
|
|
//
|
|
RegStatus = RegOpenKeyEx(ghEnumKey, pDeviceID, 0,
|
|
KEY_QUERY_VALUE, &hKey);
|
|
|
|
if (RegStatus != ERROR_SUCCESS) {
|
|
*pulLength = 0;
|
|
Status = CR_INVALID_DEVINST;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// retrieve the base device path
|
|
//
|
|
ulSize = MAX_DEVICE_ID_LEN * sizeof(WCHAR);
|
|
RegStatus = RegQueryValueEx(hKey, pszRegValueBaseDevicePath,
|
|
NULL, NULL, (LPBYTE)pParentDeviceID,
|
|
&ulSize);
|
|
|
|
if (RegStatus != ERROR_SUCCESS) {
|
|
Status = CR_REGISTRY_ERROR;
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
RegCloseKey(hKey);
|
|
hKey = NULL;
|
|
|
|
//
|
|
// Open the base device path (parent) of the specified device id
|
|
//
|
|
RegStatus = RegOpenKeyEx(ghEnumKey, pParentDeviceID, 0,
|
|
KEY_READ | KEY_WRITE, &hKey);
|
|
|
|
if (RegStatus != ERROR_SUCCESS) {
|
|
Status = CR_REGISTRY_ERROR;
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// 1. validate this parent device id
|
|
//
|
|
if (!IsValidDeviceID(pParentDeviceID, hKey, PNP_NOT_MOVED)) {
|
|
Status = CR_NO_SUCH_DEVINST;
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// 2. Retrieve the attached component list
|
|
//
|
|
ulSize = 0;
|
|
RegStatus = RegQueryValueEx(hKey, pszRegValueAttachedComponents,
|
|
NULL, NULL, NULL, &ulSize);
|
|
|
|
if (RegStatus != ERROR_SUCCESS) {
|
|
Status = CR_NO_SUCH_DEVINST; // no children
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
Buffer = (PWSTR)malloc(ulSize); // allocate a buffer
|
|
if (Buffer == NULL) {
|
|
Status = CR_OUT_OF_MEMORY;
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
RegStatus = RegQueryValueEx(hKey, pszRegValueAttachedComponents,
|
|
NULL, NULL, (LPBYTE)Buffer, &ulSize);
|
|
|
|
if (RegStatus != ERROR_SUCCESS || *Buffer == '\0') {
|
|
Status = CR_NO_SUCH_DEVINST; // bottom, no siblings
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// 3. find the member in the attached component list that matches the
|
|
// specified device instance
|
|
//
|
|
for (Component = Buffer;
|
|
*Component;
|
|
Component += lstrlen(Component) + 1) {
|
|
|
|
if (lstrcmpi(Component, pDeviceID) == 0) {
|
|
break; // match found
|
|
}
|
|
}
|
|
|
|
if (*Component == 0x0) {
|
|
//
|
|
// no match was found, an error occured
|
|
//
|
|
Status = CR_NO_SUCH_DEVINST;
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// 4. match found, is there another sibling in the list?
|
|
//
|
|
for (Component += lstrlen(Component) + 1;
|
|
*Component;
|
|
Component += lstrlen(Component) + 1) {
|
|
|
|
//
|
|
// take the first present device instance from this point on as
|
|
// the next sibling
|
|
//
|
|
if (IsValidDeviceID(Component, NULL, PNP_STRICT)) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// this child isn't valid - if it just doesn't exist, then delete
|
|
// the orphaned device instance key
|
|
//
|
|
if (!IsValidDeviceID(Component, NULL, 0)) {
|
|
|
|
if (MultiSzDeleteStringW(Buffer, Component)) {
|
|
|
|
ulSize = MultiSzSizeW(Buffer) * sizeof(WCHAR);
|
|
RegStatus = RegSetValueEx(hKey,
|
|
pszRegValueAttachedComponents, 0,
|
|
REG_MULTI_SZ, (LPBYTE)Buffer,
|
|
ulSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (*Component == 0x0) {
|
|
//
|
|
// last component in the list, no more siblings
|
|
//
|
|
Status = CR_NO_SUCH_DEVINST;
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
if (*pulLength < (ULONG)lstrlen(Component)+1) {
|
|
Status = CR_BUFFER_SMALL;
|
|
goto Clean0;
|
|
}
|
|
|
|
lstrcpy(pRelatedDeviceID, Component);
|
|
*pulLength = (ULONG)lstrlen(Component) + 1;
|
|
|
|
|
|
Clean0:
|
|
; // do nothing
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
if (hKey != NULL) {
|
|
RegCloseKey(hKey);
|
|
}
|
|
if (Buffer != NULL) {
|
|
free(Buffer);
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // GetSiblingDevInst
|
|
|
|
|
|
|
|
|
|
CONFIGRET
|
|
GetServiceDeviceListSize(
|
|
IN LPCWSTR pszService,
|
|
OUT PULONG pulLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns the a list of device instances for the specificed
|
|
enumerator.
|
|
|
|
Arguments:
|
|
|
|
pszService service whose device instances are to be listed
|
|
|
|
pulLength On output, specifies the size in characters required to hold
|
|
the device instance list.
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, it returns CR_SUCCESS, otherwise it returns
|
|
a CR_* error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
ULONG ulType = 0, ulCount = 0, ulMaxValueData = 0, ulSize = 0;
|
|
HKEY hKey = NULL, hEnumKey = NULL;
|
|
|
|
|
|
try {
|
|
//
|
|
// Open a key to the service branch
|
|
//
|
|
if (RegOpenKeyEx(ghServicesKey, pszService, 0, KEY_READ,
|
|
&hKey) != ERROR_SUCCESS) {
|
|
|
|
Status = CR_REGISTRY_ERROR;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// check if the service is specialy marked as type
|
|
// PlugPlayServiceSoftware, in which case I will not
|
|
// generate any madeup device ids and fail the call.
|
|
//
|
|
ulSize = sizeof(ulType);
|
|
if (RegQueryValueEx(hKey, pszRegValuePnPServiceType, NULL, NULL,
|
|
(LPBYTE)&ulType, &ulSize) == ERROR_SUCCESS) {
|
|
|
|
if (ulType == PlugPlayServiceSoftware) {
|
|
|
|
Status = CR_NO_SUCH_VALUE;
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// open the Enum key
|
|
//
|
|
if (RegOpenKeyEx(hKey, pszRegKeyEnum, 0, KEY_READ,
|
|
&hEnumKey) != ERROR_SUCCESS) {
|
|
//
|
|
// Enum key doesn't exist so one will be generated, estimate
|
|
// worst case device id size for the single generated device id
|
|
//
|
|
*pulLength = MAX_DEVICE_ID_LEN;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// retrieve the count of device instances controlled by this service
|
|
//
|
|
ulSize = sizeof(ulCount);
|
|
if (RegQueryValueEx(hEnumKey, pszRegValueCount, NULL, NULL,
|
|
(LPBYTE)&ulCount, &ulSize) != ERROR_SUCCESS) {
|
|
ulCount = 1; // if empty, I'll generate one
|
|
}
|
|
|
|
if (ulCount == 0) {
|
|
ulCount++; // if empty, I'll generate one
|
|
}
|
|
|
|
if (RegQueryInfoKey(hEnumKey, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, &ulMaxValueData, NULL, NULL) != ERROR_SUCCESS) {
|
|
|
|
*pulLength = ulCount * MAX_DEVICE_ID_LEN;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// worst case estimate is multiply number of device instances time
|
|
// length of the longest one + 2 null terminators
|
|
//
|
|
*pulLength = ulCount * (ulMaxValueData+1)/sizeof(WCHAR) + 2;
|
|
|
|
|
|
Clean0:
|
|
;
|
|
|
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
if (hEnumKey != NULL) {
|
|
RegCloseKey(hEnumKey);
|
|
}
|
|
if (hKey != NULL) {
|
|
RegCloseKey(hKey);
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // GetServiceDeviceListSize
|
|
|
|
|
|
|
|
|
|
CONFIGRET
|
|
GetServiceDeviceList(
|
|
IN LPCWSTR pszService,
|
|
OUT LPWSTR pBuffer,
|
|
IN OUT PULONG pulLength,
|
|
IN ULONG ulFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns the a list of device instances for the specificed
|
|
enumerator.
|
|
|
|
Arguments:
|
|
|
|
pszService service whose device instances are to be listed
|
|
|
|
pBuffer Pointer to a buffer that will hold the list in multi-sz
|
|
format
|
|
|
|
pulLength On input, specifies the size in characters of Buffer, on
|
|
Output, specifies the size in characters actually copied
|
|
to the buffer.
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, it returns CR_SUCCESS, otherwise it returns
|
|
a CR_* error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
LONG RegStatus = ERROR_SUCCESS;
|
|
WCHAR RegStr[MAX_CM_PATH], szDeviceID[MAX_DEVICE_ID_LEN+1];
|
|
ULONG ulType=0, ulBufferLen=0, ulSize=0, ulCount=0, i=0;
|
|
HKEY hKey = NULL, hEnumKey = NULL;
|
|
PPLUGPLAY_CONTROL_LEGACY_DEVGEN_DATA pControlData;
|
|
NTSTATUS NtStatus = STATUS_SUCCESS;
|
|
BOOL ServiceIsPlugPlay = FALSE;
|
|
|
|
|
|
try {
|
|
|
|
*pBuffer = '\0';
|
|
ulBufferLen = *pulLength;
|
|
|
|
//
|
|
// Open a key to the service branch
|
|
//
|
|
if (RegOpenKeyEx(ghServicesKey, pszService, 0, KEY_READ,
|
|
&hKey) != ERROR_SUCCESS) {
|
|
|
|
*pulLength = 0;
|
|
Status = CR_REGISTRY_ERROR;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// check if the service is specialy marked as type
|
|
// PlugPlayServiceSoftware, in which case I will not
|
|
// generate any madeup device ids and fail the call.
|
|
//
|
|
ulSize = sizeof(ulType);
|
|
if (RegQueryValueEx(hKey, pszRegValuePnPServiceType, NULL, NULL,
|
|
(LPBYTE)&ulType, &ulSize) == ERROR_SUCCESS) {
|
|
|
|
if (ulType == PlugPlayServiceSoftware) {
|
|
//
|
|
// for PlugPlayServiceSoftware value, fail the call
|
|
//
|
|
*pulLength = 0;
|
|
Status = CR_NO_SUCH_VALUE;
|
|
goto Clean0;
|
|
|
|
}
|
|
|
|
ServiceIsPlugPlay = TRUE;
|
|
}
|
|
|
|
//
|
|
// open the Enum key
|
|
//
|
|
RegStatus = RegOpenKeyEx(hKey, pszRegKeyEnum, 0, KEY_READ,
|
|
&hEnumKey);
|
|
|
|
if (RegStatus == ERROR_SUCCESS) {
|
|
//
|
|
// retrieve count of device instances controlled by this service
|
|
//
|
|
ulSize = sizeof(ulCount);
|
|
if (RegQueryValueEx(hEnumKey, pszRegValueCount, NULL, NULL,
|
|
(LPBYTE)&ulCount, &ulSize) != ERROR_SUCCESS) {
|
|
ulCount = 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// if there are no device instances, create a default one
|
|
//
|
|
if (RegStatus != ERROR_SUCCESS || ulCount == 0) {
|
|
|
|
if (ulFlags & CM_GETIDLIST_DONOTGENERATE) {
|
|
//
|
|
// If I'm calling this routine privately, don't generate
|
|
// a new device instance, just give me an emptry list
|
|
//
|
|
*pBuffer = '\0';
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
if (ServiceIsPlugPlay) {
|
|
//
|
|
// Also, if plugplayservice type set, don't generate a
|
|
// new device instance, just return success with an empty list
|
|
//
|
|
*pBuffer = '\0';
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
ulSize = sizeof(PLUGPLAY_CONTROL_LEGACY_DEVGEN_DATA) +
|
|
MAX_DEVICE_ID_LEN * sizeof(WCHAR);
|
|
|
|
pControlData = (PPLUGPLAY_CONTROL_LEGACY_DEVGEN_DATA)malloc(ulSize);
|
|
if (pControlData == NULL) {
|
|
Status = CR_OUT_OF_MEMORY;
|
|
goto Clean0;
|
|
}
|
|
|
|
RtlInitUnicodeString(&(pControlData->ServiceName), pszService);
|
|
NtStatus = NtPlugPlayControl(PlugPlayControlGenerateLegacyDevice,
|
|
pControlData, ulSize, &ulSize);
|
|
|
|
if (NtStatus != STATUS_SUCCESS) {
|
|
*pBuffer = '\0';
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
lstrcpy(pBuffer, pControlData->DeviceInstance);
|
|
*(pBuffer + lstrlen(pBuffer) + 1) = '\0'; // double null terminate
|
|
*pulLength = lstrlen(pBuffer) + 2;
|
|
goto Clean0;
|
|
}
|
|
|
|
|
|
//
|
|
// retrieve each device instance
|
|
//
|
|
for (i = 0; i < ulCount; i++) {
|
|
|
|
wsprintf(RegStr, TEXT("%d"), i);
|
|
|
|
ulSize = MAX_DEVICE_ID_LEN * sizeof(WCHAR);
|
|
|
|
RegStatus = RegQueryValueEx(hEnumKey, RegStr, NULL, NULL,
|
|
(LPBYTE)szDeviceID, &ulSize);
|
|
|
|
if (RegStatus != ERROR_SUCCESS) {
|
|
Status = CR_REGISTRY_ERROR;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// this string is not always null-terminated when I read it from the
|
|
// registry, even though it's REG_SZ.
|
|
//
|
|
ulSize /= sizeof(WCHAR);
|
|
|
|
if (szDeviceID[ulSize-1] != '\0') {
|
|
szDeviceID[ulSize] = '\0';
|
|
}
|
|
|
|
ulSize = ulBufferLen * sizeof(WCHAR); // total buffer size in bytes
|
|
|
|
if (!MultiSzAppendW(pBuffer, &ulSize, szDeviceID)) {
|
|
Status = CR_BUFFER_SMALL;
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
*pulLength = ulSize/sizeof(WCHAR); // chars to transfer
|
|
}
|
|
|
|
|
|
Clean0:
|
|
;
|
|
|
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
if (hEnumKey != NULL) {
|
|
RegCloseKey(hEnumKey);
|
|
}
|
|
if (hKey != NULL) {
|
|
RegCloseKey(hKey);
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // GetServiceDeviceList
|
|
|
|
|
|
|
|
|
|
CONFIGRET
|
|
GetInstanceListSize(
|
|
IN LPCWSTR pszDevice,
|
|
OUT PULONG pulLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns the a list of device instances for the specificed
|
|
enumerator.
|
|
|
|
Arguments:
|
|
|
|
pszDevice device whose instances are to be listed
|
|
|
|
pulLength On output, specifies the size in characters required to hold
|
|
the device istance list.
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, it returns CR_SUCCESS, otherwise it returns
|
|
a CR_* error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
ULONG ulCount = 0, ulMaxKeyLen = 0;
|
|
HKEY hKey = NULL;
|
|
|
|
|
|
try {
|
|
//
|
|
// Open a key to the device instance
|
|
//
|
|
if (RegOpenKeyEx(ghEnumKey, pszDevice, 0, KEY_READ,
|
|
&hKey) != ERROR_SUCCESS) {
|
|
|
|
Status = CR_REGISTRY_ERROR;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// how many instance keys are under this device?
|
|
//
|
|
if (RegQueryInfoKey(hKey, NULL, NULL, NULL, &ulCount, &ulMaxKeyLen,
|
|
NULL, NULL, NULL, NULL, NULL, NULL)
|
|
!= ERROR_SUCCESS) {
|
|
ulCount = 0;
|
|
ulMaxKeyLen = 0;
|
|
}
|
|
|
|
//
|
|
// do worst case estimate:
|
|
// length of the <enumerator>\<root> string +
|
|
// 1 char for the back slash before the instance +
|
|
// the length of the longest instance key + null term +
|
|
// multiplied by the number of instances under this device.
|
|
//
|
|
*pulLength = ulCount * (lstrlen(pszDevice) + ulMaxKeyLen + 2);
|
|
|
|
|
|
Clean0:
|
|
;
|
|
|
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
if (hKey != NULL) {
|
|
RegCloseKey(hKey);
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // GetInstanceListSize
|
|
|
|
|
|
|
|
|
|
CONFIGRET
|
|
GetInstanceList(
|
|
IN LPCWSTR pszDevice,
|
|
IN OUT LPWSTR *pBuffer,
|
|
IN OUT PULONG pulLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns the a list of device instances for the specificed
|
|
enumerator.
|
|
|
|
Arguments:
|
|
|
|
hEnumKey Handle to open Enum registry key
|
|
|
|
pszDevice device whose instances are to be listed
|
|
|
|
pBuffer On input, this points to place where the next element
|
|
should be copied (the buffer tail), on output, it also
|
|
points to the end of the buffer.
|
|
|
|
pulLength On input, specifies the size in characters of Buffer, on
|
|
Output, specifies how many characters actuall copied to
|
|
the buffer. Includes an extra byte for the double-null term.
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, it returns CR_SUCCESS, otherwise it returns
|
|
a CR_* error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
LONG RegStatus = ERROR_SUCCESS;
|
|
WCHAR RegStr[MAX_CM_PATH], szInstance[MAX_DEVICE_ID_LEN];
|
|
ULONG ulBufferLen=0, ulSize=0, ulIndex=0, ulLen=0;
|
|
HKEY hKey = NULL;
|
|
|
|
|
|
try {
|
|
//
|
|
// Open a key for this Enumerator\Device branch
|
|
//
|
|
if (RegOpenKeyEx(ghEnumKey, pszDevice, 0, KEY_ENUMERATE_SUB_KEYS,
|
|
&hKey) != ERROR_SUCCESS) {
|
|
Status = CR_REGISTRY_ERROR;
|
|
goto Clean0;
|
|
}
|
|
|
|
ulBufferLen = *pulLength; // total size of pBuffer
|
|
*pulLength = 0; // no data copied yet
|
|
ulIndex = 0;
|
|
|
|
//
|
|
// enumerate the instance keys
|
|
//
|
|
while (RegStatus == ERROR_SUCCESS) {
|
|
|
|
ulLen = MAX_DEVICE_ID_LEN; // size in chars
|
|
|
|
RegStatus = RegEnumKeyEx(hKey, ulIndex, szInstance, &ulLen,
|
|
NULL, NULL, NULL, NULL);
|
|
|
|
ulIndex++;
|
|
|
|
if (RegStatus == ERROR_SUCCESS) {
|
|
|
|
wsprintf(RegStr, TEXT("%s\\%s"),
|
|
pszDevice,
|
|
szInstance);
|
|
|
|
if (IsValidDeviceID(RegStr, NULL, PNP_NOT_MOVED | PNP_NOT_REMOVED)) {
|
|
|
|
ulSize = lstrlen(RegStr) + 1; // size of new element
|
|
*pulLength += ulSize; // size copied so far
|
|
|
|
if (*pulLength + 1 > ulBufferLen) {
|
|
*pulLength = 0;
|
|
Status = CR_BUFFER_SMALL;
|
|
goto Clean0;
|
|
}
|
|
|
|
lstrcpy(*pBuffer, RegStr); // copy the element
|
|
*pBuffer += ulSize; // move to tail of buffer
|
|
**pBuffer = 0x0; // double-null terminate it
|
|
}
|
|
}
|
|
}
|
|
|
|
*pulLength += 1; // include room for double-null terminator
|
|
|
|
Clean0:
|
|
;
|
|
|
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
if (hKey != NULL) {
|
|
RegCloseKey(hKey);
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // GetInstanceList
|
|
|
|
|
|
|
|
|
|
CONFIGRET
|
|
GetDeviceInstanceListSize(
|
|
IN LPCWSTR pszEnumerator,
|
|
OUT PULONG pulLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns the a list of device instances for the specificed
|
|
enumerator.
|
|
|
|
Arguments:
|
|
|
|
pszEnumerator Enumerator whose device instances are to be listed
|
|
|
|
pulLength On output, specifies how many characters required to hold
|
|
the device instance list.
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, it returns CR_SUCCESS, otherwise it returns
|
|
a CR_* error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
LONG RegStatus = ERROR_SUCCESS;
|
|
ULONG ulSize = 0, ulIndex = 0;
|
|
WCHAR RegStr[MAX_CM_PATH], szDevice[MAX_DEVICE_ID_LEN];
|
|
HKEY hKey = NULL;
|
|
|
|
|
|
try {
|
|
//
|
|
// initialize output length param
|
|
//
|
|
*pulLength = 0;
|
|
|
|
//
|
|
// Open a key for this Enumerator branch
|
|
//
|
|
if (RegOpenKeyEx(ghEnumKey, pszEnumerator, 0, KEY_ENUMERATE_SUB_KEYS,
|
|
&hKey) != ERROR_SUCCESS) {
|
|
Status = CR_REGISTRY_ERROR;
|
|
goto Clean0;
|
|
}
|
|
|
|
|
|
//
|
|
// Enumerate the device keys
|
|
//
|
|
ulIndex = 0;
|
|
|
|
while (RegStatus == ERROR_SUCCESS) {
|
|
|
|
ulSize = MAX_DEVICE_ID_LEN; // size in chars
|
|
|
|
RegStatus = RegEnumKeyEx(hKey, ulIndex, szDevice, &ulSize,
|
|
NULL, NULL, NULL, NULL);
|
|
ulIndex++;
|
|
|
|
if (RegStatus == ERROR_SUCCESS) {
|
|
//
|
|
// Retreive the size of the instance list for this device
|
|
//
|
|
wsprintf(RegStr, TEXT("%s\\%s"),
|
|
pszEnumerator,
|
|
szDevice);
|
|
|
|
if ((Status = GetInstanceListSize(RegStr, &ulSize)) != CR_SUCCESS) {
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
*pulLength += ulSize;
|
|
}
|
|
}
|
|
|
|
|
|
Clean0:
|
|
;
|
|
|
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
if (hKey != NULL) {
|
|
RegCloseKey(hKey);
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // GetDeviceInstanceListSize
|
|
|
|
|
|
|
|
|
|
CONFIGRET
|
|
GetDeviceInstanceList(
|
|
IN LPCWSTR pszEnumerator,
|
|
IN OUT LPWSTR *pBuffer,
|
|
IN OUT PULONG pulLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns the a list of device instances for the specificed
|
|
enumerator.
|
|
|
|
Arguments:
|
|
|
|
hEnumKey Handle of open Enum (parent) registry key
|
|
|
|
pszEnumerator Enumerator whose device instances are to be listed
|
|
|
|
pBuffer On input, this points to place where the next element
|
|
should be copied (the buffer tail), on output, it also
|
|
points to the end of the buffer.
|
|
|
|
pulLength On input, specifies the size in characters of Buffer, on
|
|
Output, specifies how many characters actuall copied to
|
|
the buffer. Includes an extra byte for the double-null
|
|
term.
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, it returns CR_SUCCESS, otherwise it returns
|
|
a CR_* error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
LONG RegStatus = ERROR_SUCCESS;
|
|
ULONG ulBufferLen=0, ulSize=0, ulIndex=0, ulLen=0;
|
|
WCHAR RegStr[MAX_CM_PATH], szDevice[MAX_DEVICE_ID_LEN];
|
|
HKEY hKey = NULL;
|
|
|
|
|
|
try {
|
|
//
|
|
// Open a key for this Enumerator branch
|
|
//
|
|
if (RegOpenKeyEx(ghEnumKey, pszEnumerator, 0, KEY_ENUMERATE_SUB_KEYS,
|
|
&hKey) != ERROR_SUCCESS) {
|
|
Status = CR_REGISTRY_ERROR;
|
|
goto Clean0;
|
|
}
|
|
|
|
ulIndex = 0;
|
|
ulSize = ulBufferLen = *pulLength; // total size of pBuffer
|
|
*pulLength = 0;
|
|
|
|
//
|
|
// Enumerate the device keys
|
|
//
|
|
while (RegStatus == ERROR_SUCCESS) {
|
|
|
|
ulLen = MAX_DEVICE_ID_LEN; // size in chars
|
|
|
|
RegStatus = RegEnumKeyEx(hKey, ulIndex, szDevice, &ulLen,
|
|
NULL, NULL, NULL, NULL);
|
|
ulIndex++;
|
|
|
|
if (RegStatus == ERROR_SUCCESS) {
|
|
//
|
|
// Enumerate the Instance keys
|
|
//
|
|
wsprintf(RegStr, TEXT("%s\\%s"),
|
|
pszEnumerator,
|
|
szDevice);
|
|
|
|
Status = GetInstanceList(RegStr, pBuffer, &ulSize);
|
|
|
|
if (Status != CR_SUCCESS) {
|
|
*pulLength = 0;
|
|
goto Clean0;
|
|
}
|
|
|
|
*pulLength += ulSize - 1; // data copied so far
|
|
ulSize = ulBufferLen - *pulLength; // buffer size left over
|
|
}
|
|
}
|
|
|
|
*pulLength += 1; // now add room for second null term
|
|
|
|
Clean0:
|
|
;
|
|
|
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
if (hKey != NULL) {
|
|
RegCloseKey(hKey);
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // GetDeviceInstanceList
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|