/*++ Copyright (c) Microsoft Corporation. All rights reserved. Module Name: rregprop.c Abstract: This module contains the server-side registry property routines. PNP_GetDeviceRegProp PNP_SetDeviceRegProp PNP_GetClassRegProp PNP_SetClassRegProp PNP_GetClassInstance PNP_CreateKey PNP_DeleteRegistryKey PNP_GetClassCount PNP_GetClassName PNP_DeleteClassKey PNP_GetInterfaceDeviceAlias PNP_GetInterfaceDeviceList PNP_GetInterfaceDeviceListSize PNP_RegisterDeviceClassAssociation PNP_UnregisterDeviceClassAssociation PNP_GetCustomDevProp This module contains the privately exported registry property routines. DeleteServicePlugPlayRegKeys Author: Paula Tomlinson (paulat) 6-23-1995 Environment: User-mode only. Revision History: 23-June-1995 paulat Creation and initial implementation. --*/ // // includes // #include "precomp.h" #pragma hdrstop #include "umpnpi.h" #include "umpnpdat.h" #include #include // // private prototypes // LPWSTR MapPropertyToString( ULONG ulProperty ); ULONG MapPropertyToNtProperty( ULONG ulProperty ); HKEY FindMostAppropriatePerHwIdSubkey( IN HKEY hDevKey, IN REGSAM samDesired, OUT LPWSTR PerHwIdSubkeyName, OUT LPDWORD PerHwIdSubkeyLen ); // // global data // extern HKEY ghEnumKey; // Key to HKLM\CCC\System\Enum - DO NOT MODIFY extern HKEY ghClassKey; // Key to HKLM\CCC\System\Class - DO NOT MODIFY extern HKEY ghPerHwIdKey; // Key to HKLM\Software\Microsoft\Windows NT\CurrentVersion\PerHwIdStorage - DO NOT MODIFY BYTE bDeviceReadPropertyFlags[] = { 0, // zero-index not used 1, // CM_DRP_DEVICEDESC 1, // CM_DRP_HARDWAREID 1, // CM_DRP_COMPATIBLEIDS 0, // CM_DRP_UNUSED0 1, // CM_DRP_SERVICE 0, // CM_DRP_UNUSED1 0, // CM_DRP_UNUSED2 1, // CM_DRP_CLASS 1, // CM_DRP_CLASSGUID 1, // CM_DRP_DRIVER 1, // CM_DRP_CONFIGFLAGS 1, // CM_DRP_MFG 1, // CM_DRP_FRIENDLYNAME 1, // CM_DRP_LOCATION_INFORMATION 1, // CM_DRP_PHYSICAL_DEVICE_OBJECT_NAME 1, // CM_DRP_CAPABILITIES 1, // CM_DRP_UI_NUMBER 1, // CM_DRP_UPPERFILTERS 1, // CM_DRP_LOWERFILTERS 1, // CM_DRP_BUSTYPEGUID 1, // CM_DRP_LEGACYBUSTYPE 1, // CM_DRP_BUSNUMBER 1, // CM_DRP_ENUMERATOR_NAME 1, // CM_DRP_SECURITY 0, // CM_DRP_SECURITY_SDS - client property only (converted from CM_DRP_SECURITY) 1, // CM_DRP_DEVTYPE 1, // CM_DRP_EXCLUSIVE 1, // CM_DRP_CHARACTERISTICS 1, // CM_DRP_ADDRESS 1, // CM_DRP_UI_NUMBER_DESC_FORMAT 1, // CM_DRP_DEVICE_POWER_DATA 1, // CM_DRP_REMOVAL_POLICY 1, // CM_DRP_REMOVAL_POLICY_HW_DEFAULT 1, // CM_DRP_REMOVAL_POLICY_OVERRIDE 1, // CM_DRP_INSTALL_STATE 1, // CM_DRP_LOCATION_PATHS }; BYTE bDeviceWritePropertyFlags[] = { 0, // zero-index not used 1, // CM_DRP_DEVICEDESC 1, // CM_DRP_HARDWAREID 1, // CM_DRP_COMPATIBLEIDS 0, // CM_DRP_UNUSED0 1, // CM_DRP_SERVICE 0, // CM_DRP_UNUSED1 0, // CM_DRP_UNUSED2 1, // CM_DRP_CLASS 1, // CM_DRP_CLASSGUID 1, // CM_DRP_DRIVER 1, // CM_DRP_CONFIGFLAGS 1, // CM_DRP_MFG 1, // CM_DRP_FRIENDLYNAME 1, // CM_DRP_LOCATION_INFORMATION 0, // CM_DRP_PHYSICAL_DEVICE_OBJECT_NAME 0, // CM_DRP_CAPABILITIES 0, // CM_DRP_UI_NUMBER 1, // CM_DRP_UPPERFILTERS 1, // CM_DRP_LOWERFILTERS 0, // CM_DRP_BUSTYPEGUID 0, // CM_DRP_LEGACYBUSTYPE 0, // CM_DRP_BUSNUMBER 0, // CM_DRP_ENUMERATOR_NAME 1, // CM_DRP_SECURITY 0, // CM_DRP_SECURITY_SDS - client property only (converted to CM_DRP_SECURITY) 1, // CM_DRP_DEVTYPE 1, // CM_DRP_EXCLUSIVE 1, // CM_DRP_CHARACTERISTICS 0, // CM_DRP_ADDRESS 1, // CM_DRP_UI_NUMBER_DESC_FORMAT 0, // CM_DRP_DEVICE_POWER_DATA 0, // CM_DRP_REMOVAL_POLICY 0, // CM_DRP_REMOVAL_POLICY_HW_DEFAULT 1, // CM_DRP_REMOVAL_POLICY_OVERRIDE 0, // CM_DRP_INSTALL_STATE 0, // CM_DRP_LOCATION_PATHS }; BYTE bClassReadPropertyFlags[] = { 0, // zero-index not used 0, // (CM_DRP_DEVICEDESC) 0, // (CM_DRP_HARDWAREID) 0, // (CM_DRP_COMPATIBLEIDS) 0, // (CM_DRP_UNUSED0) 0, // (CM_DRP_SERVICE) 0, // (CM_DRP_UNUSED1) 0, // (CM_DRP_UNUSED2) 0, // (CM_DRP_CLASS) 0, // (CM_DRP_CLASSGUID) 0, // (CM_DRP_DRIVER) 0, // (CM_DRP_CONFIGFLAGS) 0, // (CM_DRP_MFG) 0, // (CM_DRP_FRIENDLYNAME) 0, // (CM_DRP_LOCATION_INFORMATION) 0, // (CM_DRP_PHYSICAL_DEVICE_OBJECT_NAME) 0, // (CM_DRP_CAPABILITIES) 0, // (CM_DRP_UI_NUMBER) 0, // (CM_DRP_UPPERFILTERS) 0, // (CM_DRP_LOWERFILTERS) 0, // (CM_DRP_BUSTYPEGUID) 0, // (CM_DRP_LEGACYBUSTYPE) 0, // (CM_DRP_BUSNUMBER) 0, // (CM_DRP_ENUMERATOR_NAME) 1, // CM_CRP_SECURITY 0, // CM_CRP_SECURITY_SDS - client property only (converted from CM_CRP_SECURITY) 1, // CM_CRP_DEVTYPE 1, // CM_CRP_EXCLUSIVE 1, // CM_CRP_CHARACTERISTICS 0, // (CM_DRP_ADDRESS) 0, // (CM_DRP_UI_NUMBER_DESC_FORMAT) 0, // (CM_DRP_DEVICE_POWER_DATA) 0, // (CM_DRP_REMOVAL_POLICY) 0, // (CM_DRP_REMOVAL_POLICY_HW_DEFAULT) 0, // (CM_DRP_REMOVAL_POLICY_OVERRIDE) 0, // (CM_DRP_INSTALL_STATE) 0, // (CM_DRP_LOCATION_PATHS) }; BYTE bClassWritePropertyFlags[] = { 0, // zero-index not used 0, // (CM_DRP_DEVICEDESC) 0, // (CM_DRP_HARDWAREID) 0, // (CM_DRP_COMPATIBLEIDS) 0, // (CM_DRP_UNUSED0) 0, // (CM_DRP_SERVICE) 0, // (CM_DRP_UNUSED1) 0, // (CM_DRP_UNUSED2) 0, // (CM_DRP_CLASS) 0, // (CM_DRP_CLASSGUID) 0, // (CM_DRP_DRIVER) 0, // (CM_DRP_CONFIGFLAGS) 0, // (CM_DRP_MFG) 0, // (CM_DRP_FRIENDLYNAME) 0, // (CM_DRP_LOCATION_INFORMATION) 0, // (CM_DRP_PHYSICAL_DEVICE_OBJECT_NAME) 0, // (CM_DRP_CAPABILITIES) 0, // (CM_DRP_UI_NUMBER) 0, // (CM_DRP_UPPERFILTERS) 0, // (CM_DRP_LOWERFILTERS) 0, // (CM_DRP_BUSTYPEGUID) 0, // (CM_DRP_LEGACYBUSTYPE) 0, // (CM_DRP_BUSNUMBER) 0, // (CM_DRP_ENUMERATOR_NAME) 1, // CM_CRP_SECURITY 0, // CM_CRP_SECURITY_SDS - client property only (converted to CM_CRP_SECURITY) 1, // CM_CRP_DEVTYPE 1, // CM_CRP_EXCLUSIVE 1, // CM_CRP_CHARACTERISTICS 0, // (CM_DRP_ADDRESS) 0, // (CM_DRP_UI_NUMBER_DESC_FORMAT) 0, // (CM_DRP_DEVICE_POWER_DATA) 0, // (CM_DRP_REMOVAL_POLICY) 0, // (CM_DRP_REMOVAL_POLICY_HW_DEFAULT) 0, // (CM_DRP_REMOVAL_POLICY_OVERRIDE) 0, // (CM_DRP_INSTALL_STATE) 0, // (CM_DRP_LOCATION_PATHS) }; // // compile-time property-array consistancy checks // C_ASSERT(CM_DRP_MIN == CM_CRP_MIN); C_ASSERT(CM_DRP_MAX == CM_CRP_MAX); C_ASSERT(ARRAY_SIZE(bDeviceReadPropertyFlags) == (CM_DRP_MAX + 1)); C_ASSERT(ARRAY_SIZE(bDeviceWritePropertyFlags) == (CM_DRP_MAX + 1)); C_ASSERT(ARRAY_SIZE(bClassReadPropertyFlags) == (CM_CRP_MAX + 1)); C_ASSERT(ARRAY_SIZE(bClassWritePropertyFlags) == (CM_CRP_MAX + 1)); CONFIGRET PNP_GetDeviceRegProp( IN handle_t hBinding, IN LPCWSTR pDeviceID, IN ULONG ulProperty, OUT PULONG pulRegDataType, OUT LPBYTE Buffer, IN OUT PULONG pulTransferLen, IN OUT PULONG pulLength, IN ULONG ulFlags ) /*++ Routine Description: This is the RPC server entry point for the CM_Get_DevNode_Registry_Property routine. Arguments: hBinding RPC binding handle, not used. pDeviceID Supplies a string containing the device instance whose property will be read from. ulProperty ID specifying which property (the registry value) to get. pulRegDataType Supplies the address of a variable that will receive the registry data type for this property (i.e., the REG_* constants). Buffer Supplies the address of the buffer that receives the registry data. Can be NULL when simply retrieving data size. pulTransferLen Used by stubs, indicates how much data to copy back into user buffer. pulLength Parameter passed in by caller, on entry it contains the size, in bytes, of the buffer, on exit it contains either the amount of data copied to the caller's buffer (if a transfer occured) or else the size of buffer required to hold the property data. 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, CR_INVALID_POINTER, CR_NO_SUCH_VALUE, CR_REGISTRY_ERROR, or CR_BUFFER_SMALL. Remarks: The pointer passed in as the pulTransferLen argument must *NOT* be the same as the pointer passed in for the pulLength argument. --*/ { CONFIGRET Status = CR_SUCCESS; LONG RegStatus = ERROR_SUCCESS; ULONG ulSize = 0; HKEY hKey = NULL; LPWSTR pPropertyName; NTSTATUS ntStatus = STATUS_SUCCESS; PLUGPLAY_CONTROL_PROPERTY_DATA ControlData; LPCWSTR pSeparatorChar; ULONG bufferLength; HRESULT hr; UNREFERENCED_PARAMETER(hBinding); try { // // Validate parameters // ASSERT(pulTransferLen != pulLength); if ((!ARGUMENT_PRESENT(pulTransferLen)) || (!ARGUMENT_PRESENT(pulLength))) { Status = CR_INVALID_POINTER; goto Clean0; } if (INVALID_FLAGS(ulFlags, 0)) { Status = CR_INVALID_FLAG; goto Clean0; } if (!IsLegalDeviceId(pDeviceID)) { Status = CR_INVALID_DEVNODE; goto Clean0; } // // Make sure we use no more than either what the caller specified or // what was allocated by RPC, based on the transfer length. // *pulLength = min(*pulLength, *pulTransferLen); *pulTransferLen = 0; // // validate property is valid, and readable // if ((ulProperty < CM_DRP_MIN) || (ulProperty > CM_DRP_MAX) || (!bDeviceReadPropertyFlags[ulProperty])) { Status = CR_INVALID_PROPERTY; goto Clean0; } switch (ulProperty) { // // for some fields, we need to ask from kernel-mode // case CM_DRP_PHYSICAL_DEVICE_OBJECT_NAME: // // This property has special checking in kernel-mode to make // sure the supplied buffer length is even so round it down. // *pulLength &= ~1; // fall through case CM_DRP_BUSTYPEGUID: case CM_DRP_LEGACYBUSTYPE: case CM_DRP_BUSNUMBER: case CM_DRP_ADDRESS: case CM_DRP_DEVICE_POWER_DATA: case CM_DRP_REMOVAL_POLICY: case CM_DRP_REMOVAL_POLICY_HW_DEFAULT: case CM_DRP_REMOVAL_POLICY_OVERRIDE: case CM_DRP_INSTALL_STATE: case CM_DRP_LOCATION_PATHS: if ((ulProperty == CM_DRP_DEVICE_POWER_DATA) || (ulProperty == CM_DRP_BUSTYPEGUID)) { *pulRegDataType = REG_BINARY; } else if (ulProperty == CM_DRP_PHYSICAL_DEVICE_OBJECT_NAME) { *pulRegDataType = REG_SZ; } else if (ulProperty == CM_DRP_LOCATION_PATHS) { *pulRegDataType = REG_MULTI_SZ; } else { // // CM_DRP_LEGACYBUSTYPE, CM_DRP_BUSNUMBER, CM_DRP_ADDRESS, // removal policy properties, and install state are all DWORDs // *pulRegDataType = REG_DWORD; } // // For these properties, we zero out unfilled space. This ensures // deterministic downlevel behavior if we expand any returned // structures in a later release. // bufferLength = *pulLength; // // Fill in a control structure for the device list info. // memset(&ControlData, 0, sizeof(PLUGPLAY_CONTROL_PROPERTY_DATA)); RtlInitUnicodeString(&ControlData.DeviceInstance, pDeviceID); ControlData.PropertyType = MapPropertyToNtProperty(ulProperty); ControlData.Buffer = Buffer; ControlData.BufferSize = bufferLength; // // Call kernel-mode to get the device property. // ntStatus = NtPlugPlayControl(PlugPlayControlProperty, &ControlData, sizeof(ControlData)); if (NT_SUCCESS(ntStatus)) { ASSERT(bufferLength >= ControlData.BufferSize); if (bufferLength > ControlData.BufferSize) { RtlZeroMemory( Buffer + ControlData.BufferSize, bufferLength - ControlData.BufferSize ); } *pulLength = ControlData.BufferSize; // size in bytes *pulTransferLen = bufferLength; // size in bytes } else if (ntStatus == STATUS_BUFFER_TOO_SMALL) { *pulLength = ControlData.BufferSize; *pulTransferLen = 0; Status = CR_BUFFER_SMALL; } else { *pulLength = 0; *pulTransferLen = 0; Status = MapNtStatusToCmError(ntStatus); } break; case CM_DRP_ENUMERATOR_NAME: *pulRegDataType = REG_SZ; pSeparatorChar = wcschr(pDeviceID, L'\\'); ASSERT(pSeparatorChar != NULL); if (pSeparatorChar == NULL) { // // Couldn't find a path separator char in the device id. // Status=CR_INVALID_DATA; } else { // // Determine the number of bytes in the enumerator part. // ulSize = (ULONG)((PBYTE)pSeparatorChar - (PBYTE)pDeviceID) + sizeof(WCHAR); // // Fill in the caller's buffer, if it's large enough. // hr = StringCbCopyNEx((LPWSTR)Buffer, *pulLength, pDeviceID, ulSize - sizeof(WCHAR), NULL, NULL, STRSAFE_NULL_ON_FAILURE); if (SUCCEEDED(hr)) { // // Marshall the amount of data copied to the buffer // *pulTransferLen = ulSize; } else if (HRESULT_CODE(hr) == ERROR_INSUFFICIENT_BUFFER) { // // Buffer too small, marshall no data // Status = CR_BUFFER_SMALL; *pulTransferLen = 0; } else { // // Some other failire, marshall no data. // Status = CR_FAILURE; *pulTransferLen = 0; } // // Whether any data was transfered or not, return the size // required. // *pulLength = ulSize; } break; default: // // for all the other fields, just get them from the registry // open a key to the specified device id // if (RegOpenKeyEx(ghEnumKey, pDeviceID, 0, KEY_READ, &hKey) != ERROR_SUCCESS) { hKey = NULL; // ensure hKey stays NULL so we don't // erroneously try to close it. *pulLength = 0; // no size info for caller Status = CR_INVALID_DEVINST; goto Clean0; } // // retrieve the string form of the property // pPropertyName = MapPropertyToString(ulProperty); if (pPropertyName) { // // retrieve property setting // if (*pulLength == 0) { // // if length of buffer passed in is zero, just looking // for how big a buffer is needed to read the property // if (RegQueryValueEx(hKey, pPropertyName, NULL, pulRegDataType, NULL, pulLength) != ERROR_SUCCESS) { *pulLength = 0; Status = CR_NO_SUCH_VALUE; goto Clean0; } Status = CR_BUFFER_SMALL; // According to spec } else { // // retrieve the real property value, not just the size // RegStatus = RegQueryValueEx(hKey, pPropertyName, NULL, pulRegDataType, Buffer, pulLength); if (RegStatus != ERROR_SUCCESS) { if (RegStatus == ERROR_MORE_DATA) { Status = CR_BUFFER_SMALL; goto Clean0; } else { *pulLength = 0; // no size info for caller Status = CR_NO_SUCH_VALUE; goto Clean0; } } } } else { Status = CR_NO_SUCH_VALUE; goto Clean0; } } Clean0: // // Data only needs to be transferred on CR_SUCCESS. // if (Status == CR_SUCCESS) { *pulTransferLen = *pulLength; } else if (ARGUMENT_PRESENT(pulTransferLen)) { *pulTransferLen = 0; } } except(EXCEPTION_EXECUTE_HANDLER) { Status = CR_FAILURE; // // force compiler to respect statement ordering w.r.t. assignments // for these variables... // hKey = hKey; } if (hKey != NULL) { RegCloseKey(hKey); } return Status; } // PNP_GetDeviceRegProp CONFIGRET PNP_SetDeviceRegProp( IN handle_t hBinding, IN LPCWSTR pDeviceID, IN ULONG ulProperty, IN ULONG ulDataType, IN LPBYTE Buffer, IN ULONG ulLength, IN ULONG ulFlags ) /*++ Routine Description: This is the RPC server entry point for the CM_Set_DevNode_Registry_Property routine. Arguments: hBinding RPC binding handle. pDeviceID Supplies a string containing the device instance whose property will be written to. ulProperty ID specifying which property (the registry value) to set. ulDataType Supplies the registry data type for the specified property (i.e., REG_SZ, etc). Buffer Supplies the address of the buffer that receives the registry data. Can be NULL when simply retrieving data size. pulLength Parameter passed in by caller, on entry it contains the size, in bytes, of the buffer, on exit it contains either the amount of data copied to the caller's buffer (if a transfer occured) or else the size of buffer required to hold the property data. 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_ACCESS_DENIED, CR_INVALID_DEVNODE, CR_INVALID_FLAG, CR_INVALID_POINTER, CR_NO_SUCH_VALUE, CR_REGISTRY_ERROR, or CR_BUFFER_SMALL. --*/ { CONFIGRET Status = CR_SUCCESS; LONG RegStatus = ERROR_SUCCESS; HKEY hKey = NULL; LPWSTR pPropertyName; 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; } // // validate property is valid, and writeable // if ((ulProperty < CM_DRP_MIN) || (ulProperty > CM_DRP_MAX) || (!bDeviceWritePropertyFlags[ulProperty])) { Status = CR_INVALID_PROPERTY; goto Clean0; } // // Currently the only writable fields are in the registry // however do any validation of bits // this isn't foolproof but can catch some common errors // switch(ulProperty) { case CM_DRP_CONFIGFLAGS: { DWORD flags = 0; ULONG ulStatus = 0; ULONG ulProblem = 0; // // DWORD value // try to catch setting CSCONFIGFLAG_DISABLED on a non-disableable device // although we should have validated the size stuff elsewhere, it was at // client-side so double-check here // if (ulDataType != REG_DWORD || ulLength != sizeof(DWORD) || Buffer == NULL) { Status = CR_INVALID_DATA; goto Clean0; } flags = *(DWORD*)Buffer; if(flags & CONFIGFLAG_DISABLED) { // // we're interested in checking this decision to disable device // if (IsRootDeviceID(pDeviceID)) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Cannot set CONFIGFLAG_DISABLED for root device - did caller try to disable device first?\n")); Status = CR_NOT_DISABLEABLE; goto Clean0; } if((GetDeviceStatus(pDeviceID, &ulStatus, &ulProblem)==CR_SUCCESS) && !(ulStatus & DN_DISABLEABLE)) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Cannot set CONFIGFLAG_DISABLED for non-disableable device - did caller try to disable device first?\n")); Status = CR_NOT_DISABLEABLE; goto Clean0; } // // ok, looks like we can proceed to disable device // } break; } default: // // No special handling on other properties // break; } // // open a key to the specified device id // if (RegOpenKeyEx(ghEnumKey, pDeviceID, 0, KEY_READ | KEY_WRITE, &hKey) != ERROR_SUCCESS) { Status = CR_INVALID_DEVINST; goto Clean0; } // // retrieve the string form of the property // pPropertyName = MapPropertyToString(ulProperty); if (pPropertyName) { // // set (or delete) the property value // if (ulLength == 0) { RegStatus = RegDeleteValue(hKey, pPropertyName); } else { RegStatus = RegSetValueEx(hKey, pPropertyName, 0, ulDataType, Buffer, ulLength); } if (RegStatus != ERROR_SUCCESS) { Status = CR_REGISTRY_ERROR; goto Clean0; } } else { Status = CR_FAILURE; goto Clean0; } // // note that changes do not get applied until a reboot / query-remove-remove // Clean0: NOTHING; } except(EXCEPTION_EXECUTE_HANDLER) { Status = CR_FAILURE; // // force compiler to respect statement ordering w.r.t. assignments // for these variables... // hKey = hKey; } if (hKey != NULL) { RegCloseKey(hKey); } return Status; } // PNP_SetDeviceRegProp CONFIGRET PNP_GetClassRegProp( IN handle_t hBinding, IN LPCWSTR ClassGuid, IN ULONG ulProperty, OUT PULONG pulRegDataType OPTIONAL, OUT LPBYTE Buffer, IN OUT PULONG pulTransferLen, IN OUT PULONG pulLength, IN ULONG ulFlags ) /*++ Routine Description: This is the RPC server entry point for the CM_Get_DevNode_Registry_Property routine. Arguments: hBinding RPC binding handle, not used. ClassGuid Supplies a string containing the Class Guid whose property will be read from (Get) or written to (Set). ulProperty ID specifying which property (the registry value) to get or set. pulRegDataType Optionally, supplies the address of a variable that will receive the registry data type for this property (i.e., the REG_* constants). Buffer Supplies the address of the buffer that receives the registry data. Can be NULL when simply retrieving data size. pulTransferLen Used by stubs, indicates how much data to copy back into user buffer. pulLength Parameter passed in by caller, on entry it contains the size, in bytes, of the buffer, on exit it contains either the amount of data copied to the caller's buffer (if a transfer occured) or else the size of buffer required to hold the property data. 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, CR_INVALID_POINTER, CR_NO_SUCH_VALUE, CR_REGISTRY_ERROR, or CR_BUFFER_SMALL. Remarks: The pointer passed in as the pulTransferLen argument must *NOT* be the same as the pointer passed in for the pulLength argument. --*/ { CONFIGRET Status = CR_SUCCESS; LONG RegStatus = ERROR_SUCCESS; HKEY hKeyClass = NULL; HKEY hKeyProps = NULL; LPWSTR pPropertyName; UNREFERENCED_PARAMETER(hBinding); try { // // Validate parameters // ASSERT(pulTransferLen != pulLength); if (!ARGUMENT_PRESENT(pulTransferLen) || !ARGUMENT_PRESENT(pulLength)) { Status = CR_INVALID_POINTER; goto Clean0; } if (INVALID_FLAGS(ulFlags, 0)) { Status = CR_INVALID_FLAG; goto Clean0; } // // Make sure we use no more than either what the caller specified or // what was allocated by RPC, based on the transfer length. // *pulLength = min(*pulLength, *pulTransferLen); *pulTransferLen = 0; // // validate property is valid, and readable // if ((ulProperty < CM_CRP_MIN) || (ulProperty > CM_CRP_MAX) || (!bClassReadPropertyFlags[ulProperty])) { Status = CR_INVALID_PROPERTY; goto Clean0; } // // open a key to the specified GUID - this should have already been created // if (RegOpenKeyEx(ghClassKey, ClassGuid, 0, KEY_READ, &hKeyClass) != ERROR_SUCCESS) { *pulTransferLen = 0; // no output data to marshal *pulLength = 0; // no size info for caller Status = CR_NO_SUCH_REGISTRY_KEY; goto Clean0; } // // open a key to parameters - if not created, there's no params // if (RegOpenKeyEx(hKeyClass, pszRegKeyProperties, 0, KEY_READ, &hKeyProps) != ERROR_SUCCESS) { *pulTransferLen = 0; // no output data to marshal *pulLength = 0; // no size info for caller Status = CR_NO_SUCH_VALUE; goto Clean0; } // // retrieve the string form of the property // pPropertyName = MapPropertyToString(ulProperty); if (pPropertyName) { // // retrieve property setting // if (*pulLength == 0) { // // if length of buffer passed in is zero, just looking // for how big a buffer is needed to read the property // *pulTransferLen = 0; if (RegQueryValueEx(hKeyProps, pPropertyName, NULL, pulRegDataType, NULL, pulLength) != ERROR_SUCCESS) { *pulLength = 0; Status = CR_NO_SUCH_VALUE; goto Clean0; } Status = CR_BUFFER_SMALL; // According to spec } else { // // retrieve the real property value, not just the size // RegStatus = RegQueryValueEx(hKeyProps, pPropertyName, NULL, pulRegDataType, Buffer, pulLength); if (RegStatus != ERROR_SUCCESS) { if (RegStatus == ERROR_MORE_DATA) { *pulTransferLen = 0; // no output data to marshal Status = CR_BUFFER_SMALL; goto Clean0; } else { *pulTransferLen = 0; // no output data to marshal *pulLength = 0; // no size info for caller Status = CR_NO_SUCH_VALUE; goto Clean0; } } *pulTransferLen = *pulLength; } } else { *pulTransferLen = 0; // no output data to marshal *pulLength = 0; // no size info for caller Status = CR_NO_SUCH_VALUE; goto Clean0; } Clean0: NOTHING; } except(EXCEPTION_EXECUTE_HANDLER) { Status = CR_FAILURE; // // force compiler to respect statement ordering w.r.t. assignments // for these variables... // hKeyProps = hKeyProps; hKeyClass = hKeyClass; } if (hKeyProps != NULL) { RegCloseKey(hKeyProps); } if (hKeyClass != NULL) { RegCloseKey(hKeyClass); } return Status; } // PNP_GetClassRegProp CONFIGRET PNP_SetClassRegProp( IN handle_t hBinding, IN LPCWSTR ClassGuid, IN ULONG ulProperty, IN ULONG ulDataType, IN LPBYTE Buffer, IN ULONG ulLength, IN ULONG ulFlags ) /*++ Routine Description: This is the RPC server entry point for the CM_Set_DevNode_Registry_Property routine. Arguments: hBinding RPC binding handle. ClassGuid Supplies a string containing the Class Guid whose property will be read from (Get) or written to (Set). ulProperty ID specifying which property (the registry value) to get or set. ulDataType Supplies the registry data type for the specified property (i.e., REG_SZ, etc). Buffer Supplies the address of the buffer that receives the registry data. Can be NULL when simply retrieving data size. pulLength Parameter passed in by caller, on entry it contains the size, in bytes, of the buffer, on exit it contains either the amount of data copied to the caller's buffer (if a transfer occured) or else the size of buffer required to hold the property data. 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_ACCESS_DENIED, CR_INVALID_DEVNODE, CR_INVALID_FLAG, CR_INVALID_POINTER, CR_NO_SUCH_VALUE, CR_REGISTRY_ERROR, or CR_BUFFER_SMALL. --*/ { CONFIGRET Status = CR_SUCCESS; LONG RegStatus = ERROR_SUCCESS; HKEY hKeyClass = NULL; HKEY hKeyProps = NULL; LPWSTR pPropertyName; DWORD dwError; 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; } // // validate property is valid, and writeable // if ((ulProperty < CM_CRP_MIN) || (ulProperty > CM_CRP_MAX) || (!bClassWritePropertyFlags[ulProperty])) { Status = CR_INVALID_PROPERTY; goto Clean0; } // // Currently the only writable fields are in the registry // // // open a key to the specified GUID - this should have already been created // if (RegOpenKeyEx(ghClassKey, ClassGuid, 0, KEY_READ, &hKeyClass) != ERROR_SUCCESS) { Status = CR_NO_SUCH_REGISTRY_KEY; goto Clean0; } // // open a key to parameters - if not created, we need to create it with priv permissions // this is harmless for a delete, since we "need" it anyway // if (RegOpenKeyEx(hKeyClass, pszRegKeyProperties, 0, KEY_ALL_ACCESS, &hKeyProps) != ERROR_SUCCESS) { // // properties key doesn't exist // we need to create it with secure access (system-only access) // we don't expect to do this often // PSID pSystemSid = NULL; PACL pSystemAcl = NULL; SECURITY_DESCRIPTOR SecDesc; SECURITY_ATTRIBUTES SecAttrib; BOOL bSuccess; SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; EXPLICIT_ACCESS ExplicitAccess; bSuccess = AllocateAndInitializeSid( &NtAuthority, 1, // one authority - SYSTEM SECURITY_LOCAL_SYSTEM_RID, // access to system only 0, 0, 0, 0, 0, 0, 0, // unused authority locations &pSystemSid); if (bSuccess) { ExplicitAccess.grfAccessMode = SET_ACCESS; ExplicitAccess.grfInheritance = CONTAINER_INHERIT_ACE; ExplicitAccess.Trustee.pMultipleTrustee = NULL; ExplicitAccess.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; ExplicitAccess.Trustee.TrusteeForm = TRUSTEE_IS_SID; ExplicitAccess.Trustee.TrusteeType = TRUSTEE_IS_GROUP; ExplicitAccess.grfAccessPermissions = KEY_ALL_ACCESS; ExplicitAccess.Trustee.ptstrName = (LPWSTR)pSystemSid; dwError = SetEntriesInAcl( 1, &ExplicitAccess, NULL, &pSystemAcl ); if (dwError != ERROR_SUCCESS) { bSuccess = FALSE; } } if (bSuccess) { bSuccess = InitializeSecurityDescriptor( &SecDesc, SECURITY_DESCRIPTOR_REVISION); } if (bSuccess) { bSuccess = SetSecurityDescriptorDacl( &SecDesc, TRUE, pSystemAcl, FALSE); } // // mostly a setup requirement, but good to have // effectively is a pruning point in the security tree // child keys inherit our permissions, but not our parents permissions // if (bSuccess) { if (!SetSecurityDescriptorControl( &SecDesc, SE_DACL_PROTECTED | SE_DACL_AUTO_INHERITED, SE_DACL_PROTECTED | SE_DACL_AUTO_INHERITED)) { // // non fatal if this fails // NOTHING; } } if (bSuccess) { bSuccess = IsValidSecurityDescriptor( &SecDesc ); } if (bSuccess) { SecAttrib.nLength = sizeof(SecAttrib); SecAttrib.bInheritHandle = FALSE; SecAttrib.lpSecurityDescriptor = &SecDesc; if(RegCreateKeyEx(hKeyClass, pszRegKeyProperties, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, &SecAttrib, &hKeyProps, NULL) != ERROR_SUCCESS) { bSuccess = FALSE; } } // // now cleanup // if (pSystemAcl) { LocalFree(pSystemAcl); } if (pSystemSid) { FreeSid(pSystemSid); } if (bSuccess == FALSE) { Status = CR_FAILURE; goto Clean0; } } // // retrieve the string form of the property // pPropertyName = MapPropertyToString(ulProperty); if (pPropertyName) { // // set (or delete) the property value // if (ulLength == 0) { RegStatus = RegDeleteValue(hKeyProps, pPropertyName); } else { RegStatus = RegSetValueEx(hKeyProps, pPropertyName, 0, ulDataType, Buffer, ulLength); } if (RegStatus != ERROR_SUCCESS) { Status = CR_REGISTRY_ERROR; goto Clean0; } } else { Status = CR_FAILURE; goto Clean0; } // // note that changes do not get applied until a reboot / query-remove-remove // Clean0: NOTHING; } except(EXCEPTION_EXECUTE_HANDLER) { Status = CR_FAILURE; // // force compiler to respect statement ordering w.r.t. assignments // for these variables... // hKeyProps = hKeyProps; hKeyClass = hKeyClass; } if (hKeyProps != NULL) { RegCloseKey(hKeyProps); } if (hKeyClass != NULL) { RegCloseKey(hKeyClass); } return Status; } // PNP_SetClassRegProp CONFIGRET PNP_GetClassInstance( IN handle_t hBinding, IN LPCWSTR pDeviceID, OUT LPWSTR pszClassInstance, IN ULONG ulLength ) /*++ Routine Description: This is the RPC private server entry point, it doesn't not directly map one-to-one to any CM routine. Arguments: hBinding RPC binding handle. pDeviceID Supplies a string containing the device instance pszClassInstance String to return the class instance in ulLength Size of the pszClassInstance string in chars 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, CR_INVALID_POINTER, CR_NO_SUCH_VALUE, CR_ACCESS_DENIED, CR_REGISTRY_ERROR. --*/ { CONFIGRET Status; WCHAR szInstanceStr[5], szClassGuid[GUID_STRING_LEN]; WCHAR szClassInstance[GUID_STRING_LEN + 5]; DWORD disposition; ULONG ulType, ulTransferLength, ulTempLength, ulInstance; HKEY hClassKey = NULL, hInstanceKey = NULL; HRESULT hr; size_t ClassInstanceLen; try { // // Validate parameters // if (!IsLegalDeviceId(pDeviceID)) { Status = CR_INVALID_DEVNODE; goto Clean0; } // // Get the class instance key name, if one exists. // ulTempLength = ulLength * sizeof(WCHAR); ulTransferLength = ulTempLength; Status = PNP_GetDeviceRegProp(hBinding, pDeviceID, CM_DRP_DRIVER, &ulType, (LPBYTE)pszClassInstance, &ulTransferLength, &ulTempLength, 0); // // The only failure case we will handle beyond attempting to retrieve an // existing CM_DRP_DRIVER property for this device is if no such value // exists. Note that any failure to attempt to create a new class // instance key below should always return CR_NO_SUCH_VALUE to the // caller. // if (Status != CR_NO_SUCH_VALUE) { goto Clean0; } // // Create the class instance since one does not already exist. To do // this, we'll need to create new registry keys, and set a new registry // property for this device, both of which require "write" access. Up // until now, only "read" access was required, so make the additional // check now. // if (!VerifyClientAccess(hBinding, PLUGPLAY_WRITE)) { // // The client does not have access to create a class instance value // for this device. Although the initial request was only to // retrieve an existing value, the PNP_GetClassInstance API is used // only to attempt to explicitly create a new "Driver" value, and // does not directly correlate to any client CM API. We can return // "access denied" to the client, because it should be expected by // the caller when modifying persistent state on the server. // Status = CR_ACCESS_DENIED; goto Clean0; } // // Get the class GUID property for the key to create the instance under. // ulTempLength = sizeof(szClassGuid); ulTransferLength = ulTempLength; Status = PNP_GetDeviceRegProp(hBinding, pDeviceID, CM_DRP_CLASSGUID, &ulType, (LPBYTE)szClassGuid, &ulTransferLength, &ulTempLength, 0); if (Status != CR_SUCCESS) { Status = CR_NO_SUCH_VALUE; goto Clean0; } // // Open the class key. // if (RegOpenKeyEx( ghClassKey, szClassGuid, 0, KEY_READ | KEY_WRITE, &hClassKey) != ERROR_SUCCESS) { Status = CR_NO_SUCH_VALUE; goto Clean0; } for (ulInstance = 0; ulInstance <= 9999; ulInstance++) { // // Find the first available class instance key. // hr = StringCchPrintfEx(szInstanceStr, SIZECHARS(szInstanceStr), NULL, NULL, STRSAFE_NULL_ON_FAILURE, L"%04u", ulInstance); ASSERT(SUCCEEDED(hr)); if (FAILED(hr)) { Status = CR_NO_SUCH_VALUE; break; } if (RegCreateKeyEx( hClassKey, szInstanceStr, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hInstanceKey, &disposition) != ERROR_SUCCESS) { Status = CR_NO_SUCH_VALUE; break; } RegCloseKey(hInstanceKey); hInstanceKey = NULL; if (disposition != REG_CREATED_NEW_KEY) { // // Opened an existing class instance key. Try the next one. // continue; } // // We created a new class instance key. Set the CM_DRP_DRIVER value // for this device, and return. // hr = StringCchPrintf(szClassInstance, SIZECHARS(szClassInstance), L"%s\\%s", szClassGuid, szInstanceStr); ASSERT(SUCCEEDED(hr)); if (FAILED(hr)) { RegDeleteKey(hClassKey, szInstanceStr); Status = CR_NO_SUCH_VALUE; break; } ClassInstanceLen = 0; hr = StringCchLength(szClassInstance, SIZECHARS(szClassInstance), &ClassInstanceLen); ulTempLength = (ULONG)((ClassInstanceLen + 1)*sizeof(WCHAR)); Status = PNP_SetDeviceRegProp(hBinding, pDeviceID, CM_DRP_DRIVER, REG_SZ, (LPBYTE)szClassInstance, ulTempLength, 0); // // If we failed to set the devnode property, delete the registry key // we just created, or else we'll end up orphaning it. // if (Status != CR_SUCCESS) { RegDeleteKey(hClassKey, szInstanceStr); Status = CR_NO_SUCH_VALUE; break; } ASSERT(Status == CR_SUCCESS); // // Now that we've successfully set the new class instance value for // this device, attempt to retreive it again, using the buffer // supplied by the caller. // ulTempLength = ulLength * sizeof(WCHAR); ulTransferLength = ulTempLength; Status = PNP_GetDeviceRegProp(hBinding, pDeviceID, CM_DRP_DRIVER, &ulType, (LPBYTE)pszClassInstance, &ulTransferLength, &ulTempLength, 0); // // We just set the property, so we know the value exists. // ASSERT(Status != CR_NO_SUCH_VALUE); ASSERT((Status == CR_SUCCESS) || (Status == CR_BUFFER_SMALL)); break; } // // If we exhausted all possibile class instance keys, report that no // class instance exists for this device. // if (ulInstance > 9999) { Status = CR_NO_SUCH_VALUE; } // // Close the class key. // RegCloseKey(hClassKey); hClassKey = NULL; ASSERT(hInstanceKey == NULL); Clean0: NOTHING; } except(EXCEPTION_EXECUTE_HANDLER) { Status = CR_FAILURE; // // force compiler to respect statement ordering w.r.t. assignments // for these variables... // hClassKey = hClassKey; hInstanceKey = hInstanceKey; } if (hClassKey != NULL) { RegCloseKey(hClassKey); } if (hInstanceKey != NULL) { RegCloseKey(hInstanceKey); } return Status; } // PNP_GetClassInstance CONFIGRET PNP_CreateKey( IN handle_t hBinding, IN LPCWSTR pszDeviceID, IN REGSAM samDesired, IN ULONG ulFlags ) /*++ Routine Description: This is the RPC server entry point for the CM_Open_DevNode_Key_Ex routine. Arguments: hBinding RPC binding handle. pszDeviceID Supplies the device instance string. samDesired Not used. 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_ACCESS_DENIED, CR_INVALID_DEVNODE, CR_INVALID_FLAG, CR_REGISTRY_ERROR, or CR_BUFFER_SMALL. --*/ { CONFIGRET Status = CR_SUCCESS; LONG RegStatus = ERROR_SUCCESS; HKEY hKeyDevice = NULL, hKey = NULL; ULONG ulSize = 0, i = 0; BOOL bHasDacl, bStatus; SECURITY_DESCRIPTOR NewSecDesc; ACL_SIZE_INFORMATION AclSizeInfo; SID_IDENTIFIER_AUTHORITY Authority = SECURITY_NT_AUTHORITY; PSECURITY_DESCRIPTOR pSecDesc = NULL; PACL pDacl = NULL, pNewDacl = NULL; PSID pAdminSid = NULL; PACCESS_ALLOWED_ACE pAce = NULL; UNREFERENCED_PARAMETER(samDesired); 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(pszDeviceID)) { Status = CR_INVALID_DEVNODE; goto Clean0; } // // open a key to the specified device id // RegStatus = RegOpenKeyEx(ghEnumKey, pszDeviceID, 0, KEY_READ, &hKeyDevice); if (RegStatus != ERROR_SUCCESS) { Status = CR_INVALID_DEVINST; goto Clean0; } // // create the key with security inherited from parent key. Note // that I'm not using passed in access mask, in order to set the // security later, it must be created with KEY_ALL_ACCESS. // RegStatus = RegCreateKeyEx( hKeyDevice, pszRegKeyDeviceParam, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hKey, NULL); if (RegStatus != ERROR_SUCCESS) { Status = CR_REGISTRY_ERROR; goto Clean0; } //------------------------------------------------------------- // add admin-full privilege to the inherited security info //------------------------------------------------------------- // // // NOTE: we don't need to do this unless the key was newly created. In // theory we only get here when the key doesn't already exist. However // there is a remote chance of two threads getting here simultaneously. If // this happens we would end up with two admin full control ACEs. // // // create the admin-full SID // if (!AllocateAndInitializeSid( &Authority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pAdminSid)) { Status = CR_FAILURE; goto Clean0; } // // get the current security descriptor for the key // RegStatus = RegGetKeySecurity( hKey, DACL_SECURITY_INFORMATION, NULL, &ulSize); if (RegStatus != ERROR_INSUFFICIENT_BUFFER && RegStatus != ERROR_SUCCESS) { Status = CR_FAILURE; goto Clean0; } pSecDesc = HeapAlloc(ghPnPHeap, 0, ulSize); if (pSecDesc == NULL) { Status = CR_OUT_OF_MEMORY; goto Clean0; } RegStatus = RegGetKeySecurity( hKey, DACL_SECURITY_INFORMATION, pSecDesc, &ulSize); if (RegStatus != ERROR_SUCCESS) { Status = CR_REGISTRY_ERROR; goto Clean0; } // // get the current DACL // if (!GetSecurityDescriptorDacl(pSecDesc, &bHasDacl, &pDacl, &bStatus)) { Status = CR_FAILURE; goto Clean0; } // // create a new absolute security descriptor and DACL // if (!InitializeSecurityDescriptor( &NewSecDesc, SECURITY_DESCRIPTOR_REVISION)) { Status = CR_FAILURE; goto Clean0; } // // calculate the size of the new DACL // ZeroMemory(&AclSizeInfo, sizeof(AclSizeInfo)); if (bHasDacl) { if (!GetAclInformation( pDacl, &AclSizeInfo, sizeof(ACL_SIZE_INFORMATION), AclSizeInformation)) { Status = CR_FAILURE; goto Clean0; } ulSize = AclSizeInfo.AclBytesInUse; } else { ulSize = sizeof(ACL); } ulSize += sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(pAdminSid) - sizeof(DWORD); // // create and initialize the new DACL // pNewDacl = HeapAlloc(ghPnPHeap, 0, ulSize); if (pNewDacl == NULL) { Status = CR_OUT_OF_MEMORY; goto Clean0; } if (!InitializeAcl(pNewDacl, ulSize, ACL_REVISION2)) { Status = CR_FAILURE; goto Clean0; } // // copy the current (original) DACL into this new one // if (bHasDacl) { for (i = 0; i < AclSizeInfo.AceCount; i++) { if (!GetAce(pDacl, i, (LPVOID *)&pAce)) { Status = CR_FAILURE; goto Clean0; } // // We need to skip copying any ACEs which refer to the Administrator // to ensure that our full control ACE is the one and only. // if ((pAce->Header.AceType != ACCESS_ALLOWED_ACE_TYPE && pAce->Header.AceType != ACCESS_DENIED_ACE_TYPE) || !EqualSid((PSID)&pAce->SidStart, pAdminSid)) { if (!AddAce( pNewDacl, ACL_REVISION2, (DWORD)~0U, pAce, pAce->Header.AceSize)) { Status = CR_FAILURE; goto Clean0; } } } } // // and my new admin-full ace to this new DACL // if (!AddAccessAllowedAceEx( pNewDacl, ACL_REVISION2, CONTAINER_INHERIT_ACE, KEY_ALL_ACCESS, pAdminSid)) { Status = CR_FAILURE; goto Clean0; } // // Set the new DACL in the absolute security descriptor // if (!SetSecurityDescriptorDacl(&NewSecDesc, TRUE, pNewDacl, FALSE)) { Status = CR_FAILURE; goto Clean0; } // // validate the new security descriptor // if (!IsValidSecurityDescriptor(&NewSecDesc)) { Status = CR_FAILURE; goto Clean0; } // // apply the new security back to the registry key // RegStatus = RegSetKeySecurity( hKey, DACL_SECURITY_INFORMATION, &NewSecDesc); if (RegStatus != ERROR_SUCCESS) { Status = CR_REGISTRY_ERROR; goto Clean0; } Clean0: NOTHING; } except(EXCEPTION_EXECUTE_HANDLER) { Status = CR_FAILURE; // // force compiler to respect statement ordering w.r.t. assignments // for these variables... // hKeyDevice = hKeyDevice; hKey = hKey; pAdminSid = pAdminSid; pNewDacl = pNewDacl; pSecDesc = pSecDesc; } if (hKeyDevice != NULL) { RegCloseKey(hKeyDevice); } if (hKey != NULL) { RegCloseKey(hKey); } if (pAdminSid != NULL) { FreeSid(pAdminSid); } if (pNewDacl != NULL) { HeapFree(ghPnPHeap, 0, pNewDacl); } if (pSecDesc != NULL) { HeapFree(ghPnPHeap, 0, pSecDesc); } return Status; } // PNP_CreateKey CONFIGRET PNP_DeleteRegistryKey( IN handle_t hBinding, IN LPCWSTR pszDeviceID, IN LPCWSTR pszParentKey, IN LPCWSTR pszChildKey, IN ULONG ulFlags ) /*++ Routine Description: This is the RPC server entry point for the CM_Delete_DevNode_Key routine. Arguments: hBinding RPC binding handle. pszDeviceID Supplies the device instance string. pszParentKey Supplies the parent registry path of the key to be deleted. pszChildKey Supplies the subkey to be deleted. ulFlags If 0xFFFFFFFF then delete for all profiles 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_ACCESS_DENIED, CR_INVALID_DEVNODE, CR_INVALID_FLAG, CR_INVALID_POINTER, CR_NO_SUCH_VALUE, CR_REGISTRY_ERROR. --*/ { CONFIGRET Status = ERROR_SUCCESS; LONG RegStatus = ERROR_SUCCESS; HKEY hKey = NULL; WCHAR szProfile[MAX_PROFILE_ID_LEN]; PWCHAR pszRegStr = NULL, pszRegKey1 = NULL, pszRegKey2 = NULL; ULONG ulIndex = 0, ulSize = 0; BOOL bPhantom = FALSE; ULONG ulStatus, ulProblem; PWCHAR pszFormatString = NULL; HRESULT hr; // // Note, the service currently cannot access the HKCU branch, so I // assume the keys specified are under HKEY_LOCAL_MACHINE. // try { // // Verify client "write" access // if (!VerifyClientAccess(hBinding, PLUGPLAY_WRITE)) { Status = CR_ACCESS_DENIED; goto Clean0; } // // validate parameters // (currently, 0 and -1 are the only accepted flags.) // if ((ulFlags != 0) && (ulFlags != 0xFFFFFFFF)) { Status = CR_INVALID_FLAG; goto Clean0; } if (!IsLegalDeviceId(pszDeviceID)) { Status = CR_INVALID_DEVNODE; goto Clean0; } // // pszParentKey is a registry path to the pszChildKey parameter. // pszChildKey may be a single path or a compound path, a compound // path is specified if all those subkeys should be deleted (or // made volatile). Note that for real keys we never modify anything // but the lowest level private key. // if (!ARGUMENT_PRESENT(pszParentKey) || !ARGUMENT_PRESENT(pszChildKey) || ((lstrlen(pszParentKey) + 1) > MAX_CM_PATH) || ((lstrlen(pszChildKey) + 1) > MAX_CM_PATH)) { Status = CR_INVALID_POINTER; goto Clean0; } // // Allocate registry path buffers. // pszRegStr = HeapAlloc(ghPnPHeap, 0, 2*MAX_CM_PATH * sizeof(WCHAR)); if (pszRegStr == NULL) { Status = CR_OUT_OF_MEMORY; goto Clean0; } pszRegKey1 = HeapAlloc(ghPnPHeap, 0, 2*MAX_CM_PATH * sizeof(WCHAR)); if (pszRegKey1 == NULL) { Status = CR_OUT_OF_MEMORY; goto Clean0; } pszRegKey2 = HeapAlloc(ghPnPHeap, 0, 2*MAX_CM_PATH * sizeof(WCHAR)); if (pszRegKey2 == NULL) { Status = CR_OUT_OF_MEMORY; goto Clean0; } // // Is the device a phantom? // bPhantom = IsDevicePhantom((LPWSTR)pszDeviceID) || GetDeviceStatus(pszDeviceID, &ulStatus, &ulProblem) != CR_SUCCESS || !(ulStatus & DN_DRIVER_LOADED); if (!bPhantom) { // // for a real key, we never modify anything but the key // where private info is so split if compound. This may // end up leaving a dead device key around in some cases // but another instance of that device could show at any // time so we can't make it volatile. // if (SplitString(pszChildKey, TEXT('\\'), 1, pszRegStr, 2 * MAX_CM_PATH, pszRegKey2, 2 * MAX_CM_PATH)) { // // compound key, only the last subkey will be affected, // tack the rest on as part of the parent key // hr = StringCchPrintfEx(pszRegKey1, 2 * MAX_CM_PATH, NULL, NULL, STRSAFE_NULL_ON_FAILURE, L"%s\\%s", pszParentKey, pszRegStr); ASSERT(SUCCEEDED(hr)); } else { // // wasn't compound so use the whole child key // hr = StringCchCopyEx(pszRegKey1, 2 * MAX_CM_PATH, pszParentKey, NULL, NULL, STRSAFE_NULL_ON_FAILURE); ASSERT(SUCCEEDED(hr)); hr = StringCchCopyEx(pszRegKey2, 2 * MAX_CM_PATH, pszChildKey, NULL, NULL, STRSAFE_NULL_ON_FAILURE); ASSERT(SUCCEEDED(hr)); } } //------------------------------------------------------------- // SPECIAL CASE: If ulHardwareProfile == -1, then need to // delete the private key for all profiles. //------------------------------------------------------------- if (ulFlags == 0xFFFFFFFF) { hr = StringCchPrintfEx(pszRegStr, 2 * MAX_CM_PATH, NULL, NULL, STRSAFE_NULL_ON_FAILURE, L"%s\\%s", pszRegPathIDConfigDB, pszRegKeyKnownDockingStates); ASSERT(SUCCEEDED(hr)); RegStatus = RegOpenKeyEx( HKEY_LOCAL_MACHINE, pszRegStr, 0, KEY_ALL_ACCESS, &hKey); // // enumerate the hardware profile keys // for (ulIndex = 0; RegStatus == ERROR_SUCCESS; ulIndex++) { ulSize = MAX_PROFILE_ID_LEN; RegStatus = RegEnumKeyEx( hKey, ulIndex, szProfile, &ulSize, NULL, NULL, NULL, NULL); if (RegStatus == ERROR_SUCCESS) { // // if phantom, go ahead and delete it // if (bPhantom) { // // pszParentKey contains replacement symbol for the profile id, %s // pszFormatString = wcschr(pszParentKey, L'%'); ASSERT(pszFormatString && (pszFormatString[1] == L's')); if (pszFormatString && (pszFormatString[1] == L's')) { hr = StringCchPrintfEx(pszRegStr, 2 * MAX_CM_PATH, NULL, NULL, STRSAFE_NULL_ON_FAILURE, pszParentKey, szProfile); ASSERT(SUCCEEDED(hr)); Status = DeletePrivateKey( HKEY_LOCAL_MACHINE, pszRegStr, pszChildKey); } else { Status = CR_FAILURE; } } // // if real, just make it volatile // else { // // pszRegKey1 contains replacement symbol for the profile id, %s // pszFormatString = wcschr(pszRegKey1, L'%'); ASSERT(pszFormatString && (pszFormatString[1] == L's')); if (pszFormatString && (pszFormatString[1] == L's')) { hr = StringCchPrintfEx(pszRegStr, 2 * MAX_CM_PATH, NULL, NULL, STRSAFE_NULL_ON_FAILURE, pszRegKey1, szProfile); ASSERT(SUCCEEDED(hr)); KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_REGISTRY, "UMPNPMGR: PNP_DeleteRegistryKey make key %ws\\%ws volatile\n", pszRegStr, pszRegKey2)); Status = MakeKeyVolatile(pszRegStr, pszRegKey2); } else { Status = CR_FAILURE; } } if (Status != CR_SUCCESS) { goto Clean0; } } } } //------------------------------------------------------------------ // not deleting for all profiles, so just delete the specified key //------------------------------------------------------------------ else { if (bPhantom) { // // if phantom, go ahead and delete it // Status = DeletePrivateKey( HKEY_LOCAL_MACHINE, pszParentKey, pszChildKey); } else { // // if real, just make it volatile // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_REGISTRY, "UMPNPMGR: PNP_DeleteRegistryKey make key %ws\\%ws volatile\n", pszRegKey1, pszRegKey2)); Status = MakeKeyVolatile(pszRegKey1, pszRegKey2); } if (Status != CR_SUCCESS) { goto Clean0; } } Clean0: NOTHING; } except(EXCEPTION_EXECUTE_HANDLER) { Status = CR_FAILURE; // // force compiler to respect statement ordering w.r.t. assignments // for these variables... // hKey = hKey; pszRegStr = pszRegStr; pszRegKey1 = pszRegKey1; pszRegKey2 = pszRegKey2; } if (hKey != NULL) { RegCloseKey(hKey); } if (pszRegStr != NULL) { HeapFree(ghPnPHeap, 0, pszRegStr); } if (pszRegKey1 != NULL) { HeapFree(ghPnPHeap, 0, pszRegKey1); } if (pszRegKey2 != NULL) { HeapFree(ghPnPHeap, 0, pszRegKey2); } return Status; } // PNP_DeleteRegistryKey CONFIGRET PNP_GetClassCount( IN handle_t hBinding, OUT PULONG pulClassCount, IN ULONG ulFlags ) /*++ Routine Description: This is the RPC server entry point for the CM_Get_Class_Count routine. It returns the number of valid classes currently installed (listed in the registry). Arguments: hBinding RPC binding handle, not used. pulClassCount Supplies the address of a variable that will receive the number of classes installed. ulFlags Not used. 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_FLAG, CR_INVALID_POINTER, or CR_REGISTRY_ERROR Notes: ** PRESENTLY, ALWAYS RETURNS CR_CALL_NOT_IMPLEMENTED ** No corresponding CM_Get_Class_Count routine is implemented. This routine currently returns CR_CALL_NOT_IMPLEMENTED. --*/ { UNREFERENCED_PARAMETER(hBinding); UNREFERENCED_PARAMETER(pulClassCount); UNREFERENCED_PARAMETER(ulFlags); return CR_CALL_NOT_IMPLEMENTED; } // PNP_GetClassCount CONFIGRET PNP_GetClassName( IN handle_t hBinding, IN PCWSTR pszClassGuid, OUT PWSTR Buffer, IN OUT PULONG pulLength, IN ULONG ulFlags ) /*++ Routine Description: This is the RPC server entry point for the CM_Get_Class_Name routine. It returns the name of the class represented by the GUID. Arguments: hBinding RPC binding handle, not used. pszClassGuid String containing the class guid to retrieve a class name for. Buffer Supplies the address of the buffer that receives the class name. pulLength On input, this specifies the size of the Buffer in characters. On output it contains the number of characters actually copied to Buffer. 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_FLAG, CR_INVALID_POINTER, CR_BUFFER_SMALL, or CR_REGISTRY_ERROR --*/ { CONFIGRET Status = CR_SUCCESS; LONG RegStatus = ERROR_SUCCESS; WCHAR RegStr[MAX_CM_PATH]; HKEY hKey = NULL; ULONG ulLength; UNREFERENCED_PARAMETER(hBinding); try { // // Validate parameters // if (INVALID_FLAGS(ulFlags, 0)) { Status = CR_INVALID_FLAG; goto Clean0; } if ((!ARGUMENT_PRESENT(pulLength)) || (!ARGUMENT_PRESENT(Buffer) && *pulLength != 0)) { Status = CR_INVALID_POINTER; goto Clean0; } // // Open the key for the specified class guid // if ((lstrlen (pszRegPathClass) + lstrlen (pszClassGuid) + sizeof (TEXT("\\"))) > MAX_CM_PATH) { Status = CR_BUFFER_SMALL; goto Clean0; } if (FAILED(StringCchPrintf( RegStr, SIZECHARS(RegStr), TEXT("%s\\%s"), pszRegPathClass, pszClassGuid))) { Status = CR_FAILURE; goto Clean0; } RegStatus = RegOpenKeyEx( HKEY_LOCAL_MACHINE, RegStr, 0, KEY_QUERY_VALUE, &hKey); if (RegStatus != ERROR_SUCCESS) { Status = CR_REGISTRY_ERROR; goto Clean0; } // // Retrieve the class name string value // ulLength = *pulLength; *pulLength *= sizeof(WCHAR); // convert to size in bytes RegStatus = RegQueryValueEx( hKey, pszRegValueClass, NULL, NULL, (LPBYTE)Buffer, pulLength); *pulLength /= sizeof(WCHAR); // convert back to chars if (RegStatus == ERROR_SUCCESS) { Status = CR_SUCCESS; } else if (RegStatus == ERROR_MORE_DATA) { Status = CR_BUFFER_SMALL; if ((ARGUMENT_PRESENT(Buffer)) && (ulLength > 0)) { *Buffer = L'\0'; } } else { Status = CR_REGISTRY_ERROR; if ((ARGUMENT_PRESENT(Buffer)) && (ulLength > 0)) { *Buffer = L'\0'; *pulLength = 1; } } Clean0: NOTHING; } except(EXCEPTION_EXECUTE_HANDLER) { Status = CR_FAILURE; // // force compiler to respect statement ordering w.r.t. assignments // for these variables... // hKey = hKey; } if (hKey != NULL) { RegCloseKey(hKey); } return Status; } // PNP_GetClassName CONFIGRET PNP_DeleteClassKey( IN handle_t hBinding, IN PCWSTR pszClassGuid, IN ULONG ulFlags ) /*++ Routine Description: This is the RPC server entry point for the CM_Delete_Class_Key routine. It deletes the corresponding registry key. Arguments: hBinding RPC binding handle. pszClassGuid String containing the class guid to delete the device setup class registry key for. ulFlags Either CM_DELETE_CLASS_ONLY or CM_DELETE_CLASS_SUBKEYS. 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_ACCESS_DENIED, CR_INVALID_FLAG, CR_INVALID_POINTER, CR_NO_SUCH_REGISTRY_KEY, CR_REGISTRY_ERROR, or CR_FAILURE --*/ { CONFIGRET Status = CR_SUCCESS; HKEY hKey = NULL; try { // // Verify client "write" access // if (!VerifyClientAccess(hBinding, PLUGPLAY_WRITE)) { Status = CR_ACCESS_DENIED; goto Clean0; } // // Validate parameters // if (INVALID_FLAGS(ulFlags, CM_DELETE_CLASS_BITS)) { Status = CR_INVALID_FLAG; goto Clean0; } ASSERT(ARGUMENT_PRESENT(pszClassGuid)); if ((!ARGUMENT_PRESENT(pszClassGuid)) || (*pszClassGuid == L'\0')) { Status = CR_INVALID_POINTER; goto Clean0; } // // Check that the specified class key exists. // if (RegOpenKeyEx(ghClassKey, pszClassGuid, 0, KEY_READ, &hKey) != ERROR_SUCCESS) { Status = CR_NO_SUCH_REGISTRY_KEY; goto Clean0; } RegCloseKey(hKey); if (ulFlags == CM_DELETE_CLASS_SUBKEYS) { // // Delete the class key and any subkeys under it // if (!RegDeleteNode(ghClassKey, pszClassGuid)) { Status = CR_REGISTRY_ERROR; } } else if (ulFlags == CM_DELETE_CLASS_ONLY) { // // only delete the class key itself (just attempt to delete // using the registry routine, it will fail if any subkeys // exist) // if (RegDeleteKey(ghClassKey, pszClassGuid) != ERROR_SUCCESS) { Status = CR_REGISTRY_ERROR; } } Clean0: NOTHING; } except(EXCEPTION_EXECUTE_HANDLER) { Status = CR_FAILURE; } return Status; } // PNP_DeleteClassKey CONFIGRET PNP_GetInterfaceDeviceAlias( IN handle_t hBinding, IN PCWSTR pszInterfaceDevice, IN LPGUID AliasInterfaceGuid, OUT PWSTR pszAliasInterfaceDevice, IN OUT PULONG pulLength, IN OUT PULONG pulTransferLen, IN ULONG ulFlags ) /*++ Routine Description: This is the RPC server entry point for the CM_Get_Interface_Device_Alias routine. It returns an alias string for the specified guid and interface device. Arguments: hBinding RPC binding handle, not used. pszInterfaceDevice Specifies the interface device to find an alias for. AliasInterfaceGuid Supplies the interface class GUID. pszAliasInterfaceDevice Supplies the address of a variable that will receive the device interface alias of the specified device interface, that is a member of the specified alias interface class GUID. pulLength Parameter passed in by caller, on entry it contains the size, in bytes, of the buffer, on exit it contains either the amount of data copied to the caller's buffer (if a transfer occured) or else the size of buffer required to hold the property data. pulTransferLen Used by stubs, indicates how much data to copy back into user buffer. 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_FLAG, CR_INVALID_POINTER, or CR_REGISTRY_ERROR --*/ { CONFIGRET Status = CR_SUCCESS; NTSTATUS ntStatus = STATUS_SUCCESS; PLUGPLAY_CONTROL_INTERFACE_ALIAS_DATA ControlData; UNREFERENCED_PARAMETER(hBinding); try { // // Validate parameters // ASSERT(pulTransferLen != pulLength); if (!ARGUMENT_PRESENT(pszInterfaceDevice) || !ARGUMENT_PRESENT(AliasInterfaceGuid) || !ARGUMENT_PRESENT(pszAliasInterfaceDevice) || !ARGUMENT_PRESENT(pulTransferLen) || !ARGUMENT_PRESENT(pulLength)) { Status = CR_INVALID_POINTER; goto Clean0; } if (INVALID_FLAGS(ulFlags, 0)) { Status = CR_INVALID_FLAG; goto Clean0; } // // Make sure we use no more than either what the caller specified or // what was allocated by RPC, based on the transfer length. // *pulLength = min(*pulLength, *pulTransferLen); // // Fill in a control structure for the device list info. // // // Note that AliasInterfaceGuid was already validated above because this // buffer is required for the PlugPlayControlGetInterfaceDeviceAlias // control, and is probed unconditionally by kernel-mode. Better to // fail the call above with a useful status than to return the generic // CR_FAILURE after an exception/error from kernel-mode, below. // memset(&ControlData, 0, sizeof(PLUGPLAY_CONTROL_INTERFACE_ALIAS_DATA)); RtlInitUnicodeString(&ControlData.SymbolicLinkName, pszInterfaceDevice); ControlData.AliasClassGuid = AliasInterfaceGuid; ControlData.AliasSymbolicLinkName = pszAliasInterfaceDevice; ControlData.AliasSymbolicLinkNameLength = *pulLength; // chars // // Call kernel-mode to get the device interface alias. // ntStatus = NtPlugPlayControl(PlugPlayControlGetInterfaceDeviceAlias, &ControlData, sizeof(ControlData)); if (NT_SUCCESS(ntStatus)) { *pulLength = ControlData.AliasSymbolicLinkNameLength; *pulTransferLen = *pulLength + 1; } else if (ntStatus == STATUS_BUFFER_TOO_SMALL) { *pulLength = ControlData.AliasSymbolicLinkNameLength; Status = CR_BUFFER_SMALL; } else { *pulLength = 0; Status = MapNtStatusToCmError(ntStatus); } Clean0: // // Initialize output parameters // if ((Status != CR_SUCCESS) && ARGUMENT_PRESENT(pulTransferLen) && ARGUMENT_PRESENT(pszAliasInterfaceDevice) && (*pulTransferLen > 0)) { *pszAliasInterfaceDevice = L'\0'; *pulTransferLen = 1; } } except(EXCEPTION_EXECUTE_HANDLER) { Status = CR_FAILURE; } return Status; } // PNP_GetInterfaceDeviceAlias CONFIGRET PNP_GetInterfaceDeviceList( IN handle_t hBinding, IN LPGUID InterfaceGuid, IN LPCWSTR pszDeviceID, OUT LPWSTR Buffer, IN OUT PULONG pulLength, IN ULONG ulFlags ) /*++ Routine Description: This is the RPC server entry point for the CM_Get_Device_Interface_List routine. It returns a multi_sz interface device list. Arguments: hBinding RPC binding handle, not used. InterfaceGuid Supplies the interface class GUID. pszDeviceID Supplies the device instance string. Buffer Supplies the address of the buffer that receives the registry data. pulLength Specifies the size, in bytes, of the buffer. ulFlags Flags specifying which device interfaces to return. Currently, may be either: CM_GET_DEVICE_INTERFACE_LIST_PRESENT, or CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES. 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_FLAG, CR_INVALID_DEVNODE, CR_INVALID_POINTER, or CR_REGISTRY_ERROR --*/ { CONFIGRET Status = CR_SUCCESS; NTSTATUS ntStatus = STATUS_SUCCESS; PLUGPLAY_CONTROL_INTERFACE_LIST_DATA ControlData; UNREFERENCED_PARAMETER(hBinding); try { // // Validate parameters // if (INVALID_FLAGS(ulFlags, CM_GET_DEVICE_INTERFACE_LIST_BITS)) { Status = CR_INVALID_FLAG; goto Clean0; } if (!ARGUMENT_PRESENT(InterfaceGuid) || !ARGUMENT_PRESENT(pulLength) || !ARGUMENT_PRESENT(Buffer) || (*pulLength == 0)) { Status = CR_INVALID_POINTER; goto Clean0; } if (!IsLegalDeviceId(pszDeviceID)) { Status = CR_INVALID_DEVNODE; goto Clean0; } // // Fill in a control structure for the device list info. // // // Note that InterfaceGuid was already validated above because this // buffer is required for the PlugPlayControlGetInterfaceDeviceList // control, and is probed unconditionally by kernel-mode. Better to // fail the call above with a useful status than to return the generic // CR_FAILURE after an exception/error from kernel-mode, below. // memset(&ControlData, 0, sizeof(PLUGPLAY_CONTROL_INTERFACE_LIST_DATA)); RtlInitUnicodeString(&ControlData.DeviceInstance, pszDeviceID); ControlData.InterfaceGuid = InterfaceGuid; ControlData.InterfaceList = Buffer; ControlData.InterfaceListSize = *pulLength; if (ulFlags == CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES) { ControlData.Flags = 0x1; // DEVICE_INTERFACE_INCLUDE_NONACTIVE (ntos\inc\pnp.h) } else { ControlData.Flags = 0; } // // Call kernel-mode to get the device interface list. // ntStatus = NtPlugPlayControl(PlugPlayControlGetInterfaceDeviceList, &ControlData, sizeof(ControlData)); if (NT_SUCCESS(ntStatus)) { *pulLength = ControlData.InterfaceListSize; } else { *pulLength = 0; if (ntStatus == STATUS_BUFFER_TOO_SMALL) { Status = CR_BUFFER_SMALL; } else { Status = CR_FAILURE; } } Clean0: NOTHING; } except(EXCEPTION_EXECUTE_HANDLER) { Status = CR_FAILURE; } return Status; } // PNP_GetInterfaceDeviceList CONFIGRET PNP_GetInterfaceDeviceListSize( IN handle_t hBinding, OUT PULONG pulLen, IN LPGUID InterfaceGuid, IN LPCWSTR pszDeviceID, IN ULONG ulFlags ) /*++ Routine Description: This is the RPC server entry point for the CM_Get_Device_Interface_List_Size routine. It returns the size (in chars) of a multi_sz interface device list. Arguments: hBinding RPC binding handle, not used. pulLen Supplies the address of a variable that, upon successful return, receives the the size of buffer required to hold the multi_sz interface device list. InterfaceGuid Supplies the interface class GUID. pszDeviceID Supplies the device instance string. ulFlags Flags specifying which device interfaces to return. Currently, may be either: CM_GET_DEVICE_INTERFACE_LIST_PRESENT, or CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES. 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_FLAG, CR_INVALID_POINTER, or CR_REGISTRY_ERROR --*/ { CONFIGRET Status = CR_SUCCESS; NTSTATUS ntStatus = STATUS_SUCCESS; PLUGPLAY_CONTROL_INTERFACE_LIST_DATA ControlData; UNREFERENCED_PARAMETER(hBinding); try { // // Validate parameters // if (INVALID_FLAGS(ulFlags, CM_GET_DEVICE_INTERFACE_LIST_BITS)) { Status = CR_INVALID_FLAG; goto Clean0; } if (!ARGUMENT_PRESENT(InterfaceGuid) || !ARGUMENT_PRESENT(pulLen)) { Status = CR_INVALID_POINTER; goto Clean0; } if (!IsLegalDeviceId(pszDeviceID)) { Status = CR_INVALID_DEVNODE; goto Clean0; } // // Initialize the returned output length // *pulLen = 0; // // Fill in a control structure for the device list info. // // // Note that InterfaceGuid was already validated above because this // buffer is required for the PlugPlayControlGetInterfaceDeviceList // control, and is probed unconditionally by kernel-mode. Better to // fail the call above with a useful status than to return the generic // CR_FAILURE after an exception/error from kernel-mode, below. // memset(&ControlData, 0, sizeof(PLUGPLAY_CONTROL_INTERFACE_LIST_DATA)); RtlInitUnicodeString(&ControlData.DeviceInstance, pszDeviceID); ControlData.InterfaceGuid = InterfaceGuid; ControlData.InterfaceList = NULL; ControlData.InterfaceListSize = 0; if (ulFlags == CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES) { ControlData.Flags = 0x1; // DEVICE_INTERFACE_INCLUDE_NONACTIVE (ntos\inc\pnp.h) } else { ControlData.Flags = 0; } // // Call kernel-mode to get the device interface list size. // ntStatus = NtPlugPlayControl(PlugPlayControlGetInterfaceDeviceList, &ControlData, sizeof(ControlData)); if (NT_SUCCESS(ntStatus)) { *pulLen = ControlData.InterfaceListSize; } else { Status = CR_FAILURE; } Clean0: NOTHING; } except(EXCEPTION_EXECUTE_HANDLER) { Status = CR_FAILURE; } return Status; } // PNP_GetInterfaceDeviceListSize CONFIGRET PNP_RegisterDeviceClassAssociation( IN handle_t hBinding, IN LPCWSTR pszDeviceID, IN LPGUID InterfaceGuid, IN LPCWSTR pszReference OPTIONAL, OUT PWSTR pszSymLink, IN OUT PULONG pulLength, IN OUT PULONG pulTransferLen, IN ULONG ulFlags ) /*++ Routine Description: This is the RPC server entry point for the CM_Register_Device_Interface routine. It registers a device interface for the specified device and device interface class, and returns the symbolic link name for the device interface. Arguments: hBinding RPC binding handle. pszDeviceID Supplies the device instance string. InterfaceGuid Supplies the interface class guid. pszReference Optionally, supplies the reference string name. pszSymLink Receives the symbolic link name. pulLength Parameter passed in by caller, on entry it contains the size, in bytes, of the buffer, on exit it contains either the amount of data copied to the caller's buffer (if a transfer occured) or else the size of buffer required to hold the property data. pulTransferLen Used by stubs, indicates how much data to copy back into user buffer. 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_ACCESS_DENIED, CR_INVALID_FLAG, CR_INVALID_POINTER, or CR_REGISTRY_ERROR Remarks: The pointer passed in as the pulTransferLen argument must *NOT* be the same as the pointer passed in for the pulLength argument. --*/ { CONFIGRET Status = CR_SUCCESS; NTSTATUS ntStatus = STATUS_SUCCESS; PLUGPLAY_CONTROL_CLASS_ASSOCIATION_DATA ControlData; try { // // Verify client "write" access // if (!VerifyClientAccess(hBinding, PLUGPLAY_WRITE)) { Status = CR_ACCESS_DENIED; goto Clean0; } // // Validate parameters // ASSERT(pulTransferLen != pulLength); if (!ARGUMENT_PRESENT(InterfaceGuid) || !ARGUMENT_PRESENT(pszSymLink) || !ARGUMENT_PRESENT(pulTransferLen) || !ARGUMENT_PRESENT(pulLength)) { Status = CR_INVALID_POINTER; goto Clean0; } if (INVALID_FLAGS(ulFlags, 0)) { Status = CR_INVALID_FLAG; goto Clean0; } if (!IsLegalDeviceId(pszDeviceID)) { Status = CR_INVALID_DEVNODE; goto Clean0; } // // Make sure we use no more than either what the caller specified or // what was allocated by RPC, based on the transfer length. // *pulLength = min(*pulLength, *pulTransferLen); // // Fill in a control structure for the device list info. // // // Note that InterfaceGuid was already validated above because this // buffer is required for the PlugPlayControlDeviceClassAssociation // control, for Registration only. // memset(&ControlData, 0, sizeof(PLUGPLAY_CONTROL_CLASS_ASSOCIATION_DATA)); RtlInitUnicodeString(&ControlData.DeviceInstance, pszDeviceID); RtlInitUnicodeString(&ControlData.Reference, pszReference); ControlData.InterfaceGuid = InterfaceGuid; ControlData.Register = TRUE; ControlData.SymLink = pszSymLink; ControlData.SymLinkLength = *pulLength; // // Call kernel-mode to register the device association. // ntStatus = NtPlugPlayControl(PlugPlayControlDeviceClassAssociation, &ControlData, sizeof(ControlData)); if (NT_SUCCESS(ntStatus)) { *pulLength = ControlData.SymLinkLength; *pulTransferLen = *pulLength; } else if (ntStatus == STATUS_BUFFER_TOO_SMALL) { *pulLength = ControlData.SymLinkLength; Status = CR_BUFFER_SMALL; } else { *pulLength = 0; Status = MapNtStatusToCmError(ntStatus); } Clean0: // // Initialize output parameters // if ((Status != CR_SUCCESS) && ARGUMENT_PRESENT(pszSymLink) && ARGUMENT_PRESENT(pulTransferLen) && (*pulTransferLen > 0)) { *pszSymLink = L'\0'; *pulTransferLen = 1; } } except(EXCEPTION_EXECUTE_HANDLER) { Status = CR_FAILURE; } return Status; } // PNP_RegisterDeviceClassAssociation CONFIGRET PNP_UnregisterDeviceClassAssociation( IN handle_t hBinding, IN LPCWSTR pszInterfaceDevice, IN ULONG ulFlags ) /*++ Routine Description: This is the RPC server entry point for the CM_Unregister_Device_Interface routine. Arguments: hBinding RPC binding handle. pszInterfaceDevice Specifies the interface device to unregister 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_ACCESS_DENIED, CR_DEVICE_INTERFACE_ACTIVE, or CR_FAILURE. --*/ { CONFIGRET Status = CR_SUCCESS; NTSTATUS ntStatus = STATUS_SUCCESS; PLUGPLAY_CONTROL_CLASS_ASSOCIATION_DATA ControlData; try { // // Verify client "write" access // if (!VerifyClientAccess(hBinding, PLUGPLAY_WRITE)) { Status = CR_ACCESS_DENIED; goto Clean0; } // // Validate parameters // if (!ARGUMENT_PRESENT(pszInterfaceDevice)) { Status = CR_INVALID_POINTER; goto Clean0; } if (INVALID_FLAGS(ulFlags, 0)) { Status = CR_INVALID_FLAG; goto Clean0; } // // Fill in a control structure for the device list info. // // // Note that the DeviceInstance, Reference, and InterfaceGuid members // are not required for the PlugPlayControlDeviceClassAssociation // control, for unregistration only. Only the symbolic link name is // required to unregister the device interface. // memset(&ControlData, 0, sizeof(PLUGPLAY_CONTROL_CLASS_ASSOCIATION_DATA)); ControlData.Register = FALSE; ControlData.SymLink = (LPWSTR)pszInterfaceDevice; ControlData.SymLinkLength = lstrlen(pszInterfaceDevice) + 1; // // Call kernel-mode to deregister the device association. // ntStatus = NtPlugPlayControl(PlugPlayControlDeviceClassAssociation, &ControlData, sizeof(ControlData)); if (!NT_SUCCESS(ntStatus)) { if (ntStatus == STATUS_ACCESS_DENIED) { Status = CR_DEVICE_INTERFACE_ACTIVE; } else { Status = MapNtStatusToCmError(ntStatus); } } Clean0: NOTHING; } except(EXCEPTION_EXECUTE_HANDLER) { Status = CR_FAILURE; } return Status; } // PNP_UnregisterDeviceClassAssociation //------------------------------------------------------------------- // Private export for the Service Controller //------------------------------------------------------------------- CONFIGRET DeleteServicePlugPlayRegKeys( IN LPWSTR pszService ) /*++ Routine Description: This routine is called directly and privately by the Service Controller whenever a service has been deleted. It allows the SCM to delete any Plug and Play registry keys that may have been created for a service. Arguments: pszService - Specifies the name of the service. Return Value: Return CR_SUCCESS if the function succeeds, otherwise it returns one of the CR_* errors. Note: This routine is privately exported, and is to be called only by the Service Control Manager, during service deletion. --*/ { CONFIGRET Status = CR_SUCCESS; ULONG ulSize, ulFlags, ulHardwareProfile, ulPass; LPWSTR pDeviceList = NULL, pDeviceID; WCHAR szParentKey[MAX_CM_PATH], szChildKey[MAX_DEVICE_ID_LEN]; BOOL RootEnumerationRequired = FALSE; ULONG ulProblem, ulStatus; try { // // validate parameters // if (!ARGUMENT_PRESENT(pszService)) { Status = CR_INVALID_POINTER; goto Clean0; } // // retreive the maximum size required for a buffer to receive the list // of devices that this service is controlling // Status = PNP_GetDeviceListSize(NULL, pszService, &ulSize, CM_GETIDLIST_FILTER_SERVICE); if (Status != CR_SUCCESS) { goto Clean0; } pDeviceList = HeapAlloc(ghPnPHeap, 0, ulSize * sizeof(WCHAR)); if (pDeviceList == NULL) { Status = CR_OUT_OF_MEMORY; goto Clean0; } // // retrieve the list of devices that this service is controlling, make // sure that we don't generate one if none already exist // Status = PNP_GetDeviceList(NULL, pszService, pDeviceList, &ulSize, CM_GETIDLIST_FILTER_SERVICE | CM_GETIDLIST_DONOTGENERATE); if (Status != CR_SUCCESS) { goto Clean0; } // // delete the registry keys for each device instance for this service // for (pDeviceID = pDeviceList; *pDeviceID; pDeviceID += lstrlen(pDeviceID) + 1) { for (ulPass = 0; ulPass < 4; ulPass++) { // // delete the registry keys for all hardware profiles, followed // by the system global registry keys // ulFlags = 0; ulHardwareProfile = 0; if (ulPass == 0) { ulFlags = CM_REGISTRY_HARDWARE | CM_REGISTRY_CONFIG; ulHardwareProfile = 0xFFFFFFFF; } else if (ulPass == 1) { ulFlags = CM_REGISTRY_SOFTWARE | CM_REGISTRY_CONFIG; ulHardwareProfile = 0xFFFFFFFF; } else if (ulPass == 2) { ulFlags = CM_REGISTRY_HARDWARE; ulHardwareProfile = 0; } else if (ulPass == 3) { ulFlags = CM_REGISTRY_SOFTWARE; ulHardwareProfile = 0; } // // form the registry path based on the device id and the flags // if (GetDevNodeKeyPath(NULL, pDeviceID, ulFlags, ulHardwareProfile, szParentKey, SIZECHARS(szParentKey), szChildKey, SIZECHARS(szChildKey), FALSE) == CR_SUCCESS) { // // remove the specified registry key // PNP_DeleteRegistryKey( NULL, // rpc binding handle (NULL) pDeviceID, // device id szParentKey, // parent of key to delete szChildKey, // key to delete ulHardwareProfile); // flags, not used } } // // Uninstall the device instance (see also PNP_UninstallDevInst). // //------------------------------------------------------------------ // 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) && !(ulStatus & DN_DISABLEABLE)) { // // if a device is root enumerated, but not disableable, it is not uninstallable // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_REGISTRY, "UMPNPMGR: DeleteServicePlugPlayRegKeys: " "failed uninstall of %ws (this root device is not disableable)\n", pDeviceID)); } else { // // do the volatile-copy-thing // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_REGISTRY, "UMPNPMGR: DeleteServicePlugPlayRegKeys: " "doing volatile key thing on %ws\n", pDeviceID)); UninstallRealDevice(pDeviceID); } } else { //------------------------------------------------------------- // device is a phantom so actually delete it //------------------------------------------------------------- if (UninstallPhantomDevice(pDeviceID) != CR_SUCCESS) { continue; } // // if it is a root enumerated device, we need to reenumerate the // root (if not planning to do so already) so that the PDO will // go away, otherwise a new device could be created and the root // enumerator would get very confused. // if ((!RootEnumerationRequired) && (IsDeviceRootEnumerated(pDeviceID))) { RootEnumerationRequired = TRUE; } } } // // Now that we're done processing all devices, see if we need to // reenumerate the root. // if (RootEnumerationRequired) { // // Reenumerate the root devnode asynchronously so that the service // controller does not block waiting for this routine to complete!! // (If we were processing device events at this time, the SCM would // be blocked here and not be able to deliver any events for us. // That would stall the event queue, preventing a synchronous device // enumeration from completing). // ReenumerateDevInst(pszRegRootEnumerator, FALSE, CM_REENUMERATE_ASYNCHRONOUS); } Clean0: NOTHING; } except(EXCEPTION_EXECUTE_HANDLER) { Status = CR_FAILURE; // // force compiler to respect statement ordering w.r.t. assignments // for these variables... // pDeviceList = pDeviceList; } if (pDeviceList) { HeapFree(ghPnPHeap, 0, pDeviceList); } return Status; } // DeleteServicePlugPlayRegKeys //------------------------------------------------------------------- // Private utility routines //------------------------------------------------------------------- LPWSTR MapPropertyToString( ULONG ulProperty ) { switch (ulProperty) { case CM_DRP_DEVICEDESC: return pszRegValueDeviceDesc; case CM_DRP_HARDWAREID: return pszRegValueHardwareIDs; case CM_DRP_COMPATIBLEIDS: return pszRegValueCompatibleIDs; case CM_DRP_SERVICE: return pszRegValueService; case CM_DRP_CLASS: return pszRegValueClass; case CM_DRP_CLASSGUID: return pszRegValueClassGuid; case CM_DRP_DRIVER: return pszRegValueDriver; case CM_DRP_CONFIGFLAGS: return pszRegValueConfigFlags; case CM_DRP_MFG: return pszRegValueMfg; case CM_DRP_FRIENDLYNAME: return pszRegValueFriendlyName; case CM_DRP_LOCATION_INFORMATION: return pszRegValueLocationInformation; case CM_DRP_CAPABILITIES: return pszRegValueCapabilities; case CM_DRP_UI_NUMBER: return pszRegValueUiNumber; case CM_DRP_UPPERFILTERS: return pszRegValueUpperFilters; case CM_DRP_LOWERFILTERS: return pszRegValueLowerFilters; case CM_DRP_SECURITY: // and CM_CRP_SECURITY return pszRegValueSecurity; case CM_DRP_DEVTYPE: // and CM_CRP_DEVTYPE return pszRegValueDevType; case CM_DRP_EXCLUSIVE: // and CM_CRP_EXCLUSIVE return pszRegValueExclusive; case CM_DRP_CHARACTERISTICS: // and CM_CRP_CHARACTERISTICS return pszRegValueCharacteristics; case CM_DRP_UI_NUMBER_DESC_FORMAT: return pszRegValueUiNumberDescFormat; case CM_DRP_REMOVAL_POLICY_OVERRIDE: return pszRegValueRemovalPolicyOverride; default: return NULL; } } // MapPropertyToString ULONG MapPropertyToNtProperty( ULONG ulProperty ) { switch (ulProperty) { case CM_DRP_PHYSICAL_DEVICE_OBJECT_NAME: return PNP_PROPERTY_PDONAME; case CM_DRP_BUSTYPEGUID: return PNP_PROPERTY_BUSTYPEGUID; case CM_DRP_LEGACYBUSTYPE: return PNP_PROPERTY_LEGACYBUSTYPE; case CM_DRP_BUSNUMBER: return PNP_PROPERTY_BUSNUMBER; case CM_DRP_ADDRESS: return PNP_PROPERTY_ADDRESS; case CM_DRP_DEVICE_POWER_DATA: return PNP_PROPERTY_POWER_DATA; case CM_DRP_REMOVAL_POLICY: return PNP_PROPERTY_REMOVAL_POLICY; case CM_DRP_REMOVAL_POLICY_HW_DEFAULT: return PNP_PROPERTY_REMOVAL_POLICY_HARDWARE_DEFAULT; case CM_DRP_REMOVAL_POLICY_OVERRIDE: return PNP_PROPERTY_REMOVAL_POLICY_OVERRIDE; case CM_DRP_INSTALL_STATE: return PNP_PROPERTY_INSTALL_STATE; case CM_DRP_LOCATION_PATHS: return PNP_PROPERTY_LOCATION_PATHS; default: return 0; } } // MapPropertyToNtProperty CONFIGRET PNP_GetCustomDevProp( IN handle_t hBinding, IN LPCWSTR pDeviceID, IN LPCWSTR CustomPropName, OUT PULONG pulRegDataType, OUT LPBYTE Buffer, OUT PULONG pulTransferLen, IN OUT PULONG pulLength, IN ULONG ulFlags ) /*++ Routine Description: This is the RPC server entry point for the CM_Get_DevNode_Custom_Property routine. Arguments: hBinding RPC binding handle, not used. pDeviceID Supplies a string containing the device instance whose property will be read from. CustomPropName Supplies a string identifying the name of the property (registry value entry name) to be retrieved. pulRegDataType Supplies the address of a variable that will receive the registry data type for this property (i.e., the REG_* constants). Buffer Supplies the address of the buffer that receives the registry data. If the caller is simply retrieving the required size, pulLength will be zero. pulTransferLen Used by stubs, indicates how much data to copy back into user buffer. pulLength Parameter passed in by caller, on entry it contains the size, in bytes, of the buffer, on exit it contains either the amount of data copied to the caller's buffer (if a transfer occurred) or else the size of buffer required to hold the property data. ulFlags May be a combination of the following values: CM_CUSTOMDEVPROP_MERGE_MULTISZ : merge the devnode-specific REG_SZ or REG_MULTI_SZ property (if present) with the per-hardware-id REG_SZ or REG_MULTI_SZ property (if present). The result will always be a REG_MULTI_SZ. Note: REG_EXPAND_SZ data is not merged in this manner, as there is no way to indicate that the resultant list needs environment variable expansion (i.e., there's no such registry datatype as REG_EXPAND_MULTI_SZ). 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_REGISTRY_ERROR, CR_BUFFER_SMALL, CR_NO_SUCH_VALUE, or CR_FAILURE. Remarks: The pointer passed in as the pulTransferLen argument must *NOT* be the same as the pointer passed in for the pulLength argument. --*/ { CONFIGRET Status = CR_SUCCESS; LONG RegStatus; HKEY hDevKey = NULL; HKEY hDevParamsKey = NULL; HKEY hPerHwIdSubKey = NULL; WCHAR PerHwIdSubkeyName[MAX_DEVNODE_ID_LEN]; ULONG RequiredSize = 0; FILETIME CacheDate, LastUpdateTime; DWORD RegDataType, RegDataSize; LPBYTE PerHwIdBuffer = NULL; DWORD PerHwIdBufferLen = 0; LPWSTR pCurId; BOOL MergeMultiSzResult = FALSE; UNREFERENCED_PARAMETER(hBinding); try { // // Validate parameters // if (!ARGUMENT_PRESENT(pulTransferLen) || !ARGUMENT_PRESENT(pulLength)) { Status = CR_INVALID_POINTER; goto Clean0; } // // We should never have both arguments pointing to the same memory... // ASSERT(pulTransferLen != pulLength); // // ...but if we do, fail the call. // if (pulTransferLen == pulLength) { Status = CR_INVALID_POINTER; goto Clean0; } *pulTransferLen = 0; if (INVALID_FLAGS(ulFlags, CM_CUSTOMDEVPROP_BITS)) { Status = CR_INVALID_FLAG; goto Clean0; } if(!IsLegalDeviceId(pDeviceID)) { Status = CR_INVALID_DEVNODE; goto Clean0; } // // First, open the device instance key. We'll then open the "Device // Parameters" subkey off of that. We do this in two steps, because // we're likely to need a handle to the device instance key in order // to track down the per-hw-id property. // if(ERROR_SUCCESS != RegOpenKeyEx(ghEnumKey, pDeviceID, 0, KEY_READ | KEY_WRITE, &hDevKey)) { hDevKey = NULL; // ensure hDevKey is still NULL so we // won't erroneously try to close it. RequiredSize = 0; // no size info for caller Status = CR_REGISTRY_ERROR; goto Clean0; } if(ERROR_SUCCESS == RegOpenKeyEx(hDevKey, pszRegKeyDeviceParam, 0, KEY_READ, &hDevParamsKey)) { RequiredSize = *pulLength; RegStatus = RegQueryValueEx(hDevParamsKey, CustomPropName, NULL, pulRegDataType, Buffer, &RequiredSize ); if(RegStatus == ERROR_SUCCESS) { // // We need to distinguish between the case where we succeeded // because the caller supplied a zero-length buffer (we call it // CR_BUFFER_SMALL) and the "real" success case. // if((*pulLength == 0) && (RequiredSize != 0)) { Status = CR_BUFFER_SMALL; } } else { if(RegStatus == ERROR_MORE_DATA) { Status = CR_BUFFER_SMALL; } else { RequiredSize = 0; Status = CR_NO_SUCH_VALUE; } } // // At this point, Status is one of the following: // // CR_SUCCESS : we found the value and our buffer was // sufficiently-sized to hold it, // CR_BUFFER_SMALL : we found the value and our buffer wasn't // large enough to hold it, or // CR_NO_SUCH_VALUE : we didn't find the value. // // If we found a value (whether or not our buffer was large enough // to hold it), we're done, except for cases where the caller // has asked us to append the per-hw-id string(s) with the // per-devnode string(s). // if(Status == CR_NO_SUCH_VALUE) { // // No devnode-specific property, so we use the same buffer and // length for retrieval of per-hw-id property... // PerHwIdBuffer = Buffer; PerHwIdBufferLen = *pulLength; } else { // // Figure out if we need to worry about appending results // together into a multi-sz list... // if((ulFlags & CM_CUSTOMDEVPROP_MERGE_MULTISZ) && ((*pulRegDataType == REG_MULTI_SZ) || (*pulRegDataType == REG_SZ))) { MergeMultiSzResult = TRUE; // // Ensure that the size of our string(s) buffer is at least // one Unicode character. If we have a buffer of one // character, ensure that character is a null. // if(RequiredSize < sizeof(WCHAR)) { RequiredSize = sizeof(WCHAR); if(RequiredSize > *pulLength) { Status = CR_BUFFER_SMALL; } else { ASSERT(Status == CR_SUCCESS); *(PWSTR)Buffer = L'\0'; } } } if(!MergeMultiSzResult) { // // We're outta here! // if(Status == CR_SUCCESS) { // // We have data to transfer. // *pulTransferLen = RequiredSize; } goto Clean0; } else { // // We're supposed to merge our per-devnode string(s) with // any per-hw-id string(s) we find. Make sure our buffer // and length reflect a properly-formatted multi-sz list, // then setup our per-hw-id buffer info so that we'll // append to this list later on... // if(Status == CR_BUFFER_SMALL) { // // We won't even try to retrieve any actual data from // a per-hw-id key (all we'll get is the additional // size requirement). // PerHwIdBuffer = NULL; PerHwIdBufferLen = 0; if(*pulRegDataType == REG_SZ) { // // The data we retrieved from the devnode's "Device // Parameters" subkey was a REG_SZ. Add one // character width to the required length to // reflect the size of the string after conversion // to multi-sz (unless the size is 1 character, // indicating an empty string, which is also the // size of an empty multi-sz list). // if(RequiredSize > sizeof(WCHAR)) { RequiredSize += sizeof(WCHAR); } *pulRegDataType = REG_MULTI_SZ; } } else { // // We actually retrieved a REG_SZ or REG_MULTI_SZ value // into our caller-supplied buffer. Ensure that the // string(s) contained therein is(are) in proper // multi-sz format, and that the size is correct. // if(*pulRegDataType == REG_SZ) { RegDataSize = lstrlen((LPWSTR)Buffer) + 1; if((RegDataSize * sizeof(WCHAR)) > RequiredSize) { // // The string we retrieved is longer than the // buffer--this indicates the string wasn't // properly null-terminated. Discard this // string. // Status = CR_NO_SUCH_VALUE; RequiredSize = 0; PerHwIdBuffer = Buffer; PerHwIdBufferLen = *pulLength; } else { // // The string was large enough to fit in the // buffer. Add another null character to // turn this into a multi-sz (if there's room). // (Again, we don't need to do increase the // length if this is an empty string.) // if(RegDataSize == 1) { RequiredSize = sizeof(WCHAR); PerHwIdBuffer = Buffer; PerHwIdBufferLen = *pulLength; // // Assuming no per-hw-id data is found // later, this is the size of the data // we'll be handing back to the caller. // *pulTransferLen = RequiredSize; } else { RequiredSize = (RegDataSize + 1) * sizeof(WCHAR); if(RequiredSize > *pulLength) { // // Oops--while the string fits nicely into // the caller-supplied buffer, adding an // extra null char pushes it over the limit. // Turn this into a CR_BUFFER_SMALL case. // Status = CR_BUFFER_SMALL; PerHwIdBuffer = NULL; PerHwIdBufferLen = 0; } else { // // We've got room to add the extra null // character. Do so, and setup our // per-hw-id buffer to start at the end of // our existing (single string) list... // PerHwIdBuffer = (LPBYTE)((LPWSTR)Buffer + RegDataSize); PerHwIdBufferLen = *pulLength - (RegDataSize * sizeof(WCHAR)); *((LPWSTR)PerHwIdBuffer) = L'\0'; // // Assuming no per-hw-id data is found // later, this is the size of the data // we'll be handing back to the caller. // *pulTransferLen = RequiredSize; } } *pulRegDataType = REG_MULTI_SZ; } } else { // // We retrieved a multi-sz list. Step through it // to find the end of the list. // RegDataSize = 0; for(pCurId = (LPWSTR)Buffer; *pCurId; pCurId = (LPWSTR)(Buffer + RegDataSize)) { RegDataSize += (lstrlen(pCurId) + 1) * sizeof(WCHAR); if(RegDataSize < RequiredSize) { // // This string fits in the buffer, and // there's still space left over (i.e., for // at least a terminating null). Move on // to the next string in the list. // continue; } else if(RegDataSize > RequiredSize) { // // This string extends past the end of the // buffer, indicating that it wasn't // properly null-terminated. This could've // caused an exception, in which case we'd // have discarded any contents of this // value. For consistency, we'll discard // the contents anyway. (Note: a multi-sz // list that simply ommitted the final // terminating NULL will not fall into this // category--we deal with that correctly // and "fix it up".) // Status = CR_NO_SUCH_VALUE; RequiredSize = 0; PerHwIdBuffer = Buffer; PerHwIdBufferLen = *pulLength; break; } else { // // This string exactly fits into the // remaining buffer space, indicating that // the multi-sz list wasn't properly // double-null terminated. We'll go ahead // and do that now... // RequiredSize = RegDataSize + sizeof(WCHAR); if(RequiredSize > *pulLength) { // // Oops--while the string fits nicely // into the caller-supplied buffer, // adding an extra null char pushes it // over the limit. Turn this into a // CR_BUFFER_SMALL case. // Status = CR_BUFFER_SMALL; PerHwIdBuffer = NULL; PerHwIdBufferLen = 0; } else { // // We've got room to add the extra null // character. Do so, and setup our // per-hw-id buffer to start at the end // of our existing list... // PerHwIdBuffer = Buffer + RegDataSize; PerHwIdBufferLen = *pulLength - RegDataSize; *((LPWSTR)PerHwIdBuffer) = L'\0'; // // Assuming no per-hw-id data is found // later, this is the size of the data // we'll be handing back to the caller. // *pulTransferLen = RequiredSize; } // // We've reached the end of the list, so we // can break out of the loop. // break; } } // // We've now processed all (valid) strings in the // multi-sz list we retrieved. If there was a // problem (either unterminated string or // unterminated list), we fixed that up (and // adjusted RequiredSize accordingly). However, // if the list was valid, we need to compute // RequiredSize (e.g., the buffer might've been // larger than the multi-sz list). // // We can recognize a properly-formatted multi-sz // list, because that's the only time we'd have // exited the loop with pCurId pointing to a null // character... // if(!*pCurId) { ASSERT(RequiredSize >= (RegDataSize + sizeof(WCHAR))); RequiredSize = RegDataSize + sizeof(WCHAR); PerHwIdBuffer = Buffer + RegDataSize; PerHwIdBufferLen = *pulLength - RegDataSize; // // Assuming no per-hw-id data is found later, // this is the size of the data we'll be // handing back to the caller. // *pulTransferLen = RequiredSize; } } } } } } else { // // We couldn't open the devnode's "Device Parameters" subkey. // Ensure hDevParamsKey is still NULL so we won't erroneously try // to close it. // hDevParamsKey = NULL; // // Setup our pointer for retrieval of per-hw-id value... // PerHwIdBuffer = Buffer; PerHwIdBufferLen = *pulLength; // // Setup our default return values in case no per-hw-id data is // found... // Status = CR_NO_SUCH_VALUE; RequiredSize = 0; } // // From this point on use PerHwIdBuffer/PerHwIdBufferLen instead of // caller-supplied Buffer/pulLength, since we may be appending results // to those retrieved from the devnode's "Device Parameters" subkey... // // // If we get to here, then we need to go look for the value under // the appropriate per-hw-id registry key. First, figure out whether // the per-hw-id information has changed since we last cached the // most appropriate key. // RegDataSize = sizeof(LastUpdateTime); if((ERROR_SUCCESS != RegQueryValueEx(ghPerHwIdKey, pszRegValueLastUpdateTime, NULL, &RegDataType, (PBYTE)&LastUpdateTime, &RegDataSize)) || (RegDataType != REG_BINARY) || (RegDataSize != sizeof(FILETIME))) { // // We can't ascertain when (or even if) the per-hw-id database was // last populated. At this point, we bail with whatever status we // had after our attempt at retrieving the per-devnode property. // goto Clean0; } // // (RegDataSize is already set appropriately, no need to initialize it // again) // if(ERROR_SUCCESS == RegQueryValueEx(hDevKey, pszRegValueCustomPropertyCacheDate, NULL, &RegDataType, (PBYTE)&CacheDate, &RegDataSize)) { // // Just to be extra paranoid... // if((RegDataType != REG_BINARY) || (RegDataSize != sizeof(FILETIME))) { ZeroMemory(&CacheDate, sizeof(CacheDate)); } } else { ZeroMemory(&CacheDate, sizeof(CacheDate)); } if(CompareFileTime(&CacheDate, &LastUpdateTime) == 0) { // // The Per-Hw-Id database hasn't been updated since we cached away // the most-appropriate hardware id subkey. We can now use this // subkey to see if there's a per-hw-id value entry contained // therein for the requested property. // RegDataSize = sizeof(PerHwIdSubkeyName); if(ERROR_SUCCESS != RegQueryValueEx(hDevKey, pszRegValueCustomPropertyHwIdKey, NULL, &RegDataType, (PBYTE)PerHwIdSubkeyName, &RegDataSize)) { // // The value entry wasn't present, indicating there is no // applicable per-hw-id key. // goto Clean0; } else if(RegDataType != REG_SZ) { // // The data isn't a REG_SZ, like we expected. This should never // happen, but if it does, go ahead and re-assess the key we // should be using. // *PerHwIdSubkeyName = L'\0'; } else { // // We have a per-hw-id subkey to use. Go ahead and attempt to // open it up here. If we find someone has tampered with the // database and deleted this subkey, then we can at least go // re-evaluate below to see if we can find a new key that's // applicable for this devnode. // if(ERROR_SUCCESS != RegOpenKeyEx(ghPerHwIdKey, PerHwIdSubkeyName, 0, KEY_READ, &hPerHwIdSubKey)) { hPerHwIdSubKey = NULL; *PerHwIdSubkeyName = L'\0'; } } } else { // // Per-Hw-Id database has been updated since we last cached away // our custom property default key. (Note: The only time CacheDate // could be _newer than_ LastUpdateTime would be when a previous // update was (re-)applied to the per-hw-id database. In this case, // we'd want to re-assess the key we're using, since we always want // to be exactly in-sync with the current state of the database. // *PerHwIdSubkeyName = L'\0'; } if(!(*PerHwIdSubkeyName)) { // // We need to look for a (new) per-hw-id key from which to retrieve // properties applicable for this device. // hPerHwIdSubKey = FindMostAppropriatePerHwIdSubkey(hDevKey, KEY_READ, PerHwIdSubkeyName, &RegDataSize ); if(hPerHwIdSubKey) { RegStatus = RegSetValueEx(hDevKey, pszRegValueCustomPropertyHwIdKey, 0, REG_SZ, (PBYTE)PerHwIdSubkeyName, RegDataSize * sizeof(WCHAR) // need size in bytes ); } else { RegStatus = RegDeleteKey(hDevKey, pszRegValueCustomPropertyHwIdKey ); } if(RegStatus == ERROR_SUCCESS) { // // We successfully updated the cached per-hw-id key name. Now // update the CustomPropertyCacheDate to reflect the date // associated with the per-hw-id database. // RegSetValueEx(hDevKey, pszRegValueCustomPropertyCacheDate, 0, REG_BINARY, (PBYTE)&LastUpdateTime, sizeof(LastUpdateTime) ); } if(!hPerHwIdSubKey) { // // We couldn't find an applicable per-hw-id key for this // devnode. // goto Clean0; } } // // If we get to here, we have a handle to the per-hw-id subkey from // which we can query the requested property. // RegDataSize = PerHwIdBufferLen; // remember buffer size prior to call RegStatus = RegQueryValueEx(hPerHwIdSubKey, CustomPropName, NULL, &RegDataType, PerHwIdBuffer, &PerHwIdBufferLen ); if(RegStatus == ERROR_SUCCESS) { // // Again, we need to distinguish between the case where we // succeeded because we supplied a zero-length buffer (we call it // CR_BUFFER_SMALL) and the "real" success case. // if(RegDataSize == 0) { if(PerHwIdBufferLen != 0) { Status = CR_BUFFER_SMALL; } else if(MergeMultiSzResult) { // // We already have the multi-sz results we retrieved from // the devnode's "Device Parameters" subkey ready to return // to the caller... // ASSERT(*pulRegDataType == REG_MULTI_SZ); ASSERT((Status == CR_SUCCESS) || (Status == CR_BUFFER_SMALL)); ASSERT(RequiredSize >= sizeof(WCHAR)); ASSERT((Status != CR_SUCCESS) || (*pulTransferLen >= sizeof(WCHAR))); goto Clean0; } } else { // // Our success was genuine. // Status = CR_SUCCESS; } // // It's possible that we're supposed to be merging results into a // multi-sz list, but didn't find a value under the devnode's // "Device Parameters" subkey. Now that we have found a value // under the per-hw-id subkey, we need to ensure the data returned // is in multi-sz format. // if(!MergeMultiSzResult && (RequiredSize == 0)) { if((ulFlags & CM_CUSTOMDEVPROP_MERGE_MULTISZ) && ((RegDataType == REG_MULTI_SZ) || (RegDataType == REG_SZ))) { MergeMultiSzResult = TRUE; *pulRegDataType = REG_MULTI_SZ; RequiredSize = sizeof(WCHAR); if(RequiredSize > *pulLength) { Status = CR_BUFFER_SMALL; } } } } else { if(RegStatus == ERROR_MORE_DATA) { Status = CR_BUFFER_SMALL; } else { // // If we were merging results into our multi-sz list, ensure // that our list-terminating null didn't get blown away. // if(MergeMultiSzResult) { if(RegDataSize != 0) { *((LPWSTR)PerHwIdBuffer) = L'\0'; } // // We already have the multi-sz results we retrieved from // the devnode's "Device Parameters" subkey ready to return // to the caller... // ASSERT(*pulRegDataType == REG_MULTI_SZ); ASSERT((Status == CR_SUCCESS) || (Status == CR_BUFFER_SMALL)); ASSERT(RequiredSize >= sizeof(WCHAR)); ASSERT((Status != CR_SUCCESS) || (*pulTransferLen >= sizeof(WCHAR))); } else { ASSERT(Status == CR_NO_SUCH_VALUE); ASSERT(*pulTransferLen == 0); } goto Clean0; } } if(!MergeMultiSzResult) { *pulRegDataType = RegDataType; RequiredSize = PerHwIdBufferLen; if(Status == CR_SUCCESS) { // // We have data to transfer. // *pulTransferLen = RequiredSize; } } else { ASSERT(*pulRegDataType == REG_MULTI_SZ); ASSERT((Status == CR_SUCCESS) || (Status == CR_BUFFER_SMALL)); ASSERT(RequiredSize >= sizeof(WCHAR)); // // Unless the buffer size we retrieved is greater than one Unicode // character, it isn't going to affect the resultant size of our // multi-sz list. // if(PerHwIdBufferLen <= sizeof(WCHAR)) { ASSERT((Status != CR_BUFFER_SMALL) || (*pulTransferLen == 0)); goto Clean0; } if(Status == CR_BUFFER_SMALL) { // // We might've previously believed that we could return data to // the caller (e.g., because the data retrieved from the // devnode's "Device Parameters" subkey fit into our buffer. // Now that we see the data isn't going to fit, we need to // ensure that *pulTransferLen is zero to indicate no data is // being returned. // *pulTransferLen = 0; if(RegDataType == REG_MULTI_SZ) { // // Just want the lengths of the string(s) plus // their terminating nulls, excluding list- // terminating null char. // RequiredSize += (PerHwIdBufferLen - sizeof(WCHAR)); } else if(RegDataType == REG_SZ) { // // We can just add the size of this string into our // total requirement (unless it's an empty string, // in which case we don't need to do anything at // all). // RequiredSize += PerHwIdBufferLen; } else { // // per-hw-id data wasn't a REG_SZ or REG_MULTI_SZ, so // ignore it. // goto Clean0; } } else { // // We succeeded in retrieving more data into our multi-sz list. // If the data we retrieved is multi-sz, then we don't have any // additional work to do. However, if we retrieved a simple // REG_SZ, then we need to find the end of the string, and add // a second list-terminating null. // if(RegDataType == REG_MULTI_SZ) { RequiredSize += (PerHwIdBufferLen - sizeof(WCHAR)); } else if(RegDataType == REG_SZ) { RegDataSize = lstrlen((LPWSTR)PerHwIdBuffer) + 1; if((RegDataSize == 1) || ((RegDataSize * sizeof(WCHAR)) > PerHwIdBufferLen)) { // // The string we retrieved is either (a) empty or // (b) longer than the buffer (the latter indicating // that the string wasn't properly null-terminated). // In either case, we don't want to append anything to // our existing result, but we do need to ensure our // list-terminating null character is still there... // *((LPWSTR)PerHwIdBuffer) = L'\0'; } else { // // Compute total size requirement.. // RequiredSize += (RegDataSize * sizeof(WCHAR)); if(RequiredSize > *pulLength) { // // Adding the list-terminating null character // pushed us over the size of the caller- // supplied buffer. :-( // Status = CR_BUFFER_SMALL; *pulTransferLen = 0; goto Clean0; } else { // // Add list-terminating null character... // *((LPWSTR)PerHwIdBuffer + RegDataSize) = L'\0'; } } } else { // // per-hw-id data wasn't a REG_SZ or a REG_MULTI_SZ, so // ignore it. (Make sure, though, that we still have our // final terminating null character.) // *((LPWSTR)PerHwIdBuffer) = L'\0'; } *pulTransferLen = RequiredSize; } } Clean0: if (ARGUMENT_PRESENT(pulLength)) { *pulLength = RequiredSize; } } except(EXCEPTION_EXECUTE_HANDLER) { Status = CR_FAILURE; // // force compiler to respect statement ordering w.r.t. assignments // for these variables... // hDevKey = hDevKey; hDevParamsKey = hDevParamsKey; hPerHwIdSubKey = hPerHwIdSubKey; } if(hDevKey != NULL) { RegCloseKey(hDevKey); } if(hDevParamsKey != NULL) { RegCloseKey(hDevParamsKey); } if(hPerHwIdSubKey != NULL) { RegCloseKey(hPerHwIdSubKey); } return Status; } // PNP_GetCustomDevProp HKEY FindMostAppropriatePerHwIdSubkey( IN HKEY hDevKey, IN REGSAM samDesired, OUT LPWSTR PerHwIdSubkeyName, OUT LPDWORD PerHwIdSubkeyLen ) /*++ Routine Description: This routine finds the subkey in the per-hw-id database that is most appropriate for the device whose instance key was passed as input. This determination is made by taking each of the device's hardware and compatible ids, in turn, and forming a subkey name by replacing backslashes (\) with hashes (#). An attempt is made to open that subkey under the per-hw-id key, and the first such id to succeed, if any, is the most appropriate (i.e., most-specific) database entry. Note: we must consider both hardware and compatible ids, because some buses (such as PCI) may shift hardware ids down into the compatible id list under certain circumstances (e.g., PCI\VENxxxxDEVyyyy gets moved into the compatible list in the presence of subsys info). Arguments: hDevKey Supplies a handle to the device instance key for whom the most-appropriate per-hw-id subkey is to be ascertained. samDesired Supplies an access mask indicating the desired access rights to the per-hw-id key being returned. PerHwIdSubkeyName Supplies a buffer (that must be at least MAX_DEVNODE_ID_LEN characters in length) that, upon success, receives the most-appropriate per-hw-id subkey name. PerHwIdSubkeyLen Supplies the address of a variable that, upon successful return, receives the length of the subkey name (in characters), including terminating NULL, stored into the PerHwIdSubkeyName buffer. Return Value: If the function succeeds, the return value is a handle to the most- appropriate per-hw-id subkey. If the function fails, the return value is NULL. --*/ { DWORD i; DWORD RegDataType; PWCHAR IdList; HKEY hPerHwIdSubkey; PWSTR pCurId, pSrcChar, pDestChar; DWORD CurIdLen; DWORD idSize; WCHAR ids[REGSTR_VAL_MAX_HCID_LEN]; // // Note: we don't need to use structured exception handling in this // routine, since if we crash here (e.g., due to retrieval of a bogus // hardware or compatible id list), we won't leak any resource. Thus, the // caller's try/except is sufficient. // // // First process the hardware id list, and if no appropriate match // found there, then examine the compatible id list. // for(i = 0; i < 2; i++) { idSize = sizeof(ids); if((ERROR_SUCCESS != RegQueryValueEx(hDevKey, (i ? pszRegValueCompatibleIDs : pszRegValueHardwareIDs), NULL, &RegDataType, (PBYTE)ids, &idSize)) || (RegDataType != REG_MULTI_SZ)) { // // Missing or invalid id list--bail now. // return NULL; } IdList = ids; // // Now iterate through each id in our list, trying to open each one // in turn under the per-hw-id database key. // for(pCurId = IdList; *pCurId; pCurId += CurIdLen) { CurIdLen = lstrlen(pCurId) + 1; if(CurIdLen > MAX_DEVNODE_ID_LEN) { // // Bogus id in the list--skip it. // continue; } // // Transfer id into our subkey name buffer, converting path // separator characters ('\') to hashes ('#'). // pSrcChar = pCurId; pDestChar = PerHwIdSubkeyName; do { *pDestChar = (*pSrcChar != L'\\') ? *pSrcChar : L'#'; pDestChar++; } while(*(pSrcChar++)); if(ERROR_SUCCESS == RegOpenKeyEx(ghPerHwIdKey, PerHwIdSubkeyName, 0, samDesired, &hPerHwIdSubkey)) { // // We've found our key! // *PerHwIdSubkeyLen = CurIdLen; return hPerHwIdSubkey; } } } // // If we get to here, we didn't find an appropriate per-hw-id subkey to // return to the caller. // return NULL; }