/*++ Copyright (c) 1991 Microsoft Corporation Module Name: status.c Abstract: This file contains functions that are involved with setting the status for a service in the service controller. RSetServiceStatus Removal Thread RI_ScSetServiceBitsA RI_ScSetServiceBitsW ScRemoveServiceBits ScInitServerAnnounceFcn Author: Dan Lafferty (danl) 20-Mar-1991 Environment: User Mode -Win32 Revision History: 11-Apr-1996 anirudhs RSetServiceStatus: Notify NDIS when a service that belongs to a group NDIS is interested in starts running. 21-Nov-1995 anirudhs RI_ScSetServiceBitsW: Catch access violations caused if the hServiceStatus parameter is invalid. 23-Mar-1994 danl RSetServiceStatus: Only set the PopupStartFail flag when we have actually logged an event. This means that now an auto-started service can quietly stop itself without reporting an exit code, and we will not log an event or put up a popup. However, we will still put up a popup if a service stops itself during auto-start, and it provides an exit code. 20-Oct-1993 danl RSetServiceStatus: Only update the status if the service process is still running. It is possible that the status could have been blocked when the process unexpectedly terminated, and updated the status to stopped. In this case, the status that was blocked contains out-of-date information. 10-Dec-1992 danl RI_ScSetServiceBitsW & ScRemoveServiceBits no longer hold locks when calling ScNetServerSetServiceBits. 03-Nov-1992 danl RSetServiceStatus: Remove code that sets ExitCode to ERROR_GEN_FAILURE when a service transitions directly from START_PENDING to STOPPED with out an exit code of its own. 25-Aug-1992 danl RSetServiceStatus: Allow dirty checkpoint and exitcode fields. Force them clean. 19-Jun-1991 danl Allow ExitCodes to be specified for the SERVICE_STOP_PENDING state. Prior to this they were only allowed for the SERVICE_STOP state. 20-Mar-1991 danl created --*/ // // INCLUDES // #include #include // DbgPrint prototype #include // DataTypes and runtime APIs #include // needed for winbase.h #include // WaitForSingleObject #include // MIDL Generated Header File #include // Unicode string macros #include // public Service Controller Interface. #include // SC_LOG #include "dataman.h" // LPIMAGE_RECORD #include "scopen.h" // Handle structures and signature definitions #include "valid.h" // ScCurrentStateInvalid #include "svcctrl.h" // ScRemoveServiceBits #include "depend.h" // ScNotifyChangeState #include "driver.h" // ScNotifyNdis #include // NET_API_STATUS #include // NERR_Success #include // contains service name #include // SV_TYPE_NT (server announcement bits) #include // I_NetServerSetServiceBits #include // SvcRemoveWorkItem // // GLOBALS // // // This is a special storage place for the OR'd server announcement // bit masks. NOTE: This is only read or written to when the // service database exclusive lock is held. // DWORD GlobalServerAnnounce = SV_TYPE_NT; // // The following ServerHandle is the handle returned from the // LoadLibrary call which loaded netapi.dll. The entrypoint for // I_NetServerSetServiceBits is then found and stored in the // global location described below. // HANDLE ScGlobalServerHandle; typedef DWORD (WINAPI *SETSBPROC)(); SETSBPROC ScNetServerSetServiceBits = NULL; // // Function Prototypes (local functions) // DWORD RemovalThread( IN LPSERVICE_RECORD ServiceRecord ); DWORD RSetServiceStatus( IN SERVICE_STATUS_HANDLE hServiceStatus, IN LPSERVICE_STATUS lpServiceStatus ) /*++ Routine Description: This function is called by services when they need to inform the service controller of a change in state. Arguments: hServiceStatus - A DWORD value that is actually a pointer to a service record in the database. Since this is not a handle of SC_HANDLE type, there is no signature to check for this handle. lpServiceStatus - A pointer to a SERVICE_STATUS structure. This reflects the latest status of the calling service. Return Value: ERROR_INVALID_HANDLE - The specified handle is invalid. ERROR_INVALID_SERVICE_STATUS - The specified service status is invalid. Note: --*/ { DWORD status = NO_ERROR; LPSERVICE_RECORD serviceRecord; DWORD threadId; HANDLE threadHandle; DWORD oldState; DWORD oldType; LPWSTR ScSubStrings[2]; WCHAR ScErrorCodeString[25]; SC_LOG(TRACE,"In RSetServiceStatus routine\n",0); if (hServiceStatus == (SERVICE_STATUS_HANDLE) NULL) { return(ERROR_INVALID_HANDLE); } try { // // Check the signature on the handle. // if (((LPSERVICE_RECORD)hServiceStatus)->Signature != SERVICE_SIGNATURE) { SC_LOG(TRACE,"RSetServiceStatus: hServiceStatus was invalid\n",0); status = ERROR_INVALID_HANDLE; } } except(EXCEPTION_EXECUTE_HANDLER) { status = ERROR_INVALID_HANDLE; } if (status != NO_ERROR) { return(status); } // // Validate the fields in the service status structure. // if (ScCurrentStateInvalid(lpServiceStatus->dwCurrentState)) { SC_LOG2(ERROR, "RSetServiceStatus: " FORMAT_LPWSTR " set invalid " " dwCurrentState x%08lx\n", ((LPSERVICE_RECORD) hServiceStatus)->DisplayName, lpServiceStatus->dwCurrentState); ScSubStrings[0] = ((LPSERVICE_RECORD) hServiceStatus)->DisplayName; ScSubStrings[1] = ultow( lpServiceStatus->dwCurrentState, ScErrorCodeString, 16 ); ScLogEvent( EVENT_BAD_SERVICE_STATE, 2, ScSubStrings ); return(ERROR_INVALID_DATA); } if( (SERVICE_STATUS_TYPE_INVALID(lpServiceStatus->dwServiceType)) || (CONTROLS_ACCEPTED_INVALID(lpServiceStatus->dwControlsAccepted)) ) { SC_LOG(ERROR,"RSetServiceStatus: Error in one of the following\n" " ServiceType x%08lx\n", lpServiceStatus->dwServiceType); SC_LOG(ERROR," ControlsAccepted x%08lx\n", lpServiceStatus->dwControlsAccepted); return(ERROR_INVALID_DATA); } // // If the service is not in the stopped or stop-pending state, then the // exit code fields should be 0. // if (((lpServiceStatus->dwCurrentState != SERVICE_STOPPED) && (lpServiceStatus->dwCurrentState != SERVICE_STOP_PENDING)) && ((lpServiceStatus->dwWin32ExitCode != 0) || (lpServiceStatus->dwServiceSpecificExitCode != 0)) ){ SC_LOG(TRACE,"RSetServiceStatus: ExitCode fields not cleaned up " "when state indicates SERVICE_STOPPED\n",0); lpServiceStatus->dwWin32ExitCode = 0; lpServiceStatus->dwServiceSpecificExitCode = 0; } // // If the service is not in a pending state, then the waitHint and // checkPoint fields should be 0. // if ( ( (lpServiceStatus->dwCurrentState == SERVICE_STOPPED) || (lpServiceStatus->dwCurrentState == SERVICE_RUNNING) || (lpServiceStatus->dwCurrentState == SERVICE_PAUSED) ) && ( (lpServiceStatus->dwCheckPoint != 0) || (lpServiceStatus->dwWaitHint != 0) ) ){ SC_LOG(TRACE,"RSetServiceStatus: Dirty Checkpoint and WaitHint fields\n",0); lpServiceStatus->dwCheckPoint = 0; lpServiceStatus->dwWaitHint = 0; } // // Update the service record. Exclusive locks are required for this. // // NOTICE that we don't destroy the ServiceType information that was // in the service record. // serviceRecord = (LPSERVICE_RECORD)hServiceStatus; SC_LOG(TRACE,"RSetServiceStatus: Status field accepted, service %ws\n", serviceRecord->ServiceName); ScDatabaseLock( SC_GET_EXCLUSIVE,"RSetServiceStatus1"); oldState = serviceRecord->ServiceStatus.dwCurrentState; oldType = serviceRecord->ServiceStatus.dwServiceType; // // It is possible that while we were blocked waiting for the lock, // that a running service could have terminated (Due to the process // terminating). So we need to look for "late" status updates, and // filter them out. If the ImageRecord pointer is NULL, then the // Service has Terminated. Otherwise update the status. // if (serviceRecord->ImageRecord != NULL) { // // Update to the new status // memcpy( &(serviceRecord->ServiceStatus), lpServiceStatus, sizeof(SERVICE_STATUS)); serviceRecord->ServiceStatus.dwServiceType = oldType; } // // For dependency handling // if ((serviceRecord->ServiceStatus.dwCurrentState == SERVICE_RUNNING || serviceRecord->ServiceStatus.dwCurrentState == SERVICE_STOPPED || serviceRecord->ServiceStatus.dwCurrentState == SERVICE_STOP_PENDING) && oldState == SERVICE_START_PENDING) { if (serviceRecord->ServiceStatus.dwCurrentState == SERVICE_STOPPED || serviceRecord->ServiceStatus.dwCurrentState == SERVICE_STOP_PENDING) { serviceRecord->StartState = SC_START_FAIL; SC_LOG(DEPEND, "%ws START_PENDING -> FAIL\n", serviceRecord->ServiceName); } else if (serviceRecord->ServiceStatus.dwCurrentState == SERVICE_RUNNING) { serviceRecord->StartState = SC_START_SUCCESS; SC_LOG(DEPEND, "%ws START_PENDING -> RUNNING\n", serviceRecord->ServiceName); #ifdef TIMING_TEST DbgPrint("[SC_TIMING] TickCount for RUNNING service \t%ws\t%d\n", serviceRecord->ServiceName, GetTickCount()); #endif // TIMING_TEST } // // Tell the dependency handling code that a start-pending // service is now running or stopped. // ScNotifyChangeState(); } // // If the new status indicates that the service has just started, // tell NDIS to issue the PNP notifications about this service's arrival, // if it belongs to one of the groups NDIS is interested in. // if ((lpServiceStatus->dwCurrentState == SERVICE_RUNNING) && (oldState != SERVICE_RUNNING)) { ScDatabaseLock(SC_MAKE_SHARED,"RSetServiceStatus1.5"); ScNotifyNdis(serviceRecord); } ScDatabaseLock(SC_RELEASE,"RSetServiceStatus2"); // // If the new status indicates that the service has just stopped, // we need to check to see if there are any other services running // in the service process. If not, then we can ask the service to // terminate. Another thread is spawned to handle this since we need // to return from this call in order to allow the service to complete // its shutdown. // if ((lpServiceStatus->dwCurrentState == SERVICE_STOPPED) && (oldState != SERVICE_STOPPED)) { if (lpServiceStatus->dwWin32ExitCode != NO_ERROR) { if (lpServiceStatus->dwWin32ExitCode != ERROR_SERVICE_SPECIFIC_ERROR) { ScSubStrings[0] = serviceRecord->DisplayName; wcscpy(ScErrorCodeString,L"%%"); ultow(lpServiceStatus->dwWin32ExitCode, ScErrorCodeString+2, 10); ScSubStrings[1] = ScErrorCodeString; ScLogEvent( EVENT_SERVICE_EXIT_FAILED, 2, ScSubStrings ); } else { ScSubStrings[0] = serviceRecord->DisplayName; ScSubStrings[1] = ultow( lpServiceStatus->dwServiceSpecificExitCode, ScErrorCodeString, 10 ); ScLogEvent( EVENT_SERVICE_EXIT_FAILED_SPECIFIC, 2, ScSubStrings ); } // // For popup after user has logged on to indicate that some service // started at boot has failed. // if (serviceRecord->ErrorControl == SERVICE_ERROR_NORMAL || serviceRecord->ErrorControl == SERVICE_ERROR_SEVERE || serviceRecord->ErrorControl == SERVICE_ERROR_CRITICAL) { ScPopupStartFail = TRUE; } } // // Clear the server announcement bits in the global location // for this service. // ScRemoveServiceBits(serviceRecord); // // BUGBUG: If possible, don't do anything with locks here. // It would be better if ScRemoveService would get // the exclusive lock itself - rather than expect the // shared lock and converting it to exclusive. // Check into places that call ScRemoveService. // ScDatabaseLock( SC_GET_SHARED,"RSetServiceStatus3"); // // If this is the last service in the process, then delete the // process handle from the ProcessWatcher list. // if ((serviceRecord->ImageRecord != NULL) && (serviceRecord->ImageRecord->ServiceCount == 1)) { if (SvcRemoveWorkItem(serviceRecord->ImageRecord->ObjectWaitHandle)) { serviceRecord->ImageRecord->ObjectWaitHandle = NULL; } } SC_LOG(TRACE, "RSetServiceStatus:Create a thread to run ScRemoveService\n",0); threadHandle = CreateThread ( NULL, // Thread Attributes. 0L, // Stack Size (LPTHREAD_START_ROUTINE)RemovalThread, // lpStartAddress (LPVOID)serviceRecord, // lpParameter 0L, // Creation Flags &threadId); // lpThreadId if (threadHandle == (HANDLE) NULL) { SC_LOG(ERROR,"RSetServiceStatus:CreateThread failed %d\n", GetLastError()); // // If a thread couldn't be created to remove the service, // It is removed in the context of this thread. The // result of this is a somewhat dirty termination. The // service record will be removed from the installed database. // If this was the last service in the process, the process will // terminate before we return to the thread. // status = ScRemoveService(serviceRecord); ScDatabaseLock( SC_RELEASE,"RSetServiceStatus4"); } else { // // The Thread Creation was successful. Allow that thread // to free the lock. // SC_LOG(TRACE,"Thread Creation Success, thread id = %#lx\n",threadId); CloseHandle(threadHandle); } } SC_LOG(TRACE,"Return from RSetServiceStatus\n",0); return(status); } DWORD RemovalThread( IN LPSERVICE_RECORD ServiceRecord ) /*++ Routine Description: This thread is used by NetrServiceStatus to remove a service from the Service Controller's database, and also - if necessary - shut down the service process. The later step is only done if this was the last service running in that process. The use of this thread allows NetServiceStatus to return to the service so that the service can then continue to terminate itself. QUESTION: Am I going to have synchronization problems with the database by passing in a pointer to the ServiceRecord? How should I work locks on the database? Arguments: ServiceRecord - This is a pointer to the service record that is being removed. Return Value: Same as return values for RemoveService(). Note: --*/ { // // RemoveService assumes that the shared lock is already obtained. // ScRemoveService (ServiceRecord); ScDatabaseLock( SC_RELEASE, "RemovalThread1"); ExitThread(0); return(0); } DWORD RI_ScSetServiceBitsA( IN SERVICE_STATUS_HANDLE hServiceStatus, IN DWORD dwServiceBits, IN DWORD bSetBitsOn, IN DWORD bUpdateImmediately, IN LPSTR pszReserved ) /*++ Routine Description: This function Or's the Service Bits that are passed in - into a global bitmask maintained by the service controller. Everytime this function is called, we check to see if the server service is running. If it is, then we call an internal entry point in the server service to pass in the complete bitmask. This function also Or's the Service Bits into the ServerAnnounce element in the service's ServiceRecord. NOTE: The exclusive database lock is obtained and held while the service record is being read, and while the GlobalServerAnnounce bits are set. Arguments: Return Value: NO_ERROR - The operation was completely successful. The information may or may not be delivered to the Server depending on if it is running or not. or any error returned from the server service I_NetServerSetServiceBits function. Note: --*/ { DWORD status = NO_ERROR; if (ScShutdownInProgress) { return(ERROR_SHUTDOWN_IN_PROGRESS); } if (pszReserved != NULL) { return (ERROR_INVALID_PARAMETER); } return RI_ScSetServiceBitsW(hServiceStatus, dwServiceBits, bSetBitsOn, bUpdateImmediately, NULL); } DWORD RI_ScSetServiceBitsW( IN SERVICE_STATUS_HANDLE hServiceStatus, IN DWORD dwServiceBits, IN DWORD bSetBitsOn, IN DWORD bUpdateImmediately, IN LPWSTR pszReserved ) /*++ Routine Description: This function Or's the Service Bits that are passed in - into a global bitmask maintained by the service controller. Everytime this function is called, we check to see if the server service is running. If it is, then we call an internal entry point in the server service to pass in the complete bitmask. This function also Or's the Service Bits into the ServerAnnounce element in the service's ServiceRecord. NOTE: The exclusive database lock is obtained and held while the service record is being read, and while the GlobalServerAnnounce bits are set. Arguments: Return Value: NO_ERROR - The operation was completely successful. ERROR_GEN_FAILURE - The server service is there, but the call to update it failed. Note: --*/ { DWORD status = NO_ERROR; LPSERVICE_RECORD serviceRecord; LPWSTR serverServiceName; DWORD serviceState; if (ScShutdownInProgress) { return(ERROR_SHUTDOWN_IN_PROGRESS); } if (pszReserved != NULL) { return (ERROR_INVALID_PARAMETER); } if (ScNetServerSetServiceBits == (SETSBPROC)NULL) { if (! ScInitServerAnnounceFcn()) { return(ERROR_NO_NETWORK); } } serverServiceName = SERVICE_SERVER; try { // // Check the signature on the handle. // if (((LPSERVICE_RECORD)hServiceStatus)->Signature != SERVICE_SIGNATURE) { SC_LOG(ERROR,"RI_ScSetServiceBitsW: hServiceStatus %#lx was invalid\n", hServiceStatus); status = ERROR_INVALID_HANDLE; } } except(EXCEPTION_EXECUTE_HANDLER) { status = ERROR_INVALID_HANDLE; } if (status != NO_ERROR) { return(status); } ScDatabaseLock(SC_GET_EXCLUSIVE,"ScSetServiceBits1"); serviceRecord = (LPSERVICE_RECORD)hServiceStatus; if (bSetBitsOn) { // // Set the bits in the global location. // GlobalServerAnnounce |= dwServiceBits; // // Set the bits in the service record. // serviceRecord->ServerAnnounce |= dwServiceBits; } else { // // Clear the bits in the global location. // GlobalServerAnnounce &= ~dwServiceBits; // // Clear the bits in the service record. // serviceRecord->ServerAnnounce &= ~dwServiceBits; } // // If the server service is running, then send the Global mask to // the server service. // status = ScGetNamedServiceRecord( serverServiceName, &serviceRecord); if (status == NO_ERROR) { serviceState = serviceRecord->ServiceStatus.dwCurrentState; ScDatabaseLock(SC_RELEASE,"ScSetServiceBits2"); if (serviceState == SERVICE_RUNNING) { status = ScNetServerSetServiceBits( NULL, // ServerName NULL, // TransportName GlobalServerAnnounce, bUpdateImmediately); if (status != NERR_Success) { SC_LOG(ERROR,"I_ScSetServiceBits: I_NetServerSetServiceBits failed %lu\n", status); } else { SC_LOG(TRACE,"I_ScSetServiceBits: I_NetServerSetServiceBits success\n",0); } } } else { ScDatabaseLock(SC_RELEASE,"ScSetServiceBits3"); status = NO_ERROR; } SC_LOG(TRACE,"I_ScSetServiceBits: GlobalServerAnnounce = 0x%lx\n", GlobalServerAnnounce); return(status); } DWORD ScRemoveServiceBits( IN LPSERVICE_RECORD ServiceRecord ) /*++ Routine Description: This function is called when a service stops running. It looks in the service record for any server announcement bits that are set and turns them off in GlobalServerAnnounce. The ServerAnnounce element in the service record is set to 0. Arguments: ServiceRecord - This is a pointer to the service record that has changed to the stopped state. Return Value: The status returned from I_NetServerSetServiceBits. --*/ { DWORD status = NO_ERROR; LPSERVICE_RECORD serverServiceRecord; DWORD serviceState; if (ScNetServerSetServiceBits == (SETSBPROC)NULL) { if (! ScInitServerAnnounceFcn()) { return(ERROR_NO_NETWORK); } } if (ServiceRecord->ServerAnnounce != 0) { ScDatabaseLock(SC_GET_EXCLUSIVE,"ScRemoveServiceBits1"); // // Clear the bits in the global location. // GlobalServerAnnounce &= ~(ServiceRecord->ServerAnnounce); // // Clear the bits in the service record. // ServiceRecord->ServerAnnounce = 0; SC_LOG1(TRACE,"RemoveServiceBits: New GlobalServerAnnounce = 0x%lx\n", GlobalServerAnnounce); // // If the server service is running, then send the Global mask to // the server service. // status = ScGetNamedServiceRecord( SERVICE_SERVER, &serverServiceRecord); if (status == NO_ERROR) { serviceState = serverServiceRecord->ServiceStatus.dwCurrentState; ScDatabaseLock(SC_RELEASE,"ScRemoveServiceBits2"); if ( serviceState == SERVICE_RUNNING) { status = ScNetServerSetServiceBits( NULL, // ServerName NULL, // Transport name GlobalServerAnnounce, TRUE); // Update immediately. if (status != NERR_Success) { SC_LOG(ERROR,"ScRemoveServiceBits: I_NetServerSetServiceBits failed %d\n", status); } } } else { ScDatabaseLock(SC_RELEASE,"ScRemoveServiceBits2"); } } return(status); } BOOL ScInitServerAnnounceFcn( VOID ) /*++ Routine Description: Arguments: Return Value: --*/ { ScGlobalServerHandle = LoadLibraryW(L"netapi32.dll"); if (ScGlobalServerHandle == NULL) { SC_LOG(ERROR,"ScInitServerAnnouncFcn: LoadLibrary failed %d\n", GetLastError()); return(FALSE); } ScNetServerSetServiceBits = (SETSBPROC)GetProcAddress( ScGlobalServerHandle, "I_NetServerSetServiceBits"); if (ScNetServerSetServiceBits == (SETSBPROC)NULL) { SC_LOG(ERROR,"ScInitServerAnnouncFcn: GetProcAddress failed %d\n", GetLastError()); return(FALSE); } return TRUE; }