/*++ Copyright (c) 1995-2001 Microsoft Corporation 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" #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 pDeviceID on input and output. ulFlags This value depends on the value of the ulMajorAction and further defines the specific action to perform 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; try { // // Verify client privilege // if (!VerifyClientAccess(hBinding, &gLuidLoadDriverPrivilege)) { Status = CR_ACCESS_DENIED; goto Clean0; } // // validate parameters // if (INVALID_FLAGS(ulFlags, CM_CREATE_DEVNODE_BITS)) { Status = CR_INVALID_FLAG; 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; } // // RULE: validate that parent is the root devnode; only allow creating // root enumerating devices using this routine. // 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, (LPTSTR)pszDeviceID, MAX_DEVICE_ID_LEN); if (Status != CR_SUCCESS) { goto Clean0; } if (((ULONG)lstrlen(szFullDeviceID) + 1) > ulLength) { Status = CR_BUFFER_SMALL; goto Clean0; } lstrcpy(pszDeviceID, szFullDeviceID); } // // 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]) { 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 This is a device instance string to be used in performing the specified action, it's value depends on the ulMajorAction value. 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; try { // // Verify client privilege // if (!VerifyClientAccess(hBinding, &gLuidLoadDriverPrivilege)) { return CR_ACCESS_DENIED; } PNP_ENTER_SYNCHRONOUS_CALL(); // // pass the request on to a private routine that handles each major // device instance action request // switch (ulAction) { case PNP_DEVINST_SETUP: KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "CM_Setup_DevInst called\n")); if (IsLegalDeviceId(pszDeviceInstance1)) { Status = SetupDevInst(pszDeviceInstance1, ulFlags); } else { Status = CR_INVALID_DEVNODE; } break; case PNP_DEVINST_ENABLE: if (IsLegalDeviceId(pszDeviceInstance1)) { Status = EnableDevInst(pszDeviceInstance1); } else { Status = CR_INVALID_DEVNODE; } break; case PNP_DEVINST_REENUMERATE: KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "CM_Reenumerate_DevInst called\n")); if (IsLegalDeviceId(pszDeviceInstance1)) { Status = ReenumerateDevInst(pszDeviceInstance1, TRUE, ulFlags); } else { Status = CR_INVALID_DEVNODE; } break; case PNP_DEVINST_DISABLE: KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "CM_Disable_DevNode called\n")); Status = CR_CALL_NOT_IMPLEMENTED; break; case PNP_DEVINST_QUERYREMOVE: KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "CM_Query_Remove_SubTree called\n")); Status = CR_CALL_NOT_IMPLEMENTED; break; case PNP_DEVINST_REMOVESUBTREE: KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "CM_Remove_SubTree called\n")); Status = CR_CALL_NOT_IMPLEMENTED; break; case PNP_DEVINST_REQUEST_EJECT: KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "CM_Request_Device_Eject called\n")); Status = CR_CALL_NOT_IMPLEMENTED; break; case PNP_DEVINST_MOVE: Status = CR_CALL_NOT_IMPLEMENTED; break; default: Status = CR_INVALID_FLAG; break; } } except(EXCEPTION_EXECUTE_HANDLER) { Status = CR_FAILURE; } 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, not used. 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; WCHAR tmpString[16], tmpDevice[MAX_DEVICE_ID_LEN]; ULONG PropertyData, DataSize, DataTransferLen, DataType; 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 (!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? // lstrcpy(tmpString, pszRegKeyRootEnum); lstrcat(tmpString, TEXT("\\")); lstrcpyn(tmpDevice, pDeviceID, lstrlen(tmpString)+1); if (lstrcmpi(tmpString, tmpDevice) == 0) { // // Do not mark PnP BIOS enumerated as ROOT enumerated // // Bios enumerated devices look like: // Root\*aaannnn\PnPBIOS_n if (lstrlen(pDeviceID) < (4 + 1 + 8 + 1 + 8) || _wcsnicmp(&pDeviceID[14], L"PnPBIOS_", 8) != 0 ) { *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 privilege // if (!VerifyClientAccess(hBinding, &gLuidLoadDriverPrivilege)) { 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; WCHAR RegStr[MAX_CM_PATH]; ULONG ulCount=0, ulProfile = 0; ULONG ulStatus, ulProblem; try { // // Verify client privilege // if (!VerifyClientAccess(hBinding, &gLuidLoadDriverPrivilege)) { 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; try { // // Verify client privilege // if (!VerifyClientAccess(hBinding, &gLuidLoadDriverPrivilege)) { 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) || ((lstrlen(pszID) + 2) > REGSTR_VAL_MAX_HCID_LEN)) { Status = CR_INVALID_POINTER; goto Clean0; } szCurrentID[0] = L'\0'; transferLength = ulLength = REGSTR_VAL_MAX_HCID_LEN*sizeof(WCHAR); 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 // lstrcpy(szCurrentID, pszID); szCurrentID[lstrlen(pszID)] = L'\0'; szCurrentID[lstrlen(pszID) + 1] = L'\0'; Status = PNP_SetDeviceRegProp(hBinding, pszDeviceID, (ulFlags == CM_ADD_ID_HARDWARE)? CM_DRP_HARDWAREID : CM_DRP_COMPATIBLEIDS, REG_MULTI_SZ, (LPBYTE)szCurrentID, (lstrlen(szCurrentID) + 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 privilege // if (!VerifyClientAccess(hBinding, &gLuidLoadDriverPrivilege)) { 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 privilege // if (!VerifyClientAccess(hBinding, &gLuidLoadDriverPrivilege)) { 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 privilege // if (!VerifyClientAccess(hBinding, &gLuidLoadDriverPrivilege)) { 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; } Status = DisableDevInst(pszDeviceID, pVetoType, pszVetoName, ulNameLength); 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) { // // Undocking (ie ejecting a dock) uses a special privilege. // if ((!IsClientLocal(hBinding)) && (!IsClientAdministrator(hBinding))) { // // Non-local RPC calls from non-Admins are denied access, // regardless of privilege. // Status = CR_ACCESS_DENIED; } else if (!VerifyClientAccess(hBinding, &gLuidUndockPrivilege)) { // // Callers not posessing the undock privilege are denied access. // Status = CR_ACCESS_DENIED; } } 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)) { if (!VerifyClientAccess(hBinding, &gLuidLoadDriverPrivilege)) { Status = CR_ACCESS_DENIED; } } } if (Status != CR_ACCESS_DENIED) { // // 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; ULONG RegStatus = ERROR_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: if (RegOpenKeyEx(ghEnumKey, pszDeviceID, 0, KEY_READ | KEY_WRITE, &hKey) != ERROR_SUCCESS) { Status = CR_INVALID_DEVINST; goto Clean0; } // // Check the disable count, if greater than zero, do nothing // ulSize = sizeof(ulDisableCount); if (RegQueryValueEx(hKey, pszRegValueDisableCount, NULL, NULL, (LPBYTE)&ulDisableCount, &ulSize) == ERROR_SUCCESS) { 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 ) /*++ 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; ULONG RegStatus = ERROR_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). // wsprintf(RegStr, TEXT("%s\\%s"), pszDeviceID, pszRegKeyDeviceControl); 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; } // // 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 ) /*++ 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. --*/ { CONFIGRET Status = CR_SUCCESS; ULONG RegStatus = ERROR_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 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). // wsprintf(RegStr, TEXT("%s\\%s"), pszDeviceID, pszRegKeyDeviceControl); 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; } // // 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, 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 (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; } } // // 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 ulValue=0, 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; i = 0; while (i <= 9999) { wsprintf(szInstance, TEXT("%04u"), i); RegStatus = RegOpenKeyEx(hKey1, szInstance, 0, KEY_SET_VALUE, &hKey2); if (RegStatus != ERROR_SUCCESS) { break; // instance key does not exist, use this instance } // instance key exists, try next one RegCloseKey(hKey2); hKey2 = NULL; i++; } if (i > 9999) { Status = CR_FAILURE; // we ran out of instances (unlikely) goto CleanupOnFailure; } } // // open the device instance key // 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; } 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); 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 wsprintf(RegStr, TEXT("%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 // wsprintf(RegStr, TEXT("%s\\%s\\%s\\%s"), pszRegPathHwProfiles, // System\CCC\Hardware Profiles pszRegKeyCurrent, // Current pszRegPathEnum, // System\Enum pDeviceID); 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 (ulDevIdLen < (ULONG)(lstrlen(pszRegKeyRootEnum) + lstrlen(pszDeviceID) + 7)) { return CR_BUFFER_SMALL; } lstrcpy(pszFullDeviceID, pszRegKeyRootEnum); CharUpper(pszFullDeviceID); lstrcat(pszFullDeviceID, TEXT("\\")); lstrcat(pszFullDeviceID, pszDeviceID); // // try opening instance ids until we find one that doesn't already exist // while (RegStatus == ERROR_SUCCESS && ulInstanceID < 10000) { wsprintf(RegStr, TEXT("%s\\%04u"), pszFullDeviceID, ulInstanceID); RegStatus = RegOpenKeyEx(ghEnumKey, RegStr, 0, KEY_QUERY_VALUE, &hKey); if (RegStatus == ERROR_SUCCESS) { RegCloseKey(hKey); ulInstanceID++; } } if (ulInstanceID > 9999) { return CR_FAILURE; // instances all used up, seems unlikely } ASSERT((ULONG)lstrlen(RegStr) < ulDevIdLen); lstrcpy(pszFullDeviceID, RegStr); return CR_SUCCESS; } // GenerateDeviceInstance CONFIGRET UninstallRealDevice( IN LPCWSTR pszDeviceID ) { CONFIGRET Status = CR_SUCCESS; WCHAR RegStr[MAX_CM_PATH]; ULONG ulCount = 0, ulProfile = 0; //--------------------------------------------------------------------- // 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. // 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. // MakeKeyVolatile(RegStr, pszDeviceID); } // // 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; 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]; ULONG ulCount = 0, ulProfile = 0; // // 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. // SplitDeviceInstanceString(pszDeviceID, szEnumerator, szDevice, szInstance); wsprintf(RegStr, TEXT("%s\\%s"), pszRegPathEnum, szEnumerator); lstrcat(szDevice, TEXT("\\")); lstrcat(szDevice, szInstance); // // 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. // Status = GetProfileCount(&ulCount); if (Status != CR_SUCCESS) { goto Clean0; } for (ulProfile = 1; ulProfile <= ulCount; ulProfile++) { wsprintf(RegStr, TEXT("%s\\%04u\\%s\\%s"), pszRegPathHwProfiles, ulProfile, pszRegPathEnum, szEnumerator); // // Ignore the status for profile-specific keys since they may // not exist. RemoveDeviceInstance() will remove the instance // and the device if this is the only instance. // DeletePrivateKey(HKEY_LOCAL_MACHINE, RegStr, szDevice); } 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; } return (_wcsicmp(szEnumerator, pszRegKeyRootEnum) == 0); } // 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 // wsprintf(RegStr, TEXT("%s\\%s"), pszService, pszRegKeyEnum); if (RegOpenKeyEx(ghServicesKey, RegStr, 0, KEY_READ, &hKey) == ERROR_SUCCESS) { // // 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 (lstrcmpi(pszDeviceID, szData) == 0) { 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) --*/ { PENUM_ELEMENT enumElement; ENUM_ACTION enumAction; LIST_ENTRY subTreeHead; PLIST_ENTRY listEntry; CONFIGRET status; // // 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. // enumElement = HeapAlloc( ghPnPHeap, 0, sizeof(ENUM_ELEMENT) + lstrlen(DevInst)*sizeof(WCHAR) ); if (enumElement == NULL) { return CR_OUT_OF_MEMORY; } lstrcpy(enumElement->DevInst, DevInst); InsertTailList(&subTreeHead, &enumElement->ListEntry); // // Remove each entry from the head of the list on downwards. // status = CR_SUCCESS; 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; } 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; ULONG ulLen; LPWSTR pszRelations, pszCurEntry; PENUM_ELEMENT enumElement; 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 = HeapAlloc( ghPnPHeap, HEAP_ZERO_MEMORY, (ulLen+2)*sizeof(WCHAR) ); if (pszRelations == NULL) { return CR_OUT_OF_MEMORY; } status = PNP_GetDeviceList( BindingHandle, DevInst, pszRelations, &ulLen, CM_GETIDLIST_FILTER_BUSRELATIONS ); if (status != CR_SUCCESS) { HeapFree(ghPnPHeap, 0, pszRelations); return status; } for(pszCurEntry = pszRelations; *pszCurEntry; pszCurEntry = pszCurEntry + lstrlen(pszCurEntry)+1) { enumElement = HeapAlloc( ghPnPHeap, 0, sizeof(ENUM_ELEMENT) + lstrlen(pszCurEntry)*sizeof(WCHAR) ); if (enumElement == NULL) { HeapFree(ghPnPHeap, 0, pszRelations); return CR_OUT_OF_MEMORY; } // // Insert it into the end of the tree. // lstrcpy(enumElement->DevInst, pszCurEntry); InsertTailList(ListHead, &enumElement->ListEntry); } HeapFree(ghPnPHeap, 0, pszRelations); return CR_SUCCESS; }