/*++ Copyright (c) Microsoft Corporation. All rights reserved. Module Name: revent.c Abstract: This module contains the server-side event notification and device installation routines. Author: Paula Tomlinson (paulat) 6-28-1995 Environment: User-mode only. Revision History: 28-June-1995 paulat Creation and initial implementation. --*/ // // includes // #include "precomp.h" #pragma hdrstop #include "umpnpi.h" #include "umpnpdat.h" #include "pnpipc.h" #include "pnpmsg.h" #include #pragma warning(push) #pragma warning(disable:4201) // warning C4201: nonstandard extension used : nameless struct/union #include #pragma warning(pop) #include #include #pragma warning(push) #pragma warning(disable:4201) // warning C4201: nonstandard extension used : nameless struct/union #pragma warning(disable:4214) // warning C4214: nonstandard extension used : bit field types other than int #include #pragma warning(pop) #include #include #include #pragma warning(push) #pragma warning(disable:4201) // warning C4201: nonstandard extension used : nameless struct/union #include #pragma warning(pop) #include #include #include #include #include #include #include // // Maximum number of times (per pass) we will reenumerate the device tree during // GUI setup in an attempt to find and install new devices. // #define MAX_REENUMERATION_COUNT 128 // // Define and initialize a global variable, GUID_NULL // (from coguid.h) // DEFINE_GUID(GUID_NULL, 0L, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); // // Private interface device class that is used to register for all devnode change // notifications. This is no longer supported, but we want to fail anyone who registers // this GUID. // DEFINE_GUID(GUID_DEVNODE_CHANGE, 0xfa1fb208L, 0xf892, 0x11d0, 0x8a, 0x2e, 0x00, 0x00, 0xf8, 0x75, 0x3f, 0x55); // // Private interface device class that is assigned to entries registered for // device interface change notifications, using the // DEVICE_NOTIFY_ALL_INTERFACE_CLASSES flag. For internal use only. // DEFINE_GUID(GUID_DEVINTERFACE_INCLUDE_ALL_INTERFACE_CLASSES, 0x2121db68, 0x0993, 0x4a29, 0xb8, 0xe0, 0x1e, 0x51, 0x9c, 0x43, 0x72, 0xe6); // // SessionId 0 is the main session, and is always created during system startup // and remains until system shutdown, whether or not terminal services is // running. This session always hosts services.exe and all services, so it is // the only session that our ConsoleCtrlHandler can receive events for. // #define MAIN_SESSION ((ULONG) 0) #define INVALID_SESSION ((ULONG)-1) // // The active console session is the session currently connected to the physical // Console. We store this value in a global variable, whose access is // controlled by an event. The routine GetActiveConsoleSessionId() is used to // retrieve the value when it is safe to do so. // // Note that SessionId 0 is the initial console session, and that the // SessionNotificationHandler is responsible for maintaining state. // ULONG gActiveConsoleSessionId = MAIN_SESSION; // system always starts with session 0 HANDLE ghActiveConsoleSessionEvent = NULL; // nonsignaled while session change is in progress // // We always use DeviceEventWorker and BroadcastSystemMessage to deliver // notification to windows in SessionId 0. For all other sessions, we use // WinStationSendWindowMessage and WinStationBroadcastSystemMessage. // These are the timeout period (in seconds) for messages sent and broadcast to // sessions other than SessionId 0. These times should be the same as those // implemented by their SessionId 0 counterparts. // #define DEFAULT_SEND_TIME_OUT 30 // same as DeviceEventWorker #define DEFAULT_BROADCAST_TIME_OUT 5 // same as BroadcastSystemMessage // // Notification list structure. // typedef struct _PNP_NOTIFY_LIST { PVOID Next; PVOID Previous; LOCKINFO Lock; } PNP_NOTIFY_LIST, *PPNP_NOTIFY_LIST; // // Notification entry structure. // typedef struct _PNP_NOTIFY_ENTRY { PVOID Next; PVOID Previous; BOOL Unregistered; ULONG Signature; HANDLE Handle; DWORD Flags; ULONG SessionId; ULONG Freed; ULONG64 ClientCtxPtr; LPWSTR ClientName; union { struct { GUID ClassGuid; } Class; struct { HANDLE FileHandle; WCHAR DeviceId[MAX_DEVICE_ID_LEN]; } Target; struct { DWORD Reserved; } Devnode; struct { DWORD scmControls; } Service; } u; } PNP_NOTIFY_ENTRY, *PPNP_NOTIFY_ENTRY; // // Deferred operation list structure. // typedef struct _PNP_DEFERRED_LIST { PVOID Next; handle_t hBinding; PPNP_NOTIFY_ENTRY Entry; } PNP_DEFERRED_LIST, *PPNP_DEFERRED_LIST; // // Signatures describing which notification list an entry currently belongs to. // #define CLASS_ENTRY_SIGNATURE (0x07625100) #define TARGET_ENTRY_SIGNATURE (0x17625100) #define SERVICE_ENTRY_SIGNATURE (0x37625100) #define LIST_ENTRY_SIGNATURE_MASK (0xFFFFFF00) #define LIST_ENTRY_INDEX_MASK (~LIST_ENTRY_SIGNATURE_MASK) #define MarkEntryWithList(ent,value) { ent->Signature &= LIST_ENTRY_SIGNATURE_MASK;\ ent->Signature |= value; } // // Device event notification lists. // #define TARGET_HASH_BUCKETS 13 #define CLASS_GUID_HASH_BUCKETS 13 #define SERVICE_NUM_CONTROLS 3 #define HashClassGuid(_Guid) \ ( ( ((PULONG)_Guid)[0] + ((PULONG)_Guid)[1] + ((PULONG)_Guid)[2] \ + ((PULONG)_Guid)[3]) % CLASS_GUID_HASH_BUCKETS) PNP_NOTIFY_LIST TargetList[TARGET_HASH_BUCKETS]; PNP_NOTIFY_LIST ClassList[CLASS_GUID_HASH_BUCKETS]; PNP_NOTIFY_LIST ServiceList[SERVICE_NUM_CONTROLS]; PPNP_DEFERRED_LIST UnregisterList; PPNP_DEFERRED_LIST RegisterList; PPNP_DEFERRED_LIST RundownList; CRITICAL_SECTION RegistrationCS; // // These are indices into the global ServiceList array of lists containing // services registered for the corresponding service control events. // enum cBitIndex { CINDEX_HWPROFILE = 0, CINDEX_POWEREVENT = 1 }; // // These are a bit mask for the above lists. // (the two enums should match! One is 0,1,2,...n. The other 2^n.) // enum cBits { CBIT_HWPROFILE = 1, CBIT_POWEREVENT = 2 }; // // Properties describing how a notification entry was freed. // // (the entry has been removed from the notification list) #define DEFER_NOTIFY_FREE 0x80000000 // (used for debugging only) #define PNP_UNREG_FREE 0x00000100 #define PNP_UNREG_CLASS 0x00000200 #define PNP_UNREG_TARGET 0x00000400 #define PNP_UNREG_DEFER 0x00000800 #define PNP_UNREG_WIN 0x00001000 #define PNP_UNREG_SERVICE 0x00002000 #define PNP_UNREG_CANCEL 0x00004000 #define PNP_UNREG_RUNDOWN 0x00008000 // // List of devices to be installed. // typedef struct _PNP_INSTALL_LIST { PVOID Next; LOCKINFO Lock; } PNP_INSTALL_LIST, *PPNP_INSTALL_LIST; // // Device install list entry structure. // typedef struct _PNP_INSTALL_ENTRY { PVOID Next; DWORD Flags; WCHAR szDeviceId[MAX_DEVICE_ID_LEN]; } PNP_INSTALL_ENTRY, *PPNP_INSTALL_ENTRY; // // Install event list. // PNP_INSTALL_LIST InstallList; // // Flags for PNP_INSTALL_ENTRY nodes // #define PIE_SERVER_SIDE_INSTALL_ATTEMPTED 0x00000001 #define PIE_DEVICE_INSTALL_REQUIRED_REBOOT 0x00000002 // // Device install client information list structure. // typedef struct _INSTALL_CLIENT_LIST { PVOID Next; LOCKINFO Lock; } INSTALL_CLIENT_LIST, *PINSTALL_CLIENT_LIST; // // Device install client information list entry structure. // typedef struct _INSTALL_CLIENT_ENTRY { PVOID Next; ULONG RefCount; ULONG ulSessionId; HANDLE hEvent; HANDLE hPipe; HANDLE hProcess; HANDLE hDisconnectEvent; ULONG ulInstallFlags; WCHAR LastDeviceId[MAX_DEVICE_ID_LEN]; } INSTALL_CLIENT_ENTRY, *PINSTALL_CLIENT_ENTRY; // // Device install client list. // INSTALL_CLIENT_LIST InstallClientList; // // Global BOOL that tracks if a server side device install reboot is needed. // BOOL gServerSideDeviceInstallRebootNeeded = FALSE; // // private prototypes // DWORD ThreadProc_DeviceEvent( LPDWORD lpParam ); DWORD ThreadProc_DeviceInstall( LPDWORD lpParam ); DWORD ThreadProc_GuiSetupDeviceInstall( LPDWORD lpThreadParam ); DWORD ThreadProc_FactoryPreinstallDeviceInstall( LPDWORD lpThreadParam ); DWORD ThreadProc_ReenumerateDeviceTree( LPVOID lpThreadParam ); BOOL InstallDevice( IN LPWSTR pszDeviceId, IN OUT PULONG SessionId, IN ULONG Flags ); DWORD InstallDeviceServerSide( IN LPWSTR pszDeviceId, IN OUT PBOOL RebootRequired, IN OUT PBOOL DeviceHasProblem, IN OUT PULONG SessionId, IN ULONG Flags ); BOOL CreateDeviceInstallClient( IN ULONG SessionId, OUT PINSTALL_CLIENT_ENTRY *DeviceInstallClient ); BOOL ConnectDeviceInstallClient( IN ULONG SessionId, OUT PINSTALL_CLIENT_ENTRY *DeviceInstallClient ); BOOL DisconnectDeviceInstallClient( IN PINSTALL_CLIENT_ENTRY DeviceInstallClient ); PINSTALL_CLIENT_ENTRY LocateDeviceInstallClient( IN ULONG SessionId ); VOID ReferenceDeviceInstallClient( IN PINSTALL_CLIENT_ENTRY DeviceInstallClient ); VOID DereferenceDeviceInstallClient( IN PINSTALL_CLIENT_ENTRY DeviceInstallClient ); BOOL DoDeviceInstallClient( IN LPWSTR DeviceId, IN PULONG SessionId, IN ULONG Flags, OUT PINSTALL_CLIENT_ENTRY *DeviceInstallClient ); BOOL InitNotification( VOID ); VOID TermNotification( VOID ); ULONG ProcessDeviceEvent( IN PPLUGPLAY_EVENT_BLOCK EventBlock, IN DWORD EventBufferSize, OUT PPNP_VETO_TYPE VetoType, OUT LPWSTR VetoName, IN OUT PULONG VetoNameLength ); ULONG NotifyInterfaceClassChange( IN DWORD ServiceControl, IN DWORD EventId, IN DWORD Flags, IN PDEV_BROADCAST_DEVICEINTERFACE ClassData ); ULONG NotifyTargetDeviceChange( IN DWORD ServiceControl, IN DWORD EventId, IN DWORD Flags, IN PDEV_BROADCAST_HANDLE HandleData, IN LPWSTR DeviceId, OUT PPNP_VETO_TYPE VetoType OPTIONAL, OUT LPWSTR VetoName OPTIONAL, IN OUT PULONG VetoNameLength OPTIONAL ); ULONG NotifyHardwareProfileChange( IN DWORD ServiceControl, IN DWORD EventId, IN DWORD Flags, OUT PPNP_VETO_TYPE VetoType OPTIONAL, OUT LPWSTR VetoName OPTIONAL, IN OUT PULONG VetoNameLength OPTIONAL ); ULONG NotifyPower( IN DWORD ServiceControl, IN DWORD EventId, IN DWORD EventData, IN DWORD Flags, OUT PPNP_VETO_TYPE VetoType OPTIONAL, OUT LPWSTR VetoName OPTIONAL, IN OUT PULONG VetoNameLength OPTIONAL ); BOOL SendCancelNotification( IN PPNP_NOTIFY_ENTRY LastEntry, IN DWORD ServiceControl, IN DWORD EventId, IN ULONG Flags, IN PDEV_BROADCAST_HDR NotifyData OPTIONAL, IN LPWSTR DeviceId OPTIONAL ); VOID BroadcastCompatibleDeviceMsg( IN DWORD EventId, IN PDEV_BROADCAST_DEVICEINTERFACE ClassData, IN PDWORD CurrentMask ); VOID BroadcastVolumeNameChange( VOID ); DWORD GetAllVolumeMountPoints( VOID ); BOOL EventIdFromEventGuid( IN CONST GUID *EventGuid, OUT LPDWORD EventId, OUT LPDWORD Flags, OUT LPDWORD ServiceControl ); ULONG SendHotplugNotification( IN CONST GUID *EventGuid, IN PPNP_VETO_TYPE VetoType OPTIONAL, IN LPWSTR MultiSzList, IN OUT PULONG SessionId, IN ULONG Flags ); VOID LogErrorEvent( DWORD dwEventID, DWORD dwError, WORD nStrings, ... ); VOID LogWarningEvent( DWORD dwEventID, WORD nStrings, ... ); BOOL LockNotifyList( IN LOCKINFO *Lock ); VOID UnlockNotifyList( IN LOCKINFO *Lock ); PPNP_NOTIFY_LIST GetNotifyListForEntry( IN PPNP_NOTIFY_ENTRY entry ); BOOL DeleteNotifyEntry( IN PPNP_NOTIFY_ENTRY Entry, IN BOOLEAN RpcNotified ); VOID AddNotifyEntry( IN PPNP_NOTIFY_LIST NotifyList, IN PPNP_NOTIFY_ENTRY NewEntry ); ULONG HashString( IN LPWSTR String, IN ULONG Buckets ); DWORD MapQueryEventToCancelEvent( IN DWORD QueryEventId ); VOID FixUpDeviceId( IN OUT LPWSTR DeviceId ); ULONG MapSCMControlsToControlBit( IN ULONG scmControls ); DWORD GetFirstPass( IN BOOL bQuery ); DWORD GetNextPass( IN DWORD curPass, IN BOOL bQuery ); BOOL NotifyEntryThisPass( IN PPNP_NOTIFY_ENTRY Entry, IN DWORD Pass ); DWORD GetPassFromEntry( IN PPNP_NOTIFY_ENTRY Entry ); BOOL GetClientName( IN PPNP_NOTIFY_ENTRY entry, OUT LPWSTR lpszClientName, IN OUT PULONG pulClientNameLength ); BOOL GetWindowsExeFileName( IN HWND hWnd, OUT LPWSTR lpszFileName, IN OUT PULONG pulFileNameLength ); PPNP_NOTIFY_ENTRY GetNextNotifyEntry( IN PPNP_NOTIFY_ENTRY Entry, IN DWORD Flags ); PPNP_NOTIFY_ENTRY GetFirstNotifyEntry( IN PPNP_NOTIFY_LIST List, IN DWORD Flags ); BOOL InitializeHydraInterface( VOID ); DWORD LoadDeviceInstaller( VOID ); VOID UnloadDeviceInstaller( VOID ); BOOL PromptUser( IN OUT PULONG SessionId, IN ULONG Flags ); VOID DoRunOnce( VOID ); BOOL GetSessionUserToken( IN ULONG ulSessionId, OUT LPHANDLE lphUserToken ); BOOL IsUserLoggedOnSession( IN ULONG ulSessionId ); BOOL IsSessionConnected( IN ULONG ulSessionId ); BOOL IsSessionLocked( IN ULONG ulSessionId ); BOOL IsConsoleSession( IN ULONG ulSessionId ); DWORD CreateUserSynchEvent( IN HANDLE hUserToken, IN LPCWSTR lpName, OUT HANDLE *phEvent ); BOOL CreateNoPendingInstallEvent( VOID ); DWORD CreateUserReadNamedPipe( IN HANDLE hUserToken, IN LPCWSTR lpName, IN ULONG ulSize, OUT HANDLE *phPipe ); ULONG CheckEjectPermissions( IN LPWSTR DeviceId, OUT PPNP_VETO_TYPE VetoType OPTIONAL, OUT LPWSTR VetoName OPTIONAL, IN OUT PULONG VetoNameLength OPTIONAL ); VOID LogSurpriseRemovalEvent( IN LPWSTR MultiSzList ); PWCHAR BuildFriendlyName( IN LPWSTR InstancePath ); CONFIGRET DevInstNeedsInstall( IN LPCWSTR DevInst, IN BOOL CheckReinstallConfigFlag, OUT BOOL *NeedsInstall ); PWSTR BuildBlockedDriverList( IN OUT LPGUID GuidList, IN ULONG GuidCount ); VOID SendInvalidIDNotifications( IN ULONG ulSessionId ); // // global data // extern HANDLE ghInst; // Module handle extern HKEY ghEnumKey; // Key to HKLM\CCC\System\Enum - DO NOT MODIFY extern HKEY ghServicesKey; // Key to HKLM\CCC\System\Services - DO NOT MODIFY extern HKEY ghClassKey; // Key to HKLM\CCC\System\Class - DO NOT MODIFY extern DWORD CurrentServiceState; // PlugPlay service state - DO NOT MODIFY extern PSVCS_GLOBAL_DATA PnPGlobalData; // SCM global data HANDLE ghInitMutex = NULL; HANDLE ghUserToken = NULL; LOCKINFO gTokenLock; BOOL gbMainSessionLocked = FALSE; ULONG gNotificationInProg = 0; // 0 -> No notification or unregister underway. DWORD gAllDrivesMask = 0; // bitmask of all physical volume mountpoints. BOOL gbSuppressUI = FALSE; // TRUE if PNP should never display UI (newdev, hotplug). BOOL gbOobeInProgress = FALSE;// TRUE if the OOBE is running during this boot. BOOL gbPreservePreInstall = FALSE; // TRUE if PNP should respect a device's pre-install settings. BOOL gbStatelessBoot = FALSE; // TRUE if this is a stateless (rebootless) boot. const WCHAR RegMemoryManagementKeyName[] = TEXT("System\\CurrentControlSet\\Control\\Session Manager\\Memory Management"); const WCHAR RegVerifyDriverLevelValueName[] = TEXT("VerifyDriverLevel"); // // Device Installer instance handle and necessary entrypoints. // This data is only referenced by the (non-GUI setup) device install thread // (ThreadProc_DeviceInstall). // typedef HDEVINFO (WINAPI *FP_CREATEDEVICEINFOLIST)(CONST GUID *, HWND); typedef BOOL (WINAPI *FP_OPENDEVICEINFO)(HDEVINFO, PCWSTR, HWND, DWORD, PSP_DEVINFO_DATA); typedef BOOL (WINAPI *FP_BUILDDRIVERINFOLIST)(HDEVINFO, PSP_DEVINFO_DATA, DWORD); typedef BOOL (WINAPI *FP_DESTROYDEVICEINFOLIST)(HDEVINFO); typedef BOOL (WINAPI *FP_CALLCLASSINSTALLER)(DI_FUNCTION, HDEVINFO, PSP_DEVINFO_DATA); typedef BOOL (WINAPI *FP_INSTALLCLASS)(HWND, PCWSTR, DWORD, HSPFILEQ); typedef BOOL (WINAPI *FP_GETSELECTEDDRIVER)(HDEVINFO, PSP_DEVINFO_DATA, PSP_DRVINFO_DATA); typedef BOOL (WINAPI *FP_GETDRIVERINFODETAIL)(HDEVINFO, PSP_DEVINFO_DATA, PSP_DRVINFO_DATA, PSP_DRVINFO_DETAIL_DATA, DWORD, PDWORD); typedef BOOL (WINAPI *FP_GETDEVICEINSTALLPARAMS)(HDEVINFO, PSP_DEVINFO_DATA, PSP_DEVINSTALL_PARAMS); typedef BOOL (WINAPI *FP_SETDEVICEINSTALLPARAMS)(HDEVINFO, PSP_DEVINFO_DATA, PSP_DEVINSTALL_PARAMS); typedef BOOL (WINAPI *FP_GETDRIVERINSTALLPARAMS)(HDEVINFO, PSP_DEVINFO_DATA, PSP_DRVINFO_DATA, PSP_DRVINSTALL_PARAMS); typedef BOOL (WINAPI *FP_SETCLASSINSTALLPARAMS)(HDEVINFO, PSP_DEVINFO_DATA, PSP_CLASSINSTALL_HEADER, DWORD); typedef BOOL (WINAPI *FP_GETCLASSINSTALLPARAMS)(HDEVINFO, PSP_DEVINFO_DATA, PSP_CLASSINSTALL_HEADER, DWORD, PDWORD); typedef HINF (WINAPI *FP_OPENINFFILE)(PCWSTR, PCWSTR, DWORD, PUINT); typedef VOID (WINAPI *FP_CLOSEINFFILE)(HINF); typedef BOOL (WINAPI *FP_FINDFIRSTLINE)(HINF, PCWSTR, PCWSTR, PINFCONTEXT); typedef BOOL (WINAPI *FP_FINDNEXTMATCHLINE)(PINFCONTEXT, PCWSTR, PINFCONTEXT); typedef BOOL (WINAPI *FP_GETSTRINGFIELD)(PINFCONTEXT, DWORD, PWSTR, DWORD, PDWORD); typedef VOID (*FP_SETGLOBALFLAGS)(DWORD); typedef DWORD (*FP_GETGLOBALFLAGS)(VOID); typedef PPSP_RUNONCE_NODE (*FP_ACCESSRUNONCENODELIST)(VOID); typedef VOID (*FP_DESTROYRUNONCENODELIST)(VOID); HINSTANCE ghDeviceInstallerLib = NULL; FP_CREATEDEVICEINFOLIST fpCreateDeviceInfoList; FP_OPENDEVICEINFO fpOpenDeviceInfo; FP_BUILDDRIVERINFOLIST fpBuildDriverInfoList; FP_DESTROYDEVICEINFOLIST fpDestroyDeviceInfoList; FP_CALLCLASSINSTALLER fpCallClassInstaller; FP_INSTALLCLASS fpInstallClass; FP_GETSELECTEDDRIVER fpGetSelectedDriver; FP_GETDRIVERINFODETAIL fpGetDriverInfoDetail; FP_GETDEVICEINSTALLPARAMS fpGetDeviceInstallParams; FP_SETDEVICEINSTALLPARAMS fpSetDeviceInstallParams; FP_GETDRIVERINSTALLPARAMS fpGetDriverInstallParams; FP_SETCLASSINSTALLPARAMS fpSetClassInstallParams; FP_GETCLASSINSTALLPARAMS fpGetClassInstallParams; FP_OPENINFFILE fpOpenInfFile; FP_CLOSEINFFILE fpCloseInfFile; FP_FINDFIRSTLINE fpFindFirstLine; FP_FINDNEXTMATCHLINE fpFindNextMatchLine; FP_GETSTRINGFIELD fpGetStringField; FP_SETGLOBALFLAGS fpSetGlobalFlags; FP_GETGLOBALFLAGS fpGetGlobalFlags; FP_ACCESSRUNONCENODELIST fpAccessRunOnceNodeList; FP_DESTROYRUNONCENODELIST fpDestroyRunOnceNodeList; // // typdef for comctl32's DestroyPropertySheetPage API, needed in cases where // class-/co-installers supply wizard pages (that need to be destroyed). // typedef BOOL (WINAPI *FP_DESTROYPROPERTYSHEETPAGE)(HPROPSHEETPAGE); // // typedefs for ANSI and Unicode variants of rundll32 proc entrypoint. // typedef void (WINAPI *RUNDLLPROCA)(HWND hwndStub, HINSTANCE hInstance, LPSTR pszCmdLine, int nCmdShow); typedef void (WINAPI *RUNDLLPROCW)(HWND hwndStub, HINSTANCE hInstance, LPWSTR pszCmdLine, int nCmdShow); // // typedefs for Terminal Services message dispatch routines, in winsta.dll. // typedef LONG (*FP_WINSTABROADCASTSYSTEMMESSAGE)( HANDLE hServer, BOOL sendToAllWinstations, ULONG sessionID, ULONG timeOut, DWORD dwFlags, DWORD *lpdwRecipients, ULONG uiMessage, WPARAM wParam, LPARAM lParam, LONG *pResponse ); typedef LONG (*FP_WINSTASENDWINDOWMESSAGE)( HANDLE hServer, ULONG sessionID, ULONG timeOut, ULONG hWnd, ULONG Msg, WPARAM wParam, LPARAM lParam, LONG *pResponse ); typedef BOOLEAN (WINAPI * FP_WINSTAQUERYINFORMATIONW)( HANDLE hServer, ULONG LogonId, WINSTATIONINFOCLASS WinStationInformationClass, PVOID pWinStationInformation, ULONG WinStationInformationLength, PULONG pReturnLength ); HINSTANCE ghWinStaLib = NULL; FP_WINSTASENDWINDOWMESSAGE fpWinStationSendWindowMessage = NULL; FP_WINSTABROADCASTSYSTEMMESSAGE fpWinStationBroadcastSystemMessage = NULL; FP_WINSTAQUERYINFORMATIONW fpWinStationQueryInformationW = NULL; // // typedefs for Terminal Services support routines, in wtsapi32.dll. // typedef BOOL (*FP_WTSQUERYSESSIONINFORMATION)( IN HANDLE hServer, IN DWORD SessionId, IN WTS_INFO_CLASS WTSInfoClass, OUT LPWSTR * ppBuffer, OUT DWORD * pBytesReturned ); typedef VOID (*FP_WTSFREEMEMORY)( IN PVOID pMemory ); HINSTANCE ghWtsApi32Lib = NULL; FP_WTSQUERYSESSIONINFORMATION fpWTSQuerySessionInformation = NULL; FP_WTSFREEMEMORY fpWTSFreeMemory = NULL; // // Service controller callback routines for authentication and notification to // services. // PSCMCALLBACK_ROUTINE pServiceControlCallback; PSCMAUTHENTICATION_CALLBACK pSCMAuthenticate; // // Device install events // #define NUM_INSTALL_EVENTS 2 #define LOGGED_ON_EVENT 0 #define NEEDS_INSTALL_EVENT 1 HANDLE InstallEvents[NUM_INSTALL_EVENTS] = {NULL, NULL}; HANDLE ghNoPendingInstalls = NULL; // // Veto definitions // #define UnknownVeto(t,n,l) { *(t) = PNP_VetoTypeUnknown; } #define WinBroadcastVeto(h,t,n,l) { *(t) = PNP_VetoWindowsApp;\ GetWindowsExeFileName(h,n,l); } #define WindowVeto(e,t,n,l) { *(t) = PNP_VetoWindowsApp;\ GetClientName(e,n,l); } #define ServiceVeto(e,t,n,l) { *(t) = PNP_VetoWindowsService;\ GetClientName(e,n,l); } // // Sentinel for event loop control // #define PASS_COMPLETE 0x7fffffff BOOL PnpConsoleCtrlHandler( DWORD dwCtrlType ) /*++ Routine Description: This routine handles control signals received by the process for the session the process is associated with. Arguments: dwCtrlType - Indicates the type of control signal received by the handler. This value is one of the following: CTRL_C_EVENT, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, CTRL_SHUTDOWN_EVENT Return Value: If the function handles the control signal, it should return TRUE. If it returns FALSE, the next handler function in the list of handlers for this process is used. --*/ { PINSTALL_CLIENT_ENTRY pDeviceInstallClient; switch (dwCtrlType) { case CTRL_LOGOFF_EVENT: // // The system sends the logoff event to the registered console ctrl // handlers for a console process when a user is logging off from the // session associated with that process. Since UMPNPMGR runs within the // context of the services.exe process, which always resides in session // 0, that is the only session for which this handler will receive // logoff events. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT, "UMPNPMGR: PnpConsoleCtrlHandler: CTRL_LOGOFF_EVENT: Session %d\n", MAIN_SESSION)); // // Close the handle to the user access token for the main session. // ASSERT(gTokenLock.LockHandles); LockPrivateResource(&gTokenLock); if (ghUserToken) { CloseHandle(ghUserToken); ghUserToken = NULL; } UnlockPrivateResource(&gTokenLock); // // If the main session was the active Console session, (or should be // treated as the active console session because Fast User Switching is // disabled) when the user logged off, reset the "logged on" event. // if (IsConsoleSession(MAIN_SESSION)) { if (InstallEvents[LOGGED_ON_EVENT]) { ResetEvent(InstallEvents[LOGGED_ON_EVENT]); KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_EVENT, "UMPNPMGR: PnpConsoleCtrlHandler: CTRL_LOGOFF_EVENT: ResetEvent LOGGED_ON_EVENT\n")); } } // // If we currently have a device install UI client on this session, // we should attempt to close it now, before logging off. // LockNotifyList(&InstallClientList.Lock); pDeviceInstallClient = LocateDeviceInstallClient(MAIN_SESSION); if (pDeviceInstallClient) { DereferenceDeviceInstallClient(pDeviceInstallClient); } UnlockNotifyList(&InstallClientList.Lock); break; default: // // No special processing for any other events. // break; } // // Returning FALSE passes this control to the next registered CtrlHandler in // the list of handlers for this process (services.exe), so that other // services will get a chance to look at this. // return FALSE; } // PnpConsoleCtrlHandler DWORD InitializePnPManager( LPDWORD lpParam ) /*++ Routine Description: This thread routine is created from srventry.c when services.exe attempts to start the plug and play service. The init routine in srventry.c does critical initialize then creates this thread to do pnp initialization so that it can return back to the service controller before pnp init completes. Arguments: lpParam - Not used. Return Value: Currently returns TRUE/FALSE. --*/ { DWORD dwStatus = TRUE; DWORD ThreadID = 0; HANDLE hThread = NULL, hEventThread = NULL; HKEY hKey = NULL; LONG status; BOOL bGuiModeSetup = FALSE, bFactoryPreInstall = FALSE; ULONG ulSize, ulValue; UNREFERENCED_PARAMETER(lpParam); KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT, "UMPNPMGR: InitializePnPManager\n")); // // Initialize events that will control when to install devices later. // InstallEvents[LOGGED_ON_EVENT] = CreateEvent(NULL, TRUE, FALSE, NULL); if (InstallEvents[LOGGED_ON_EVENT] == NULL) { LogErrorEvent(ERR_CREATING_LOGON_EVENT, GetLastError(), 0); } InstallEvents[NEEDS_INSTALL_EVENT] = CreateEvent(NULL, TRUE, FALSE, NULL); if (InstallEvents[NEEDS_INSTALL_EVENT] == NULL) { LogErrorEvent(ERR_CREATING_INSTALL_EVENT, GetLastError(), 0); } // // Create the pending install event. // if (!CreateNoPendingInstallEvent()) { LogErrorEvent(ERR_CREATING_PENDING_INSTALL_EVENT, GetLastError(), 0); } ASSERT(ghNoPendingInstalls != NULL); // // Initialize event to control access to the current session during session // change events. The event state is initially signalled since this service // initializes when only session 0 exists (prior to the initialization of // termsrv, or the creation of any other sessions). // ghActiveConsoleSessionEvent = CreateEvent(NULL, TRUE, TRUE, NULL); if (ghActiveConsoleSessionEvent == NULL) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_ERRORS, "UMPNPMGR: Failed to initialize ghActiveConsoleSessionEvent!!, error = %d\n", GetLastError())); } // // Setup a console control handler so that I can keep track of logoffs to // the main session (SessionId 0). This is still necessary because Terminal // Services may not always be available. (see PNP_ReportLogOn). // (I only get logoff notification via this handler so I still // rely on the kludge in userinit.exe to tell me about logons). // if (!SetConsoleCtrlHandler(PnpConsoleCtrlHandler, TRUE)) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT | DBGF_ERRORS, "UMPNPMGR: SetConsoleCtrlHandler failed, error = %d\n", GetLastError())); } // // Acquire a mutex now to make sure I get through this // initialization task before getting pinged by a logon // ghInitMutex = CreateMutex(NULL, TRUE, NULL); ASSERT(ghInitMutex != NULL); if (ghInitMutex == NULL) { return FALSE; } try { // // Check if we're running during one of the assorted flavors of setup. // status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\Setup"), 0, KEY_READ, &hKey); if (status == ERROR_SUCCESS) { // // Determine if factory pre-install is in progress. // ulValue = 0; ulSize = sizeof(ulValue); status = RegQueryValueEx(hKey, TEXT("FactoryPreInstallInProgress"), NULL, NULL, (LPBYTE)&ulValue, &ulSize); if ((status == ERROR_SUCCESS) && (ulValue == 1)) { bFactoryPreInstall = TRUE; gbSuppressUI = TRUE; KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_EVENT, "UMPNPMGR: Will suppress all UI in Factory Mode\n")); LogWarningEvent(WRN_FACTORY_UI_SUPPRESSED, 0, NULL); } if (!bFactoryPreInstall) { // // Determine if Gui Mode Setup is in progress (but not mini-setup). // ulValue = 0; ulSize = sizeof(ulValue); status = RegQueryValueEx(hKey, TEXT("SystemSetupInProgress"), NULL, NULL, (LPBYTE)&ulValue, &ulSize); if (status == ERROR_SUCCESS) { bGuiModeSetup = (ulValue == 1); } if (bGuiModeSetup) { // // Well, we're in GUI-mode setup, but we need to make sure // we're not in mini-setup, or factory pre-install. We // treat mini-setup like any other boot of the system, and // factory pre-install is a delayed version of a normal // boot. // ulValue = 0; ulSize = sizeof(ulValue); status = RegQueryValueEx(hKey, TEXT("MiniSetupInProgress"), NULL, NULL, (LPBYTE)&ulValue, &ulSize); if ((status == ERROR_SUCCESS) && (ulValue == 1)) { // // Well, we're in mini-setup, but we need to make sure // that he doesn't want us to do PnP re-enumeration. // ulValue = 0; ulSize = sizeof(ulValue); status = RegQueryValueEx(hKey, TEXT("MiniSetupDoPnP"), NULL, NULL, (LPBYTE)&ulValue, &ulSize); if ((status != ERROR_SUCCESS) || (ulValue == 0)) { // // Nope. Treat this like any other boot of the // system. // bGuiModeSetup = FALSE; } } } } // // Determine if this is an OOBE boot. // ulValue = 0; ulSize = sizeof(ulValue); status = RegQueryValueEx(hKey, TEXT("OobeInProgress"), NULL, NULL, (LPBYTE)&ulValue, &ulSize); if (status == ERROR_SUCCESS) { gbOobeInProgress = (ulValue == 1); } // // Close the SYSTEM\Setup key. // RegCloseKey(hKey); hKey = NULL; } else { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: Failure opening key SYSTEM\\Setup (%d)\n", status)); } // // Check if we should consider pre-installation of devices complete. // status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\WPA\\PnP"), 0, KEY_READ, &hKey); if (status == ERROR_SUCCESS) { // // Check for the "PreservePreInstall" flag to determine if we should // preserve settings on preinstalled devices (i.e. do not reinstall // finish-install devices). // ulValue = 0; ulSize = sizeof(ulValue); status = RegQueryValueEx(hKey, pszRegValuePreservePreInstall, NULL, NULL, (LPBYTE)&ulValue, &ulSize); if ((status == ERROR_SUCCESS) && (ulValue == 1)) { gbPreservePreInstall = TRUE; } // // Check for the "StatelessBoot" flag to determine if this is a // stateless boot (no-reboot) boot of the OS. // ulValue = 0; ulSize = sizeof(ulValue); status = RegQueryValueEx(hKey, TEXT("StatelessBoot"), NULL, NULL, (LPBYTE)&ulValue, &ulSize); if ((status == ERROR_SUCCESS) && (ulValue == 1)) { gbStatelessBoot = TRUE; } RegCloseKey(hKey); hKey = NULL; } // // If this is EmbeddedNT, check whether PNP should display UI. // Note that this is only checked once per system boot, when the // service is initialized. // if (IsEmbeddedNT()) { if (RegOpenKeyEx(ghServicesKey, pszRegKeyPlugPlayServiceParams, 0, KEY_READ, &hKey) == ERROR_SUCCESS) { ulValue = 0; ulSize = sizeof(ulValue); if ((RegQueryValueEx(hKey, TEXT("SuppressUI"), NULL, NULL, (LPBYTE)&ulValue, &ulSize) == ERROR_SUCCESS) && (ulValue == 1)) { gbSuppressUI = TRUE; KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_EVENT, "UMPNPMGR: Will suppress all UI on EmbeddedNT\n")); LogWarningEvent(WRN_EMBEDDEDNT_UI_SUPPRESSED, 0, NULL); } RegCloseKey(hKey); } } // // Initialize the interfaces to Hydra, if Hydra is running on this system. // if (IsTerminalServer()) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT, "UMPNPMGR: Initializing interfaces to Terminal Services.\n")); if (!InitializeHydraInterface()) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_WARNINGS | DBGF_EVENT, "UMPNPMGR: Failed to initialize interfaces to Terminal Services!\n")); } } // // Initialize the global drive letter mask // gAllDrivesMask = GetAllVolumeMountPoints(); // // Create a thread that monitors device events. // hEventThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc_DeviceEvent, NULL, 0, &ThreadID); // // Create the appropriate thread to handle the device installation. // The two cases are when gui mode setup is in progress and for // a normal user boot case. // if (bFactoryPreInstall) { // // FactoryPreInstallInProgress // hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc_FactoryPreinstallDeviceInstall, NULL, 0, &ThreadID); } else if (bGuiModeSetup) { // // SystemSetupInProgress, // including MiniSetupInProgress with MiniSetupDoPnP // hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc_GuiSetupDeviceInstall, NULL, 0, &ThreadID); } else { // // Standard system boot, or // SystemSetupInProgress with MiniSetupInProgress (but not MiniSetupDoPnP) // hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc_DeviceInstall, NULL, 0, &ThreadID); } } except(EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_EVENT, "UMPNPMGR: Exception in InitializePnPManager!\n")); ASSERT(0); dwStatus = FALSE; // // Reference the following variables so the compiler will respect // statement ordering w.r.t. their assignment. // hThread = hThread; hEventThread = hEventThread; } // // signal the init mutex so that logon init activity can procede // ReleaseMutex(ghInitMutex); if (hThread != NULL) { CloseHandle(hThread); } if (hEventThread != NULL) { CloseHandle(hEventThread); } return dwStatus; } // InitializePnPManager //------------------------------------------------------------------------ // Post Log-On routines //------------------------------------------------------------------------ CONFIGRET PNP_ReportLogOn( IN handle_t hBinding, IN BOOL bAdmin, IN DWORD ProcessID ) /*++ Routine Description: This routine is used to report logon events. It is called from the userinit.exe process during logon, via CMP_Report_LogOn. Arguments: hBinding - RPC binding handle. bAdmin - Not used. ProcessID - Process ID of the userinit.exe process that will be used to retrieve the access token for the user associated with this logon. Return Value: Return CR_SUCCESS if the function succeeds, CR_FAILURE otherwise. Notes: When a user logs on to the console session, we signal the "logged on" event, which will wake the device installation thread to perform any pending client-side device install events. Client-side device installation, requires the user access token to create a rundll32 process in the logged on user's security context. Although Terminal Services is now always running on all flavors of Whistler, it is not started during safe mode. It may also not be started by the time session 0 is available for logon as the Console session. For those reasons, SessionId 0 is still treated differently from the other sessions. Since Terminal Services may not be available during a logon to session 0, we cache a handle to the access token associated with the userinit.exe process. The handle is closed when we receive a logoff event for our process's session (SessionId 0), via PnpConsoleCtrlHandler. Handles to user access tokens for all other sessions are retrieved on demand, using GetWinStationUserToken, since Terminal Services must necessarily be available for the creation of those sessions. --*/ { CONFIGRET Status = CR_SUCCESS; HANDLE hUserProcess = NULL, hUserToken = NULL; RPC_STATUS rpcStatus; DWORD dwWait; ULONG ulSessionId, ulSessionIdCopy; PWSTR MultiSzGuidList = NULL; UNREFERENCED_PARAMETER(bAdmin); // // This routine only services requests from local RPC clients. // if (!IsClientLocal(hBinding)) { return CR_ACCESS_DENIED; } // // Wait for the init mutex - this ensures that the pnp init // routine (called when the service starts) has had a chance // to complete first. // if (ghInitMutex != NULL) { dwWait = WaitForSingleObject(ghInitMutex, 180000); // 3 minutes if (dwWait != WAIT_OBJECT_0) { // // mutex was abandoned or timed out during the wait, // don't attempt any further init activity // return CR_FAILURE; } } try { // // Make sure that the caller is a member of the interactive group. // if (!IsClientInteractive(hBinding)) { Status = CR_FAILURE; goto Clean0; } // // Impersonate the client and retrieve the SessionId. // rpcStatus = RpcImpersonateClient(hBinding); if (rpcStatus != RPC_S_OK) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: PNP_ReportLogOn: RpcImpersonateClient failed, error = %d\n", rpcStatus)); Status = CR_FAILURE; goto Clean0; } // // Keep track of the client's session. // ulSessionId = GetClientLogonId(); ulSessionIdCopy = ulSessionId; KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT, "UMPNPMGR: PNP_ReportLogOn: SessionId %d\n", ulSessionId)); // // NTRAID #181685-2000/09/11-jamesca: // // Currently, terminal services send notification of logons to // "remote" sessions before the server's process creation thread is // running. If we set the logged on event, and there are devices // waiting to be installed, we will immediately call // CreateProcessAsUser on that session, which will fail. As a // (temporary?) workaround, we'll continue to use PNP_ReportLogOn to // receive logon notification from userinit.exe, now for all sessions. // // // If this is a logon to SessionId 0, save a handle to the access token // associated with the userinit.exe process. We need this later to // create a rundll32 process in the logged on user's security context // for client-side device installation and hotplug notifications. // if (ulSessionId == MAIN_SESSION) { ASSERT(gTokenLock.LockHandles); LockPrivateResource(&gTokenLock); // // We should have gotten rid of the cached user token during logoff, // so if we still have one, ignore this spurious logon report. // //ASSERT(ghUserToken == NULL); if (ghUserToken == NULL) { // // While still impersonating the client, open a handle to the user // access token of the calling process (userinit.exe). // hUserProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, ProcessID); if (hUserProcess) { if (OpenProcessToken( hUserProcess, TOKEN_ALL_ACCESS, &hUserToken)) { ASSERT(hUserToken != NULL); // // Duplicate the userinit process token so that we have // one of our own that we can safely enable/disable // privileges for, without affecting that process. // if (!DuplicateTokenEx( hUserToken, 0, NULL, SecurityImpersonation, TokenPrimary, &ghUserToken)) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: PNP_ReportLogOn: " "DuplicateTokenEx failed, error = %d\n", GetLastError())); ghUserToken = NULL; } CloseHandle(hUserToken); } CloseHandle(hUserProcess); } else { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: PNP_ReportLogOn: OpenProcess failed, error = %d\n", rpcStatus)); ASSERT(0); } } ASSERT(ghUserToken); UnlockPrivateResource(&gTokenLock); } // // Stop impersonating. // rpcStatus = RpcRevertToSelf(); if (rpcStatus != RPC_S_OK) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: PNP_ReportLogOn: RpcRevertToSelf failed, error = %d\n", rpcStatus)); ASSERT(rpcStatus == RPC_S_OK); } // // If this is a logon to the "Console" session, signal the event that // indicates a Console user is currently logged on. // if (IsConsoleSession(ulSessionId)) { if (InstallEvents[LOGGED_ON_EVENT]) { SetEvent(InstallEvents[LOGGED_ON_EVENT]); KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT | DBGF_INSTALL, "UMPNPMGR: PNP_ReportLogOn: SetEvent LOGGED_ON_EVENT\n")); } } // // For every logon to every session, send a generic blocked driver // notification if the system has blocked any drivers from loading so // far this boot. // MultiSzGuidList = BuildBlockedDriverList((LPGUID)NULL, 0); if (MultiSzGuidList != NULL) { SendHotplugNotification((LPGUID)&GUID_DRIVER_BLOCKED, NULL, MultiSzGuidList, &ulSessionId, 0); HeapFree(ghPnPHeap, 0, MultiSzGuidList); MultiSzGuidList = NULL; } ulSessionId = ulSessionIdCopy; // // Check if there were any invalid IDs encountered before we started // and send notification to user as needed. // SendInvalidIDNotifications(ulSessionId); Clean0: NOTHING; } except(EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Exception in PNP_ReportLogOn\n")); ASSERT(0); Status = CR_FAILURE; // // Reference the following variable so the compiler will respect // statement ordering w.r.t. its assignment. // ghInitMutex = ghInitMutex; MultiSzGuidList = MultiSzGuidList; } if (ghInitMutex != NULL) { ReleaseMutex(ghInitMutex); } if (MultiSzGuidList != NULL) { HeapFree(ghPnPHeap, 0, MultiSzGuidList); } return Status; } // PNP_ReportLogon typedef struct _DEVICE_INSTALL_ENTRY { SIZE_T Index; ULONG Depth; }DEVICE_INSTALL_ENTRY, *PDEVICE_INSTALL_ENTRY; int __cdecl compare_depth( const void *a, const void *b ) { PDEVICE_INSTALL_ENTRY entry1, entry2; entry1 = (PDEVICE_INSTALL_ENTRY)a; entry2 = (PDEVICE_INSTALL_ENTRY)b; if (entry1->Depth > entry2->Depth) { return -1; } else if (entry1->Depth < entry2->Depth) { return 1; } return 0; } DWORD ThreadProc_GuiSetupDeviceInstall( LPDWORD lpThreadParam ) /*++ Routine Description: This routine is a thread procedure. This thread is only active during GUI-mode setup and passes device notifications down a pipe to setup. There are two passes, which *must* match exactly with the two passes in GUI-setup Once the passes are complete, we proceed to Phase-2 of normal serverside install Arguments: lpThreadParam - Not used. Return Value: Not used, currently returns result of ThreadProc_DeviceInstall - which will normally not return --*/ { CONFIGRET Status = CR_SUCCESS; LPWSTR pDeviceList = NULL, pszDevice = NULL; ULONG ulSize = 0, ulConfig, Pass, threadID; ULONG ulPostSetupSkipPhase1 = TRUE; HANDLE hPipeEvent = NULL, hPipe = NULL, hBatchEvent = NULL, hThread = NULL; PPNP_INSTALL_ENTRY entry = NULL; PDEVICE_INSTALL_ENTRY pSortArray = NULL; LONG lCount; BOOL needsInstall; ULONG ulReenumerationCount; UNREFERENCED_PARAMETER(lpThreadParam); try { // // 2 Passes, must match up with the 2 passes in SysSetup. // generally, most, if not all devices, will be picked up // and installed by syssetup // for (Pass = 1; Pass <= 2; Pass++) { ulReenumerationCount = 0; // // If Gui mode setup is in progress, we don't need to wait for a logon // event. Just wait on the event that indicates when gui mode setup // has opened the pipe and is ready to recieve device names. Attempt to // create the event first (in case I beat setup to it), if it exists // already then just open it by name. This is a manual reset event. // hPipeEvent = CreateEvent(NULL, TRUE, FALSE, PNP_CREATE_PIPE_EVENT); if (!hPipeEvent) { if (GetLastError() == ERROR_ALREADY_EXISTS) { hPipeEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, PNP_CREATE_PIPE_EVENT); if (!hPipeEvent) { Status = CR_FAILURE; goto Clean0; } } else { Status = CR_FAILURE; goto Clean0; } } if (WaitForSingleObject(hPipeEvent, INFINITE) != WAIT_OBJECT_0) { Status = CR_FAILURE; goto Clean0; // event must have been abandoned } // // Reset the manual-reset event back to the non-signalled state. // ResetEvent(hPipeEvent); hBatchEvent = CreateEvent(NULL, TRUE, FALSE, PNP_BATCH_PROCESSED_EVENT); if (!hBatchEvent) { if (GetLastError() == ERROR_ALREADY_EXISTS) { hBatchEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, PNP_BATCH_PROCESSED_EVENT); if (!hBatchEvent) { Status = CR_FAILURE; goto Clean0; } } else { Status = CR_FAILURE; goto Clean0; } } // // Open the client side of the named pipe, the server side was opened // by gui mode setup. // if (!WaitNamedPipe(PNP_NEW_HW_PIPE, PNP_PIPE_TIMEOUT)) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_ERRORS, "UMPNPMGR: ThreadProc_GuiSetupDeviceInstall: WaitNamedPipe failed!\n")); Status = CR_FAILURE; goto Clean0; } hPipe = CreateFile(PNP_NEW_HW_PIPE, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hPipe == INVALID_HANDLE_VALUE) { LogErrorEvent(ERR_CREATING_SETUP_PIPE, GetLastError(), 0); Status = CR_FAILURE; goto Clean0; } // // Retreive the list of all devices for all enumerators // Start out with a reasonably-sized buffer (16K characters) in // hopes of avoiding 2 calls to get the device list. // ulSize = 16384; for ( ; ; ) { pDeviceList = HeapAlloc(ghPnPHeap, 0, ulSize * sizeof(WCHAR)); if (pDeviceList == NULL) { Status = CR_OUT_OF_MEMORY; goto Clean0; } Status = PNP_GetDeviceList(NULL, NULL, pDeviceList, &ulSize, 0); if (Status == CR_SUCCESS) { break; } else if(Status == CR_BUFFER_SMALL) { // // Our initial buffer wasn't large enough. Free the current // buffer. // HeapFree(ghPnPHeap, 0, pDeviceList); pDeviceList = NULL; // // Now, go ahead and make the call to retrieve the actual // size required. // Status = PNP_GetDeviceListSize(NULL, NULL, &ulSize, 0); if (Status != CR_SUCCESS) { goto Clean0; } } else { // // We failed for some reason other than buffer-too-small. // Bail now. // goto Clean0; } } // // Count the number of devices we are installing. // for (pszDevice = pDeviceList, lCount = 0; *pszDevice; pszDevice += lstrlen(pszDevice) + 1, lCount++) { } pSortArray = HeapAlloc(ghPnPHeap, 0, lCount * sizeof(DEVICE_INSTALL_ENTRY)); if (pSortArray) { NTSTATUS ntStatus; PLUGPLAY_CONTROL_DEPTH_DATA depthData; LPWSTR pTempList; HRESULT hr; // // Initialize all the information we need to sort devices. // for (pszDevice = pDeviceList, lCount = 0; *pszDevice; pszDevice += lstrlen(pszDevice) + 1, lCount++) { pSortArray[lCount].Index = pszDevice - pDeviceList; depthData.DeviceDepth = 0; RtlInitUnicodeString(&depthData.DeviceInstance, pszDevice); ntStatus = NtPlugPlayControl(PlugPlayControlGetDeviceDepth, &depthData, sizeof(depthData)); pSortArray[lCount].Depth = depthData.DeviceDepth; } // // Sort the array so that deeper devices are ahead. // qsort(pSortArray, lCount, sizeof(DEVICE_INSTALL_ENTRY), compare_depth); // // Copy the data so that the device instance strings are sorted. // pTempList = HeapAlloc(ghPnPHeap, 0, ulSize * sizeof(WCHAR)); if (pTempList) { for (pszDevice = pTempList, lCount--; lCount >= 0; lCount--) { hr = StringCchCopyEx( pszDevice, ulSize, &pDeviceList[pSortArray[lCount].Index], NULL, NULL, STRSAFE_NULL_ON_FAILURE); ASSERT(SUCCEEDED(hr)); if (SUCCEEDED(hr)) { ulSize -= lstrlen(pszDevice); } pszDevice += lstrlen(pszDevice) + 1; } *pszDevice = TEXT('\0'); HeapFree(ghPnPHeap, 0, pDeviceList); pDeviceList = pTempList; } HeapFree(ghPnPHeap, 0, pSortArray); } // // PHASE 1 // // Search the registry for devices to install. // for (pszDevice = pDeviceList; *pszDevice; pszDevice += lstrlen(pszDevice) + 1) { // // Is device present? // if (IsDeviceIdPresent(pszDevice)) { if (Pass == 1) { // // First time through, pass everything in the registry to // guimode setup via the pipe, whether they are marked as // needing to be installed or not. // if (!WriteFile(hPipe, pszDevice, (lstrlen(pszDevice)+1) * sizeof(WCHAR), &ulSize, NULL)) { LogErrorEvent(ERR_WRITING_SETUP_PIPE, GetLastError(), 0); } } else { // // Second time through, only pass along anything // that is marked as needing to be installed. // DevInstNeedsInstall(pszDevice, FALSE, &needsInstall); if (needsInstall) { if (!WriteFile(hPipe, pszDevice, (lstrlen(pszDevice)+1) * sizeof(WCHAR), &ulSize, NULL)) { LogErrorEvent(ERR_WRITING_SETUP_PIPE, GetLastError(), 0); } } } } else if (Pass == 1) { // // device ID is not present // we should have marked this as needs re-install // ulConfig = GetDeviceConfigFlags(pszDevice, NULL); if ((ulConfig & CONFIGFLAG_REINSTALL)==0) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_WARNINGS, "UMPNPMGR: Setup - %ws not present and not marked as needing reinstall - setting CONFIGFLAG_REINSTALL\n", pszDevice)); ulConfig |= CONFIGFLAG_REINSTALL; PNP_SetDeviceRegProp(NULL, pszDevice, CM_DRP_CONFIGFLAGS, REG_DWORD, (LPBYTE)&ulConfig, sizeof(ulConfig), 0 ); } } } // // PHASE 2 // do { // // Write a NULL ID to indicate end of this batch. // if (!WriteFile(hPipe, TEXT(""), sizeof(WCHAR), &ulSize, NULL)) { LogErrorEvent(ERR_WRITING_SETUP_PIPE, GetLastError(), 0); } // // Wait for gui mode setup to complete processing of the last // batch. // if (WaitForSingleObject(hBatchEvent, PNP_GUISETUP_INSTALL_TIMEOUT) != WAIT_OBJECT_0) { // // The event was either abandoned or timed out, give up. // goto Clean1; } ResetEvent(hBatchEvent); // // Reenumerate the tree from the ROOT on a separate thread. // hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc_ReenumerateDeviceTree, (LPVOID)pszRegRootEnumerator, 0, &threadID); if (hThread == NULL) { goto Clean1; } if (WaitForSingleObject(hThread, PNP_GUISETUP_INSTALL_TIMEOUT) != WAIT_OBJECT_0) { // // The event was either abandadoned or timed out, give up. // goto Clean1; } // // Check if we have reenumerated for too long. // if (++ulReenumerationCount >= MAX_REENUMERATION_COUNT) { // // Either something is wrong with one of the enumerators in // the system (more likely) or this device tree is // unreasonably deep. In the latter case, the remaining // devices will get installed post setup. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_ERRORS, "UMPNPMGR: ThreadProc_GuiSetupDeviceInstall: " "Reenumerated %d times, some enumerator is misbehaving!\n", ulReenumerationCount)); ASSERT(ulReenumerationCount < MAX_REENUMERATION_COUNT); goto Clean1; } // // If we dont have any devices in the install list, we are done. // if (InstallList.Next == NULL) { break; } // // Install any new devices found as a result of setting up the // previous batch of devices. // lCount = 0; LockNotifyList(&InstallList.Lock); while (InstallList.Next != NULL) { // // Retrieve and remove the first (oldest) entry in the // install device list. // entry = (PPNP_INSTALL_ENTRY)InstallList.Next; InstallList.Next = entry->Next; UnlockNotifyList(&InstallList.Lock); ASSERT(!(entry->Flags & (PIE_SERVER_SIDE_INSTALL_ATTEMPTED | PIE_DEVICE_INSTALL_REQUIRED_REBOOT))); // // Should we install this device? // DevInstNeedsInstall(entry->szDeviceId, FALSE, &needsInstall); if (needsInstall) { // // Give this device name to gui mode setup via the pipe // if (!WriteFile(hPipe, entry->szDeviceId, (lstrlen(entry->szDeviceId)+1) * sizeof(WCHAR), &ulSize, NULL)) { LogErrorEvent(ERR_WRITING_SETUP_PIPE, GetLastError(), 0); } else { lCount++; } } HeapFree(ghPnPHeap, 0, entry); LockNotifyList(&InstallList.Lock); } UnlockNotifyList(&InstallList.Lock); } while (lCount > 0); Clean1: CloseHandle(hPipe); hPipe = INVALID_HANDLE_VALUE; CloseHandle(hPipeEvent); hPipeEvent = NULL; CloseHandle(hBatchEvent); hBatchEvent = NULL; if (hThread) { CloseHandle(hThread); hThread = NULL; } HeapFree(ghPnPHeap, 0, pDeviceList); pDeviceList = NULL; } // for Clean0: NOTHING; } except(EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_INSTALL, "UMPNPMGR: Exception in ThreadProc_GuiSetupDeviceInstall\n")); ASSERT(0); Status = CR_FAILURE; // // Reference the following variables so the compiler will respect // statement ordering w.r.t. their assignment. // hPipe = hPipe; hPipeEvent = hPipeEvent; hBatchEvent = hBatchEvent; hThread = hThread; pDeviceList = pDeviceList; } if (hPipe != INVALID_HANDLE_VALUE) { CloseHandle(hPipe); } if (hPipeEvent != NULL) { CloseHandle(hPipeEvent); } if (hBatchEvent != NULL) { CloseHandle(hBatchEvent); } if (hThread) { CloseHandle(hThread); } if (pDeviceList != NULL) { HeapFree(ghPnPHeap, 0, pDeviceList); } // // will typically never return // return ThreadProc_DeviceInstall(&ulPostSetupSkipPhase1); } // ThreadProc_GuiSetupDeviceInstall DWORD ThreadProc_FactoryPreinstallDeviceInstall( LPDWORD lpThreadParam ) /*++ Routine Description: This routine is a thread procedure. This thread is only active during GUI-mode setup when we are doing a factory preinstall. This function simply creates and event, and then waits before kicking off normal pnp device install Arguments: lpThreadParam - Not used. Return Value: Not used, currently returns result of ThreadProc_DeviceInstall - which will normally not return. --*/ { HANDLE hEvent = NULL; CONFIGRET Status = CR_SUCCESS; UNREFERENCED_PARAMETER(lpThreadParam); try { hEvent = CreateEvent(NULL, TRUE, FALSE, PNP_CREATE_PIPE_EVENT); if (!hEvent) { if (GetLastError() == ERROR_ALREADY_EXISTS) { hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, PNP_CREATE_PIPE_EVENT); if (!hEvent) { Status = CR_FAILURE; goto Clean0; } } else { Status = CR_FAILURE; goto Clean0; } } if (WaitForSingleObject(hEvent, INFINITE) != WAIT_OBJECT_0) { Status = CR_FAILURE; goto Clean0; // event must have been abandoned } Clean0: NOTHING; } except(EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_INSTALL, "UMPNPMGR: Exception in ThreadProc_FactoryPreinstallDeviceInstall\n")); ASSERT(0); Status = CR_FAILURE; // // Reference the following variable so the compiler will respect // statement ordering w.r.t. its assignment. // hEvent = hEvent; } if (hEvent != NULL) { CloseHandle(hEvent); } // // will typically never return // return ThreadProc_DeviceInstall(NULL); } // ThreadProc_FactoryPreinstallDeviceInstall //----------------------------------------------------------------------------- // Device enumeration thread - created on demand //----------------------------------------------------------------------------- DWORD ThreadProc_ReenumerateDeviceTree( LPVOID lpThreadParam ) /*++ Routine Description: This routine is a thread procedure. This thread is created dynamically to perform a synchronous device re-enumeration. This thread can be waited on and abandoned after a specified timeout, if necessary. Arguments: lpThreadParam - Specifies a pointer to the device instance path that should be re-enumerated. Return Value: Not used, currently returns 0. --*/ { PLUGPLAY_CONTROL_DEVICE_CONTROL_DATA controlData; // // Reenumerate the tree from the root specified. // memset(&controlData, 0 , sizeof(PLUGPLAY_CONTROL_DEVICE_CONTROL_DATA)); controlData.Flags = 0; RtlInitUnicodeString(&controlData.DeviceInstance, (PCWSTR)lpThreadParam); NtPlugPlayControl(PlugPlayControlEnumerateDevice, &controlData, sizeof(controlData)); return 0; } // ThreadProc_ReenumerateDeviceTree //----------------------------------------------------------------------------- // Device installation thread //----------------------------------------------------------------------------- DWORD ThreadProc_DeviceInstall( LPDWORD lpParam ) /*++ Routine Description: This routine is a thread procedure. It is invoked during a normal boot, or after the GUI-setup special case has finished During Phase-1, all devices are checked During Phase-2, all new devices are checked as they arrive. Arguments: lpParam - if given and non-zero (currently only when called from ThreadProc_GuiSetupDeviceInstall) skips Phase-1, will never prompt for reboot Return Value: Not used, currently returns Status failure code, should typically not return --*/ { CONFIGRET Status = CR_SUCCESS; LPWSTR pDeviceList = NULL, pszDevice = NULL; ULONG ulSize = 0, ulProblem = 0, ulStatus, ulConfig; DWORD InstallDevStatus, WaitResult; PPNP_INSTALL_ENTRY InstallEntry = NULL; PPNP_INSTALL_ENTRY current, TempInstallList, CurDupeNode, PrevDupeNode; BOOL InstallListLocked = FALSE; BOOL RebootRequired, needsInstall; BOOL DeviceHasProblem = FALSE, SingleDeviceHasProblem = FALSE; BOOL bStillInGuiModeSetup = lpParam ? (BOOL)lpParam[0] : FALSE; ULONG ulClientSessionId = INVALID_SESSION; ULONG ulFlags = 0; HANDLE hAutoStartEvent; HRESULT hr; if (!bStillInGuiModeSetup) { // // If the OOBE is not running, wait until the service control manager // has begun starting autostart services before we attempt to install // any devices. When the OOBE is running, we don't wait for anything // because the OOBE waits on us (via CMP_WaitNoPendingInstallEvents) to // finish server-side installing any devices that we can before it lets // the SCM autostart services and set this event. // if (!gbOobeInProgress) { hAutoStartEvent = OpenEvent(SYNCHRONIZE, FALSE, SC_AUTOSTART_EVENT_NAME); if (hAutoStartEvent) { // // Wait until the service controller allows other services to // start before we try to install any devices in phases 1 and 2, // below. // WaitResult = WaitForSingleObject(hAutoStartEvent, INFINITE); ASSERT(WaitResult == WAIT_OBJECT_0); CloseHandle(hAutoStartEvent); } else { // // The service controller always creates this event, so it must // exist by the time our service is started. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_ERRORS, "UMPNPMGR: Failed to open %ws event, error = %d\n", SC_AUTOSTART_EVENT_NAME, GetLastError())); ASSERT(0); } } try { // // Phase 1: // // Check the enum branch in the registry and attempt to install, one // right after the other, any devices that need to be installed right // now. Typically these devices showed up during boot. // // Retrieve the list of devices that currently need to be installed. // Start out with a reasonably-sized buffer (16K characters) in hopes // of avoiding 2 calls to get the device list. // // this phase is skipped during GUI-mode setup, and is handled by // ThreadProc_GuiSetupDeviceInstall // ulSize = 16384; for ( ; ; ) { pDeviceList = HeapAlloc(ghPnPHeap, 0, ulSize * sizeof(WCHAR)); if (pDeviceList == NULL) { Status = CR_OUT_OF_MEMORY; goto Clean1; } Status = PNP_GetDeviceList(NULL, NULL, pDeviceList, &ulSize, 0); if (Status == CR_SUCCESS) { break; } else if(Status == CR_BUFFER_SMALL) { // // Our initial buffer wasn't large enough. Free the current // buffer. // HeapFree(ghPnPHeap, 0, pDeviceList); pDeviceList = NULL; // // Now, go ahead and make the call to retrieve the actual size // required. // Status = PNP_GetDeviceListSize(NULL, NULL, &ulSize, 0); if (Status != CR_SUCCESS) { goto Clean1; } } else { // // We failed for some reason other than buffer-too-small. Bail // now. // goto Clean1; } } // // Make sure we have the device installer APIs at our disposal // before starting server-side install. // InstallDevStatus = LoadDeviceInstaller(); if (InstallDevStatus != NO_ERROR) { goto Clean1; } // // Get the config flag for each device, and install any that need to be // installed. // for (pszDevice = pDeviceList; *pszDevice; pszDevice += lstrlen(pszDevice) + 1) { // // Should the device be installed? // if (DevInstNeedsInstall(pszDevice, FALSE, &needsInstall) == CR_SUCCESS) { if (needsInstall) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: Installing device (%ws) server-side\n", pszDevice)); RebootRequired = FALSE; // // Make sure we have the device installer APIs at our disposal // before starting server-side install. // InstallDevStatus = LoadDeviceInstaller(); if (InstallDevStatus == NO_ERROR) { if (IsFastUserSwitchingEnabled()) { ulFlags = DEVICE_INSTALL_DISPLAY_ON_CONSOLE; } else { ulClientSessionId = MAIN_SESSION; ulFlags = 0; } // // Attempt server-side installation of this device. // InstallDevStatus = InstallDeviceServerSide(pszDevice, &RebootRequired, &SingleDeviceHasProblem, &ulClientSessionId, ulFlags); } if(InstallDevStatus == NO_ERROR) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: Installing device (%ws), Server-side installation succeeded!\n", pszDevice)); if (SingleDeviceHasProblem) { DeviceHasProblem = TRUE; } } else { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: Installing device (%ws), Server-side installation failed (Status = 0x%08X)\n", pszDevice, InstallDevStatus)); } if((InstallDevStatus != NO_ERROR) || RebootRequired) { // // Allocate and initialize a new device install entry // block. // InstallEntry = HeapAlloc(ghPnPHeap, 0, sizeof(PNP_INSTALL_ENTRY)); if(!InstallEntry) { Status = CR_OUT_OF_MEMORY; goto Clean1; } InstallEntry->Next = NULL; InstallEntry->Flags = PIE_SERVER_SIDE_INSTALL_ATTEMPTED; if(InstallDevStatus == NO_ERROR) { // // We didn't get here because the install failed, // so it must've been because the installation // requires a reboot. // ASSERT(RebootRequired); InstallEntry->Flags |= PIE_DEVICE_INSTALL_REQUIRED_REBOOT; // // Set the global server side device install // reboot needed bool to TRUE. // gServerSideDeviceInstallRebootNeeded = TRUE; } // // Copy the Device ID to the install list entry. // Upon failure, we will just end up adding an // install entry with a NULL device id to the list. // Non ideal, but we should still do it so that we // can preserve the flags that may indicate a reboot // is required. // hr = StringCchCopyEx(InstallEntry->szDeviceId, MAX_DEVICE_ID_LEN, pszDevice, NULL, NULL, STRSAFE_NULL_ON_FAILURE); ASSERT(SUCCEEDED(hr)); // // Insert this entry in the device install list. // LockNotifyList(&InstallList.Lock); InstallListLocked = TRUE; current = (PPNP_INSTALL_ENTRY)InstallList.Next; if(!current) { InstallList.Next = InstallEntry; } else { while((PPNP_INSTALL_ENTRY)current->Next) { current = (PPNP_INSTALL_ENTRY)current->Next; } current->Next = InstallEntry; } // // Newly-allocated entry now added to the list--NULL // out the pointer so we won't try to free it if we // happen to encounter an exception later. // InstallEntry = NULL; UnlockNotifyList(&InstallList.Lock); InstallListLocked = FALSE; SetEvent(InstallEvents[NEEDS_INSTALL_EVENT]); } } } else { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: Ignoring not present device (%ws)\n", pszDevice)); } } Clean1: // // Up to this point, we have only attempted server-side installation // of devices, so any device install clients we might have launched // would have been for UI only. Since we are done installing // devices for the time being, we should unload the device installer // APIs, and get rid of any device install clients that currently // exist. // UnloadDeviceInstaller(); } except(EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_INSTALL, "UMPNPMGR: Exception in ThreadProc_DeviceInstall\n")); ASSERT(0); Status = CR_FAILURE; // // Reference the following variables so the compiler will respect // statement ordering w.r.t. assignment. // pDeviceList = pDeviceList; InstallListLocked = InstallListLocked; InstallEntry = InstallEntry; } if(InstallEntry) { HeapFree(ghPnPHeap, 0, InstallEntry); InstallEntry = NULL; } if(InstallListLocked) { UnlockNotifyList(&InstallList.Lock); InstallListLocked = FALSE; } if(pDeviceList != NULL) { HeapFree(ghPnPHeap, 0, pDeviceList); } } // // NOTE: We should remove this line if we ever hook up the 'finished // installing hardware' balloon so that it comes up if we install devices // before a user logs on. // DeviceHasProblem = FALSE; // // Maintain a temporary list of PNP_INSTALL_ENTRY nodes that we needed to // initiate client-side installation for, but couldn't these nodes get // re-added to the master InstallList once all entries have been processed. // Also, keep a pointer to the end of the list for efficient appending of // nodes to the queue. // current = TempInstallList = NULL; try { // // Phase 2: Hang around and be prepared to install any devices // that come on line for the first time while we're // running. // we may come into Phase 2 (skipping Phase 1) in GUI-Setup // for ( ; ; ) { // // Before starting an indefinite wait, test the event state and set // the ghNoPendingInstalls event accordingly. This event is just a // backdoor way for device manager (and others) to see if we're // still installing things. // if(WaitForSingleObject(InstallEvents[NEEDS_INSTALL_EVENT], 0) != WAIT_OBJECT_0) { // // There's nothing waiting to be installed--set the event. // SetEvent(ghNoPendingInstalls); } // // Wait until the device event thread tells us we need to // dynamically install a new device (or until somebody logs on). // WaitForMultipleObjects(NUM_INSTALL_EVENTS, InstallEvents, FALSE, // wake up on either event INFINITE // I can wait all day ); // // After I empty the list, this thread can sleep until another new // device needs to be installed... // ResetEvent(InstallEvents[NEEDS_INSTALL_EVENT]); // // ...or until a user logs in (note that we only want to awake once // per log-in. // ResetEvent(InstallEvents[LOGGED_ON_EVENT]); // // We now have something to do, so reset the event that lets folks // like DevMgr know when we're idle. // ResetEvent(ghNoPendingInstalls); #if DBG RtlValidateHeap(ghPnPHeap,0,NULL); #endif // // Process each device that needs to be installed. // while (InstallList.Next != NULL) { // // Retrieve and remove the first (oldest) entry in the // install device list. // LockNotifyList(&InstallList.Lock); InstallListLocked = TRUE; InstallEntry = (PPNP_INSTALL_ENTRY)InstallList.Next; InstallList.Next = InstallEntry->Next; // // Now, scan the rest of the list looking for additional nodes // related to this same device. If we find any, OR their flags // into our 'master' node, and remove the duplicated nodes from // the list. We can get duplicates due to the fact that both // the event thread and this thread can be placing items in the // list. We don't want to be attempting (failing) server-side // installations multiple times. // CurDupeNode = (PPNP_INSTALL_ENTRY)InstallList.Next; PrevDupeNode = NULL; while(CurDupeNode) { if (CompareString( LOCALE_INVARIANT, NORM_IGNORECASE, InstallEntry->szDeviceId, -1, CurDupeNode->szDeviceId, -1) == CSTR_EQUAL) { // // We have a duplicate! OR the flags into those of // the install entry we retrieved from the head of // the list. // InstallEntry->Flags |= CurDupeNode->Flags; // // Now remove this duplicate node from the list. // if(PrevDupeNode) { PrevDupeNode->Next = CurDupeNode->Next; } else { InstallList.Next = CurDupeNode->Next; } HeapFree(ghPnPHeap, 0, CurDupeNode); if(PrevDupeNode) { CurDupeNode = (PPNP_INSTALL_ENTRY)PrevDupeNode->Next; } else { CurDupeNode = (PPNP_INSTALL_ENTRY)InstallList.Next; } } else { PrevDupeNode = CurDupeNode; CurDupeNode = (PPNP_INSTALL_ENTRY)CurDupeNode->Next; } } UnlockNotifyList(&InstallList.Lock); InstallListLocked = FALSE; if(InstallEntry->Flags & PIE_DEVICE_INSTALL_REQUIRED_REBOOT) { // // We've already performed a (successful) server-side // installation on this device. Remember the fact that a // reboot is needed, so we'll prompt after processing this // batch of new hardware. // // This will be our last chance to prompt for reboot on this // node, because the next thing we're going to do is free // this install entry! // gServerSideDeviceInstallRebootNeeded = TRUE; } else { // // Verify that device really needs to be installed // ulConfig = GetDeviceConfigFlags(InstallEntry->szDeviceId, NULL); Status = GetDeviceStatus(InstallEntry->szDeviceId, &ulStatus, &ulProblem); if (Status == CR_SUCCESS) { // // Note that we must explicitly check below for the // presence of the CONFIGFLAG_REINSTALL config flag. We // can't simply rely on the CM_PROB_REINSTALL problem // being set, because we may have encountered a device // during our phase 1 processing whose installation was // deferred because it provided finish-install wizard // pages. Since we only discover that this is the case // _after_ successful completion of DIF_INSTALLDEVICE, // it's too late to set the problem (kernel-mode PnP // manager only allows us to set a problem of needs- // reboot for a running devnode). // Status = DevInstNeedsInstall( InstallEntry->szDeviceId, TRUE, &needsInstall); if ((Status == CR_SUCCESS) && needsInstall) { if(!(InstallEntry->Flags & PIE_SERVER_SIDE_INSTALL_ATTEMPTED)) { // // We haven't tried to install this device // server-side yet, so try that now. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: Installing device (%ws) server-side\n\t Status = 0x%08X\n\t Problem = %d\n\t ConfigFlags = 0x%08X\n", InstallEntry->szDeviceId, ulStatus, ulProblem, ulConfig)); // // Make sure we have the device installer APIs at our disposal // before starting server-side install. // InstallDevStatus = LoadDeviceInstaller(); if (InstallDevStatus == NO_ERROR) { if (IsFastUserSwitchingEnabled()) { ulFlags = DEVICE_INSTALL_DISPLAY_ON_CONSOLE; } else { ulClientSessionId = MAIN_SESSION; ulFlags = 0; } InstallDevStatus = InstallDeviceServerSide( InstallEntry->szDeviceId, &gServerSideDeviceInstallRebootNeeded, &SingleDeviceHasProblem, &ulClientSessionId, ulFlags); } if(InstallDevStatus == NO_ERROR) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: Installing device (%ws), Server-side installation succeeded!\n", InstallEntry->szDeviceId)); if (SingleDeviceHasProblem) { DeviceHasProblem = TRUE; } } else { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: Installing device (%ws), Server-side installation failed (Status = 0x%08X)\n", InstallEntry->szDeviceId, InstallDevStatus)); InstallEntry->Flags |= PIE_SERVER_SIDE_INSTALL_ATTEMPTED; } } else { // // Set some bogus error so we'll drop into the // non-server install codepath below. // InstallDevStatus = ERROR_INVALID_DATA; } if(InstallDevStatus != NO_ERROR) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: Installing device (%ws) client-side\n\t Status = 0x%08X\n\t Problem = %d\n\t ConfigFlags = 0x%08X\n", InstallEntry->szDeviceId, ulStatus, ulProblem, ulConfig)); if (IsFastUserSwitchingEnabled()) { ulFlags = DEVICE_INSTALL_DISPLAY_ON_CONSOLE; } else { ulClientSessionId = MAIN_SESSION; ulFlags = 0; } if (!InstallDevice(InstallEntry->szDeviceId, &ulClientSessionId, ulFlags)) { // // We weren't able to kick off a device // install on the client side (probably // because no one was logged in). Stick // this PNP_INSTALL_ENTRY node into a // temporary list that we'll re-add into // the InstallList queue once we've emptied // it. // if(current) { current->Next = InstallEntry; current = InstallEntry; } else { ASSERT(!TempInstallList); TempInstallList = current = InstallEntry; } // // NULL out the InstallEntry pointer so we // don't try to free it later. // InstallEntry = NULL; } } } else if((ulStatus & DN_HAS_PROBLEM) && (ulProblem == CM_PROB_NEED_RESTART)) { // // This device was percolated up from kernel-mode // for the sole purpose of requesting a reboot. // This presently only happens when we encounter a // duplicate devnode, and we then "unique-ify" it // to keep from bugchecking. We don't want the // unique-ified devnode to actually be installed/ // used. Instead, we just want to give the user a // prompt to reboot, and after they reboot, all // should be well. The scenario where this has // arisen is in relation to a USB printer (with a // serial number) that is moved from one port to // another during a suspend. When we resume, we // have both an arrival and a removal to process, // and if we process the arrival first, we think // we've found a dupe. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: Duplicate device detected (%ws), need to prompt user to reboot!\n", InstallEntry->szDeviceId)); // // Stick this entry into our temporary list to deal // with later... // if(current) { current->Next = InstallEntry; current = InstallEntry; } else { ASSERT(!TempInstallList); TempInstallList = current = InstallEntry; } // // If possible, we want to prompt for reboot right // away (that is, after all install events are // drained)... // gServerSideDeviceInstallRebootNeeded = TRUE; // // If no user is logged in yet, flag this install // entry so we'll try to prompt for reboot the next // time we're awakened (which hopefully will be due // to a user logging in). // InstallEntry->Flags |= PIE_DEVICE_INSTALL_REQUIRED_REBOOT; // // NULL out the InstallEntry pointer so we // don't try to free it later. // InstallEntry = NULL; } } } if(InstallEntry) { HeapFree(ghPnPHeap, 0, InstallEntry); InstallEntry = NULL; } } // // We've processed all device install events known to us at this // time. If we encountered any device whose installation requires // a reboot, prompt the logged-in user (if any) to reboot now. // if (gServerSideDeviceInstallRebootNeeded) { ulFlags = DEVICE_INSTALL_FINISHED_REBOOT; if (IsFastUserSwitchingEnabled()) { ulFlags |= DEVICE_INSTALL_DISPLAY_ON_CONSOLE; } else { ulClientSessionId = MAIN_SESSION; } if (bStillInGuiModeSetup) { // // if we're still in GUI setup, we're going to suppress // any reboot prompts // gServerSideDeviceInstallRebootNeeded = FALSE; } else if (PromptUser(&ulClientSessionId, ulFlags)) { // // We successfully delivered the reboot prompt, so if the // user chose to ignore it, we don't want to prompt again // for reboot the next time new hardware shows up (unless // that hardware also requires a reboot). // gServerSideDeviceInstallRebootNeeded = FALSE; } } if(TempInstallList) { // // Add our temporary list of PNP_INSTALL_ENTRY nodes back into // the InstallList queue. We _do not_ set the event that says // there's more to do, so these nodes will be seen again only // if (a) somebody logs in or (b) more new hardware shows up. // // Note: we cannot assume that the list is empty, because there // may have been an insertion after the last time we checked it // above. We want to add our stuff to the beginning of the // InstallList queue, since the items we just finished // processing appeared before any new entries that might be // there now. // LockNotifyList(&InstallList.Lock); InstallListLocked = TRUE; ASSERT(current); current->Next = InstallList.Next; InstallList.Next = TempInstallList; // // Null out our temporary install list pointers to indicate // that the list is now empty. // current = TempInstallList = NULL; UnlockNotifyList(&InstallList.Lock); InstallListLocked = FALSE; } // // Before starting an indefinite wait, test the InstallEvents to see // if there any new devices to install, or there are still devices // to be installed in the InstallList. If neither of these is the // case after waiting a few seconds, we'll notify the user that // we're done installing devices for now, unload setupapi, and close // all device install clients. // WaitResult = WaitForMultipleObjects(NUM_INSTALL_EVENTS, InstallEvents, FALSE, DEVICE_INSTALL_COMPLETE_WAIT_TIME); if ((WaitResult != (WAIT_OBJECT_0 + NEEDS_INSTALL_EVENT)) && (InstallList.Next == NULL)) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: ThreadProc_DeviceInstall: no more devices to install.\n")); // // There's nothing waiting to be installed--set the event. // SetEvent(ghNoPendingInstalls); // // Notify the user (if any), that we think we're done installing // devices for now. Note that if we never used a device install // client at any time in this pass (all server-side or silent // installs), then we won't prompt about this. // ulFlags = DEVICE_INSTALL_BATCH_COMPLETE; if (DeviceHasProblem) { ulFlags |= DEVICE_INSTALL_PROBLEM; } if (IsFastUserSwitchingEnabled()) { ulFlags |= DEVICE_INSTALL_DISPLAY_ON_CONSOLE; } else { ulClientSessionId = MAIN_SESSION; } PromptUser(&ulClientSessionId, ulFlags); // // Clear the DeviceHasProblem boolean since we just notified the // user. // DeviceHasProblem = FALSE; // // We notified the user, now wait around for 10 more seconds // from the time of prompting before closing the client to make // sure that some new device doesn't arrive, in which case we // would just immediately load the installer again. // WaitResult = WaitForMultipleObjects(NUM_INSTALL_EVENTS, InstallEvents, FALSE, DEVICE_INSTALL_COMPLETE_DISPLAY_TIME); if ((WaitResult != (WAIT_OBJECT_0 + NEEDS_INSTALL_EVENT)) && (InstallList.Next == NULL)) { // // Unload the device installer, and get rid of any device // install clients that currently exist on any sessions. // Note that closing the device install client will make the // above prompt go away. // UnloadDeviceInstaller(); } } } } except(EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_INSTALL, "UMPNPMGR: Exception in ThreadProc_DeviceInstall\n")); ASSERT(0); Status = CR_FAILURE; // // Reference the following variables so the compiler will respect // statement ordering w.r.t. their assignment. // InstallListLocked = InstallListLocked; InstallEntry = InstallEntry; TempInstallList = TempInstallList; } if(InstallListLocked) { UnlockNotifyList(&InstallList.Lock); } if(InstallEntry) { HeapFree(ghPnPHeap, 0, InstallEntry); } while(TempInstallList) { current = (PPNP_INSTALL_ENTRY)(TempInstallList->Next); HeapFree(ghPnPHeap, 0, TempInstallList); TempInstallList = current; } // // meaningless return value, since this thread should never exit. // return (DWORD)Status; } // ThreadProc_DeviceInstall BOOL InstallDevice( IN LPWSTR pszDeviceId, IN OUT PULONG SessionId, IN ULONG Flags ) /*++ Routine Description: This routine initiates a device installation with the device install client (newdev.dll) on the current active console session, creating one if necessary. This routine waits for the client to signal completion, the process to signal that it has terminated, or this service to signal that we have disconnected ourselves from the client. Arguments: pszDeviceId - device instance ID of the devnode to be installed. SessionId - Supplies the address of a variable containing the SessionId on which the device install client is to be displayed. If successful, the SessionId will contain the session on which the device install client process was launched. Otherwise, will contain an invalid SessionId, INVALID_SESSION (0xFFFFFFFF). Flags - Specifies flags describing the behavior of the device install client. The following flags are currently defined: DEVICE_INSTALL_DISPLAY_ON_CONSOLE - if specified, the value in the SessionId variable will be ignored, and the device installclient will always be displayed on the current active console session. The SessionId of the current active console session will be returned in the SessionId. Return Value: Returns TRUE is the device installation was completed by the device install client, FALSE otherwise. --*/ { BOOL b; HANDLE hFinishEvents[3] = { NULL, NULL, NULL }; DWORD dwWait; PINSTALL_CLIENT_ENTRY pDeviceInstallClient = NULL; HRESULT hr; // // Assume failure // b = FALSE; // // Validate parameters // ASSERT(SessionId); if (SessionId == NULL) { return FALSE; } try { // // Calling DoDeviceInstallClient will create the newdev.dll process and open a named // pipe if it isn't already been done earlier. It will then send the Device Id to // newdev.dll over the pipe. // if (DoDeviceInstallClient(pszDeviceId, SessionId, Flags, &pDeviceInstallClient)) { ASSERT(pDeviceInstallClient); ASSERT(pDeviceInstallClient->ulSessionId == *SessionId); // // Keep track of the device id last sent to this client before we // disconnected from it. This will avoid duplicate popups if we // reconnect to this session again, and attempt to client-side // install the same device. // hr = StringCchCopyEx(pDeviceInstallClient->LastDeviceId, MAX_DEVICE_ID_LEN, pszDeviceId, NULL, NULL, STRSAFE_NULL_ON_FAILURE); // // Upon failure, the recorded LastDeviceId is NULL, so we will not // be able to determine what device this client last handled. Not // ideal, but not fatal either. // ASSERT(SUCCEEDED(hr)); // // Wait for the device install to be signaled from newdev.dll // to let us know that it has completed the installation. // // Wait on the client's process as well, to catch the case // where the process crashes (or goes away) without signaling the // device install event. // // Also wait on the disconnect event in case we have explicitly // disconnected from the client while switching sessions. // hFinishEvents[0] = pDeviceInstallClient->hProcess; hFinishEvents[1] = pDeviceInstallClient->hEvent; hFinishEvents[2] = pDeviceInstallClient->hDisconnectEvent; dwWait = WaitForMultipleObjects(3, hFinishEvents, FALSE, INFINITE); if (dwWait == WAIT_OBJECT_0) { // // If the return is WAIT_OBJECT_0 then the newdev.dll // process has gone away. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: InstallDevice: process signalled, closing device install client!\n")); } else if (dwWait == (WAIT_OBJECT_0 + 1)) { // // If the return is WAIT_OBJECT_0 + 1 then the device // installer successfully received the request. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: InstallDevice: device install succeeded\n")); b = TRUE; // // This device install client is no longer processing any // devices, so clear the device id. // *pDeviceInstallClient->LastDeviceId = L'\0'; } else if (dwWait == (WAIT_OBJECT_0 + 2)) { // // If the return is WAIT_OBJECT_0 + 2 then we were explicitly // disconnected from the device install client. Consider the // device install unsuccessful so that this device remains in // the install list. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: InstallDevice: device install client disconnected\n")); } else { // // The wait was satisfied for some reason other than the // specified objects. This should never happen, but just in // case, we'll close the client. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_ERRORS, "UMPNPMGR: InstallDevice: wait completed unexpectedly!\n")); } // // Remove the reference placed on the client while it was in use. // LockNotifyList(&InstallClientList.Lock); DereferenceDeviceInstallClient(pDeviceInstallClient); if ((dwWait != (WAIT_OBJECT_0 + 1)) && (dwWait != (WAIT_OBJECT_0 + 2))) { // // Unless the client signalled successful receipt of the // request, or the client's session was disconnected from the // console, the attempt to use this client was unsuccessful. // Remove the initial reference so all associated handles will // be closed and the entry will be freed when it is no longer in // use. // // // Note that if we were unsuccessful because of a // logoff, we would have already dereferenced the // client then, in which case the above dereference // was the final one, and pDeviceInstallClient would // be invalid. Instead, attempt to re-locate the // client by the session id. // pDeviceInstallClient = LocateDeviceInstallClient(*SessionId); if (pDeviceInstallClient) { ASSERT(pDeviceInstallClient->RefCount == 1); DereferenceDeviceInstallClient(pDeviceInstallClient); } } UnlockNotifyList(&InstallClientList.Lock); } } except (EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_INSTALL, "UMPNPMGR: Exception in InstallDevice!\n")); ASSERT(0); b = FALSE; } return b; } // InstallDevice //----------------------------------------------------------------------------- // Device event server-side rpc routines //----------------------------------------------------------------------------- CONFIGRET PNP_RegisterNotification( IN handle_t hBinding, IN ULONG_PTR hRecipient, IN LPWSTR ServiceName, IN LPBYTE NotificationFilter, IN ULONG ulSize, IN DWORD Flags, OUT PNP_NOTIFICATION_CONTEXT *Context, IN ULONG ProcessId, IN ULONG64 *ClientContext ) /*++ Routine Description: This routine is the rpc server-side of the CMP_RegisterNotification routine. It performs the remaining parameter validation and actually registers the notification request appropriately. Arguments: hBinding - RPC binding handle. hRecipient - The Flags value specifies what type of handle this is, currently it's either a window handle or a service handle. NotificationFilter - Specifies a pointer to one of the DEV_BROADCAST_XXX structures. ulSize - Specifies the size of the NotificationFilter structure. Flags - Specifies additional paramters used to describe the client or the supplied parameters. The Flags parameter is subdivided into multiple fields that are interpreted separately, as described below. ** The Flags parameter contains a field that describes the type of the hRecipient handle passed in. This field should be interpreted as an enum, and can be extracted from the Flags parameter using the following mask: DEVICE_NOTIFY_HANDLE_MASK Currently one of the following values must be specified by this field: DEVICE_NOTIFY_WINDOW_HANDLE - hRecipient is a window handle (HWND) for a window whose WNDPROC will be registered to receive WM_DEVICECHANGE window messages for the filtered events specified by the supplied NotificationFilter. DEVICE_NOTIFY_SERVICE_HANDLE - hRecipient is a service status handle (SERVICE_STATUS_HANDLE) for a service whose HandlerEx routine will be registered to receive SERVICE_CONTROL_DEVICEEVENT service controls for the filtered events specified by the supplied NotificationFilter. NOTE: in reality - hRecipient is just the name of the service, as resolved by the cfgmgr32 client. the SCM will actually resolve this name for us to the true SERVICE_STATUS_HANDLE for this service. DEVICE_NOTIFY_COMPLETION_HANDLE - not currently implemented. ** The Flags parameter contains a field that described additional properties for the notification. This field should be interpreted as a bitmask, and can be extracted from the Flags parameter using the following mask: DEVICE_NOTIFY_PROPERTY_MASK Currently, the following flags are defined for this field: DEVICE_NOTIFY_ALL_INTERFACE_CLASSES - This flag is only valid when a DBT_DEVTYP_DEVICEINTERFACE type notification filter is supplied. This flag specifies that the caller wishes to receive notification of events for device interfaces of all classes. If this flag is specified, the dbcc_classguid member of the NotificationFilter structure is ignored. ** The Flags parameter also contains a "Reserved" field, that is reserved for use by the cfgmgr32 client to this interface only. This field should be interpreted as a bitmask, and can be extracted from the Flags parameter using the following mask: DEVICE_NOTIFY_RESERVED_MASK Currently, the following flags are defined for this field: DEVICE_NOTIFY_WOW64_CLIENT - Specifies to a 64-bit server caller is a 32-bit process running on WOW64. The 64-bit server uses this information to construct 32-bit compatible notification filters for the client. Context - On return, this value returns the server notification context to the client, that is supplied when unregistering this notification request. hProcess - Process Id of the calling application. ClientContext - Specifies a pointer to a 64-bit value that contains the client-context pointer. This value is the HDEVNOTIFY notification handle returned to caller upon successful registration. It is actually a pointer to the client memory that will reference the returned server-notification context pointer - but is never used as a pointer here on the server-side. It is only used by the server to be specified as the dbch_hdevnotify member of the DEV_BROADCAST_HANDLE notification structure, supplied to the caller on DBT_DEVTYP_HANDLE notification events. NOTE: This value is truncated to 32-bits on 32-bit platforms, but is always transmitted as a 64-bit value by the RPC interface - for consistent marshalling of the data by RPC for all 32-bit / 64-bit client / server combinations. Return Value: Return CR_SUCCESS if the function succeeds, otherwise it returns one of the CR_* errors. Notes: This RPC server interface is used by local RPC clients only; it is never called remotely. --*/ { CONFIGRET Status = CR_SUCCESS; RPC_STATUS rpcStatus; DEV_BROADCAST_HDR UNALIGNED *p; PPNP_NOTIFY_ENTRY entry = NULL; ULONG hashValue, ulSessionId; HANDLE hProcess = NULL, localHandle = NULL; PPNP_NOTIFY_LIST notifyList = NULL; BOOLEAN bLocked = FALSE, bCritSecHeld = FALSE; // // This routine only services requests from local RPC clients. // if (!IsClientLocal(hBinding)) { return CR_ACCESS_DENIED; } try { // // Validate parameters. // if (!ARGUMENT_PRESENT(Context)) { Status = CR_INVALID_POINTER; goto Clean0; } *Context = NULL; if ((!ARGUMENT_PRESENT(NotificationFilter)) || (!ARGUMENT_PRESENT(ClientContext)) || (*ClientContext == 0)) { Status = CR_INVALID_POINTER; goto Clean0; } // // the RPC interface specifies ServiceName as a [ref] parameter, // so it should never be NULL. // ASSERT(ARGUMENT_PRESENT(ServiceName)); // // DEVICE_NOTIFY_BITS is a private mask, defined specifically for // validation by the client and server. It contains the bitmask for all // handle types (DEVICE_NOTIFY_COMPLETION_HANDLE specifically excluded // below), and all other flags that are currently defined - both public // and reserved. // if (INVALID_FLAGS(Flags, DEVICE_NOTIFY_BITS)) { Status = CR_INVALID_FLAG; goto Clean0; } // // Completion handles are not currently implemented. // DEVICE_NOTIFY_COMPLETION_HANDLE defined privately in winuserp.h, // reserved for future use (??). // if ((Flags & DEVICE_NOTIFY_HANDLE_MASK) == DEVICE_NOTIFY_COMPLETION_HANDLE) { Status = CR_INVALID_FLAG; goto Clean0; } // // Make sure the Notification filter is a valid size. // if ((ulSize < sizeof(DEV_BROADCAST_HDR)) || (((PDEV_BROADCAST_HDR)NotificationFilter)->dbch_size < sizeof(DEV_BROADCAST_HDR))) { Status = CR_BUFFER_SMALL; goto Clean0; } ASSERT(ulSize == ((PDEV_BROADCAST_HDR)NotificationFilter)->dbch_size); // // Impersonate the client and retrieve the SessionId. // rpcStatus = RpcImpersonateClient(hBinding); if (rpcStatus != RPC_S_OK) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: RpcImpersonateClient failed, error = %d\n", rpcStatus)); Status = CR_FAILURE; goto Clean0; } ulSessionId = GetClientLogonId(); rpcStatus = RpcRevertToSelf(); if (rpcStatus != RPC_S_OK) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: RpcRevertToSelf failed, error = %d\n", rpcStatus)); ASSERT(rpcStatus == RPC_S_OK); } // // Handle the different types of notification filters. // p = (PDEV_BROADCAST_HDR)NotificationFilter; switch (p->dbch_devicetype) { case DBT_DEVTYP_OEM: case DBT_DEVTYP_VOLUME: case DBT_DEVTYP_PORT: case DBT_DEVTYP_NET: // // These structures are either obsolete, or used for broadcast-only // notifications. // Status = CR_INVALID_DATA; break; case DBT_DEVTYP_HANDLE: { // // DEV_BROADCAST_HANDLE based notification. // DEV_BROADCAST_HANDLE UNALIGNED *filter = (PDEV_BROADCAST_HANDLE)NotificationFilter; PLUGPLAY_CONTROL_TARGET_RELATION_DATA controlData; NTSTATUS ntStatus; #ifdef _WIN64 DEV_BROADCAST_HANDLE64 UNALIGNED filter64; // // Check if the client is running on WOW64. // if (Flags & DEVICE_NOTIFY_WOW64_CLIENT) { // // Convert the 32-bit DEV_BROADCAST_HANDLE notification filter // to 64-bit. // DEV_BROADCAST_HANDLE32 UNALIGNED *filter32 = (PDEV_BROADCAST_HANDLE32)NotificationFilter; // // Validate the 32-bit input filter data // ASSERT(filter32->dbch_size >= sizeof(DEV_BROADCAST_HANDLE32)); if (filter32->dbch_size < sizeof(DEV_BROADCAST_HANDLE32) || ulSize < sizeof(DEV_BROADCAST_HANDLE32)) { Status = CR_INVALID_DATA; goto Clean0; } memset(&filter64, 0, sizeof(DEV_BROADCAST_HANDLE64)); filter64.dbch_size = sizeof(DEV_BROADCAST_HANDLE64); filter64.dbch_devicetype = DBT_DEVTYP_HANDLE; filter64.dbch_handle = (ULONG64)filter32->dbch_handle; // // use the converted 64-bit filter and size from now on, instead // of the caller supplied 32-bit filter. // filter = (PDEV_BROADCAST_HANDLE)&filter64; ulSize = sizeof(DEV_BROADCAST_HANDLE64); } #endif // _WIN64 // // Validate the input filter data // if (filter->dbch_size < sizeof(DEV_BROADCAST_HANDLE) || ulSize < sizeof(DEV_BROADCAST_HANDLE)) { Status = CR_INVALID_DATA; goto Clean0; } // // The DEVICE_NOTIFY_INCLUDE_ALL_INTERFACE_CLASSES flag is only // valid for the DBT_DEVTYP_DEVICEINTERFACE notification filter // type. // if ((Flags & DEVICE_NOTIFY_PROPERTY_MASK) & DEVICE_NOTIFY_ALL_INTERFACE_CLASSES) { Status = CR_INVALID_FLAG; goto Clean0; } entry = HeapAlloc(ghPnPHeap, 0, sizeof(PNP_NOTIFY_ENTRY)); if (entry == NULL) { Status = CR_OUT_OF_MEMORY; goto Clean0; } // // Find the device id that corresponds to this file handle. // In this case, use a duplicated instance of the file handle // for this process, not the caller's process. // rpcStatus = RpcImpersonateClient(hBinding); if (rpcStatus != RPC_S_OK) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: RpcImpersonateClient failed, error = %d\n", rpcStatus)); Status = CR_FAILURE; HeapFree(ghPnPHeap, 0, entry); goto Clean0; } hProcess = OpenProcess(PROCESS_DUP_HANDLE, FALSE, ProcessId); if (hProcess == NULL) { // // Last error set by OpenProcess routine // Status = CR_FAILURE; HeapFree(ghPnPHeap, 0, entry); rpcStatus = RpcRevertToSelf(); if (rpcStatus != RPC_S_OK) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: RpcRevertToSelf failed, error = %d\n", rpcStatus)); ASSERT(rpcStatus == RPC_S_OK); } goto Clean0; } if (!DuplicateHandle(hProcess, (HANDLE)filter->dbch_handle, GetCurrentProcess(), &localHandle, 0, FALSE, DUPLICATE_SAME_ACCESS)) { // // Last error set by DuplicateHandle routine // Status = CR_FAILURE; HeapFree(ghPnPHeap, 0, entry); CloseHandle(hProcess); rpcStatus = RpcRevertToSelf(); if (rpcStatus != RPC_S_OK) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: RpcRevertToSelf failed, error = %d\n", rpcStatus)); ASSERT(rpcStatus == RPC_S_OK); } goto Clean0; } rpcStatus = RpcRevertToSelf(); if (rpcStatus != RPC_S_OK) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: RpcRevertToSelf failed, error = %d\n", rpcStatus)); ASSERT(rpcStatus == RPC_S_OK); } memset(&controlData, 0 , sizeof(PLUGPLAY_CONTROL_TARGET_RELATION_DATA)); controlData.UserFileHandle = localHandle; controlData.DeviceInstance = entry->u.Target.DeviceId; controlData.DeviceInstanceLen = sizeof(entry->u.Target.DeviceId); ntStatus = NtPlugPlayControl(PlugPlayControlTargetDeviceRelation, &controlData, sizeof(controlData)); CloseHandle(localHandle); CloseHandle(hProcess); if (!NT_SUCCESS(ntStatus)) { Status = MapNtStatusToCmError(ntStatus); HeapFree(ghPnPHeap, 0, entry); goto Clean0; } // // Sanitize the device id // FixUpDeviceId(entry->u.Target.DeviceId); // // Copy the client name for the window or service, supplied by // ServiceName. The maximum service name buffer length required for // services is MAX_SERVICE_NAME_LEN (256 characters), which should // be a reasonable limit for both. // if (ARGUMENT_PRESENT(ServiceName)) { HRESULT hr; size_t ServiceNameLen = 0; hr = StringCchLength(ServiceName, MAX_SERVICE_NAME_LEN, &ServiceNameLen); if (FAILED(hr)) { ServiceNameLen = MAX_SERVICE_NAME_LEN - 1; } entry->ClientName = (LPWSTR)HeapAlloc( ghPnPHeap, 0, (ServiceNameLen+1)*sizeof(WCHAR)); if (entry->ClientName == NULL) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_WARNINGS, "UMPNPMGR: PNP_RegisterNotification " "failed to allocate memory for ClientName!\n")); Status = CR_OUT_OF_MEMORY; HeapFree (ghPnPHeap,0,entry); goto Clean0; } // // Copy to the allocated buffer, truncating if necessary. // hr = StringCchCopy(entry->ClientName, ServiceNameLen + 1, ServiceName); ASSERT(SUCCEEDED(hr)); } else { entry->ClientName = NULL; } // // Resolve the service status handle from the supplied service name. // if ((Flags & DEVICE_NOTIFY_HANDLE_MASK) == DEVICE_NOTIFY_SERVICE_HANDLE) { hRecipient = (ULONG_PTR)NULL; if ((pSCMAuthenticate != NULL) && (ARGUMENT_PRESENT(ServiceName))) { SERVICE_STATUS_HANDLE serviceHandle; if (pSCMAuthenticate(ServiceName, &serviceHandle) == NO_ERROR) { hRecipient = (ULONG_PTR)serviceHandle; } } if (!hRecipient) { Status = CR_INVALID_DATA; if (entry->ClientName) { HeapFree(ghPnPHeap, 0, entry->ClientName); } HeapFree(ghPnPHeap, 0, entry); *Context = NULL; goto Clean0; } } // // Add this entry to the target list // entry->Signature = TARGET_ENTRY_SIGNATURE; entry->Handle = (HANDLE)hRecipient; entry->Flags = Flags; entry->Unregistered = FALSE; entry->Freed = 0; entry->SessionId = ulSessionId; // // Save the caller's file handle (to pass back to caller // during notification). // entry->u.Target.FileHandle = filter->dbch_handle; EnterCriticalSection(&RegistrationCS); bCritSecHeld = TRUE; if (gNotificationInProg != 0) { // // If a notification is happening, add this entry to the list of // deferred registrations. // PPNP_DEFERRED_LIST regNode; regNode = (PPNP_DEFERRED_LIST) HeapAlloc(ghPnPHeap, 0, sizeof (PNP_DEFERRED_LIST)); if (!regNode) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_WARNINGS, "UMPNPMGR: Error allocating deferred list entry during registration!\n")); Status = CR_OUT_OF_MEMORY; if (entry->ClientName) { HeapFree(ghPnPHeap, 0, entry->ClientName); } HeapFree(ghPnPHeap, 0, entry); LeaveCriticalSection(&RegistrationCS); bCritSecHeld = FALSE; goto Clean0; } // // Do not notify this entry until after the current // notification is finished. // entry->Unregistered = TRUE; regNode->hBinding = 0; regNode->Entry = entry; regNode->Next = RegisterList; RegisterList = regNode; } hashValue = HashString(entry->u.Target.DeviceId, TARGET_HASH_BUCKETS); notifyList = &TargetList[hashValue]; MarkEntryWithList(entry,hashValue); LockNotifyList(¬ifyList->Lock); bLocked = TRUE; AddNotifyEntry(&TargetList[hashValue], entry); entry->ClientCtxPtr = (ULONG64)*ClientContext; *Context = entry; UnlockNotifyList(¬ifyList->Lock); bLocked = FALSE; LeaveCriticalSection(&RegistrationCS); bCritSecHeld = FALSE; break; } case DBT_DEVTYP_DEVICEINTERFACE: { DEV_BROADCAST_DEVICEINTERFACE UNALIGNED *filter = (PDEV_BROADCAST_DEVICEINTERFACE)NotificationFilter; // // Validate the input filter data // if (filter->dbcc_size < sizeof(DEV_BROADCAST_DEVICEINTERFACE) || ulSize < sizeof (DEV_BROADCAST_DEVICEINTERFACE) ) { Status = CR_INVALID_DATA; goto Clean0; } // // We no longer support the private GUID_DEVNODE_CHANGE interface so return // CR_INVALID_DATA if this GUID is passed in. // if (GuidEqual(&GUID_DEVNODE_CHANGE, &filter->dbcc_classguid)) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_WARNINGS, "UMPNPMGR: RegisterDeviceNotification using GUID_DEVNODE_CHANGE is not supported!\n")); Status = CR_INVALID_DATA; goto Clean0; } // // The GUID_DEVINTERFACE_INCLUDE_ALL_INTERFACE_CLASSES interface is // not supported directly. It is for internal use only. Return // CR_INVALID_DATA if this GUID is passed in. // if (GuidEqual(&GUID_DEVINTERFACE_INCLUDE_ALL_INTERFACE_CLASSES, &filter->dbcc_classguid)) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_WARNINGS, "UMPNPMGR: RegisterDeviceNotification using this class GUID is not supported!\n")); Status = CR_INVALID_DATA; goto Clean0; } entry = HeapAlloc(ghPnPHeap, 0, sizeof(PNP_NOTIFY_ENTRY)); if (entry == NULL) { Status = CR_OUT_OF_MEMORY; goto Clean0; } // // Copy the client name for the window or service, supplied by // ServiceName. The maximum service name buffer length required for // services is MAX_SERVICE_NAME_LEN (256 characters), which should // be a reasonable limit for both. // if (ARGUMENT_PRESENT(ServiceName)) { HRESULT hr; size_t ServiceNameLen = 0; hr = StringCchLength(ServiceName, MAX_SERVICE_NAME_LEN, &ServiceNameLen); if (FAILED(hr)) { ServiceNameLen = MAX_SERVICE_NAME_LEN - 1; } entry->ClientName = (LPWSTR)HeapAlloc( ghPnPHeap, 0, (ServiceNameLen+1)*sizeof(WCHAR)); if (entry->ClientName == NULL) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_WARNINGS, "UMPNPMGR: PNP_RegisterNotification " "failed to allocate memory for ClientName!\n")); Status = CR_OUT_OF_MEMORY; HeapFree (ghPnPHeap,0,entry); goto Clean0; } // // Copy to the allocated buffer, truncating if necessary. // hr = StringCchCopy(entry->ClientName, ServiceNameLen + 1, ServiceName); ASSERT(SUCCEEDED(hr)); } else { entry->ClientName = NULL; } // // Resolve the service status handle from the supplied service name. // if ((Flags & DEVICE_NOTIFY_HANDLE_MASK) == DEVICE_NOTIFY_SERVICE_HANDLE) { hRecipient = (ULONG_PTR)NULL; if (pSCMAuthenticate && ServiceName) { SERVICE_STATUS_HANDLE serviceHandle; if (pSCMAuthenticate(ServiceName, &serviceHandle) == NO_ERROR) { hRecipient = (ULONG_PTR)serviceHandle; } } if (!hRecipient) { Status = CR_INVALID_DATA; if (entry->ClientName) { HeapFree(ghPnPHeap, 0, entry->ClientName); } HeapFree(ghPnPHeap, 0, entry); *Context = NULL; goto Clean0; } } // // Add this entry to the class list // entry->Signature = CLASS_ENTRY_SIGNATURE; entry->Handle = (HANDLE)hRecipient; entry->Flags = Flags; entry->Unregistered = FALSE; entry->Freed = 0; entry->SessionId = ulSessionId; // // If the caller is registering for all interface class events, // ignore the caller supplied class GUID and use a private GUID. // Otherwise, copy the caller supplied GUID to the notification list // entry. // if ((Flags & DEVICE_NOTIFY_PROPERTY_MASK) & DEVICE_NOTIFY_ALL_INTERFACE_CLASSES) { memcpy(&entry->u.Class.ClassGuid, &GUID_DEVINTERFACE_INCLUDE_ALL_INTERFACE_CLASSES, sizeof(GUID)); } else { memcpy(&entry->u.Class.ClassGuid, &filter->dbcc_classguid, sizeof(GUID)); } EnterCriticalSection(&RegistrationCS); bCritSecHeld = TRUE; if (gNotificationInProg != 0) { // // If a notification is happening, add this entry to the list of // deferred registrations. // PPNP_DEFERRED_LIST regNode; regNode = (PPNP_DEFERRED_LIST) HeapAlloc(ghPnPHeap, 0, sizeof (PNP_DEFERRED_LIST)); if (!regNode) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_WARNINGS, "UMPNPMGR: Error allocating deferred list entry during registration!\n")); Status = CR_OUT_OF_MEMORY; if (entry->ClientName) { HeapFree(ghPnPHeap, 0, entry->ClientName); } HeapFree(ghPnPHeap, 0, entry); LeaveCriticalSection(&RegistrationCS); bCritSecHeld = FALSE; goto Clean0; } // // Do not notify this entry until after the current // notification is finished. // entry->Unregistered = TRUE; regNode->hBinding = 0; regNode->Entry = entry; regNode->Next = RegisterList; RegisterList = regNode; } hashValue = HashClassGuid(&entry->u.Class.ClassGuid); notifyList = &ClassList[hashValue]; MarkEntryWithList(entry,hashValue); LockNotifyList(¬ifyList->Lock); bLocked = TRUE; AddNotifyEntry(&ClassList[hashValue],entry); entry->ClientCtxPtr = (ULONG64)*ClientContext; *Context = entry; UnlockNotifyList(¬ifyList->Lock); bLocked = FALSE; LeaveCriticalSection(&RegistrationCS); bCritSecHeld = FALSE; break; } default: Status = CR_INVALID_DATA; goto Clean0; } Clean0: NOTHING; } except(EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_EVENT, "UMPNPMGR: Exception in PNP_RegisterNotification\n")); ASSERT(0); Status = CR_FAILURE; if (bLocked) { UnlockNotifyList(¬ifyList->Lock); } if (bCritSecHeld) { LeaveCriticalSection(&RegistrationCS); } } return Status; } // PNP_RegisterNotification CONFIGRET PNP_UnregisterNotification( IN handle_t hBinding, IN PPNP_NOTIFICATION_CONTEXT Context ) /*++ Routine Description: This routine is the rpc server-side of the CMP_UnregisterNotification routine. It performs the remaining parameter validation and unregisters the corresponding notification entry. Arguments: hBinding - RPC binding handle (not used). Context - Contains the address of a HDEVNOTIFY notification handle that was supplied when this notification request was registered. Note that when the server context is freed, this context handle must be set to NULL. Return Value: Return CR_SUCCESS if the function succeeds, otherwise it returns one of the CR_* errors. Notes: Note that the Context comes in as a PNP_NOTIFICATION_CONTEXT pointer It is NOT one of those. The case is correct. This is to work around RPC and user. This RPC server interface is used by local RPC clients only; it is never called remotely. --*/ { CONFIGRET Status = CR_SUCCESS; ULONG hashValue = 0; PPNP_DEFERRED_LIST unregNode; PPNP_NOTIFY_LIST notifyList = NULL; BOOLEAN bLocked = FALSE; // // This routine only services requests from local RPC clients. // if (!IsClientLocal(hBinding)) { return CR_ACCESS_DENIED; } try { // // validate notification handle // PPNP_NOTIFY_ENTRY entry = (PPNP_NOTIFY_ENTRY)*Context; EnterCriticalSection (&RegistrationCS); if (entry == NULL) { Status = CR_INVALID_DATA; goto Clean0; } if (gNotificationInProg != 0) { if (RegisterList) { // // Check to see if this entry is in the deferred RegisterList. // PPNP_DEFERRED_LIST currReg,prevReg; currReg = RegisterList; prevReg = NULL; while (currReg) { // // Entries in the deferred RegisterList are to be skipped // during notification. // ASSERT(currReg->Entry->Unregistered); if (currReg->Entry == entry) { // // Remove this entry from the deferred RegisterList. // if (prevReg) { prevReg->Next = currReg->Next; } else { RegisterList = currReg->Next; } HeapFree(ghPnPHeap, 0, currReg); if (prevReg) { currReg = prevReg->Next; } else { currReg = RegisterList; } } else { prevReg = currReg; currReg = currReg->Next; } } } switch (entry->Signature & LIST_ENTRY_SIGNATURE_MASK) { case CLASS_ENTRY_SIGNATURE: case TARGET_ENTRY_SIGNATURE: { unregNode = (PPNP_DEFERRED_LIST) HeapAlloc(ghPnPHeap, 0, sizeof (PNP_DEFERRED_LIST)); if (!unregNode) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_WARNINGS, "UMPNPMGR: Error allocating deferred list entry during unregistration!\n")); Status = CR_OUT_OF_MEMORY; goto Clean0; } // // This param is not used, if this changes, change this line too. // unregNode->hBinding= 0; notifyList = GetNotifyListForEntry(entry); if (notifyList) { // // The entry is part of a notification list, so make // sure not to notify on it. // LockNotifyList(¬ifyList->Lock); bLocked = TRUE; entry->Unregistered = TRUE; UnlockNotifyList(¬ifyList->Lock); bLocked = FALSE; } unregNode->Entry = entry; unregNode->Next = UnregisterList; UnregisterList = unregNode; *Context = NULL; break; } default: Status = CR_INVALID_DATA; KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_WARNINGS | DBGF_ERRORS, "UMPNPMGR: PNP_UnregisterNotification: invalid signature on entry at %x\n", entry)); break; } goto Clean0; } // // Free the notification entry from the appropriate list. // switch (entry->Signature & LIST_ENTRY_SIGNATURE_MASK) { case CLASS_ENTRY_SIGNATURE: hashValue = HashClassGuid(&entry->u.Class.ClassGuid); notifyList = &ClassList[hashValue]; LockNotifyList(¬ifyList->Lock); bLocked = TRUE; entry->Freed |= (PNP_UNREG_FREE|PNP_UNREG_CLASS); DeleteNotifyEntry(entry,TRUE); UnlockNotifyList(¬ifyList->Lock); bLocked = FALSE; *Context = NULL; break; case TARGET_ENTRY_SIGNATURE: hashValue = HashString(entry->u.Target.DeviceId, TARGET_HASH_BUCKETS); notifyList = &TargetList[hashValue]; LockNotifyList(¬ifyList->Lock); bLocked = TRUE; entry->Freed |= (PNP_UNREG_FREE|PNP_UNREG_TARGET); DeleteNotifyEntry(entry,TRUE); UnlockNotifyList(¬ifyList->Lock); bLocked = FALSE; *Context = NULL; break; default: Status = CR_INVALID_DATA; KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_WARNINGS | DBGF_ERRORS, "UMPNPMGR: PNP_UnregisterNotification: invalid signature on entry at %x\n", entry)); } Clean0: LeaveCriticalSection(&RegistrationCS); } except(EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_EVENT, "UMPNPMGR: PNP_UnregisterNotification caused an exception!\n")); ASSERT(0); SetLastError(ERROR_EXCEPTION_IN_SERVICE); Status = CR_FAILURE; if (bLocked) { UnlockNotifyList(¬ifyList->Lock); } LeaveCriticalSection(&RegistrationCS); } return Status; } // PNP_UnregisterNotification //----------------------------------------------------------------------------- // Dynamic Event Notification Support //----------------------------------------------------------------------------- DWORD ThreadProc_DeviceEvent( LPDWORD lpParam ) /*++ Routine Description: This routine is a thread procedure. This thread handles all device event notification from kernel-mode. Arguments: lpParam - Not used. Return Value: Currently returns TRUE/FALSE. --*/ { DWORD status = TRUE, result = 0; NTSTATUS ntStatus = STATUS_SUCCESS; PPLUGPLAY_EVENT_BLOCK eventBlock = NULL; ULONG totalSize, variableSize; BOOL notDone = TRUE; PVOID p = NULL; PNP_VETO_TYPE vetoType; WCHAR vetoName[MAX_VETO_NAME_LENGTH]; ULONG vetoNameLength; PLUGPLAY_CONTROL_USER_RESPONSE_DATA userResponse; PPNP_NOTIFY_LIST notifyList = NULL; PPNP_DEFERRED_LIST reg,regFree,unreg,unregFree,rundown,rundownFree; UNREFERENCED_PARAMETER(lpParam); try { // // Initialize event buffer used to pass info back from kernel-mode in. // variableSize = 4096 - sizeof(PLUGPLAY_EVENT_BLOCK); totalSize = sizeof(PLUGPLAY_EVENT_BLOCK) + variableSize; eventBlock = (PPLUGPLAY_EVENT_BLOCK)HeapAlloc(ghPnPHeap, 0, totalSize); if (eventBlock == NULL) { LogErrorEvent(ERR_ALLOCATING_EVENT_BLOCK, ERROR_NOT_ENOUGH_MEMORY, 0); SetLastError(ERROR_NOT_ENOUGH_MEMORY); status = FALSE; ASSERT(0); goto Clean0; } // // Retrieve device events synchronously (this is more efficient // than using apcs). // while (notDone) { ntStatus = NtGetPlugPlayEvent(NULL, NULL, // Context eventBlock, totalSize); if (ntStatus == STATUS_BUFFER_TOO_SMALL) { // // Kernel-mode side couldn't transfer the event because // my buffer is too small, realloc and attempt to retrieve // the event again. // variableSize += 1024; totalSize = variableSize + sizeof(PLUGPLAY_EVENT_BLOCK); p = HeapReAlloc(ghPnPHeap, 0, eventBlock, totalSize); if (p == NULL) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Couldn't reallocate event block to size %d\n", totalSize)); LogErrorEvent(ERR_ALLOCATING_EVENT_BLOCK, ERROR_NOT_ENOUGH_MEMORY, 0); SetLastError(ERROR_NOT_ENOUGH_MEMORY); status = FALSE; ASSERT(0); goto Clean0; } eventBlock = (PPLUGPLAY_EVENT_BLOCK)p; } if (ntStatus == STATUS_SUCCESS) { // // An event was retrieved, process it. // gNotificationInProg = 1; vetoType = PNP_VetoTypeUnknown; vetoName[0] = L'\0'; vetoNameLength = MAX_VETO_NAME_LENGTH; try { // // Process the device event. // result = ProcessDeviceEvent(eventBlock, totalSize, &vetoType, vetoName, &vetoNameLength); } except(EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_EVENT, "UMPNPMGR: Exception in ProcessDeviceEvent!\n")); ASSERT(0); // // An exception while processing the event should not be // considered a failure of the event itself. // result = TRUE; vetoType = PNP_VetoTypeUnknown; vetoName[0] = L'\0'; vetoNameLength = 0; } ASSERT(vetoNameLength < MAX_VETO_NAME_LENGTH && vetoName[vetoNameLength] == L'\0'); // // Notify kernel-mode of the user-mode result. // userResponse.Response = result; userResponse.VetoType = vetoType; userResponse.VetoName = vetoName; userResponse.VetoNameLength = vetoNameLength; NtPlugPlayControl(PlugPlayControlUserResponse, &userResponse, sizeof(userResponse)); EnterCriticalSection (&RegistrationCS); if (RegisterList != NULL) { // // Complete Registrations requested during notification. // reg = RegisterList; RegisterList=NULL; } else { reg = NULL; } if (UnregisterList != NULL) { // // Complete Unregistrations requested during notification. // unreg = UnregisterList; UnregisterList = NULL; } else { unreg = NULL; } if (RundownList != NULL) { // // Complete Unregistrations requested during notification. // rundown = RundownList; RundownList = NULL; } else { rundown = NULL; } gNotificationInProg = 0; while (reg) { // // This entry has already been added to the appropriate // notification list. Allow this entry to receive // notifications. // notifyList = GetNotifyListForEntry(reg->Entry); ASSERT(notifyList); if (notifyList) { LockNotifyList(¬ifyList->Lock); } reg->Entry->Unregistered = FALSE; if (notifyList) { UnlockNotifyList(¬ifyList->Lock); } // // Remove the entry from the deferred registration list. // regFree = reg; reg = reg->Next; HeapFree(ghPnPHeap, 0, regFree); } while (unreg) { PNP_UnregisterNotification(unreg->hBinding,&unreg->Entry); // // Remove the entry from the deferred unregistration list. // unregFree = unreg; unreg = unreg->Next; HeapFree(ghPnPHeap, 0, unregFree); } while (rundown) { PNP_NOTIFICATION_CONTEXT_rundown(rundown->Entry); // // Remove the entry from the deferred rundown list. // rundownFree = rundown; rundown = rundown->Next; HeapFree(ghPnPHeap, 0, rundownFree); } LeaveCriticalSection(&RegistrationCS); } if (ntStatus == STATUS_NOT_IMPLEMENTED) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: NtGetPlugPlayEvent returned STATUS_NOT_IMPLEMENTED\n")); ASSERT(FALSE); } if (ntStatus == STATUS_USER_APC) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: ThreadProc_DeviceEvent exiting on STATUS_USER_APC\n")); ASSERT(FALSE); } } Clean0: NOTHING; } except(EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_EVENT, "UMPNPMGR: Exception in ThreadProc_DeviceEvent!\n")); ASSERT(0); status = FALSE; // // Reference the following variable so the compiler will respect // statement ordering w.r.t. its assignment. // eventBlock = eventBlock; } KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_EVENT, "UMPNPMGR: Exiting ThreadProc_DeviceEvent!!!!\n")); TermNotification(); if (eventBlock != NULL) { HeapFree(ghPnPHeap, 0, eventBlock); } return status; } // ThreadProc_DeviceEvent BOOL InitNotification( VOID ) /*++ Routine Description: This routine allocates and initializes notification lists, etc. Arguments: Not used. Return Value: Currently returns TRUE/FALSE. --*/ { ULONG i; // // Initialize the interface device (class) list // memset(ClassList, 0, sizeof(PNP_NOTIFY_LIST) * CLASS_GUID_HASH_BUCKETS); for (i = 0; i < CLASS_GUID_HASH_BUCKETS; i++) { ClassList[i].Next = NULL; ClassList[i].Previous = NULL; InitPrivateResource(&ClassList[i].Lock); } // // Initialize the target device list // memset(TargetList, 0, sizeof(PNP_NOTIFY_LIST) * TARGET_HASH_BUCKETS); for (i = 0; i < TARGET_HASH_BUCKETS; i++) { TargetList[i].Next = NULL; TargetList[i].Previous = NULL; InitPrivateResource(&TargetList[i].Lock); } // // Initialize the install list // InstallList.Next = NULL; InitPrivateResource(&InstallList.Lock); // // Initialize the install client list // InstallClientList.Next = NULL; InitPrivateResource(&InstallClientList.Lock); // // Initialize the lock for user token access // InitPrivateResource(&gTokenLock); // // Initialize the service handle list // memset(ServiceList, 0, sizeof(PNP_NOTIFY_LIST) * SERVICE_NUM_CONTROLS); for (i = 0; i < SERVICE_NUM_CONTROLS; i++) { ServiceList[i].Next = NULL; ServiceList[i].Previous = NULL; InitPrivateResource(&ServiceList[i].Lock); } // // Initialize Registration/Unregistration CS. // try { InitializeCriticalSection(&RegistrationCS); } except(EXCEPTION_EXECUTE_HANDLER) { return FALSE; } // // Initialize deferred Registration/Unregistration lists. // RegisterList = NULL; UnregisterList = NULL; RundownList = NULL; // // Initialize gNotificationInProg flag. // gNotificationInProg = 0; return TRUE; } // InitNotification VOID TermNotification( VOID ) /*++ Routine Description: This routine frees notification resources. Arguments: Not used. Return Value: No return. --*/ { ULONG i; // // Free the interface device (class) list locks // for (i = 0; i < CLASS_GUID_HASH_BUCKETS; i++) { if (LockNotifyList(&ClassList[i].Lock)) { DestroyPrivateResource(&ClassList[i].Lock); } } // // Free the target device list locks // for (i = 0; i < TARGET_HASH_BUCKETS; i++) { if (LockNotifyList(&TargetList[i].Lock)) { DestroyPrivateResource(&TargetList[i].Lock); } } // // Free the service notification list locks // for (i = 0; i < SERVICE_NUM_CONTROLS; i++) { if (LockNotifyList(&ServiceList[i].Lock)) { DestroyPrivateResource(&ServiceList[i].Lock); } } // // Free the install list lock // if (LockNotifyList(&InstallList.Lock)) { DestroyPrivateResource(&InstallList.Lock); } // // Free the lock for user token access // if (LockNotifyList(&gTokenLock)) { DestroyPrivateResource(&gTokenLock); } // // Free the install client list lock // if (LockNotifyList(&InstallClientList.Lock)) { DestroyPrivateResource(&InstallClientList.Lock); } // // Close the handle to winsta.dll // if (ghWinStaLib) { fpWinStationSendWindowMessage = NULL; fpWinStationBroadcastSystemMessage = NULL; FreeLibrary(ghWinStaLib); ghWinStaLib = NULL; } // // Close the handle to wtsapi32.dll // if (ghWtsApi32Lib) { fpWTSQuerySessionInformation = NULL; fpWTSFreeMemory = NULL; FreeLibrary(ghWtsApi32Lib); ghWtsApi32Lib = NULL; } return; } // TermNotification ULONG ProcessDeviceEvent( IN PPLUGPLAY_EVENT_BLOCK EventBlock, IN DWORD EventBufferSize, OUT PPNP_VETO_TYPE VetoType, OUT LPWSTR VetoName, IN OUT PULONG VetoNameLength ) /*++ Routine Description: This routine processes device events recieved from the kernel-mode pnp manager. Arguments: EventBlock - contains the event data. EventBlockSize - specifies the size (in bytes) of EventBlock. Return Value: Returns FALSE if unsuccessful, or in the case of a vetoed query event. Returns TRUE otherwise. Notes: This routine takes part in translating kernel mode PnP events into user mode notifications. Currently, the notification code is dispersed and duplicated throughout several routines. All notifications can be said to have the following form though: result = DeliverMessage( MessageFlags, // [MSG_POST, MSG_SEND, MSG_QUERY] | // [MSG_WALK_LIST_FORWARD, MSG_WALK_LIST_BACKWARDS] Target, // A local window handle, hydra window handle (with // session ID), service handle, or "broadcast". // Better yet, it could take lists... wParam, // DBT_* (or corresponding SERVICE_CONTROL_* message) lParam, // Appropriate data (note: user has hardcoded knowledge // about these via DBT_ type). queueTimeout, // Exceeded if there exists messages in the queue but // no message has been drained in the given time. Note // that this means a message can fail immediately. responseTimeout, // Exceeded if *this* message has not been processed in // the elasped time. VetoName, // For queries, the name of the vetoer. VetoType // Type of vetoer component (window, service, ...) ); DeviceEventWorker implements targeted sends and posts (normal exported Win32 API cannot be used as they won't reach other desktops). Currently User32 does not allow posts of DBT_* messages with lParam data, mainly because a caller might send the message to itself, in which case no copy is made. This in theory presents the caller with no opportunity to free that data (note that this scenario would never occur with UmPnpMgr however, as we have no WndProc). User implements this function with a fixed responseTimeout of thirty seconds. This API can but should not be used for broadcasts. WinStationSendWindowMessage sends messages to windows within Hydra clients on a machine. There is no corresponding WinStationPostWindowMessage. All the code in this component passes a ResponseTimeout of five seconds. There is no queueTimeout. BroadcastSystemMessage implements broadcasts to all applications and desktops in the non-console (ie non-Hydra) session. As with DeviceEventWorker, User32 does not allow posts of DBT_* messages with lParam data (regardless of whether you pass in BSF_IGNORECURRENTTASK). All code in this component passes a ResponseTimeout of thirty seconds. QueueTimeout is optional, fixed five seconds. ResponseTimeout cannot be specified, but the maximum value would be five seconds per top level window. There is no information returned on which window vetoed a query. WinStationBroadcastSystemMessage broadcasts to all applications and desktops on a given machine's Hydra sessions. No posts of any kind may be done through this API. All code in this component passes a ResponseTimeout of five seconds. QueueTimeout is an optional, fixed five seconds. There is no information on which window vetoed a query. ServiceControlCallback sends messages to registered services. There is no posting or timeout facilities of any kind. Actually, each queued registration entry should be queued with a callback. We implement the callback, and there it hides the underlying complexities. --*/ { DWORD eventId, serviceControl, flags, status = TRUE; LPWSTR p = NULL; ULONG vetoNameSize; ULONG ulLength, ulCustomDataLength, ulClientSessionId, ulHotplugFlags; HRESULT hr; UNREFERENCED_PARAMETER(EventBufferSize); // // Validate parameters // ASSERT(EventBlock->TotalSize >= sizeof(PLUGPLAY_EVENT_BLOCK)); if (EventBlock->TotalSize < sizeof(PLUGPLAY_EVENT_BLOCK)) { return FALSE; } // // Convert the event guid into a dbt style event id. // if (!EventIdFromEventGuid(&EventBlock->EventGuid, &eventId, &flags, &serviceControl)) { if (VetoNameLength != NULL) { *VetoNameLength = 0; } return FALSE; } if (VetoNameLength != NULL && !((EventBlock->EventCategory == TargetDeviceChangeEvent) || (EventBlock->EventCategory == CustomDeviceEvent) || (EventBlock->EventCategory == HardwareProfileChangeEvent) || (EventBlock->EventCategory == PowerEvent) ) ){ *VetoNameLength = 0; } vetoNameSize = *VetoNameLength; switch (EventBlock->EventCategory) { case TargetDeviceChangeEvent: case VetoEvent: case BlockedDriverEvent: case InvalidIDEvent: if (IsFastUserSwitchingEnabled()) { ulHotplugFlags = HOTPLUG_DISPLAY_ON_CONSOLE; ulClientSessionId = INVALID_SESSION; } else { ulHotplugFlags = 0; ulClientSessionId = MAIN_SESSION; } break; default: ulHotplugFlags = 0; ulClientSessionId = INVALID_SESSION; break; } // // Notify registered callers first (class changes will also send generic // broadcast if the type is volume or port). // switch (EventBlock->EventCategory) { case CustomDeviceEvent: { // // Convert the pnp event block into a dbt style structure. // PDEV_BROADCAST_HANDLE pNotify; PLUGPLAY_CUSTOM_NOTIFICATION *pTarget; if (*EventBlock->u.CustomNotification.DeviceIds == L'\0') { // // There are no device IDs, can't do notification in this case // just return // if (VetoNameLength != NULL) { *VetoNameLength = 0; } KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Ignoring CustomDeviceEvent with no Device IDs\n")); return FALSE; } // // Custom events should always be this GUID, and that guid should always // be converted into the below eventId. // ASSERT(GuidEqual(&EventBlock->EventGuid, &GUID_PNP_CUSTOM_NOTIFICATION)); ASSERT(eventId == DBT_CUSTOMEVENT); // // Handle and Marshall the custom notification. // // // The amount of space allocated for the EventBlock + IDs is always a // multiple of sizeof(PVOID) in order to keep the notification structure // aligned. // ulLength = sizeof(PLUGPLAY_EVENT_BLOCK) + (lstrlen(EventBlock->u.CustomNotification.DeviceIds) + 1) * sizeof(WCHAR); ulLength += sizeof(PVOID) - 1; ulLength &= ~(sizeof(PVOID) - 1); // // The notification structure follows the Event Block and IDs // pTarget = (PPLUGPLAY_CUSTOM_NOTIFICATION)((PUCHAR)EventBlock + ulLength); ulCustomDataLength = pTarget->HeaderInfo.Size - FIELD_OFFSET(PLUGPLAY_CUSTOM_NOTIFICATION,CustomDataBuffer); pNotify = HeapAlloc(ghPnPHeap, 0, sizeof(DEV_BROADCAST_HANDLE) + ulCustomDataLength); if (pNotify == NULL) { LogErrorEvent(ERR_ALLOCATING_NOTIFICATION_STRUCTURE, ERROR_NOT_ENOUGH_MEMORY, 0); status = FALSE; break; } memset(pNotify, 0, sizeof(DEV_BROADCAST_HANDLE) + ulCustomDataLength); pNotify->dbch_size = sizeof(DEV_BROADCAST_HANDLE) + ulCustomDataLength; pNotify->dbch_devicetype = DBT_DEVTYP_HANDLE; pNotify->dbch_nameoffset = pTarget->NameBufferOffset; pNotify->dbch_eventguid = pTarget->HeaderInfo.Event; memcpy( pNotify->dbch_data, pTarget->CustomDataBuffer, ulCustomDataLength); *VetoNameLength = vetoNameSize; status = NotifyTargetDeviceChange( serviceControl, eventId, flags, pNotify, EventBlock->u.CustomNotification.DeviceIds, VetoType, VetoName, VetoNameLength); if (GuidEqual(&pNotify->dbch_eventguid, (LPGUID)&GUID_IO_VOLUME_NAME_CHANGE)) { // // Broadcast compatible volume removal and arrival notifications // (if any) after the custom name change event has been sent to // all recipients. // BroadcastVolumeNameChange(); } HeapFree(ghPnPHeap, 0, pNotify); break; } case TargetDeviceChangeEvent: { // // Convert the pnp event block into a dbt style structure. // PDEV_BROADCAST_HANDLE pNotify; if (*EventBlock->u.TargetDevice.DeviceIds == L'\0') { // // There are no device IDs, can't do notification in this case // just return // if (VetoNameLength != NULL) { *VetoNameLength = 0; } KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Ignoring TargetDeviceChangeEvent with no Device IDs\n")); return FALSE; } // // If this is a surprise removal event then call HOTPLUG.DLL to display // a warning to the user before sending this event to other apps. // if (GuidEqual(&EventBlock->EventGuid,&GUID_DEVICE_SAFE_REMOVAL)) { SendHotplugNotification( &EventBlock->EventGuid, NULL, EventBlock->u.TargetDevice.DeviceIds, &ulClientSessionId, ulHotplugFlags ); } else if (GuidEqual(&EventBlock->EventGuid, &GUID_DEVICE_KERNEL_INITIATED_EJECT)) { *VetoNameLength = vetoNameSize; status = CheckEjectPermissions( EventBlock->u.TargetDevice.DeviceIds, VetoType, VetoName, VetoNameLength ); } else if (GuidEqual(&EventBlock->EventGuid,&GUID_DEVICE_SURPRISE_REMOVAL)) { LogSurpriseRemovalEvent(EventBlock->u.TargetDevice.DeviceIds); #if 0 // We don't display surpise-removal bubbles anymore... SendHotplugNotification( &EventBlock->EventGuid, NULL, EventBlock->u.TargetDevice.DeviceIds, &ulClientSessionId, ulHotplugFlags ); #endif } if (eventId == 0) { // // Internal event, no broadcasting should be done. // if (VetoNameLength != NULL) { *VetoNameLength = 0; } break; } pNotify = HeapAlloc(ghPnPHeap, 0, sizeof(DEV_BROADCAST_HANDLE)); if (pNotify == NULL) { LogErrorEvent(ERR_ALLOCATING_BROADCAST_HANDLE, ERROR_NOT_ENOUGH_MEMORY, 0); status = FALSE; if (VetoNameLength != NULL) { *VetoNameLength = 0; } break; } memset(pNotify, 0, sizeof(DEV_BROADCAST_HANDLE)); pNotify->dbch_nameoffset = -1; // empty except for custom events pNotify->dbch_size = sizeof(DEV_BROADCAST_HANDLE); pNotify->dbch_devicetype = DBT_DEVTYP_HANDLE; for (p = EventBlock->u.TargetDevice.DeviceIds; *p; p += lstrlen(p) + 1) { *VetoNameLength = vetoNameSize; KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT, "UMPNPMGR: Processing TargetDeviceChangeEvent (0x%lx) for %ws\n", eventId, p)); status = NotifyTargetDeviceChange(serviceControl, eventId, flags, pNotify, p, VetoType, VetoName, VetoNameLength); if (!status && (flags & BSF_QUERY)) { LPWSTR pFail = p; DWORD dwCancelEventId; // // Use the appropriate cancel device event id that corresponds to the // original query device event id. // dwCancelEventId = MapQueryEventToCancelEvent(eventId); for (p = EventBlock->u.TargetDevice.DeviceIds; *p && p != pFail; p += lstrlen(p) + 1) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT, "UMPNPMGR: Processing TargetDeviceChangeEvent (0x%lx) for %ws\n", dwCancelEventId, p)); NotifyTargetDeviceChange( serviceControl, dwCancelEventId, BSF_NOHANG, pNotify, p, NULL, NULL, NULL); } break; } } HeapFree(ghPnPHeap, 0, pNotify); break; } case DeviceClassChangeEvent: { // // Convert the pnp event block into a dbt style structure. // PDEV_BROADCAST_DEVICEINTERFACE pNotify; ULONG ulSize; ulSize = sizeof(DEV_BROADCAST_DEVICEINTERFACE) + (lstrlen(EventBlock->u.DeviceClass.SymbolicLinkName) * sizeof(WCHAR)); KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT, "UMPNPMGR: Processing DeviceClassChangeEvent (0x%lx) for %ws\n", eventId, EventBlock->u.DeviceClass.SymbolicLinkName)); pNotify = HeapAlloc(ghPnPHeap, 0, ulSize); if (pNotify == NULL) { LogErrorEvent(ERR_ALLOCATING_BROADCAST_INTERFACE, ERROR_NOT_ENOUGH_MEMORY, 0); status = FALSE; break; } pNotify->dbcc_size = ulSize; pNotify->dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; pNotify->dbcc_reserved = 0; memcpy(&pNotify->dbcc_classguid, &EventBlock->u.DeviceClass.ClassGuid, sizeof(GUID)); hr = StringCbCopy(pNotify->dbcc_name, ulSize - sizeof(DEV_BROADCAST_DEVICEINTERFACE) + sizeof(WCHAR), EventBlock->u.DeviceClass.SymbolicLinkName); ASSERT(SUCCEEDED(hr)); if (FAILED(hr)) { status = FALSE; break; } // // Note: the symbolic link name is passed in kernel-mode format (\??\), // convert to user-mode format (\\?\) before sending notification. // Note that the only difference is the second character. // pNotify->dbcc_name[1] = L'\\'; status = NotifyInterfaceClassChange(serviceControl, eventId, flags, pNotify); break; } case HardwareProfileChangeEvent: KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT, "UMPNPMGR: Processing HardwareProfileChangeEvent (0x%lx)\n", eventId)); *VetoNameLength = vetoNameSize; status = NotifyHardwareProfileChange(serviceControl, eventId, flags, VetoType, VetoName, VetoNameLength); break; case PowerEvent: *VetoNameLength = vetoNameSize; // // Since all power events arrive under a single event GUID, // EventIdFromEventGuid cannot correctly determine the event id or query // flags from it. Instead, we get the event id directly from the device // event block, and add the BSF_QUERY flag here, if appropriate. // eventId = EventBlock->u.PowerNotification.NotificationCode; KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT, "UMPNPMGR: Processing PowerEvent (0x%lx)\n", eventId)); if ((eventId == PBT_APMQUERYSUSPEND) || (eventId == PBT_APMQUERYSTANDBY)) { flags |= BSF_QUERY; } else { flags &= ~BSF_QUERY; } status = NotifyPower(serviceControl, eventId, EventBlock->u.PowerNotification.NotificationData, flags, VetoType, VetoName, VetoNameLength); break; case VetoEvent: KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT, "UMPNPMGR: Processing VetoEvent\n")); status = SendHotplugNotification( &EventBlock->EventGuid, &EventBlock->u.VetoNotification.VetoType, EventBlock->u.VetoNotification.DeviceIdVetoNameBuffer, &ulClientSessionId, ulHotplugFlags ); break; case DeviceInstallEvent: { // // Initiate installation; we can't wait around here for a user, but // after installation is complete, kernel-mode will be notified // that they can attempt to start the device now. // PPNP_INSTALL_ENTRY entry = NULL, current = NULL; KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT, "UMPNPMGR: Processing DeviceInstallEvent for %ws\n", EventBlock->u.InstallDevice.DeviceId)); // // Device install events should always be this GUID, and that guid // should always be converted into the below eventId, serviceControl and // flags. // ASSERT(GuidEqual(&EventBlock->EventGuid, &GUID_DEVICE_ENUMERATED)); ASSERT((eventId == DBT_DEVICEARRIVAL) && (serviceControl == 0) && (flags == 0)); // // Allocate and initialize a new device install entry block. // entry = HeapAlloc(ghPnPHeap, 0, sizeof(PNP_INSTALL_ENTRY)); if (!entry) { break; } entry->Next = NULL; entry->Flags = 0; hr = StringCchCopy(entry->szDeviceId, MAX_DEVICE_ID_LEN, EventBlock->u.InstallDevice.DeviceId); ASSERT(SUCCEEDED(hr)); // // Insert this entry in the device install list. // LockNotifyList(&InstallList.Lock); current = (PPNP_INSTALL_ENTRY)InstallList.Next; if (current == NULL) { InstallList.Next = entry; } else { while ((PPNP_INSTALL_ENTRY)current->Next != NULL) { current = (PPNP_INSTALL_ENTRY)current->Next; } current->Next = entry; } UnlockNotifyList(&InstallList.Lock); SetEvent(InstallEvents[NEEDS_INSTALL_EVENT]); // // Generate a devnode changed message // NotifyTargetDeviceChange(serviceControl, eventId, flags, NULL, EventBlock->u.InstallDevice.DeviceId, NULL, NULL, NULL); break; } case BlockedDriverEvent: { LPGUID BlockedDriverGuid; PWSTR MultiSzGuidList = NULL; // // Display notification to the Console session that the system just // blocked a driver from loading on the system. // ASSERT(GuidEqual(&EventBlock->EventGuid, &GUID_DRIVER_BLOCKED)); // // We currently only ever have one blocked driver GUID per event, // but SendHotplugNotification and hotplug.dll are setup to deal // with multi-sz lists, so we'll just construct one for them. This // keeps hotplug.dll extensible, should we decide in the future to // have the kernel-mode pnpmgr "batch" blocked drivers per devnode. // BlockedDriverGuid = (LPGUID)&EventBlock->u.BlockedDriverNotification.BlockedDriverGuid; KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT, "UMPNPMGR: Processing BlockedDriverEvent for GUID = " "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}\n", BlockedDriverGuid->Data1, BlockedDriverGuid->Data2, BlockedDriverGuid->Data3, BlockedDriverGuid->Data4[0], BlockedDriverGuid->Data4[1], BlockedDriverGuid->Data4[2], BlockedDriverGuid->Data4[3], BlockedDriverGuid->Data4[4], BlockedDriverGuid->Data4[5], BlockedDriverGuid->Data4[6], BlockedDriverGuid->Data4[7])); MultiSzGuidList = BuildBlockedDriverList(BlockedDriverGuid, 1); if (MultiSzGuidList != NULL) { SendHotplugNotification((LPGUID)&GUID_DRIVER_BLOCKED, NULL, MultiSzGuidList, &ulClientSessionId, ulHotplugFlags); HeapFree(ghPnPHeap, 0, MultiSzGuidList); MultiSzGuidList = NULL; } break; } case InvalidIDEvent: { ASSERT(GuidEqual(&EventBlock->EventGuid, &GUID_DEVICE_INVALID_ID)); // // Display notification to the Console session that the system just // encountered an invalid ID from a device. // SendHotplugNotification((LPGUID)&GUID_DEVICE_INVALID_ID, NULL, &EventBlock->u.InvalidIDNotification.ParentId[0], &ulClientSessionId, ulHotplugFlags); break; } default: break; } return status; } // ProcessDeviceEvent ULONG NotifyInterfaceClassChange( IN DWORD ServiceControl, IN DWORD EventId, IN DWORD Flags, IN PDEV_BROADCAST_DEVICEINTERFACE ClassData ) /*++ Routine Description: This routine notifies registered services and windows of device interface change events. Arguments: ServiceControl - Specifies class of service event (power, device, hwprofile change). EventId - Specifies the DBT style event id for the device event. (see sdk\inc\dbt.h for defined device events) Flags - Unused (Specifies BroadcastSystemMessage BSF_ flags.) ClassData - Pointer to a PDEV_BROADCAST_DEVICEINTERFACE structure that is already filled out with the pertinent data for this event. Return Value: Returns TRUE. --*/ { NTSTATUS ntStatus = STATUS_SUCCESS; DWORD result; ULONG hashValue, pass, i; PPNP_NOTIFY_ENTRY classEntry = NULL, nextEntry = NULL; PPNP_NOTIFY_LIST notifyList; LPGUID entryGuid[3]; UNREFERENCED_PARAMETER(Flags); // // Search the notification lists twice - once to notify entries registered // on the device interface class for this device interface, and again to // notify entries registered for all device interfaces. // entryGuid[0] = (LPGUID)&ClassData->dbcc_classguid; entryGuid[1] = (LPGUID)&GUID_DEVINTERFACE_INCLUDE_ALL_INTERFACE_CLASSES; entryGuid[2] = (LPGUID)NULL; for (i = 0; entryGuid[i] != NULL; i++) { // // The list of registered callers is hashed for quicker access and // comparison. Walk the list of registered callers and notify anyone // that registered an interest in this device interface class guid. // hashValue = HashClassGuid(entryGuid[i]); notifyList = &ClassList[hashValue]; LockNotifyList(¬ifyList->Lock); classEntry = GetFirstNotifyEntry(&ClassList[hashValue], 0); pass = GetFirstPass(FALSE); while (pass != PASS_COMPLETE) { while (classEntry) { nextEntry = GetNextNotifyEntry(classEntry, 0); if (classEntry->Unregistered) { classEntry = nextEntry; continue; } if (GuidEqual(entryGuid[i], &classEntry->u.Class.ClassGuid)) { if (GuidEqual(&classEntry->u.Class.ClassGuid, &GUID_DEVINTERFACE_INCLUDE_ALL_INTERFACE_CLASSES)) { // // If the entry is marked with our special GUID, make // sure it is because it was registered with the // appropriate flag. // ASSERT((classEntry->Flags & DEVICE_NOTIFY_PROPERTY_MASK) & DEVICE_NOTIFY_ALL_INTERFACE_CLASSES); } if ((pass == DEVICE_NOTIFY_WINDOW_HANDLE) && (GetPassFromEntry(classEntry) == DEVICE_NOTIFY_WINDOW_HANDLE)) { // // Note, class changes currently only support non-query type // messages so special processing is not required (PostMessage // only). Unfortunately, the PostMessage call currently fails // if the high bit of the wParam value is set (which it is in // this case), so we are forced to Send the message (rather than // Post it). USER group implemented it this way because the original // Win95 spec doesn't call for the recipient to free the message // so we have to free it and we have no idea when it's safe // with a PostMessage call. // UnlockNotifyList(¬ifyList->Lock); if (classEntry->SessionId == MAIN_SESSION) { ntStatus = DeviceEventWorker(classEntry->Handle, EventId, (LPARAM)ClassData, TRUE, &result); } else { if (fpWinStationSendWindowMessage) { try { if (fpWinStationSendWindowMessage(SERVERNAME_CURRENT, classEntry->SessionId, DEFAULT_SEND_TIME_OUT, HandleToUlong(classEntry->Handle), WM_DEVICECHANGE, (WPARAM)EventId, (LPARAM)ClassData, (LONG*)&result)) { ntStatus = STATUS_SUCCESS; } else { ntStatus = STATUS_UNSUCCESSFUL; } } except (EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Exception calling WinStationSendWindowMessage!\n")); ASSERT(0); ntStatus = STATUS_SUCCESS; } } } LockNotifyList(¬ifyList->Lock); if (!NT_SUCCESS(ntStatus)) { if (ntStatus == STATUS_INVALID_HANDLE) { // // window handle no longer exists, cleanup this entry // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_WARNINGS | DBGF_ERRORS, "UMPNPMGR: Invalid window handle for '%ws' during DeviceClassChangeEvent, removing entry.\n", classEntry->ClientName)); classEntry->Freed |= (PNP_UNREG_FREE|PNP_UNREG_DEFER|PNP_UNREG_WIN); DeleteNotifyEntry(classEntry,FALSE); } else if (ntStatus == STATUS_UNSUCCESSFUL) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_WARNINGS | DBGF_ERRORS, "UMPNPMGR: Window '%ws' timed out on DeviceClassChangeEvent\n", classEntry->ClientName)); LogWarningEvent(WRN_INTERFACE_CHANGE_TIMED_OUT, 1, classEntry->ClientName); } } } else if ((pass == DEVICE_NOTIFY_SERVICE_HANDLE) && (GetPassFromEntry(classEntry) == DEVICE_NOTIFY_SERVICE_HANDLE)) { // // Call the services handler routine... // if (pServiceControlCallback) { UnlockNotifyList(¬ifyList->Lock); result = NO_ERROR; try { (pServiceControlCallback)((SERVICE_STATUS_HANDLE)classEntry->Handle, ServiceControl, EventId, (LPARAM)ClassData, &result); } except (EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Exception calling Service Control Manager!\n")); result = NO_ERROR; ASSERT(0); } LockNotifyList(¬ifyList->Lock); } } else if ((pass == DEVICE_NOTIFY_COMPLETION_HANDLE) && (GetPassFromEntry(classEntry) == DEVICE_NOTIFY_COMPLETION_HANDLE)) { // // Complete the notification handle. // NOTE: Notification completion handles not implemented. // NOTHING; } } classEntry = nextEntry; } pass=GetNextPass(pass,FALSE); classEntry = GetFirstNotifyEntry (&ClassList[hashValue],0); } UnlockNotifyList(¬ifyList->Lock); } // // Perform Win9x compatible device interface arrival and removal notification. // BroadcastCompatibleDeviceMsg(EventId, ClassData, NULL); HeapFree(ghPnPHeap, 0, ClassData); // // For device interface notification, there are no query type events, by // definition, so we always return TRUE from this routine (no veto). // return TRUE; } // NotifyInterfaceClassChange ULONG NotifyTargetDeviceChange( IN DWORD ServiceControl, IN DWORD EventId, IN DWORD Flags, IN PDEV_BROADCAST_HANDLE HandleData, IN LPWSTR DeviceId, OUT PPNP_VETO_TYPE VetoType OPTIONAL, OUT LPWSTR VetoName OPTIONAL, IN OUT PULONG VetoNameLength OPTIONAL ) /*++ Routine Description: This routine notifies registered services and windows of target device change events. Arguments: ServiceControl - Specifies class of service event (power, device, hwprofile change). EventId - Specifies the DBT style event id for the device event. (see sdk\inc\dbt.h for defined device events) Flags - Specifies BroadcastSystemMessage BSF_ flags. Note that BroadcastSystemMessage is not actually used for target device events, but the specified BSF_ flags are used to determine query and cancel event notification ordering. HandleData - Pointer to a PDEV_BROADCAST_HANDLE structure that is already filled out with most of the pertinent data for this event. DeviceId - Supplies the device instance id of the target device for this event. VetoType - For query-type events, supplies the address of a variable to receive, upon failure, the type of the component responsible for vetoing the request. VetoName - For query-type events, supplies the address of a variable to receive, upon failure, the name of the component responsible for vetoing the request. VetoNameLength - For query-type events, supplies the address of a variable specifying the size of the of buffer specified by the VetoName parameter. Upon failure, this address will specify the length of the string stored in that buffer by this routine. Return Value: Returns FALSE in the case of a vetoed query event, TRUE otherwise. Note: For DBT_DEVICEARRIVAL, DBT_DEVICEREMOVEPENDING, and DBT_DEVICEREMOVECOMPLETE events this routine also broadcasts a WM_DEVICECHANGE / DBT_DEVNODES_CHANGED message to all windows. There is no additional device-specific data for this message; it is only used by components like device manager to refresh the list of devices in the system. Also note that the DBT_DEVNODES_CHANGED message is the only notification sent for DBT_DEVICEARRIVAL (kernel GUID_DEVICE_ARRIVAL) events. --*/ { NTSTATUS ntStatus = STATUS_SUCCESS; DWORD result = 0; ULONG hashValue, pass; PPNP_NOTIFY_ENTRY targetEntry, nextEntry; PPNP_NOTIFY_LIST notifyList = NULL; BOOL bLocked = FALSE; DWORD err; BOOL serviceVetoedQuery; DWORD recipients = BSM_ALLDESKTOPS | BSM_APPLICATIONS; LONG response; #ifdef _WIN64 DEV_BROADCAST_HANDLE32 UNALIGNED *HandleData32 = NULL; ULONG ulHandleDataSize; #endif // _WIN64 PVOID pHandleData; serviceVetoedQuery = FALSE; // // If we're doing a query, then VetoType, VetoName, and VetoNameLength must // all be specified. // ASSERT(!(Flags & BSF_QUERY) || (VetoType && VetoName && VetoNameLength)); if (!(Flags & BSF_QUERY) && (VetoNameLength != NULL)) { // // Not vetoable. // *VetoNameLength = 0; } // // Broadcast the DBT_DEVNODES_CHANGED message before any other notification // events, so components listening for those can update themselves in a // timely manner, and not be delayed by apps/services hung on their // notification event. This broadcasts is a post, so it will return // immediately, and complete asynchronously. // if ((EventId == DBT_DEVICEARRIVAL) || (EventId == DBT_DEVICEREMOVEPENDING) || (EventId == DBT_DEVICEREMOVECOMPLETE)) { BroadcastSystemMessage(BSF_POSTMESSAGE, &recipients, WM_DEVICECHANGE, DBT_DEVNODES_CHANGED, (LPARAM)NULL); if (fpWinStationBroadcastSystemMessage) { try { fpWinStationBroadcastSystemMessage(SERVERNAME_CURRENT, TRUE, 0, DEFAULT_BROADCAST_TIME_OUT, BSF_NOHANG | BSF_POSTMESSAGE, &recipients, WM_DEVICECHANGE, (WPARAM)DBT_DEVNODES_CHANGED, (LPARAM)NULL, &response); } except (EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Exception calling WinStationBroadcastSystemMessage!\n")); ASSERT(0); } } } // // For target device arrival events, no additional notification is // performed. // if (EventId == DBT_DEVICEARRIVAL) { goto Clean0; } #ifdef _WIN64 // // Prepare a 32-bit notification structure, which we'll need to send to any // WOW64 clients that are registered. // ASSERT(sizeof(DEV_BROADCAST_HANDLE) == sizeof(DEV_BROADCAST_HANDLE64)); ASSERT(HandleData->dbch_size >= sizeof(DEV_BROADCAST_HANDLE64)); ulHandleDataSize = HandleData->dbch_size - sizeof(DEV_BROADCAST_HANDLE64) + sizeof(DEV_BROADCAST_HANDLE32); ASSERT(ulHandleDataSize >= sizeof(DEV_BROADCAST_HANDLE32)); HandleData32 = HeapAlloc(ghPnPHeap, 0, ulHandleDataSize); if (HandleData32 == NULL) { goto Clean0; } memset(HandleData32, 0, ulHandleDataSize); HandleData32->dbch_size = ulHandleDataSize; HandleData32->dbch_devicetype = DBT_DEVTYP_HANDLE; HandleData32->dbch_nameoffset = HandleData->dbch_nameoffset; memcpy(&HandleData32->dbch_eventguid, &HandleData->dbch_eventguid, sizeof(GUID)); memcpy(&HandleData32->dbch_data, &HandleData->dbch_data, (HandleData->dbch_size - FIELD_OFFSET(DEV_BROADCAST_HANDLE, dbch_data))); #endif // _WIN64 // // Sanitize the device id // FixUpDeviceId(DeviceId); // // The list of registered callers is hashed for quicker access and // comparison. Walk the list of registered callers and notify anyone // that registered an interest in this device instance. // hashValue = HashString(DeviceId, TARGET_HASH_BUCKETS); notifyList = &TargetList[hashValue]; LockNotifyList(¬ifyList->Lock); bLocked = TRUE; pass = GetFirstPass(Flags & BSF_QUERY); do { targetEntry = GetFirstNotifyEntry (notifyList,Flags); while (targetEntry) { nextEntry = GetNextNotifyEntry(targetEntry,Flags); if (targetEntry->Unregistered) { targetEntry = nextEntry; continue; } if (CompareString( LOCALE_INVARIANT, NORM_IGNORECASE, DeviceId, -1, targetEntry->u.Target.DeviceId, -1) == CSTR_EQUAL) { if ((pass == DEVICE_NOTIFY_WINDOW_HANDLE) && (GetPassFromEntry(targetEntry) == DEVICE_NOTIFY_WINDOW_HANDLE)) { // // Note: we could get away with only doing a send message // if the Flags has BSF_QUERY set and do a post message in // all other cases. Unfortunately, the PostMessage call currently // fails if the high bit of the wParam value is set (which it is in // this case), so we are forced to Send the message (rather than // Post it). USER group implemented it this way because the original // Win95 spec doesn't call for the recipient to free the message // so we have to free it and we have no idea when it's safe // with a PostMessage call. // HandleData->dbch_handle = targetEntry->u.Target.FileHandle; HandleData->dbch_hdevnotify = (HDEVNOTIFY)((ULONG_PTR)targetEntry->ClientCtxPtr); UnlockNotifyList(¬ifyList->Lock); bLocked = FALSE; // // Always send the native DEV_BROADCAST_HANDLE structure to // windows. If any 64-bit/32-bit conversion needs to be // done for this client, ntuser will do it for us. // pHandleData = HandleData; if (targetEntry->SessionId == MAIN_SESSION ) { ntStatus = DeviceEventWorker(targetEntry->Handle, EventId, (LPARAM)pHandleData, TRUE, &result); } else { if (fpWinStationSendWindowMessage) { try { if (fpWinStationSendWindowMessage(SERVERNAME_CURRENT, targetEntry->SessionId, DEFAULT_SEND_TIME_OUT, HandleToUlong(targetEntry->Handle), WM_DEVICECHANGE, (WPARAM)EventId, (LPARAM)pHandleData, (LONG*)&result)) { ntStatus = STATUS_SUCCESS; } else { ntStatus = STATUS_UNSUCCESSFUL; } } except (EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Exception calling WinStationSendWindowMessage!\n")); ASSERT(0); ntStatus = STATUS_SUCCESS; } } } LockNotifyList(¬ifyList->Lock); bLocked = TRUE; if (NT_SUCCESS(ntStatus)) { // // This call succeeded, if it's a query type call, check // the result returned. // if ((Flags & BSF_QUERY) && (result == BROADCAST_QUERY_DENY)) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT, "UMPNPMGR: Window '%ws' vetoed TargetDeviceChangeEvent\n", targetEntry->ClientName)); WindowVeto(targetEntry, VetoType, VetoName, VetoNameLength); // // Haven't told the services yet. Note that we // always call this routine with the native // DEV_BROADCAST_HANDLE structure, since it walks // the entire list itself. It will do the // conversion again, if necessary. // SendCancelNotification(targetEntry, ServiceControl, EventId, BSF_QUERY, (PDEV_BROADCAST_HDR)HandleData, DeviceId); goto Clean0; } } else if (ntStatus == STATUS_INVALID_HANDLE) { // // window handle no longer exists, cleanup this entry // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_WARNINGS, "UMPNPMGR: Invalid window handle for '%ws' during TargetDeviceChangeEvent, removing entry.\n", targetEntry->ClientName)); targetEntry->Freed |= (PNP_UNREG_FREE|PNP_UNREG_DEFER|PNP_UNREG_WIN); DeleteNotifyEntry(targetEntry,FALSE); } else if (ntStatus == STATUS_UNSUCCESSFUL) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_WARNINGS, "UMPNPMGR: Window '%ws' timed out on TargetDeviceChangeEvent\n", targetEntry->ClientName)); LogWarningEvent(WRN_TARGET_DEVICE_CHANGE_TIMED_OUT, 1, targetEntry->ClientName); } } else if ((pass == DEVICE_NOTIFY_SERVICE_HANDLE) && (GetPassFromEntry(targetEntry) == DEVICE_NOTIFY_SERVICE_HANDLE)) { if (pServiceControlCallback) { // // Call the services handler routine... // HandleData->dbch_handle = targetEntry->u.Target.FileHandle; HandleData->dbch_hdevnotify = (HDEVNOTIFY)((ULONG_PTR)targetEntry->ClientCtxPtr); // // Assume we're sending the native DEV_BROADCAST_HANDLE // structure. // pHandleData = HandleData; #ifdef _WIN64 // // If the client is running on WOW64, send it the 32-bit // DEV_BROADCAST_HANDLE structure we created instead. // if (targetEntry->Flags & DEVICE_NOTIFY_WOW64_CLIENT) { HandleData32->dbch_handle = (ULONG32)PtrToUlong(targetEntry->u.Target.FileHandle); HandleData32->dbch_hdevnotify = (ULONG32)PtrToUlong((HDEVNOTIFY)targetEntry->ClientCtxPtr); pHandleData = HandleData32; } #endif // _WIN64 try { UnlockNotifyList(¬ifyList->Lock); bLocked = FALSE; err = NO_ERROR; try { (pServiceControlCallback)((SERVICE_STATUS_HANDLE)targetEntry->Handle, ServiceControl, EventId, (LPARAM)pHandleData, &err); } except (EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Exception calling Service Control Manager!\n")); ASSERT(0); err = NO_ERROR; } LockNotifyList(¬ifyList->Lock); bLocked = TRUE; // // convert Win32 error into window message-style // return value // if (err == NO_ERROR) { result = TRUE; } else { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT, "UMPNPMGR: Service %ws responded to TargetDeviceChangeEvent with status=0x%08lx\n", targetEntry->ClientName, err)); // // This service specifically requested to receive this // notification - it should know how to handle it. // ASSERT(err != ERROR_CALL_NOT_IMPLEMENTED); // // Log the error the service used to veto. // LogWarningEvent(WRN_TARGET_DEVICE_CHANGE_SERVICE_VETO, 1, targetEntry->ClientName); result = BROADCAST_QUERY_DENY; } if ((Flags & BSF_QUERY) && (result == BROADCAST_QUERY_DENY)) { serviceVetoedQuery = TRUE; ServiceVeto(targetEntry, VetoType, VetoName, VetoNameLength ); // // This service vetoed the query, tell everyone // else it was cancelled. Note that we always // call this routine with the native // DEV_BROADCAST_HANDLE structure, since it // walks the entire list itself. It will do the // conversion again, if necessary. // SendCancelNotification(targetEntry, ServiceControl, EventId, BSF_QUERY, (PDEV_BROADCAST_HDR)HandleData, DeviceId); } } except (EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Exception calling Service Control Manager!\n")); ASSERT(0); // // Reference the "serviceVetoedQuery" variable to // ensure the compiler will respect statement // ordering w.r.t. this variable. We want to make // sure we know with certainty whether any service // vetoed the query, even if subsequently sending // the cancel caused an access violation. // serviceVetoedQuery = serviceVetoedQuery; } if (serviceVetoedQuery) { goto Clean0; } } } else if ((pass == DEVICE_NOTIFY_COMPLETION_HANDLE) && (GetPassFromEntry(targetEntry) == DEVICE_NOTIFY_COMPLETION_HANDLE)) { // // Complete the notification handle. // NOTE: Notification completion handles not implemented. // NOTHING; } } targetEntry = nextEntry; } // while } while ((pass = GetNextPass(pass, (Flags & BSF_QUERY))) != PASS_COMPLETE); if (VetoNameLength != NULL) { *VetoNameLength = 0; } Clean0: if (bLocked) { UnlockNotifyList(¬ifyList->Lock); } #ifdef _WIN64 // // Free the 32-bit DEV_BROADCAST_HANDLE structure, if we allocated one. // if (HandleData32 != NULL) { HeapFree(ghPnPHeap, 0, HandleData32); } #endif // _WIN64 return (result != BROADCAST_QUERY_DENY); } // NotifyTargetDeviceChange ULONG NotifyHardwareProfileChange( IN DWORD ServiceControl, IN DWORD EventId, IN DWORD Flags, OUT PPNP_VETO_TYPE VetoType OPTIONAL, OUT LPWSTR VetoName OPTIONAL, IN OUT PULONG VetoNameLength OPTIONAL ) /*++ Routine Description: This routine notifies registered services and all windows of hardware profile change events. Arguments: ServiceControl - Specifies class of service event (power, device, hwprofile change). EventId - Specifies the DBT style event id for the device event. (see sdk\inc\dbt.h for defined hardware profile change events) Flags - Specifies BroadcastSystemMessage BSF_ flags. VetoType - For query-type events, supplies the address of a variable to receive, upon failure, the type of the component responsible for vetoing the request. VetoName - For query-type events, supplies the address of a variable to receive, upon failure, the name of the component responsible for vetoing the request. VetoNameLength - For query-type events, supplies the address of a variable specifying the size of the of buffer specified by the VetoName parameter. Upon failure, this address will specify the length of the string stored in that buffer by this routine. Return Value: Returns FALSE in the case of a vetoed query event, TRUE otherwise. --*/ { DWORD pass; DWORD recipients = BSM_ALLDESKTOPS | BSM_APPLICATIONS; PPNP_NOTIFY_ENTRY entry = NULL, nextEntry = NULL; PPNP_NOTIFY_LIST notifyList = NULL; BOOL bLocked = FALSE; LONG response; ULONG successful; LONG result; DWORD err; // // If we're doing a query, then VetoType, VetoName, and VetoNameLength must // all be specified. // ASSERT(!(Flags & BSF_QUERY) || (VetoType && VetoName && VetoNameLength)); if (!(Flags & BSF_QUERY) && (VetoNameLength != NULL)) { // // Not vetoable. // *VetoNameLength = 0; } notifyList = &ServiceList[CINDEX_HWPROFILE]; LockNotifyList(¬ifyList->Lock); bLocked = TRUE; successful = TRUE; pass = GetFirstPass(Flags & BSF_QUERY); try { while (pass != PASS_COMPLETE) { if (pass == DEVICE_NOTIFY_WINDOW_HANDLE) { // // Notify the Windows // UnlockNotifyList (¬ifyList->Lock); bLocked = FALSE; // // ISSUE-2002/02/20-jamesca: BroadcastSystemMessage veto info? // // While we could *technically* use BroadcastSystemMessageEx // to receive the hWnd of the vetoing window, and a handle to // the Desktop that it is on, we don't actually know what // WindowStation that Desktop is in!!! The BSM_ALLDESKTOPS // flags actually causes the message to be sent to all // Desktops on *all WindowStations* in this session -- which // for a non-interactive service in session 0 (such as this), // would include all Desktops in all WindowStations being used // by all non-interactive services. In reality, a vetoing // application would *most likely* be an interactive process // on WinSta0, but there really is no guarantee that's the // case. Even if we did know the complete WindowStation and // Desktop location of the window, we would need to have a // thread running on the same desktop just to access it - // which would require either changing the windowstation for // our entire process (a very very bad idea, since we share // this process with the SCM and other services!!), or start a // whole new process over there, and communicate with it. As // you can see, this info was probably only intended to be // useful for a caller already in the interactive // WindowStation, to find out about vetoing windows also in // the interactive WindowStation, and is therefore not really // of much use to us -- so we may as well just go back to // using BroadcastSystemMessage. *sigh* // result = BroadcastSystemMessage(Flags, &recipients, WM_DEVICECHANGE, (WPARAM)EventId, (LPARAM)NULL); if ((result <= 0) && (Flags & BSF_QUERY)) { WinBroadcastVeto(NULL, VetoType, VetoName, VetoNameLength); successful = FALSE; break; } if ((result > 0) || (!(Flags & BSF_QUERY))) { if (fpWinStationBroadcastSystemMessage) { try { fpWinStationBroadcastSystemMessage(SERVERNAME_CURRENT, TRUE, 0, DEFAULT_BROADCAST_TIME_OUT, Flags, &recipients, WM_DEVICECHANGE, (WPARAM)EventId, (LPARAM)NULL, &result); } except (EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Exception calling WinStationBroadcastSystemMessage!\n")); ASSERT(0); result = 1; } } } LockNotifyList (¬ifyList->Lock); bLocked = TRUE; if ((result < 0) && (Flags & BSF_QUERY)) { UnknownVeto(VetoType, VetoName, VetoNameLength); successful = FALSE; break; } else if ((result == 0) && (Flags & BSF_QUERY)) { WinBroadcastVeto(NULL, VetoType, VetoName, VetoNameLength); successful = FALSE; break; } } else if (pass == DEVICE_NOTIFY_SERVICE_HANDLE) { // // Notify the services // entry = GetFirstNotifyEntry (notifyList,Flags & BSF_QUERY); while (entry) { nextEntry = GetNextNotifyEntry(entry,Flags & BSF_QUERY); if (entry->Unregistered) { entry = nextEntry; continue; } ASSERT(GetPassFromEntry(entry) == DEVICE_NOTIFY_SERVICE_HANDLE); // // This is a direct call, not a message via. USER // if (pServiceControlCallback) { UnlockNotifyList (¬ifyList->Lock); bLocked = FALSE; err = NO_ERROR; try { (pServiceControlCallback)((SERVICE_STATUS_HANDLE)entry->Handle, ServiceControl, EventId, (LPARAM)NULL, &err); } except (EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Exception calling Service Control Manager!\n")); ASSERT(0); err = NO_ERROR; } LockNotifyList (¬ifyList->Lock); bLocked = TRUE; // // convert Win32 error into window message-style return // value. // if (err == NO_ERROR) { result = TRUE; } else { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT, "UMPNPMGR: Service %ws responded to HardwareProfileChangeEvent with status=0x%08lx\n", entry->ClientName, err)); // // This service specifically requested to receive this // notification - it should know how to handle it. // ASSERT(err != ERROR_CALL_NOT_IMPLEMENTED); // // Log the error the service used to veto. // LogWarningEvent(WRN_HWPROFILE_CHANGE_SERVICE_VETO, 1, entry->ClientName); result = BROADCAST_QUERY_DENY; } if ((Flags & BSF_QUERY) && (result == BROADCAST_QUERY_DENY)) { ServiceVeto(entry, VetoType, VetoName, VetoNameLength); successful = FALSE; break; } } entry = nextEntry; } } if (!successful) { break; } pass = GetNextPass (pass,Flags & BSF_QUERY); } } except (EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Exception in service callback in NotifyHardwareProfileChange\n")); ASSERT(0); if (Flags & BSF_QUERY) { UnknownVeto(VetoType, VetoName, VetoNameLength); successful = FALSE; } } try { if (!successful) { ASSERT(Flags & BSF_QUERY); // // If a service vetoed the query, inform the services and windows, // otherwise only the windows know what was coming. // if (pass == DEVICE_NOTIFY_SERVICE_HANDLE) { SendCancelNotification( entry, ServiceControl, EventId, BSF_QUERY, NULL, NULL); } UnlockNotifyList (¬ifyList->Lock); bLocked = FALSE; BroadcastSystemMessage(Flags & ~BSF_QUERY, &recipients, WM_DEVICECHANGE, (WPARAM)MapQueryEventToCancelEvent(EventId), (LPARAM)NULL); if (fpWinStationBroadcastSystemMessage) { try { fpWinStationBroadcastSystemMessage(SERVERNAME_CURRENT, TRUE, 0, DEFAULT_BROADCAST_TIME_OUT, Flags & ~BSF_QUERY, &recipients, WM_DEVICECHANGE, (WPARAM)MapQueryEventToCancelEvent(EventId), (LPARAM)NULL, &response); } except (EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Exception calling WinStationBroadcastSystemMessage\n")); ASSERT(0); } } LockNotifyList (¬ifyList->Lock); bLocked = TRUE; } } except (EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Exception in service callback in NotifyHardwareProfileChange\n")); ASSERT(0); } if (bLocked) { UnlockNotifyList (¬ifyList->Lock); } // // if successful, we are not returning veto info. // if (successful && (VetoNameLength != NULL)) { *VetoNameLength = 0; } return successful; } // NotifyHardwareProfileChange BOOL SendCancelNotification( IN PPNP_NOTIFY_ENTRY LastEntry, IN DWORD ServiceControl, IN DWORD EventId, IN ULONG Flags, IN PDEV_BROADCAST_HDR NotifyData OPTIONAL, IN LPWSTR DeviceId OPTIONAL ) /*++ Routine Description: This routine sends a cancel notification to the entries in the range specified. This routine assumes the appropriate list is already locked. Arguments: LastEntry - Specifies the last list entry that received the original query notification, and was responsible for failing the request. We will stop sending cancel notification events when we get to this one. ServiceControl - Specifies class of service event (power, device, hwprofile change). EventId - Specifies the DBT style event id for the device event. (see sdk\inc\dbt.h for defined device events) Flags - Specifies BroadcastSystemMessage BSF_ flags. Note that BroadcastSystemMessage is not actually used for target device events, but the specified BSF_ flags are used to determine query and cancel event notification ordering. NotifyData - Optionally, supplies a pointer to a PDEV_BROADCAST_Xxx structure that is already filled out with most of the pertinent data for this event. This parameter may be NULL for "global" events that are not associated with any device, such as power and hardware profile change events. DeviceId - Optionally, supplies the device instance id of the target device for this event. This parameter may be NULL for "global" events that are not associated with any device, such as power and hardware profile change events. Return Value: Returns TRUE / FALSE. --*/ { NTSTATUS ntStatus = STATUS_SUCCESS; DWORD cancelEventId; DWORD result, pass, lastPass; PPNP_NOTIFY_ENTRY entry, headEntry; PPNP_NOTIFY_LIST notifyList; #ifdef _WIN64 DEV_BROADCAST_HANDLE32 UNALIGNED *HandleData32 = NULL; ULONG ulHandleDataSize; #endif // _WIN64 PVOID pNotifyData; #ifdef _WIN64 if ((ARGUMENT_PRESENT(NotifyData)) && (NotifyData->dbch_devicetype == DBT_DEVTYP_HANDLE)) { // // If cancelling a DEV_BROADCAST_HANDLE type event, prepare a 32-bit // notification structure, which we'll need to send to any WOW64 clients // that are registered. // ulHandleDataSize = ((PDEV_BROADCAST_HANDLE)NotifyData)->dbch_size - sizeof(DEV_BROADCAST_HANDLE64) + sizeof(DEV_BROADCAST_HANDLE32); ASSERT(ulHandleDataSize >= sizeof(DEV_BROADCAST_HANDLE32)); HandleData32 = HeapAlloc(ghPnPHeap, 0, ulHandleDataSize); if (HandleData32 == NULL) { return FALSE; } memset(HandleData32, 0, ulHandleDataSize); HandleData32->dbch_size = ulHandleDataSize; HandleData32->dbch_devicetype = DBT_DEVTYP_HANDLE; HandleData32->dbch_nameoffset = ((PDEV_BROADCAST_HANDLE)NotifyData)->dbch_nameoffset; memcpy(&HandleData32->dbch_eventguid, &((PDEV_BROADCAST_HANDLE)NotifyData)->dbch_eventguid, sizeof(GUID)); memcpy(&HandleData32->dbch_data, &((PDEV_BROADCAST_HANDLE)NotifyData)->dbch_data, (NotifyData->dbch_size - FIELD_OFFSET(DEV_BROADCAST_HANDLE, dbch_data))); } #endif // _WIN64 // // Use the appropriate cancel device event id that corresponds to the // original query device event id. // cancelEventId = MapQueryEventToCancelEvent(EventId); // // Get the corresponding notification list // notifyList = GetNotifyListForEntry(LastEntry); ASSERT(notifyList); if (notifyList == NULL) { return FALSE; } // // Get the pass we vetoed things on // lastPass = GetPassFromEntry(LastEntry); // // Get the opposite end of the list // headEntry = GetFirstNotifyEntry(notifyList, (Flags ^ BSF_QUERY)); // // Walk the list of registered callers backwards(!) and notify anyone that registered // an interest in this device instance. Start with the FirstEntry and stop // just before the LastEntry (the LastEntry is the one that vetoed the // request in the first place). // for(pass = lastPass; pass != PASS_COMPLETE; pass = GetNextPass(pass, (Flags ^ BSF_QUERY))) { // // If this is the pass the request was vetoed on, then start on the // vetoer entry itself. Otherwise begin again at the appropriate end // of the list. // for(entry = (pass == lastPass) ? LastEntry : headEntry; entry; entry = GetNextNotifyEntry(entry, (Flags ^ BSF_QUERY))) { if (!NotifyEntryThisPass(entry, pass)) { continue; } switch(pass) { case DEVICE_NOTIFY_SERVICE_HANDLE: if ((!ARGUMENT_PRESENT(DeviceId)) || (CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, DeviceId, -1, entry->u.Target.DeviceId, -1) == CSTR_EQUAL)) { if (pServiceControlCallback) { // // Assume we're sending the native structure. // pNotifyData = NotifyData; if ((ARGUMENT_PRESENT(NotifyData)) && (NotifyData->dbch_devicetype == DBT_DEVTYP_HANDLE)) { // // If it's a DBT_DEVTYP_HANDLE notification, set // the hdevnotify and file handle fields for the // client we're notifying. // ((PDEV_BROADCAST_HANDLE)NotifyData)->dbch_handle = entry->u.Target.FileHandle; ((PDEV_BROADCAST_HANDLE)NotifyData)->dbch_hdevnotify = (HDEVNOTIFY)((ULONG_PTR)entry->ClientCtxPtr); #ifdef _WIN64 // // If the client is running on WOW64, send it the 32-bit // DEV_BROADCAST_HANDLE structure we created instead. // if (entry->Flags & DEVICE_NOTIFY_WOW64_CLIENT) { HandleData32->dbch_handle = (ULONG32)PtrToUlong(entry->u.Target.FileHandle); HandleData32->dbch_hdevnotify = (ULONG32)PtrToUlong((HDEVNOTIFY)entry->ClientCtxPtr); pNotifyData = HandleData32; } #endif // _WIN64 } // // Call the services handler routine... // UnlockNotifyList(¬ifyList->Lock); result = NO_ERROR; try { (pServiceControlCallback)((SERVICE_STATUS_HANDLE)entry->Handle, ServiceControl, cancelEventId, (LPARAM)pNotifyData, &result); } except (EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Exception calling Service Control Manager!\n")); result = NO_ERROR; ASSERT(0); } LockNotifyList(¬ifyList->Lock); } } break; case DEVICE_NOTIFY_WINDOW_HANDLE: // // Notify the windows. Note that events with NULL DeviceId's // (for example hardware profile change events) are not // registerable by windows. Luckily for them, we broadcast // such info anyway. // if ((ARGUMENT_PRESENT(DeviceId)) && (CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, DeviceId, -1, entry->u.Target.DeviceId, -1) == CSTR_EQUAL)) { ASSERT(NotifyData); // // Always send the native DEV_BROADCAST_HANDLE structure to // windows. If any 64-bit/32-bit conversion needs to be // done for this client, ntuser will do it for us. // pNotifyData = NotifyData; if ((ARGUMENT_PRESENT(NotifyData)) && (NotifyData->dbch_devicetype == DBT_DEVTYP_HANDLE)) { // // If it's a DBT_DEVTYP_HANDLE notification, set // the hdevnotify and file handle fields for the // client we're notifying. // ((PDEV_BROADCAST_HANDLE)NotifyData)->dbch_handle = entry->u.Target.FileHandle; ((PDEV_BROADCAST_HANDLE)NotifyData)->dbch_hdevnotify = (HDEVNOTIFY)((ULONG_PTR)entry->ClientCtxPtr); } UnlockNotifyList(¬ifyList->Lock); if (entry->SessionId == MAIN_SESSION) { ntStatus = DeviceEventWorker(entry->Handle, cancelEventId, (LPARAM)pNotifyData, TRUE, &result // ignore result ); } else if (fpWinStationSendWindowMessage) { try { if (fpWinStationSendWindowMessage(SERVERNAME_CURRENT, entry->SessionId, DEFAULT_SEND_TIME_OUT, HandleToUlong(entry->Handle), WM_DEVICECHANGE, (WPARAM)cancelEventId, (LPARAM)pNotifyData, (LONG*)&result)) { ntStatus = STATUS_SUCCESS; } else { ntStatus = STATUS_UNSUCCESSFUL; } } except (EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Exception calling WinStationSendWindowMessage!\n")); ASSERT(0); ntStatus = STATUS_SUCCESS; } } LockNotifyList(¬ifyList->Lock); if (!NT_SUCCESS(ntStatus)) { if (ntStatus == STATUS_INVALID_HANDLE) { // // window handle no longer exists, cleanup this entry // entry->Freed |= (PNP_UNREG_FREE|PNP_UNREG_DEFER|PNP_UNREG_WIN|PNP_UNREG_CANCEL); DeleteNotifyEntry(entry,FALSE); } else if (ntStatus == STATUS_UNSUCCESSFUL) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT, "UMPNPMGR: Window '%ws' timed out on cancel notification event\n", entry->ClientName)); LogWarningEvent(WRN_CANCEL_NOTIFICATION_TIMED_OUT, 1, entry->ClientName); } } } break; case DEVICE_NOTIFY_COMPLETION_HANDLE: // // NOTE: Completion handles not currently implemented. // NOTHING; break; } } } #ifdef _WIN64 // // Free the 32-bit DEV_BROADCAST_HANDLE structure, if we allocated one. // if (HandleData32 != NULL) { HeapFree(ghPnPHeap, 0, HandleData32); } #endif // _WIN64 return TRUE; } // SendCancelNotification VOID BroadcastCompatibleDeviceMsg( IN DWORD EventId, IN PDEV_BROADCAST_DEVICEINTERFACE ClassData, IN PDWORD CurrentMask ) /*++ Routine Description: Deliver Win9x compatible event notification for the arrival and removal of device interfaces to volume and port class devices. Arguments: EventId - Specifies the DBT style event id. Currently, only DBT_DEVICEARRIVAL and DBT_DEVICEREMOVECOMPLETE events are supported. ClassData - Pointer to a PDEV_BROADCAST_DEVICEINTERFACE structure that is already filled out with the pertinent data. Currently, only volume and port class device interfaces are supported. (For volume class devices, the symbolic link ClassData->dbcc_name is OPTIONAL - see Notes below.) Return Value: None. Notes: For volume class device broadcasts only, this routine may also be called generically, with no symbolic link information provided. When no symbolic link information to a volume device is supplied, the broadcast mask is determined only from the current drive letter mappings and the global drive letter mask (gAllDrivesMask) prior to this event. In this case, the global drive letter mask is NOT updated here, and the caller should do so after both the removal and arrival broadcasts in response to the name change are performed. Currently, this type of call is only made from BroadcastVolumeNameChange. For volume class interface DBT_DEVICEREMOVECOMPLETE broadcasts, the drive letter mask to be broadcast is always determined only by comparing drive letters present prior to the remove of the interface with those present at this time. This is done because the former mount points for this device are no longer known when the interface removal event is received. Even so, it is still necessary for the symbolic link corresponding to this interface to be supplied to distinguish between the actual removal of the interface (where the global drive letter mask is updated), the above case, where it is not. --*/ { LONG status = ERROR_SUCCESS; LONG result = 0; DWORD recipients = BSM_APPLICATIONS | BSM_ALLDESKTOPS; DWORD flags = BSF_IGNORECURRENTTASK | BSF_NOHANG; HRESULT hr; // // Validate the input event data. // if ((ClassData->dbcc_devicetype != DBT_DEVTYP_DEVICEINTERFACE) || (ClassData->dbcc_size < sizeof(DEV_BROADCAST_DEVICEINTERFACE))) { return; } if ((EventId != DBT_DEVICEARRIVAL) && (EventId != DBT_DEVICEREMOVECOMPLETE)) { // // If the requested Event is not DBT_DEVICEARRIVAL or // DBT_DEVICEREMOVECOMPLETE, don't broadcast any messages. // return; } if (GuidEqual(&ClassData->dbcc_classguid, (LPGUID)&GUID_DEVINTERFACE_VOLUME)) { // // Volume class device interface events. // PDEV_BROADCAST_VOLUME pVolume; DWORD broadcastmask = 0; if (EventId == DBT_DEVICEARRIVAL) { if (ClassData->dbcc_name[0] == L'\0') { // // If no symbolic link name was supplied, we were asked to // broadcast volume device arrivals in response to a volume name // change event. Broadcast any new drive letters found. // DWORD currentmask; // // If a current drive letter mask was provided, use it. // if (ARGUMENT_PRESENT(CurrentMask)) { currentmask = *CurrentMask; } else { currentmask = GetAllVolumeMountPoints(); } broadcastmask = (~gAllDrivesMask & currentmask); } else { // // For volume class device interface arrival events, the volume // device name is retrieved from the interface, and is compared to // the volume names of all drive letter mountpoints in the system to // determine the drive letter(s) corresponding to the arriving // volume device interface. // LPWSTR devicePath, p; WCHAR thisVolumeName[MAX_PATH]; WCHAR enumVolumeName[MAX_PATH]; WCHAR driveName[4]; ULONG length; BOOL bResult; // // Allocate a temporary buffer for munging the symbolic link, with // enough room for a trailing '\' char (should we need to add one), // and the terminating NULL char. // length = lstrlen(ClassData->dbcc_name); devicePath = HeapAlloc(ghPnPHeap, 0, (length+1)*sizeof(WCHAR)+sizeof(UNICODE_NULL)); if (devicePath == NULL) { status = ERROR_NOT_ENOUGH_MEMORY; goto Clean0; } hr = StringCchCopy(devicePath, length + 1, ClassData->dbcc_name); ASSERT(SUCCEEDED(hr)); // // Search for the occurence of a refstring (if any) by looking for the // next occurance of a '\' char, after the initial "\\?\". // p = wcschr(&(devicePath[4]), TEXT('\\')); if (!p) { // // No refstring is present in the symbolic link; add a trailing // '\' char (as required by GetVolumeNameForVolumeMountPoint). // p = devicePath + length; *p = TEXT('\\'); } // // If there is no refstring present, we have added a trailing '\', // and placed p at that position. If a refstring is present, p is // at the position of the '\' char that separates the munged device // interface name, and the refstring; since we don't need the // refstring to reach the parent interface key, we can use the next // char for NULL terminating the string in both cases. // p++; *p = UNICODE_NULL; // // Get the Volume Name for this Mount Point // thisVolumeName[0] = TEXT('\0'); bResult = GetVolumeNameForVolumeMountPoint(devicePath, thisVolumeName, MAX_PATH); HeapFree(ghPnPHeap, 0, devicePath); if (!bResult || !thisVolumeName[0]) { status = ERROR_BAD_PATHNAME; goto Clean0; } // // Initialize the drive name string // driveName[1] = TEXT(':'); driveName[2] = TEXT('\\'); driveName[3] = UNICODE_NULL; // // Find the drive letter mount point(s) for this volume device by // enumerating all possible volume mount points and comparing each // mounted volume name with the name of the volume corresponding to // this device interface. // for (driveName[0] = TEXT('A'); driveName[0] <= TEXT('Z'); driveName[0]++) { enumVolumeName[0] = UNICODE_NULL; GetVolumeNameForVolumeMountPoint(driveName, enumVolumeName, MAX_PATH); if (CompareString( LOCALE_INVARIANT, NORM_IGNORECASE, thisVolumeName, -1, enumVolumeName, -1) == CSTR_EQUAL) { // // Add the corresponding bit for this drive letter to the mask // broadcastmask |= (1 << (driveName[0] - TEXT('A'))); } } // // Update the global drive letter mask to include new drive // letters only. Note that we don't set it to the current mask, // because that may omit volumes that have been removed, but // that we have not yet received removal notification for - // which we would not notice as removed when the removal // notification finally came. // gAllDrivesMask |= broadcastmask; } } else if (EventId == DBT_DEVICEREMOVECOMPLETE) { // // For volume class device interface removal events, the volume name // (and hence, drive mountpoints) corresponding to this device // interface has already been removed, and is no longer available. // Instead, the bitmask of all drive letter mountpoints for current // physical volumes is compared with that prior to the removal of // this device. All missing drive mountpoints are assumed to have // been associated with this volume device interface, and are // subsequently broadcasted with this interface removal // notification. // DWORD currentmask; // // Determine all current volume mount points, and broadcast any // missing drive letters. // // // If a current drive letter mask was provided, use it. // if (ARGUMENT_PRESENT(CurrentMask)) { currentmask = *CurrentMask; } else { currentmask = GetAllVolumeMountPoints(); } broadcastmask = (gAllDrivesMask & ~currentmask); // // Only update the global drive letter in response to the // removal of a interface. For volume name changes, we update // outside of this routine. // if (ClassData->dbcc_name[0] != L'\0') { // // Update the global drive letter mask to exclude removed drive // letters only. Note that we don't set it to the current mask, // because that may include volumes that have been added, but // that we have not yet received arrival notification for - // which we would not notice as added when the arrival // notification finally came. // gAllDrivesMask &= ~broadcastmask; } } // // If there is nothing to broadcast, then we're done. // if (broadcastmask == 0) { status = ERROR_SUCCESS; goto Clean0; } // // Fill out the volume broadcast structure. // pVolume = (PDEV_BROADCAST_VOLUME)HeapAlloc( ghPnPHeap, 0, sizeof(DEV_BROADCAST_VOLUME)); if (pVolume == NULL) { status = ERROR_NOT_ENOUGH_MEMORY; goto Clean0; } pVolume->dbcv_size = sizeof(DEV_BROADCAST_VOLUME); pVolume->dbcv_devicetype = DBT_DEVTYP_VOLUME; pVolume->dbcv_flags = 0; pVolume->dbcv_reserved = 0; pVolume->dbcv_unitmask = broadcastmask; // // Broadcast the message to all components // result = BroadcastSystemMessage(flags, &recipients, WM_DEVICECHANGE, EventId, (LPARAM)pVolume); if (fpWinStationBroadcastSystemMessage) { try { fpWinStationBroadcastSystemMessage(SERVERNAME_CURRENT, TRUE, 0, DEFAULT_BROADCAST_TIME_OUT, flags, &recipients, WM_DEVICECHANGE, (WPARAM)EventId, (LPARAM)pVolume, &result); } except (EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Exception calling WinStationBroadcastSystemMessage!\n")); ASSERT(0); } } // // Free the broadcast structure. // HeapFree(ghPnPHeap, 0, pVolume); } else if ((GuidEqual(&ClassData->dbcc_classguid, (LPGUID)&GUID_DEVINTERFACE_PARALLEL)) || (GuidEqual(&ClassData->dbcc_classguid, (LPGUID)&GUID_DEVINTERFACE_COMPORT))) { // // COM and LPT port class device interface events. // PDEV_BROADCAST_PORT pPort; LPWSTR p; LPWSTR deviceInterfacePath = NULL; LPWSTR deviceInterfaceName = NULL; LPWSTR deviceInstance = NULL; HKEY hKey; WCHAR szTempString[MAX_PATH]; ULONG ulSize; size_t DevicePathLen = 0, DeviceClassesLen = 0; // // Convert the interface class GUID to a string. // if (StringFromGuid((LPGUID)&ClassData->dbcc_classguid, szTempString, SIZECHARS(szTempString)) != NO_ERROR) { status = ERROR_INVALID_PARAMETER; goto Clean0; } // // Build the complete path to the device interface key for this device. // hr = StringCchLength(ClassData->dbcc_name, STRSAFE_MAX_CCH, &DevicePathLen); ASSERT(SUCCEEDED(hr)); ASSERT(DevicePathLen > 4); if (DevicePathLen < 4) { status = ERROR_INVALID_PARAMETER; goto Clean0; } hr = StringCchLength(pszRegPathDeviceClasses, MAX_CM_PATH, &DeviceClassesLen); ASSERT(SUCCEEDED(hr)); ASSERT(DeviceClassesLen > 0); if ((FAILED(hr)) || (DeviceClassesLen == 0)) { status = ERROR_INVALID_PARAMETER; goto Clean0; } ulSize = (ULONG)DeviceClassesLen + 1 + MAX_GUID_STRING_LEN + (ULONG)DevicePathLen + 1; deviceInterfacePath = (LPWSTR)HeapAlloc( ghPnPHeap, 0, (ulSize * sizeof(WCHAR))); if (deviceInterfacePath == NULL) { status = ERROR_NOT_ENOUGH_MEMORY; goto Clean0; } // // Copy the path to the "DeviceClasses" registry key // hr = StringCchCopy(deviceInterfacePath, ulSize, pszRegPathDeviceClasses); if (SUCCEEDED(hr)) { hr = StringCchCat(deviceInterfacePath, ulSize, L"\\"); } // // Append the interface class GUID to the registry path // if (SUCCEEDED(hr)) { hr = StringCchCat(deviceInterfacePath, ulSize, szTempString); } if (SUCCEEDED(hr)) { hr = StringCchCatEx(deviceInterfacePath, ulSize, L"\\", &deviceInterfaceName, NULL, STRSAFE_NULL_ON_FAILURE); } // // Append the symbolic link name to the registry path. // if (SUCCEEDED(hr)) { hr = StringCchCat(deviceInterfacePath, ulSize, ClassData->dbcc_name); } ASSERT(SUCCEEDED(hr)); if (FAILED(hr)) { status = ERROR_BAD_PATHNAME; HeapFree(ghPnPHeap, 0, deviceInterfacePath); goto Clean0; } ASSERT(deviceInterfaceName != NULL); // // Munge the symbolic link name to form the interface key name. // (Note: The munging process is optimized here; we only have to munge // the leading "\\?\" segment since the rest of the given symbolic link // is already munged, with the exception of the refstring seperator char, // if any, which we will handle below.) // deviceInterfaceName[0] = TEXT('#'); deviceInterfaceName[1] = TEXT('#'); ASSERT(deviceInterfaceName[2] == TEXT('?')); deviceInterfaceName[3] = TEXT('#'); // // Search for the begininng of the refstring (if any), by looking for // the next occurance of a '\' char. // p = wcschr(&(deviceInterfaceName[4]), TEXT('\\')); // // If there is a RefString component to the device interface path, // remove it from the path by replacing the path separator character // with a NULL-terminating character. // if (p != NULL) { *p = L'\0'; } // // Open the device interface key // status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, deviceInterfacePath, 0, KEY_READ, &hKey); HeapFree(ghPnPHeap, 0, deviceInterfacePath); if (status != ERROR_SUCCESS) { hKey = NULL; goto Clean0; } // // Allocate a string large enough to store the path from the Enum key, // to the "\Device Parameters" subkey of this Device Instance's registry // key. // ulSize = MAX_CM_PATH * sizeof(WCHAR); deviceInstance = (LPWSTR)HeapAlloc( ghPnPHeap, 0, ulSize); if (deviceInstance == NULL) { status = ERROR_NOT_ENOUGH_MEMORY; RegCloseKey(hKey); hKey = NULL; goto Clean0; } // // Retrieve the device instance that owns this interface. // status = RegQueryValueEx(hKey, pszRegValueDeviceInstance, 0, NULL, (LPBYTE)deviceInstance, &ulSize); RegCloseKey(hKey); hKey = NULL; if (status != ERROR_SUCCESS) { HeapFree(ghPnPHeap, 0, deviceInstance); goto Clean0; } // // Open the "Device Parameters" key under the HKLM\SYSTEM\CCS\Enum // subkey for this DeviceInstance. // hr = StringCchCat(deviceInstance, MAX_CM_PATH, L"\\"); if (SUCCEEDED(hr)) { hr = StringCchCat(deviceInstance, MAX_CM_PATH, pszRegKeyDeviceParam); } if (SUCCEEDED(hr)) { status = RegOpenKeyEx(ghEnumKey, deviceInstance, 0, KEY_READ, &hKey); } else { status = ERROR_BAD_PATHNAME; } HeapFree(ghPnPHeap, 0, deviceInstance); if (status != ERROR_SUCCESS) { goto Clean0; } // // Query the "PortName" value for the compatible name of this device. // ulSize = MAX_PATH*sizeof(WCHAR); status = RegQueryValueEx(hKey, pszRegValuePortName, 0, NULL, (LPBYTE)szTempString, &ulSize); RegCloseKey(hKey); if (status != ERROR_SUCCESS) { goto Clean0; } // // Fill out the port broadcast structure. // pPort = (PDEV_BROADCAST_PORT)HeapAlloc( ghPnPHeap, 0, sizeof(DEV_BROADCAST_PORT) + ulSize); if (pPort == NULL) { status = ERROR_NOT_ENOUGH_MEMORY; goto Clean0; } pPort->dbcp_size = sizeof(DEV_BROADCAST_PORT) + ulSize; pPort->dbcp_devicetype = DBT_DEVTYP_PORT; pPort->dbcp_reserved = 0; hr = StringCbCopy(pPort->dbcp_name, ulSize + sizeof(WCHAR), szTempString); ASSERT(SUCCEEDED(hr)); if (FAILED(hr)) { HeapFree(ghPnPHeap, 0, pPort); status = ERROR_BAD_PATHNAME; goto Clean0; } // // Broadcast the message to all components // result = BroadcastSystemMessage(flags, &recipients, WM_DEVICECHANGE, EventId, (LPARAM)pPort); if (fpWinStationBroadcastSystemMessage) { try { fpWinStationBroadcastSystemMessage(SERVERNAME_CURRENT, TRUE, 0, DEFAULT_BROADCAST_TIME_OUT, flags, &recipients, WM_DEVICECHANGE, (WPARAM)EventId, (LPARAM)pPort, &result); } except (EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Exception calling WinStationBroadcastSystemMessage!\n")); ASSERT(0); } } // // Free the broadcast structure. // HeapFree(ghPnPHeap, 0, pPort); } Clean0: return; } // BroadcastCompatibleDeviceMsg VOID BroadcastVolumeNameChange( VOID ) /*++ Routine Description: Perform Win9x compatible volume removal and arrival messages, to be called in reponse to a volume name change event. Arguments: None. Return Value: None. Notes: The drive mask to be broadcast will be determined by comparing the current drive letter mask with that prior to the event. The global drive letter mask is also updated here, after all removal and arrival notifications have been sent. --*/ { DEV_BROADCAST_DEVICEINTERFACE volumeNotify; DWORD currentmask = 0; // // Fill out a DEV_BROADCAST_DEVICEINTERFACE structure. // ZeroMemory(&volumeNotify, sizeof(DEV_BROADCAST_DEVICEINTERFACE)); volumeNotify.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE); volumeNotify.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; volumeNotify.dbcc_reserved = 0; memcpy(&volumeNotify.dbcc_classguid, &GUID_DEVINTERFACE_VOLUME, sizeof(GUID)); // // A null symbolic link name for dbcc_name designates that // BroadcastCompatibleDeviceMsg is to determine the drive mask to // broadcast by checking differences between the last broadcast drive // mask (gAllDrivesMask), and the current drive mask. // // When broadcasting in response to a volume name change, we must wait until // both removal and arrival messages have been sent before we can update the // global drive letter mask. A null symbolic link name specifies that // BroadcastCompatibleDeviceMsg should not update the global mask; this will // be done here, after all broadcasts are complete. // volumeNotify.dbcc_name[0] = L'\0'; // // Retrieve the current drive letter mask. // currentmask = GetAllVolumeMountPoints(); // // Broadcast volume removal notification for any drive letter moint points // no longer in use, followed by volume arrival notification for new // BroadcastCompatibleDeviceMsg(DBT_DEVICEREMOVECOMPLETE, &volumeNotify, ¤tmask); BroadcastCompatibleDeviceMsg(DBT_DEVICEARRIVAL, &volumeNotify, ¤tmask); // // Now that both removal and arrival messages have been sent, update the // global drive letter mask to reflect what we just broadcast. // gAllDrivesMask = currentmask; return; } // BroadcastVolumeNameChange DWORD GetAllVolumeMountPoints( VOID ) /*++ Routine Description: Queries all drive letter mountpoints ('A'-'Z') and returns a bitmask representing all such mount points currently in use by physical volume devices. Arguments: None. Return Value: Returns a bit mask representing drive letter mount points ('A'-'Z') in use by physical volume devices. Note: The returned bit mask includes only mount points for physical volume class devices. Network mounted drives are not included. --*/ { WCHAR driveName[4]; WCHAR volumeName[MAX_PATH]; DWORD driveLetterMask=0; // // Initialize drive name and mask // driveName[1] = TEXT(':'); driveName[2] = TEXT('\\'); driveName[3] = UNICODE_NULL; // // Compare the name of this volume with those of all mounted volumes in the system // for (driveName[0] = TEXT('A'); driveName[0] <= TEXT('Z'); driveName[0]++) { volumeName[0] = UNICODE_NULL; if (!GetVolumeNameForVolumeMountPoint(driveName, volumeName, MAX_PATH)) { continue; } if (volumeName[0] != UNICODE_NULL) { // // Add the corresponding bit for this drive letter to the mask // driveLetterMask |= (1 << (driveName[0] - TEXT('A'))); } } return driveLetterMask; } // GetAllVolumeMountPoints ULONG NotifyPower( IN DWORD ServiceControl, IN DWORD EventId, IN DWORD EventData, IN DWORD Flags, OUT PPNP_VETO_TYPE VetoType OPTIONAL, OUT LPWSTR VetoName OPTIONAL, IN OUT PULONG VetoNameLength OPTIONAL ) /*++ Routine Description: This routine notifies services of system-wide power events. Arguments: ServiceControl - Specifies class of service event (power, device, hwprofile change). EventId - Specifies the PBT style event id for the power event. (see sdk\inc\pbt.h for defined power events) EventData - Specifies additional data for the event. Flags - Specifies BroadcastSystemMessage BSF_ flags. VetoType - For query-type events, supplies the address of a variable to receive, upon failure, the type of the component responsible for vetoing the request. VetoName - For query-type events, supplies the address of a variable to receive, upon failure, the name of the component responsible for vetoing the request. VetoNameLength - For query-type events, supplies the address of a variable specifying the size of the of buffer specified by the VetoName parameter. Upon failure, this address will specify the length of the string stored in that buffer by this routine. Return Value: Returns FALSE in the case of a vetoed query event, TRUE otherwise. Notes: This routine currently only notifies services of power events. Notification to windows is handled directly by USER. Power events are placed in the plug and play event queue via a private call from USER, for the explicit purpose of notifying services of system-wide power events, done here. --*/ { NTSTATUS status=STATUS_SUCCESS; PPNP_NOTIFY_ENTRY entry, nextEntry; PPNP_NOTIFY_LIST notifyList = NULL; BOOL bLocked = FALSE; DWORD err; LONG result; // // NOTE: Services are not currently sent EventData for power events. The // SCM currently ASSERTs that this will always be zero. // // The SDK states that WM_POWERBROADCAST "RESUME" type messages may contain // the PBTF_APMRESUMEFROMFAILURE flag in the LPARAM field, and that "QUERY" // type messages may contain a single bit in the LPARAM field specifying // whether user interaction is allowed. // // Although these don't currently seem to be used much (even for window // messages, as stated), shouldn't EventData also be valid for service power // event notification? // UNREFERENCED_PARAMETER(EventData); // // If we're doing a query, then VetoType, VetoName, and VetoNameLength must // all be specified. // ASSERT(!(Flags & BSF_QUERY) || (VetoType && VetoName && VetoNameLength)); if (!(Flags & BSF_QUERY) && (VetoNameLength != NULL)) { // // Not vetoable. // *VetoNameLength = 0; } notifyList = &ServiceList[CINDEX_POWEREVENT]; LockNotifyList (¬ifyList->Lock); bLocked = TRUE; // //Services only. User sends out messages to apps // try { // //Notify the services // entry = GetFirstNotifyEntry(notifyList,0); if (!entry) { // // can't veto if no one registered. // if (VetoNameLength != NULL) { *VetoNameLength = 0; } } while (entry) { nextEntry = GetNextNotifyEntry(entry,0); if (entry->Unregistered) { entry = nextEntry; continue; } // // This is a direct call, not a message via. USER // if (pServiceControlCallback) { UnlockNotifyList (¬ifyList->Lock); bLocked = FALSE; err = NO_ERROR; try { (pServiceControlCallback)((SERVICE_STATUS_HANDLE)entry->Handle, ServiceControl, EventId, (LPARAM)NULL, // Currently, no EventData allowed for services &err); } except (EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Exception calling Service Control Manager!\n")); err = NO_ERROR; ASSERT(0); } LockNotifyList (¬ifyList->Lock); bLocked = TRUE; // // convert Win32 error into window message-style return // value. // if (err == NO_ERROR) { result = TRUE; } else { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT, "UMPNPMGR: Service %ws responded to PowerEvent, with status=0x%08lx\n", entry->ClientName, err)); // // This service specifically requested to receive this // notification - it should know how to handle it. // ASSERT(err != ERROR_CALL_NOT_IMPLEMENTED); // // Log the error the service used to veto. // LogWarningEvent(WRN_POWER_EVENT_SERVICE_VETO, 1, entry->ClientName); result = BROADCAST_QUERY_DENY; } // // Check if one of the QUERY messages was denied // if ((Flags & BSF_QUERY) && (result == BROADCAST_QUERY_DENY)) { ServiceVeto(entry, VetoType, VetoName, VetoNameLength ); // // This service vetoed the query, tell everyone else // it was cancelled. // SendCancelNotification(entry, ServiceControl, EventId, 0, NULL, NULL); status = STATUS_UNSUCCESSFUL; break; } } entry = nextEntry; } } except (EXCEPTION_EXECUTE_HANDLER){ KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Exception delivering Power Notification to Service Control Manager\n")); ASSERT(0); } if (bLocked) { UnlockNotifyList (¬ifyList->Lock); } // // if successful, we are not returning veto info. // if (NT_SUCCESS(status) && (VetoNameLength != NULL)) { *VetoNameLength = 0; } return (NT_SUCCESS(status)); } // NotifyPower CONFIGRET RegisterServiceNotification( IN SERVICE_STATUS_HANDLE hService, IN LPWSTR pszService, IN DWORD scmControls, IN BOOL bServiceStopped ) /*++ Routine Description: This routine is called directly and privately by the service controller. It allows the SCM to register or unregister services for events sent by this service. Arguments: hService - Specifies the service handle. pszService - Specifies the name of the service. scmControls - Specifies the messages that SCM wants to listen to. bServiceStopped - Specifies whether the service is stopped. Return Value: Return CR_SUCCESS if the function succeeds, otherwise it returns one of the CR_* errors. Notes: This routine is called anytime a service changes the state of the SERVICE_ACCEPT_POWEREVENT or SERVICE_ACCEPT_HARDWAREPROFILECHANGE flags in its list of accepted controls. This routine is also called by the SCM whenever any service has stopped, to make sure that the specified service status handle is no longer registered to receive SERVICE_CONTROL_DEVICEEVENT events. Although it is the responsibility of the service to unregister for any device event notifications that it has registered to receive before it is stopped, its service status handle may be reused by the service controller, so we must clean up any remaining device event registrations so that other services will not receive them instead. This is necessary for shared process services, since RPC rundown on the notification handle will not occur until the service's process exits, which may be long after the service has stopped. --*/ { ULONG cBits, i=0; CONFIGRET Status = CR_SUCCESS; PPNP_NOTIFY_ENTRY entry = NULL, curentry, nextentry; PLOCKINFO LockHeld = NULL; // // Filter out the accepted controls we care about. // cBits = MapSCMControlsToControlBit(scmControls); // // If we were called because the service was stopped, make sure that we // always unregister for all notifications. // if (bServiceStopped) { ASSERT(cBits == 0); cBits = 0; } try { EnterCriticalSection(&RegistrationCS); // // Add or remove an entry in the array for each control bits. // for (i = 0;i< SERVICE_NUM_CONTROLS;i++) { if (LockNotifyList(&ServiceList[i].Lock)) { LockHeld = &ServiceList[i].Lock; } else { // // Couldn't acquire the lock. Just move on to the next control // bit. // continue; } // // Check to see if an entry for this service handle already exists // in our list. // for (curentry = GetFirstNotifyEntry(&ServiceList[i],0); curentry; curentry = GetNextNotifyEntry(curentry,0)) { if (curentry->Handle == (HANDLE)hService) { break; } } // // At this point, if curentry is non-NULL, then the service // handle is already in our list, otherwise, it is not. // if (cBits & (1 << i)) { // // If entry isn't already in the list, then add it. // if (!curentry) { entry = HeapAlloc(ghPnPHeap, 0, sizeof(PNP_NOTIFY_ENTRY)); if (NULL == entry) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); Status = CR_OUT_OF_MEMORY; UnlockNotifyList(LockHeld); LockHeld = NULL; goto Clean0; } RtlZeroMemory (entry,sizeof (PNP_NOTIFY_ENTRY)); entry->Handle = (HANDLE)hService; entry->Signature = SERVICE_ENTRY_SIGNATURE; entry->Freed = 0; entry->Flags = DEVICE_NOTIFY_SERVICE_HANDLE; entry->ClientName = NULL; if (ARGUMENT_PRESENT(pszService)) { HRESULT hr; size_t ServiceNameLen = 0; hr = StringCchLength(pszService, MAX_SERVICE_NAME_LEN, &ServiceNameLen); if (FAILED(hr)) { ServiceNameLen = MAX_SERVICE_NAME_LEN - 1; } entry->ClientName = (LPWSTR)HeapAlloc( ghPnPHeap, 0, (ServiceNameLen+1)*sizeof(WCHAR)); if (entry->ClientName == NULL) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); Status = CR_OUT_OF_MEMORY; HeapFree(ghPnPHeap,0,entry); UnlockNotifyList(LockHeld); LockHeld = NULL; goto Clean0; } // // Copy to the allocated buffer, truncating if necessary. // hr = StringCchCopy(entry->ClientName, ServiceNameLen + 1, pszService); ASSERT(SUCCEEDED(hr)); } entry->u.Service.scmControls = scmControls; MarkEntryWithList(entry,i); AddNotifyEntry(&ServiceList[i], entry); // // Now reset entry pointer to NULL so we won't try to free // it if we encounter an exception // entry = NULL; } } else { // // If entry is in the list, then remove it. // if (curentry) { curentry->Freed |= (PNP_UNREG_FREE|PNP_UNREG_DEFER|PNP_UNREG_SERVICE); DeleteNotifyEntry(curentry,TRUE); } } UnlockNotifyList(LockHeld); LockHeld = NULL; } // // If the service is being stopped, unregister all outstanding device // event registrations. // if (bServiceStopped) { // // If a notification is currently in progress, check to see if there // are any entries for this service in the deferred RegisterList or // UnregisterList. // if (gNotificationInProg != 0) { if (RegisterList) { PPNP_DEFERRED_LIST currReg, prevReg; currReg = RegisterList; prevReg = NULL; while (currReg) { ASSERT(currReg->Entry->Unregistered); if (currReg->Entry->Handle == (HANDLE)hService) { if (prevReg) { prevReg->Next = currReg->Next; } else { RegisterList = currReg->Next; } HeapFree(ghPnPHeap, 0, currReg); if (prevReg) { currReg = prevReg->Next; } else { currReg = RegisterList; } } else { prevReg = currReg; currReg = currReg->Next; } } } if (UnregisterList) { PPNP_DEFERRED_LIST currUnreg, prevUnreg; currUnreg = UnregisterList; prevUnreg = NULL; while (currUnreg) { ASSERT(currUnreg->Entry->Unregistered); if (currUnreg->Entry->Handle == (HANDLE)hService) { if (prevUnreg) { prevUnreg->Next = currUnreg->Next; } else { UnregisterList = currUnreg->Next; } HeapFree(ghPnPHeap, 0, currUnreg); if (prevUnreg) { currUnreg = prevUnreg->Next; } else { currUnreg = UnregisterList; } } else { prevUnreg = currUnreg; currUnreg = currUnreg->Next; } } } } // // Check for any target device notification entries for this // service. // for (i = 0; i < TARGET_HASH_BUCKETS; i++) { if (LockNotifyList(&TargetList[i].Lock)) { LockHeld = &TargetList[i].Lock; } else { // // Couldn't acquire the lock. Just move on to the next list. // continue; } // // Check to see if an entry for this service handle exists in // this list. // curentry = GetFirstNotifyEntry(&TargetList[i],0); while(curentry) { nextentry = GetNextNotifyEntry(curentry,0); if (curentry->Unregistered) { curentry = nextentry; continue; } if (curentry->Handle == (HANDLE)hService) { // // Remove the entry from the notification list. // curentry->Unregistered = TRUE; curentry->Freed |= (PNP_UNREG_FREE|PNP_UNREG_TARGET); DeleteNotifyEntry(curentry,FALSE); // // Only log a warning if the PlugPlay service has not // already stopped. Otherwise, the client may actually // have tried to unregister after we were shut down. // if (CurrentServiceState != SERVICE_STOPPED && CurrentServiceState != SERVICE_STOP_PENDING) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_WARNINGS | DBGF_EVENT, "UMPNPMGR: Service '%ws' " "may have stopped without unregistering " "for TargetDeviceChange notification.\n", curentry->ClientName)); LogWarningEvent(WRN_STOPPED_SERVICE_REGISTERED, 1, curentry->ClientName); } } curentry = nextentry; } UnlockNotifyList(LockHeld); LockHeld = NULL; } // // Check for any device interface notification entries for this // service. // for (i = 0; i < CLASS_GUID_HASH_BUCKETS; i++) { if (LockNotifyList(&ClassList[i].Lock)) { LockHeld = &ClassList[i].Lock; } else { // // Couldn't acquire the lock. Just move on to the next list. // continue; } // // Check to see if an entry for this service handle exists in // this list. // curentry = GetFirstNotifyEntry(&ClassList[i],0); while(curentry) { nextentry = GetNextNotifyEntry(curentry,0); if (curentry->Unregistered) { curentry = nextentry; continue; } if (curentry->Handle == (HANDLE)hService) { // // Remove the entry from the notification list. // curentry->Unregistered = TRUE; curentry->Freed |= (PNP_UNREG_FREE|PNP_UNREG_CLASS); DeleteNotifyEntry(curentry,FALSE); // // Only log a warning if the PlugPlay service has not // already stopped. Otherwise, the client may actually // have tried to unregister after we were shut down. // if (CurrentServiceState != SERVICE_STOPPED && CurrentServiceState != SERVICE_STOP_PENDING) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_WARNINGS | DBGF_EVENT, "UMPNPMGR: Service '%ws' " "may have stopped without unregistering " "for DeviceInterfaceChange notification.\n", curentry->ClientName)); LogWarningEvent(WRN_STOPPED_SERVICE_REGISTERED, 1, curentry->ClientName); } } curentry = nextentry; } UnlockNotifyList(LockHeld); LockHeld = NULL; } } Clean0: LeaveCriticalSection(&RegistrationCS); } except (EXCEPTION_EXECUTE_HANDLER){ KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Exception in RegisterServiceNotification!!\n")); ASSERT(0); SetLastError(ERROR_EXCEPTION_IN_SERVICE); Status = CR_FAILURE; if (LockHeld) { UnlockNotifyList(LockHeld); } LeaveCriticalSection(&RegistrationCS); if (entry) { if (entry->ClientName) { HeapFree(ghPnPHeap, 0, entry->ClientName); } HeapFree(ghPnPHeap, 0, entry); } } return Status; } // RegisterServiceNotification CONFIGRET RegisterScmCallback( IN PSCMCALLBACK_ROUTINE pScCallback, IN PSCMAUTHENTICATION_CALLBACK pScAuthCallback ) /*++ Routine Description: This routine is called directly and privately by the service controller. It allows the SCM to dynamically provide this service with callback routines. Arguments: pScCallback - Specifies the entrypoint for the routine that should be used to have the service controller send special controls to a service (which ControlService would block), on behalf of the user-mode plug and play manager. pScAuthCallback - Specifies the entrypoint for the routine that should be used to retrieve the service status for a service. Return Value: Returns CR_SUCCESS. --*/ { ASSERT(pScCallback); ASSERT(pScAuthCallback); pServiceControlCallback = pScCallback; pSCMAuthenticate = pScAuthCallback; return CR_SUCCESS; } // RegisterScmCallback CONFIGRET UnRegisterScmCallback( VOID ) /*++ Routine Description: This routine is called directly and privately by the service controller. It allows the SCM to unregister the callback routines previously registered by RegisterScmCallback. Arguments: None. Return Value: Returns CR_SUCCESS. --*/ { pServiceControlCallback = NULL; pSCMAuthenticate = NULL; return CR_SUCCESS; } // UnRegisterScmCallback ULONG MapSCMControlsToControlBit( IN ULONG scmControls ) /*++ Routine Description: Returns a bitmask of control bits specifying ServiceList lists to which a service should be added or removed from, based on the controls currently accepted by the service. Arguments: scmControls - Specifies the service controls currently accepted by a service. Return Value: Returns a bitmask of control bits corresponding to entries in the ServiceList array of lists to which a service should be added or removed from, based on the controls currently accepted by the service. Notes: Services are added or removed from a ServiceList notification list by adding or removing the corresponding SERVICE_ACCEPT_* control from its list of accepted controls when calling SetServiceStatus(). The service control manager calls RegisterServiceNotification() as appropriate to register or unregister the service to receive that control. Currently, only SERVICE_ACCEPT_HARDWAREPROFILECHANGE and SERVICE_ACCEPT_POWEREVENT are supported. A service registers to receive the SERVICE_CONTROL_DEVICEEVENT control by calling RegisterDeviceNotification, and is stored in the appropriate TargetList or ClassList entry. --*/ { ULONG retBits=0; if (scmControls & SERVICE_ACCEPT_HARDWAREPROFILECHANGE) { retBits |= CBIT_HWPROFILE; } if (scmControls & SERVICE_ACCEPT_POWEREVENT) { retBits |= CBIT_POWEREVENT; } return retBits; } // MapSCMControlsToControlBit DWORD GetFirstPass( IN BOOL bQuery ) /*++ Routine Description: This routine retrieves the first class of handles to notify. The subsequent class of handles to notify should be retrieved by calling GetNextPass(...); Arguments: bQuery - If TRUE, starts with window handles, otherwise service handles. Return Value: Returns the first class of handles to notify. Notes: See GetNextPass() for the notification pass progression. --*/ { // // Since services are generally less likely to veto device event queries, we // first make sure that all windows succeed the query before notifying any // services. For non-query events, services should be the first to know. // return (bQuery) ? DEVICE_NOTIFY_WINDOW_HANDLE : DEVICE_NOTIFY_SERVICE_HANDLE; } DWORD GetNextPass( IN DWORD curPass, IN BOOL bQuery ) /*++ Routine Description: This routine retrieves the next class of handles to notify. If there is no subsequent class of handles to notify, PASS_COMPLETE is returned. Arguments: curPass Current pass. bQuery If TRUE, proceed from window handles to completion handles to service handles. Otherwise process in reverse. Return Value: Returns the subsequent pass. Notes: For query events, the notification pass progression is: DEVICE_NOTIFY_WINDOW_HANDLE, DEVICE_NOTIFY_COMPLETION_HANDLE, DEVICE_NOTIFY_SERVICE_HANDLE, PASS_COMPLETE For non-query events, the notification pass progression is: DEVICE_NOTIFY_SERVICE_HANDLE, DEVICE_NOTIFY_COMPLETION_HANDLE, DEVICE_NOTIFY_WINDOW_HANDLE, PASS_COMPLETE --*/ { if (bQuery) { if (curPass == DEVICE_NOTIFY_WINDOW_HANDLE ) { curPass = DEVICE_NOTIFY_COMPLETION_HANDLE; } else if (curPass == DEVICE_NOTIFY_COMPLETION_HANDLE) { curPass = DEVICE_NOTIFY_SERVICE_HANDLE; } else { curPass = PASS_COMPLETE; } } else { if (curPass == DEVICE_NOTIFY_SERVICE_HANDLE ) { curPass = DEVICE_NOTIFY_COMPLETION_HANDLE; } else if (curPass == DEVICE_NOTIFY_COMPLETION_HANDLE) { curPass = DEVICE_NOTIFY_WINDOW_HANDLE; } else { curPass = PASS_COMPLETE; } } return curPass; } BOOL NotifyEntryThisPass( IN PPNP_NOTIFY_ENTRY Entry, IN DWORD Pass ) { ASSERT(Pass != PASS_COMPLETE); return ((!(Entry->Unregistered)) && (GetPassFromEntry(Entry) == Pass)); } DWORD GetPassFromEntry( IN PPNP_NOTIFY_ENTRY Entry ) { return (Entry->Flags & DEVICE_NOTIFY_HANDLE_MASK); } BOOL EventIdFromEventGuid( IN CONST GUID *EventGuid, OUT LPDWORD EventId, OUT LPDWORD Flags, OUT LPDWORD ServiceControl ) /*++ Routine Description: This thread routine converts an event guid into the corresponding event id that user-mode code expects (used in BroadcastSystemMessage). Arguments: EventGuid Specifies an event guid. EventId Returns the id form (from dbt.h) of the guid in EventGuid. Flags Returns the flags that should be used when broadcasting this event. NOTE: device ARRIVAL and event CANCEL are considered "Queries" since the bottom level drivers need to be told first. Return Value: Currently returns TRUE/FALSE. Notes: Most users of this function call it mainly to retrieve the EventId. Those functions typically examine the returned flags only to check the BSF_QUERY flag (ie, they don't call BroadcastSystemMessage). Depending on whether BSF_QUERY is set, the notification lists will be walked forwards or backwards. We should really return something generic such as: [MSG_POST, MSG_QUERY, MSG_SEND] | [MSG_FORWARDS, MSG_BACKWARDS] Then we should implement a BsmFlagsFromMsgFlags function. --*/ { // // BSF_IGNORECURRENTTASK - Sent messages do not appear in the sending // processes message queue. // // BSF_QUERY - If any recipient vetoes the message by returning // the appropriate value, the broadcast is failed // (ie, BroadcastSystemMessage returns 0). // // BSF_NOHANG - Non-posted messages are automatically failed if // the window has not processed any available // messages within a system defined time (as of // 04/20/1999 this is 5 seconds). // (SendMessageTimeout: SMTO_ABORTIFHUNG) // // BSF_FORCEIFHUNG - Failures due to timeouts or hangs are instead // treated as successes. // // BSF_NOTIMEOUTIFNOTHUNG - If a window has not responded to the passed in // notification, but is actively processing // subsequent messages, then it is assumed to be // interacting with the user, in which case the // timeout is on hold. // (SendMessageTimeout: SMTO_NOTIMEOUTIFNOTHUNG) // // BSF_POSTMESSAGE - Message is posted, results ignored. Note that // a notification with private data in the lParam // *cannot* be posted - the OS does not make a // private copy, but rather treats the broadcast // as if it were a SendMessage if you try. // // BSF_ALLOWSFW - Windows that receive the broadcast are allowed // to become foreground windows. // // Also, DBT messages >= 0x8000 have lParams pointing to blocks of data that // need to be marshalled around. As user doesn't support "snapshotting" the // data for posts, we can't pass in BSF_POSTMESSAGE. // *Flags = BSF_IGNORECURRENTTASK; // // Standard (well-known) event guids. // if (GuidEqual(EventGuid, (LPGUID)&GUID_HWPROFILE_QUERY_CHANGE)) { *Flags |= BSF_QUERY | BSF_ALLOWSFW | BSF_FORCEIFHUNG | BSF_NOHANG; *EventId = DBT_QUERYCHANGECONFIG; *ServiceControl = SERVICE_CONTROL_HARDWAREPROFILECHANGE; } else if (GuidEqual(EventGuid, (LPGUID)&GUID_HWPROFILE_CHANGE_CANCELLED)) { *Flags |= BSF_POSTMESSAGE; *EventId = DBT_CONFIGCHANGECANCELED; *ServiceControl = SERVICE_CONTROL_HARDWAREPROFILECHANGE; } else if (GuidEqual(EventGuid, (LPGUID)&GUID_HWPROFILE_CHANGE_COMPLETE)) { *Flags |= BSF_POSTMESSAGE; *EventId = DBT_CONFIGCHANGED; *ServiceControl = SERVICE_CONTROL_HARDWAREPROFILECHANGE; } else if (GuidEqual(EventGuid, (LPGUID)&GUID_DEVICE_INTERFACE_ARRIVAL)) { *Flags |= BSF_NOHANG; *EventId = DBT_DEVICEARRIVAL; *ServiceControl = SERVICE_CONTROL_DEVICEEVENT; } else if (GuidEqual(EventGuid, (LPGUID)&GUID_DEVICE_INTERFACE_REMOVAL)) { // // NOTE - BSF_QUERY is set so that we run the list backwards. No actual // broadcasts are done on this Id. // *Flags |= BSF_NOHANG | BSF_QUERY; *EventId = DBT_DEVICEREMOVECOMPLETE; *ServiceControl = SERVICE_CONTROL_DEVICEEVENT; } else if (GuidEqual(EventGuid, (LPGUID)&GUID_TARGET_DEVICE_QUERY_REMOVE)) { *Flags |= BSF_QUERY | BSF_ALLOWSFW | BSF_FORCEIFHUNG | BSF_NOHANG; *EventId = DBT_DEVICEQUERYREMOVE; *ServiceControl = SERVICE_CONTROL_DEVICEEVENT; } else if (GuidEqual(EventGuid, (LPGUID)&GUID_TARGET_DEVICE_REMOVE_CANCELLED)) { *Flags |= BSF_NOHANG; *EventId = DBT_DEVICEQUERYREMOVEFAILED; *ServiceControl = SERVICE_CONTROL_DEVICEEVENT; } else if (GuidEqual(EventGuid, (LPGUID)&GUID_DEVICE_REMOVE_PENDING)) { // // NOTE - BSF_QUERY is set so that we run the list backwards. No actual // broadcasts are done on this Id. // *Flags |= BSF_NOHANG | BSF_QUERY; *EventId = DBT_DEVICEREMOVEPENDING; *ServiceControl = SERVICE_CONTROL_DEVICEEVENT; } else if (GuidEqual(EventGuid, (LPGUID)&GUID_TARGET_DEVICE_REMOVE_COMPLETE)) { // // NOTE - BSF_QUERY is set so that we run the list backwards. No actual // broadcasts are done on this Id. // *Flags |= BSF_NOHANG | BSF_QUERY; *EventId = DBT_DEVICEREMOVECOMPLETE; *ServiceControl = SERVICE_CONTROL_DEVICEEVENT; } else if (GuidEqual(EventGuid, (LPGUID)&GUID_DEVICE_ARRIVAL)) { *Flags |= BSF_NOHANG; *EventId = DBT_DEVICEARRIVAL; *ServiceControl = SERVICE_CONTROL_DEVICEEVENT; } else if (GuidEqual(EventGuid, (LPGUID)&GUID_DEVICE_ENUMERATED)) { *Flags = 0; *EventId = DBT_DEVICEARRIVAL; *ServiceControl = 0; // // Private event guids (kernel-mode pnp to user-mode pnp communication). // Setting EventId to zero causes ProcessDeviceEvent to swallow these // TargetDeviceChangeEvent events. // } else if (GuidEqual(EventGuid, (LPGUID)&GUID_DEVICE_SAFE_REMOVAL) || GuidEqual(EventGuid, (LPGUID)&GUID_DEVICE_EJECT_VETOED) || GuidEqual(EventGuid, (LPGUID)&GUID_DEVICE_REMOVAL_VETOED) || GuidEqual(EventGuid, (LPGUID)&GUID_DEVICE_WARM_EJECT_VETOED) || GuidEqual(EventGuid, (LPGUID)&GUID_DEVICE_STANDBY_VETOED) || GuidEqual(EventGuid, (LPGUID)&GUID_DEVICE_HIBERNATE_VETOED) || GuidEqual(EventGuid, (LPGUID)&GUID_DEVICE_KERNEL_INITIATED_EJECT) || GuidEqual(EventGuid, (LPGUID)&GUID_DEVICE_SURPRISE_REMOVAL) || GuidEqual(EventGuid, (LPGUID)&GUID_DRIVER_BLOCKED) || GuidEqual(EventGuid, (LPGUID)&GUID_DEVICE_INVALID_ID)) { *Flags = 0; *EventId = 0; *ServiceControl = 0; } else if (GuidEqual(EventGuid, (LPGUID)&GUID_PNP_CUSTOM_NOTIFICATION)) { // // Custom events cannot be failed (ie they aren't queries) // *EventId = DBT_CUSTOMEVENT; *Flags |= BSF_NOHANG; *ServiceControl = SERVICE_CONTROL_DEVICEEVENT; } else if (GuidEqual(EventGuid, (LPGUID)&GUID_PNP_POWER_NOTIFICATION)) { // // These are treated as custom too. // *EventId = DBT_CUSTOMEVENT; *Flags |= BSF_NOHANG; *ServiceControl = SERVICE_CONTROL_POWEREVENT; } else { // // Anything that makes it here is a bug. // ASSERT(GuidEqual(EventGuid, (LPGUID)&GUID_PNP_CUSTOM_NOTIFICATION)); *EventId = 0; *Flags = 0; *ServiceControl = 0; } return TRUE; } // EventIdFromEventGuid ULONG SendHotplugNotification( IN CONST GUID *EventGuid, IN PPNP_VETO_TYPE VetoType OPTIONAL, IN LPWSTR MultiSzList, IN OUT PULONG SessionId, IN ULONG Flags ) /*++ Routine Description: This routine kicks off a hotplug.dll process (if someone is logged in). We use a named pipe to comunicate with the user mode process and have it display the requested UI. Arguments: EventGuid - Specifies an event GUID. VetoType - For events requiring a vetoer, supplies the address of a variable containing the type of the component responsible for vetoing the request. MultiSzList - Supplies the MultiSz list to be sent to hotplu.dll. This is usually a device ID, possibly followed by a list of vetoers (which may or may not be device ID's). SessionId - Supplies the address of a variable containing the SessionId on which the hotplug dialog is to be displayed. If successful, the SessionId will contain the id of the session in which the device install client process was launched. Otherwise, will contain an invalid session id, INVALID_SESSION (0xFFFFFFFF). Flags - Specifies flags describing the behavior of the hotplug dialog. The following flags are currently defined: HOTPLUG_DISPLAY_ON_CONSOLE - if specified, the value in the SessionId variable will be ignored, and the hotplug dialog will always be displayed on the current active console session. Return Value: Currently returns TRUE/FALSE. Return Value: If the process was successfully created, the return value is TRUE. This routine doesn't wait until the process terminates. If we couldn't create the process (e.g., because no user was logged in), the return value is FALSE. --*/ { BOOL bStatus; STARTUPINFO StartupInfo; PROCESS_INFORMATION ProcessInfo; WCHAR szCmdLine[MAX_PATH]; WCHAR szHotPlugDllEntryPoint[80]; HANDLE hHotPlugPipe = NULL; HANDLE hHotPlugEvent = NULL; HANDLE hFinishEvents[2] = { NULL, NULL }; HANDLE hTemp, hUserToken = NULL; RPC_STATUS rpcStatus = RPC_S_OK; GUID newGuid; WCHAR szGuidString[MAX_GUID_STRING_LEN]; WCHAR szHotPlugPipeName[MAX_PATH]; WCHAR szHotPlugEventName[MAX_PATH]; ULONG ulHotPlugEventNameSize; ULONG ulMultiSzListSize; ULONG ulSize, ulSessionId = INVALID_SESSION; WIN32_FIND_DATA findData; LPWSTR pszName = NULL; PVOID lpEnvironment = NULL; OVERLAPPED overlapped; DWORD dwError, dwWait, dwBytes; HRESULT hr; size_t Len = 0; // // Check if we should skip client side UI. // if (gbSuppressUI) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_WARNINGS, "UMPNPMGR: SendHotplugNotification: " "UI has been suppressed, exiting.\n")); LogWarningEvent(WRN_HOTPLUG_UI_SUPPRESSED, 1, MultiSzList); return FALSE; } // // Initialize process, startup and overlapped structures, since we // depend on them being NULL during cleanup here on out. // ZeroMemory(&ProcessInfo, sizeof(ProcessInfo)); ZeroMemory(&StartupInfo, sizeof(StartupInfo)); ZeroMemory(&overlapped, sizeof(overlapped)); // // Assume failure // bStatus = FALSE; try { // // Determine the session to use, based on the supplied flags. // if (Flags & HOTPLUG_DISPLAY_ON_CONSOLE) { ulSessionId = GetActiveConsoleSessionId(); } else { ASSERT(*SessionId != INVALID_SESSION); ulSessionId = *SessionId; } // // Before doing anything, check that hotplug.dll is actually present on // the system. // szCmdLine[0] = L'\0'; ulSize = GetSystemDirectory(szCmdLine, MAX_PATH); if ((ulSize == 0) || ((ulSize + 2 + ARRAY_SIZE(HOTPLUG_DLL)) > MAX_PATH)) { return FALSE; } hr = StringCchCat(szCmdLine, SIZECHARS(szCmdLine), L"\\"); if (SUCCEEDED(hr)) { hr = StringCchCat(szCmdLine, SIZECHARS(szCmdLine), HOTPLUG_DLL); } if (FAILED(hr)) { return FALSE; } hTemp = FindFirstFile(szCmdLine, &findData); if(hTemp != INVALID_HANDLE_VALUE) { FindClose(hTemp); } else { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_WARNINGS | DBGF_EVENT, "UMPNPMGR: SendHotplugNotification: %ws not found, error = %d, exiting\n", szCmdLine, GetLastError())); LogWarningEvent(WRN_HOTPLUG_NOT_PRESENT, 1, szCmdLine); return FALSE; } // // Get the user access token for the active console session user. // if (!GetSessionUserToken(ulSessionId, &hUserToken)) { return FALSE; } // // Create a named pipe and event for communication and synchronization // with HotPlug. The event and named pipe must be global so that // UMPNPMGR can interact with a HotPlug client in a different session, // but it must still be unique for that session. Add a generated GUID // so the names are not entirely well-known. // rpcStatus = UuidCreate(&newGuid); if ((rpcStatus != RPC_S_OK) && (rpcStatus != RPC_S_UUID_LOCAL_ONLY)) { goto clean0; } if (StringFromGuid((LPGUID)&newGuid, szGuidString, MAX_GUID_STRING_LEN) != NO_ERROR) { goto clean0; } if (FAILED(StringCchPrintf( szHotPlugPipeName, SIZECHARS(szHotPlugPipeName), L"%ws_%d.%ws", PNP_HOTPLUG_PIPE, ulSessionId, szGuidString))) { goto clean0; } if (FAILED(StringCchPrintf( szHotPlugEventName, SIZECHARS(szHotPlugEventName), L"Global\\%ws_%d.%ws", PNP_HOTPLUG_EVENT, ulSessionId, szGuidString))) { goto clean0; } if (FAILED(StringCchLength( szHotPlugEventName, SIZECHARS(szHotPlugEventName), &Len))) { goto clean0; } ulHotPlugEventNameSize = (ULONG)((Len + 1) * sizeof(WCHAR)); // // Get the length of the multi-sz list. This is usually a device ID // possibly followed by a list of vetoers which may or may not be device // Id's // ulMultiSzListSize = 0; for (pszName = MultiSzList; *pszName; pszName += lstrlen(pszName) + 1) { ulMultiSzListSize += (lstrlen(pszName) + 1) * sizeof(WCHAR); } ulMultiSzListSize += sizeof(WCHAR); // // The approximate size of the named pipe output buffer should be large // enough to hold the greater of either: // - The name and size of the named event string, OR // - The type, size and contents of the multi-sz list. // ulSize = max(sizeof(ulHotPlugEventNameSize) + ulHotPlugEventNameSize, sizeof(PNP_VETO_TYPE) + sizeof(ulMultiSzListSize) + ulMultiSzListSize); // // Open up a named pipe to communicate with hotplug.dll. // if (CreateUserReadNamedPipe( hUserToken, szHotPlugPipeName, ulSize, &hHotPlugPipe) != NO_ERROR) { ASSERT(hHotPlugPipe == NULL); goto clean0; } // // Create an event that a user-client can synchronize with and set, and // that we will block on after we send all the device IDs to // hotplug.dll. // if (CreateUserSynchEvent( hUserToken, szHotPlugEventName, &hHotPlugEvent) != NO_ERROR) { ASSERT(hHotPlugEvent == NULL); goto clean0; } if (GuidEqual(EventGuid, (LPGUID)&GUID_DEVICE_EJECT_VETOED)) { // // GUID_DEVICE_EJECT_VETOED : HotPlugEjectVetoed // Expects veto information. // hr = StringCchCopyEx(szHotPlugDllEntryPoint, SIZECHARS(szHotPlugDllEntryPoint), TEXT("HotPlugEjectVetoed"), NULL, NULL, STRSAFE_NULL_ON_FAILURE); ASSERT(SUCCEEDED(hr)); ASSERT(VetoType); } else if (GuidEqual(EventGuid, (LPGUID)&GUID_DEVICE_REMOVAL_VETOED)) { // // GUID_DEVICE_REMOVAL_VETOED : HotPlugRemovalVetoed // Expects veto information. // hr = StringCchCopyEx(szHotPlugDllEntryPoint, SIZECHARS(szHotPlugDllEntryPoint), TEXT("HotPlugRemovalVetoed"), NULL, NULL, STRSAFE_NULL_ON_FAILURE); ASSERT(SUCCEEDED(hr)); ASSERT(VetoType); } else if (GuidEqual(EventGuid, (LPGUID)&GUID_DEVICE_STANDBY_VETOED)) { // // GUID_DEVICE_STANDBY_VETOED : HotPlugStandbyVetoed // Expects veto information. // hr = StringCchCopyEx(szHotPlugDllEntryPoint, SIZECHARS(szHotPlugDllEntryPoint), TEXT("HotPlugStandbyVetoed"), NULL, NULL, STRSAFE_NULL_ON_FAILURE); ASSERT(SUCCEEDED(hr)); ASSERT(VetoType); } else if (GuidEqual(EventGuid, (LPGUID)&GUID_DEVICE_HIBERNATE_VETOED)) { // // GUID_DEVICE_HIBERNATE_VETOED : HotPlugHibernateVetoed // Expects veto information. // hr = StringCchCopyEx(szHotPlugDllEntryPoint, SIZECHARS(szHotPlugDllEntryPoint), TEXT("HotPlugHibernateVetoed"), NULL, NULL, STRSAFE_NULL_ON_FAILURE); ASSERT(SUCCEEDED(hr)); ASSERT(VetoType); } else if (GuidEqual(EventGuid, (LPGUID)&GUID_DEVICE_WARM_EJECT_VETOED)) { // // GUID_DEVICE_WARM_EJECT_VETOED : HotPlugWarmEjectVetoed // Expects veto information. // hr = StringCchCopyEx(szHotPlugDllEntryPoint, SIZECHARS(szHotPlugDllEntryPoint), TEXT("HotPlugWarmEjectVetoed"), NULL, NULL, STRSAFE_NULL_ON_FAILURE); ASSERT(SUCCEEDED(hr)); ASSERT(VetoType); } else if (GuidEqual(EventGuid, (LPGUID)&GUID_DEVICE_SAFE_REMOVAL)) { // // GUID_DEVICE_SAFE_REMOVAL : HotPlugSafeRemovalNotification // No veto information. // hr = StringCchCopyEx(szHotPlugDllEntryPoint, SIZECHARS(szHotPlugDllEntryPoint), TEXT("HotPlugSafeRemovalNotification"), NULL, NULL, STRSAFE_NULL_ON_FAILURE); ASSERT(SUCCEEDED(hr)); ASSERT(VetoType == NULL); } else if (GuidEqual(EventGuid, (LPGUID)&GUID_DEVICE_SURPRISE_REMOVAL)) { // // GUID_DEVICE_SURPRISE_REMOVAL : HotPlugSurpriseWarn // No veto information. // hr = StringCchCopyEx(szHotPlugDllEntryPoint, SIZECHARS(szHotPlugDllEntryPoint), TEXT("HotPlugSurpriseWarn"), NULL, NULL, STRSAFE_NULL_ON_FAILURE); ASSERT(SUCCEEDED(hr)); ASSERT(VetoType == NULL); } else if (GuidEqual(EventGuid, (LPGUID)&GUID_DRIVER_BLOCKED)) { // // GUID_DRIVER_BLOCKED : HotPlugDriverBlocked // No veto information. // hr = StringCchCopyEx(szHotPlugDllEntryPoint, SIZECHARS(szHotPlugDllEntryPoint), TEXT("HotPlugDriverBlocked"), NULL, NULL, STRSAFE_NULL_ON_FAILURE); ASSERT(SUCCEEDED(hr)); ASSERT(VetoType == NULL); } else if (GuidEqual(EventGuid, (LPGUID)&GUID_DEVICE_INVALID_ID)) { // // GUID_DEVICE_INVALID_ID : HotPlugChildWithInvalidId // No veto information. // hr = StringCchCopyEx(szHotPlugDllEntryPoint, SIZECHARS(szHotPlugDllEntryPoint), TEXT("HotPlugChildWithInvalidId"), NULL, NULL, STRSAFE_NULL_ON_FAILURE); ASSERT(SUCCEEDED(hr)); ASSERT(VetoType == NULL); } else { // // Unknown device event. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_EVENT, "UMPNPMGR: SendHotplugNotification: " "Unknown device event!\n")); ASSERT(0); goto clean0; } // // Attempt to create the user's environment block. If for some reason we // can't, we'll just have to create the process without it. // if (!CreateEnvironmentBlock(&lpEnvironment, hUserToken, FALSE)) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_EVENT, "UMPNPMGR: SendHotplugNotification: " "Failed to allocate environment block, error = %d!\n", GetLastError())); lpEnvironment = NULL; } // // Launch hotplug.dll using rundll32.exe, passing it the pipe name. // "rundll32.exe hotplug.dll, " // if (FAILED(StringCchPrintf( szCmdLine, SIZECHARS(szCmdLine), TEXT("%ws %ws,%ws %ws"), RUNDLL32_EXE, HOTPLUG_DLL, szHotPlugDllEntryPoint, szHotPlugPipeName))) { goto clean0; } StartupInfo.cb = sizeof(StartupInfo); StartupInfo.wShowWindow = SW_SHOW; StartupInfo.lpDesktop = DEFAULT_INTERACTIVE_DESKTOP; // WinSta0\Default // // CreateProcessAsUser will create the process in the session // specified by the by user-token. // if (!CreateProcessAsUser(hUserToken, // hToken NULL, // lpApplicationName szCmdLine, // lpCommandLine NULL, // lpProcessAttributes NULL, // lpThreadAttributes FALSE, // bInheritHandles CREATE_UNICODE_ENVIRONMENT | DETACHED_PROCESS, // dwCreationFlags lpEnvironment, // lpEnvironment NULL, // lpDirectory &StartupInfo, // lpStartupInfo &ProcessInfo // lpProcessInfo )) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT | DBGF_ERRORS, "UMPNPMGR: SendHotplugNotification: " "Create rundll32 process failed, error = %d\n", GetLastError())); goto clean0; } ASSERT(ProcessInfo.hProcess); ASSERT(ProcessInfo.hThread); // // Create an event for use with overlapped I/O - no security, manual // reset, not signalled, no name. // overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (overlapped.hEvent == NULL) { goto clean0; } // // Connect to the newly created named pipe. If hotplug is not already // connected to the named pipe, then ConnectNamedPipe() will fail with // ERROR_IO_PENDING, and we will wait on the overlapped event. If // newdev is already connected, it will fail with ERROR_PIPE_CONNECTED. // Note however that neither of these is an error condition. // if (!ConnectNamedPipe(hHotPlugPipe, &overlapped)) { // // Overlapped ConnectNamedPipe should always return FALSE on // success. Check the last error to see what really happened. // dwError = GetLastError(); if (dwError == ERROR_IO_PENDING) { // // I/O is pending, wait up to one minute for the client to // connect, also wait on the process in case it terminates // unexpectedly. // hFinishEvents[0] = overlapped.hEvent; hFinishEvents[1] = ProcessInfo.hProcess; dwWait = WaitForMultipleObjects(2, hFinishEvents, FALSE, PNP_PIPE_TIMEOUT); // 60 seconds if (dwWait == WAIT_OBJECT_0) { // // The overlapped I/O operation completed. Check the status // of the operation. // if (!GetOverlappedResult(hHotPlugPipe, &overlapped, &dwBytes, FALSE)) { goto clean0; } } else { // // Either the connection timed out, or the client process // exited. Cancel pending I/O against the pipe, and quit. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_ERRORS, "UMPNPMGR: SendHotPlugNotification: " "Connect timed out, or client process exited!\n")); CancelIo(hHotPlugPipe); goto clean0; } } else if (dwError != ERROR_PIPE_CONNECTED) { // // If the last error indicates anything other than pending I/O, // or that The client is already connected to named pipe, fail. // goto clean0; } } else { // // ConnectNamedPipe should not return anything but FALSE in // overlapped mode. // goto clean0; } // // The client is now connected to the named pipe. // Close the overlapped event. // CloseHandle(overlapped.hEvent); overlapped.hEvent = NULL; // // The first data in the pipe will be the length of the name of the // event that will be used to sync up umpnpmgr.dll and hotplug.dll. // if (!WriteFile(hHotPlugPipe, &ulHotPlugEventNameSize, sizeof(ulHotPlugEventNameSize), &ulSize, NULL)) { LogErrorEvent(ERR_WRITING_SURPRISE_REMOVE_PIPE, GetLastError(), 0); goto clean0; } // // The next data in the pipe will be the name of the event that will // be used to sync up umpnpmgr.dll and hotplug.dll. // if (!WriteFile(hHotPlugPipe, (LPCVOID)szHotPlugEventName, ulHotPlugEventNameSize, &ulSize, NULL)) { LogErrorEvent(ERR_WRITING_SURPRISE_REMOVE_PIPE, GetLastError(), 0); goto clean0; } if (ARGUMENT_PRESENT(VetoType)) { // // For the notification types expecting veto information, // send the Veto type to the client. // if (!WriteFile(hHotPlugPipe, (LPCVOID)VetoType, sizeof(PNP_VETO_TYPE), &ulSize, NULL)) { LogErrorEvent(ERR_WRITING_SURPRISE_REMOVE_PIPE, GetLastError(), 0); goto clean0; } } // // Send the string length to the client // if (!WriteFile(hHotPlugPipe, (LPCVOID)&ulMultiSzListSize, sizeof(ulMultiSzListSize), &ulSize, NULL)) { LogErrorEvent(ERR_WRITING_SURPRISE_REMOVE_PIPE, GetLastError(), 0); goto clean0; } // // Now send over the entire string // if (!WriteFile(hHotPlugPipe, MultiSzList, ulMultiSzListSize, &ulSize, NULL)) { LogErrorEvent(ERR_WRITING_SURPRISE_REMOVE_PIPE, GetLastError(), 0); goto clean0; } // // When we are done writing, we need to close the pipe handles so that // the client will get a ReadFile error and know that we are finished. // if (hHotPlugPipe) { CloseHandle(hHotPlugPipe); hHotPlugPipe = NULL; } // // Wait for hotplug.dll to respond by setting the event before before // returning. Also wait on the process as well, to catch the case where // the process crashes (or goes away) without signaling the device // install event. // hFinishEvents[0] = hHotPlugEvent; hFinishEvents[1] = ProcessInfo.hProcess; WaitForMultipleObjects(2, hFinishEvents, FALSE, INFINITE); bStatus = TRUE; clean0: NOTHING; } except (EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Exception in SendHotPlugNotification!!\n")); ASSERT(0); bStatus = FALSE; // // Reference the following variables so the compiler will respect // statement ordering w.r.t. their assignment. // lpEnvironment = lpEnvironment; ProcessInfo.hThread = ProcessInfo.hThread; ProcessInfo.hProcess = ProcessInfo.hProcess; hUserToken = hUserToken; hHotPlugPipe = hHotPlugPipe; hHotPlugEvent = hHotPlugEvent; } if (lpEnvironment) { DestroyEnvironmentBlock(lpEnvironment); } if (ProcessInfo.hThread) { CloseHandle(ProcessInfo.hThread); } if (ProcessInfo.hProcess) { CloseHandle(ProcessInfo.hProcess); } if (hUserToken) { CloseHandle(hUserToken); } if (overlapped.hEvent) { CloseHandle(overlapped.hEvent); } if (hHotPlugPipe) { CloseHandle(hHotPlugPipe); } if (hHotPlugEvent) { CloseHandle(hHotPlugEvent); } if (!bStatus) { *SessionId = INVALID_SESSION; } else { *SessionId = ulSessionId; } return bStatus; } // SendHotplugNotification ULONG CheckEjectPermissions( IN LPWSTR DeviceId, OUT PPNP_VETO_TYPE VetoType OPTIONAL, OUT LPWSTR VetoName OPTIONAL, IN OUT PULONG VetoNameLength OPTIONAL ) /*++ Routine Description: Checks that the user has eject permissions for the specified device. Arguments: DeviceId - Specifies the device instance id of the device for which eject permissions are to be checked. VetoType - Supplies the address of a variable to receive, upon failure, the type of the component responsible for vetoing the request. VetoName - Supplies the address of a variable to receive, upon failure, the name of the component responsible for vetoing the request. VetoNameLength - Supplies the address of a variable specifying the size of the of buffer specified by the VetoName parameter. Upon failure, this address will specify the length of the string stored in that buffer by this routine. Return Value: FALSE if the eject should be blocked, TRUE otherwise. Note: This routine is called while processing a kernel-initiated ejection event. On this side of the event, we are NOT in the context of the user who initiated the ejection, but since only the active console user was allowed to initiate the request that triggered this event, we use the access token of the active console user for the check on this side also. (should the active console user change between the request and this event, this would check that the user that the current active console user has eject permissions; this is still a valid thing to do since it is the console user who will receive the ejected hardware) --*/ { BOOL bResult, bDockDevice; ULONG ulPropertyData, ulDataSize, ulDataType; ULONG ulTransferLen, ulConsoleSessionId; HANDLE hUserToken = NULL; // // Is this a dock? // bDockDevice = FALSE; ulDataSize = ulTransferLen = sizeof(ULONG); if (CR_SUCCESS == PNP_GetDeviceRegProp(NULL, DeviceId, CM_DRP_CAPABILITIES, &ulDataType, (LPBYTE)&ulPropertyData, &ulTransferLen, &ulDataSize, 0)) { if (ulPropertyData & CM_DEVCAP_DOCKDEVICE) { // // Undocking (ie ejecting a dock) uses a special privilege. // bDockDevice = TRUE; } } else { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: PNP_GetDeviceRegProp failed, error = %d\n", GetLastError())); return FALSE; } ulConsoleSessionId = GetActiveConsoleSessionId(); if ((IsSessionLocked(ulConsoleSessionId)) || (!GetSessionUserToken(ulConsoleSessionId, &hUserToken))) { // // If the console session is locked or no user is logged in, supply no // user token, and verify strictly against the policy permissions // required to eject the dock or device, absent a user. // hUserToken = NULL; } bResult = VerifyKernelInitiatedEjectPermissions(hUserToken, bDockDevice); if (bResult == FALSE) { if (ARGUMENT_PRESENT(VetoType)) { *VetoType = PNP_VetoInsufficientRights; } if (ARGUMENT_PRESENT(VetoNameLength)) { // // VetoNameLength is in characters. // if (ARGUMENT_PRESENT(VetoName) && *VetoNameLength) { *VetoName = UNICODE_NULL; } *VetoNameLength = 0; } } if (hUserToken) { CloseHandle(hUserToken); } return bResult; } // CheckEjectPermissions //--------------------------------------------------------------------------- // Private Utility Routines //--------------------------------------------------------------------------- VOID LogErrorEvent( DWORD dwEventID, DWORD dwError, WORD nStrings, ... ) { HANDLE hEventLog; LPWSTR *paStrings; va_list pArg; DWORD index; hEventLog = RegisterEventSource(NULL, TEXT("PlugPlayManager")); if (hEventLog == NULL) { return; } if (nStrings) { paStrings = HeapAlloc(ghPnPHeap, 0, nStrings * sizeof(LPWSTR)); if (paStrings != NULL) { va_start(pArg, nStrings); for (index = 0; index < nStrings; index++) { paStrings[index] = va_arg(pArg, LPWSTR); } va_end(pArg); ReportEvent( hEventLog, EVENTLOG_ERROR_TYPE, 0, // wCategory dwEventID, // dwEventID NULL, // lpUserSID nStrings, // wNumStrings sizeof(dwError), // dwDataSize paStrings, // lpStrings &dwError); // lpRawData HeapFree(ghPnPHeap, 0, paStrings); } } else { ReportEvent( hEventLog, EVENTLOG_ERROR_TYPE, 0, // wCategory dwEventID, // dwEventID NULL, // lpUserSID 0, // wNumStrings sizeof(dwError), // dwDataSize NULL, // lpStrings &dwError); // lpRawData } DeregisterEventSource(hEventLog); } VOID LogWarningEvent( DWORD dwEventID, WORD nStrings, ... ) { HANDLE hEventLog; LPWSTR *paStrings; va_list pArg; DWORD index; hEventLog = RegisterEventSource(NULL, TEXT("PlugPlayManager")); if (hEventLog == NULL) { return; } paStrings = HeapAlloc(ghPnPHeap, 0, nStrings * sizeof(LPWSTR)); if (paStrings != NULL) { va_start(pArg, nStrings); for (index = 0; index < nStrings; index++) { paStrings[index] = va_arg(pArg, LPWSTR); } va_end(pArg); ReportEvent( hEventLog, EVENTLOG_WARNING_TYPE, 0, // wCategory dwEventID, // dwEventID NULL, // lpUserSID nStrings, // wNumStrings 0, // dwDataSize paStrings, // lpStrings NULL); // lpRawData HeapFree(ghPnPHeap, 0, paStrings); } DeregisterEventSource(hEventLog); } BOOL LockNotifyList( IN LOCKINFO *Lock ) { return LockPrivateResource(Lock); } VOID UnlockNotifyList( IN LOCKINFO *Lock ) { UnlockPrivateResource(Lock); } PPNP_NOTIFY_LIST GetNotifyListForEntry( IN PPNP_NOTIFY_ENTRY Entry ) /*++ Routine Description: This routine retrives the notification list that the given entry is in, based on the list entry signature. If this entry has been removed from a notification list (via DeleteNotifyEntry), NULL is returned. Arguments: Entry - Specifies a notification entry for the coresponding notification list is to be found. Return Value: Returns the notification list this entry is a member of, or NULL if the entry is not in any notification list. --*/ { PPNP_NOTIFY_LIST notifyList; if (!Entry) { return NULL; } // // Retrieve the list pointer from the entry signature. // The signature contains two pieces of data. // // It is a ULONG, with byte 0 being a list index and // bytes 1,2,3 being the signature // We mask and compare the top 3 bytes to find which list // then return the address of the list to lock based on the // index in the bottom byte. // switch (Entry->Signature & LIST_ENTRY_SIGNATURE_MASK) { case TARGET_ENTRY_SIGNATURE: notifyList = &TargetList[Entry->Signature & LIST_ENTRY_INDEX_MASK]; break; case CLASS_ENTRY_SIGNATURE: notifyList = &ClassList[Entry->Signature & LIST_ENTRY_INDEX_MASK]; break; case SERVICE_ENTRY_SIGNATURE: notifyList = &ServiceList[Entry->Signature & LIST_ENTRY_INDEX_MASK]; break; case 0: // // If the entry Signature is 0, this entry has been removed from it's // notification list. // notifyList = NULL; break; default: // // Should never get here! // ASSERT (FALSE); notifyList = NULL; break; } return notifyList; } // GetNotifyListForEntry BOOL DeleteNotifyEntry( IN PPNP_NOTIFY_ENTRY Entry, IN BOOLEAN RpcNotified ) /*++ Routine Description: This routine removes an entry from a notification list and frees the memory for that entry. Arguments: Entry - Specifies an entry in one of the notification lists that is to be deleted. Return Value: Returns TRUE or FALSE. --*/ { PPNP_NOTIFY_ENTRY previousEntry = Entry->Previous; if (!(Entry->Freed & DEFER_NOTIFY_FREE)) { if (previousEntry == NULL) { return FALSE; } // // hook up the forward and backwards pointers // previousEntry->Next = Entry->Next; if (Entry->Next) { ((PPNP_NOTIFY_ENTRY)(Entry->Next))->Previous = previousEntry; } // // Clear the entry signature now that it is no longer part of any list. // Entry->Signature = 0; } if (RpcNotified || (Entry->Freed & DEFER_NOTIFY_FREE)) { if (Entry->ClientName) { HeapFree (ghPnPHeap,0,Entry->ClientName); Entry->ClientName = NULL; } HeapFree(ghPnPHeap, 0, Entry); }else { // //Let the entry dangle until the RPC rundown // Entry->Freed |= DEFER_NOTIFY_FREE; } return TRUE; } // DeleteNotifyEntry; VOID AddNotifyEntry( IN PPNP_NOTIFY_LIST NotifyList, IN PPNP_NOTIFY_ENTRY NewEntry ) /*++ Routine Description: This routine inserts an entry at the tail of a notification list. Arguments: Entry - Specifies an entry to be added to a notification list Return Value: None. --*/ { PPNP_NOTIFY_ENTRY previousEntry = NULL, currentEntry = NULL; // // Skip to the last entry in this list. // previousEntry = (PPNP_NOTIFY_ENTRY)NotifyList; currentEntry = previousEntry->Next; while (currentEntry) { previousEntry = currentEntry; currentEntry = currentEntry->Next; } // // Attach this entry to the end of the list. // previousEntry->Next = NewEntry; NewEntry->Previous = previousEntry; NewEntry->Next = NULL; return; } // AddNotifyEntry; PPNP_NOTIFY_ENTRY GetNextNotifyEntry( IN PPNP_NOTIFY_ENTRY Entry, IN DWORD Flags ) /*++ Routine Description: Returns the next entry in the notification list for the entry specified, in the direction specified by the Flags. Arguments: Entry - Specified a notification list entry. Flags - Specifies BSF_* flags indicating the direction the list is to be traversed. If BSF_QUERY is specified, the previous list entry is returned, otherwise returns the next entry forward in the list. Return Value: Returns the next entry in the notification list, or NULL if no such entry exists. --*/ { PPNP_NOTIFY_ENTRY nextEntry = NULL; if (Entry == NULL) { return Entry; } // // Determine if this is a QUERY (or a resume). In which case // we go back -> front. // if (Flags & BSF_QUERY) { nextEntry = Entry->Previous; // // If the previous entry is the list head, there is no next entry. // if ((nextEntry == NULL) || (nextEntry->Previous == NULL)) { return NULL; } } else { nextEntry = Entry->Next; } return nextEntry; } PPNP_NOTIFY_ENTRY GetFirstNotifyEntry( IN PPNP_NOTIFY_LIST List, IN DWORD Flags ) /*++ Routine Description: Returns the first entry in the specified notification list, starting from the direction specified by the Flags. Arguments: List - Specified a notification list. Flags - Specifies BSF_* flags indicating the end of the list from which the first entry is to be retrieved. If BSF_QUERY is specified, the last list entry is returned, otherwise returns the first entry in the list. Return Value: Returns the first entry in the notification list, or NULL if no such entry exists. --*/ { PPNP_NOTIFY_ENTRY previousEntry = NULL, currentEntry = NULL, firstEntry = NULL; // // Determine if this is a QUERY (or a resume). In which case // we go back -> front. // if (Flags & BSF_QUERY) { // // Skip to the last entry in this list. // previousEntry = (PPNP_NOTIFY_ENTRY)List; currentEntry = previousEntry->Next; while (currentEntry) { previousEntry = currentEntry; currentEntry = currentEntry->Next; } if (!previousEntry->Previous) { // // If the list is empty, there is no first entry. // firstEntry = NULL; } else { firstEntry = previousEntry; } } else { firstEntry = (PPNP_NOTIFY_ENTRY)List->Next; } return firstEntry; } ULONG HashString( IN LPWSTR String, IN ULONG Buckets ) /*++ Routine Description: This routine performs a quick and dirty hash of a unicode string. Arguments: String - Null-terminated unicode string to perform hash on. Buckets - Number of hashing buckets. Return Value: Returns a hash value between 0 and Buckets. --*/ { LPWSTR p = String; ULONG hash = 0; while (*p) { hash ^= *p; p++; } hash = hash % Buckets; return hash; } // HashString DWORD MapQueryEventToCancelEvent( IN DWORD QueryEventId ) /*++ Routine Description: This routine maps a query device event id (such as query remove) to the corresponding cancel device event id (such as cancel remove). The event ids are based on DBT_Xxx values from DBT.H. Arguments: QueryEventId - A DBT_Xxx query type device event id. Return Value: Returns the corresponding cancel device event id or -1 if it fails. --*/ { DWORD cancelEventId; switch (QueryEventId) { case DBT_QUERYCHANGECONFIG: cancelEventId = DBT_CONFIGCHANGECANCELED; break; case DBT_DEVICEQUERYREMOVE: cancelEventId = DBT_DEVICEQUERYREMOVEFAILED; break; case PBT_APMQUERYSUSPEND: cancelEventId = PBT_APMQUERYSUSPENDFAILED; break; case PBT_APMQUERYSTANDBY: cancelEventId = PBT_APMQUERYSTANDBYFAILED; default: cancelEventId = (DWORD)-1; break; } return cancelEventId; } // MapQueryEventToCancelEvent VOID FixUpDeviceId( IN OUT LPWSTR DeviceId ) /*++ Routine Description: This routine copies a device id, fixing it up as it does the copy. 'Fixing up' means that the string is made upper-case, and that the following character ranges are turned into underscores (_): c <= 0x20 (' ') c > 0x7F c == 0x2C (',') (NOTE: This algorithm is also implemented in the Config Manager APIs, and must be kept in sync with that routine. To maintain device identifier compatibility, these routines must work the same as Win95.) Arguments: Return Value: None. --*/ { PWCHAR p; CharUpper(DeviceId); p = DeviceId; while (*p) { if ((*p <= TEXT(' ')) || (*p > (WCHAR)0x7F) || (*p == TEXT(','))) { *p = TEXT('_'); } p++; } } // FixUpDeviceId BOOL GetWindowsExeFileName( IN HWND hWnd, OUT LPWSTR lpszFileName, IN OUT PULONG pulFileNameLength ) /*++ Routine Description: This routine retrieves the module file name for the process that the specified window belongs to. Arguments: hWnd - Supplies the handle to the window whose process module file name is to be retrieved. lpszFileName - Supplies the address of a variable to receive, upon success, the module file name of the window's process. pulFileNameLength - Supplies the address of a variable specifying the size of the of buffer specified by the lpszFileName parameter. Upon success, this address will specify the length of the string stored in that buffer by this routine. Return Value: Returns TRUE. Notes: Not implemented. Currently returns a NULL string for the file name. --*/ { UNREFERENCED_PARAMETER(hWnd); if ((!ARGUMENT_PRESENT(lpszFileName)) || (!ARGUMENT_PRESENT(pulFileNameLength))) { return FALSE; } if (*pulFileNameLength > 0) { *pulFileNameLength = 0; lpszFileName[0] = UNICODE_NULL; } return TRUE; } // GetWindowsExeFileName BOOL InitializeHydraInterface( VOID ) /*++ Routine Description: This routine loads the terminal services support libraries and locates required function entrypoints. Arguments: None. Return Value: Returns TRUE if the terminal services support libraries were successfully loaded, and entrypoints located. --*/ { BOOL Status = FALSE; // // Load the base library that contains the user message dispatch routines // for Terminal Services. // ghWinStaLib = LoadLibrary(WINSTA_DLL); if (!ghWinStaLib) { return FALSE; } fpWinStationSendWindowMessage = (FP_WINSTASENDWINDOWMESSAGE)GetProcAddress( ghWinStaLib, "WinStationSendWindowMessage"); fpWinStationBroadcastSystemMessage = (FP_WINSTABROADCASTSYSTEMMESSAGE)GetProcAddress( ghWinStaLib, "WinStationBroadcastSystemMessage"); fpWinStationQueryInformationW = (FP_WINSTAQUERYINFORMATIONW)GetProcAddress( ghWinStaLib, "WinStationQueryInformationW"); if (!fpWinStationSendWindowMessage || !fpWinStationBroadcastSystemMessage || !fpWinStationQueryInformationW) { goto Clean0; } // // Load the library that contains Terminal Services support routines. // ghWtsApi32Lib = LoadLibrary(WTSAPI32_DLL); if (!ghWtsApi32Lib) { goto Clean0; } fpWTSQuerySessionInformation = (FP_WTSQUERYSESSIONINFORMATION)GetProcAddress( ghWtsApi32Lib, "WTSQuerySessionInformationW"); fpWTSFreeMemory = (FP_WTSFREEMEMORY)GetProcAddress( ghWtsApi32Lib, "WTSFreeMemory"); if (!fpWTSQuerySessionInformation || !fpWTSFreeMemory) { goto Clean0; } Status = TRUE; Clean0: ASSERT(Status == TRUE); if (!Status) { // // Something failed. Unload all libraries. // fpWinStationSendWindowMessage = NULL; fpWinStationBroadcastSystemMessage = NULL; fpWinStationQueryInformationW = NULL; if (ghWinStaLib) { FreeLibrary(ghWinStaLib); ghWinStaLib = NULL; } fpWTSQuerySessionInformation = NULL; fpWTSFreeMemory = NULL; if (ghWtsApi32Lib) { FreeLibrary(ghWtsApi32Lib); ghWtsApi32Lib = NULL; } } return Status; } // InitializeHydraInterface BOOL GetClientName( IN PPNP_NOTIFY_ENTRY entry, OUT LPWSTR lpszClientName, IN OUT PULONG pulClientNameLength ) /*++ Routine Description: This routine retrieves the client name for the specified notification list entry. Arguments: entry - Specifies a notification list entry. lpszClientName - Supplies the address of a variable to receive, the client name of the window's process. pulClientNameLength - Supplies the address of a variable specifying the size of the of buffer specified by the lpszFileName parameter. Upon return, this address will specify the length of the string stored in that buffer by this routine. Return Value: Returns TRUE. --*/ { size_t BufferLen = 0, ClientNameLen = 0; // // Validate parameters. // if ((!ARGUMENT_PRESENT(lpszClientName)) || (!ARGUMENT_PRESENT(pulClientNameLength)) || (*pulClientNameLength == 0)) { return FALSE; } // // Copy as much of the client name that will fit into the specified buffer, // (including the NULL terminating char). // BufferLen = *pulClientNameLength; *pulClientNameLength = 0; *lpszClientName = L'\0'; if ((!ARGUMENT_PRESENT(entry)) || (entry->ClientName == NULL)) { return FALSE; } ASSERT(BufferLen > 0); // // Copy client name to specified buffer, allow truncation. // if (FAILED(StringCchCopyEx( lpszClientName, BufferLen, entry->ClientName, NULL, NULL, STRSAFE_IGNORE_NULLS))) { // // Failure from truncation can be handled safely. // NOTHING; } // // Count the number of characters copied to the buffer. // if (FAILED(StringCchLength( lpszClientName, BufferLen, &ClientNameLen))) { *lpszClientName = L'\0'; return FALSE; } // // The size returned does not include the terminating NULL. // ASSERT(ClientNameLen < MAX_SERVICE_NAME_LEN); *pulClientNameLength = (ULONG)ClientNameLen; return TRUE; } // GetClientName void __RPC_USER PNP_NOTIFICATION_CONTEXT_rundown( PPNP_NOTIFICATION_CONTEXT hEntry ) /*++ Routine Description: Rundown routine for RPC. This will get called if a client/server pipe breaks without unregistering a notification. If a notification is in progress when rundown is called, the entry is kept in a deferred list, and this routines is explicitly called again for the deferred entry, after notification is complete. This routine frees the memory associated with the notification entry that is no longer needed. Arguments: hEntry - Specifies a notification entry for which RPC has requested rundown. Return Value: None. --*/ { PPNP_NOTIFY_LIST notifyList = NULL; PPNP_NOTIFY_ENTRY node; PPNP_DEFERRED_LIST rundownNode; BOOLEAN bLocked = FALSE; KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_WARNINGS | DBGF_EVENT, "UMPNPMGR: Cleaning up broken pipe\n")); try { EnterCriticalSection(&RegistrationCS); node = (PPNP_NOTIFY_ENTRY) hEntry; if (gNotificationInProg != 0) { // // Before freeing the entry, we need to make sure that it's not sitting // around in the deferred RegisterList or UnregisterList. // if (RegisterList != NULL) { // // Check to see if this entry is in the deferred RegisterList. // PPNP_DEFERRED_LIST currReg,prevReg; currReg = RegisterList; prevReg = NULL; while (currReg) { ASSERT(currReg->Entry->Unregistered); if (currReg->Entry == node) { // // Remove this entry from the deferred RegisterList. // if (prevReg) { prevReg->Next = currReg->Next; } else { RegisterList = currReg->Next; } HeapFree(ghPnPHeap, 0, currReg); if (prevReg) { currReg = prevReg->Next; } else { currReg = RegisterList; } } else { prevReg = currReg; currReg = currReg->Next; } } } if (UnregisterList != NULL) { // // Check to see if this entry is in the deferred UnregisterList. // PPNP_DEFERRED_LIST currUnreg,prevUnreg; currUnreg = UnregisterList; prevUnreg = NULL; while (currUnreg) { ASSERT(currUnreg->Entry->Unregistered); if (currUnreg->Entry == node) { // // Remove this entry from the deferred UnregisterList. // if (prevUnreg) { prevUnreg->Next = currUnreg->Next; } else { UnregisterList = currUnreg->Next; } HeapFree(ghPnPHeap, 0, currUnreg); if (prevUnreg) { currUnreg = prevUnreg->Next; } else { currUnreg = UnregisterList; } } else { prevUnreg = currUnreg; currUnreg = currUnreg->Next; } } } // // If the entry to be rundown is part of a notification list, make // sure it does not get notified. // notifyList = GetNotifyListForEntry(node); if (notifyList) { LockNotifyList(¬ifyList->Lock); bLocked = TRUE; node->Unregistered = TRUE; UnlockNotifyList(¬ifyList->Lock); bLocked = FALSE; } // // Delay rundown of this entry until after the notification in // progress is complete. // rundownNode = (PPNP_DEFERRED_LIST) HeapAlloc(ghPnPHeap, 0, sizeof (PNP_DEFERRED_LIST)); if (!rundownNode) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_WARNINGS, "UMPNPMGR: Error allocating deferred list entry during RPC rundown!\n")); goto Clean0; } rundownNode->hBinding = 0; rundownNode->Entry = node; rundownNode->Next = RundownList; RundownList = rundownNode; } else { if (!(node->Freed & DEFER_NOTIFY_FREE)) { // // This entry is still in a notification list. // notifyList = GetNotifyListForEntry(node); ASSERT(notifyList); if (notifyList) { // // Lock the notification list and delete this entry. // LockNotifyList (¬ifyList->Lock); bLocked = TRUE; } node->Freed |= (PNP_UNREG_FREE|PNP_UNREG_RUNDOWN); DeleteNotifyEntry (node,TRUE); if (notifyList) { UnlockNotifyList (¬ifyList->Lock); bLocked = FALSE; } } else { // // This node has been removed from the list, and should just be deleted // DeleteNotifyEntry (node,TRUE); } } Clean0: LeaveCriticalSection(&RegistrationCS); } except (EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_WARNINGS, "UMPNPMGR: Exception during PNP_NOTIFICATION_CONTEXT_rundown!\n")); ASSERT(0); if (bLocked) { UnlockNotifyList (¬ifyList->Lock); } LeaveCriticalSection(&RegistrationCS); } return; } // PNP_NOTIFICATION_CONTEXT_rundown DWORD LoadDeviceInstaller( VOID ) /*++ Routine Description: This routine loads setupapi.dll and retrieves the necessary device install entrypoints. It also creates two named events used to communicate with the client-side UI in the case where there's a user logged in. If setupapi.dll is already loaded, it simply returns success. Arguments: None Return Value: If successful, NO_ERROR is returned. Otherwise, a Win32 error code is returned indicating the cause of failure. --*/ { DWORD Err = NO_ERROR; DWORD SetupGlobalFlags; if (ghDeviceInstallerLib) { return NO_ERROR; } ghDeviceInstallerLib = LoadLibrary(SETUPAPI_DLL); if (!ghDeviceInstallerLib) { return GetLastError(); } try { // // Locate the SETUPAPI entrypoints required to perform device // installation. // fpCreateDeviceInfoList = (FP_CREATEDEVICEINFOLIST)GetProcAddress( ghDeviceInstallerLib, "SetupDiCreateDeviceInfoList"); if (!fpCreateDeviceInfoList) { goto HitFailure; } fpOpenDeviceInfo = (FP_OPENDEVICEINFO)GetProcAddress( ghDeviceInstallerLib, "SetupDiOpenDeviceInfoW"); if (!fpOpenDeviceInfo) { goto HitFailure; } fpBuildDriverInfoList = (FP_BUILDDRIVERINFOLIST)GetProcAddress( ghDeviceInstallerLib, "SetupDiBuildDriverInfoList"); if (!fpBuildDriverInfoList) { goto HitFailure; } fpDestroyDeviceInfoList = (FP_DESTROYDEVICEINFOLIST)GetProcAddress( ghDeviceInstallerLib, "SetupDiDestroyDeviceInfoList"); if (!fpDestroyDeviceInfoList) { goto HitFailure; } fpCallClassInstaller = (FP_CALLCLASSINSTALLER)GetProcAddress( ghDeviceInstallerLib, "SetupDiCallClassInstaller"); if (!fpCallClassInstaller) { goto HitFailure; } fpInstallClass = (FP_INSTALLCLASS)GetProcAddress( ghDeviceInstallerLib, "SetupDiInstallClassW"); if (!fpInstallClass) { goto HitFailure; } fpGetSelectedDriver = (FP_GETSELECTEDDRIVER)GetProcAddress( ghDeviceInstallerLib, "SetupDiGetSelectedDriverW"); if (!fpGetSelectedDriver) { goto HitFailure; } fpGetDriverInfoDetail = (FP_GETDRIVERINFODETAIL)GetProcAddress( ghDeviceInstallerLib, "SetupDiGetDriverInfoDetailW"); if (!fpGetDriverInfoDetail) { goto HitFailure; } fpGetDeviceInstallParams = (FP_GETDEVICEINSTALLPARAMS)GetProcAddress( ghDeviceInstallerLib, "SetupDiGetDeviceInstallParamsW"); if (!fpGetDeviceInstallParams) { goto HitFailure; } fpSetDeviceInstallParams = (FP_SETDEVICEINSTALLPARAMS)GetProcAddress( ghDeviceInstallerLib, "SetupDiSetDeviceInstallParamsW"); if (!fpSetDeviceInstallParams) { goto HitFailure; } fpGetDriverInstallParams = (FP_GETDRIVERINSTALLPARAMS)GetProcAddress( ghDeviceInstallerLib, "SetupDiGetDriverInstallParamsW"); if (!fpGetDriverInstallParams) { goto HitFailure; } fpSetClassInstallParams = (FP_SETCLASSINSTALLPARAMS)GetProcAddress( ghDeviceInstallerLib, "SetupDiSetClassInstallParamsW"); if (!fpSetClassInstallParams) { goto HitFailure; } fpGetClassInstallParams = (FP_GETCLASSINSTALLPARAMS)GetProcAddress( ghDeviceInstallerLib, "SetupDiGetClassInstallParamsW"); if (!fpGetClassInstallParams) { goto HitFailure; } fpOpenInfFile = (FP_OPENINFFILE)GetProcAddress( ghDeviceInstallerLib, "SetupOpenInfFileW"); if (!fpOpenInfFile) { goto HitFailure; } fpCloseInfFile = (FP_CLOSEINFFILE)GetProcAddress( ghDeviceInstallerLib, "SetupCloseInfFile"); if (!fpCloseInfFile) { goto HitFailure; } fpFindFirstLine = (FP_FINDFIRSTLINE)GetProcAddress( ghDeviceInstallerLib, "SetupFindFirstLineW"); if (!fpFindFirstLine) { goto HitFailure; } fpFindNextMatchLine = (FP_FINDNEXTMATCHLINE)GetProcAddress( ghDeviceInstallerLib, "SetupFindNextMatchLineW"); if (!fpFindNextMatchLine) { goto HitFailure; } fpGetStringField = (FP_GETSTRINGFIELD)GetProcAddress( ghDeviceInstallerLib, "SetupGetStringFieldW"); if (!fpGetStringField) { goto HitFailure; } fpSetGlobalFlags = (FP_SETGLOBALFLAGS)GetProcAddress( ghDeviceInstallerLib, "pSetupSetGlobalFlags"); if (!fpSetGlobalFlags) { goto HitFailure; } fpGetGlobalFlags = (FP_GETGLOBALFLAGS)GetProcAddress( ghDeviceInstallerLib, "pSetupGetGlobalFlags"); if (!fpGetGlobalFlags) { goto HitFailure; } fpAccessRunOnceNodeList = (FP_ACCESSRUNONCENODELIST)GetProcAddress( ghDeviceInstallerLib, "pSetupAccessRunOnceNodeList"); if (!fpAccessRunOnceNodeList) { goto HitFailure; } fpDestroyRunOnceNodeList = (FP_DESTROYRUNONCENODELIST)GetProcAddress( ghDeviceInstallerLib, "pSetupDestroyRunOnceNodeList"); if (!fpDestroyRunOnceNodeList) { goto HitFailure; } // // Now configure setupapi for server-side installation // SetupGlobalFlags = fpGetGlobalFlags(); // // We want to run non-interactive and do RunOnce entries server-side // SetupGlobalFlags |= (PSPGF_NONINTERACTIVE | PSPGF_SERVER_SIDE_RUNONCE); // // Make sure we _aren't_ skipping backup--it is essential that we be // able to completely back-out of an installation half-way through if // we encounter a failure (e.g., an unsigned file). // SetupGlobalFlags &= ~PSPGF_NO_BACKUP; fpSetGlobalFlags(SetupGlobalFlags); // // If we get to here, we succeeded. // goto clean0; HitFailure: // // Failed to retrieve some entrypoint. // Err = GetLastError(); clean0: NOTHING; } except(EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_WARNINGS, "UMPNPMGR: Exception during LoadDeviceInstaller!\n")); ASSERT(0); Err = ERROR_INVALID_DATA; } if(Err != NO_ERROR) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_ERRORS, "UMPNPMGR: failed to load device installer, error = %d\n", Err)); FreeLibrary(ghDeviceInstallerLib); ghDeviceInstallerLib = NULL; } else { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: Loaded device installer\n", Err)); } return Err; } // LoadDeviceInstaller VOID UnloadDeviceInstaller( VOID ) /*++ Routine Description: This unloads setupapi.dll if it's presently loaded. Arguments: None. Return Value: None. --*/ { PINSTALL_CLIENT_ENTRY pDeviceInstallClient, pNextDeviceInstallClient; // // Unload setupapi.dll. // if(ghDeviceInstallerLib) { FreeLibrary(ghDeviceInstallerLib); ghDeviceInstallerLib = NULL; KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: Unloaded device installer\n")); } // // Close any device install clients that exist. // LockNotifyList(&InstallClientList.Lock); pDeviceInstallClient = InstallClientList.Next; while (pDeviceInstallClient) { ASSERT(pDeviceInstallClient->RefCount == 1); pNextDeviceInstallClient = pDeviceInstallClient->Next; DereferenceDeviceInstallClient(pDeviceInstallClient); pDeviceInstallClient = pNextDeviceInstallClient; } UnlockNotifyList(&InstallClientList.Lock); return; } // UnloadDeviceInstaller DWORD InstallDeviceServerSide( IN LPWSTR pszDeviceId, IN OUT PBOOL RebootRequired, IN OUT PBOOL DeviceHasProblem, IN OUT PULONG SessionId, IN ULONG Flags ) /*++ Routine Description: This routine attempts to install the specified device in the context of umpnpmgr (i.e., on the server-side of the ConfigMgr interface). Arguments: pszDeviceId - device instance ID of the devnode to be installed. RebootRequired - Supplies the address of a boolean variable that will be set to TRUE if the (successful) installation of this device requires a reboot. Note, the existing value of this variable is preserved if either (a) the installation fails or (b) no reboot was required. DeviceHasProblem - Supplies the address of a boolean variable that will be set to TRUE if the device has a CM_PROB_Xxx code after the drivers were installed. Note, this value is only set if the installation succeedes. SessionId - Supplies the address of a variable containing the SessionId on which the device install client is to be displayed. If successful, the SessionId will contain the id of the session in which the device install client UI process was launched. Otherwise, will contain an invalid session id INVALID_SESSION, (0xFFFFFFFF). Flags - Specifies flags describing the behavior of the device install client. The following flags are currently defined: DEVICE_INSTALL_DISPLAY_ON_CONSOLE - if specified, the value in the SessionId variable will be ignored, and the device installclient will always be displayed on the current active console session. Return Value: If the device installation was successful, the return value is NO_ERROR. Otherwise, the return value is a Win32 error code indicating the cause of failure. --*/ { DWORD Err; HDEVINFO DeviceInfoSet; SP_DEVINFO_DATA DeviceInfoData; LPWSTR pszClassGuid; WCHAR szBuffer[MAX_PATH]; HKEY hKey; SP_DEVINSTALL_PARAMS DeviceInstallParams; BOOL b, bDoClientUI = FALSE; LPWSTR p; SP_DRVINFO_DATA DriverInfoData; ULONG ulType; ULONG ulSize; DWORD Capabilities; SP_NEWDEVICEWIZARD_DATA NewDevWizData; BOOL RemoveNewDevDescValue = FALSE; PSP_DRVINFO_DETAIL_DATA pDriverInfoDetailData = NULL; DWORD DriverInfoDetailDataSize; HINF hInf; INFCONTEXT InfContext; DWORD i, dwWait; HANDLE hFinishEvents[3] = { NULL, NULL, NULL }; PINSTALL_CLIENT_ENTRY pDeviceInstallClient = NULL; ULONG ulSessionId = INVALID_SESSION; ULONG ulTransferLen; ULONG ulStatus, ulProblem; HRESULT hr; // // Now create a container set for our device information element. // DeviceInfoSet = fpCreateDeviceInfoList(NULL, NULL); if(DeviceInfoSet == INVALID_HANDLE_VALUE) { return GetLastError(); } DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA); if(!fpOpenDeviceInfo(DeviceInfoSet, pszDeviceId, NULL, 0, &DeviceInfoData)) { goto clean1; } // // OK, it looks like we're going to be able to attempt a server-side // install. Next up is the (potentially time-consuming) driver search. // Before we start that, we want to fire up some UI on the client side (if // somebody is logged in) letting them know we've found their hardware and // are working on installing it. // // NOTE: We don't fire up client-side UI if the device has the SilentInstall // capability. // ulSize = ulTransferLen = sizeof(Capabilities); if ((CR_SUCCESS != PNP_GetDeviceRegProp(NULL, pszDeviceId, CM_DRP_CAPABILITIES, &ulType, (LPBYTE)&Capabilities, &ulTransferLen, &ulSize, 0)) || !(Capabilities & CM_DEVCAP_SILENTINSTALL)) { // // Either we couldn't retrieve the capabilities property (shouldn't // happen, or we did retrieve it but the silent-install bit wasn't set. // bDoClientUI = TRUE; // // If we're not going to determine the session to use for UI, use the // SessionId supplied by the caller. // if ((Flags & DEVICE_INSTALL_DISPLAY_ON_CONSOLE) == 0) { ASSERT(*SessionId != INVALID_SESSION); ulSessionId = *SessionId; } // // Go ahead and fire up the client-side UI. // DoDeviceInstallClient(pszDeviceId, &ulSessionId, Flags | DEVICE_INSTALL_UI_ONLY | DEVICE_INSTALL_PLAY_SOUND, &pDeviceInstallClient); } // // Do a default driver search for this device. // if(!fpBuildDriverInfoList(DeviceInfoSet, &DeviceInfoData, SPDIT_COMPATDRIVER)) { goto clean1; } // // Select the best driver from the list we just built. // if(!fpCallClassInstaller(DIF_SELECTBESTCOMPATDRV, DeviceInfoSet, &DeviceInfoData)) { goto clean1; } DriverInfoData.cbSize = sizeof(SP_DRVINFO_DATA); b = fpGetSelectedDriver(DeviceInfoSet, &DeviceInfoData, &DriverInfoData); ASSERT(b); // the above call shouldn't fail if(!b) { goto clean1; } // // NOTE: the multi-port serial class has some buggy co-installers that // always popup UI, without using the finish-install wizard page mechanism, // and without regard to the DI_QUIETINSTALL flag. Until they clean up // their act, we must disallow server-side installation of those devices // as well. // if(GuidEqual(&GUID_DEVCLASS_MULTIPORTSERIAL, &(DeviceInfoData.ClassGuid))) { Err = ERROR_DI_DONT_INSTALL; goto clean0; } // // Kludge to allow INFs to force client-side (i.e., interactive) // installation for certain devices. They do this by referencing a // hardware or compatible ID in an "InteractiveInstall" entry in the INF's // [ControlFlags] section. The format of one of these lines is: // // InteractiveInstall = [, ... ] // // and there may be any number of these lines. // // // First, retrieve the driver info detail data (this contains the hardware // ID and any compatible IDs specified by this INF driver entry). // b = fpGetDriverInfoDetail(DeviceInfoSet, &DeviceInfoData, &DriverInfoData, NULL, 0, &DriverInfoDetailDataSize ); Err = GetLastError(); // // The above call to get driver info detail data should never succeed // because the buffer will alwyas be too small (we're just interested in // sizing the buffer at this point). // ASSERT(!b && (Err == ERROR_INSUFFICIENT_BUFFER)); if(b || (Err != ERROR_INSUFFICIENT_BUFFER)) { Err = ERROR_INVALID_DATA; goto clean0; } // // Now that we know how big of a buffer we need to hold the driver info // details, allocate the buffer and retrieve the information. // pDriverInfoDetailData = HeapAlloc(ghPnPHeap, 0, DriverInfoDetailDataSize); if(!pDriverInfoDetailData) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } pDriverInfoDetailData->cbSize = sizeof(SP_DRVINFO_DETAIL_DATA); if(!fpGetDriverInfoDetail(DeviceInfoSet, &DeviceInfoData, &DriverInfoData, pDriverInfoDetailData, DriverInfoDetailDataSize, NULL)) { Err = GetLastError(); ASSERT(FALSE); // we should never fail this call. goto clean0; } // // OK, we have all the hardware and compatible IDs for this driver node. // Now we need to open up the INF and see if any of them are referenced in // an "InteractiveInstall" control flag entry. // hInf = fpOpenInfFile(pDriverInfoDetailData->InfFileName, NULL, INF_STYLE_WIN4, NULL ); if(hInf == INVALID_HANDLE_VALUE) { // // For some reason, we couldn't open the INF! // goto clean1; } b = FALSE; // // Look at each InteractiveInstall line in the INF's [ControlFlags] // section... // if(fpFindFirstLine(hInf, pszControlFlags, pszInteractiveInstall, &InfContext)) { do { // // and within each line, examine each value... // for(i = 1; fpGetStringField(&InfContext, i, szBuffer, sizeof(szBuffer) / sizeof(WCHAR), NULL); i++) { // // Check to see if this ID matches up with one of the driver // node's hardware or compatible IDs. // for(p = pDriverInfoDetailData->HardwareID; *p; p += (lstrlen(p) + 1)) { if (CompareString( LOCALE_INVARIANT, NORM_IGNORECASE, p, -1, szBuffer, -1) == CSTR_EQUAL) { // // We found a match! We must defer the installation to // the client-side. // b = TRUE; goto InteractiveInstallSearchDone; } } } } while(fpFindNextMatchLine(&InfContext, pszInteractiveInstall, &InfContext)); } InteractiveInstallSearchDone: // // We're done with the INF--close it. // fpCloseInfFile(hInf); if(b) { Err = ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION; goto clean0; } // // Check to see if it's OK to install this driver. // if(!fpCallClassInstaller(DIF_ALLOW_INSTALL, DeviceInfoSet, &DeviceInfoData) && ((Err = GetLastError()) != ERROR_DI_DO_DEFAULT)) { goto clean0; } // // Tell our client-side UI (if any) it's time to update the device's // description and class icon. // if (pDeviceInstallClient) { // // Retrieve the device description from the driver node we're about to // install. We don't want to write this out as the devnode's DeviceDesc // property, because some class installers have dependencies upon being // able to retrieve the unaltered description as reported by the // enumerator. So instead, we write this out as the REG_SZ // NewDeviceDesc value entry to the devnode's hardware key. // DriverInfoData.cbSize = sizeof(SP_DRVINFO_DATA); b = fpGetSelectedDriver(DeviceInfoSet, &DeviceInfoData, &DriverInfoData); ASSERT(b); // the above call shouldn't fail if(b) { // // Make sure that the hardware key is created (with the right // security). // PNP_CreateKey(NULL, pszDeviceId, KEY_READ, 0 ); // // Now, open the Device Parameters subkey so we can write out the // device's new description. // if (SUCCEEDED(StringCchPrintf( szBuffer, SIZECHARS(szBuffer), L"%s\\%s", pszDeviceId, pszRegKeyDeviceParam))) { if(ERROR_SUCCESS == RegOpenKeyEx(ghEnumKey, szBuffer, 0, KEY_READ | KEY_WRITE, &hKey)) { if(ERROR_SUCCESS == RegSetValueEx( hKey, pszRegValueNewDeviceDesc, 0, REG_SZ, (LPBYTE)(DriverInfoData.Description), (lstrlen(DriverInfoData.Description) + 1) * sizeof(WCHAR))) { RemoveNewDevDescValue = TRUE; } RegCloseKey(hKey); hKey = NULL; } } } // // Wait for the device install to be signaled from newdev.dll to let us // know that it has completed displaying the UI request. // // Wait on the client's process as well, to catch the case // where the process crashes (or goes away) without signaling the // device install event. // // Also wait on the disconnect event in case we have explicitly // disconnected from the client while switching sessions. // // We don't want to wait forever in case NEWDEV.DLL hangs for some // reason. So we will give it 5 seconds to complete the UI only // install and then continue on without it. // // Note that the client is still referenced for our use, and should be // dereferenced when we're done with it. // hFinishEvents[0] = pDeviceInstallClient->hProcess; hFinishEvents[1] = pDeviceInstallClient->hEvent; hFinishEvents[2] = pDeviceInstallClient->hDisconnectEvent; dwWait = WaitForMultipleObjects(3, hFinishEvents, FALSE, 5000); if (dwWait == WAIT_OBJECT_0) { // // If the return is WAIT_OBJECT_0 then the newdev.dll process has // gone away. Close the device install client and clean up all of // the associated handles. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: InstallDeviceServerSide: process signalled, closing device install client!\n")); } else if (dwWait == (WAIT_OBJECT_0 + 1)) { // // If the return is WAIT_OBJECT_0 + 1 then the device installer // successfully received the request. This is the only case where // we don't want to close the client, since we may want to reuse it // later. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: InstallDeviceServerSide: device install client succeeded\n")); } else if (dwWait == (WAIT_OBJECT_0 + 2)) { // // If the return is WAIT_OBJECT_0 + 2 then we were explicitly // disconnected from the device install client. For server-side // installation, we don't need to keep the client UI around on the // disconnected session, so we should close it here also. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: InstallDeviceServerSide: device install client disconnected\n")); } else if (dwWait == WAIT_TIMEOUT) { // // Timed out while waiting for the device install client. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_WARNINGS, "UMPNPMGR: InstallDeviceServerSide: timed out waiting for device install client!\n")); } else { // // The wait was satisfied for some reason other than the // specified objects. This should never happen, but just in // case, we'll close the client. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_ERRORS, "UMPNPMGR: InstallDeviceServerSide: wait completed unexpectedly!\n")); } LockNotifyList(&InstallClientList.Lock); // // Remove the reference placed on the client while it was in use. // DereferenceDeviceInstallClient(pDeviceInstallClient); if (dwWait != (WAIT_OBJECT_0 + 1)) { // // Unless the client signalled successful receipt of the // request, we probably won't be able to use this client // anymore. Remove the initial reference so all // associated handles will be closed and the entry will be // freed when it is no longer in use. // // // Note that if we were unsuccessful because of a // logoff, we would have already dereferenced the // client then, in which case the above dereference // was the final one, and pDeviceInstallClient would // be invalid. Instead, attempt to re-locate the // client by the session id. // pDeviceInstallClient = LocateDeviceInstallClient(ulSessionId); if (pDeviceInstallClient) { ASSERT(pDeviceInstallClient->RefCount == 1); DereferenceDeviceInstallClient(pDeviceInstallClient); } ulSessionId = INVALID_SESSION; } pDeviceInstallClient = NULL; UnlockNotifyList(&InstallClientList.Lock); } // // If we're doing client side UI for this device, attempt to refresh the UI again. // if (bDoClientUI) { // // When we attempt to refresh the client-side UI, if we display the // refreshed UI on a different session than the one we had previously, // close the previous device install client. // ULONG ulPrevSessionId = ulSessionId; // // If we're not going to determine the session to use for UI, use the // SessionId supplied by the caller. // if ((Flags & DEVICE_INSTALL_DISPLAY_ON_CONSOLE) == 0) { ASSERT(*SessionId != INVALID_SESSION); ulSessionId = *SessionId; } DoDeviceInstallClient(pszDeviceId, &ulSessionId, Flags | DEVICE_INSTALL_UI_ONLY, &pDeviceInstallClient); if ((ulPrevSessionId != INVALID_SESSION) && (ulPrevSessionId != ulSessionId)) { PINSTALL_CLIENT_ENTRY pPrevDeviceInstallClient; LockNotifyList(&InstallClientList.Lock); pPrevDeviceInstallClient = LocateDeviceInstallClient(ulPrevSessionId); if (pPrevDeviceInstallClient) { ASSERT(pPrevDeviceInstallClient->RefCount == 1); DereferenceDeviceInstallClient(pPrevDeviceInstallClient); } UnlockNotifyList(&InstallClientList.Lock); } } // // OK, everything looks good for installing this driver. Check to see if // this INF's class is already installed--if not, then we need to install // it before proceeding. // if(RPC_S_OK != UuidToString(&(DeviceInfoData.ClassGuid), &pszClassGuid)) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } hr = StringCchPrintf(szBuffer, SIZECHARS(szBuffer), L"{%s}", pszClassGuid); RpcStringFree(&pszClassGuid); if (FAILED(hr)) { Err = HRESULT_CODE(hr); goto clean0; } if(RegOpenKeyEx(ghClassKey, szBuffer, 0, KEY_READ, &hKey) != ERROR_SUCCESS) { if(!fpInstallClass(NULL, pDriverInfoDetailData->InfFileName, 0, NULL)) { goto clean1; } } else { // // The class key already exists--assume that the class has previously // been installed. // RegCloseKey(hKey); } // // Now we're ready to install the device. First, install the files. // if(!fpCallClassInstaller(DIF_INSTALLDEVICEFILES, DeviceInfoSet, &DeviceInfoData)) { goto clean1; } // // Set a flag in the device install parameters so that we don't try to // re-copy the files during subsequent DIF operations. // DeviceInstallParams.cbSize = sizeof(SP_DEVINSTALL_PARAMS); b = fpGetDeviceInstallParams(DeviceInfoSet, &DeviceInfoData, &DeviceInstallParams); ASSERT(b); // the above call shouldn't fail if(!b) { goto clean1; } DeviceInstallParams.Flags |= DI_NOFILECOPY; b = fpSetDeviceInstallParams(DeviceInfoSet, &DeviceInfoData, &DeviceInstallParams); ASSERT(b); // the above call shouldn't fail if(!b) { goto clean1; } // // Now finish up the installation. // if(!fpCallClassInstaller(DIF_REGISTER_COINSTALLERS, DeviceInfoSet, &DeviceInfoData)) { goto clean1; } if(!fpCallClassInstaller(DIF_INSTALLINTERFACES, DeviceInfoSet, &DeviceInfoData)) { goto clean1; } if(!fpCallClassInstaller(DIF_INSTALLDEVICE, DeviceInfoSet, &DeviceInfoData)) { ULONG ulConfig; // // Before we do anything to blow away last error, retrieve it. // Err = GetLastError(); // // It's possible that the installation got far enough to have cleared // any problems on the device (i.e., SetupDiInstallDevice succeeded, // but the class installer or co-installer subsequently failed during // some post-processing). // // We want to make sure that the devnode is marked as needing re-install // because we might lose the client-side install request (e.g., the // user reboots without logging in). // ulConfig = GetDeviceConfigFlags(pszDeviceId, NULL); ulConfig |= CONFIGFLAG_REINSTALL; PNP_SetDeviceRegProp(NULL, pszDeviceId, CM_DRP_CONFIGFLAGS, REG_DWORD, (LPBYTE)&ulConfig, sizeof(ulConfig), 0 ); goto clean0; } // // We're not quite out of the woods yet. We need to check if the class-/ // co-installers want to display finish-install wizard pages. If so, then // we need to set the CONFIGFLAG_REINSTALL flag for this devnode and report // failure so that we'll re-attempt the install as a client-side // installation (where a wizard can actually be displayed). // ZeroMemory(&NewDevWizData, sizeof(NewDevWizData)); NewDevWizData.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER); NewDevWizData.ClassInstallHeader.InstallFunction = DIF_NEWDEVICEWIZARD_FINISHINSTALL; b = fpSetClassInstallParams(DeviceInfoSet, &DeviceInfoData, (PSP_CLASSINSTALL_HEADER)&NewDevWizData, sizeof(NewDevWizData) ); ASSERT(b); // the above call shouldn't fail if(b) { b = fpCallClassInstaller(DIF_NEWDEVICEWIZARD_FINISHINSTALL, DeviceInfoSet, &DeviceInfoData ); if(b || (ERROR_DI_DO_DEFAULT == GetLastError())) { // // Retrieve the install params // b = (fpGetClassInstallParams(DeviceInfoSet, &DeviceInfoData, (PSP_CLASSINSTALL_HEADER)&NewDevWizData, sizeof(NewDevWizData), NULL) && (NewDevWizData.ClassInstallHeader.InstallFunction == DIF_NEWDEVICEWIZARD_FINISHINSTALL) ); if(b) { // // Are there any pages? // if(!NewDevWizData.NumDynamicPages) { b = FALSE; } else { // // b is already TRUE if we made it here so no need to set // HMODULE hComCtl32; FP_DESTROYPROPERTYSHEETPAGE fpDestroyPropertySheetPage; // // We don't want to link to comctl32, nor do we want to // always explicitly load it every time we load the device // installer. (The number of devices that request finish- // install pages should be small.) Thus, we load it on- // demand right here, retrieve the entrypoint to the // DestroyPropertySheetPage routine, and then unload the // DLL once we've destroyed all the property pages. // // NOTE: (lonnym): If we can't load comctl32 or get the // entrypont for DestroyPropertySheetPage, then we'll leak // these wizard pages! // hComCtl32 = LoadLibrary(TEXT("comctl32.dll")); if(hComCtl32) { fpDestroyPropertySheetPage = (FP_DESTROYPROPERTYSHEETPAGE)GetProcAddress( hComCtl32, "DestroyPropertySheetPage" ); if(fpDestroyPropertySheetPage) { for(i = 0; i < NewDevWizData.NumDynamicPages; i++) { fpDestroyPropertySheetPage(NewDevWizData.DynamicPages[i]); } } FreeLibrary(hComCtl32); } } } } } if(b) { ULONG ulConfig; CONFIGRET cr; // // One or more finish-install wizard pages were provided--we must defer // this installation to the client-side. // ulConfig = GetDeviceConfigFlags(pszDeviceId, NULL); ulConfig |= CONFIGFLAG_REINSTALL; cr = PNP_SetDeviceRegProp(NULL, pszDeviceId, CM_DRP_CONFIGFLAGS, REG_DWORD, (LPBYTE)&ulConfig, sizeof(ulConfig), 0 ); ASSERT(cr == CR_SUCCESS); Err = ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION; goto clean0; } // // The installation was a success! Check to see if a reboot is needed. // b = fpGetDeviceInstallParams(DeviceInfoSet, &DeviceInfoData, &DeviceInstallParams); ASSERT(b); // the above call shouldn't fail if(b) { if(DeviceInstallParams.Flags & (DI_NEEDRESTART | DI_NEEDREBOOT)) { *RebootRequired = TRUE; } } // // Process any RunOnce (RunDll32) entries that may have been queued up // during this installation. // DoRunOnce(); // // Check to see if the device has a problem. // if ((GetDeviceStatus(pszDeviceId, &ulStatus, &ulProblem) != CR_SUCCESS) || (ulStatus & DN_HAS_PROBLEM)) { *DeviceHasProblem = TRUE; } else { *DeviceHasProblem = FALSE; } Err = NO_ERROR; goto clean0; clean1: // // Failures where error is in GetLastError() can come here. // Err = GetLastError(); clean0: fpDestroyDeviceInfoList(DeviceInfoSet); if(pDriverInfoDetailData) { HeapFree(ghPnPHeap, 0, pDriverInfoDetailData); } // // Clear out our list of RunOnce work items (note that the list will // already be empty if the device install succeeded and we called // DoRunOnce() above). // fpDestroyRunOnceNodeList(); // // If we stored out a NewDeviceDesc value to the devnode's hardware key // above, go and remove that turd now. // if(RemoveNewDevDescValue) { // // Open the Device Parameters subkey so we can delete the value. // if (SUCCEEDED(StringCchPrintf( szBuffer, SIZECHARS(szBuffer), L"%s\\%s", pszDeviceId, pszRegKeyDeviceParam))) { if (RegOpenKeyEx(ghEnumKey, szBuffer, 0, KEY_READ | KEY_WRITE, &hKey) == ERROR_SUCCESS) { RegDeleteValue(hKey, pszRegValueNewDeviceDesc); RegCloseKey(hKey); } } } if (pDeviceInstallClient) { // // Wait for the device install to be signaled from newdev.dll to let us // know that it has completed displaying the UI request. // // Wait on the client's process as well, to catch the case // where the process crashes (or goes away) without signaling the // device install event. // // Also wait on the disconnect event in case we have explicitly // disconnected from the client while switching sessions. // // We don't want to wait forever in case NEWDEV.DLL hangs for some // reason. So we will give it 5 seconds to complete the UI only // install and then continue on without it. // // Note that the client is still referenced for our use, and should be // dereferenced when we're done with it. // hFinishEvents[0] = pDeviceInstallClient->hProcess; hFinishEvents[1] = pDeviceInstallClient->hEvent; hFinishEvents[2] = pDeviceInstallClient->hDisconnectEvent; dwWait = WaitForMultipleObjects(3, hFinishEvents, FALSE, 5000); if (dwWait == WAIT_OBJECT_0) { // // If the return is WAIT_OBJECT_0 then the newdev.dll process has // gone away. Close the device install client and clean up all of // the associated handles. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: InstallDeviceServerSide: " "process signalled, closing device install client!\n")); } else if (dwWait == (WAIT_OBJECT_0 + 1)) { // // If the return is WAIT_OBJECT_0 + 1 then the device installer // successfully received the request. This is the only case where // we don't want to close the client, since we may want to reuse it // later. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: InstallDeviceServerSide: " "device install client succeeded\n")); } else if (dwWait == (WAIT_OBJECT_0 + 2)) { // // If the return is WAIT_OBJECT_0 + 2 then we were explicitly // disconnected from the device install client. For server-side // installation, we don't need to keep the client UI around on the // disconnected session, so we should close it here also. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: InstallDeviceServerSide: " "device install client disconnected\n")); } else if (dwWait == WAIT_TIMEOUT) { // // Timed out while waiting for the device install client. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_WARNINGS, "UMPNPMGR: InstallDeviceServerSide: " "timed out waiting for device install client!\n")); } else { // // The wait was satisfied for some reason other than the // specified objects. This should never happen, but just in // case, we'll close the client. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_ERRORS, "UMPNPMGR: InstallDeviceServerSide: " "wait completed unexpectedly!\n")); } LockNotifyList(&InstallClientList.Lock); // // Remove the reference placed on the client while it was in use. // DereferenceDeviceInstallClient(pDeviceInstallClient); if (dwWait != (WAIT_OBJECT_0 + 1)) { // // Unless the client signalled successful receipt of the // request, we probably won't be able to use this client // anymore. Remove the initial reference so all // associated handles will be closed and the entry will be // freed when it is no longer in use. // // // Note that if we were unsuccessful because of a // logoff, we would have already dereferenced the // client then, in which case the above dereference // was the final one, and pDeviceInstallClient would // be invalid. Instead, attempt to re-locate the // client by the session id. // pDeviceInstallClient = LocateDeviceInstallClient(ulSessionId); if (pDeviceInstallClient) { ASSERT(pDeviceInstallClient->RefCount == 1); DereferenceDeviceInstallClient(pDeviceInstallClient); } ulSessionId = INVALID_SESSION; } pDeviceInstallClient = NULL; UnlockNotifyList(&InstallClientList.Lock); } if (bDoClientUI) { // // Note that if client-side UI was created during the server-side device // install, it will still exist when we are done. The caller should // dereference it when it is done installing all devices to make it go // away. // *SessionId = ulSessionId; } else { // // There was never any client-side UI for this device install. // *SessionId = INVALID_SESSION; } return Err; } // InstallDeviceServerSide BOOL PromptUser( IN OUT PULONG SessionId, IN ULONG Flags ) /*++ Routine Description: This routine will notify the logged-on user (if any) with a specified message. Arguments: SessionId - Supplies the address of a variable containing the SessionId on which the device install client is to be displayed. If successful, the SessionId will contain the id of the session in which the reboot dialog process was launched. Otherwise, will contain an invalid session id, INVALID_SESSION, (0xFFFFFFFF). Flags - Specifies flags describing the behavior of the reboot dialog displayed by the device install client. The following flags are currently defined: DEVICE_INSTALL_FINISHED_REBOOT - if specified, the user should be prompted to reboot. DEVICE_INSTALL_BATCH_COMPLETE - if specified, the user should be prompted that the plug and play manager is finished installing a batch of devices. DEVICE_INSTALL_DISPLAY_ON_CONSOLE - if specified, the value in the SessionId variable will be ignored, and the device installclient will always be displayed on the current active console session. Return Value: If the user is successfully notified, the return value is TRUE. If we couldn't ask the user (i.e., no user was logged in), the return value is FALSE. Notes: If the user was prompted for a reboot, this doesn't necessarily mean that a reboot is in progress. --*/ { BOOL bStatus = FALSE; ULONG ulValue, ulSize, ulSessionId = INVALID_SESSION; HANDLE hFinishEvents[3] = { NULL, NULL, NULL }; DWORD dwWait; PINSTALL_CLIENT_ENTRY pDeviceInstallClient = NULL; try { // // Check if we should skip client side UI. // if (gbSuppressUI) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_WARNINGS, "UMPNPMGR: PromptUser: Client-side UI has been suppressed, exiting.\n")); LogWarningEvent(WRN_REBOOT_UI_SUPPRESSED, 0, NULL); *SessionId = INVALID_SESSION; return FALSE; } // // Determine the session to use, based on the supplied flags. // if (Flags & DEVICE_INSTALL_DISPLAY_ON_CONSOLE) { ulSessionId = GetActiveConsoleSessionId(); } else { ASSERT(*SessionId != INVALID_SESSION); ulSessionId = *SessionId; } ASSERT(ulSessionId != INVALID_SESSION); // // If the specified session is not currently connected anywhere, don't // bother creating any UI. // if (!IsSessionConnected(ulSessionId)) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT, "UMPNPMGR: PromptUser: SessionId %d not connected, exiting\n", ulSessionId)); return FALSE; } // // If a device install client is already running on this session, // connect to it. Otherwise, create a new one. // LockNotifyList(&InstallClientList.Lock); // // First, try to connect to an existing client already running on this // session. // bStatus = ConnectDeviceInstallClient(ulSessionId, &pDeviceInstallClient); if (bStatus) { if ((Flags & DEVICE_INSTALL_BATCH_COMPLETE) && (pDeviceInstallClient->ulInstallFlags & DEVICE_INSTALL_BATCH_COMPLETE)) { // // If there is an existing client, and we're sending it the // "we're done" message, and the last thing this client did was // display that message, don't bother sending it again. // pDeviceInstallClient = NULL; bStatus = FALSE; } } else if (!(Flags & DEVICE_INSTALL_BATCH_COMPLETE)) { // // If there isn't an existing client for this session, and we're not // launching one just to say "we're done", then go ahead and create // a new device install client for this session. // bStatus = CreateDeviceInstallClient(ulSessionId, &pDeviceInstallClient); } if (bStatus) { // // Whether we are using an existing client, or created a // new one, the client should only have the initial // reference from when it was added to the list, since any // use of the client is done on this single install // thread. // ASSERT(pDeviceInstallClient); ASSERT(pDeviceInstallClient->RefCount == 1); // // Reference the device install client while it is in use. // We'll remove this reference when we're done with it. // ReferenceDeviceInstallClient(pDeviceInstallClient); } UnlockNotifyList(&InstallClientList.Lock); if (!bStatus) { *SessionId = INVALID_SESSION; return FALSE; } ASSERT(pDeviceInstallClient); // // Don't send newdev the display on console flag, if it was specified. // ulValue = Flags & ~DEVICE_INSTALL_DISPLAY_ON_CONSOLE; // // Send newdev.dll the specified signal. // if (WriteFile(pDeviceInstallClient->hPipe, &ulValue, sizeof(ulValue), &ulSize, NULL )) { // // newdev.dll expects two DWORDs to be sent over the pipe each time. The second // DWORD should just be set to 0 in this case. // ulValue = 0; if (WriteFile(pDeviceInstallClient->hPipe, &ulValue, sizeof(ulValue), &ulSize, NULL )) { bStatus = TRUE; } else { bStatus = FALSE; LogErrorEvent(ERR_WRITING_SERVER_INSTALL_PIPE, GetLastError(), 0); } } else { bStatus = FALSE; LogErrorEvent(ERR_WRITING_SERVER_INSTALL_PIPE, GetLastError(), 0); } if (bStatus) { bStatus = FALSE; // // Wait for the event to be signaled from newdev.dll // to let us know that it has received the information. // // Wait on the process as well, to catch the case where the process // crashes (or goes away) without signaling the event. // // Also wait on the disconnect event in case we have just // disconnected from the device install client, in which case the // event and process handles are no longer valid. // hFinishEvents[0] = pDeviceInstallClient->hProcess; hFinishEvents[1] = pDeviceInstallClient->hEvent; hFinishEvents[2] = pDeviceInstallClient->hDisconnectEvent; dwWait = WaitForMultipleObjects(3, hFinishEvents, FALSE, INFINITE); if (dwWait == WAIT_OBJECT_0) { // // If the return is WAIT_OBJECT_0 then the newdev.dll // process has gone away. Consider the request unsuccessful // so that we will retry again at a later time. Orphan the // device install client and clean up all of the associated // handles. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT, "UMPNPMGR: PromptUser: process signalled, orphaning device install client!\n")); } else if (dwWait == (WAIT_OBJECT_0 + 1)) { // // If the return is WAIT_OBJECT_0 + 1 then the request was // received successfully. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT, "UMPNPMGR: PromptUser: device install client succeeded\n")); // // Remember the last request serviced by this client. // pDeviceInstallClient->ulInstallFlags = Flags; bStatus = TRUE; } else if (dwWait == (WAIT_OBJECT_0 + 2)) { // // If the return is WAIT_OBJECT_0 + 2 then the device // install client was explicitly disconnected before // the request was received. Consider the request // unsuccessful so that we will retry again at a later // time. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT, "UMPNPMGR: PromptUser: device install client orphaned!\n")); } } LockNotifyList(&InstallClientList.Lock); // // Remove the reference placed on the client while it was in use. // DereferenceDeviceInstallClient(pDeviceInstallClient); if (!bStatus) { // // Unless the client signalled successful receipt of the // request, we probably won't be able to use this client // anymore. Remove the initial reference so all // associated handles will be closed and the entry will be // freed when it is no longer in use. // // // Note that if we were unsuccessful because of a // logoff, we would have already dereferenced the // client then, in which case the above dereference // was the final one, and pDeviceInstallClient would // be invalid. Instead, attempt to re-locate the // client by the session id. // pDeviceInstallClient = LocateDeviceInstallClient(ulSessionId); if (pDeviceInstallClient) { ASSERT(pDeviceInstallClient->RefCount == 1); DereferenceDeviceInstallClient(pDeviceInstallClient); } } UnlockNotifyList(&InstallClientList.Lock); } except (EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_WARNINGS, "UMPNPMGR: Exception during PromptUser!\n")); ASSERT(0); bStatus = FALSE; } if (!bStatus) { *SessionId = INVALID_SESSION; } else { *SessionId = ulSessionId; } return bStatus; } // PromptUser BOOL CreateDeviceInstallClient( IN ULONG SessionId, OUT PINSTALL_CLIENT_ENTRY *DeviceInstallClient ) /*++ Routine Description: This routine kicks off a newdev.dll process (if someone is logged in). We use a named pipe to comunicate with the user mode process and have it either display UI for a server side install, or do the install itself on the client side. Arguments: SessionId - Session for which a device install client should be created or connected to. DeviceInstallClient - Receives a pointer to receive a pointer to the device install client for this session. Return Value: Returns TRUE if a device install client was created, or if an existing device install client was found for the specified session. This routine doesn't wait until the process terminates. Returns FALSE if a device install client could not be created. Notes: The InstallClientList lock must be acquired by the caller of this routine. --*/ { STARTUPINFO StartupInfo; PROCESS_INFORMATION ProcessInfo; WCHAR szCmdLine[MAX_PATH]; WCHAR szDeviceInstallPipeName[MAX_PATH]; WCHAR szDeviceInstallEventName[MAX_PATH]; ULONG ulDeviceInstallEventNameSize; HANDLE hFinishEvents[2] = { NULL, NULL }; HANDLE hTemp, hUserToken = NULL; PINSTALL_CLIENT_ENTRY entry; RPC_STATUS rpcStatus = RPC_S_OK; GUID newGuid; WCHAR szGuidString[MAX_GUID_STRING_LEN]; HANDLE hDeviceInstallPipe = NULL, hDeviceInstallEvent = NULL; HANDLE hDeviceInstallDisconnectEvent = NULL; PINSTALL_CLIENT_ENTRY pDeviceInstallClient = NULL; ULONG ulSize; WIN32_FIND_DATA findData; BOOL bStatus; PVOID lpEnvironment = NULL; OVERLAPPED overlapped = {0,0,0,0,0}; DWORD dwError, dwWait, dwBytes; HRESULT hr; size_t Len = 0; // // Validate output parameter. // ASSERT(DeviceInstallClient); if (!DeviceInstallClient) { return FALSE; } // // Make sure the specified SessionId is valid. // ASSERT(SessionId != INVALID_SESSION); if (SessionId == INVALID_SESSION) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_ERRORS, "UMPNPMGR: CreateDeviceInstallClient: Invalid Console SessionId %d, exiting!\n", SessionId)); return FALSE; } // // Initialize process, startup and overlapped structures, since we // depend on them being NULL during cleanup here on out. // ZeroMemory(&ProcessInfo, sizeof(ProcessInfo)); ZeroMemory(&StartupInfo, sizeof(StartupInfo)); ZeroMemory(&overlapped, sizeof(overlapped)); // // Assume failure // bStatus = FALSE; try { // // Before doing anything, check that newdev.dll is actually present on // the system. // szCmdLine[0] = L'\0'; ulSize = GetSystemDirectory(szCmdLine, MAX_PATH); if ((ulSize == 0) || ((ulSize + 2 + ARRAY_SIZE(NEWDEV_DLL)) > MAX_PATH)) { return FALSE; } hr = StringCchCat(szCmdLine, SIZECHARS(szCmdLine), L"\\"); if (SUCCEEDED(hr)) { hr = StringCchCat(szCmdLine, SIZECHARS(szCmdLine), NEWDEV_DLL); } if (FAILED(hr)) { return FALSE; } hTemp = FindFirstFile(szCmdLine, &findData); if(hTemp != INVALID_HANDLE_VALUE) { FindClose(hTemp); } else { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_ERRORS, "UMPNPMGR: CreateDeviceInstallClient: %ws not found, error = %d, exiting\n", szCmdLine, GetLastError())); LogWarningEvent(WRN_NEWDEV_NOT_PRESENT, 1, szCmdLine); return FALSE; } // // Get the user access token for the active console session user. // if (!GetSessionUserToken(SessionId, &hUserToken) || (hUserToken == NULL)) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: CreateDeviceInstallClient: Unable to get user token for Session %d,\n" " postponing client-side installation, error = %d\n", SessionId, GetLastError())); return FALSE; } // // If the user Winstation for this session is locked, and Fast User // Switching is enabled, then we're at the welcome screen. Don't create // a device install client, because we don't want to hang the install // thread if nobody's actually around to do anything about it. If the // session is locked, but FUS is not disabled, maintain previous // behavior, and launch the device install client. The user will have // to unlock or logoff before another user can logon anyways. // if (IsSessionLocked(SessionId) && IsFastUserSwitchingEnabled()) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: CreateDeviceInstallClient: Session %d locked with FUS enabled,\n" " postponing client-side installation.\n", SessionId)); CloseHandle(hUserToken); return FALSE; } // // Create a named pipe and event for communication and synchronization // with the client-side device installer. The event and named pipe must // be global so that UMPNPMGR can interact with a device install client // in a different session, but it must still be unique for that session. // Add a generated GUID so the names are not entirely well-known. // rpcStatus = UuidCreate(&newGuid); if ((rpcStatus != RPC_S_OK) && (rpcStatus != RPC_S_UUID_LOCAL_ONLY)) { goto Clean0; } if (StringFromGuid((LPGUID)&newGuid, szGuidString, MAX_GUID_STRING_LEN) != NO_ERROR) { goto Clean0; } if (FAILED(StringCchPrintf( szDeviceInstallPipeName, SIZECHARS(szDeviceInstallPipeName), L"%ws_%d.%ws", PNP_DEVICE_INSTALL_PIPE, SessionId, szGuidString))) { goto Clean0; } if (FAILED(StringCchPrintf( szDeviceInstallEventName, SIZECHARS(szDeviceInstallEventName), L"Global\\%ws_%d.%ws", PNP_DEVICE_INSTALL_EVENT, SessionId, szGuidString))) { goto Clean0; } if (FAILED(StringCchLength( szDeviceInstallEventName, SIZECHARS(szDeviceInstallEventName), &Len))) { goto Clean0; } ulDeviceInstallEventNameSize = (ULONG)((Len + 1) * sizeof(WCHAR)); // // The approximate size of the named pipe output buffer should be large // enough to hold the greater of either: // - The name and size of the named event string, OR // - The install flags, name and device instance id size for at least // one device install. // ulSize = max(sizeof(ulDeviceInstallEventNameSize) + ulDeviceInstallEventNameSize, 2 * sizeof(ULONG) + (MAX_DEVICE_ID_LEN * sizeof(WCHAR))); // // Open up a named pipe to communicate with the newdev user-client. // if (CreateUserReadNamedPipe( hUserToken, szDeviceInstallPipeName, ulSize, &hDeviceInstallPipe) != NO_ERROR) { ASSERT(hDeviceInstallPipe == NULL); goto Clean0; } // // Create an event that a user-client can synchronize with and set, and // that we will block on after we send a device install to newdev.dll. // if (CreateUserSynchEvent( hUserToken, szDeviceInstallEventName, &hDeviceInstallEvent) != NO_ERROR) { ASSERT(hDeviceInstallEvent == NULL); goto Clean0; } // // Create an event that we can use internally such that waiters can know // when to disconnect from the device install client. // hDeviceInstallDisconnectEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (!hDeviceInstallDisconnectEvent) { goto Clean0; } // // Launch newdev.dll using rundll32.exe, passing it the pipe name. // "rundll32.exe newdev.dll,ClientSideInstall " // if (FAILED(StringCchPrintf( szCmdLine, SIZECHARS(szCmdLine), L"%ws %ws,%ws %ws", RUNDLL32_EXE, NEWDEV_DLL, L"ClientSideInstall", szDeviceInstallPipeName))) { goto Clean0; } #if DBG // // Retrieve debugger settings from the service key. // { HKEY hKey; if (RegOpenKeyEx(ghServicesKey, pszRegKeyPlugPlayServiceParams, 0, KEY_READ, &hKey) == ERROR_SUCCESS) { ULONG ulValue = 0; WCHAR szDebugCmdLine[MAX_PATH]; ulSize = sizeof(ulValue); if ((RegQueryValueEx(hKey, pszRegValueDebugInstall, NULL, NULL, (LPBYTE)&ulValue, &ulSize) == ERROR_SUCCESS) &&(ulValue == 1)) { ulSize = sizeof(szDebugCmdLine); if (RegQueryValueEx(hKey, pszRegValueDebugInstallCommand, NULL, NULL, (LPBYTE)szDebugCmdLine, &ulSize) != ERROR_SUCCESS) { // // If no debugger was retrieved, use the default // debugger (ntsd.exe). // if (FAILED(StringCchCopyEx( szDebugCmdLine, SIZECHARS(szDebugCmdLine), NTSD_EXE, NULL, NULL, STRSAFE_NULL_ON_FAILURE))) { // // No debugger will be used. // NOTHING; } } if ((SUCCEEDED(StringCchCatEx( szDebugCmdLine, SIZECHARS(szDebugCmdLine), L" ", NULL, NULL, STRSAFE_NULL_ON_FAILURE | STRSAFE_IGNORE_NULLS))) && (SUCCEEDED(StringCchCatEx( szDebugCmdLine, SIZECHARS(szDebugCmdLine), szCmdLine, NULL, NULL, STRSAFE_NULL_ON_FAILURE | STRSAFE_IGNORE_NULLS)))) { // // Only overwrite the original command line buffer with // a debug command line info if we were successful in // builing a debug command line. // if (FAILED(StringCchCopyEx( szCmdLine, SIZECHARS(szCmdLine), szDebugCmdLine, NULL, NULL, STRSAFE_IGNORE_NULLS))) { // // Nothing more we can do here. // NOTHING; } } } RegCloseKey(hKey); } } #endif // DBG // // Attempt to create the user's environment block. If for some reason we // can't, we'll just have to create the process without it. // if (!CreateEnvironmentBlock(&lpEnvironment, hUserToken, FALSE)) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_ERRORS, "UMPNPMGR: CreateDeviceInstallClient: " "Failed to allocate environment block, error = %d!\n", GetLastError())); lpEnvironment = NULL; } StartupInfo.cb = sizeof(StartupInfo); StartupInfo.wShowWindow = SW_SHOW; StartupInfo.lpDesktop = DEFAULT_INTERACTIVE_DESKTOP; // WinSta0\Default // // CreateProcessAsUser will create the process in the session // specified by the by user-token. // if (!CreateProcessAsUser(hUserToken, // hToken NULL, // lpApplicationName szCmdLine, // lpCommandLine NULL, // lpProcessAttributes NULL, // lpThreadAttributes FALSE, // bInheritHandles CREATE_UNICODE_ENVIRONMENT | DETACHED_PROCESS, // dwCreationFlags lpEnvironment, // lpEnvironment NULL, // lpDirectory &StartupInfo, // lpStartupInfo &ProcessInfo // lpProcessInfo )) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_ERRORS, "UMPNPMGR: CreateDeviceInstallClient: " "Create rundll32 process failed, error = %d\n", GetLastError())); goto Clean0; } ASSERT(ProcessInfo.hProcess); ASSERT(ProcessInfo.hThread); // // Create an event for use with overlapped I/O - no security, manual // reset, not signalled, no name. // overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (overlapped.hEvent == NULL) { goto Clean0; } // // Connect to the newly created named pipe. If newdev is not already // connected to the named pipe, then ConnectNamedPipe() will fail with // ERROR_IO_PENDING, and we will wait on the overlapped event. If // newdev is already connected, it will fail with ERROR_PIPE_CONNECTED. // Note however that neither of these is an error condition. // if (!ConnectNamedPipe(hDeviceInstallPipe, &overlapped)) { // // Overlapped ConnectNamedPipe should always return FALSE on // success. Check the last error to see what really happened. // dwError = GetLastError(); if (dwError == ERROR_IO_PENDING) { // // I/O is pending, wait up to one minute for the client to // connect, also wait on the process in case it terminates // unexpectedly. // hFinishEvents[0] = overlapped.hEvent; hFinishEvents[1] = ProcessInfo.hProcess; dwWait = WaitForMultipleObjects(2, hFinishEvents, FALSE, PNP_PIPE_TIMEOUT); // 60 seconds if (dwWait == WAIT_OBJECT_0) { // // The overlapped I/O operation completed. Check the status // of the operation. // if (!GetOverlappedResult(hDeviceInstallPipe, &overlapped, &dwBytes, FALSE)) { goto Clean0; } } else { // // Either the connection timed out, or the client process // exited. Cancel pending I/O against the pipe, and quit. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_ERRORS, "UMPNPMGR: CreateDeviceInstallClient: " "Connect timed out, or client process exited!\n")); CancelIo(hDeviceInstallPipe); goto Clean0; } } else if (dwError != ERROR_PIPE_CONNECTED) { // // If the last error indicates anything other than pending I/O, // or that The client is already connected to named pipe, fail. // goto Clean0; } } else { // // ConnectNamedPipe should not return anything but FALSE in // overlapped mode. // goto Clean0; } // // The client is now connected to the named pipe. // Close the overlapped event. // CloseHandle(overlapped.hEvent); overlapped.hEvent = NULL; // // The first data in the device install pipe will be the length of // the name of the event that will be used to sync up umpnpmgr.dll // and newdev.dll. // if (!WriteFile(hDeviceInstallPipe, &ulDeviceInstallEventNameSize, sizeof(ulDeviceInstallEventNameSize), &ulSize, NULL)) { LogErrorEvent(ERR_WRITING_SERVER_INSTALL_PIPE, GetLastError(), 0); goto Clean0; } // // The next data in the device install pipe will be the name of the // event that will be used to sync up umpnpmgr.dll and newdev.dll. // if (!WriteFile(hDeviceInstallPipe, (LPCVOID)szDeviceInstallEventName, ulDeviceInstallEventNameSize, &ulSize, NULL)) { LogErrorEvent(ERR_WRITING_SERVER_INSTALL_PIPE, GetLastError(), 0); goto Clean0; } // // Allocate a new device install client entry for the list, and save all // the handles with it. // pDeviceInstallClient = HeapAlloc(ghPnPHeap, 0, sizeof(INSTALL_CLIENT_ENTRY)); if(!pDeviceInstallClient) { goto Clean0; } pDeviceInstallClient->Next = NULL; pDeviceInstallClient->RefCount = 1; pDeviceInstallClient->ulSessionId = SessionId; pDeviceInstallClient->hEvent = hDeviceInstallEvent; pDeviceInstallClient->hPipe = hDeviceInstallPipe; pDeviceInstallClient->hProcess = ProcessInfo.hProcess; pDeviceInstallClient->hDisconnectEvent = hDeviceInstallDisconnectEvent; pDeviceInstallClient->ulInstallFlags = 0; pDeviceInstallClient->LastDeviceId[0] = L'\0'; // // Insert the newly created device install client info to our list. // The caller must have previously acquired the InstallClientList lock. // entry = (PINSTALL_CLIENT_ENTRY)InstallClientList.Next; if (!entry) { InstallClientList.Next = pDeviceInstallClient; } else { while ((PINSTALL_CLIENT_ENTRY)entry->Next) { entry = (PINSTALL_CLIENT_ENTRY)entry->Next; } entry->Next = pDeviceInstallClient; } bStatus = TRUE; Clean0: NOTHING; } except (EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_INSTALL, "UMPNPMGR: Exception during CreateDeviceInstallClient!\n")); ASSERT(0); bStatus = FALSE; // // Reference the following variable so the compiler will respect // statement ordering w.r.t. its assignment. // lpEnvironment = lpEnvironment; ProcessInfo.hThread = ProcessInfo.hThread; ProcessInfo.hProcess = ProcessInfo.hProcess; hUserToken = hUserToken; hDeviceInstallDisconnectEvent = hDeviceInstallDisconnectEvent; hDeviceInstallEvent = hDeviceInstallEvent; hDeviceInstallPipe = hDeviceInstallPipe; } if (lpEnvironment) { DestroyEnvironmentBlock(lpEnvironment); } // // Close the handle to the thread since we don't need it. // if (ProcessInfo.hThread) { CloseHandle(ProcessInfo.hThread); } if (hUserToken) { CloseHandle(hUserToken); } if (overlapped.hEvent) { CloseHandle(overlapped.hEvent); } if (!bStatus) { ASSERT(!pDeviceInstallClient); if (hDeviceInstallDisconnectEvent) { CloseHandle(hDeviceInstallDisconnectEvent); } if (hDeviceInstallEvent) { CloseHandle(hDeviceInstallEvent); } if (hDeviceInstallPipe) { CloseHandle(hDeviceInstallPipe); } if (ProcessInfo.hProcess) { CloseHandle(ProcessInfo.hProcess); } *DeviceInstallClient = NULL; } else { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: CreateDeviceInstallClient: created new client for Session %d.\n", SessionId)); ASSERT(pDeviceInstallClient); ASSERT(pDeviceInstallClient->hEvent); ASSERT(pDeviceInstallClient->hPipe); ASSERT(pDeviceInstallClient->hProcess); ASSERT(pDeviceInstallClient->hDisconnectEvent); *DeviceInstallClient = pDeviceInstallClient; } return bStatus; } // CreateDeviceInstallClient BOOL ConnectDeviceInstallClient( IN ULONG SessionId, OUT PINSTALL_CLIENT_ENTRY *DeviceInstallClient ) /*++ Routine Description: Retrieves the device install client handles for the specified session, if one exists. Arguments: SessionId - Session for which a device install client should be created or connected to. DeviceInstallClient - Receives a pointer to receive the a pointer to the device install client for this session. Return Value: Returns TRUE if an existing device install client was found for the specified session, FALSE otherwise. Notes: The InstallClientList lock must be acquired by the caller of this routine. --*/ { PINSTALL_CLIENT_ENTRY entry; BOOL bClientFound = FALSE; // // Make sure the specified SessionId is valid. // ASSERT(SessionId != INVALID_SESSION); if (SessionId == INVALID_SESSION) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_ERRORS, "UMPNPMGR: ConnectDeviceInstallClient: Invalid SessionId %d, exiting!\n", SessionId)); return FALSE; } // // Validate output parameters. // ASSERT(DeviceInstallClient); if (!DeviceInstallClient) { return FALSE; } entry = LocateDeviceInstallClient(SessionId); if (entry) { // // An existing client was found for this session, so we should already // have event, pipe, and process handles for it. // ASSERT(entry->hEvent); ASSERT(entry->hPipe); ASSERT(entry->hProcess); // // Make sure the client's process object is in the nonsignalled state, // else newdev has already gone away, and we can't use it. // if (WaitForSingleObject(entry->hProcess, 0) != WAIT_TIMEOUT) { // // Remove the initial reference to close the handles and remove it // from our list. // ASSERT(entry->RefCount == 1); DereferenceDeviceInstallClient(entry); } else { // // If we are reconnecting to a client that was last used during a // previous connection to this session, we will not have a disconnect // event for it yet, so create one here. If we just created this client // during the current connection to this session, we will already have a // disconnect event for it. // if (!entry->hDisconnectEvent) { entry->hDisconnectEvent = CreateEvent(NULL, TRUE, FALSE, NULL); } // // Either way, make sure we have a disconnect event by now. // ASSERT(entry->hDisconnectEvent); if (entry->hDisconnectEvent) { bClientFound = TRUE; KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: ConnectDeviceInstallClient: found existing client on Session %d.\n", SessionId)); *DeviceInstallClient = entry; } } } if (!bClientFound) { *DeviceInstallClient = NULL; } return bClientFound; } // ConnectDeviceInstallClient BOOL DisconnectDeviceInstallClient( IN PINSTALL_CLIENT_ENTRY DeviceInstallClient ) /*++ Routine Description: This routine disconnects from the current client-side install process (if one exists) by signalling the appropriate hDisconnectEvent and closing the handle. Arguments: DeviceInstallClient - Receives a pointer to the device install client that should be disconnected. Return Value: Returns TRUE if successful, FALSE otherwise. Notes: The InstallClientList lock must be acquired by the caller of this routine. --*/ { BOOL bStatus = FALSE; ASSERT(DeviceInstallClient); if (DeviceInstallClient) { ASSERT(DeviceInstallClient->hEvent); ASSERT(DeviceInstallClient->hPipe); ASSERT(DeviceInstallClient->hProcess); // // We may or may not have a handle to a diconnect event because we may // have an existing client for this session, but not reconnected to it. // // If we do have an hDisconnectEvent, set the event now since we // will otherwise block waiting for newdev.dll to set the // hDeviceInstallEvent. Setting the hDisconnectEvent alerts the // waiter that the device install was NOT successful, and that it // should preserve the device in the install list. // if (DeviceInstallClient->hDisconnectEvent) { SetEvent(DeviceInstallClient->hDisconnectEvent); CloseHandle(DeviceInstallClient->hDisconnectEvent); DeviceInstallClient->hDisconnectEvent = NULL; } KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: Disconnected from device install client on Console SessionId %d\n", DeviceInstallClient->ulSessionId)); bStatus = TRUE; } return bStatus; } // DisconnectDeviceInstallClient PINSTALL_CLIENT_ENTRY LocateDeviceInstallClient( IN ULONG SessionId ) /*++ Routine Description: This routine locates the client-side install process for a given session (if one exists). Arguments: SessionId - Session whose device install client should be located. Return Value: Returns a device install client entry if successful, NULL otherwise. Note: The InstallClientList lock must be acquired by the caller of this routine. --*/ { PINSTALL_CLIENT_ENTRY entry, foundEntry = NULL; BOOL bClientFound = FALSE; // // Make sure the specified SessionId is valid. // ASSERT(SessionId != INVALID_SESSION); if (SessionId == INVALID_SESSION) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_ERRORS, "UMPNPMGR: LocateDeviceInstallClient: Invalid Console SessionId %d, exiting!\n", SessionId)); return FALSE; } // // Search for a client on the specified session. // for (entry = (PINSTALL_CLIENT_ENTRY)InstallClientList.Next; entry != NULL; entry = entry->Next) { if (entry->ulSessionId == SessionId) { // // Make sure we only have one entry per session. // ASSERT(!bClientFound); bClientFound = TRUE; foundEntry = entry; } } return foundEntry; } // LocateDeviceInstallClient VOID ReferenceDeviceInstallClient( IN PINSTALL_CLIENT_ENTRY DeviceInstallClient ) /*++ Routine Description: This routine increments the reference count for a device install client entry. Parameters: DeviceInstallClient - Supplies a pointer to the device install client to be referenced. Return Value: None. Note: The appropriate synchronization lock must be held on the device install client list before this routine can be called --*/ { ASSERT(DeviceInstallClient); ASSERT(((LONG)DeviceInstallClient->RefCount) > 0); KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT | DBGF_INSTALL, "UMPNPMGR: ---------------- ReferenceDeviceInstallClient : Session %d [%d --> %d]\n", DeviceInstallClient->ulSessionId, DeviceInstallClient->RefCount, DeviceInstallClient->RefCount + 1)); DeviceInstallClient->RefCount++; return; } // ReferenceDeviceInstallClient VOID DereferenceDeviceInstallClient( IN PINSTALL_CLIENT_ENTRY DeviceInstallClient ) /*++ Routine Description: This routine decrements the reference count for a device install client entry, removing the entry from the list and freeing the associated memory if there are no outstanding reference counts. Parameters: DeviceInstallClient - Supplies a pointer to the device install client to be dereferenced. Return Value: None. Note: The appropriate synchronization lock must be held on the device install client list before this routine can be called --*/ { ASSERT(DeviceInstallClient); ASSERT(((LONG)DeviceInstallClient->RefCount) > 0); // // Avoid over-dereferencing the client. // if (((LONG)DeviceInstallClient->RefCount) > 0) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT | DBGF_INSTALL, "UMPNPMGR: ---------------- DereferenceDeviceInstallClient: Session %d [%d --> %d]\n", DeviceInstallClient->ulSessionId, DeviceInstallClient->RefCount, DeviceInstallClient->RefCount - 1)); DeviceInstallClient->RefCount--; } else { return; } // // If the refcount is zero then the entry no longer needs to be in the list // so remove and free it. // if (DeviceInstallClient->RefCount == 0) { BOOL bClientFound = FALSE; PINSTALL_CLIENT_ENTRY entry, prev; entry = (PINSTALL_CLIENT_ENTRY)InstallClientList.Next; prev = NULL; while (entry) { if (entry == DeviceInstallClient) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT | DBGF_INSTALL, "UMPNPMGR: ---------------- DereferenceDeviceInstallClient: Removing client for Session %d\n", entry->ulSessionId)); // // We should have handles to the pipe, event and process objects for // the client, because we will close them here. // ASSERT(entry->hPipe); ASSERT(entry->hEvent); ASSERT(entry->hProcess); // // We may or may not have a handle to a diconnect event because we // may have an existing client for this session, but not yet // connected to it. // // If we do have an hDisconnectEvent, set the event now since we // will otherwise block waiting for newdev.dll to set the // hDeviceInstallEvent. Setting the hDisconnectEvent alerts the // waiter that the device install was NOT successful, and that it // should preserve the device in the install list. // if (entry->hDisconnectEvent) { SetEvent(entry->hDisconnectEvent); CloseHandle(entry->hDisconnectEvent); } // // Close the pipe and event handles so that the client will get a // ReadFile error and know that we are finished. Close the process // handle as well. // if (entry->hPipe) { CloseHandle(entry->hPipe); } if (entry->hEvent) { CloseHandle(entry->hEvent); } if (entry->hProcess) { CloseHandle(entry->hProcess); } // // Remove the device install client entry from the list, and free it // now. // if (prev) { prev->Next = entry->Next; } else { InstallClientList.Next = entry->Next; } HeapFree(ghPnPHeap, 0, entry); if(prev) { entry = (PINSTALL_CLIENT_ENTRY)prev->Next; } else { entry = (PINSTALL_CLIENT_ENTRY)InstallClientList.Next; } bClientFound = TRUE; break; } prev = entry; entry = (PINSTALL_CLIENT_ENTRY)entry->Next; } ASSERT(bClientFound); } return; } // DereferenceDeviceInstallClient BOOL DoDeviceInstallClient( IN LPWSTR DeviceId, IN PULONG SessionId, IN ULONG Flags, OUT PINSTALL_CLIENT_ENTRY *DeviceInstallClient ) /*++ Routine Description: This routine kicks off a newdev.dll process (if someone is logged in) that displays UI informing the user of the status of the server-side device installation. Arguments: DeviceId - Supplies the devnode ID of the device being installed. SessionId - Specifies the session that the newdev client is to be launched on. If the DEVICE_INSTALL_DISPLAY_ON_CONSOLE flag is specified, the specified SessionId is ignored. Upon successful return, the SessionId for the the session where the device install client was created is returned. If unsuccessful, the returned SessionId is INVALID_SESSION, (0xFFFFFFFF). Flags - Specifies flags describing the behavior of the device install client. The following flags are currently defined: DEVICE_INSTALL_UI_ONLY - tells newdev.dll whether to do a full install or just show UI while umpnpmgr.dll is doing a server side install. DEVICE_INSTALL_PLAY_SOUND - tells newdev.dll whether to play a sound. DEVICE_INSTALL_DISPLAY_ON_CONSOLE - if specified, the value specified in SessionId will be ignored, and the client will always be displayed on the current active console session. DeviceInstallClient - Supplies the address of a variable to receive, upon success, a pointer to a pointer to a device install client. Return Value: If the process was successfully created, the return value is TRUE. This routine doesn't wait until the process terminates. If we couldn't create the process (e.g., because no user was logged in), the return value is FALSE. Notes: None. --*/ { BOOL bStatus, bSameDevice = FALSE; ULONG DeviceIdSize, ulSize, ulSessionId; ULONG InstallFlags; PINSTALL_CLIENT_ENTRY pDeviceInstallClient = NULL; // // Assume failure. // bStatus = FALSE; // // Validate output parameters. // if (!DeviceInstallClient || !SessionId) { return FALSE; } try { // // Check if we should skip all client side UI. // if (gbSuppressUI) { // // If we were launching newdev for client-side installation, log an // event to let someone know that we didn't. // if (!(Flags & DEVICE_INSTALL_UI_ONLY)) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL | DBGF_WARNINGS, "UMPNPMGR: DoDeviceInstallClient: Client-side newdev UI has been suppressed, exiting.\n")); LogWarningEvent(WRN_NEWDEV_UI_SUPPRESSED, 1, DeviceId); } goto Clean0; } // // Determine the session to use, based on the supplied flags. // if (Flags & DEVICE_INSTALL_DISPLAY_ON_CONSOLE) { ulSessionId = GetActiveConsoleSessionId(); } else { ASSERT(*SessionId != INVALID_SESSION); ulSessionId = *SessionId; } ASSERT(ulSessionId != INVALID_SESSION); // // If the specified session is not currently connected anywhere, don't // bother creating any UI. // if (!IsSessionConnected(ulSessionId)) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT, "UMPNPMGR: DoDeviceInstallClient: SessionId %d not connected, exiting\n", ulSessionId)); goto Clean0; } // // Lock the client list while we retrieve / create a client to use. // LockNotifyList(&InstallClientList.Lock); // // First, try to connect to an existing client already running on this // session. // bStatus = ConnectDeviceInstallClient(ulSessionId, &pDeviceInstallClient); if (bStatus) { // // If the client we just reconnected to was client-side installing // this same device when it was last disconnected, don't send it // again. // if ((IS_FLAG_CLEAR(Flags, DEVICE_INSTALL_UI_ONLY)) && (CompareString( LOCALE_INVARIANT, NORM_IGNORECASE, pDeviceInstallClient->LastDeviceId, -1, DeviceId, -1) == CSTR_EQUAL)) { bSameDevice = TRUE; } } else { // // Create a new device install client for this session. // bStatus = CreateDeviceInstallClient(ulSessionId, &pDeviceInstallClient); } if (bStatus) { // // The client should only have the initial reference from when it // was added to the list, since any use of the client is done on // this single install thread. // ASSERT(pDeviceInstallClient); ASSERT(pDeviceInstallClient->RefCount == 1); // // Keep track of both client and server flags. // pDeviceInstallClient->ulInstallFlags = Flags; // // Reference the device install client while it is in use. The // caller must remove this reference when it is done with it. // ReferenceDeviceInstallClient(pDeviceInstallClient); } UnlockNotifyList(&InstallClientList.Lock); if (!bStatus || bSameDevice) { // // If we don't have a client, or we don't need to resend the device // instance to install, we're done. // goto Clean0; } // // Filter out the install flags that the client doesn't know about. // InstallFlags = (Flags & DEVICE_INSTALL_CLIENT_MASK); DeviceIdSize = (lstrlen(DeviceId) + 1) * sizeof(WCHAR); // // Make sure we reset the device install event since we will block waiting for // newdev.dll to set this event to let us know that it is finished with the current // installation. // if (pDeviceInstallClient->hEvent) { ResetEvent(pDeviceInstallClient->hEvent); } // // When sending stuff to newdev.dll over the device install pipe it expects // two ULONGs followed by the DeviceID. The first ULONG is the Flags which // tells newdev whether we are doing a UI only install or a full install. // The next ULONG is the size of the Device ID and then we send the DeviceID. // if (WriteFile(pDeviceInstallClient->hPipe, &InstallFlags, sizeof(InstallFlags), &ulSize, NULL )) { if (WriteFile(pDeviceInstallClient->hPipe, &DeviceIdSize, sizeof(DeviceIdSize), &ulSize, NULL )) { if (WriteFile(pDeviceInstallClient->hPipe, DeviceId, DeviceIdSize, &ulSize, NULL )) { bStatus = TRUE; } else { LogErrorEvent(ERR_WRITING_SERVER_INSTALL_PIPE, GetLastError(), 0); } } else { LogErrorEvent(ERR_WRITING_SERVER_INSTALL_PIPE, GetLastError(), 0); } } else { LogErrorEvent(ERR_WRITING_SERVER_INSTALL_PIPE, GetLastError(), 0); } // // Note that we don't remove the reference placed on the install client // entry while it was in use, because it will be handed back to the // caller, who will wait on the client's event and process handles. The // caller should remove the reference when it is no longer using these. // Removing the final reference will cause the client to be closed. // } except(EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_WARNINGS, "UMPNPMGR: Exception during DoDeviceInstallClient!\n")); ASSERT(0); bStatus = FALSE; // // Reference the following variable so the compiler will respect // statement ordering w.r.t. its assignment. // pDeviceInstallClient = pDeviceInstallClient; } Clean0: if (!bStatus) { // // If we had a device install client at some point, but failed to send // it the request, remove the reference we placed on it. // if (pDeviceInstallClient) { LockNotifyList(&InstallClientList.Lock); DereferenceDeviceInstallClient(pDeviceInstallClient); UnlockNotifyList(&InstallClientList.Lock); } // // Let the caller know there isn't a device install client handling // this request. // *SessionId = INVALID_SESSION; *DeviceInstallClient = NULL; } else { // // Make sure we're returning valid client information. // ASSERT(pDeviceInstallClient); ASSERT(pDeviceInstallClient->hEvent); ASSERT(pDeviceInstallClient->hPipe); ASSERT(pDeviceInstallClient->hProcess); ASSERT(pDeviceInstallClient->hDisconnectEvent); ASSERT(pDeviceInstallClient->ulSessionId != INVALID_SESSION); *SessionId = pDeviceInstallClient->ulSessionId; *DeviceInstallClient = pDeviceInstallClient; } return bStatus; } // DoDeviceInstallClient unsigned _stdcall ThreadProc_RunOnce( LPVOID lpThreadParameter ) /*++ Routine Description: This routine performs server-side processing of the RunOnce entries that have been accumulated by setupapi. The RunOnce node list will be empty upon return. Arguments: lpThreadParameter - Specifies the head of the RunOnce node list to be processed. Return Value: If successful, the return value is NO_ERROR. If failure, the return value is a Win32 error code indicating the cause of failure. --*/ { DWORD Err = NO_ERROR; PPSP_RUNONCE_NODE RunOnceNode; HINSTANCE hLib; CHAR AnsiBuffer[MAX_PATH * 2]; PSTR EndPtr; RUNDLLPROCA fpRunDllProcA = NULL; RUNDLLPROCW fpRunDllProcW = NULL; HRESULT hr; // // This thread is executed synchronously during the server-side device // installer process, therefore setupapi must already be loaded. The // RunOnce node list is stored as global state in SETUPAPI.DLL, while it is // loaded for this instance of server-side device install processing. // ASSERT(ghDeviceInstallerLib != NULL); // // ISSUE-2002/02/20-jamesca: Consider a separate thread for each entry? // Note that this routine processes all RunOnce entries in the context of // a single thread. Any catastrophic errors encountered while processing // one entry will affect or prevent subsequent entries. For greater // isolation, we could consider creating a separate thread for each, but // that would adversely affect performance to protect against things that // are supposed to be signed in the first place, and any RunOnce entry we // call could do worse things to this process anyways. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_INSTALL, "UMPNPMGR: Processing RunOnce entries " "during server-side device install.\n")); // // Process each node in the list supplied. This thread is only created // because there are nodes to be processed, so the list must be non-NULL. // RunOnceNode = (PPSP_RUNONCE_NODE)lpThreadParameter; ASSERT(RunOnceNode != NULL); while (RunOnceNode != NULL) { hLib = NULL; try { // // First, load the DLL (setupapi already did the signature // verification for us, so this should be safe). // hLib = LoadLibrary(RunOnceNode->DllFullPath); if (hLib) { // // First, try to retrieve the 'W' (Unicode) version of the entrypoint. // if (SUCCEEDED(StringCchCopyExA( AnsiBuffer, (sizeof(AnsiBuffer) / sizeof(CHAR)) - 1, RunOnceNode->DllEntryPointName, &EndPtr, NULL, STRSAFE_IGNORE_NULLS | STRSAFE_NULL_ON_FAILURE))) { *EndPtr = 'W'; *(EndPtr + 1) = '\0'; fpRunDllProcW = (RUNDLLPROCW)GetProcAddress(hLib, AnsiBuffer); } if (!fpRunDllProcW) { // // Couldn't find unicode entrypt, try 'A' decorated one // *EndPtr = 'A'; fpRunDllProcA = (RUNDLLPROCA)GetProcAddress(hLib, AnsiBuffer); if (!fpRunDllProcA) { // // Couldn't find 'A' decorated entrypt, try undecorated name // undecorated entrypts are assumed to be ANSI // *EndPtr = '\0'; fpRunDllProcA = (RUNDLLPROCA)GetProcAddress(hLib, AnsiBuffer); } } // // We shoulda found one of these... // ASSERT(fpRunDllProcW || fpRunDllProcA); if (fpRunDllProcW) { // // Re-use our ANSI buffer to hold a writeable copy of our // DLL argument string. // hr = StringCchCopyW((LPWSTR)AnsiBuffer, sizeof(AnsiBuffer) / sizeof(WCHAR), // size of buffer in WCHARs RunOnceNode->DllParams); ASSERT(SUCCEEDED(hr)); fpRunDllProcW(NULL, ghInst, (LPWSTR)AnsiBuffer, SW_HIDE); } else if (fpRunDllProcA) { // // Need to convert the arg string to ANSI first... // WideCharToMultiByte(CP_ACP, 0, // default composite char behavior RunOnceNode->DllParams, -1, AnsiBuffer, sizeof(AnsiBuffer), NULL, NULL ); fpRunDllProcA(NULL, ghInst, AnsiBuffer, SW_HIDE); } } } except(EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS | DBGF_INSTALL, "UMPNPMGR: Exception %d during ThreadProc_RunOnce!\n", GetExceptionCode())); Err = GetExceptionCode(); ASSERT(0); // // Reference the following variable so the compiler will respect // statement ordering w.r.t. its assignment. // hLib = hLib; } // // Free the library, if loaded. // if (hLib != NULL) { FreeLibrary(hLib); hLib = NULL; } // // If we encountered an exception processing this entry, exit // immediately. Don't process any additional entries because the // exception may have occured because the thread state was corrupted by // one of our callees, which could cause problems for the others. Note, // the main device install thread is waiting on this thread, and will // log an error in the eventlog if we exit with an error. // if (Err != NO_ERROR) { goto Clean0; } // // We're still doing ok, move on to the next one. // RunOnceNode = RunOnceNode->Next; } // // If we make it here, we managed to process all queued RunOnce entries // without any catastrophic failures. // ASSERT(Err == NO_ERROR); Clean0: _endthreadex(Err); // // Unreachable code, but it makes the compiler happy. // return Err; } // ThreadProc_RunOnce VOID DoRunOnce( VOID ) /*++ Routine Description: This routine performs server-side processing of the RunOnce entries that have been accumulated by setupapi. The RunOnce node list will be empty upon return. Arguments: None. Return Value: None. --*/ { PPSP_RUNONCE_NODE RunOnceNode; HANDLE hRunOnceThread; DWORD ThreadID = 0, ThreadExitCode = NO_ERROR, WaitStatus; // // First, check to see if there are any RunOnce entries that need to be // processed. // RunOnceNode = fpAccessRunOnceNodeList(); if (RunOnceNode != NULL) { // // Create the thread that will process the RunOnce RUNDLL entries that have // been queued up. // hRunOnceThread = (HANDLE)_beginthreadex( (void*)NULL, (unsigned)0, (unsigned int (__stdcall *)(void *))ThreadProc_RunOnce, (void*)RunOnceNode, (unsigned)0, (unsigned int*)&ThreadID); if (hRunOnceThread != NULL) { // // Wait synchronously for the RunOnce thread to complete processing the // nodes, and exit. // WaitStatus = WaitForSingleObject( hRunOnceThread, INFINITE); ASSERT(WaitStatus == WAIT_OBJECT_0); if (GetExitCodeThread( hRunOnceThread, &ThreadExitCode)) { // // If the thread exit code was not NO_ERROR, some exception // occured while processing the RunOnce entries. // if (ThreadExitCode != NO_ERROR) { LogErrorEvent(ERR_PROCESSING_RUNONCE, ThreadExitCode, 0); } } else { // // The above wait on the thread handle succeeded, so the thread // should NOT still be active. // ASSERT(GetLastError() != STILL_ACTIVE); } // // Close the handle to the thread object. // CloseHandle(hRunOnceThread); } } // // Free all the members in the list. // fpDestroyRunOnceNodeList(); return; } // DoRunOnce DWORD SessionNotificationHandler( IN DWORD EventType, IN PWTSSESSION_NOTIFICATION SessionNotification ) /*++ Routine Description: This routine handles console switch events. Arguments: EventType - The type of event that has occurred. SessionNotification - Additional event information. Return Value: If successful, the return value is NO_ERROR. If failure, the return value is a Win32 error code indicating the cause of failure. Notes: Session change notification events are used to determine when there is a session with a logged on user currently connected to the Console. When a user session is connected to the Console, we signal the "logged on" event, which will wake the device installation thread to perform any pending client-side device install events. When there is no user session connected to the Console, the "logged on" event is reset. The "logged on" event may also be set/reset for logon/logoff events to session 0 by PNP_ReportLogOn / PnpConsoleCtrlHandler, in the event that Terminal Services are not available. --*/ { PINSTALL_CLIENT_ENTRY pDeviceInstallClient; // // Validate the session change notification structure. // ASSERT(SessionNotification); ASSERT(SessionNotification->cbSize >= sizeof(WTSSESSION_NOTIFICATION)); if ((!ARGUMENT_PRESENT(SessionNotification)) || (SessionNotification->cbSize < sizeof(WTSSESSION_NOTIFICATION))) { return ERROR_INVALID_PARAMETER; } switch (EventType) { case WTS_CONSOLE_CONNECT: // // The notification was sent because the specified session was // connected to the Console. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT | DBGF_INSTALL, "UMPNPMGR: WTS_CONSOLE_CONNECT: " "SessionId %d\n", SessionNotification->dwSessionId)); // // Keep track globally of the current active console session, and // signal that it's safe to access it. // // NOTE - we must set the ghActiveConsoleSessionEvent here, prior to // calling IsConsoleSession below, which waits on it, else we will // deadlock out service's control handler. // gActiveConsoleSessionId = (ULONG)SessionNotification->dwSessionId; if (ghActiveConsoleSessionEvent) { SetEvent(ghActiveConsoleSessionEvent); } // // If the session just connected to the Console already has a logged // on user, signal the "logged on" event. // if (IsConsoleSession((ULONG)SessionNotification->dwSessionId) && IsUserLoggedOnSession((ULONG)SessionNotification->dwSessionId)) { if (InstallEvents[LOGGED_ON_EVENT]) { SetEvent(InstallEvents[LOGGED_ON_EVENT]); KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT | DBGF_INSTALL, "UMPNPMGR: WTS_CONSOLE_CONNECT: " "SetEvent LOGGED_ON_EVENT\n")); } } break; case WTS_CONSOLE_DISCONNECT: // // The notification was sent because the specified session // was disconnected from the Console. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT | DBGF_INSTALL, "UMPNPMGR: WTS_CONSOLE_DISCONNECT: " "SessionId %d\n", SessionNotification->dwSessionId)); // // Check if the session just disconnected from the "Console" has a // logged on user. // if (IsConsoleSession((ULONG)SessionNotification->dwSessionId) && IsUserLoggedOnSession((ULONG)SessionNotification->dwSessionId)) { // // Reset the "logged on" event. // if (InstallEvents[LOGGED_ON_EVENT]) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT | DBGF_INSTALL, "UMPNPMGR: WTS_CONSOLE_DISCONNECT: " "ResetEvent LOGGED_ON_EVENT\n")); ResetEvent(InstallEvents[LOGGED_ON_EVENT]); } // // Since this is a console switch event, only do something with // a device install client on the console session if it's // behavior was specifically designated for the console (i.e. - // it was put on this session because it was the active console // session at the time). // LockNotifyList(&InstallClientList.Lock); pDeviceInstallClient = LocateDeviceInstallClient((ULONG)SessionNotification->dwSessionId); if ((pDeviceInstallClient) && (pDeviceInstallClient->ulInstallFlags & DEVICE_INSTALL_DISPLAY_ON_CONSOLE)) { if (pDeviceInstallClient->ulInstallFlags & DEVICE_INSTALL_UI_ONLY) { // // If it was just for UI only, dereference it to make it // go away when it's no longer in use. // DereferenceDeviceInstallClient(pDeviceInstallClient); } else { // // Otherwise, it is a legitimate client-side // installation in progress, so just disconnect from it. // This does not remove a reference because we want it // to stay around in case the session is reconnected to // and the device still needs to be installed, - or // until we find out that there are no more devices to // install, in which case we'll close it. // DisconnectDeviceInstallClient(pDeviceInstallClient); } } UnlockNotifyList(&InstallClientList.Lock); } // // The current active console session is invalid until we receive a // subsequent console connect event. Reset the event. // // NOTE - we must reset the ghActiveConsoleSessionEvent here, after // calling IsConsoleSession above, which waits on it, else we will // deadlock out service's control handler. // if (ghActiveConsoleSessionEvent) { ResetEvent(ghActiveConsoleSessionEvent); } gActiveConsoleSessionId = INVALID_SESSION; break; case WTS_REMOTE_CONNECT: // // The specified session was connected remotely. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT | DBGF_INSTALL, "UMPNPMGR: WTS_REMOTE_CONNECT: " "SessionId %d\n", SessionNotification->dwSessionId)); if (((ULONG)SessionNotification->dwSessionId == MAIN_SESSION) && (IsUserLoggedOnSession((ULONG)SessionNotification->dwSessionId)) && (!IsFastUserSwitchingEnabled())) { // // If the remote session that was just connected from the "Console" // has a logged on user, signal the "logged on" event. // if (InstallEvents[LOGGED_ON_EVENT]) { SetEvent(InstallEvents[LOGGED_ON_EVENT]); KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT | DBGF_INSTALL, "UMPNPMGR: WTS_REMOTE_CONNECT: " "SetEvent LOGGED_ON_EVENT\n")); } } break; case WTS_REMOTE_DISCONNECT: // // The specified session was disconnected remotely. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT | DBGF_INSTALL, "UMPNPMGR: WTS_REMOTE_DISCONNECT: " "SessionId %d\n", SessionNotification->dwSessionId)); if (((ULONG)SessionNotification->dwSessionId == MAIN_SESSION) && (IsUserLoggedOnSession((ULONG)SessionNotification->dwSessionId)) && (!IsFastUserSwitchingEnabled())) { // // If the remote session that was disconnected from the "Console" // has a logged on user, reset the "logged on" event. // if (InstallEvents[LOGGED_ON_EVENT]) { ResetEvent(InstallEvents[LOGGED_ON_EVENT]); KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT | DBGF_INSTALL, "UMPNPMGR: WTS_REMOTE_DISCONNECT: " "ResetEvent LOGGED_ON_EVENT\n")); } // // Since this remote session is being treated as the console, // only do something with a device install client if it's // behavior was NOT specifically designated for the console // (i.e. - it was put on this session because it was the active // console session at the time). // LockNotifyList(&InstallClientList.Lock); pDeviceInstallClient = LocateDeviceInstallClient((ULONG)SessionNotification->dwSessionId); if ((pDeviceInstallClient) && ((pDeviceInstallClient->ulInstallFlags & DEVICE_INSTALL_DISPLAY_ON_CONSOLE) == 0)) { if (pDeviceInstallClient->ulInstallFlags & DEVICE_INSTALL_UI_ONLY) { // // If it was just for UI only, dereference it to make it // go away when it's no longer in use. // DereferenceDeviceInstallClient(pDeviceInstallClient); } else { // // Otherwise, it is a legitimate client-side // installation in progress, so just disconnect from it. // This does not remove a reference because we want it // to stay around in case the session is reconnected to // and the device still needs to be installed, - or // until we find out that there are no more devices to // install, in which case we'll close it. // DisconnectDeviceInstallClient(pDeviceInstallClient); } } UnlockNotifyList(&InstallClientList.Lock); } break; case WTS_SESSION_UNLOCK: // // The interactive windowstation on the specified session was unlocked. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT | DBGF_INSTALL, "UMPNPMGR: WTS_SESSION_UNLOCK: " "SessionId %d\n", SessionNotification->dwSessionId)); if (SessionNotification->dwSessionId == MAIN_SESSION) { // // For the main session, Terminal Services may or may not be // available, so we keep track of this state ourselves. // gbMainSessionLocked = FALSE; } if (IsFastUserSwitchingEnabled()) { // // When Fast User Switching is enabled, unlocking the windowstation // is a return from the "Welcome" desktop, so we treat it as a // logon ... // // // If this is a logon to the "Console" session, signal the event that // indicates a Console user is currently logged on. // // NOTE: we check gActiveConsoleSessionId directly here, without // waiting on the corresponding event because this unlock may // happen during a Console session change for another session, // in which case we will hang here in the service control // handler, waiting for the event to be set - and not be able to // receive the service control that actually lets us set the // event!!! Synchronization is not so important here because we // are not using the session for anything, just comparing // against it. If a session change really is in progress, this // session can't be the Console session anyways. // // Also, since Fast User Switching is enabled, we can just // compare against the active Console session id, and not bother // with the session 0 thing. // if (SessionNotification->dwSessionId == gActiveConsoleSessionId) { if (InstallEvents[LOGGED_ON_EVENT]) { SetEvent(InstallEvents[LOGGED_ON_EVENT]); KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT | DBGF_INSTALL, "UMPNPMGR: WTS_SESSION_UNLOCK with FUS: " "SetEvent LOGGED_ON_EVENT\n")); } } } else { // // When Fast User Switching is not enabled, we don't do anything // special when the winstation is unlocked. // // No-FUS, no-muss. NOTHING; } break; case WTS_SESSION_LOGON: // // NTRAID #181685-2000/09/11-jamesca: // // Currently, terminal services sends notification of logons to // "remote" sessions before the server's process creation thread // is running. If we set the logged on event, and there are // devices waiting to be installed, we will immediately call // CreateProcessAsUser on that session, which will fail. As a // (temporary?) workaround, we'll continue to use PNP_ReportLogOn // to receive logon notification from userinit.exe, now for all // sessions. // break; case WTS_SESSION_LOCK: // // The interactive windowstation on the specified session was locked. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT | DBGF_INSTALL, "UMPNPMGR: WTS_SESSION_LOCK: " "SessionId %d\n", SessionNotification->dwSessionId)); if (SessionNotification->dwSessionId == MAIN_SESSION) { // // For the main session, Terminal Services may or may not be // available, so we keep track of this state ourselves. // gbMainSessionLocked = TRUE; } if (IsFastUserSwitchingEnabled()) { // // When Fast User Switching is enabled, locking the windowstation // displays the "Welcome" desktop, potentially allowing a different // user to logon, so we treat it as a logoff ... // // // If this is a "logoff" from the "Console" session, reset the event // that indicates a Console user is currently logged on. // // // NOTE: we check gActiveConsoleSessionId directly here, without // waiting on the corresponding event because this lock may // happen during a Console session change for another session, // in which case we will hang here in the service control // handler, waiting for the event to be set - and not be able to // receive the service control that actually lets us set the // event!!! Synchronization is not so important here because we // are not using the session for anything, just comparing // against it. If a session change really is in progress, this // session can't be the Console session anyways. // // Also, since Fast User Switching is enabled, we can just // compare against the active Console session id, and not bother // with the session 0 thing. // if (SessionNotification->dwSessionId == gActiveConsoleSessionId) { if (InstallEvents[LOGGED_ON_EVENT]) { ResetEvent(InstallEvents[LOGGED_ON_EVENT]); KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT | DBGF_INSTALL, "UMPNPMGR: WTS_SESSION_LOCK with FUS: " "ResetEvent LOGGED_ON_EVENT\n")); } } } else { // // When Fast User Switching is not enabled, we don't do anything // special when the winstation is locked. // // No-FUS, no-muss. NOTHING; } break; case WTS_SESSION_LOGOFF: // // A user logged off from the specified session. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT | DBGF_INSTALL, "UMPNPMGR: WTS_SESSION_LOGOFF: " "SessionId %d\n", SessionNotification->dwSessionId)); if (((ULONG)SessionNotification->dwSessionId != MAIN_SESSION) && ((ULONG)SessionNotification->dwSessionId == gActiveConsoleSessionId)) { // // If the logoff occurred on the Console session (but not // session 0), reset the "logged on" event. // Session 0 logoffs are still handled by PnpConsoleCtrlHandler. // if (InstallEvents[LOGGED_ON_EVENT]) { ResetEvent(InstallEvents[LOGGED_ON_EVENT]); KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT | DBGF_INSTALL, "UMPNPMGR: WTS_SESSION_LOGOFF: " "ResetEvent LOGGED_ON_EVENT\n", SessionNotification->dwSessionId)); } // // If we currently have a device install UI client on this session, // we should attempt to close it now, before logging off. // LockNotifyList(&InstallClientList.Lock); pDeviceInstallClient = LocateDeviceInstallClient((ULONG)SessionNotification->dwSessionId); if (pDeviceInstallClient) { DereferenceDeviceInstallClient(pDeviceInstallClient); } UnlockNotifyList(&InstallClientList.Lock); } break; default: // // Unrecognized session change notification event. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_EVENT | DBGF_INSTALL | DBGF_ERRORS, "UMPNPMGR: Unknown SERVICE_CONTROL_SESSIONCHANGE event type (%d) " "received for SessionId %d!!\n", EventType, SessionNotification->dwSessionId)); break; } return NO_ERROR; } // SessionNotificationHandler BOOL IsUserLoggedOnSession( IN ULONG ulSessionId ) /*++ Routine Description: Checks to see if a user is logged on to the specified session. Arguments: ulSessionId - The session to be checked. Return Value: Returns TRUE if a user is currently logged on to the specified session, FALSE otherwise. --*/ { BOOL bResult = FALSE; LPWSTR pszUserName; DWORD dwSize; if (ulSessionId == MAIN_SESSION) { // // For the main session, Terminal Services may or may not be available, // so we just check if we currently have a handle to the user token. // ASSERT(gTokenLock.LockHandles); LockPrivateResource(&gTokenLock); if (ghUserToken != NULL) { bResult = TRUE; } UnlockPrivateResource(&gTokenLock); } else { // // If the specified session is not the main session, // query the session information to see if there is already a // user logged on. // if (fpWTSQuerySessionInformation && fpWTSFreeMemory) { pszUserName = NULL; dwSize = 0; if (fpWTSQuerySessionInformation((HANDLE)WTS_CURRENT_SERVER_HANDLE, (DWORD)ulSessionId, (WTS_INFO_CLASS)WTSUserName, (LPWSTR*)&pszUserName, &dwSize)) { if ((pszUserName != NULL) && (lstrlen(pszUserName) != 0)) { bResult = TRUE; } // // Free the supplied buffer // if (pszUserName) { fpWTSFreeMemory((PVOID)pszUserName); } } else { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_WARNINGS, "UMPNPMGR: WTSQuerySessionInformation failed for SessionId %d, " "error = %d\n", ulSessionId, GetLastError())); } } } return bResult; } // IsUserLoggedOnSession BOOL IsSessionConnected( IN ULONG ulSessionId ) /*++ Routine Description: Checks if the specified session is connected. Arguments: ulSessionId - The session to be checked. Return Value: Returns TRUE if the specified session is currently connected, FALSE otherwise. Notes: This routine assumes that the specified session is connected, unless we can poitively determine that it is not. i.e., if Terminal Services are not available, it is assumed that the specified session is connected. --*/ { BOOL bResult = TRUE; LPWSTR pBuffer; DWORD dwSize; // // Query the specified session. // if (fpWTSQuerySessionInformation && fpWTSFreeMemory) { pBuffer = NULL; dwSize = 0; if (fpWTSQuerySessionInformation((HANDLE)WTS_CURRENT_SERVER_HANDLE, (DWORD)ulSessionId, (WTS_INFO_CLASS)WTSConnectState, (LPWSTR*)&pBuffer, &dwSize)) { // // The session state must be either Active or Connected. // if ((pBuffer == NULL) || ((((INT)*pBuffer) != WTSActive) && (((INT)*pBuffer) != WTSConnected))) { // // The specified session is not currently connected. // bResult = FALSE; } // // Free the supplied buffer // if (pBuffer) { fpWTSFreeMemory((PVOID)pBuffer); } } } else { // // If the above TS entrypoints are not set, terminal services is not // enabled. This must be session 0, and it must be connected. // ASSERT(ulSessionId == MAIN_SESSION); } return bResult; } // IsSessionConnected BOOL IsSessionLocked( IN ULONG ulSessionId ) /*++ Routine Description: Checks to see if the interactive windowstation for the specified session is locked. Arguments: ulSessionId - The session to be checked. Return Value: Returns TRUE if the interactive windowstation for the specified session is locked, FALSE otherwise. --*/ { BOOL bLocked = FALSE; DWORD dwReturnLength; if (ulSessionId == MAIN_SESSION) { // // For the main session, Terminal Services may or may not be available, // so we just check our internal state variable. // bLocked = gbMainSessionLocked; } else { // // If the specified session is not the main session, query Terminal // Services for that session's WinStation information. // try { if (!fpWinStationQueryInformationW(SERVERNAME_CURRENT, ulSessionId, WinStationLockedState, (PVOID)&bLocked, sizeof(bLocked), &dwReturnLength)) { bLocked = FALSE; KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_WARNINGS, "UMPNPMGR: WinStationQueryInformation failed for SessionId %d, " "error = %d\n", ulSessionId, GetLastError())); } } except(EXCEPTION_EXECUTE_HANDLER) { bLocked = FALSE; } } return bLocked; } // IsSessionLocked BOOL IsConsoleSession( IN ULONG ulSessionId ) /*++ Routine Description: Checks to see if the specified session is the "Console" session. When Terminal Services Fast User Switching is enabled, this means that the session is the session connected to the physical display. When Fast User Switching is disabled, this means that the session is Session 0. Arguments: ulSessionId - The session to be checked. Return Value: Returns TRUE if the specified session should currently be considered the "Console" session. Notes: Note that this routine may potentially wait in GetActiveConsoleSessionId(), on the event we use to guard access to the active console session. Because of that, this routine should not be called in cases where it prevents a console connect or console disconnect from taking place, unless the event is known to be set appropriately. --*/ { BOOL bFusEnabled; bFusEnabled = IsFastUserSwitchingEnabled(); if ((!bFusEnabled && (ulSessionId == MAIN_SESSION)) || ( bFusEnabled && (ulSessionId == GetActiveConsoleSessionId()))) { return TRUE; } else { return FALSE; } } // IsConsoleSession ULONG GetActiveConsoleSessionId( VOID ) /*++ Routine Description: This routine returns the session id for the current active Console session. If a Console session switch event is in progress, it will wait until it is complete before returning. Arguments: None. Return Value: Session Id of the current active Console session. --*/ { ULONG ulConsoleSessionId; DWORD dwWait; ASSERT(ghActiveConsoleSessionEvent != NULL); // // If we have nothing to wait on, just return the current state. // if (ghActiveConsoleSessionEvent == NULL) { return gActiveConsoleSessionId; } ulConsoleSessionId = INVALID_SESSION; while (ulConsoleSessionId == INVALID_SESSION) { // // Wait on the console session event until we retrieve a valid Console // session id. // // We do this because a subtle race can occur when our service's control // handler processes a Console connect, which signals the console // session event and satisfies this wait, but then immediately processes // a subsequent Console disconnect, resetting the event, and // invalidating the active console session id -- BEFORE this // wait-satisfied thread is rescheduled to run. Once rescheduled, this // thread could end up reading an invalid value as the current active // Console session id. // // In that case however, the console session event would have been reset // already, so we can simply wait until it is signalled again, and // return the session id of the active Console session when the // succession of connect/disconnect requests that have been processed by // our service's control handler handler have been synchronized with // this waiting thread. // dwWait = WaitForSingleObject(ghActiveConsoleSessionEvent, INFINITE); ASSERT(dwWait == WAIT_OBJECT_0); ulConsoleSessionId = gActiveConsoleSessionId; } ASSERT(ulConsoleSessionId != INVALID_SESSION); return ulConsoleSessionId; } // GetActiveConsoleSessionId BOOL GetSessionUserToken( IN ULONG ulSessionId, OUT LPHANDLE lphUserToken ) /*++ Routine Description: This routine returns a handle to the user access token for the user at the Console session. Arguments: ulSession - Specifies the session for which the interactive user's token is to be retrieved. lphUserToken - Specifies the address to receive the handle to the user access token. Note that if this routine was successful, the caller is responsible for closing this handle. Return Value: Returns TRUE if successful, FALSE otherwise. --*/ { BOOL bResult = FALSE; HANDLE hImpersonationToken = INVALID_HANDLE_VALUE; RPC_STATUS rpcStatus; // // Verify that we were supplied a location to store the user token handle. // if (lphUserToken == NULL) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: NULL lphUserToken supplied to GetSessionUserToken!\n")); return FALSE; } if (ulSessionId == MAIN_SESSION) { // // A logon to session 0 can't be dependent on termsrv.exe, so we always // cache a handle to the user access token for that session during the // call to PNP_ReportLogon for session 0. If we currently have a handle // to the token, return it. // ASSERT(gTokenLock.LockHandles); LockPrivateResource(&gTokenLock); if (ghUserToken) { // // Duplicate the handle so that the caller can always safely close // it, no matter where it came from. // bResult = DuplicateHandle(GetCurrentProcess(), ghUserToken, GetCurrentProcess(), lphUserToken, 0, TRUE, DUPLICATE_SAME_ACCESS); if (!bResult) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: DuplicateHandle failed for ghUserToken for SessionId %d, error = %d\n", ulSessionId, GetLastError())); } } else { // // If we don't have a handle to a user access token for session 0, // there is probably not any user logged on to that session. // bResult = FALSE; } UnlockPrivateResource(&gTokenLock); } else { // // If the specified session is some session other than session 0, // Terminal Services must necessarily be available. Call // GetWinStationUserToken to retrieve a handle to the user access token // for this session. // bResult = GetWinStationUserToken(ulSessionId, &hImpersonationToken); if (bResult) { // // The token retrieved by GetWinStationUserToken is an impersonation // token. CreateProcessAsUser requires a primary token, so we must // duplicate the impersonation token to get one. Create a primary // token with the same access rights as the original token. // bResult = DuplicateTokenEx(hImpersonationToken, 0, NULL, SecurityImpersonation, TokenPrimary, lphUserToken); // // Close the handle to the impersonation token. // CloseHandle(hImpersonationToken); if (!bResult) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: DuplicateTokenEx failed, error = %d\n", GetLastError())); } } else { // // Find out what the problem was. // rpcStatus = GetLastError(); if (rpcStatus == RPC_S_INVALID_BINDING) { // // This is some error related to the service not being // available. Since we only call this for sessions other than // the main session, termsrv should definitely be available. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: GetWinStationUserToken returned error = %d for SessionId %d!!\n", rpcStatus, ulSessionId)); ASSERT(FALSE); } else { // // Some other error, the service may never be avaiable so bail // out now. // KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_WARNINGS, "UMPNPMGR: GetWinStationUserToken failed for SessionId %d, error = %d\n", ulSessionId, rpcStatus)); } } } // // If successful, we should always be returning a valid handle. // ASSERT(!bResult || ((*lphUserToken != INVALID_HANDLE_VALUE) && (*lphUserToken != NULL))); return bResult; } // GetSessionUserToken DWORD CreateUserSynchEvent( IN HANDLE hUserToken, IN LPCWSTR lpName, OUT HANDLE *phEvent ) /*++ Routine Description: This routine creates an event that the specified user can synchronize with. This is used so that we can communicate with NewDev and HotPlug processes running in the user's context. Arguments: hUserToken - Specifies a handle to the user access token for whom the event will be created. lpName - Name of event to create. phEvent - Supplies the address of a variable that will receive a handle to the event. Return Value: If successful, the return value is NO_ERROR. If failure, the return value is a Win32 error code indicating the cause of failure. --*/ { DWORD Err = ERROR_SUCCESS; PSID pUserSid = NULL; PACL pDacl = NULL; ULONG ulAclSize; SECURITY_DESCRIPTOR sd; SECURITY_ATTRIBUTES sa; // // Retrieve the User SID // pUserSid = GetUserSid(hUserToken); if (pUserSid == NULL) { Err = GetLastError(); goto Clean0; } ASSERT(IsValidSid(pUserSid)); // // Use the LocalSystem SID provided in the SCM global data. // ASSERT(PnPGlobalData != NULL); ASSERT(IsValidSid(PnPGlobalData->LocalSystemSid)); // // Determine the size required for the DACL // ulAclSize = sizeof(ACL); ulAclSize += sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(pUserSid) - sizeof(DWORD); ulAclSize += sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(PnPGlobalData->LocalSystemSid) - sizeof(DWORD); // // Allocate and initialize the DACL // pDacl = (PACL)HeapAlloc( ghPnPHeap, 0, ulAclSize); if (pDacl == NULL) { Err = ERROR_NOT_ENOUGH_MEMORY; goto Clean0; } if (!InitializeAcl(pDacl, ulAclSize, ACL_REVISION)) { Err = GetLastError(); goto Clean0; } // // Add an ACE to the DACL for LocalSystem EVENT_ALL_ACCESS // if (!AddAccessAllowedAceEx( pDacl, ACL_REVISION, 0, EVENT_ALL_ACCESS, PnPGlobalData->LocalSystemSid)) { Err = GetLastError(); goto Clean0; } // // Add an ACE to the DACL for User EVENT_QUERY_STATE, EVENT_MODIFY_STATE, and SYNCHRONIZE // if (!AddAccessAllowedAceEx( pDacl, ACL_REVISION, 0, EVENT_QUERY_STATE | EVENT_MODIFY_STATE | SYNCHRONIZE, pUserSid)) { Err = GetLastError(); goto Clean0; } ASSERT(IsValidAcl(pDacl)); // // Allocate and initialize the security descriptor // if (!InitializeSecurityDescriptor( &sd, SECURITY_DESCRIPTOR_REVISION)) { Err = GetLastError(); goto Clean0; } // // Set the new DACL in the security descriptor // if (!SetSecurityDescriptorDacl( &sd, TRUE, pDacl, FALSE)) { Err = GetLastError(); goto Clean0; } ASSERT(IsValidSecurityDescriptor(&sd)); // // Add the security descriptor to the security attributes // sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = &sd; sa.bInheritHandle = FALSE; // // Create the manual-reset event with a nonsignaled initial state. // *phEvent = CreateEvent(&sa, TRUE, FALSE, lpName); if (*phEvent == NULL) { Err = GetLastError(); goto Clean0; } // // Check that the named event did not already exist. // ASSERT(GetLastError() != ERROR_ALREADY_EXISTS); if (GetLastError() == ERROR_ALREADY_EXISTS) { Err = ERROR_ALREADY_EXISTS; CloseHandle(*phEvent); *phEvent = NULL; goto Clean0; } Clean0: // // Cleanup. // if (pUserSid != NULL) { HeapFree(ghPnPHeap, 0, pUserSid); } if (pDacl != NULL) { HeapFree(ghPnPHeap, 0, pDacl); } return Err; } // CreateUserSynchEvent BOOL CreateNoPendingInstallEvent( VOID ) /*++ Routine Description: This routine creates the "PnP_No_Pending_Install_Events" global named event, which is set and reset by the UMPNPMGR ThreadProc_DeviceInstall server-side device install thread, and waited on by the CMP_WaitNoPendingInstalls CFGMGR32 API, which allows clients to synchronize with the event directly, to determine when PNP is done actively installing any devices. Arguments: None. Return Value: Returns TRUE if successful, FALSE otherwise. --*/ { DWORD Err = NO_ERROR; PACL pDacl = NULL; ULONG ulAclSize; SECURITY_DESCRIPTOR sd; SECURITY_ATTRIBUTES sa; // // Use the SIDs provided in the SCM global data. This routine is called // from our initialization thread, which is created during our service start // routine, so the SCM provided global data is available to us by now. // ASSERT(PnPGlobalData != NULL); ASSERT(IsValidSid(PnPGlobalData->LocalSystemSid)); ASSERT(IsValidSid(PnPGlobalData->AliasAdminsSid)); ASSERT(IsValidSid(PnPGlobalData->AliasUsersSid)); // // Determine the size required for the DACL // ulAclSize = sizeof(ACL); ulAclSize += sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(PnPGlobalData->LocalSystemSid) - sizeof(DWORD); ulAclSize += sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(PnPGlobalData->AliasAdminsSid) - sizeof(DWORD); ulAclSize += sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(PnPGlobalData->AliasUsersSid) - sizeof(DWORD); // // Allocate and initialize the DACL // pDacl = (PACL)HeapAlloc( ghPnPHeap, 0, ulAclSize); if (pDacl == NULL) { Err = ERROR_NOT_ENOUGH_MEMORY; goto Clean0; } if (!InitializeAcl(pDacl, ulAclSize, ACL_REVISION)) { Err = GetLastError(); goto Clean0; } // // Add an ACE to the DACL for LocalSystem EVENT_ALL_ACCESS // if (!AddAccessAllowedAceEx( pDacl, ACL_REVISION, 0, EVENT_ALL_ACCESS, PnPGlobalData->LocalSystemSid)) { Err = GetLastError(); goto Clean0; } // // Add an ACE to the DACL for Administrators EVENT_QUERY_STATE and SYNCHRONIZE // if (!AddAccessAllowedAceEx( pDacl, ACL_REVISION, 0, EVENT_QUERY_STATE | SYNCHRONIZE, PnPGlobalData->AliasAdminsSid)) { Err = GetLastError(); goto Clean0; } // // Add an ACE to the DACL for Users EVENT_QUERY_STATE and SYNCHRONIZE // if (!AddAccessAllowedAceEx( pDacl, ACL_REVISION, 0, EVENT_QUERY_STATE | SYNCHRONIZE, PnPGlobalData->AliasUsersSid)) { Err = GetLastError(); goto Clean0; } ASSERT(IsValidAcl(pDacl)); // // Allocate and initialize the security descriptor // if (!InitializeSecurityDescriptor( &sd, SECURITY_DESCRIPTOR_REVISION)) { Err = GetLastError(); goto Clean0; } // // Set the new DACL in the security descriptor // if (!SetSecurityDescriptorDacl( &sd, TRUE, pDacl, FALSE)) { Err = GetLastError(); goto Clean0; } ASSERT(IsValidSecurityDescriptor(&sd)); // // Add the security descriptor to the security attributes // sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = &sd; sa.bInheritHandle = FALSE; // // Create the manual-reset event with a nonsignaled initial state. // ghNoPendingInstalls = CreateEvent(&sa, TRUE, FALSE, PNP_NO_INSTALL_EVENTS); if (ghNoPendingInstalls == NULL) { Err = GetLastError(); goto Clean0; } // // Check that the named event did not already exist. // ASSERT(GetLastError() != ERROR_ALREADY_EXISTS); if (GetLastError() == ERROR_ALREADY_EXISTS) { Err = ERROR_ALREADY_EXISTS; CloseHandle(ghNoPendingInstalls); ghNoPendingInstalls = NULL; goto Clean0; } Clean0: // // Cleanup. // if (pDacl != NULL) { HeapFree(ghPnPHeap, 0, pDacl); } SetLastError(Err); return(Err == NO_ERROR); } // CreateNoPendingInstallEvent DWORD CreateUserReadNamedPipe( IN HANDLE hUserToken, IN LPCWSTR lpName, IN ULONG ulSize, OUT HANDLE *phPipe ) /*++ Routine Description: This routine creates a named pipe that the specified user can read from. This is used so that we can communicate with NewDev and HotPlug processes running in the user's context. Arguments: hUserToken - Specifies a handle to the user access token for whom the named pipe will be created. lpName - Name of pipe to create. ulSize - Specifies the size of the output buffer for the named pipe. phPipe - Supplies the address of a variable that will receive a handle to the pipe. Return Value: If successful, the return value is NO_ERROR. If failure, the return value is a Win32 error code indicating the cause of failure. --*/ { DWORD Err = ERROR_SUCCESS; PSID pUserSid = NULL; PACL pDacl = NULL; ULONG ulAclSize; SECURITY_DESCRIPTOR sd; SECURITY_ATTRIBUTES sa; // // Retrieve the User SID // pUserSid = GetUserSid(hUserToken); if (pUserSid == NULL) { Err = GetLastError(); goto Clean0; } ASSERT(IsValidSid(pUserSid)); // // Use the LocalSystem SID provided in the SCM global data. // ASSERT(PnPGlobalData != NULL); ASSERT(IsValidSid(PnPGlobalData->LocalSystemSid)); // // Determine the size required for the DACL // ulAclSize = sizeof(ACL); ulAclSize += sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(pUserSid) - sizeof(DWORD); ulAclSize += sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(PnPGlobalData->LocalSystemSid) - sizeof(DWORD); // // Allocate and initialize the DACL // pDacl = (PACL)HeapAlloc( ghPnPHeap, 0, ulAclSize); if (pDacl == NULL) { Err = ERROR_NOT_ENOUGH_MEMORY; goto Clean0; } if (!InitializeAcl(pDacl, ulAclSize, ACL_REVISION)) { Err = GetLastError(); goto Clean0; } // // Add an ACE to the DACL for LocalSystem FILE_ALL_ACCESS // if (!AddAccessAllowedAceEx( pDacl, ACL_REVISION, 0, FILE_ALL_ACCESS, PnPGlobalData->LocalSystemSid)) { Err = GetLastError(); goto Clean0; } // // Add an ACE to the DACL for User FILE_GENERIC_READ // if (!AddAccessAllowedAceEx( pDacl, ACL_REVISION, 0, FILE_GENERIC_READ, pUserSid)) { Err = GetLastError(); goto Clean0; } ASSERT(IsValidAcl(pDacl)); // // Allocate and initialize the security descriptor // if (!InitializeSecurityDescriptor( &sd, SECURITY_DESCRIPTOR_REVISION)) { Err = GetLastError(); goto Clean0; } // // Set the new DACL in the security descriptor // if (!SetSecurityDescriptorDacl( &sd, TRUE, pDacl, FALSE)) { Err = GetLastError(); goto Clean0; } ASSERT(IsValidSecurityDescriptor(&sd)); // // Add the security descriptor to the security attributes // sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = &sd; sa.bInheritHandle = FALSE; // // Create the named pipe. // *phPipe = CreateNamedPipe( lpName, PIPE_ACCESS_OUTBOUND | // outbound data only FILE_FLAG_OVERLAPPED | // use overlapped structure FILE_FLAG_FIRST_PIPE_INSTANCE, // make sure we are the creator of the pipe PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, 1, // only one instance is allowed, and we are its creator ulSize, // out buffer size 0, // in buffer size PNP_PIPE_TIMEOUT, // default timeout &sa); // security attributes if (*phPipe == INVALID_HANDLE_VALUE) { Err = GetLastError(); *phPipe = NULL; goto Clean0; } // // Check that the named pipe did not already exist. // ASSERT(GetLastError() != ERROR_ALREADY_EXISTS); if (GetLastError() == ERROR_ALREADY_EXISTS) { Err = ERROR_ALREADY_EXISTS; CloseHandle(*phPipe); *phPipe = NULL; goto Clean0; } Clean0: // // Cleanup. // if (pUserSid != NULL) { HeapFree(ghPnPHeap, 0, pUserSid); } if (pDacl != NULL) { HeapFree(ghPnPHeap, 0, pDacl); } return Err; } // CreateUserReadNamedPipe VOID LogSurpriseRemovalEvent( IN LPWSTR MultiSzList ) /*++ Routine Description: One or more non-SurpriseRemovalOK devices were removed without prior warning. Record the removals in the event log. Arguments: MultiSz list of device instance paths. Return Value: None. --*/ { LPWSTR instancePath, friendlyName; CONFIGRET configRet; ULONG ulRegDataType, ulRemovalPolicy, ulVerifierFlags, ulTransferLen, ulLength; HKEY hMmKey = NULL; LONG lResult; for(instancePath = MultiSzList; ((*instancePath) != UNICODE_NULL); instancePath += lstrlen(instancePath) + 1) { ulTransferLen = ulLength = sizeof(ULONG); configRet = PNP_GetDeviceRegProp( NULL, instancePath, CM_DRP_REMOVAL_POLICY, &ulRegDataType, (LPBYTE) &ulRemovalPolicy, &ulTransferLen, &ulLength, 0 ); if (configRet != CR_SUCCESS) { continue; } if (ulRemovalPolicy == CM_REMOVAL_POLICY_EXPECT_SURPRISE_REMOVAL) { // // For devices which we expect surprise removal, we look to see if // the verifier is enabled. // lResult = RegOpenKeyEx( HKEY_LOCAL_MACHINE, RegMemoryManagementKeyName, 0, KEY_QUERY_VALUE, &hMmKey ); if ( lResult == ERROR_SUCCESS ) { ulLength = sizeof(ULONG); lResult = RegQueryValueEx( hMmKey, RegVerifyDriverLevelValueName, 0, &ulRegDataType, (LPBYTE) &ulVerifierFlags, &ulLength ); RegCloseKey(hMmKey); // // ADRIAO ISSUE 2001/02/14 - // We don't yet have a BIOS verification flag yet, so even // though the verifier may be targetted at a specific driver // for a WHQL test, we will log an event log here. // if ((lResult != ERROR_SUCCESS) || (!(ulVerifierFlags & DRIVER_VERIFIER_ENHANCED_IO_CHECKING))) { continue; } } } friendlyName = BuildFriendlyName(instancePath); if (friendlyName) { LogErrorEvent( ERR_SURPRISE_REMOVAL_2, 0, 2, friendlyName, instancePath ); HeapFree(ghPnPHeap, 0, friendlyName); } else { LogErrorEvent( ERR_SURPRISE_REMOVAL_1, 0, 1, instancePath ); } } } PWCHAR BuildFriendlyName( IN LPWSTR InstancePath ) { PWCHAR friendlyName; CONFIGRET configRet; ULONG ulLength, ulTransferLen; WCHAR szBuffer[MAX_PATH]; ULONG ulRegDataType; GUID classGuid; handle_t hBinding; hBinding = NULL; // // Try the registry for FRIENDLYNAME // ulLength = ulTransferLen = sizeof(szBuffer); configRet = PNP_GetDeviceRegProp( hBinding, InstancePath, CM_DRP_FRIENDLYNAME, &ulRegDataType, (LPBYTE) szBuffer, &ulTransferLen, &ulLength, 0 ); if (configRet != CR_SUCCESS || !*szBuffer) { // // Try the registry for DEVICEDESC // ulLength = ulTransferLen = sizeof(szBuffer); configRet = PNP_GetDeviceRegProp( hBinding, InstancePath, CM_DRP_DEVICEDESC, &ulRegDataType, (LPBYTE) szBuffer, &ulTransferLen, &ulLength, 0 ); if (configRet != CR_SUCCESS || !*szBuffer) { // // Initialize ClassGuid to GUID_NULL // CopyMemory(&classGuid, &GUID_NULL, sizeof(GUID)); // // Try the registry for CLASSNAME // ulLength = ulTransferLen = sizeof(szBuffer); configRet = PNP_GetDeviceRegProp( hBinding, InstancePath, CM_DRP_CLASSGUID, &ulRegDataType, (LPBYTE) szBuffer, &ulTransferLen, &ulLength, 0 ); if (configRet == CR_SUCCESS) { GuidFromString(szBuffer, &classGuid); } if (!GuidEqual(&classGuid, &GUID_NULL) && !GuidEqual(&classGuid, &GUID_DEVCLASS_UNKNOWN)) { ulLength = ulTransferLen = sizeof(szBuffer); configRet = PNP_GetDeviceRegProp( hBinding, InstancePath, CM_DRP_CLASS, &ulRegDataType, (LPBYTE) szBuffer, &ulTransferLen, &ulLength, 0 ); } else { configRet = CR_NO_SUCH_VALUE; } } } if (configRet == CR_SUCCESS && *szBuffer) { friendlyName = HeapAlloc(ghPnPHeap, HEAP_ZERO_MEMORY, ulLength); if (friendlyName) { memcpy(friendlyName, szBuffer, ulLength); } } else { friendlyName = NULL; } return friendlyName; } ENUM_ACTION QueueInstallationCallback( IN LPCWSTR DevInst, IN OUT PVOID Context ) /*++ Routine Description: This routine is called back for each devnode in a given subtree. It places each device node in that subtree into the installation queue so that it'll be reinstalled *if* appropriate (the installation side code checked the state of the devnode.) Arguments: DevInst InstancePath of current devnode. Context A pointer to QI_CONTEXT data (needed to handle the single-level enum case.) Return Value: ENUM_ACTION (Either EA_CONTINUE, EA_SKIP_SUBTREE, or EA_STOP_ENUMERATION) --*/ { PQI_CONTEXT pqiContext; PPNP_INSTALL_ENTRY entry, current; CONFIGRET status; BOOL needsReinstall; HRESULT hr; pqiContext = (PQI_CONTEXT)Context; status = DevInstNeedsInstall(DevInst, FALSE, &needsReinstall); if (status != CR_SUCCESS) { // // The devnode disappeared out from under us. Skip it's subtree. // return EA_SKIP_SUBTREE; } if (needsReinstall) { // // This devnode needs installation. Allocate and initialize a new // device install entry block. // entry = (PPNP_INSTALL_ENTRY) HeapAlloc( ghPnPHeap, 0, sizeof(PNP_INSTALL_ENTRY)); if (entry == NULL) { pqiContext->Status = CR_OUT_OF_MEMORY; return EA_STOP_ENUMERATION; } hr = StringCchCopy(entry->szDeviceId, MAX_DEVICE_ID_LEN, DevInst); ASSERT(SUCCEEDED(hr)); entry->Next = NULL; entry->Flags = 0; // // Insert this entry in the device install list. // LockNotifyList(&InstallList.Lock); current = (PPNP_INSTALL_ENTRY)InstallList.Next; if (current == NULL) { InstallList.Next = entry; } else { while ((PPNP_INSTALL_ENTRY)current->Next != NULL) { current = (PPNP_INSTALL_ENTRY)current->Next; } current->Next = entry; } UnlockNotifyList(&InstallList.Lock); SetEvent(InstallEvents[NEEDS_INSTALL_EVENT]); // // You might think we could skip the children if a parent is going to // be reinstalled. However, setupapi might decide not to tear down the // stack. // } // // If this is a single-level enumeration, we only want to touch the parent // and his immediate children. // if (pqiContext->HeadNodeSeen && pqiContext->SingleLevelEnumOnly) { return EA_SKIP_SUBTREE; } pqiContext->HeadNodeSeen = TRUE; return EA_CONTINUE; } // QueueInstallationCallback CONFIGRET DevInstNeedsInstall( IN LPCWSTR DevInst, IN BOOL CheckReinstallConfigFlag, OUT BOOL *NeedsInstall ) /*++ Routine Description: This routine determines whether a particular DevInst needs to be passed off to Setupapi for installation. Arguments: DevInst - InstancePath of devnode to check. CheckReinstallConfigFlag - Specifies if the CONFIGFLAG_REINSTALL ConfigFlag should explicitly also be checked. NeedsInstall - Recieves TRUE if the devnode is present and needs to be installed, FALSE otherwise. Return Value: CONFIGRET (if the devnode isn't present, this will be CR_NO_SUCH_DEVINST.) --*/ { CONFIGRET status; ULONG ulStatus, ulProblem, ulConfig; // // Preinit // *NeedsInstall = FALSE; // // Is the device present? // status = GetDeviceStatus(DevInst, &ulStatus, &ulProblem); if (status == CR_SUCCESS) { // // Implementation note: In kernel-mode when we first process this // device instance, if there is no ConfigFlag value present, then we // set a problem of CM_PROB_NOT_CONFIGURED (this would always happen // for brand new device instances). If there is already a ConfigFlag // value of CONFIGFLAG_REINSTALL, then we set a problem of // CM_PROB_REINSTALL. Either problem will trigger an installation of // this device, the only difference is in how SetupDi routines handle // a failed installation: If ConfigFlag is CONFIGFLAG_NOT_CONFIGURED, // then a failed install will leave the ConfigFlag alone and set a // problem of CM_PROB_FAILED_INSTALL. If there is no ConfigFlag, then // ConfigFlag will be set to CONFIGFLAG_DISABLED. // if ((ulStatus & DN_HAS_PROBLEM) && ((ulProblem == CM_PROB_REINSTALL) || (ulProblem == CM_PROB_NOT_CONFIGURED))) { *NeedsInstall = TRUE; } ulConfig = GetDeviceConfigFlags(DevInst, NULL); // // In some cases, we explicitly need to also check for the // CONFIGFLAG_REINSTALL ConfigFlag, because the devnode may not yet have // the CM_PROB_REINSTALL problem code. // if ((CheckReinstallConfigFlag) && (ulConfig & CONFIGFLAG_REINSTALL)) { *NeedsInstall = TRUE; } // // Addendum to Implementation note: If there is no ConfigFlag present, // but the device has the RawDeviceOK capability - OR - a matching // Service is found for the device in the CriticalDeviceDatabase, then // the device is started, but marked by kernel-mode with the // CONFIGFLAG_FINISH_INSTALL, indicating that user-mode should complete // the installation. // if (ulConfig & CONFIGFLAG_FINISH_INSTALL) { *NeedsInstall = TRUE; if (gbPreservePreInstall) { // // If we are expected to preserve critical device database / // device pre-installation settings, check if this finish // install device indicates installation is complete. // HKEY hKeyDevInst; ULONG ulValue, ulSize; if (RegOpenKeyEx( ghEnumKey, DevInst, 0, KEY_READ | KEY_WRITE, &hKeyDevInst) == ERROR_SUCCESS) { ulValue = 0; ulSize = sizeof(ulValue); if (RegQueryValueEx( hKeyDevInst, pszRegValuePreservePreInstall, NULL, NULL, (LPBYTE)&ulValue, &ulSize) == ERROR_SUCCESS) { if (ulValue == 1) { // // Unset the finish-install config flag. // ulConfig &= ~CONFIGFLAG_FINISH_INSTALL; PNP_SetDeviceRegProp( NULL, DevInst, CM_DRP_CONFIGFLAGS, REG_DWORD, (LPBYTE)&ulConfig, sizeof(ulConfig), 0); // // Device does not need to be installed. // *NeedsInstall = FALSE; } // // Delete the PreservePreInstall value. // RegDeleteValue( hKeyDevInst, pszRegValuePreservePreInstall); } RegCloseKey(hKeyDevInst); } } } } else if (IsRootDeviceID(DevInst)) { status = CR_SUCCESS; } return status; } PWSTR BuildBlockedDriverList( IN OUT LPGUID GuidList, IN ULONG GuidCount ) /*++ Routine Description: This routine builds a multi-sz list of GUIDs, based on the array of GUIDs supplied. If no GUIDs were supplied, this routine returns a list of all drivers currently blocked by the system. Arguments: GuidList - Address of the array of blocked driver GUIDs to create the multi-sz list from. This argument may be NULL to retrieve a list of all drivers currently blocked by the system. GuidCount - Specifies the number of GUIDs in the array. If GuidList is NULL, this argument must be 0. Return Value: Returns a MultiSz list of blocked driver GUIDs, based on the supplied parameters. Returns NULL if no GUIDs were supplied, and no GUIDs are currently being blocked by the system. If a multi-sz list was returned, the caller is responsible for freeing the associated buffer. --*/ { CONFIGRET Status = STATUS_SUCCESS; ULONG ulLength, ulTemp; PBYTE Buffer = NULL; PWSTR MultiSzList = NULL, p; try { // // Validate parameters. // if (((!ARGUMENT_PRESENT(GuidList)) && (GuidCount != 0)) || ((ARGUMENT_PRESENT(GuidList)) && (GuidCount == 0))) { Status = CR_FAILURE; goto Clean0; } if (GuidCount == 0) { // // We were called without a list of GUIDs, so we need to get the // list ourselves. // ASSERT(!ARGUMENT_PRESENT(GuidList)); ulLength = 0; ulTemp = 0; Status = PNP_GetBlockedDriverInfo( NULL, NULL, &ulTemp, &ulLength, 0); // // If no drivers are currently being blocked, or we encountered some // other failure, we have nothing to display, so just return. // if ((Status != CR_BUFFER_SMALL) || (ulLength == 0)) { Status = CR_FAILURE; goto Clean0; } // // Allocate a buffer to retrieve the list of GUIDs. // Buffer = HeapAlloc(ghPnPHeap, 0, ulLength); if (Buffer == NULL) { Status = CR_FAILURE; goto Clean0; } // // Get the list of GUIDs for currently blocked drivers. // ulTemp = 0; Status = PNP_GetBlockedDriverInfo( NULL, Buffer, &ulTemp, &ulLength, 0); // // We thought there was a list when we checked before, so we better // have one now. // ASSERT(Status != CR_BUFFER_SMALL); ASSERT(ulLength != 0); ASSERT(ulTemp != 0); if (Status != CR_SUCCESS) { goto Clean0; } // // Use the list we just retrieved. Note that Buffer is non-NULL // when we allocate our own buffer for the array, so make sure we // free it below. // GuidCount = ulLength / sizeof(GUID); GuidList = (LPGUID)Buffer; } // // We must have a list of GUIDs to convert by this point. // ASSERT(GuidCount > 0); ASSERT(GuidList != NULL); // // Allocate a buffer to hold the multi-sz list of stringified GUIDs. // ulLength = (GuidCount*MAX_GUID_STRING_LEN + 1) * sizeof(WCHAR); MultiSzList = HeapAlloc(ghPnPHeap, 0, ulLength); if (MultiSzList == NULL) { Status = CR_FAILURE; goto Clean0; } ZeroMemory(MultiSzList, ulLength); // // Traverse the list of GUIDs, converting to strings as we go. // for (p = MultiSzList, ulTemp = 0; ulTemp < GuidCount; ulTemp++, p+= lstrlen(p) + 1) { if (StringFromGuid( (LPGUID)&(GuidList[ulTemp]), p, ((ulLength/sizeof(WCHAR)) - (ULONG)(p - MultiSzList))) != NO_ERROR) { Status = CR_FAILURE; goto Clean0; } } *p = L'\0'; // // Success!! // Status = CR_SUCCESS; Clean0: NOTHING; } except(EXCEPTION_EXECUTE_HANDLER) { KdPrintEx((DPFLTR_PNPMGR_ID, DBGF_ERRORS, "UMPNPMGR: Exception in BuildBlockedDriverList!\n")); ASSERT(0); Status = CR_FAILURE; // // Reference the following variables so the compiler will respect // statement ordering w.r.t. their assignment. // Buffer = Buffer; MultiSzList = MultiSzList; } // // Free the GUID list buffer, if we allocated one. // if (Buffer != NULL) { HeapFree(ghPnPHeap, 0, Buffer); } // // Don't return a list if we were unsuccessful. // if ((Status != CR_SUCCESS) && (MultiSzList != NULL)) { HeapFree(ghPnPHeap, 0, MultiSzList); MultiSzList = NULL; } return MultiSzList; } // BuildBlockedDriverList CONFIGRET PNP_GetServerSideDeviceInstallFlags( IN handle_t hBinding, PULONG pulSSDIFlags, ULONG ulFlags ) /*++ Routine Description: This is the RPC server entry point for the CMP_GetServerSideDeviceInstallFlags routine. Arguments: hBinding - RPC binding handle, not used. pulSSDIFlags - A ULONG pointer, supplied by the caller. This is used to pass back the following server side device install flags: SSDI_REBOOT_PENDING - A reboot is pending from a server side device install. ulFlags Not used, must be zero. Return Value: Return CR_SUCCESS if the function succeeds, otherwise it returns one of the CR_* errors. --*/ { CONFIGRET Status = CR_SUCCESS; UNREFERENCED_PARAMETER(hBinding); try { // // Validate parameters // if (!ARGUMENT_PRESENT(pulSSDIFlags)) { Status = CR_INVALID_POINTER; goto Clean0; } if (INVALID_FLAGS(ulFlags, 0)) { Status = CR_INVALID_FLAG; goto Clean0; } *pulSSDIFlags = 0; // // SSDI_REBOOT_PENDING // Determine if a server side device install reboot is pending. // if (gServerSideDeviceInstallRebootNeeded) { *pulSSDIFlags |= SSDI_REBOOT_PENDING; } Clean0: NOTHING; } except(EXCEPTION_EXECUTE_HANDLER) { Status = CR_FAILURE; } return Status; } // PNP_GetServerSideDeviceInstallFlags VOID SendInvalidIDNotifications( IN ULONG ulSessionId ) /*++ Routine Description: This routine scans the entire device tree looking for devices with DN_CHILD_WITH_INVALID_ID set. For all those, it sends notification to hotplug. There is a race here between this function and the notifications from kernel mode but that's probably ok (double notifications). Arguments: None. Return Value: None. --*/ { WCHAR szCurrentDevice[MAX_DEVICE_ID_LEN + 1], szNextDevice[MAX_DEVICE_ID_LEN + 1]; ULONG ulStatus, ulProblem; CONFIGRET cr; PLUGPLAY_CONTROL_RELATED_DEVICE_DATA controlData; NTSTATUS ntStatus; HRESULT hr; // // Start from the device tree root. // if (FAILED(StringCchCopyEx( szCurrentDevice, SIZECHARS(szCurrentDevice), pszRegRootEnumerator, NULL, NULL, STRSAFE_NULL_ON_FAILURE))) { return; } // // Walk the entire tree and send notification to show the balloon for every device with // DN_CHILD_WITH_INVALID_ID flag. // do { // // Check if this device has the DN_ bit set. // ulStatus = 0; cr = GetDeviceStatus(szCurrentDevice, &ulStatus, &ulProblem); if ((cr == CR_SUCCESS) && (ulStatus & DN_CHILD_WITH_INVALID_ID)) { // // terminate MULTI_SZ. // szCurrentDevice[wcslen(szCurrentDevice) + 1] = UNICODE_NULL; // // Notify the user via hotplug. // SendHotplugNotification((LPGUID)&GUID_DEVICE_INVALID_ID, NULL, szCurrentDevice, &ulSessionId, 0); } // // Get the child. // controlData.Relation = PNP_RELATION_CHILD; RtlInitUnicodeString(&controlData.TargetDeviceInstance, szCurrentDevice); controlData.RelatedDeviceInstance = szNextDevice; controlData.RelatedDeviceInstanceLength = SIZECHARS(szNextDevice) - 1; // MAX_DEVICE_ID_LEN ntStatus = NtPlugPlayControl(PlugPlayControlGetRelatedDevice, &controlData, sizeof(controlData)); if (NT_SUCCESS(ntStatus)) { if (FAILED(StringCchCopyEx( szCurrentDevice, SIZECHARS(szCurrentDevice), szNextDevice, NULL, NULL, STRSAFE_NULL_ON_FAILURE))) { // // Unable to copy the device id, stop the walk. // break; } // // Continue the walk. // continue; } if (ntStatus != STATUS_NO_SUCH_DEVICE) { // // We failed for some other reason, stop the walk. // break; } // // If no child, get the sibling. // while (!IsRootDeviceID(szCurrentDevice)) { controlData.Relation = PNP_GET_SIBLING_DEVICE_INSTANCE; RtlInitUnicodeString(&controlData.TargetDeviceInstance, szCurrentDevice); controlData.RelatedDeviceInstance = szNextDevice; controlData.RelatedDeviceInstanceLength = SIZECHARS(szNextDevice) - 1; // MAX_DEVICE_ID_LEN ntStatus = NtPlugPlayControl(PlugPlayControlGetRelatedDevice, &controlData, sizeof(controlData)); if (NT_SUCCESS(ntStatus)) { hr = StringCchCopyEx(szCurrentDevice, SIZECHARS(szCurrentDevice), szNextDevice, NULL, NULL, STRSAFE_NULL_ON_FAILURE); ASSERT(SUCCEEDED(hr)); break; } // // If no more siblings, go up the tree one level. // controlData.Relation = PNP_GET_PARENT_DEVICE_INSTANCE; RtlInitUnicodeString(&controlData.TargetDeviceInstance, szCurrentDevice); controlData.RelatedDeviceInstance = szNextDevice; controlData.RelatedDeviceInstanceLength = SIZECHARS(szNextDevice) - 1; // MAX_DEVICE_ID_LEN ntStatus = NtPlugPlayControl(PlugPlayControlGetRelatedDevice, &controlData, sizeof(controlData)); if (!NT_SUCCESS(ntStatus)) { // // No parent? Something went wrong or we completed our tree walk. // break; } hr = StringCchCopyEx(szCurrentDevice, SIZECHARS(szCurrentDevice), szNextDevice, NULL, NULL, STRSAFE_NULL_ON_FAILURE); ASSERT(SUCCEEDED(hr)); } } while (NT_SUCCESS(ntStatus) && (!IsRootDeviceID(szCurrentDevice))); return; } // SendInvalidIDNotifications