X-Git-Url: http://git.iain.cx/?a=blobdiff_plain;f=service.cpp;h=ee60c1072fa7f0cd36712db4489f9bf09f9270ed;hb=eac4d7eedfe338703f0103aac01af2ca306f615a;hp=c3a5008850196988b42721d76f782996dd277f1e;hpb=7fdd3f6c44421a7c4872ecea56e550c19489c949;p=nssm.git diff --git a/service.cpp b/service.cpp index c3a5008..ee60c10 100644 --- a/service.cpp +++ b/service.cpp @@ -18,6 +18,85 @@ typedef struct { int last; } list_t; +/* + Check the status in response to a control. + Returns: 1 if the status is expected, eg STOP following CONTROL_STOP. + 0 if the status is desired, eg STOPPED following CONTROL_STOP. + -1 if the status is undesired, eg STOPPED following CONTROL_START. +*/ +static inline int service_control_response(unsigned long control, unsigned long status) { + switch (control) { + case NSSM_SERVICE_CONTROL_START: + switch (status) { + case SERVICE_START_PENDING: + return 1; + + case SERVICE_RUNNING: + return 0; + + default: + return -1; + } + + case SERVICE_CONTROL_STOP: + case SERVICE_CONTROL_SHUTDOWN: + switch (status) { + case SERVICE_STOP_PENDING: + return 1; + + case SERVICE_STOPPED: + return 0; + + default: + return -1; + } + + case SERVICE_CONTROL_PAUSE: + switch (status) { + case SERVICE_PAUSE_PENDING: + return 1; + + case SERVICE_PAUSED: + return 0; + + default: + return -1; + } + + case SERVICE_CONTROL_CONTINUE: + switch (status) { + case SERVICE_CONTINUE_PENDING: + return 1; + + case SERVICE_RUNNING: + return 0; + + default: + return -1; + } + + case SERVICE_CONTROL_INTERROGATE: + return 0; + } + + return 0; +} + +static inline int await_service_control_response(unsigned long control, SC_HANDLE service_handle, SERVICE_STATUS *service_status, unsigned long initial_status) { + int tries = 0; + while (QueryServiceStatus(service_handle, service_status)) { + int response = service_control_response(control, service_status->dwCurrentState); + /* Alas we can't WaitForSingleObject() on an SC_HANDLE. */ + if (! response) return response; + if (response > 0 || service_status->dwCurrentState == initial_status) { + if (++tries > 10) return response; + Sleep(50 * tries); + } + else return response; + } + return -1; +} + int affinity_mask_to_string(__int64 mask, TCHAR **string) { if (! string) return 1; if (! mask) { @@ -602,6 +681,7 @@ void cleanup_nssm_service(nssm_service_t *service) { if (service->wait_handle) UnregisterWait(service->process_handle); if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section); if (service->throttle_timer) CloseHandle(service->throttle_timer); + if (service->initial_env) FreeEnvironmentStrings(service->initial_env); HeapFree(GetProcessHeap(), 0, service); } @@ -701,14 +781,20 @@ int pre_edit_service(int argc, TCHAR **argv) { for (i = 0; settings[i].name; i++) _ftprintf(stderr, _T("%s\n"), settings[i].name); return 1; } - if (argc < mandatory) return usage(1); additional = 0; if (additional_mandatory) { + if (argc < mandatory) { + print_message(stderr, NSSM_MESSAGE_MISSING_SUBPARAMETER, parameter); + return 1; + } additional = argv[3]; remainder = 4; } - else additional = argv[remainder]; + else { + additional = argv[remainder]; + if (argc < mandatory) return usage(1); + } } nssm_service_t *service = alloc_nssm_service(); @@ -1053,33 +1139,36 @@ int control_service(unsigned long control, int argc, TCHAR **argv) { int ret; unsigned long error; SERVICE_STATUS service_status; - if (control == 0) { + if (control == NSSM_SERVICE_CONTROL_START) { + unsigned long initial_status = SERVICE_STOPPED; ret = StartService(service_handle, (unsigned long) argc, (const TCHAR **) argv); error = GetLastError(); - CloseHandle(service_handle); CloseServiceHandle(services); if (error == ERROR_IO_PENDING) { /* Older versions of Windows return immediately with ERROR_IO_PENDING indicate that the operation is still in progress. Newer versions - will return it if there really is a delay. As far as we're - concerned the operation is a success. We don't claim to offer a - fully-feature service control method; it's just a quick 'n' dirty - interface. - - In the future we may identify and handle this situation properly. + will return it if there really is a delay. */ ret = 1; error = ERROR_SUCCESS; } if (ret) { - _tprintf(_T("%s: %s"), canonical_name, error_string(error)); + int response = await_service_control_response(control, service_handle, &service_status, initial_status); + CloseHandle(service_handle); + + if (response) { + print_message(stderr, NSSM_MESSAGE_BAD_CONTROL_RESPONSE, canonical_name, service_status_text(service_status.dwCurrentState), service_control_text(control)); + return 1; + } + else _tprintf(_T("%s: %s: %s"), canonical_name, service_control_text(control), error_string(error)); return 0; } else { - _ftprintf(stderr, _T("%s: %s"), canonical_name, error_string(error)); + CloseHandle(service_handle); + _ftprintf(stderr, _T("%s: %s: %s"), canonical_name, service_control_text(control), error_string(error)); return 1; } } @@ -1093,16 +1182,7 @@ int control_service(unsigned long control, int argc, TCHAR **argv) { error = GetLastError(); if (ret) { - switch (service_status.dwCurrentState) { - case SERVICE_STOPPED: _tprintf(_T("SERVICE_STOPPED\n")); break; - case SERVICE_START_PENDING: _tprintf(_T("SERVICE_START_PENDING\n")); break; - case SERVICE_STOP_PENDING: _tprintf(_T("SERVICE_STOP_PENDING\n")); break; - case SERVICE_RUNNING: _tprintf(_T("SERVICE_RUNNING\n")); break; - case SERVICE_CONTINUE_PENDING: _tprintf(_T("SERVICE_CONTINUE_PENDING\n")); break; - case SERVICE_PAUSE_PENDING: _tprintf(_T("SERVICE_PAUSE_PENDING\n")); break; - case SERVICE_PAUSED: _tprintf(_T("SERVICE_PAUSED\n")); break; - default: _tprintf(_T("?\n")); return 1; - } + _tprintf(_T("%s\n"), service_status_text(service_status.dwCurrentState)); return 0; } else { @@ -1112,8 +1192,8 @@ int control_service(unsigned long control, int argc, TCHAR **argv) { } else { ret = ControlService(service_handle, control, &service_status); + unsigned long initial_status = service_status.dwCurrentState; error = GetLastError(); - CloseHandle(service_handle); CloseServiceHandle(services); if (error == ERROR_IO_PENDING) { @@ -1122,11 +1202,22 @@ int control_service(unsigned long control, int argc, TCHAR **argv) { } if (ret) { - _tprintf(_T("%s: %s"), canonical_name, error_string(error)); + int response = await_service_control_response(control, service_handle, &service_status, initial_status); + CloseHandle(service_handle); + + if (response) { + print_message(stderr, NSSM_MESSAGE_BAD_CONTROL_RESPONSE, canonical_name, service_status_text(service_status.dwCurrentState), service_control_text(control)); + return 1; + } + else _tprintf(_T("%s: %s: %s"), canonical_name, service_control_text(control), error_string(error)); return 0; } else { - _ftprintf(stderr, _T("%s: %s"), canonical_name, error_string(error)); + CloseHandle(service_handle); + _ftprintf(stderr, _T("%s: %s: %s"), canonical_name, service_control_text(control), error_string(error)); + if (error == ERROR_SERVICE_NOT_ACTIVE) { + if (control == SERVICE_CONTROL_SHUTDOWN || control == SERVICE_CONTROL_STOP) return 0; + } return 1; } } @@ -1234,6 +1325,9 @@ void WINAPI service_main(unsigned long argc, TCHAR **argv) { } } + /* Remember our initial environment. */ + service->initial_env = GetEnvironmentStrings(); + monitor_service(service); } @@ -1275,12 +1369,26 @@ int monitor_service(nssm_service_t *service) { TCHAR *service_control_text(unsigned long control) { switch (control) { /* HACK: there is no SERVICE_CONTROL_START constant */ - case 0: return _T("START"); + case NSSM_SERVICE_CONTROL_START: return _T("START"); case SERVICE_CONTROL_STOP: return _T("STOP"); case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN"); case SERVICE_CONTROL_PAUSE: return _T("PAUSE"); case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE"); case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE"); + case NSSM_SERVICE_CONTROL_ROTATE: return _T("ROTATE"); + default: return 0; + } +} + +TCHAR *service_status_text(unsigned long status) { + switch (status) { + case SERVICE_STOPPED: return _T("SERVICE_STOPPED"); + case SERVICE_START_PENDING: return _T("SERVICE_START_PENDING"); + case SERVICE_STOP_PENDING: return _T("SERVICE_STOP_PENDING"); + case SERVICE_RUNNING: return _T("SERVICE_RUNNING"); + case SERVICE_CONTINUE_PENDING: return _T("SERVICE_CONTINUE_PENDING"); + case SERVICE_PAUSE_PENDING: return _T("SERVICE_PAUSE_PENDING"); + case SERVICE_PAUSED: return _T("SERVICE_PAUSED"); default: return 0; } } @@ -1355,7 +1463,8 @@ unsigned long WINAPI service_control_handler(unsigned long control, unsigned lon ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime)); SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0); } - service->status.dwCurrentState = SERVICE_CONTINUE_PENDING; + /* We can't continue if the application is running! */ + if (! service->process_handle) service->status.dwCurrentState = SERVICE_CONTINUE_PENDING; service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN; log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0); SetServiceStatus(service->status_handle, &service->status); @@ -1368,6 +1477,12 @@ unsigned long WINAPI service_control_handler(unsigned long control, unsigned lon */ log_service_control(service->name, control, false); return ERROR_CALL_NOT_IMPLEMENTED; + + case NSSM_SERVICE_CONTROL_ROTATE: + log_service_control(service->name, control, true); + if (service->rotate_stdout_online == NSSM_ROTATE_ONLINE) service->rotate_stdout_online = NSSM_ROTATE_ONLINE_ASAP; + if (service->rotate_stderr_online == NSSM_ROTATE_ONLINE) service->rotate_stderr_online = NSSM_ROTATE_ONLINE_ASAP; + return NO_ERROR; } /* Unknown control */ @@ -1408,22 +1523,21 @@ int start_service(nssm_service_t *service) { throttle_restart(service); + /* Set the environment. */ + if (service->env) duplicate_environment(service->env); + if (service->env_extra) set_environment_block(service->env_extra); + bool inherit_handles = false; if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true; unsigned long flags = service->priority & priority_mask(); + if (service->stdin_pipe) flags |= DETACHED_PROCESS; if (service->affinity) flags |= CREATE_SUSPENDED; -#ifdef UNICODE - flags |= CREATE_UNICODE_ENVIRONMENT; -#endif - if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) { + if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, 0, service->dir, &si, &pi)) { unsigned long exitcode = 3; unsigned long error = GetLastError(); - if (error == ERROR_INVALID_PARAMETER && service->env) { - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0); - if (test_environment(service->env)) exitcode = 4; - } - else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0); + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0); close_output_handles(&si); + duplicate_environment(service->initial_env); return stop_service(service, exitcode, true, true); } service->process_handle = pi.hProcess; @@ -1433,6 +1547,9 @@ int start_service(nssm_service_t *service) { close_output_handles(&si); + /* Restore our environment. */ + duplicate_environment(service->initial_env); + if (service->affinity) { /* We are explicitly storing service->affinity as a 64-bit unsigned integer @@ -1503,6 +1620,12 @@ int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, UnregisterWait(service->wait_handle); service->wait_handle = 0; } + if (service->stdin_pipe) { + CloseHandle(service->stdin_pipe); + service->stdin_pipe = 0; + } + + service->rotate_stdout_online = service->rotate_stderr_online = NSSM_ROTATE_OFFLINE; if (default_action && ! exitcode && ! graceful) { log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_GRACEFUL_SUICIDE, service->name, service->exe, exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_REALLY], 0); @@ -1551,6 +1674,8 @@ void CALLBACK end_service(void *arg, unsigned char why) { service->stopping = true; + service->rotate_stdout_online = service->rotate_stderr_online = NSSM_ROTATE_OFFLINE; + /* Check exit code */ unsigned long exitcode = 0; TCHAR code[16];