//%LICENSE//////////////////////////////////////////////////////////////// // // Licensed to The Open Group (TOG) under one or more contributor license // agreements. Refer to the OpenPegasusNOTICE.txt file distributed with // this work for additional information regarding copyright ownership. // Each contributor licenses this file to you under the OpenPegasus Open // Source License; you may not use this file except in compliance with the // License. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ////////////////////////////////////////////////////////////////////////// // // Author: Tony Fiorentino (fiorentino_tony@emc.com) // //%///////////////////////////////////////////////////////////////////////////// #include #include #include #include #include "Service.h" //------------------------------------------------------------------------- // G L O B A L S //------------------------------------------------------------------------- int Service::g_argc = 0; char **Service::g_argv = NULL; char *Service::g_service_name = NULL; char *Service::g_event_source = NULL; DWORD Service::g_flags = 0; DWORD Service::g_current_state = 0; SERVICE_STATUS_HANDLE Service::g_service_status_handle = 0; SERVICE_MAIN_T Service::g_service_main = NULL; //------------------------------------------------------------------------- // P U B L I C //------------------------------------------------------------------------- Service::Service() { } Service::Service(const char* service_name) { // ATTN: I have to allocate memory here, unless I want to change the // char* g_service_name to a char[]. Changing to an array of char // affects a lot more code. Previously, this method had a char* for its // input parameter, and since we were using #define's the values were // always in memory. // Now we are using an interface method that returns const char* -hns PEP222 g_service_name = (char*) malloc(strlen(service_name)+1); memset(g_service_name, '\0', strlen(service_name)+1); strncpy(g_service_name, service_name, strlen(service_name)); } Service::Service( const char* service_name, char* event_source) { g_event_source = event_source; g_service_name = (char*) malloc(strlen(service_name)+1); memset(g_service_name, '\0', strlen(service_name)+1); strncpy(g_service_name, service_name, strlen(service_name)); } Service::~Service() { // ATTN: Will open Bugzilla on this. I need to change this to either use // char arrays OR deallocate the memory elsewhere. I cannot use any fancy // AutoPtr stuff since Pegasus code is kept out of this OS-specific file // -hns PEP#222 /*if (g_service_name != NULL) { free(g_service_name); }*/ } void Service::SetServiceName(char* service_name) { if (g_service_name != NULL) { free(g_service_name); } g_service_name = (char*) malloc(strlen(service_name)+1); memset(g_service_name, '\0', strlen(service_name)+1); strncpy(g_service_name, service_name, strlen(service_name)); } /*-------------------------------------------------------------------------* * Method: Install * * * * Args: * * display_name * * The service's display name (hopefully more descriptive, will show * * up in the Win32 Services MMC snapin as "Service Name"). * * * * exe_name: * * The service's executable name (full path, please) * * * * flags: * * Reserved. Currently unused. * * * * Description: * * This function creates the service. * * * * NOTE: If the process is successfully launched as a Win32 service, this * * function never returns, but calls exit() instead. * *-------------------------------------------------------------------------*/ Service::ReturnCode Service::Install( char *display_name, char *description, char *exe_name) { ReturnCode status = SERVICE_RETURN_SUCCESS; SC_HANDLE sch; if (g_service_name == NULL || display_name == NULL || exe_name == NULL) return SERVICE_ERROR_NOT_FOUND; // SERVICE_ERROR_NOT_FOUND else if ((sch = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE)) == NULL) status = get_error(GetLastError(), "open"); else { SC_HANDLE service = CreateService( sch, // SCManager database g_service_name, // name of service display_name, // service name to display SERVICE_ALL_ACCESS, // desired access SERVICE_WIN32_OWN_PROCESS, // service type SERVICE_DEMAND_START, // start type SERVICE_ERROR_NORMAL, // error control type exe_name, // service's binary NULL, // no load ordering group NULL, // no tag identifier NULL, // no dependencies NULL, // LocalSystem account NULL); // no password if (service == NULL) { status = get_error(GetLastError(), "create"); return status; } else { change_service_description(service, description); CloseServiceHandle(service); } CloseServiceHandle(sch); } return status; } /*-------------------------------------------------------------------------* * Method: Remove * * * * Description: * * Removes the service. * *-------------------------------------------------------------------------*/ Service::ReturnCode Service::Remove() { ReturnCode status = SERVICE_RETURN_SUCCESS; SC_HANDLE sch; if (g_service_name == NULL) return SERVICE_ERROR_NOT_FOUND; /* SERVICE_ERROR_NOT_FOUND */ else if ((sch = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE)) == NULL) status = get_error(GetLastError(), "open"); else { SC_HANDLE service = OpenService(sch, g_service_name, DELETE); if (service == NULL) { status = get_error(GetLastError(), "open"); } else { if (!DeleteService(service)) { status = get_error(GetLastError(), "remove"); } CloseServiceHandle(service); } CloseServiceHandle(sch); } return status; } /*-------------------------------------------------------------------------* * Method: Start * * * * Args: * * wait_time: * * The user supplied wait (~1 second per QueryServiceStatus() attempt) * * * * Description: * * Attempt to start the service. * *-------------------------------------------------------------------------*/ Service::ReturnCode Service::Start(int wait_time) { ReturnCode status = SERVICE_RETURN_SUCCESS; SERVICE_STATUS service_status; SC_HANDLE sch; if (g_service_name == NULL) return SERVICE_ERROR_NOT_FOUND; // SERVICE_ERROR_NOT_FOUND else if ((sch = OpenSCManager(NULL, NULL, GENERIC_READ)) == NULL) status = get_error(GetLastError(), "open"); else { SC_HANDLE service = OpenService( sch, g_service_name, SERVICE_START | SERVICE_QUERY_STATUS); if (service == NULL) status = get_error(GetLastError(), "open"); else if (!StartService(service, g_argc, (const char **)g_argv)) status = get_error(GetLastError(), "start"); else { int i, max = (wait_time > 0) ? wait_time : 5; // Loop up to max times waiting for the service // state to change to RUNNING for (i = 0; i < max; i++) { if (!QueryServiceStatus(service, &service_status)) { status = get_error(GetLastError(), "query"); return status; // QUERY_FAIL } if (service_status.dwCurrentState == SERVICE_RUNNING) break; Sleep(1 * CLOCKS_PER_SEC); } status = (i < max) ? SERVICE_RETURN_SUCCESS : SERVICE_ERROR_REQUEST_TIMEOUT; CloseServiceHandle(service); } CloseServiceHandle(sch); } return status; } /*-------------------------------------------------------------------------* * Method: Stop * * * * Args: * * wait_time: * * The user supplied wait (~1 second per QueryServiceStatus() attempt) * * * * Description: * * Attempt to stop the service. * *-------------------------------------------------------------------------*/ Service::ReturnCode Service::Stop(int wait_time) { ReturnCode status = SERVICE_RETURN_SUCCESS; SERVICE_STATUS service_status; SC_HANDLE sch; if (g_service_name == NULL) return SERVICE_ERROR_NOT_FOUND; // SERVICE_ERROR_NOT_FOUND else if ((sch = OpenSCManager(NULL, NULL, GENERIC_READ)) == NULL) status = get_error(GetLastError(), "open"); // show_error("OpenSCMManager", "service", GetLastError()); else { SC_HANDLE service = OpenService( sch, g_service_name, SERVICE_STOP | SERVICE_QUERY_STATUS); if (service == NULL) status = get_error(GetLastError(), "open"); else if (!ControlService( service, SERVICE_CONTROL_STOP, &service_status)) status = get_error(GetLastError(), "stop"); else { int i, max = (wait_time > 0) ? wait_time : 5; // Loop up to max times waiting for the service // state to change to STOPPED for (i = 0; i < max; i++) { if (!QueryServiceStatus(service, &service_status)) { status = get_error(GetLastError(), "query"); return status; // QUERY_FAIL } if (service_status.dwCurrentState == SERVICE_STOPPED) break; Sleep(1 * CLOCKS_PER_SEC); } status = (i < max) ? SERVICE_RETURN_SUCCESS : SERVICE_ERROR_REQUEST_TIMEOUT; CloseServiceHandle(service); } CloseServiceHandle(sch); } return status; } /*-------------------------------------------------------------------------* * Method: Run * * * * Args: * * service_main: * * The user supplied service_main function (not to be confused with * * real_service_main above) * * * * flags: * * Reserved. Currently unused. * * * * Description: * * This function interacts with the SCM to run the current process * * as a Win32 service. * * * * NOTE: If the process is successfully launched as a Win32 service, this * * function never returns, but calls exit() instead. * *-------------------------------------------------------------------------*/ Service::ReturnCode Service::Run(SERVICE_MAIN_T service_main, DWORD flags) { ReturnCode status = SERVICE_RETURN_SUCCESS; SERVICE_TABLE_ENTRY dispatchTable[] = { { g_service_name, real_service_main }, { NULL, NULL } }; // Validate the arguments as best we can if (g_service_name == NULL || service_main == NULL) return SERVICE_ERROR_NOT_FOUND; // SERVICE_ERROR_NOT_FOUND // Save parameters in global variables g_flags = flags; g_service_main = service_main; // Kick off the service if (!StartServiceCtrlDispatcher(dispatchTable)) { status = get_error(GetLastError(), "start"); return status; // FAIL } // Don't call exit() return status; } /*-------------------------------------------------------------------------* * Method: GetState * * * * Description: * * Returns the state of the service into "state". * *-------------------------------------------------------------------------*/ Service::ReturnCode Service::GetState(State* state) { ReturnCode status = SERVICE_RETURN_SUCCESS; SERVICE_STATUS service_status; SC_HANDLE sch; if (g_service_name == NULL) return SERVICE_ERROR_NOT_FOUND; // SERVICE_ERROR_NOT_FOUND else if ((sch = OpenSCManager(NULL, NULL, GENERIC_READ)) == NULL) status = get_error(GetLastError(), "open"); else { SC_HANDLE service = OpenService(sch, g_service_name, SERVICE_QUERY_STATUS); if (service == NULL) status = get_error(GetLastError(), "open"); else if (!QueryServiceStatus(service, &service_status)) status = get_error(GetLastError(), "query"); else { *state = get_state(service_status.dwCurrentState); CloseServiceHandle(service); } CloseServiceHandle(sch); } return status; } /*-------------------------------------------------------------------------* * Method: LogEvent * * * * Args: * * event_type: * * The Win32 event type. Valid event types are: * * EVENTLOG_SUCCESS : Success event * * EVENTLOG_ERROR_TYPE : Error event * * EVENTLOG_WARNING_TYPE : Warning event * * EVENTLOG_INFORMATION_TYPE : Information event * * EVENTLOG_AUDIT_SUCCESS : Success audit event * * EVENTLOG_AUDIT_FAILURE : Failure audit event * * * * event_id: * * A fancy name for error code or error number. * * * * string: * * String to be logged or merged in with the error string in the * * message DLL. * * * * Description: * * This function provides a simple layer over the Win32 error logging * * API's. * * * * Returns: * * true if event was successfully logged * * false if the event could not be logged * *-------------------------------------------------------------------------*/ bool Service::LogEvent(WORD event_type, DWORD event_id, const char *string) { BOOL status; HANDLE h_event_source = RegisterEventSource(NULL, g_event_source); if (h_event_source == NULL) FALSE; status = ReportEvent( h_event_source, event_type, 0, event_id, NULL, 1, 0, &string, NULL); DeregisterEventSource(h_event_source); return (status == TRUE); } //------------------------------------------------------------------------- // P R I V A T E //------------------------------------------------------------------------- /*-------------------------------------------------------------------------* * Routine: real_service_main * * * * Args: * * argc: * * The number of arguments in the argv array * * argv: * * An array of strings representing the command line arguments * * * * Description: * * This function is the real service main (as opposed to the user * * supplied service main, which is called service_main). * * * * Returns: * * nothing * *-------------------------------------------------------------------------*/ void __stdcall Service::real_service_main(DWORD argc, LPTSTR *argv) { // Let the SCM know we're alive and kicking report_status(SERVICE_START_PENDING, NO_ERROR, 0, 5000); // If the command line arguments include the string "-debug" then // invoke the debugger if (check_args_for_string("-debug")) DebugBreak(); // Save copy of argc and argc in global variables g_argc = argc; g_argv = argv; // Start service actions g_service_status_handle = RegisterServiceCtrlHandler(g_service_name, service_control_handler); if (g_service_status_handle == 0) { show_error("register", "Service Control Handler", GetLastError()); report_status(SERVICE_STOPPED, NO_ERROR, 0, 5000); return; } // Change our state to RUNNING, then invoke the user supplied // service_main function. After the user's service_main exits, // change the service state to STOPPED. report_status(SERVICE_RUNNING, NO_ERROR, 0, 5000); g_service_main(STARTUP_FLAG, argc, argv); report_status(SERVICE_STOPPED, NO_ERROR, 0, 5000); return; } /*-------------------------------------------------------------------------* * Routine: check_args_for_string * * * * Args: * * string: * * The string to match. * * * * Description: * * This function iterates through the command line arguments searching * * for the string specified by the string parameter. * * * * Returns: * * true if the string was found * * false if the string was not found * *-------------------------------------------------------------------------*/ bool Service::check_args_for_string(char* string) { int i; for (i = 1; i < g_argc; i++) { if (strcmp(g_argv[i], string) == 0) return true; } return false; } /*-------------------------------------------------------------------------* * Method: service_control_handler * * * * Args: * * control: * * The control sent from the SCM telling us what action to take: * * start, stop, pause, continue, etc. * * * * Description: * * This function handles control messages sent from the SCM. Currently * * the only message that is handled differently is the STOP message, * * which invokes the user's service main function passing to it the * * SHUTDOWN_FLAG. The user is then responsible to perform all shutdown * * related tasks. * * * * Returns: * * Nothing * *-------------------------------------------------------------------------*/ void WINAPI Service::service_control_handler(DWORD control) { /* Currently, only the stop contol requires special handling */ if (control == SERVICE_CONTROL_STOP) { report_status(SERVICE_STOP_PENDING, NO_ERROR, 0, 5000); g_service_main(SHUTDOWN_FLAG, g_argc, g_argv); return; } /* For every other control, just send back our current state */ report_status(g_current_state, NO_ERROR, 0, 5000); } /*-------------------------------------------------------------------------* * Method: report_status * * * * Args: * * current_state: * * The service's new state. May be any valid Win32 service state. * * * * exit_code: * * This must be a Win32 exit code. If the server is exiting due to * * an error returned from the Win32 API, then this is the place to * * report the error status. Most of the time, this will be NO_ERROR. * * * * check_point: * * An integer value that should start at zero and increment as each * * discrete step within a phase is completed. For example, if startup * * consists of 3 steps, then the startup will issue its first check- * * point of zero, then increment up through and including 2 as each * * step is finished. * * * * wait_hint: * * Tells the SCM how long to expect to wait for the next status * * update. * * * * Description: * * This function provides an even higher level of abstraction over * * the Win32 event logging API's. * * * * Returns: * * true if the status was successfully reported * * false if the status could not be reported * *-------------------------------------------------------------------------*/ bool Service::report_status( DWORD current_state, DWORD exit_code, DWORD check_point, DWORD wait_hint) { SERVICE_STATUS current_status = { SERVICE_WIN32, current_state, SERVICE_CONTROL_INTERROGATE, exit_code, 0, check_point, wait_hint }; /* Wait until we're started before we accept a stop control */ if (current_state == SERVICE_RUNNING) current_status.dwControlsAccepted += SERVICE_ACCEPT_STOP; /* Save new state */ g_current_state = current_state; return (SetServiceStatus(g_service_status_handle, ¤t_status) == TRUE); } /*-------------------------------------------------------------------------* * Method: change_service_description * * * * Args: * * service: * * Handle to the service * * * * description: * * The service's description * * * * Description: * * This function sets the description for the service. The description * * text shows up in the Services mmc snapin. The description is added * * as a benefit to users, but does not affect the service's operation. * * Therefore, if this function should fail for any reason, there is * * no need to stop. That's why this function has no return value. * * * * Returns: * * Nothing * * * * Notes: * * This function uses the ChangeServiceConfig2() API function which * * first appeared in Windows2000. Therefore, this code checks the * * OS version, before calling ChangeServiceConfig2(). * *-------------------------------------------------------------------------*/ void Service::change_service_description(SC_HANDLE service, char *description) { OSVERSIONINFO osvi; /* * Test for Windows 2000 or greater */ osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if (GetVersionEx(&osvi) != 0 && osvi.dwPlatformId == VER_PLATFORM_WIN32_NT && osvi.dwMajorVersion >= 5) { typedef BOOL (WINAPI *CHANGE_SERVICE_CONFIG2_T)(SC_HANDLE, DWORD, LPVOID); HINSTANCE hdll = LoadLibrary("advapi32.dll"); if (hdll != NULL) { SERVICE_DESCRIPTION sd; CHANGE_SERVICE_CONFIG2_T csc; csc = (CHANGE_SERVICE_CONFIG2_T) GetProcAddress(hdll, "ChangeServiceConfig2A"); if (csc) { sd.lpDescription = description; csc(service, SERVICE_CONFIG_DESCRIPTION, &sd); } FreeLibrary(hdll); } } } /*-------------------------------------------------------------------------* * Method: show_error * * * * Args: * * action: * * A single verb decribing the action going on when the error * * occurred. For example, "opening", "creating", etc. * * * * object: * * Description of the object on which was action occurred. Examples * * are "file", "service", etc. * * * * hr: * * The error status. Can be an hresult, or Win32 error status. * * * * Description: * * This function provides an even higher level of abstraction over * * the Win32 event logging API's. * * * * Returns: * * true if event was successfully logged * * false if the event could not be logged * *-------------------------------------------------------------------------*/ bool Service::show_error(const char *action, const char *object, DWORD hr) { char console_title[_MAX_PATH] = {0}; char txt[_MAX_PATH] = ""; char msg[_MAX_PATH] = ""; DWORD nchars; nchars = FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), msg, sizeof(msg), NULL); if (nchars == 0) sprintf(msg, "Unknown error code - %%X%x", hr); else if (nchars > 1) { if (msg[nchars - 1] == '\n') msg[nchars - 1] = '\0'; if (msg[nchars - 2] == '\r') msg[nchars - 2] = '\0'; } sprintf(txt, "Failed to %s %s %s. Reason: %s", action, object, g_service_name, msg); // Running from a console window // send courtesy message txt to stderr if (GetConsoleTitle(console_title, _MAX_PATH) > 0) { PEGASUS_STD(cerr) << txt << PEGASUS_STD(endl); } return LogEvent(EVENTLOG_ERROR_TYPE, 1, txt); } Service::State Service::get_state(DWORD scm_state) { return (State)scm_state; } Service::ReturnCode Service::get_error(DWORD error_status, const char *action) { switch (error_status) { /* // INFO: Could add cases to suppress error message. case ERROR_SERVICE_DOES_NOT_EXIST: case ERROR_SERVICE_CANNOT_ACCEPT_CTRL: break; */ case SERVICE_RETURN_SUCCESS: break; default: show_error(action, "service", error_status); break; } return (ReturnCode)error_status; }