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.
3652 lines
101 KiB
3652 lines
101 KiB
/*++
|
|
|
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
Module Name:
|
|
|
|
rdevnode.c
|
|
|
|
Abstract:
|
|
|
|
This module contains the server-side device node APIs.
|
|
|
|
PNP_CreateDevInst
|
|
PNP_DeviceInstanceAction
|
|
PNP_GetDeviceStatus
|
|
PNP_SetDeviceProblem
|
|
PNP_UninstallDevInst
|
|
PNP_AddID
|
|
PNP_RegisterDriver
|
|
PNP_QueryRemove
|
|
PNP_DisableDevInst
|
|
PNP_RequestDeviceEject
|
|
|
|
Author:
|
|
|
|
Paula Tomlinson (paulat) 7-11-1995
|
|
|
|
Environment:
|
|
|
|
User-mode only.
|
|
|
|
Revision History:
|
|
|
|
11-July-1995 paulat
|
|
|
|
Creation and initial implementation.
|
|
|
|
--*/
|
|
|
|
|
|
//
|
|
// includes
|
|
//
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
#include "umpnpi.h"
|
|
#include "umpnpdat.h"
|
|
|
|
|
|
//
|
|
// private prototypes
|
|
//
|
|
CONFIGRET
|
|
SetupDevInst(
|
|
IN PCWSTR pszDeviceID,
|
|
IN ULONG ulFlags
|
|
);
|
|
|
|
CONFIGRET
|
|
CreateDefaultDeviceInstance(
|
|
IN PCWSTR pszDeviceID,
|
|
IN PCWSTR pszParentID,
|
|
IN BOOL bPhantom,
|
|
IN BOOL bMigrated
|
|
);
|
|
|
|
ULONG
|
|
GetCurrentConfigFlag(
|
|
IN PCWSTR pDeviceID
|
|
);
|
|
|
|
BOOL
|
|
MarkDevicePhantom(
|
|
IN HKEY hKey,
|
|
IN ULONG ulValue
|
|
);
|
|
|
|
CONFIGRET
|
|
GenerateDeviceInstance(
|
|
OUT LPWSTR pszFullDeviceID,
|
|
IN LPWSTR pszDeviceID,
|
|
IN ULONG ulDevId
|
|
);
|
|
|
|
BOOL
|
|
IsDeviceRegistered(
|
|
IN LPCWSTR pszDeviceID,
|
|
IN LPCWSTR pszService
|
|
);
|
|
|
|
BOOL
|
|
IsPrivatePhantomFromFirmware(
|
|
IN HKEY hKey
|
|
);
|
|
|
|
typedef struct {
|
|
|
|
LIST_ENTRY ListEntry;
|
|
WCHAR DevInst[ANYSIZE_ARRAY];
|
|
|
|
} ENUM_ELEMENT, *PENUM_ELEMENT;
|
|
|
|
CONFIGRET
|
|
EnumerateSubTreeTopDownBreadthFirstWorker(
|
|
IN handle_t BindingHandle,
|
|
IN LPCWSTR DevInst,
|
|
IN OUT PLIST_ENTRY ListHead
|
|
);
|
|
|
|
//
|
|
// 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
|
|
|
|
|
|
|
|
CONFIGRET
|
|
PNP_CreateDevInst(
|
|
IN handle_t hBinding,
|
|
IN OUT LPWSTR pszDeviceID,
|
|
IN LPWSTR pszParentDeviceID,
|
|
IN ULONG ulLength,
|
|
IN ULONG ulFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the RPC server entry point for the CM_Create_DevNode routine.
|
|
|
|
Arguments:
|
|
|
|
hBinding RPC binding handle.
|
|
|
|
pszDeviceID Device instance to create.
|
|
|
|
pszParentDeviceID Parent of the new device.
|
|
|
|
ulLength Max length of pszDeviceID on input and output.
|
|
|
|
ulFlags Supplies flags specifying options for the creation of the
|
|
device instance. May be one of the following values:
|
|
|
|
CM_CREATE_DEVNODE_NORMAL
|
|
Create the device instance now, and perform installation
|
|
for it at a later time.
|
|
|
|
CM_CREATE_DEVNODE_PHANTOM
|
|
Create a phantom device instance (i.e., a handle to a
|
|
device instance that is not alive as far as the ConfigMgr
|
|
APIs are concerned). This may be used for CM APIs that
|
|
require a devnode handle, but for which no real devnode
|
|
currently exists (e.g., registry property APIs). This
|
|
flag may not be specified with CR_CREATE_DEVNODE_NORMAL.
|
|
|
|
CM_CREATE_DEVNODE_GENERATE_ID
|
|
Create a Root-enumerated devnode using a unique device
|
|
instance ID generated from the supplied device ID in
|
|
pszDeviceID. If this flag is set, then pszDeviceID is
|
|
assumed to contain simply a device ID (i.e., no enumerator
|
|
key prefix, and no device instance suffix). A unique
|
|
4-digit, base-10 identifier string will be created under
|
|
Enum\Root\<pszDeviceID>, and the devnode will be created
|
|
based on that device instance ID. For instance, to add a
|
|
new legacy COM port devnode, this API would be called with
|
|
a pszDeviceID of *PNP0500. Assuming there was already one
|
|
COM port instance in the registry (instance 0000), the new
|
|
device instance ID would be: Root\*PNP0500\0001 The caller
|
|
may find out what device instance name was generated by
|
|
calling CM_Get_Device_ID with the devnode returned from
|
|
this API.
|
|
|
|
CM_CREATE_DEVNODE_DO_NOT_INSTALL
|
|
If this flag is set, the device will be registered as
|
|
controlled by the Service currently specified for the
|
|
device, by the CM_DRP_SERVICE device registry property.
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, the return value is CR_SUCCESS. Otherwise it
|
|
returns a CR_ error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
ULONG RegStatus = ERROR_SUCCESS;
|
|
HKEY hKey = NULL;
|
|
WCHAR szFullDeviceID[MAX_DEVICE_ID_LEN];
|
|
ULONG ulStatusFlag=0, ulConfigFlag=0, ulCSConfigFlag=0, ulProblem=0;
|
|
ULONG ulPhantom = 0, ulMigrated = 0;
|
|
ULONG ulSize=0;
|
|
PLUGPLAY_CONTROL_DEVICE_CONTROL_DATA ControlData;
|
|
WCHAR szService[MAX_PATH];
|
|
NTSTATUS ntStatus;
|
|
HRESULT hr;
|
|
size_t DeviceIDLen = 0;
|
|
|
|
|
|
try {
|
|
//
|
|
// Verify client "write" access.
|
|
//
|
|
if (!VerifyClientAccess(hBinding,
|
|
PLUGPLAY_WRITE)) {
|
|
Status = CR_ACCESS_DENIED;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// validate parameters
|
|
//
|
|
|
|
if (INVALID_FLAGS(ulFlags, CM_CREATE_DEVNODE_BITS)) {
|
|
Status = CR_INVALID_FLAG;
|
|
goto Clean0;
|
|
}
|
|
|
|
if (!ARGUMENT_PRESENT(pszDeviceID)) {
|
|
Status = CR_INVALID_POINTER;
|
|
goto Clean0;
|
|
}
|
|
|
|
ASSERT(ARGUMENT_PRESENT(pszParentDeviceID));
|
|
|
|
if (!ARGUMENT_PRESENT(pszParentDeviceID)) {
|
|
Status = CR_INVALID_POINTER;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Additionally, Windows NT does not support the
|
|
// CM_CREATE_DEVNODE_NO_WAIT_INSTALL flag.
|
|
//
|
|
|
|
if (ulFlags & CM_CREATE_DEVNODE_NO_WAIT_INSTALL) {
|
|
Status = CR_INVALID_FLAG;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Validate that parent is the root devnode. This routine only allows
|
|
// the creation of root-enumerated devices.
|
|
//
|
|
|
|
if (!IsRootDeviceID(pszParentDeviceID)) {
|
|
Status = CR_INVALID_DEVINST;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// create a unique instance value if requested
|
|
//
|
|
|
|
if (ulFlags & CM_CREATE_DEVNODE_GENERATE_ID) {
|
|
|
|
Status =
|
|
GenerateDeviceInstance(
|
|
szFullDeviceID,
|
|
pszDeviceID,
|
|
MAX_DEVICE_ID_LEN);
|
|
|
|
if (Status != CR_SUCCESS) {
|
|
goto Clean0;
|
|
}
|
|
|
|
if (FAILED(StringCchLength(
|
|
szFullDeviceID,
|
|
MAX_DEVICE_ID_LEN,
|
|
&DeviceIDLen))) {
|
|
Status = CR_INVALID_DEVICE_ID;
|
|
goto Clean0;
|
|
}
|
|
|
|
ASSERT(DeviceIDLen > 0);
|
|
ASSERT(DeviceIDLen < MAX_DEVICE_ID_LEN);
|
|
|
|
if (((ULONG)(DeviceIDLen + 1)) > ulLength) {
|
|
Status = CR_BUFFER_SMALL;
|
|
goto Clean0;
|
|
}
|
|
|
|
hr = StringCchCopyEx(pszDeviceID,
|
|
ulLength,
|
|
szFullDeviceID,
|
|
NULL, NULL,
|
|
STRSAFE_NULL_ON_FAILURE);
|
|
|
|
ASSERT(HRESULT_CODE(hr) != ERROR_INSUFFICIENT_BUFFER);
|
|
|
|
if (HRESULT_CODE(hr) == ERROR_INSUFFICIENT_BUFFER) {
|
|
Status = CR_BUFFER_SMALL;
|
|
goto Clean0;
|
|
} else if (FAILED(hr)) {
|
|
Status = CR_FAILURE;
|
|
goto Clean0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// try opening the registry key for this device instance
|
|
//
|
|
RegStatus = RegOpenKeyEx(ghEnumKey, pszDeviceID, 0,
|
|
KEY_READ | KEY_WRITE, &hKey);
|
|
|
|
KdPrintEx((DPFLTR_PNPMGR_ID,
|
|
DBGF_REGISTRY,
|
|
"UMPNPMGR: PNP_CreateDevInst opened key %ws\n",
|
|
pszDeviceID));
|
|
|
|
//
|
|
// if the key already exists, check if it is marked as "Migrated".
|
|
//
|
|
if (RegStatus == ERROR_SUCCESS) {
|
|
ulSize = sizeof(ULONG);
|
|
if (RegQueryValueEx(hKey,
|
|
pszRegValueMigrated,
|
|
NULL,
|
|
NULL,
|
|
(LPBYTE)&ulMigrated,
|
|
&ulSize) != ERROR_SUCCESS) {
|
|
ulMigrated = 0;
|
|
} else {
|
|
//
|
|
// if the value exists at all, be paranoid and check that it's 1
|
|
//
|
|
ASSERT(ulMigrated == 1);
|
|
}
|
|
}
|
|
|
|
//
|
|
// first handle phantom devnode case
|
|
//
|
|
if (ulFlags & CM_CREATE_DEVNODE_PHANTOM) {
|
|
//
|
|
// for a phantom devnode, it must not already exist in the registry
|
|
// unless it's an unregistered firmware mapper device instance.
|
|
//
|
|
if (RegStatus == ERROR_SUCCESS) {
|
|
ASSERT(hKey != NULL);
|
|
//
|
|
// Check to see if the device is migrated, or is a firmware
|
|
// mapper-created phantom--if so, it's OK to allow the create to
|
|
// succeed.
|
|
//
|
|
if (ulMigrated != 0) {
|
|
//
|
|
// this key was specifically request (not generated) so it
|
|
// will be used -- remove the migrated value.
|
|
//
|
|
RegDeleteValue(hKey, pszRegValueMigrated);
|
|
Status = CR_SUCCESS;
|
|
} else if (IsPrivatePhantomFromFirmware(hKey)) {
|
|
Status = CR_SUCCESS;
|
|
} else {
|
|
Status = CR_ALREADY_SUCH_DEVINST;
|
|
}
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// it doesn't exist in the registry so create a phantom devnode
|
|
//
|
|
CreateDefaultDeviceInstance(pszDeviceID,
|
|
pszParentDeviceID,
|
|
TRUE,
|
|
FALSE);
|
|
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// for a normal devnode, fail if the device is already present in the
|
|
// registry and alive, and not migrated
|
|
//
|
|
if ((RegStatus == ERROR_SUCCESS) &&
|
|
(IsDeviceIdPresent(pszDeviceID)) &&
|
|
(ulMigrated == 0)) {
|
|
//
|
|
// Set status to NEEDS ENUM and fail the create call.
|
|
//
|
|
Status = CR_ALREADY_SUCH_DEVINST;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// if couldn't open the device instance, or the key was migrated, then
|
|
// most likely the key doesn't exist yet, or should be treated as if it
|
|
// doesn't exist yet, so create a device instance key with default
|
|
// values.
|
|
//
|
|
if ((RegStatus != ERROR_SUCCESS) || (ulMigrated != 0)) {
|
|
|
|
//
|
|
// this key will be used -- remove the migrated value
|
|
// and close the key.
|
|
//
|
|
if (ulMigrated != 0) {
|
|
ASSERT(RegStatus == ERROR_SUCCESS);
|
|
ASSERT(hKey != NULL);
|
|
RegDeleteValue(hKey, pszRegValueMigrated);
|
|
RegCloseKey(hKey);
|
|
hKey = NULL;
|
|
}
|
|
|
|
//
|
|
// create the default device instance, finding and unused instance
|
|
// if necessary. if the key was migrated, a new key will not be
|
|
// created, but the default instance data will be added to the
|
|
// existing key.
|
|
//
|
|
CreateDefaultDeviceInstance(pszDeviceID,
|
|
pszParentDeviceID,
|
|
FALSE,
|
|
(ulMigrated != 0));
|
|
|
|
KdPrintEx((DPFLTR_PNPMGR_ID,
|
|
DBGF_REGISTRY,
|
|
"UMPNPMGR: PNP_CreateDevInst opened key %ws\n",
|
|
pszDeviceID));
|
|
|
|
RegStatus = RegOpenKeyEx(ghEnumKey, pszDeviceID, 0,
|
|
KEY_READ | KEY_WRITE, &hKey);
|
|
|
|
if (RegStatus != ERROR_SUCCESS) {
|
|
Status = CR_REGISTRY_ERROR;
|
|
goto Clean0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// retrieve flags
|
|
//
|
|
|
|
ulConfigFlag = GetDeviceConfigFlags(pszDeviceID, hKey);
|
|
ulCSConfigFlag = GetCurrentConfigFlag(pszDeviceID);
|
|
|
|
//
|
|
// check if the device is blocked
|
|
//
|
|
|
|
if ((ulCSConfigFlag & CSCONFIGFLAG_DO_NOT_CREATE) ||
|
|
(ulConfigFlag & CONFIGFLAG_REMOVED) ||
|
|
(ulConfigFlag & CONFIGFLAG_NET_BOOT)) {
|
|
|
|
Status = CR_CREATE_BLOCKED;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Call kernel-mode to create the device node
|
|
//
|
|
|
|
memset(&ControlData, 0, sizeof(PLUGPLAY_CONTROL_DEVICE_CONTROL_DATA));
|
|
RtlInitUnicodeString(&ControlData.DeviceInstance, pszDeviceID);
|
|
ControlData.Flags = 0;
|
|
|
|
ntStatus = NtPlugPlayControl(PlugPlayControlInitializeDevice,
|
|
&ControlData,
|
|
sizeof(ControlData));
|
|
if (!NT_SUCCESS(ntStatus)) {
|
|
Status = CR_FAILURE;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Retrieve devnode status
|
|
//
|
|
|
|
GetDeviceStatus(pszDeviceID, &ulStatusFlag, &ulProblem);
|
|
|
|
//
|
|
// Are we converting a phantom into a real devnode?
|
|
//
|
|
|
|
ulSize = sizeof(ULONG);
|
|
if (RegQueryValueEx(hKey, pszRegValuePhantom, NULL, NULL,
|
|
(LPBYTE)&ulPhantom, &ulSize) != ERROR_SUCCESS) {
|
|
ulPhantom = 0;
|
|
}
|
|
|
|
if (ulPhantom) {
|
|
|
|
//
|
|
// If we're turning a phantom into a real devnode, suppress the found new
|
|
// hardware popup for this device, then clear the phantom flag.
|
|
//
|
|
|
|
RegDeleteValue(hKey, pszRegValuePhantom);
|
|
|
|
} else {
|
|
|
|
//
|
|
// if device not installed, set a problem
|
|
//
|
|
|
|
if (ulConfigFlag & CONFIGFLAG_REINSTALL ||
|
|
ulConfigFlag & CONFIGFLAG_FAILEDINSTALL) {
|
|
|
|
SetDeviceStatus(pszDeviceID, DN_HAS_PROBLEM, CM_PROB_NOT_CONFIGURED);
|
|
}
|
|
}
|
|
|
|
if (ulFlags & CM_CREATE_DEVNODE_DO_NOT_INSTALL) {
|
|
|
|
//
|
|
// If the device has a service, register it
|
|
//
|
|
|
|
ulSize = MAX_PATH * sizeof(WCHAR);
|
|
if (RegQueryValueEx(hKey, pszRegValueService, NULL, NULL,
|
|
(LPBYTE)szService, &ulSize) == ERROR_SUCCESS) {
|
|
|
|
if (szService[0] != L'\0') {
|
|
|
|
memset(&ControlData, 0, sizeof(PLUGPLAY_CONTROL_DEVICE_CONTROL_DATA));
|
|
RtlInitUnicodeString(&ControlData.DeviceInstance, pszDeviceID);
|
|
ControlData.Flags = 0;
|
|
|
|
NtPlugPlayControl(PlugPlayControlRegisterNewDevice,
|
|
&ControlData,
|
|
sizeof(ControlData));
|
|
}
|
|
}
|
|
}
|
|
|
|
Clean0:
|
|
NOTHING;
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
if (hKey != NULL) {
|
|
RegCloseKey(hKey);
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // PNP_CreateDevInst
|
|
|
|
|
|
|
|
CONFIGRET
|
|
PNP_DeviceInstanceAction(
|
|
IN handle_t hBinding,
|
|
IN ULONG ulAction,
|
|
IN ULONG ulFlags,
|
|
IN PCWSTR pszDeviceInstance1,
|
|
IN PCWSTR pszDeviceInstance2
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the RPC server entry point for the ConfigManager routines that
|
|
perform some operation on DevNodes (such as create, setup, disable,
|
|
and enable, etc). It handles various routines in this one routine by
|
|
accepting a major and minor action value.
|
|
|
|
Arguments:
|
|
|
|
hBinding RPC binding handle.
|
|
|
|
ulMajorAction Specifies the requested action to perform (one of the
|
|
PNP_DEVINST_* values)
|
|
|
|
ulFlags This value depends on the value of the ulMajorAction and
|
|
further defines the specific action to perform
|
|
|
|
pszDeviceInstance1 This is a device instance string to be used in
|
|
performing the specified action, it's value depends on
|
|
the ulMajorAction value.
|
|
|
|
pszDeviceInstance2 Not used, must be NULL.
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, the return value is CR_SUCCESS.
|
|
If the function fails, the return value is one of the following:
|
|
CR_ALREADY_SUCH_DEVNODE,
|
|
CR_INVALID_DEVICE_ID,
|
|
CR_INVALID_DEVNODE,
|
|
CR_INVALID_FLAG,
|
|
CR_FAILURE,
|
|
CR_NOT_DISABLEABLE,
|
|
CR_INVALID_POINTER, or
|
|
CR_OUT_OF_MEMORY.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
BOOL Locked = FALSE;
|
|
PWSTR PrivilegedServiceAction = NULL;
|
|
|
|
//
|
|
// Verify client "execute" access
|
|
//
|
|
if (!VerifyClientAccess(hBinding,
|
|
PLUGPLAY_EXECUTE)) {
|
|
return CR_ACCESS_DENIED;
|
|
}
|
|
|
|
//
|
|
// Verify client privilege
|
|
//
|
|
switch (ulAction) {
|
|
|
|
case PNP_DEVINST_SETUP:
|
|
PrivilegedServiceAction = L"Device Action (setup device)";
|
|
break;
|
|
|
|
case PNP_DEVINST_ENABLE:
|
|
PrivilegedServiceAction = L"Device Action (enable device)";
|
|
break;
|
|
|
|
case PNP_DEVINST_REENUMERATE:
|
|
PrivilegedServiceAction = L"Device Action (re-enumerate device)";
|
|
break;
|
|
|
|
case PNP_DEVINST_DISABLE:
|
|
case PNP_DEVINST_QUERYREMOVE:
|
|
case PNP_DEVINST_REMOVESUBTREE:
|
|
case PNP_DEVINST_REQUEST_EJECT:
|
|
case PNP_DEVINST_MOVE:
|
|
PrivilegedServiceAction = L"Device Action (action not implemented)";
|
|
break;
|
|
|
|
default:
|
|
PrivilegedServiceAction = L"Device Action (unknown action)";
|
|
break;
|
|
}
|
|
|
|
if (!VerifyClientPrivilege(
|
|
hBinding,
|
|
SE_LOAD_DRIVER_PRIVILEGE,
|
|
PrivilegedServiceAction)) {
|
|
return CR_ACCESS_DENIED;
|
|
}
|
|
|
|
//
|
|
// NOTE: We enter a critical section here to guard against concurrent
|
|
// read/write operations to the "DisableCount" registry value of any
|
|
// single device instance by SetupDevInst, EnableDevInst (called by this
|
|
// routine, below), and DisableDevInst.
|
|
//
|
|
PNP_ENTER_SYNCHRONOUS_CALL();
|
|
Locked = TRUE;
|
|
|
|
try {
|
|
//
|
|
// Validate parameters.
|
|
//
|
|
if (ARGUMENT_PRESENT(pszDeviceInstance2)) {
|
|
Status = CR_INVALID_POINTER;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// pass the request on to a private routine that handles each major
|
|
// device instance action request
|
|
//
|
|
switch (ulAction) {
|
|
|
|
case PNP_DEVINST_SETUP:
|
|
if (IsLegalDeviceId(pszDeviceInstance1)) {
|
|
Status = SetupDevInst(pszDeviceInstance1, ulFlags);
|
|
} else {
|
|
Status = CR_INVALID_DEVNODE;
|
|
}
|
|
break;
|
|
|
|
case PNP_DEVINST_ENABLE:
|
|
if (IsLegalDeviceId(pszDeviceInstance1)) {
|
|
Status = EnableDevInst(pszDeviceInstance1, TRUE);
|
|
} else {
|
|
Status = CR_INVALID_DEVNODE;
|
|
}
|
|
break;
|
|
|
|
case PNP_DEVINST_REENUMERATE:
|
|
if (IsLegalDeviceId(pszDeviceInstance1)) {
|
|
Status = ReenumerateDevInst(pszDeviceInstance1, TRUE, ulFlags);
|
|
} else {
|
|
Status = CR_INVALID_DEVNODE;
|
|
}
|
|
break;
|
|
|
|
case PNP_DEVINST_DISABLE:
|
|
Status = CR_CALL_NOT_IMPLEMENTED;
|
|
break;
|
|
|
|
case PNP_DEVINST_QUERYREMOVE:
|
|
Status = CR_CALL_NOT_IMPLEMENTED;
|
|
break;
|
|
|
|
case PNP_DEVINST_REMOVESUBTREE:
|
|
Status = CR_CALL_NOT_IMPLEMENTED;
|
|
break;
|
|
|
|
case PNP_DEVINST_REQUEST_EJECT:
|
|
Status = CR_CALL_NOT_IMPLEMENTED;
|
|
break;
|
|
|
|
case PNP_DEVINST_MOVE:
|
|
Status = CR_CALL_NOT_IMPLEMENTED;
|
|
break;
|
|
|
|
default:
|
|
Status = CR_INVALID_FLAG;
|
|
break;
|
|
}
|
|
|
|
Clean0:
|
|
NOTHING;
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
//
|
|
// Reference the following variables so the compiler will respect
|
|
// statement ordering w.r.t. their assignment.
|
|
//
|
|
Locked = Locked;
|
|
}
|
|
|
|
if (Locked) {
|
|
PNP_LEAVE_SYNCHRONOUS_CALL();
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // PNP_DeviceInstanceAction
|
|
|
|
|
|
|
|
CONFIGRET
|
|
PNP_GetDeviceStatus(
|
|
IN handle_t hBinding,
|
|
IN LPCWSTR pDeviceID,
|
|
OUT PULONG pulStatus,
|
|
OUT PULONG pulProblem,
|
|
IN ULONG ulFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the RPC server entry point for the ConfigManager
|
|
CM_Get_DevNode_Status routines. It retrieves device instance specific
|
|
status information.
|
|
|
|
Arguments:
|
|
|
|
hBinding RPC binding handle.
|
|
|
|
pDeviceID This is a device instance string to retrieve status
|
|
information for.
|
|
|
|
pulStatus Pointer to ULONG variable to return Status Flags in
|
|
|
|
pulProblem Pointer to ULONG variable to return Problem in
|
|
|
|
ulFlags Not used, must be zero.
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, the return value is CR_SUCCESS.
|
|
If the function fails, the return value is one of the following:
|
|
CR_INVALID_DEVNODE,
|
|
CR_INVALID_FLAG, or
|
|
CR_INVALID_POINTER.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
ULONG PropertyData, DataSize, DataTransferLen, DataType;
|
|
size_t DeviceIDLen = 0;
|
|
|
|
UNREFERENCED_PARAMETER(hBinding);
|
|
|
|
try {
|
|
//
|
|
// Validate parameters.
|
|
//
|
|
if (INVALID_FLAGS(ulFlags, 0)) {
|
|
Status = CR_INVALID_FLAG;
|
|
goto Clean0;
|
|
}
|
|
|
|
if ((!ARGUMENT_PRESENT(pulStatus)) ||
|
|
(!ARGUMENT_PRESENT(pulProblem))) {
|
|
Status = CR_INVALID_POINTER;
|
|
goto Clean0;
|
|
}
|
|
|
|
*pulStatus = 0;
|
|
*pulProblem = 0;
|
|
|
|
if (FAILED(StringCchLength(
|
|
pDeviceID,
|
|
MAX_DEVICE_ID_LEN,
|
|
&DeviceIDLen))) {
|
|
Status = CR_INVALID_DEVNODE;
|
|
goto Clean0;
|
|
}
|
|
|
|
ASSERT(DeviceIDLen < MAX_DEVICE_ID_LEN);
|
|
|
|
if (DeviceIDLen == 0) {
|
|
Status = CR_INVALID_DEVNODE;
|
|
goto Clean0;
|
|
}
|
|
|
|
if (!IsLegalDeviceId(pDeviceID)) {
|
|
Status = CR_INVALID_DEVNODE;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Retrieve the Flags information from the DeviceNode (which is then
|
|
// mapped into status and problem values).
|
|
//
|
|
|
|
Status = GetDeviceStatus(pDeviceID, pulStatus, pulProblem);
|
|
if (Status != CR_SUCCESS) {
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Map special flags that aren't stored in the DeviceNode Flags field
|
|
//
|
|
|
|
//
|
|
// DN_ROOT_ENUMERATED?
|
|
//
|
|
|
|
if (IsDeviceRootEnumerated(pDeviceID)) {
|
|
//
|
|
// Do not mark PnP BIOS enumerated as ROOT enumerated
|
|
//
|
|
// Bios enumerated devices look like:
|
|
// Root\*aaannnn\PnPBIOS_n
|
|
//
|
|
|
|
if ((DeviceIDLen < (4 + 1 + 8 + 1 + 8)) ||
|
|
(CompareString(LOCALE_INVARIANT, NORM_IGNORECASE,
|
|
&pDeviceID[14], 8,
|
|
TEXT("PnPBIOS__"), 8) != CSTR_EQUAL)) {
|
|
*pulStatus |= DN_ROOT_ENUMERATED;
|
|
}
|
|
}
|
|
|
|
//
|
|
// DN_REMOVABLE?
|
|
//
|
|
DataSize = DataTransferLen = sizeof(ULONG);
|
|
if (CR_SUCCESS == PNP_GetDeviceRegProp(NULL,
|
|
pDeviceID,
|
|
CM_DRP_CAPABILITIES,
|
|
&DataType,
|
|
(LPBYTE)&PropertyData,
|
|
&DataTransferLen,
|
|
&DataSize,
|
|
0)) {
|
|
|
|
if (PropertyData & CM_DEVCAP_REMOVABLE) {
|
|
*pulStatus |= DN_REMOVABLE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// DN_MANUAL?
|
|
//
|
|
DataSize = DataTransferLen = sizeof(ULONG);
|
|
if (CR_SUCCESS != PNP_GetDeviceRegProp(NULL,
|
|
pDeviceID,
|
|
CM_DRP_CONFIGFLAGS,
|
|
&DataType,
|
|
(LPBYTE)&PropertyData,
|
|
&DataTransferLen,
|
|
&DataSize,
|
|
0)) {
|
|
PropertyData = 0;
|
|
}
|
|
|
|
if (PropertyData & CONFIGFLAG_MANUAL_INSTALL) {
|
|
*pulStatus |= DN_MANUAL;
|
|
}
|
|
|
|
//
|
|
// If there isn't already a problem, check to see if the config flags indicate this
|
|
// was a failed installation.
|
|
//
|
|
if (!(*pulStatus & DN_HAS_PROBLEM) && (PropertyData & CONFIGFLAG_FAILEDINSTALL)) {
|
|
*pulStatus |= DN_HAS_PROBLEM;
|
|
*pulProblem = CM_PROB_FAILED_INSTALL;
|
|
}
|
|
|
|
|
|
Clean0:
|
|
NOTHING;
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
|
|
return Status;
|
|
|
|
} // PNP_GetDeviceStatus
|
|
|
|
|
|
|
|
CONFIGRET
|
|
PNP_SetDeviceProblem(
|
|
IN handle_t hBinding,
|
|
IN LPCWSTR pDeviceID,
|
|
IN ULONG ulProblem,
|
|
IN ULONG ulFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the RPC server entry point for the ConfigManager
|
|
CM_Set_DevNode_Problem routines. It set device instance specific
|
|
problem information.
|
|
|
|
Arguments:
|
|
|
|
hBinding RPC binding handle.
|
|
|
|
pDeviceID This is a device instance string to retrieve status
|
|
information for.
|
|
|
|
ulProblem A ULONG variable that specifies the Problem
|
|
|
|
ulFlags May be one of the following two values:
|
|
|
|
CM_SET_DEVNODE_PROBLEM_NORMAL -- only set problem
|
|
if currently no problem
|
|
|
|
CM_SET_DEVNODE_PROBLEM_OVERRIDE -- override current
|
|
problem with new problem
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, the return value is CR_SUCCESS.
|
|
If the function fails, the return value is one of the following:
|
|
CR_INVALID_DEVNODE,
|
|
CR_INVALID_FLAG.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
ULONG ulCurrentProblem = 0, ulCurrentStatus = 0;
|
|
|
|
try {
|
|
//
|
|
// Verify client "execute" access
|
|
//
|
|
if (!VerifyClientAccess(hBinding,
|
|
PLUGPLAY_EXECUTE)) {
|
|
Status = CR_ACCESS_DENIED;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Verify client privilege
|
|
//
|
|
if (!VerifyClientPrivilege(hBinding,
|
|
SE_LOAD_DRIVER_PRIVILEGE,
|
|
L"Device Action (set device problem code)")) {
|
|
Status = CR_ACCESS_DENIED;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Validate parameters
|
|
//
|
|
if (INVALID_FLAGS(ulFlags, CM_SET_DEVNODE_PROBLEM_BITS)) {
|
|
Status = CR_INVALID_FLAG;
|
|
goto Clean0;
|
|
}
|
|
|
|
if (!IsLegalDeviceId(pDeviceID)) {
|
|
Status = CR_INVALID_DEVNODE;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// If there's already a problem, do nothing unless CM_SET_DEVNODE_PROBLEM_OVERRIDE
|
|
// is specified.
|
|
//
|
|
|
|
Status = GetDeviceStatus(pDeviceID, &ulCurrentStatus, &ulCurrentProblem);
|
|
if (Status != CR_SUCCESS) {
|
|
goto Clean0;
|
|
}
|
|
|
|
if (ulProblem) {
|
|
//
|
|
// The caller is wanting to set a problem. Make sure that if the device
|
|
// already has a problem, it's the same one as we're trying to set (unless
|
|
// we're overriding the current problem).
|
|
//
|
|
if ((ulCurrentStatus & DN_HAS_PROBLEM) &&
|
|
(ulCurrentProblem != ulProblem) &&
|
|
((ulFlags & CM_SET_DEVNODE_PROBLEM_BITS) != CM_SET_DEVNODE_PROBLEM_OVERRIDE)) {
|
|
|
|
Status = CR_FAILURE;
|
|
goto Clean0;
|
|
}
|
|
}
|
|
|
|
if (!ulProblem) {
|
|
Status = ClearDeviceStatus(pDeviceID, DN_HAS_PROBLEM, ulCurrentProblem);
|
|
} else {
|
|
Status = SetDeviceStatus(pDeviceID, DN_HAS_PROBLEM, ulProblem);
|
|
}
|
|
|
|
Clean0:
|
|
NOTHING;
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // PNP_SetDeviceProblem
|
|
|
|
|
|
|
|
CONFIGRET
|
|
PNP_UninstallDevInst(
|
|
IN handle_t hBinding,
|
|
IN LPCWSTR pDeviceID,
|
|
IN ULONG ulFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the RPC server entry point for the ConfigManager
|
|
CM_Deinstall_DevNode routine. It removes the device instance
|
|
registry key and any subkeys (only for phantoms).
|
|
|
|
Arguments:
|
|
|
|
hBinding RPC binding handle.
|
|
|
|
pDeviceID The device instance to deinstall.
|
|
|
|
ulFlags Not used, must be zero.
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, the return value is CR_SUCCESS.
|
|
Otherwise it returns one of the CR_ERROR codes.
|
|
|
|
--*/
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
ULONG ulStatus, ulProblem;
|
|
|
|
try {
|
|
//
|
|
// Verify client "write" access
|
|
//
|
|
if (!VerifyClientAccess(hBinding,
|
|
PLUGPLAY_WRITE)) {
|
|
Status = CR_ACCESS_DENIED;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Validate parameters
|
|
//
|
|
if (INVALID_FLAGS(ulFlags, 0)) {
|
|
Status = CR_INVALID_FLAG;
|
|
goto Clean0;
|
|
}
|
|
|
|
if (!IsLegalDeviceId(pDeviceID)) {
|
|
Status = CR_INVALID_DEVNODE;
|
|
goto Clean0;
|
|
}
|
|
|
|
//------------------------------------------------------------------
|
|
// Uninstall deletes instance key (and all subkeys) for all
|
|
// the hardware keys (this means the main Enum branch, the
|
|
// config specific keys under HKLM, and the Enum branch under
|
|
// HKCU). In the case of the user hardware keys (under HKCU),
|
|
// I delete those whether it's a phantom or not, but since
|
|
// I can't access the user key from the service side, I have
|
|
// to do that part on the client side. For the main hw Enum key
|
|
// and the config specific hw keys, I only delete them outright
|
|
// if they are phantoms. If not a phantom, then I just make the
|
|
// device instance volatile (by saving the original key, deleting
|
|
// old key, creating new volatile key and restoring the old
|
|
// contents) so at least it will go away during the next boot
|
|
//------------------------------------------------------------------
|
|
|
|
if ((GetDeviceStatus(pDeviceID, &ulStatus, &ulProblem) == CR_SUCCESS) &&
|
|
(ulStatus & DN_DRIVER_LOADED)) {
|
|
|
|
//-------------------------------------------------------------
|
|
// device is not a phantom
|
|
//-------------------------------------------------------------
|
|
|
|
if ((ulStatus & DN_ROOT_ENUMERATED)!=0 &&
|
|
(ulStatus & DN_DISABLEABLE)==0) {
|
|
|
|
//
|
|
// if a device is root enumerated, but not disableable, it is not uninstallable
|
|
// return status is CR_NOT_DISABLEABLE, as that is why it cannot be uninstalled
|
|
//
|
|
KdPrintEx((DPFLTR_PNPMGR_ID,
|
|
DBGF_REGISTRY,
|
|
"UMPNPMGR: PNP_UninstallDevInst failed uninstall of %ws (this root device is not disableable)\n",
|
|
pDeviceID));
|
|
|
|
Status = CR_NOT_DISABLEABLE;
|
|
|
|
} else {
|
|
|
|
//
|
|
// do the volatile-copy-thing
|
|
//
|
|
KdPrintEx((DPFLTR_PNPMGR_ID,
|
|
DBGF_REGISTRY,
|
|
"UMPNPMGR: PNP_UninstallDevInst doing volatile key thing on %ws\n",
|
|
pDeviceID));
|
|
|
|
Status = UninstallRealDevice(pDeviceID);
|
|
}
|
|
|
|
} else {
|
|
|
|
//-------------------------------------------------------------
|
|
// device is a phantom
|
|
//-------------------------------------------------------------
|
|
|
|
//
|
|
// deregister the device, and delete the registry keys.
|
|
//
|
|
Status = UninstallPhantomDevice(pDeviceID);
|
|
if (Status != CR_SUCCESS) {
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// if it is a root enumerated device, we need to reenumerate the
|
|
// root so that the PDO will go away, otherwise a new device could
|
|
// be created and the root enumerator would get very confused.
|
|
//
|
|
if (IsDeviceRootEnumerated(pDeviceID)) {
|
|
ReenumerateDevInst(pszRegRootEnumerator, FALSE, 0);
|
|
}
|
|
}
|
|
|
|
Clean0:
|
|
NOTHING;
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // PNP_UninstallDevInst
|
|
|
|
|
|
|
|
CONFIGRET
|
|
PNP_AddID(
|
|
IN handle_t hBinding,
|
|
IN LPCWSTR pszDeviceID,
|
|
IN LPCWSTR pszID,
|
|
IN ULONG ulFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the RPC server entry point for the ConfigManager
|
|
CM_Add_ID routine. It adds a hardware or compatible ID to
|
|
the registry for this device instance.
|
|
|
|
Arguments:
|
|
|
|
hBinding RPC binding handle.
|
|
|
|
pszDeviceID The device instance to add an ID for.
|
|
|
|
pszID The hardware or compatible ID to add.
|
|
|
|
ulFlags Specifies the type of ID to add.
|
|
May be one of the following two values:
|
|
|
|
CM_ADD_ID_HARDWARE -- add hardware ID
|
|
|
|
CM_ADD_ID_COMPATIBLE -- add compatible ID
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, the return value is CR_SUCCESS.
|
|
Otherwise it returns one of the CR_ERROR codes.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
WCHAR szCurrentID[REGSTR_VAL_MAX_HCID_LEN];
|
|
ULONG ulLength = 0, transferLength, type;
|
|
size_t IDLen = 0;
|
|
|
|
|
|
try {
|
|
//
|
|
// Verify client "write" access
|
|
//
|
|
if (!VerifyClientAccess(hBinding,
|
|
PLUGPLAY_WRITE)) {
|
|
Status = CR_ACCESS_DENIED;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Validate parameters
|
|
//
|
|
if (INVALID_FLAGS(ulFlags, CM_ADD_ID_BITS)) {
|
|
Status = CR_INVALID_FLAG;
|
|
goto Clean0;
|
|
}
|
|
|
|
if (!ARGUMENT_PRESENT(pszID)) {
|
|
Status = CR_INVALID_POINTER;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Make sure the NULL-terminated ID, plus the double-NULL terminating
|
|
// character is not larger than the max size for a multi-sz hardware or
|
|
// compatible id.
|
|
//
|
|
if (FAILED(StringCchLength(
|
|
pszID,
|
|
REGSTR_VAL_MAX_HCID_LEN - 1,
|
|
&IDLen))) {
|
|
Status = CR_INVALID_POINTER;
|
|
goto Clean0;
|
|
}
|
|
|
|
ASSERT((IDLen + 2) <= REGSTR_VAL_MAX_HCID_LEN);
|
|
|
|
szCurrentID[0] = L'\0';
|
|
|
|
ulLength = REGSTR_VAL_MAX_HCID_LEN * sizeof(WCHAR);
|
|
transferLength = ulLength;
|
|
|
|
Status = PNP_GetDeviceRegProp(hBinding,
|
|
pszDeviceID,
|
|
(ulFlags == CM_ADD_ID_HARDWARE)? CM_DRP_HARDWAREID : CM_DRP_COMPATIBLEIDS,
|
|
&type,
|
|
(LPBYTE)szCurrentID,
|
|
&transferLength,
|
|
&ulLength,
|
|
0);
|
|
if (Status == CR_SUCCESS) {
|
|
|
|
if (!MultiSzSearchStringW(szCurrentID, pszID)) {
|
|
//
|
|
// This ID is not already in the list, so append the new ID
|
|
// to the end of the existing IDs and write it back to the
|
|
// registry
|
|
//
|
|
ulLength = REGSTR_VAL_MAX_HCID_LEN*sizeof(WCHAR);
|
|
if (MultiSzAppendW(szCurrentID,
|
|
&ulLength,
|
|
pszID)) {
|
|
|
|
Status = PNP_SetDeviceRegProp(hBinding,
|
|
pszDeviceID,
|
|
(ulFlags == CM_ADD_ID_HARDWARE)? CM_DRP_HARDWAREID : CM_DRP_COMPATIBLEIDS,
|
|
REG_MULTI_SZ,
|
|
(LPBYTE)szCurrentID,
|
|
ulLength,
|
|
0);
|
|
} else {
|
|
//
|
|
// Couldn't append the new ID to the multi-sz.
|
|
//
|
|
Status = CR_FAILURE;
|
|
goto Clean0;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
//
|
|
// write out the id with a double null terminator
|
|
//
|
|
PWCHAR pszDestEnd = NULL;
|
|
size_t cchRemaining = 0;
|
|
|
|
if (FAILED(StringCchCopyEx(
|
|
szCurrentID,
|
|
SIZECHARS(szCurrentID),
|
|
pszID,
|
|
&pszDestEnd,
|
|
&cchRemaining,
|
|
STRSAFE_NULL_ON_FAILURE))) {
|
|
Status = CR_FAILURE;
|
|
goto Clean0;
|
|
}
|
|
|
|
ASSERT(pszDestEnd != NULL);
|
|
ASSERT(cchRemaining > 0);
|
|
|
|
if (cchRemaining < 2) {
|
|
Status = CR_FAILURE;
|
|
goto Clean0;
|
|
}
|
|
|
|
pszDestEnd[0] = L'\0';
|
|
pszDestEnd[1] = L'\0';
|
|
|
|
if (FAILED(StringCchLength(
|
|
szCurrentID,
|
|
REGSTR_VAL_MAX_HCID_LEN - 1,
|
|
&IDLen))) {
|
|
Status = CR_FAILURE;
|
|
goto Clean0;
|
|
}
|
|
|
|
ASSERT((IDLen + 1) < REGSTR_VAL_MAX_HCID_LEN);
|
|
|
|
Status = PNP_SetDeviceRegProp(hBinding,
|
|
pszDeviceID,
|
|
(ulFlags == CM_ADD_ID_HARDWARE)? CM_DRP_HARDWAREID : CM_DRP_COMPATIBLEIDS,
|
|
REG_MULTI_SZ,
|
|
(LPBYTE)szCurrentID,
|
|
(ULONG)((IDLen+2)*sizeof(WCHAR)),
|
|
0);
|
|
}
|
|
|
|
Clean0:
|
|
NOTHING;
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // PNP_AddID
|
|
|
|
|
|
|
|
CONFIGRET
|
|
PNP_RegisterDriver(
|
|
IN handle_t hBinding,
|
|
IN LPCWSTR pszDeviceID,
|
|
IN ULONG ulFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the RPC server entry point for the ConfigManager
|
|
CM_Register_Device_Driver routine. It setups flags for the
|
|
driver/device and enumerates it.
|
|
|
|
Arguments:
|
|
|
|
hBinding RPC binding handle.
|
|
|
|
pszDeviceID The device instance to register the driver for.
|
|
|
|
ulFlags Flags associated with the driver.
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, the return value is CR_SUCCESS.
|
|
Otherwise it returns one of the CR_ERROR codes.
|
|
|
|
--*/
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
ULONG ulStatusFlag = 0;
|
|
|
|
try {
|
|
//
|
|
// Verify client "execute" access
|
|
//
|
|
if (!VerifyClientAccess(hBinding,
|
|
PLUGPLAY_EXECUTE)) {
|
|
Status = CR_ACCESS_DENIED;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Verify client privilege
|
|
//
|
|
if (!VerifyClientPrivilege(hBinding,
|
|
SE_LOAD_DRIVER_PRIVILEGE,
|
|
L"Device Action (register device driver)")) {
|
|
Status = CR_ACCESS_DENIED;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Validate parameters
|
|
//
|
|
if (INVALID_FLAGS(ulFlags, CM_REGISTER_DEVICE_DRIVER_BITS)) {
|
|
Status = CR_INVALID_FLAG;
|
|
goto Clean0;
|
|
}
|
|
|
|
if (!IsLegalDeviceId(pszDeviceID)) {
|
|
Status = CR_INVALID_DEVNODE;
|
|
goto Clean0;
|
|
}
|
|
|
|
SetDeviceStatus(pszDeviceID, ulStatusFlag, 0);
|
|
|
|
Clean0:
|
|
NOTHING;
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // PNP_RegisterDriver
|
|
|
|
|
|
|
|
CONFIGRET
|
|
PNP_QueryRemove(
|
|
IN handle_t hBinding,
|
|
IN LPCWSTR pszDeviceID,
|
|
OUT PPNP_VETO_TYPE pVetoType,
|
|
OUT LPWSTR pszVetoName,
|
|
IN ULONG ulNameLength,
|
|
IN ULONG ulFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the RPC server entry point for the CM_Query_And_Remove_SubTree
|
|
routine.
|
|
|
|
Arguments:
|
|
|
|
hBinding RPC binding handle.
|
|
|
|
pszDeviceID Device instance to query and remove.
|
|
|
|
ulFlags Specifies flags describing how the query removal
|
|
should be processed.
|
|
|
|
Currently, the following flags are defined:
|
|
CM_REMOVE_UI_OK
|
|
CM_REMOVE_UI_NOT_OK,
|
|
CM_REMOVE_NO_RESTART,
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, the return value is CR_SUCCESS. Otherwise it
|
|
returns a CR_ error code.
|
|
|
|
Note:
|
|
|
|
Note that this routine actually checks for presence of the CM_REMOVE_* flags,
|
|
not the CR_QUERY_REMOVE_* flags. Note that currently the following
|
|
CM_QUERY_REMOVE_* and CM_REMOVE_* flags are defined:
|
|
|
|
CM_QUERY_REMOVE_UI_OK, == CM_REMOVE_UI_OK
|
|
CM_QUERY_REMOVE_UI_NOT_OK == CM_REMOVE_UI_NOT_OK
|
|
CM_REMOVE_NO_RESTART
|
|
|
|
Which is why we can simply check for the CM_REMOVE_* flags.
|
|
|
|
Also, note that currently the CM_REMOVE_UI_OK and CM_REMOVE_UI_NOT_OK flags
|
|
are ignored here on the server side. User interface dialogs are displayed
|
|
based on whether veto type and veto name buffers are supplied, so the client
|
|
has used these flags to determine whether a buffer was to be supplied or not.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
|
|
try {
|
|
//
|
|
// Verify client "execute" access
|
|
//
|
|
if (!VerifyClientAccess(hBinding,
|
|
PLUGPLAY_EXECUTE)) {
|
|
Status = CR_ACCESS_DENIED;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Verify client privilege
|
|
//
|
|
if (!VerifyClientPrivilege(hBinding,
|
|
SE_LOAD_DRIVER_PRIVILEGE,
|
|
L"Device Action (query-remove and remove device)")) {
|
|
Status = CR_ACCESS_DENIED;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Validate parameters
|
|
//
|
|
if (INVALID_FLAGS(ulFlags, CM_REMOVE_BITS)) {
|
|
Status = CR_INVALID_FLAG;
|
|
goto Clean0;
|
|
}
|
|
|
|
if (!IsLegalDeviceId(pszDeviceID) ||
|
|
IsRootDeviceID(pszDeviceID)) {
|
|
Status = CR_INVALID_DEVINST;
|
|
goto Clean0;
|
|
}
|
|
|
|
Status = QueryAndRemoveSubTree(pszDeviceID,
|
|
pVetoType,
|
|
pszVetoName,
|
|
ulNameLength,
|
|
(ulFlags & CM_REMOVE_NO_RESTART) ?
|
|
PNP_QUERY_AND_REMOVE_NO_RESTART :
|
|
0);
|
|
|
|
Clean0:
|
|
NOTHING;
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // PNP_QueryRemove
|
|
|
|
|
|
|
|
CONFIGRET
|
|
PNP_DisableDevInst(
|
|
IN handle_t hBinding,
|
|
IN LPCWSTR pszDeviceID,
|
|
OUT PPNP_VETO_TYPE pVetoType,
|
|
OUT LPWSTR pszVetoName,
|
|
IN ULONG ulNameLength,
|
|
IN ULONG ulFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the RPC server entry point for the CM_Disable_DevNode_Ex routine.
|
|
|
|
Arguments:
|
|
|
|
hBinding RPC binding handle.
|
|
|
|
pszDeviceID Device instance to disable.
|
|
|
|
ulFlags May specify CM_DISABLE_BITS.
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds, the return value is CR_SUCCESS. Otherwise it
|
|
returns a CR_ error code.
|
|
|
|
Note:
|
|
|
|
Note that although the client may supply flags to this routine, they are not
|
|
used.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
|
|
try {
|
|
//
|
|
// Verify client "execute" access
|
|
//
|
|
if (!VerifyClientAccess(hBinding,
|
|
PLUGPLAY_EXECUTE)) {
|
|
Status = CR_ACCESS_DENIED;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Verify client privilege
|
|
//
|
|
if (!VerifyClientPrivilege(hBinding,
|
|
SE_LOAD_DRIVER_PRIVILEGE,
|
|
L"Device Action (disable device)")) {
|
|
Status = CR_ACCESS_DENIED;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Validate parameters
|
|
//
|
|
if (INVALID_FLAGS(ulFlags, CM_DISABLE_BITS)) {
|
|
Status = CR_INVALID_FLAG;
|
|
goto Clean0;
|
|
}
|
|
|
|
if (!IsLegalDeviceId(pszDeviceID) ||
|
|
IsRootDeviceID(pszDeviceID)) {
|
|
Status = CR_INVALID_DEVINST;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// NOTE: We enter a critical section here to guard against concurrent
|
|
// read/write operations to the "DisableCount" registry value of any
|
|
// single device instance by DisableDevInst (below), and SetupDevInst,
|
|
// EnableDevInst (called by PNP_DeviceInstanceAction).
|
|
//
|
|
PNP_ENTER_SYNCHRONOUS_CALL();
|
|
|
|
Status = DisableDevInst(pszDeviceID,
|
|
pVetoType,
|
|
pszVetoName,
|
|
ulNameLength,
|
|
TRUE);
|
|
|
|
PNP_LEAVE_SYNCHRONOUS_CALL();
|
|
|
|
Clean0:
|
|
NOTHING;
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // PNP_DisableDevInst
|
|
|
|
|
|
|
|
CONFIGRET
|
|
PNP_RequestDeviceEject(
|
|
IN handle_t hBinding,
|
|
IN LPCWSTR pszDeviceID,
|
|
OUT PPNP_VETO_TYPE pVetoType,
|
|
OUT LPWSTR pszVetoName,
|
|
IN ULONG ulNameLength,
|
|
IN ULONG ulFlags
|
|
)
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
ULONG ulPropertyData, ulDataSize, ulTransferLen, ulDataType;
|
|
BOOL bDockDevice = FALSE;
|
|
|
|
try {
|
|
//
|
|
// Validate parameters
|
|
//
|
|
if (INVALID_FLAGS(ulFlags, 0)) {
|
|
Status = CR_INVALID_FLAG;
|
|
goto Clean0;
|
|
}
|
|
|
|
if (!IsLegalDeviceId(pszDeviceID)) {
|
|
Status = CR_INVALID_DEVNODE;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Do the appropriate security test
|
|
//
|
|
ulDataSize = ulTransferLen = sizeof(ULONG);
|
|
if (CR_SUCCESS == PNP_GetDeviceRegProp(NULL,
|
|
pszDeviceID,
|
|
CM_DRP_CAPABILITIES,
|
|
&ulDataType,
|
|
(LPBYTE)&ulPropertyData,
|
|
&ulTransferLen,
|
|
&ulDataSize,
|
|
0)) {
|
|
|
|
if (ulPropertyData & CM_DEVCAP_DOCKDEVICE) {
|
|
bDockDevice = TRUE;
|
|
}
|
|
}
|
|
|
|
if (bDockDevice) {
|
|
//
|
|
// Ejecting a dock via the CM APIs always requires the undock
|
|
// privilege (hardware-initiated ejects are handled a little
|
|
// differently, as they may be affected by policy).
|
|
//
|
|
|
|
//
|
|
// Verify client "execute" access
|
|
//
|
|
if (!VerifyClientAccess(hBinding,
|
|
PLUGPLAY_EXECUTE)) {
|
|
Status = CR_ACCESS_DENIED;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Verify client "undock" privilege
|
|
//
|
|
if (!VerifyClientPrivilege(hBinding,
|
|
SE_UNDOCK_PRIVILEGE,
|
|
L"UNDOCK: EJECT DOCK DEVICE")) {
|
|
Status = CR_ACCESS_DENIED;
|
|
goto Clean0;
|
|
}
|
|
|
|
} else {
|
|
//
|
|
// If the client is not interactive, or is not using the active
|
|
// console session, we require the special load-driver privilege.
|
|
//
|
|
if ((!IsClientUsingLocalConsole(hBinding)) ||
|
|
(!IsClientInteractive(hBinding))) {
|
|
|
|
//
|
|
// Verify client "execute" access
|
|
//
|
|
if (!VerifyClientAccess(hBinding,
|
|
PLUGPLAY_EXECUTE)) {
|
|
Status = CR_ACCESS_DENIED;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Verify client "load driver" privilege
|
|
//
|
|
if (!VerifyClientPrivilege(hBinding,
|
|
SE_LOAD_DRIVER_PRIVILEGE,
|
|
L"Device Action (eject device)")) {
|
|
Status = CR_ACCESS_DENIED;
|
|
goto Clean0;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Call kernel-mode to eject the device node
|
|
//
|
|
Status = QueryAndRemoveSubTree(pszDeviceID,
|
|
pVetoType,
|
|
pszVetoName,
|
|
ulNameLength,
|
|
PNP_QUERY_AND_REMOVE_EJECT_DEVICE);
|
|
|
|
Clean0:
|
|
NOTHING;
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // PNP_RequestDeviceEject
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Private functions
|
|
//-------------------------------------------------------------------
|
|
|
|
CONFIGRET
|
|
SetupDevInst(
|
|
IN PCWSTR pszDeviceID,
|
|
IN ULONG ulFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
Return value:
|
|
|
|
The return value is CR_SUCCESS if the function suceeds and one of the
|
|
CR_* values if it fails.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
HKEY hKey = NULL;
|
|
ULONG ulStatusFlag=0, ulProblem=0, ulDisableCount=0, ulSize=0;
|
|
PLUGPLAY_CONTROL_DEVICE_CONTROL_DATA ControlData;
|
|
NTSTATUS ntStatus = STATUS_SUCCESS;
|
|
|
|
try {
|
|
//
|
|
// Validate parameters
|
|
//
|
|
if (IsRootDeviceID(pszDeviceID)) {
|
|
goto Clean0;
|
|
}
|
|
|
|
if (INVALID_FLAGS(ulFlags, CM_SETUP_BITS)) {
|
|
Status = CR_INVALID_FLAG;
|
|
goto Clean0;
|
|
}
|
|
|
|
switch(ulFlags) {
|
|
|
|
case CM_SETUP_DOWNLOAD:
|
|
case CM_SETUP_WRITE_LOG_CONFS:
|
|
//
|
|
// On NT, these values are a no-op.
|
|
//
|
|
break;
|
|
|
|
case CM_SETUP_DEVNODE_READY:
|
|
case CM_SETUP_DEVNODE_RESET:
|
|
|
|
ulDisableCount = 0;
|
|
if (RegOpenKeyEx(ghEnumKey, pszDeviceID, 0, KEY_READ | KEY_WRITE,
|
|
&hKey) == ERROR_SUCCESS) {
|
|
|
|
//
|
|
// Check the disable count, if greater than zero, do nothing
|
|
//
|
|
ulSize = sizeof(ulDisableCount);
|
|
RegQueryValueEx(hKey, pszRegValueDisableCount, NULL, NULL,
|
|
(LPBYTE)&ulDisableCount, &ulSize);
|
|
}
|
|
|
|
if (ulDisableCount > 0) {
|
|
|
|
break;
|
|
}
|
|
|
|
GetDeviceStatus(pszDeviceID, &ulStatusFlag, &ulProblem);
|
|
|
|
//
|
|
// If there's no problem or if install was done already
|
|
// (immediately) then there's nothing more to do
|
|
//
|
|
if (ulStatusFlag & DN_STARTED) {
|
|
break;
|
|
}
|
|
|
|
if (ulStatusFlag & DN_HAS_PROBLEM) {
|
|
//
|
|
// reset the problem and set status to need to enum
|
|
//
|
|
Status = ClearDeviceStatus(pszDeviceID, DN_HAS_PROBLEM, ulProblem);
|
|
}
|
|
|
|
if (Status == CR_SUCCESS) {
|
|
|
|
//
|
|
// Have kernel-mode pnp manager start the driver/device now.
|
|
// If kernel-mode doesn't have a pdo for this device then it's
|
|
// probably either a Win32 service or a phantom devnode.
|
|
//
|
|
|
|
memset(&ControlData, 0, sizeof(PLUGPLAY_CONTROL_DEVICE_CONTROL_DATA));
|
|
RtlInitUnicodeString(&ControlData.DeviceInstance, pszDeviceID);
|
|
ControlData.Flags = 0;
|
|
|
|
if (ulFlags == CM_SETUP_DEVNODE_READY) {
|
|
|
|
ntStatus = NtPlugPlayControl(PlugPlayControlStartDevice,
|
|
&ControlData,
|
|
sizeof(ControlData));
|
|
|
|
} else {
|
|
|
|
ntStatus = NtPlugPlayControl(PlugPlayControlResetDevice,
|
|
&ControlData,
|
|
sizeof(ControlData));
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case CM_SETUP_PROP_CHANGE:
|
|
//
|
|
// Not sure what Win9x does with this, but it ain't implemented on
|
|
// NT. Let fall through to the default (invalid flag) case...
|
|
//
|
|
|
|
default:
|
|
Status = CR_INVALID_FLAG;
|
|
}
|
|
|
|
Clean0:
|
|
NOTHING;
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
if (hKey != NULL) {
|
|
RegCloseKey(hKey);
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // SetupDevInst
|
|
|
|
|
|
|
|
CONFIGRET
|
|
EnableDevInst(
|
|
IN PCWSTR pszDeviceID,
|
|
IN BOOL UseDisableCount
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the server-side work for CM_Enable_DevNode. It
|
|
disables the specified device ID
|
|
|
|
Arguments:
|
|
|
|
pszDeviceID String that contains the device id to enable
|
|
|
|
Return value:
|
|
|
|
The return value is CR_SUCCESS if the function suceeds and one of the
|
|
CR_* values if it fails.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
HKEY hKey = NULL;
|
|
WCHAR RegStr[MAX_PATH];
|
|
ULONG ulDisableCount, ulProblem = 0, ulStatus = 0, ulSize;
|
|
|
|
|
|
try {
|
|
|
|
//
|
|
// Verify it isn't the root, can't disable/enable the root. We can
|
|
// probably get rid of this test once we're sure that the root
|
|
// devnode is always marded as not disablable.
|
|
//
|
|
|
|
if (IsRootDeviceID(pszDeviceID)) {
|
|
Status = CR_INVALID_DEVINST;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Open a key to the specified device instances volatile control key.
|
|
// This is also a partial check whether the device is really present
|
|
// (if so then it has a Control key).
|
|
//
|
|
|
|
if (FAILED(StringCchPrintf(
|
|
RegStr,
|
|
SIZECHARS(RegStr),
|
|
L"%s\\%s",
|
|
pszDeviceID,
|
|
pszRegKeyDeviceControl))) {
|
|
Status = CR_FAILURE;
|
|
goto Clean0;
|
|
}
|
|
|
|
if (RegOpenKeyEx(ghEnumKey, RegStr, 0, KEY_READ | KEY_WRITE,
|
|
&hKey) != ERROR_SUCCESS) {
|
|
|
|
//
|
|
// NTRAID #174944-2000/08/30-jamesca:
|
|
// Remove dependence on the presence of volatile Control subkey
|
|
// for present devices.
|
|
//
|
|
|
|
Status = CR_INVALID_DEVINST;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Check the current DisableCount, if we're supposed to.
|
|
//
|
|
|
|
if (UseDisableCount) {
|
|
|
|
//
|
|
// Get the current disable count from the registry
|
|
//
|
|
|
|
ulSize = sizeof(ulDisableCount);
|
|
if (RegQueryValueEx(hKey, pszRegValueDisableCount, NULL, NULL,
|
|
(LPBYTE)&ulDisableCount, &ulSize) != ERROR_SUCCESS) {
|
|
|
|
//
|
|
// disable count not set yet, assume zero
|
|
//
|
|
|
|
ulDisableCount = 0;
|
|
}
|
|
|
|
//
|
|
// if the DisableCount is zero, then we're already enabled
|
|
//
|
|
|
|
if (ulDisableCount > 0) {
|
|
//
|
|
// Decrement disable count. If the disable count is greater than one,
|
|
// then just return (disable count must drop to zero in order to
|
|
// actually reenable)
|
|
//
|
|
|
|
ulDisableCount--;
|
|
|
|
RegSetValueEx(hKey, pszRegValueDisableCount, 0, REG_DWORD,
|
|
(LPBYTE)&ulDisableCount, sizeof(ulDisableCount));
|
|
|
|
if (ulDisableCount > 0) {
|
|
goto Clean0; // success
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Retrieve the problem and status values
|
|
//
|
|
|
|
if (GetDeviceStatus(pszDeviceID, &ulStatus, &ulProblem) == CR_SUCCESS) {
|
|
|
|
//
|
|
// If the problem is only that the device instance is disabled,
|
|
// then reenable it now
|
|
//
|
|
|
|
if ((ulStatus & DN_HAS_PROBLEM) && (ulProblem == CM_PROB_DISABLED)) {
|
|
|
|
Status = SetupDevInst(pszDeviceID, CM_SETUP_DEVNODE_READY);
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// The device isn't currently active or it is a service.
|
|
//
|
|
|
|
Status = CR_SUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// For now I'm not doing anything if there was a problem other than
|
|
// not being enabled.
|
|
//
|
|
|
|
Clean0:
|
|
NOTHING;
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
if (hKey != NULL) {
|
|
RegCloseKey(hKey);
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // EnableDevInst
|
|
|
|
|
|
|
|
CONFIGRET
|
|
DisableDevInst(
|
|
IN PCWSTR pszDeviceID,
|
|
OUT PPNP_VETO_TYPE pVetoType,
|
|
OUT LPWSTR pszVetoName,
|
|
IN ULONG ulNameLength,
|
|
IN BOOL UseDisableCount
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the server-side work for CM_Disable_DevNode. It
|
|
disables the specified device ID.
|
|
|
|
Arguments:
|
|
|
|
pszDeviceID String that contains the device id to disable
|
|
|
|
Return value:
|
|
|
|
The return value is CR_SUCCESS if the function suceeds and one of the
|
|
CR_* values if it fails.
|
|
|
|
Note:
|
|
|
|
This routine does not verify that a client is granted the pre-requisite
|
|
access, or possesses the required privilege(s) to perform this operation.
|
|
It is the caller's responsibility to perform these checks prior to calling
|
|
this routine.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
HKEY hKey = NULL;
|
|
WCHAR RegStr[MAX_PATH];
|
|
ULONG ulDisableCount=0, ulProblem=0, ulStatus=0, ulSize=0;
|
|
PNP_VETO_TYPE VetoType;
|
|
|
|
|
|
try {
|
|
|
|
//
|
|
// Verify it isn't the root, can't disable/enable the root. We can
|
|
// probably get rid of this test once we're sure that the root
|
|
// devnode is always marked as not disablable.
|
|
//
|
|
|
|
if (IsRootDeviceID(pszDeviceID)) {
|
|
Status = CR_INVALID_DEVINST;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Open a key to the specified device instances volatile control key.
|
|
// This is also a partial check whether the device is really present
|
|
// (if so then it has a Control key).
|
|
//
|
|
|
|
if (FAILED(StringCchPrintf(
|
|
RegStr,
|
|
SIZECHARS(RegStr),
|
|
L"%s\\%s",
|
|
pszDeviceID,
|
|
pszRegKeyDeviceControl))) {
|
|
Status = CR_FAILURE;
|
|
goto Clean0;
|
|
}
|
|
|
|
if (RegOpenKeyEx(ghEnumKey, RegStr, 0, KEY_READ | KEY_WRITE,
|
|
&hKey) != ERROR_SUCCESS) {
|
|
|
|
//
|
|
// NTRAID #174944-2000/08/30-jamesca:
|
|
// Remove dependence on the presence of volatile Control subkey
|
|
// for present devices.
|
|
//
|
|
|
|
Status = CR_INVALID_DEVINST;
|
|
goto Clean0;
|
|
}
|
|
|
|
|
|
//
|
|
// Check the current DisableCount, if we're supposed to.
|
|
//
|
|
|
|
if (UseDisableCount) {
|
|
|
|
//
|
|
// Get the current disable count from the registry.
|
|
//
|
|
|
|
ulSize = sizeof(ulDisableCount);
|
|
if (RegQueryValueEx(hKey, pszRegValueDisableCount, NULL, NULL,
|
|
(LPBYTE)&ulDisableCount, &ulSize) != ERROR_SUCCESS) {
|
|
|
|
//
|
|
// disable count not set yet, assume zero
|
|
//
|
|
|
|
ulDisableCount = 0;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// If the disable count is currently zero (or we're not using it at
|
|
// all), then this is the first disable, so there's work to do.
|
|
// Otherwise, we just increment the disable count and resave it in the
|
|
// registry.
|
|
//
|
|
|
|
if ((!UseDisableCount) || (ulDisableCount == 0)) {
|
|
|
|
//
|
|
// determine if the device instance is stopable
|
|
//
|
|
|
|
if (GetDeviceStatus(pszDeviceID, &ulStatus, &ulProblem) == CR_SUCCESS) {
|
|
|
|
if (!(ulStatus & DN_DISABLEABLE)) {
|
|
Status = CR_NOT_DISABLEABLE;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// Attempt to query remove and remove this device instance.
|
|
//
|
|
|
|
VetoType = PNP_VetoTypeUnknown;
|
|
Status = QueryAndRemoveSubTree(pszDeviceID,
|
|
&VetoType,
|
|
pszVetoName,
|
|
ulNameLength,
|
|
PNP_QUERY_AND_REMOVE_DISABLE);
|
|
if(pVetoType != NULL) {
|
|
*pVetoType = VetoType;
|
|
}
|
|
if (Status != CR_SUCCESS) {
|
|
if (VetoType == PNP_VetoNonDisableable) {
|
|
//
|
|
// specially handle this Veto case
|
|
// this case is unlikely to occur unless something becomes
|
|
// non-disableable between the status-check and when we
|
|
// try to remove it
|
|
//
|
|
Status = CR_NOT_DISABLEABLE;
|
|
}
|
|
goto Clean0;
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// The device isn't active or it is a service.
|
|
//
|
|
|
|
Status = CR_SUCCESS;
|
|
}
|
|
}
|
|
|
|
if (UseDisableCount) {
|
|
|
|
//
|
|
// update and save the disable count
|
|
//
|
|
|
|
ulDisableCount++;
|
|
RegSetValueEx(hKey, pszRegValueDisableCount, 0, REG_DWORD,
|
|
(LPBYTE)&ulDisableCount, sizeof(ulDisableCount));
|
|
}
|
|
|
|
Clean0:
|
|
NOTHING;
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
if (hKey != NULL) {
|
|
RegCloseKey(hKey);
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // DisableDevInst
|
|
|
|
|
|
|
|
CONFIGRET
|
|
ReenumerateDevInst(
|
|
IN PCWSTR pszDeviceID,
|
|
IN BOOL EnumSubTree,
|
|
IN ULONG ulFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the server-side work for CM_Reenumerate_DevNode. It
|
|
reenumerates the specified device instance.
|
|
|
|
Arguments:
|
|
|
|
pszDeviceID String that contains the device id to reenumerate.
|
|
|
|
EnumSubTree Specifies whether to reenumerate the entire device subtree.
|
|
|
|
ulFlags Any enumeration control flags.
|
|
|
|
Return value:
|
|
|
|
The return value is CR_SUCCESS if the function suceeds and one of the
|
|
CR_* values if it fails.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
PLUGPLAY_CONTROL_DEVICE_CONTROL_DATA ControlData;
|
|
NTSTATUS ntStatus = STATUS_SUCCESS;
|
|
ULONG ulEnumFlags = 0;
|
|
QI_CONTEXT qiContext;
|
|
|
|
//
|
|
// NOTE: For Windows 95, the devnode is marked as needing to be
|
|
// reenumerating (by or'ing StatusFlags with DN_NEED_TO_ENUM), then
|
|
// sometime later, after the initial flurry of reenumeration requests,
|
|
// the whole tree is processed
|
|
//
|
|
|
|
if (INVALID_FLAGS(ulFlags, CM_REENUMERATE_BITS)) {
|
|
return CR_INVALID_FLAG;
|
|
}
|
|
|
|
try {
|
|
|
|
//
|
|
// Attempt to handle this via kernel-mode, if kernel-mode
|
|
// doesn't have a pdo for this device then it's probably either a
|
|
// Win32 service or a phantom devnode.
|
|
//
|
|
|
|
if (!EnumSubTree) {
|
|
ulEnumFlags |= PNP_ENUMERATE_DEVICE_ONLY;
|
|
}
|
|
|
|
if (ulFlags & CM_REENUMERATE_ASYNCHRONOUS) {
|
|
ulEnumFlags |= PNP_ENUMERATE_ASYNCHRONOUS;
|
|
}
|
|
|
|
if (ulFlags & CM_REENUMERATE_RETRY_INSTALLATION) {
|
|
|
|
qiContext.HeadNodeSeen = FALSE;
|
|
qiContext.SingleLevelEnumOnly = !EnumSubTree;
|
|
qiContext.Status = CR_SUCCESS;
|
|
|
|
Status = EnumerateSubTreeTopDownBreadthFirst(
|
|
NULL,
|
|
pszDeviceID,
|
|
QueueInstallationCallback,
|
|
(PVOID) &qiContext
|
|
);
|
|
|
|
if (Status != CR_SUCCESS) {
|
|
|
|
return Status;
|
|
}
|
|
|
|
if (qiContext.Status != CR_SUCCESS) {
|
|
|
|
return qiContext.Status;
|
|
}
|
|
}
|
|
|
|
memset(&ControlData, 0, sizeof(PLUGPLAY_CONTROL_DEVICE_CONTROL_DATA));
|
|
RtlInitUnicodeString(&ControlData.DeviceInstance, pszDeviceID);
|
|
ControlData.Flags = ulEnumFlags;
|
|
|
|
ntStatus = NtPlugPlayControl(PlugPlayControlEnumerateDevice,
|
|
&ControlData,
|
|
sizeof(ControlData));
|
|
|
|
if (!NT_SUCCESS(ntStatus)) {
|
|
if (ntStatus == STATUS_NO_SUCH_DEVICE) {
|
|
Status = CR_INVALID_DEVNODE; // probably a win32 service
|
|
} else {
|
|
Status = MapNtStatusToCmError(ntStatus);
|
|
}
|
|
}
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = CR_FAILURE;
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // ReenumerateDevInst
|
|
|
|
|
|
|
|
CONFIGRET
|
|
QueryAndRemoveSubTree(
|
|
IN PCWSTR pszDeviceID,
|
|
OUT PPNP_VETO_TYPE pVetoType,
|
|
OUT LPWSTR pszVetoName,
|
|
IN ULONG ulNameLength,
|
|
IN ULONG ulFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the server-side work for CM_Query_Remove_Subtree. It
|
|
determines whether subtree can be removed.
|
|
|
|
Arguments:
|
|
|
|
pszDeviceID String that contains the device id to query remove
|
|
|
|
ulFlags Specifies flags for PLUGPLAY_CONTROL_QUERY_AND_REMOVE_DATA
|
|
May be one of:
|
|
PNP_QUERY_AND_REMOVE_NO_RESTART
|
|
PNP_QUERY_AND_REMOVE_DISABLE
|
|
PNP_QUERY_AND_REMOVE_UNINSTALL
|
|
PNP_QUERY_AND_REMOVE_EJECT_DEVICE
|
|
|
|
Return value:
|
|
|
|
The return value is CR_SUCCESS if the function suceeds and one of the
|
|
CR_* values if it fails.
|
|
|
|
--*/
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
PLUGPLAY_CONTROL_QUERY_AND_REMOVE_DATA ControlData;
|
|
NTSTATUS ntStatus = STATUS_SUCCESS;
|
|
|
|
if (ARGUMENT_PRESENT(pVetoType)) {
|
|
*pVetoType = 0;
|
|
}
|
|
|
|
if (ARGUMENT_PRESENT(pszVetoName) && (ulNameLength > 0)) {
|
|
*pszVetoName = L'\0';
|
|
}
|
|
|
|
//---------------------------------------------------------------------
|
|
// Attempt to handle this via kernel-mode first, if kernel-mode
|
|
// doesn't have a pdo for this device then it's probably either a
|
|
// Win32 service or a phantom devnode, so we'll do the old default
|
|
// Windows NT 4.0 behaviour for now.
|
|
//---------------------------------------------------------------------
|
|
|
|
memset(&ControlData, 0, sizeof(PLUGPLAY_CONTROL_QUERY_AND_REMOVE_DATA));
|
|
RtlInitUnicodeString(&ControlData.DeviceInstance, pszDeviceID);
|
|
ControlData.Flags = ulFlags;
|
|
ControlData.VetoType = PNP_VetoTypeUnknown;
|
|
ControlData.VetoName = pszVetoName;
|
|
ControlData.VetoNameLength = ulNameLength;
|
|
|
|
ntStatus = NtPlugPlayControl(PlugPlayControlQueryAndRemoveDevice,
|
|
&ControlData,
|
|
sizeof(ControlData));
|
|
|
|
if (!NT_SUCCESS(ntStatus)) {
|
|
if (ntStatus == STATUS_NO_SUCH_DEVICE) {
|
|
Status = CR_INVALID_DEVNODE; // probably a win32 service or legacy driver
|
|
} else if (ntStatus == STATUS_PLUGPLAY_QUERY_VETOED) {
|
|
KdPrintEx((DPFLTR_PNPMGR_ID,
|
|
DBGF_WARNINGS,
|
|
"Query vetoed: Type = %d, Name = %ws\n",
|
|
ControlData.VetoType,
|
|
ControlData.VetoName));
|
|
|
|
if (pVetoType != NULL) {
|
|
*pVetoType = ControlData.VetoType;
|
|
}
|
|
|
|
if (ARGUMENT_PRESENT(pszVetoName) &&
|
|
(ulNameLength > ControlData.VetoNameLength)) {
|
|
pszVetoName[ControlData.VetoNameLength] = L'\0';
|
|
}
|
|
Status = CR_REMOVE_VETOED;
|
|
} else {
|
|
Status = MapNtStatusToCmError(ntStatus);
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // QueryRemoveSubTree
|
|
|
|
|
|
|
|
CONFIGRET
|
|
CreateDefaultDeviceInstance(
|
|
IN PCWSTR pszDeviceID,
|
|
IN PCWSTR pszParentID,
|
|
IN BOOL bPhantom,
|
|
IN BOOL bMigrated
|
|
)
|
|
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
LONG RegStatus = ERROR_SUCCESS;
|
|
HKEY hKey1 = NULL, hKey2 = NULL;
|
|
WCHAR szBase[MAX_DEVICE_ID_LEN];
|
|
WCHAR szDevice[MAX_DEVICE_ID_LEN];
|
|
WCHAR szInstance[MAX_DEVICE_ID_LEN];
|
|
WCHAR RegStr[MAX_DEVICE_ID_LEN];
|
|
ULONG ulDisposition=0, i=0;
|
|
|
|
UNREFERENCED_PARAMETER(pszParentID);
|
|
|
|
//
|
|
// make sure we were specified a valid instance path.
|
|
//
|
|
if (!IsLegalDeviceId(pszDeviceID)) {
|
|
Status = CR_INVALID_DEVNODE;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// split the supplied instance path into enumerator, device, and instance
|
|
//
|
|
SplitDeviceInstanceString(pszDeviceID, szBase, szDevice, szInstance);
|
|
|
|
//
|
|
// open a key to base enumerator (create if doesn't already exist)
|
|
//
|
|
RegStatus = RegCreateKeyEx(ghEnumKey, szBase, 0, NULL,
|
|
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,
|
|
NULL, &hKey2, NULL);
|
|
|
|
if (RegStatus != ERROR_SUCCESS) {
|
|
Status = CR_REGISTRY_ERROR;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// open a key to device (create if doesn't already exist)
|
|
//
|
|
RegStatus = RegCreateKeyEx(hKey2, szDevice, 0, NULL,
|
|
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,
|
|
NULL, &hKey1, NULL);
|
|
|
|
if (RegStatus != ERROR_SUCCESS) {
|
|
Status = CR_REGISTRY_ERROR;
|
|
goto CleanupOnFailure;
|
|
}
|
|
|
|
RegCloseKey(hKey2); // done with Base Key
|
|
hKey2 = NULL;
|
|
|
|
//
|
|
// open a key to instance (if already exists)
|
|
//
|
|
RegStatus = RegOpenKeyEx(hKey1, szInstance, 0, KEY_SET_VALUE, &hKey2);
|
|
|
|
//
|
|
// if the key was migrated, an existing instance id should have been
|
|
// supplied. for non-migrated instances, the key should not exist yet.
|
|
//
|
|
if (bMigrated) {
|
|
ASSERT(RegStatus == ERROR_SUCCESS);
|
|
} else {
|
|
ASSERT(RegStatus != ERROR_SUCCESS);
|
|
}
|
|
|
|
//
|
|
// if the specified key exists, but the instance is not migrated, find an
|
|
// unused instance value. if a migrated instance was specified, don't
|
|
// bother finding an unused instance - we can just use this one.
|
|
//
|
|
if ((RegStatus == ERROR_SUCCESS) && (!bMigrated)) {
|
|
//
|
|
// find a new instance id to use
|
|
//
|
|
RegCloseKey(hKey2); // done with Instance key
|
|
hKey2 = NULL;
|
|
|
|
for (i=0; i <= 9999; i++) {
|
|
|
|
if (SUCCEEDED(StringCchPrintf(
|
|
szInstance,
|
|
SIZECHARS(szInstance),
|
|
L"%04u",
|
|
i))) {
|
|
|
|
//
|
|
// attemnpt to open this instance key
|
|
//
|
|
RegStatus =
|
|
RegOpenKeyEx(
|
|
hKey1,
|
|
szInstance,
|
|
0,
|
|
KEY_SET_VALUE,
|
|
&hKey2);
|
|
|
|
if (RegStatus != ERROR_SUCCESS) {
|
|
//
|
|
// instance key does not exist, use this instance
|
|
//
|
|
break;
|
|
}
|
|
|
|
//
|
|
// instance key exists, try next one
|
|
//
|
|
RegCloseKey(hKey2);
|
|
hKey2 = NULL;
|
|
}
|
|
}
|
|
|
|
if (i > 9999) {
|
|
Status = CR_FAILURE; // we ran out of instances (unlikely)
|
|
goto CleanupOnFailure;
|
|
}
|
|
}
|
|
|
|
if (hKey2 == NULL) {
|
|
//
|
|
// open the device instance key, if we haven't already.
|
|
//
|
|
RegStatus = RegCreateKeyEx(hKey1, szInstance, 0, NULL,
|
|
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,
|
|
NULL, &hKey2, &ulDisposition);
|
|
|
|
if (RegStatus != ERROR_SUCCESS) {
|
|
Status = CR_REGISTRY_ERROR;
|
|
goto CleanupOnFailure;
|
|
}
|
|
|
|
ASSERT(ulDisposition == REG_CREATED_NEW_KEY);
|
|
|
|
} else {
|
|
//
|
|
// We already opened the instance key above.
|
|
//
|
|
ASSERT(RegStatus == ERROR_SUCCESS);
|
|
}
|
|
|
|
RegCloseKey(hKey1); // done with device key
|
|
hKey1 = NULL;
|
|
|
|
//
|
|
// set the default device instance values
|
|
//
|
|
|
|
if (bPhantom) {
|
|
//
|
|
// phantoms are not present by definition
|
|
//
|
|
MarkDevicePhantom(hKey2, TRUE);
|
|
}
|
|
|
|
//
|
|
// go ahead and create the volatile Control key at this point.
|
|
//
|
|
|
|
RegCreateKeyEx(hKey2, pszRegKeyDeviceControl, 0, NULL,
|
|
REG_OPTION_VOLATILE, KEY_ALL_ACCESS,
|
|
NULL, &hKey1, &ulDisposition);
|
|
|
|
ASSERT(ulDisposition == REG_CREATED_NEW_KEY);
|
|
|
|
RegCloseKey(hKey2); // done with instance key
|
|
hKey2 = NULL;
|
|
|
|
goto Clean0; // success
|
|
|
|
|
|
CleanupOnFailure:
|
|
|
|
//
|
|
// attempt to cleanup the device instance (don't delete device or base if
|
|
// other subkeys under it).
|
|
//
|
|
|
|
RegDeleteKey(ghEnumKey, pszDeviceID); // delete instance
|
|
|
|
if (SUCCEEDED(StringCchPrintf(
|
|
RegStr,
|
|
SIZECHARS(RegStr),
|
|
L"%s\\%s",
|
|
szBase,
|
|
szDevice))) {
|
|
RegDeleteKey(ghEnumKey, RegStr);
|
|
}
|
|
|
|
Clean0:
|
|
|
|
if (hKey1 != NULL) {
|
|
RegCloseKey(hKey1);
|
|
}
|
|
if (hKey2 != NULL) {
|
|
RegCloseKey(hKey2);
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // CreateDefaultDeviceInstance
|
|
|
|
|
|
|
|
ULONG
|
|
GetCurrentConfigFlag(
|
|
IN PCWSTR pDeviceID
|
|
)
|
|
{
|
|
HKEY hKey;
|
|
WCHAR RegStr[MAX_PATH];
|
|
ULONG ulSize = 0, ulCSConfigFlag = 0;
|
|
|
|
|
|
//
|
|
// open a key to the current hardware profile for this device instance
|
|
// System\CCS\Hardware Profiles\Current\System\Enum
|
|
//
|
|
if (FAILED(StringCchPrintf(
|
|
RegStr,
|
|
SIZECHARS(RegStr),
|
|
L"%s\\%s\\%s\\%s",
|
|
pszRegPathHwProfiles,
|
|
pszRegKeyCurrent,
|
|
pszRegPathEnum,
|
|
pDeviceID))) {
|
|
return 0;
|
|
}
|
|
|
|
if (RegOpenKeyEx(
|
|
HKEY_LOCAL_MACHINE, RegStr, 0, KEY_QUERY_VALUE, &hKey)
|
|
!= ERROR_SUCCESS) {
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// retrieve the config specific flag
|
|
//
|
|
ulSize = sizeof(ulCSConfigFlag);
|
|
|
|
if (RegQueryValueEx(
|
|
hKey, pszRegValueCSConfigFlags, NULL, NULL,
|
|
(LPBYTE)&ulCSConfigFlag, &ulSize) != ERROR_SUCCESS) {
|
|
//
|
|
// status flags not set yet, assume zero
|
|
//
|
|
ulCSConfigFlag = 0;
|
|
}
|
|
|
|
RegCloseKey(hKey);
|
|
|
|
return ulCSConfigFlag;
|
|
|
|
} // GetCurrentConfigFlag
|
|
|
|
|
|
|
|
BOOL
|
|
MarkDevicePhantom(
|
|
IN HKEY hKey,
|
|
IN ULONG ulValue
|
|
)
|
|
{
|
|
//
|
|
// a phantom device should have a Phantom value of TRUE
|
|
//
|
|
RegSetValueEx(
|
|
hKey, pszRegValuePhantom, 0, REG_DWORD,
|
|
(LPBYTE)&ulValue, sizeof(ULONG));
|
|
|
|
return TRUE;
|
|
|
|
} // MarkDevicePhantom
|
|
|
|
|
|
|
|
CONFIGRET
|
|
GenerateDeviceInstance(
|
|
OUT LPWSTR pszFullDeviceID,
|
|
IN LPWSTR pszDeviceID,
|
|
IN ULONG ulDevIdLen
|
|
)
|
|
{
|
|
LONG RegStatus = ERROR_SUCCESS;
|
|
WCHAR RegStr[MAX_PATH];
|
|
HKEY hKey;
|
|
ULONG ulInstanceID = 0;
|
|
LPWSTR p;
|
|
|
|
|
|
//
|
|
// validate the device id component (can't have invalid character or a
|
|
// backslash)
|
|
//
|
|
for (p = pszDeviceID; *p; p++) {
|
|
if (*p <= TEXT(' ') ||
|
|
*p > (WCHAR)0x7F ||
|
|
*p == TEXT('\\')) {
|
|
return CR_INVALID_DEVICE_ID;
|
|
}
|
|
}
|
|
|
|
//
|
|
// make sure the supplied buffer is large enough to hold the name of the ROOT
|
|
// enumerator, the supplied device id, a generated instance id ('0000'), two
|
|
// path separator characters, plus a terminating NULL character.
|
|
//
|
|
if (FAILED(StringCchCopyEx(
|
|
pszFullDeviceID,
|
|
ulDevIdLen,
|
|
pszRegKeyRootEnum,
|
|
NULL, NULL,
|
|
STRSAFE_NULL_ON_FAILURE))) {
|
|
return CR_BUFFER_SMALL;
|
|
}
|
|
|
|
CharUpper(pszFullDeviceID);
|
|
|
|
if (FAILED(StringCchCatEx(pszFullDeviceID,
|
|
ulDevIdLen,
|
|
L"\\",
|
|
NULL, NULL,
|
|
STRSAFE_NULL_ON_FAILURE))) {
|
|
return CR_BUFFER_SMALL;
|
|
}
|
|
|
|
if (FAILED(StringCchCatEx(pszFullDeviceID,
|
|
ulDevIdLen,
|
|
pszDeviceID,
|
|
NULL, NULL,
|
|
STRSAFE_NULL_ON_FAILURE))) {
|
|
return CR_BUFFER_SMALL;
|
|
}
|
|
|
|
//
|
|
// try opening instance ids until we find one that doesn't already exist
|
|
//
|
|
for (ulInstanceID = 0; ulInstanceID <= 9999; ulInstanceID++) {
|
|
|
|
if (SUCCEEDED(StringCchPrintf(
|
|
RegStr,
|
|
SIZECHARS(RegStr),
|
|
L"%s\\%04u",
|
|
pszFullDeviceID,
|
|
ulInstanceID))) {
|
|
|
|
//
|
|
// attemnpt to open this instance key
|
|
//
|
|
RegStatus =
|
|
RegOpenKeyEx(
|
|
ghEnumKey,
|
|
RegStr,
|
|
0,
|
|
KEY_QUERY_VALUE,
|
|
&hKey);
|
|
|
|
if (RegStatus != ERROR_SUCCESS) {
|
|
//
|
|
// instance key does not exist, use this instance
|
|
//
|
|
break;
|
|
}
|
|
|
|
//
|
|
// instance key exists, try next one
|
|
//
|
|
RegCloseKey(hKey);
|
|
hKey = NULL;
|
|
}
|
|
}
|
|
|
|
if (ulInstanceID > 9999) {
|
|
return CR_FAILURE; // instances all used up, seems unlikely
|
|
}
|
|
|
|
if (FAILED(StringCchCopyEx(
|
|
pszFullDeviceID,
|
|
ulDevIdLen,
|
|
RegStr,
|
|
NULL, NULL,
|
|
STRSAFE_NULL_ON_FAILURE))) {
|
|
return CR_FAILURE;
|
|
}
|
|
|
|
return CR_SUCCESS;
|
|
|
|
} // GenerateDeviceInstance
|
|
|
|
|
|
|
|
CONFIGRET
|
|
UninstallRealDevice(
|
|
IN LPCWSTR pszDeviceID
|
|
)
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
LONG RegStatus;
|
|
WCHAR RegStr[MAX_CM_PATH];
|
|
WCHAR szProfile[MAX_PROFILE_ID_LEN];
|
|
ULONG ulLen = 0, ulIndex = 0;
|
|
HKEY hKeyHwProfiles = NULL;
|
|
|
|
|
|
//---------------------------------------------------------------------
|
|
// This is the case where a real device id couldn't be stopped, so we
|
|
// cannot really safely delete the device id at this point since the
|
|
// service may still try to use it. Instead, I'll make this device
|
|
// id registry key volatile so that it will eventually go away when
|
|
// the system is shutdown. To make the key volatile, I have to copy
|
|
// it to a temporary spot, delete the original key and recreate it
|
|
// as a volatile key and copy everything back.
|
|
//---------------------------------------------------------------------
|
|
|
|
|
|
//
|
|
// first, convert the device instance key under the main Enum
|
|
// branch to volatile
|
|
//
|
|
|
|
Status = MakeKeyVolatile(pszRegPathEnum, pszDeviceID);
|
|
|
|
if (Status != CR_SUCCESS) {
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// next, check each hardware profile and delete any entries for this
|
|
// device instance.
|
|
//
|
|
|
|
RegStatus = RegOpenKeyEx(HKEY_LOCAL_MACHINE, pszRegPathHwProfiles,
|
|
0, KEY_READ,
|
|
&hKeyHwProfiles);
|
|
|
|
if (RegStatus == ERROR_SUCCESS) {
|
|
|
|
//
|
|
// Enumerate all existing profile-specific Enum branches.
|
|
//
|
|
|
|
ulIndex = 0;
|
|
|
|
for ( ; ; ) {
|
|
|
|
ulLen = SIZECHARS(szProfile);
|
|
|
|
RegStatus = RegEnumKeyEx(hKeyHwProfiles, ulIndex++,
|
|
szProfile, &ulLen,
|
|
NULL, NULL, NULL, NULL);
|
|
|
|
if (RegStatus == ERROR_NO_MORE_ITEMS) {
|
|
//
|
|
// No more keys to enumerate, stop enumerating.
|
|
//
|
|
break;
|
|
|
|
} else if (RegStatus == ERROR_MORE_DATA) {
|
|
//
|
|
// Key is not a valid profile key, skip to the next.
|
|
//
|
|
continue;
|
|
|
|
} else if (RegStatus != ERROR_SUCCESS) {
|
|
//
|
|
// Some other error, stop enumerating.
|
|
//
|
|
break;
|
|
|
|
} else {
|
|
//
|
|
// Form the profile-specific Enum key path.
|
|
//
|
|
|
|
if (SUCCEEDED(StringCchPrintf(
|
|
RegStr,
|
|
SIZECHARS(RegStr),
|
|
L"%s\\%s\\%s",
|
|
pszRegPathHwProfiles,
|
|
szProfile,
|
|
pszRegPathEnum))) {
|
|
|
|
//
|
|
// Attempt to make the profile-specific device instance key
|
|
// volatile. Ignore the status for profile-specific keys since
|
|
// they may not exist.
|
|
//
|
|
|
|
MakeKeyVolatile(RegStr, pszDeviceID);
|
|
}
|
|
}
|
|
}
|
|
|
|
RegCloseKey(hKeyHwProfiles);
|
|
}
|
|
|
|
//
|
|
// finally, mark the device as being removed
|
|
//
|
|
|
|
SetDeviceStatus(pszDeviceID, DN_WILL_BE_REMOVED, 0);
|
|
|
|
Clean0:
|
|
|
|
return Status;
|
|
|
|
} // UninstallRealDevice
|
|
|
|
|
|
|
|
CONFIGRET
|
|
UninstallPhantomDevice(
|
|
IN LPCWSTR pszDeviceID
|
|
)
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
LONG RegStatus;
|
|
NTSTATUS NtStatus = STATUS_SUCCESS;
|
|
PLUGPLAY_CONTROL_DEVICE_CONTROL_DATA ControlData;
|
|
WCHAR szEnumerator[MAX_DEVICE_ID_LEN],
|
|
szDevice[MAX_DEVICE_ID_LEN],
|
|
szInstance[MAX_DEVICE_ID_LEN];
|
|
WCHAR RegStr[MAX_CM_PATH];
|
|
WCHAR szProfile[MAX_PROFILE_ID_LEN];
|
|
ULONG ulIndex = 0, ulLen = 0;
|
|
HKEY hKeyHwProfiles = NULL;
|
|
|
|
|
|
//
|
|
// 1. Deregister the original device id (only on phantoms)
|
|
//
|
|
|
|
memset(&ControlData, 0, sizeof(PLUGPLAY_CONTROL_DEVICE_CONTROL_DATA));
|
|
RtlInitUnicodeString(&ControlData.DeviceInstance, pszDeviceID);
|
|
ControlData.Flags = 0;
|
|
|
|
NtStatus = NtPlugPlayControl(PlugPlayControlDeregisterDevice,
|
|
&ControlData,
|
|
sizeof(ControlData));
|
|
|
|
//
|
|
// Don't bother with the status here, the device might not have
|
|
// been registered which would cause the previous call to fail.
|
|
// Keep trying to clean (uninstall) this device instance.
|
|
//
|
|
|
|
//
|
|
// 2. Remove the instance under the main enum branch. If this is the
|
|
// only instance, then the device will be removed as well. The parent
|
|
// key to DeletePrivateKey is the registry path up to the enumerator
|
|
// and the child key is the device and instance.
|
|
//
|
|
|
|
//
|
|
// Get the device id's component parts.
|
|
//
|
|
|
|
if (!SplitDeviceInstanceString(
|
|
pszDeviceID,
|
|
szEnumerator,
|
|
szDevice,
|
|
szInstance)) {
|
|
Status = CR_FAILURE;
|
|
goto Clean0;
|
|
}
|
|
|
|
if (FAILED(StringCchPrintf(
|
|
RegStr,
|
|
SIZECHARS(RegStr),
|
|
L"%s\\%s",
|
|
pszRegPathEnum,
|
|
szEnumerator))) {
|
|
Status = CR_FAILURE;
|
|
goto Clean0;
|
|
}
|
|
|
|
if (FAILED(StringCchCat(
|
|
szDevice,
|
|
SIZECHARS(szDevice),
|
|
L"\\"))) {
|
|
Status = CR_FAILURE;
|
|
goto Clean0;
|
|
}
|
|
|
|
if (FAILED(StringCchCat(
|
|
szDevice,
|
|
SIZECHARS(szDevice),
|
|
szInstance))) {
|
|
Status = CR_FAILURE;
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// delete the device instance key
|
|
//
|
|
|
|
Status = DeletePrivateKey(HKEY_LOCAL_MACHINE, RegStr, szDevice);
|
|
|
|
if (Status != CR_SUCCESS) {
|
|
goto Clean0;
|
|
}
|
|
|
|
//
|
|
// 3. Now check each hardware profile and delete any entries for this
|
|
// device instance.
|
|
//
|
|
|
|
RegStatus = RegOpenKeyEx(HKEY_LOCAL_MACHINE, pszRegPathHwProfiles,
|
|
0, KEY_READ,
|
|
&hKeyHwProfiles);
|
|
|
|
if (RegStatus == ERROR_SUCCESS) {
|
|
|
|
//
|
|
// Enumerate all existing profile-specific Enum branches.
|
|
//
|
|
|
|
ulIndex = 0;
|
|
|
|
for ( ; ; ) {
|
|
|
|
ulLen = SIZECHARS(szProfile);
|
|
|
|
RegStatus = RegEnumKeyEx(hKeyHwProfiles, ulIndex++,
|
|
szProfile, &ulLen,
|
|
NULL, NULL, NULL, NULL);
|
|
|
|
if (RegStatus == ERROR_NO_MORE_ITEMS) {
|
|
//
|
|
// No more keys to enumerate, stop enumerating.
|
|
//
|
|
break;
|
|
|
|
} else if (RegStatus == ERROR_MORE_DATA) {
|
|
//
|
|
// Key is not a valid profile key, skip to the next.
|
|
//
|
|
continue;
|
|
|
|
} else if (RegStatus != ERROR_SUCCESS) {
|
|
//
|
|
// Some other error, stop enumerating.
|
|
//
|
|
break;
|
|
|
|
} else {
|
|
//
|
|
// Form the profile-specific registry key path.
|
|
//
|
|
|
|
if (SUCCEEDED(StringCchPrintf(
|
|
RegStr,
|
|
SIZECHARS(RegStr),
|
|
L"%s\\%s\\%s\\%s",
|
|
pszRegPathHwProfiles,
|
|
szProfile,
|
|
pszRegPathEnum,
|
|
szEnumerator))) {
|
|
|
|
//
|
|
// Attempt to delete the profile-specific key for this device.
|
|
// Ignore the status for profile-specific keys since they may not
|
|
// exist. DeletePrivateKey() will remove the instance and the
|
|
// device, if this is the only instance.
|
|
//
|
|
|
|
DeletePrivateKey(HKEY_LOCAL_MACHINE, RegStr, szDevice);
|
|
}
|
|
}
|
|
}
|
|
|
|
RegCloseKey(hKeyHwProfiles);
|
|
}
|
|
|
|
Clean0:
|
|
|
|
return Status;
|
|
|
|
} // UninstallPhantomDevice
|
|
|
|
|
|
|
|
BOOL
|
|
IsDeviceRootEnumerated(
|
|
IN LPCWSTR pszDeviceID
|
|
)
|
|
{
|
|
WCHAR szEnumerator[MAX_DEVICE_ID_LEN],
|
|
szDevice[MAX_DEVICE_ID_LEN],
|
|
szInstance[MAX_DEVICE_ID_LEN];
|
|
|
|
if (!SplitDeviceInstanceString(pszDeviceID,
|
|
szEnumerator,
|
|
szDevice,
|
|
szInstance)) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (CompareString(LOCALE_INVARIANT,
|
|
NORM_IGNORECASE,
|
|
szEnumerator,
|
|
-1,
|
|
pszRegKeyRootEnum,
|
|
-1) == CSTR_EQUAL) {
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
} // IsDeviceRootEnumerated
|
|
|
|
|
|
|
|
BOOL
|
|
IsDeviceRegistered(
|
|
IN LPCWSTR pszDeviceID,
|
|
IN LPCWSTR pszService
|
|
)
|
|
{
|
|
WCHAR RegStr[MAX_PATH], szData[MAX_DEVICE_ID_LEN], szValue[MAX_PATH];
|
|
HKEY hKey = NULL;
|
|
LONG RegStatus = ERROR_SUCCESS;
|
|
ULONG ulIndex = 0, ulDataSize = 0, ulValueSize = 0, i = 0;
|
|
BOOL Status = FALSE;
|
|
|
|
|
|
//
|
|
// open the service's volatile enum registry key
|
|
//
|
|
if (FAILED(StringCchPrintf(
|
|
RegStr,
|
|
SIZECHARS(RegStr),
|
|
L"%s\\%s",
|
|
pszService,
|
|
pszRegKeyEnum))) {
|
|
return FALSE;
|
|
}
|
|
|
|
RegStatus =
|
|
RegOpenKeyEx(
|
|
ghServicesKey,
|
|
RegStr,
|
|
0,
|
|
KEY_READ,
|
|
&hKey);
|
|
if (RegStatus != ERROR_SUCCESS) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Enumerate all the values under this key
|
|
//
|
|
while (RegStatus == ERROR_SUCCESS) {
|
|
|
|
ulDataSize = MAX_DEVICE_ID_LEN * sizeof(WCHAR);
|
|
ulValueSize = MAX_PATH;
|
|
|
|
RegStatus = RegEnumValue(hKey, ulIndex, szValue, &ulValueSize,
|
|
NULL, &i, (LPBYTE)szData, &ulDataSize);
|
|
|
|
if (RegStatus == ERROR_SUCCESS) {
|
|
|
|
ulIndex++;
|
|
|
|
if (CompareString(LOCALE_INVARIANT,
|
|
NORM_IGNORECASE,
|
|
pszDeviceID,
|
|
-1,
|
|
szData,
|
|
-1) == CSTR_EQUAL) {
|
|
Status = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
RegCloseKey(hKey);
|
|
|
|
return Status;
|
|
|
|
} // IsDeviceRegistered
|
|
|
|
|
|
|
|
BOOL
|
|
IsPrivatePhantomFromFirmware(
|
|
IN HKEY hKey
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine checks to see if the supplied device instance registry key is
|
|
for a firmware mapper-created private phantom.
|
|
|
|
Arguments:
|
|
|
|
hKey - Supplied the handle to the registry key for the device instance to
|
|
be examined.
|
|
|
|
Return value:
|
|
|
|
If the device instance registry key represents a firmware mapper-reported
|
|
phantom, the return value is TRUE. Otherwise, it is FALSE.
|
|
|
|
--*/
|
|
{
|
|
ULONG ValueSize, Value;
|
|
HKEY hControlKey;
|
|
BOOL b = FALSE;
|
|
|
|
//
|
|
// First, make sure that this is indeed a phantom
|
|
//
|
|
ValueSize = sizeof(Value);
|
|
Value = 0;
|
|
|
|
if((ERROR_SUCCESS != RegQueryValueEx(hKey,
|
|
pszRegValuePhantom,
|
|
NULL,
|
|
NULL,
|
|
(LPBYTE)&Value,
|
|
&ValueSize))
|
|
|| !Value)
|
|
{
|
|
//
|
|
// Not a phantom
|
|
//
|
|
goto clean0;
|
|
}
|
|
|
|
//
|
|
// OK, we have a phantom--did it come from the firmware mapper?
|
|
//
|
|
ValueSize = sizeof(Value);
|
|
Value = 0;
|
|
|
|
if((ERROR_SUCCESS != RegQueryValueEx(hKey,
|
|
pszRegValueFirmwareIdentified,
|
|
NULL,
|
|
NULL,
|
|
(LPBYTE)&Value,
|
|
&ValueSize))
|
|
|| !Value)
|
|
{
|
|
//
|
|
// This phantom didn't come from the firmware mapper.
|
|
//
|
|
goto clean0;
|
|
}
|
|
|
|
//
|
|
// Finally, we need to check to see whether the device is actually present
|
|
// on this boot. If not, we want to return FALSE, because we don't want
|
|
// detection modules to be registering devnodes for non-existent hardware.
|
|
//
|
|
if(ERROR_SUCCESS == RegOpenKeyEx(hKey,
|
|
pszRegKeyDeviceControl,
|
|
0,
|
|
KEY_READ,
|
|
&hControlKey)) {
|
|
|
|
ValueSize = sizeof(Value);
|
|
Value = 0;
|
|
|
|
if((ERROR_SUCCESS == RegQueryValueEx(hControlKey,
|
|
pszRegValueFirmwareMember,
|
|
NULL,
|
|
NULL,
|
|
(LPBYTE)&Value,
|
|
&ValueSize))
|
|
&& Value)
|
|
{
|
|
b = TRUE;
|
|
}
|
|
|
|
RegCloseKey(hControlKey);
|
|
}
|
|
|
|
clean0:
|
|
|
|
return b;
|
|
|
|
} // IsPrivatePhantomFromFirmware
|
|
|
|
|
|
|
|
CONFIGRET
|
|
EnumerateSubTreeTopDownBreadthFirst(
|
|
IN handle_t BindingHandle,
|
|
IN LPCWSTR DevInst,
|
|
IN PFN_ENUMTREE CallbackFunction,
|
|
IN OUT PVOID Context
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine walks a subtree in a breadth-first nonrecursive manner.
|
|
|
|
Arguments:
|
|
|
|
BindingHandle RPC Binding handle
|
|
|
|
DevInst InstancePath of device to begin with. It is assumed
|
|
that this InstancePath is valid.
|
|
|
|
CallbackFunction Function to call for each node in the subtree (DevInst
|
|
included)
|
|
|
|
Context Context information to pass to the callback function.
|
|
|
|
|
|
Return Value:
|
|
|
|
CONFIGRET (Success if walk progressed through every node specified by the
|
|
CallbackFunction, failure due to low memory, bad instance path,
|
|
or other problems)
|
|
|
|
--*/
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
PENUM_ELEMENT enumElement;
|
|
ENUM_ACTION enumAction;
|
|
LIST_ENTRY subTreeHead;
|
|
PLIST_ENTRY listEntry;
|
|
size_t DeviceIDLen = 0;
|
|
|
|
//
|
|
// This algorithm is a nonrecursive tree walk. It works by building a list.
|
|
// Parents are removed from the head of the list and their children are
|
|
// added to the end of the list. This enforces a breadth-first downward
|
|
// tree walk.
|
|
//
|
|
InitializeListHead(&subTreeHead);
|
|
|
|
//
|
|
// This walk includes the head node as well, so insert it into the list.
|
|
//
|
|
if (!ARGUMENT_PRESENT(DevInst)) {
|
|
return CR_INVALID_POINTER;
|
|
}
|
|
|
|
if (FAILED(StringCchLength(
|
|
DevInst,
|
|
MAX_DEVICE_ID_LEN,
|
|
&DeviceIDLen))) {
|
|
return CR_INVALID_DEVICE_ID;
|
|
}
|
|
|
|
ASSERT(DeviceIDLen < MAX_DEVICE_ID_LEN);
|
|
|
|
enumElement = (PENUM_ELEMENT)
|
|
HeapAlloc(ghPnPHeap, 0,
|
|
sizeof(ENUM_ELEMENT) + (DeviceIDLen * sizeof(WCHAR)));
|
|
|
|
if (enumElement == NULL) {
|
|
return CR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
if (FAILED(StringCchCopyEx(
|
|
enumElement->DevInst,
|
|
DeviceIDLen + 1,
|
|
DevInst,
|
|
NULL, NULL,
|
|
STRSAFE_NULL_ON_FAILURE))) {
|
|
HeapFree(ghPnPHeap, 0, enumElement);
|
|
return CR_FAILURE;
|
|
}
|
|
|
|
InsertTailList(&subTreeHead, &enumElement->ListEntry);
|
|
|
|
//
|
|
// Remove each entry from the head of the list on downwards.
|
|
//
|
|
|
|
while(!IsListEmpty(&subTreeHead)) {
|
|
|
|
listEntry = RemoveHeadList(&subTreeHead);
|
|
|
|
enumElement = CONTAINING_RECORD(listEntry, ENUM_ELEMENT, ListEntry);
|
|
|
|
enumAction = CallbackFunction(enumElement->DevInst, Context);
|
|
|
|
if (enumAction == EA_STOP_ENUMERATION) {
|
|
HeapFree(ghPnPHeap, 0, enumElement);
|
|
break;
|
|
}
|
|
|
|
if (enumAction != EA_SKIP_SUBTREE) {
|
|
|
|
Status = EnumerateSubTreeTopDownBreadthFirstWorker(
|
|
BindingHandle,
|
|
enumElement->DevInst,
|
|
&subTreeHead
|
|
);
|
|
|
|
if (Status != CR_SUCCESS) {
|
|
HeapFree(ghPnPHeap, 0, enumElement);
|
|
break;
|
|
}
|
|
}
|
|
|
|
HeapFree(ghPnPHeap, 0, enumElement);
|
|
}
|
|
|
|
//
|
|
// There might be entries left in the list if we bailed prematurely. Clean
|
|
// them out here.
|
|
//
|
|
while(!IsListEmpty(&subTreeHead)) {
|
|
|
|
listEntry = RemoveHeadList(&subTreeHead);
|
|
enumElement = CONTAINING_RECORD(listEntry, ENUM_ELEMENT, ListEntry);
|
|
HeapFree(ghPnPHeap, 0, enumElement);
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // EnumerateSubTreeTopDownBreadthFirst
|
|
|
|
|
|
|
|
CONFIGRET
|
|
EnumerateSubTreeTopDownBreadthFirstWorker(
|
|
IN handle_t BindingHandle,
|
|
IN LPCWSTR DevInst,
|
|
IN OUT PLIST_ENTRY ListHead
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine inserts all the child relations of DevInst onto the passed in
|
|
list.
|
|
|
|
Arguments:
|
|
|
|
BindingHandle RPC Binding handle
|
|
|
|
DevInst InstancePath to enumerate.
|
|
|
|
ListHead List to append children to.
|
|
|
|
Return Value:
|
|
|
|
CONFIGRET
|
|
|
|
--*/
|
|
{
|
|
CONFIGRET Status = CR_SUCCESS;
|
|
ULONG ulLen;
|
|
LPWSTR pszRelations, pszCurEntry;
|
|
PENUM_ELEMENT enumElement;
|
|
size_t DeviceIDLen = 0;
|
|
|
|
|
|
//
|
|
// Get the size required for the list of all bus relations
|
|
//
|
|
Status = PNP_GetDeviceListSize(
|
|
BindingHandle,
|
|
DevInst,
|
|
&ulLen,
|
|
CM_GETIDLIST_FILTER_BUSRELATIONS);
|
|
|
|
if ((Status != CR_SUCCESS) || (ulLen == 0)) {
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Allocate an element for the first entry.
|
|
//
|
|
pszRelations = (LPWSTR)
|
|
HeapAlloc(ghPnPHeap, HEAP_ZERO_MEMORY,
|
|
((ulLen + 2)*sizeof(WCHAR)));
|
|
|
|
if (pszRelations == NULL) {
|
|
return CR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
//
|
|
// Get the list of all bus relations
|
|
//
|
|
Status = PNP_GetDeviceList(
|
|
BindingHandle,
|
|
DevInst,
|
|
pszRelations,
|
|
&ulLen,
|
|
CM_GETIDLIST_FILTER_BUSRELATIONS);
|
|
|
|
if (Status != CR_SUCCESS) {
|
|
HeapFree(ghPnPHeap, 0, pszRelations);
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Insert each related device into the list
|
|
//
|
|
for (pszCurEntry = pszRelations;
|
|
*pszCurEntry != L'\0';
|
|
pszCurEntry = pszCurEntry + lstrlen(pszCurEntry)+1) {
|
|
|
|
if (FAILED(StringCchLength(
|
|
pszCurEntry,
|
|
MAX_DEVICE_ID_LEN,
|
|
&DeviceIDLen))) {
|
|
continue;
|
|
}
|
|
|
|
ASSERT(DeviceIDLen < MAX_DEVICE_ID_LEN);
|
|
|
|
enumElement = (PENUM_ELEMENT)
|
|
HeapAlloc(ghPnPHeap, 0,
|
|
sizeof(ENUM_ELEMENT) +
|
|
((DeviceIDLen + 1 - ANYSIZE_ARRAY) * sizeof(WCHAR)));
|
|
|
|
if (enumElement == NULL) {
|
|
HeapFree(ghPnPHeap, 0, pszRelations);
|
|
return CR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
if (FAILED(StringCchCopy(
|
|
enumElement->DevInst,
|
|
DeviceIDLen + 1,
|
|
pszCurEntry))) {
|
|
HeapFree(ghPnPHeap, 0, enumElement);
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Insert it into the end of the tree.
|
|
//
|
|
InsertTailList(ListHead, &enumElement->ListEntry);
|
|
}
|
|
|
|
HeapFree(ghPnPHeap, 0, pszRelations);
|
|
|
|
return CR_SUCCESS;
|
|
|
|
} // EnumerateSubTreeTopDownBreadthFirstWorker
|
|
|
|
|
|
|
|
|
|
|