#include "ace/config-all.h" #if defined (ACE_WIN32) #include "ace/NT_Service.h" #if !defined (__ACE_INLINE__) #include "ace/NT_Service.inl" #endif /* __ACE_INLINE__ */ #include "ace/Log_Category.h" #include "ace/Service_Object.h" #include "ace/OS_NS_errno.h" ACE_BEGIN_VERSIONED_NAMESPACE_DECL ACE_ALLOC_HOOK_DEFINE(ACE_NT_Service) // ACE_NT_Service destructor. ACE_NT_Service::~ACE_NT_Service () { if (this->svc_sc_handle_ != 0) { CloseServiceHandle (this->svc_sc_handle_); this->svc_sc_handle_ = 0; } delete [] this->desc_; delete [] this->name_; delete [] this->host_; } // This default implementation of ACE_NT_Service::open sets the // service's status to START_PENDING with the estimated time until // STARTED set to the value given when this object was constructed. // Then the svc function is called, which implements the guts of the // service. Note that this function is running in a thread created by // the OS, not by ACE_Thread_Manager. The thread manager does not // know anything about this thread. The service can, however, use // ACE_Thread_Manager to start more threads if desired. When the svc // function returns, the service status is set to STOPPED, and exit // codes set based on errno/GetLastError if the svc function returns // -1. // // The svc function is expected to set the service status to SERVICE_RUNNING // after it initializes. // // The handle_control function will be called for each time there is a // request for the service. It is up to that function and svc to // cooperate to both respond appropriately to the request (by at least // updating the service's status) and to fulfill the request. int ACE_NT_Service::open (void *args) { ACE_UNUSED_ARG (args); this->report_status (SERVICE_START_PENDING, 0); int svc_return = this->svc (); if (svc_return == 0) { this->svc_status_.dwWin32ExitCode = NO_ERROR; this->svc_status_.dwServiceSpecificExitCode = 0; } else { if (errno == 0) { this->svc_status_.dwWin32ExitCode = GetLastError (); } else { this->svc_status_.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; this->svc_status_.dwServiceSpecificExitCode = errno; } } return svc_return; } int ACE_NT_Service::fini () { return this->report_status (SERVICE_STOPPED, 0); } void ACE_NT_Service::handle_control (DWORD control_code) { switch (control_code) { case SERVICE_CONTROL_SHUTDOWN: case SERVICE_CONTROL_STOP: this->stop_requested (control_code); break; case SERVICE_CONTROL_PAUSE: this->pause_requested (control_code); break; case SERVICE_CONTROL_CONTINUE: this->continue_requested (control_code); break; case SERVICE_CONTROL_INTERROGATE: this->interrogate_requested (control_code); break; } } void ACE_NT_Service::stop_requested (DWORD) { this->report_status (SERVICE_STOP_PENDING); /* how to cancel? */ } void ACE_NT_Service::pause_requested (DWORD) { this->report_status (SERVICE_PAUSE_PENDING); this->suspend (); this->report_status (SERVICE_PAUSED); } void ACE_NT_Service::continue_requested (DWORD) { this->report_status (SERVICE_CONTINUE_PENDING); this->resume (); this->report_status (SERVICE_RUNNING); } void ACE_NT_Service::interrogate_requested (DWORD) { this->report_status (0); } void ACE_NT_Service::name (const ACE_TCHAR *name, const ACE_TCHAR *desc) { delete [] this->desc_; delete [] this->name_; if (desc == 0) desc = name; this->name_ = ACE::strnew (name); this->desc_ = ACE::strnew (desc); } void ACE_NT_Service::host (const ACE_TCHAR *host) { delete [] this->host_; if (this->svc_sc_handle_ != 0) { CloseServiceHandle (this->svc_sc_handle_); this->svc_sc_handle_ = 0; } if (host == 0) { this->host_ = 0; } else { this->host_ = ACE::strnew (host); } } int ACE_NT_Service::insert (DWORD start_type, DWORD error_control, const ACE_TCHAR *exe_path, const ACE_TCHAR *group_name, LPDWORD tag_id, const ACE_TCHAR *dependencies, const ACE_TCHAR *account_name, const ACE_TCHAR *password, DWORD desired_access) { ACE_TCHAR this_exe[MAXPATHLEN + 2]; // Insure ACE_OS::last_error finds GetLastError unless we set errno. errno = 0; if (exe_path == 0) { if (ACE_TEXT_GetModuleFileName (0, this_exe + 1, MAXPATHLEN) == 0) return -1; // Make sure that this_exe is quoted this_exe[0] = ACE_TEXT ('\"'); ACE_OS::strcat (this_exe, ACE_TEXT ("\"")); exe_path = this_exe; } SC_HANDLE sc_mgr = ACE_TEXT_OpenSCManager (this->host (), 0, SC_MANAGER_ALL_ACCESS); if (sc_mgr == 0) { ACE_OS::set_errno_to_last_error(); return -1; } SC_HANDLE sh = ACE_TEXT_CreateService (sc_mgr, this->name (), this->desc (), desired_access, this->svc_status_.dwServiceType, start_type, error_control, exe_path, group_name, tag_id, dependencies, account_name, password); // If there was an error, stash GetLastError before CloseServiceHandle // smashes it. ACE_OS::last_error will find the saved error value. if (sh == 0) ACE_OS::set_errno_to_last_error (); CloseServiceHandle (sc_mgr); if (sh == 0) return -1; if (this->svc_sc_handle_ != 0) CloseServiceHandle (this->svc_sc_handle_); this->svc_sc_handle_ = sh; return 0; } int ACE_NT_Service::remove () { if (this->svc_sc_handle () == 0) return -1; if (DeleteService (this->svc_sc_handle()) == 0 && GetLastError () != ERROR_SERVICE_MARKED_FOR_DELETE) return -1; return 0; } // Sets the startup type for the service. Returns -1 on error, 0 on // success. int ACE_NT_Service::startup (DWORD startup) { SC_HANDLE svc = this->svc_sc_handle (); if (svc == 0) return -1; BOOL ok = ChangeServiceConfig (svc, (DWORD) SERVICE_NO_CHANGE,// No change to service type startup, // New startup type (DWORD) SERVICE_NO_CHANGE,// No change to error ctrl 0, // No change to pathname 0, // No change to load group 0, // No change to tag 0, // No change to dependencies 0, 0, // No change to acct/passwd 0); // No change to name return ok ? 0 : -1; } // Returns the current startup type. DWORD ACE_NT_Service::startup () { // The query buffer will hold strings as well as the defined struct. // The string pointers in the struct point to other areas in the // passed memory area, so it has to be large enough to hold the // struct plus all the strings. char cfgbuff[1024]; LPQUERY_SERVICE_CONFIG cfg; DWORD cfgsize, needed_size; SC_HANDLE svc = this->svc_sc_handle (); if (svc == 0) { // To distinguish this error from the QueryServiceConfig failure // below, return the DWORD equivalent of -2, rather than -1. return MAXDWORD - 1; } cfgsize = sizeof cfgbuff; cfg = (LPQUERY_SERVICE_CONFIG) cfgbuff; BOOL ok = QueryServiceConfig (svc, cfg, cfgsize, &needed_size); if (ok) return cfg->dwStartType; // Zero is a valid return value for QueryServiceConfig, so if // QueryServiceConfig fails, return the DWORD equivalent of -1. return MAXDWORD; } void ACE_NT_Service::capture_log_msg_attributes () { ACE_Log_Msg::init_hook (this->log_msg_attributes_); } void ACE_NT_Service::inherit_log_msg_attributes () { // There's no thread descriptor involved with a NT-started // thread, so the first arg is 0. ACE_Log_Msg::inherit_hook (0, this->log_msg_attributes_); } int ACE_NT_Service::start_svc (ACE_Time_Value *wait_time, DWORD *svc_state, DWORD argc, const ACE_TCHAR **argv) { SC_HANDLE svc = this->svc_sc_handle (); if (svc == 0) return -1; if (!ACE_TEXT_StartService (svc, argc, argv)) return -1; this->wait_for_service_state (SERVICE_RUNNING, wait_time); if (svc_state != 0) *svc_state = this->svc_status_.dwCurrentState; return 0; } int ACE_NT_Service::stop_svc (ACE_Time_Value *wait_time, DWORD *svc_state) { SC_HANDLE svc = this->svc_sc_handle (); if (svc == 0) return -1; if (!ControlService (svc, SERVICE_CONTROL_STOP, &this->svc_status_)) return -1; this->wait_for_service_state (SERVICE_STOPPED, wait_time); if (svc_state != 0) *svc_state = this->svc_status_.dwCurrentState; return 0; } int ACE_NT_Service::pause_svc (ACE_Time_Value *wait_time, DWORD *svc_state) { SC_HANDLE svc = this->svc_sc_handle (); if (svc == 0) return -1; if (!ControlService (svc, SERVICE_CONTROL_PAUSE, &this->svc_status_)) return -1; this->wait_for_service_state (SERVICE_PAUSED, wait_time); if (svc_state != 0) *svc_state = this->svc_status_.dwCurrentState; return 0; } int ACE_NT_Service::continue_svc (ACE_Time_Value *wait_time, DWORD *svc_state) { SC_HANDLE svc = this->svc_sc_handle (); if (svc == 0) return -1; if (!ControlService (svc, SERVICE_CONTROL_CONTINUE, &this->svc_status_)) return -1; this->wait_for_service_state (SERVICE_RUNNING, wait_time); if (svc_state != 0) *svc_state = this->svc_status_.dwCurrentState; return 0; } DWORD ACE_NT_Service::state (ACE_Time_Value *wait_hint) { DWORD curr_state; if (this->state (&curr_state, wait_hint) == -1) return 0; return curr_state; } int ACE_NT_Service::state (DWORD *pstate, ACE_Time_Value *wait_hint) { SC_HANDLE svc = this->svc_sc_handle (); if (svc == 0) return -1; // Need to create a temporary copy of this variable since the // QueryServiceStatus call will modify the setting depending on the // current state of the Service. If the service is currently // STOPPED, the value will be cleared. DWORD controls_accepted = this->svc_status_.dwControlsAccepted; if (QueryServiceStatus (svc, &this->svc_status_) == 0) return -1; if (wait_hint != 0) wait_hint->msec (static_cast (this->svc_status_.dwWaitHint)); *pstate = this->svc_status_.dwCurrentState; this->svc_status_.dwControlsAccepted = controls_accepted; return 0; } // test_access // // Open a new handle, ignoring any handle open in svc_sc_handle_. // This function's results are returned without leaving the handle // open. int ACE_NT_Service::test_access (DWORD desired_access) { int status = -1; // Guilty until proven innocent SC_HANDLE sc_mgr = ACE_TEXT_OpenSCManager (this->host (), 0, GENERIC_READ); if (sc_mgr != 0) { SC_HANDLE handle = ACE_TEXT_OpenService (sc_mgr, this->name (), desired_access); CloseServiceHandle (sc_mgr); if (handle != 0) { status = 0; CloseServiceHandle (handle); } } return status; } // report_status // // Reports the current status. If new_status is not 0, it sets the // status to the new value before reporting. NOTE - this assumes that // no actual service status values have the value 0. This is true in // WinNT 4. If the status is a 'pending' type, the supplied time hint // is used unless it's 0, in which case the existing hint is used. // The dwWaitHint is not updated by this function. The checkpoint is // incremented by one after a pending report. int ACE_NT_Service::report_status (DWORD new_status, DWORD time_hint) { int bump_checkpoint = 0; int retval = 0; DWORD save_controls = 0; if (new_status != 0) this->svc_status_.dwCurrentState = new_status; switch (this->svc_status_.dwCurrentState) { case SERVICE_START_PENDING: save_controls = this->svc_status_.dwControlsAccepted; this->svc_status_.dwControlsAccepted = 0; /* Fall through */ case SERVICE_STOP_PENDING: case SERVICE_CONTINUE_PENDING: case SERVICE_PAUSE_PENDING: this->svc_status_.dwWaitHint = time_hint ? time_hint : this->start_time_; bump_checkpoint = 1; break; default: this->svc_status_.dwCheckPoint = 0; } retval = SetServiceStatus (this->svc_handle_, &this->svc_status_) ? 0 : -1; if (save_controls != 0) this->svc_status_.dwControlsAccepted = save_controls; if (bump_checkpoint) ++this->svc_status_.dwCheckPoint; return retval; } SC_HANDLE ACE_NT_Service::svc_sc_handle () { if (this->svc_sc_handle_ == 0) { SC_HANDLE sc_mgr = ACE_TEXT_OpenSCManager (this->host (), 0, SC_MANAGER_ALL_ACCESS); if (sc_mgr != 0) { this->svc_sc_handle_ = ACE_TEXT_OpenService (sc_mgr, this->name (), SERVICE_ALL_ACCESS); if (this->svc_sc_handle_ == 0) ACE_OS::set_errno_to_last_error (); CloseServiceHandle (sc_mgr); } else ACE_OS::set_errno_to_last_error (); } return this->svc_sc_handle_; } void ACE_NT_Service::wait_for_service_state (DWORD desired_state, ACE_Time_Value *wait_time) { DWORD last_state = 0; DWORD last_check_point = 0; int first_time = 1; int service_ok; ACE_Time_Value time_out = ACE_OS::gettimeofday (); if (wait_time != 0) time_out += *wait_time; // Poll until the service reaches the desired state. for (;;) { service_ok = 0 != QueryServiceStatus (this->svc_sc_handle_, &this->svc_status_); // If we cannot query the service, we are done. if (!service_ok) break; // If the service has the desired state, we are done. if (desired_state == this->svc_status_.dwCurrentState) break; // If we time-out, we are done if (wait_time != 0 && ACE_OS::gettimeofday () > time_out ) { errno = ETIME; break; } if (first_time) { // remember the service state, the first time we wait last_state = this->svc_status_.dwCurrentState; last_check_point = this->svc_status_.dwCheckPoint; first_time = 0; } else { // update the state change. if (last_state != this->svc_status_.dwCurrentState) { last_state = this->svc_status_.dwCurrentState; last_check_point = this->svc_status_.dwCheckPoint; } else { // The check-point should have increased if (this->svc_status_.dwCheckPoint > last_check_point) last_check_point = this->svc_status_.dwCheckPoint; else { // Service control failure, we are done. service_ok = 0; break; } } } ::Sleep (this->svc_status_.dwWaitHint); } return; } ACE_END_VERSIONED_NAMESPACE_DECL #endif /* ACE_WIN32 */