4 bool use_critical_section;
\r
6 extern imports_t imports;
\r
7 extern settings_t settings[];
\r
9 const TCHAR *exit_action_strings[] = { _T("Restart"), _T("Ignore"), _T("Exit"), _T("Suicide"), 0 };
\r
10 const TCHAR *startup_strings[] = { _T("SERVICE_AUTO_START"), _T("SERVICE_DELAYED_AUTO_START"), _T("SERVICE_DEMAND_START"), _T("SERVICE_DISABLED"), 0 };
\r
11 const TCHAR *priority_strings[] = { _T("REALTIME_PRIORITY_CLASS"), _T("HIGH_PRIORITY_CLASS"), _T("ABOVE_NORMAL_PRIORITY_CLASS"), _T("NORMAL_PRIORITY_CLASS"), _T("BELOW_NORMAL_PRIORITY_CLASS"), _T("IDLE_PRIORITY_CLASS"), 0 };
\r
19 Check the status in response to a control.
\r
20 Returns: 1 if the status is expected, eg STOP following CONTROL_STOP.
\r
21 0 if the status is desired, eg STOPPED following CONTROL_STOP.
\r
22 -1 if the status is undesired, eg STOPPED following CONTROL_START.
\r
24 static inline int service_control_response(unsigned long control, unsigned long status) {
\r
26 case NSSM_SERVICE_CONTROL_START:
\r
28 case SERVICE_START_PENDING:
\r
31 case SERVICE_RUNNING:
\r
38 case SERVICE_CONTROL_STOP:
\r
39 case SERVICE_CONTROL_SHUTDOWN:
\r
41 case SERVICE_STOP_PENDING:
\r
44 case SERVICE_STOPPED:
\r
51 case SERVICE_CONTROL_PAUSE:
\r
53 case SERVICE_PAUSE_PENDING:
\r
56 case SERVICE_PAUSED:
\r
63 case SERVICE_CONTROL_CONTINUE:
\r
65 case SERVICE_CONTINUE_PENDING:
\r
68 case SERVICE_RUNNING:
\r
75 case SERVICE_CONTROL_INTERROGATE:
\r
82 static inline int await_service_control_response(unsigned long control, SC_HANDLE service_handle, SERVICE_STATUS *service_status, unsigned long initial_status) {
\r
84 while (QueryServiceStatus(service_handle, service_status)) {
\r
85 int response = service_control_response(control, service_status->dwCurrentState);
\r
86 /* Alas we can't WaitForSingleObject() on an SC_HANDLE. */
\r
87 if (! response) return response;
\r
88 if (response > 0 || service_status->dwCurrentState == initial_status) {
\r
89 if (++tries > 10) return response;
\r
92 else return response;
\r
97 int affinity_mask_to_string(__int64 mask, TCHAR **string) {
\r
98 if (! string) return 1;
\r
106 /* SetProcessAffinityMask() accepts a mask of up to 64 processors. */
\r
108 for (n = 0; n < _countof(set); n++) set[n].first = set[n].last = -1;
\r
110 for (i = 0, n = 0; i < _countof(set); i++) {
\r
111 if (mask & (1LL << i)) {
\r
112 if (set[n].first == -1) set[n].first = set[n].last = (int) i;
\r
113 else if (set[n].last == (int) i - 1) set[n].last = (int) i;
\r
116 set[n].first = set[n].last = (int) i;
\r
121 /* Worst case is 2x2 characters for first and last CPU plus - and/or , */
\r
122 size_t len = (size_t) (n + 1) * 6;
\r
123 *string = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * sizeof(TCHAR));
\r
124 if (! string) return 2;
\r
128 for (i = 0; i <= n; i++) {
\r
129 if (i) (*string)[s++] = _T(',');
\r
130 ret = _sntprintf_s(*string + s, 3, _TRUNCATE, _T("%u"), set[i].first);
\r
132 HeapFree(GetProcessHeap(), 0, *string);
\r
137 if (set[i].last != set[i].first) {
\r
138 ret =_sntprintf_s(*string + s, 4, _TRUNCATE, _T("%c%u"), (set[i].last == set[i].first + 1) ? _T(',') : _T('-'), set[i].last);
\r
140 HeapFree(GetProcessHeap(), 0, *string);
\r
151 int affinity_string_to_mask(TCHAR *string, __int64 *mask) {
\r
152 if (! mask) return 1;
\r
155 if (! string) return 0;
\r
164 unsigned long number;
\r
166 for (n = 0; n < _countof(set); n++) set[n].first = set[n].last = -1;
\r
170 ret = str_number(s, &number, &end);
\r
172 if (ret == 0 || ret == 2) {
\r
173 if (number >= _countof(set)) return 2;
\r
174 set[n].first = set[n].last = (int) number;
\r
186 if (! *(++s)) return 3;
\r
187 ret = str_number(s, &number, &end);
\r
188 if (ret == 0 || ret == 2) {
\r
190 if (! *s || *s == _T(',')) {
\r
191 set[n].last = (int) number;
\r
208 for (i = 0; i <= n; i++) {
\r
209 for (int j = set[i].first; j <= set[i].last; j++) (__int64) *mask |= (1LL << (__int64) j);
\r
215 inline unsigned long priority_mask() {
\r
216 return REALTIME_PRIORITY_CLASS | HIGH_PRIORITY_CLASS | ABOVE_NORMAL_PRIORITY_CLASS | NORMAL_PRIORITY_CLASS | BELOW_NORMAL_PRIORITY_CLASS | IDLE_PRIORITY_CLASS;
\r
219 int priority_constant_to_index(unsigned long constant) {
\r
220 switch (constant & priority_mask()) {
\r
221 case REALTIME_PRIORITY_CLASS: return NSSM_REALTIME_PRIORITY;
\r
222 case HIGH_PRIORITY_CLASS: return NSSM_HIGH_PRIORITY;
\r
223 case ABOVE_NORMAL_PRIORITY_CLASS: return NSSM_ABOVE_NORMAL_PRIORITY;
\r
224 case BELOW_NORMAL_PRIORITY_CLASS: return NSSM_BELOW_NORMAL_PRIORITY;
\r
225 case IDLE_PRIORITY_CLASS: return NSSM_IDLE_PRIORITY;
\r
227 return NSSM_NORMAL_PRIORITY;
\r
230 unsigned long priority_index_to_constant(int index) {
\r
232 case NSSM_REALTIME_PRIORITY: return REALTIME_PRIORITY_CLASS;
\r
233 case NSSM_HIGH_PRIORITY: return HIGH_PRIORITY_CLASS;
\r
234 case NSSM_ABOVE_NORMAL_PRIORITY: return ABOVE_NORMAL_PRIORITY_CLASS;
\r
235 case NSSM_BELOW_NORMAL_PRIORITY: return BELOW_NORMAL_PRIORITY_CLASS;
\r
236 case NSSM_IDLE_PRIORITY: return IDLE_PRIORITY_CLASS;
\r
238 return NORMAL_PRIORITY_CLASS;
\r
241 static inline unsigned long throttle_milliseconds(unsigned long throttle) {
\r
242 /* pow() operates on doubles. */
\r
243 unsigned long ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;
\r
248 Wrapper to be called in a new thread so that we can acknowledge a STOP
\r
249 control immediately.
\r
251 static unsigned long WINAPI shutdown_service(void *arg) {
\r
252 return stop_service((nssm_service_t *) arg, 0, true, true);
\r
255 /* Connect to the service manager */
\r
256 SC_HANDLE open_service_manager(unsigned long access) {
\r
257 SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, access);
\r
259 if (is_admin) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENSCMANAGER_FAILED, 0);
\r
266 /* Open a service by name or display name. */
\r
267 SC_HANDLE open_service(SC_HANDLE services, TCHAR *service_name, unsigned long access, TCHAR *canonical_name, unsigned long canonical_namelen) {
\r
268 SC_HANDLE service_handle = OpenService(services, service_name, access);
\r
269 if (service_handle) {
\r
270 if (canonical_name && canonical_name != service_name) {
\r
271 TCHAR displayname[SERVICE_NAME_LENGTH];
\r
272 unsigned long displayname_len = (unsigned long) _countof(displayname);
\r
273 GetServiceDisplayName(services, service_name, displayname, &displayname_len);
\r
274 unsigned long keyname_len = canonical_namelen;
\r
275 GetServiceKeyName(services, displayname, canonical_name, &keyname_len);
\r
277 return service_handle;
\r
280 unsigned long error = GetLastError();
\r
281 if (error != ERROR_SERVICE_DOES_NOT_EXIST) {
\r
282 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED, error_string(GetLastError()));
\r
286 /* We can't look for a display name because there's no buffer to store it. */
\r
287 if (! canonical_name) {
\r
288 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED, error_string(GetLastError()));
\r
292 unsigned long bufsize, required, count, i;
\r
293 unsigned long resume = 0;
\r
294 EnumServicesStatus(services, SERVICE_DRIVER | SERVICE_FILE_SYSTEM_DRIVER | SERVICE_KERNEL_DRIVER | SERVICE_WIN32, SERVICE_STATE_ALL, 0, 0, &required, &count, &resume);
\r
295 error = GetLastError();
\r
296 if (error != ERROR_MORE_DATA) {
\r
297 print_message(stderr, NSSM_MESSAGE_ENUMSERVICESSTATUS_FAILED, error_string(GetLastError()));
\r
301 ENUM_SERVICE_STATUS *status = (ENUM_SERVICE_STATUS *) HeapAlloc(GetProcessHeap(), 0, required);
\r
303 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("ENUM_SERVICE_STATUS"), _T("open_service()"));
\r
307 bufsize = required;
\r
310 EnumServicesStatus() returns:
\r
311 1 when it retrieved data and there's no more data to come.
\r
312 0 and sets last error to ERROR_MORE_DATA when it retrieved data and
\r
313 there's more data to come.
\r
314 0 and sets last error to something else on error.
\r
316 int ret = EnumServicesStatus(services, SERVICE_DRIVER | SERVICE_FILE_SYSTEM_DRIVER | SERVICE_KERNEL_DRIVER | SERVICE_WIN32, SERVICE_STATE_ALL, status, bufsize, &required, &count, &resume);
\r
318 error = GetLastError();
\r
319 if (error != ERROR_MORE_DATA) {
\r
320 HeapFree(GetProcessHeap(), 0, status);
\r
321 print_message(stderr, NSSM_MESSAGE_ENUMSERVICESSTATUS_FAILED, error_string(GetLastError()));
\r
326 for (i = 0; i < count; i++) {
\r
327 if (str_equiv(status[i].lpDisplayName, service_name)) {
\r
328 if (_sntprintf_s(canonical_name, canonical_namelen, _TRUNCATE, _T("%s"), status[i].lpServiceName) < 0) {
\r
329 HeapFree(GetProcessHeap(), 0, status);
\r
330 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canonical_name"), _T("open_service()"));
\r
334 HeapFree(GetProcessHeap(), 0, status);
\r
335 return open_service(services, canonical_name, access, 0, 0);
\r
342 /* Recurse so we can get an error message. */
\r
343 return open_service(services, service_name, access, 0, 0);
\r
346 QUERY_SERVICE_CONFIG *query_service_config(const TCHAR *service_name, SC_HANDLE service_handle) {
\r
347 QUERY_SERVICE_CONFIG *qsc;
\r
348 unsigned long bufsize;
\r
349 unsigned long error;
\r
351 QueryServiceConfig(service_handle, 0, 0, &bufsize);
\r
352 error = GetLastError();
\r
353 if (error == ERROR_INSUFFICIENT_BUFFER) {
\r
354 qsc = (QUERY_SERVICE_CONFIG *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufsize);
\r
356 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("QUERY_SERVICE_CONFIG"), _T("query_service_config()"), 0);
\r
361 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service_name, error_string(error), 0);
\r
365 if (! QueryServiceConfig(service_handle, qsc, bufsize, &bufsize)) {
\r
366 HeapFree(GetProcessHeap(), 0, qsc);
\r
367 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service_name, error_string(GetLastError()), 0);
\r
374 int set_service_description(const TCHAR *service_name, SC_HANDLE service_handle, TCHAR *buffer) {
\r
375 SERVICE_DESCRIPTION description;
\r
376 ZeroMemory(&description, sizeof(description));
\r
378 lpDescription must be NULL if we aren't changing, the new description
\r
381 if (buffer && buffer[0]) description.lpDescription = buffer;
\r
382 else description.lpDescription = _T("");
\r
384 if (ChangeServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, &description)) return 0;
\r
386 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DESCRIPTION_FAILED, service_name, error_string(GetLastError()), 0);
\r
390 int get_service_description(const TCHAR *service_name, SC_HANDLE service_handle, unsigned long len, TCHAR *buffer) {
\r
391 if (! buffer) return 1;
\r
393 unsigned long bufsize;
\r
394 QueryServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, 0, 0, &bufsize);
\r
395 unsigned long error = GetLastError();
\r
396 if (error == ERROR_INSUFFICIENT_BUFFER) {
\r
397 SERVICE_DESCRIPTION *description = (SERVICE_DESCRIPTION *) HeapAlloc(GetProcessHeap(), 0, bufsize);
\r
398 if (! description) {
\r
399 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_CONFIG_DESCRIPTION"), _T("get_service_description()"));
\r
403 if (QueryServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, (unsigned char *) description, bufsize, &bufsize)) {
\r
404 if (description->lpDescription) _sntprintf_s(buffer, len, _TRUNCATE, _T("%s"), description->lpDescription);
\r
405 else ZeroMemory(buffer, len * sizeof(TCHAR));
\r
406 HeapFree(GetProcessHeap(), 0, description);
\r
410 HeapFree(GetProcessHeap(), 0, description);
\r
411 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DESCRIPTION"), error_string(error));
\r
416 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DESCRIPTION"), error_string(error));
\r
423 int get_service_startup(const TCHAR *service_name, SC_HANDLE service_handle, const QUERY_SERVICE_CONFIG *qsc, unsigned long *startup) {
\r
424 if (! qsc) return 1;
\r
426 switch (qsc->dwStartType) {
\r
427 case SERVICE_DEMAND_START: *startup = NSSM_STARTUP_MANUAL; break;
\r
428 case SERVICE_DISABLED: *startup = NSSM_STARTUP_DISABLED; break;
\r
429 default: *startup = NSSM_STARTUP_AUTOMATIC;
\r
432 if (*startup != NSSM_STARTUP_AUTOMATIC) return 0;
\r
434 /* Check for delayed start. */
\r
435 unsigned long bufsize;
\r
436 unsigned long error;
\r
437 QueryServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, 0, 0, &bufsize);
\r
438 error = GetLastError();
\r
439 if (error == ERROR_INSUFFICIENT_BUFFER) {
\r
440 SERVICE_DELAYED_AUTO_START_INFO *info = (SERVICE_DELAYED_AUTO_START_INFO *) HeapAlloc(GetProcessHeap(), 0, bufsize);
\r
442 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_DELAYED_AUTO_START_INFO"), _T("get_service_startup()"));
\r
446 if (QueryServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, (unsigned char *) info, bufsize, &bufsize)) {
\r
447 if (info->fDelayedAutostart) *startup = NSSM_STARTUP_DELAYED;
\r
448 HeapFree(GetProcessHeap(), 0, info);
\r
452 error = GetLastError();
\r
453 if (error != ERROR_INVALID_LEVEL) {
\r
454 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));
\r
459 else if (error != ERROR_INVALID_LEVEL) {
\r
460 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_DELAYED_AUTO_START_INFO"), error_string(error));
\r
467 int get_service_username(const TCHAR *service_name, const QUERY_SERVICE_CONFIG *qsc, TCHAR **username, size_t *usernamelen) {
\r
468 if (! username) return 1;
\r
469 if (! usernamelen) return 1;
\r
474 if (! qsc) return 1;
\r
476 if (qsc->lpServiceStartName[0]) {
\r
477 if (is_localsystem(qsc->lpServiceStartName)) return 0;
\r
479 size_t len = _tcslen(qsc->lpServiceStartName);
\r
480 *username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(TCHAR));
\r
482 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("username"), _T("get_service_username()"));
\r
486 memmove(*username, qsc->lpServiceStartName, (len + 1) * sizeof(TCHAR));
\r
487 *usernamelen = len;
\r
493 /* Set default values which aren't zero. */
\r
494 void set_nssm_service_defaults(nssm_service_t *service) {
\r
495 if (! service) return;
\r
497 service->type = SERVICE_WIN32_OWN_PROCESS;
\r
498 service->priority = NORMAL_PRIORITY_CLASS;
\r
499 service->stdin_sharing = NSSM_STDIN_SHARING;
\r
500 service->stdin_disposition = NSSM_STDIN_DISPOSITION;
\r
501 service->stdin_flags = NSSM_STDIN_FLAGS;
\r
502 service->stdout_sharing = NSSM_STDOUT_SHARING;
\r
503 service->stdout_disposition = NSSM_STDOUT_DISPOSITION;
\r
504 service->stdout_flags = NSSM_STDOUT_FLAGS;
\r
505 service->stderr_sharing = NSSM_STDERR_SHARING;
\r
506 service->stderr_disposition = NSSM_STDERR_DISPOSITION;
\r
507 service->stderr_flags = NSSM_STDERR_FLAGS;
\r
508 service->throttle_delay = NSSM_RESET_THROTTLE_RESTART;
\r
509 service->stop_method = ~0;
\r
510 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
511 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
512 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
515 /* Allocate and zero memory for a service. */
\r
516 nssm_service_t *alloc_nssm_service() {
\r
517 nssm_service_t *service = (nssm_service_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(nssm_service_t));
\r
518 if (! service) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("alloc_nssm_service()"), 0);
\r
522 /* Free memory for a service. */
\r
523 void cleanup_nssm_service(nssm_service_t *service) {
\r
524 if (! service) return;
\r
525 if (service->username) HeapFree(GetProcessHeap(), 0, service->username);
\r
526 if (service->password) {
\r
527 SecureZeroMemory(service->password, service->passwordlen);
\r
528 HeapFree(GetProcessHeap(), 0, service->password);
\r
530 if (service->env) HeapFree(GetProcessHeap(), 0, service->env);
\r
531 if (service->env_extra) HeapFree(GetProcessHeap(), 0, service->env_extra);
\r
532 if (service->handle) CloseHandle(service->handle);
\r
533 if (service->process_handle) CloseHandle(service->process_handle);
\r
534 if (service->wait_handle) UnregisterWait(service->process_handle);
\r
535 if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);
\r
536 if (service->throttle_timer) CloseHandle(service->throttle_timer);
\r
537 if (service->initial_env) FreeEnvironmentStrings(service->initial_env);
\r
538 HeapFree(GetProcessHeap(), 0, service);
\r
541 /* About to install the service */
\r
542 int pre_install_service(int argc, TCHAR **argv) {
\r
543 nssm_service_t *service = alloc_nssm_service();
\r
544 set_nssm_service_defaults(service);
\r
545 if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);
\r
547 /* Show the dialogue box if we didn't give the service name and path */
\r
548 if (argc < 2) return nssm_gui(IDD_INSTALL, service);
\r
551 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("service"), _T("pre_install_service()"));
\r
554 _sntprintf_s(service->exe, _countof(service->exe), _TRUNCATE, _T("%s"), argv[1]);
\r
556 /* Arguments are optional */
\r
557 size_t flagslen = 0;
\r
560 for (i = 2; i < argc; i++) flagslen += _tcslen(argv[i]) + 1;
\r
561 if (! flagslen) flagslen = 1;
\r
562 if (flagslen > _countof(service->flags)) {
\r
563 print_message(stderr, NSSM_MESSAGE_FLAGS_TOO_LONG);
\r
567 for (i = 2; i < argc; i++) {
\r
568 size_t len = _tcslen(argv[i]);
\r
569 memmove(service->flags + s, argv[i], len * sizeof(TCHAR));
\r
571 if (i < argc - 1) service->flags[s++] = _T(' ');
\r
574 /* Work out directory name */
\r
575 _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe);
\r
576 strip_basename(service->dir);
\r
578 int ret = install_service(service);
\r
579 cleanup_nssm_service(service);
\r
583 /* About to edit the service. */
\r
584 int pre_edit_service(int argc, TCHAR **argv) {
\r
585 /* Require service name. */
\r
586 if (argc < 2) return usage(1);
\r
588 /* Are we editing on the command line? */
\r
589 enum { MODE_EDITING, MODE_GETTING, MODE_SETTING, MODE_RESETTING } mode = MODE_EDITING;
\r
590 const TCHAR *verb = argv[0];
\r
591 const TCHAR *service_name = argv[1];
\r
592 bool getting = false;
\r
593 bool unsetting = false;
\r
595 /* Minimum number of arguments. */
\r
597 /* Index of first value. */
\r
600 if (str_equiv(verb, _T("get"))) {
\r
602 mode = MODE_GETTING;
\r
604 else if (str_equiv(verb, _T("set"))) {
\r
606 mode = MODE_SETTING;
\r
608 else if (str_equiv(verb, _T("reset")) || str_equiv(verb, _T("unset"))) {
\r
610 mode = MODE_RESETTING;
\r
612 if (argc < mandatory) return usage(1);
\r
614 const TCHAR *parameter = 0;
\r
615 settings_t *setting = 0;
\r
618 /* Validate the parameter. */
\r
619 if (mandatory > 2) {
\r
620 bool additional_mandatory = false;
\r
622 parameter = argv[2];
\r
623 for (i = 0; settings[i].name; i++) {
\r
624 setting = &settings[i];
\r
625 if (! str_equiv(setting->name, parameter)) continue;
\r
626 if (((setting->additional & ADDITIONAL_GETTING) && mode == MODE_GETTING) || ((setting->additional & ADDITIONAL_SETTING) && mode == MODE_SETTING) || ((setting->additional & ADDITIONAL_RESETTING) && mode == MODE_RESETTING)) {
\r
627 additional_mandatory = true;
\r
632 if (! settings[i].name) {
\r
633 print_message(stderr, NSSM_MESSAGE_INVALID_PARAMETER, parameter);
\r
634 for (i = 0; settings[i].name; i++) _ftprintf(stderr, _T("%s\n"), settings[i].name);
\r
639 if (additional_mandatory) {
\r
640 if (argc < mandatory) {
\r
641 print_message(stderr, NSSM_MESSAGE_MISSING_SUBPARAMETER, parameter);
\r
644 additional = argv[3];
\r
647 else if (str_equiv(setting->name, NSSM_NATIVE_OBJECTNAME) && mode == MODE_SETTING) {
\r
648 additional = argv[3];
\r
652 additional = argv[remainder];
\r
653 if (argc < mandatory) return usage(1);
\r
657 nssm_service_t *service = alloc_nssm_service();
\r
658 _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), service_name);
\r
660 /* Open service manager */
\r
661 SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);
\r
663 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
667 /* Try to open the service */
\r
668 unsigned long access = SERVICE_QUERY_CONFIG;
\r
669 if (mode != MODE_GETTING) access |= SERVICE_CHANGE_CONFIG;
\r
670 service->handle = open_service(services, service->name, access, service->name, _countof(service->name));
\r
671 if (! service->handle) {
\r
672 CloseServiceHandle(services);
\r
676 /* Get system details. */
\r
677 QUERY_SERVICE_CONFIG *qsc = query_service_config(service->name, service->handle);
\r
679 CloseHandle(service->handle);
\r
680 CloseServiceHandle(services);
\r
684 service->type = qsc->dwServiceType;
\r
685 if (! (service->type & SERVICE_WIN32_OWN_PROCESS)) {
\r
686 if (mode != MODE_GETTING) {
\r
687 HeapFree(GetProcessHeap(), 0, qsc);
\r
688 CloseHandle(service->handle);
\r
689 CloseServiceHandle(services);
\r
690 print_message(stderr, NSSM_MESSAGE_CANNOT_EDIT, service->name, NSSM_WIN32_OWN_PROCESS, 0);
\r
695 if (get_service_startup(service->name, service->handle, qsc, &service->startup)) {
\r
696 if (mode != MODE_GETTING) {
\r
697 HeapFree(GetProcessHeap(), 0, qsc);
\r
698 CloseHandle(service->handle);
\r
699 CloseServiceHandle(services);
\r
704 if (get_service_username(service->name, qsc, &service->username, &service->usernamelen)) {
\r
705 if (mode != MODE_GETTING) {
\r
706 HeapFree(GetProcessHeap(), 0, qsc);
\r
707 CloseHandle(service->handle);
\r
708 CloseServiceHandle(services);
\r
713 _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), qsc->lpDisplayName);
\r
715 /* Get the canonical service name. We open it case insensitively. */
\r
716 unsigned long bufsize = _countof(service->name);
\r
717 GetServiceKeyName(services, service->displayname, service->name, &bufsize);
\r
719 /* Remember the executable in case it isn't NSSM. */
\r
720 _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), qsc->lpBinaryPathName);
\r
721 HeapFree(GetProcessHeap(), 0, qsc);
\r
723 /* Get extended system details. */
\r
724 if (get_service_description(service->name, service->handle, _countof(service->description), service->description)) {
\r
725 if (mode != MODE_GETTING) {
\r
726 CloseHandle(service->handle);
\r
727 CloseServiceHandle(services);
\r
732 /* Get NSSM details. */
\r
733 get_parameters(service, 0);
\r
735 CloseServiceHandle(services);
\r
737 if (! service->exe[0]) {
\r
738 service->native = true;
\r
739 if (mode != MODE_GETTING) print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE, service->name, NSSM, service->image);
\r
742 /* Editing with the GUI. */
\r
743 if (mode == MODE_EDITING) {
\r
744 nssm_gui(IDD_EDIT, service);
\r
748 /* Trying to manage App* parameters for a non-NSSM service. */
\r
749 if (! setting->native && service->native) {
\r
750 CloseHandle(service->handle);
\r
751 print_message(stderr, NSSM_MESSAGE_NATIVE_PARAMETER, setting->name, NSSM);
\r
759 if (mode == MODE_GETTING) {
\r
760 if (! service->native) {
\r
761 key = open_registry(service->name, KEY_READ);
\r
762 if (! key) return 4;
\r
765 if (setting->native) ret = get_setting(service->name, service->handle, setting, &value, additional);
\r
766 else ret = get_setting(service->name, key, setting, &value, additional);
\r
768 CloseHandle(service->handle);
\r
772 switch (setting->type) {
\r
773 case REG_EXPAND_SZ:
\r
776 _tprintf(_T("%s\n"), value.string ? value.string : _T(""));
\r
777 HeapFree(GetProcessHeap(), 0, value.string);
\r
781 _tprintf(_T("%u\n"), value.numeric);
\r
785 if (! service->native) RegCloseKey(key);
\r
786 CloseHandle(service->handle);
\r
790 /* Build the value. */
\r
791 if (mode == MODE_RESETTING) {
\r
792 /* Unset the parameter. */
\r
795 else if (remainder == argc) {
\r
799 /* Set the parameter. */
\r
801 size_t delimiterlen = (setting->additional & ADDITIONAL_CRLF) ? 2 : 1;
\r
802 for (i = remainder; i < argc; i++) len += _tcslen(argv[i]) + delimiterlen;
\r
805 value.string = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));
\r
806 if (! value.string) {
\r
807 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("value"), _T("edit_service()"));
\r
808 CloseHandle(service->handle);
\r
813 for (i = remainder; i < argc; i++) {
\r
814 size_t len = _tcslen(argv[i]);
\r
815 memmove(value.string + s, argv[i], len * sizeof(TCHAR));
\r
817 if (i < argc - 1) {
\r
818 if (setting->additional & ADDITIONAL_CRLF) {
\r
819 value.string[s++] = _T('\r');
\r
820 value.string[s++] = _T('\n');
\r
822 else value.string[s++] = _T(' ');
\r
825 value.string[s] = _T('\0');
\r
828 if (! service->native) {
\r
829 key = open_registry(service->name, KEY_WRITE);
\r
831 if (value.string) HeapFree(GetProcessHeap(), 0, value.string);
\r
836 if (setting->native) ret = set_setting(service->name, service->handle, setting, &value, additional);
\r
837 else ret = set_setting(service->name, key, setting, &value, additional);
\r
838 if (value.string) HeapFree(GetProcessHeap(), 0, value.string);
\r
840 if (! service->native) RegCloseKey(key);
\r
841 CloseHandle(service->handle);
\r
845 if (! service->native) RegCloseKey(key);
\r
846 CloseHandle(service->handle);
\r
851 /* About to remove the service */
\r
852 int pre_remove_service(int argc, TCHAR **argv) {
\r
853 nssm_service_t *service = alloc_nssm_service();
\r
854 set_nssm_service_defaults(service);
\r
855 if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);
\r
857 /* Show dialogue box if we didn't pass service name and "confirm" */
\r
858 if (argc < 2) return nssm_gui(IDD_REMOVE, service);
\r
859 if (str_equiv(argv[1], _T("confirm"))) {
\r
860 int ret = remove_service(service);
\r
861 cleanup_nssm_service(service);
\r
864 print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);
\r
868 /* Install the service */
\r
869 int install_service(nssm_service_t *service) {
\r
870 if (! service) return 1;
\r
872 /* Open service manager */
\r
873 SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE);
\r
875 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
876 cleanup_nssm_service(service);
\r
880 /* Get path of this program */
\r
881 GetModuleFileName(0, service->image, _countof(service->image));
\r
883 /* Create the service - settings will be changed in edit_service() */
\r
884 service->handle = CreateService(services, service->name, service->name, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, service->image, 0, 0, 0, 0, 0);
\r
885 if (! service->handle) {
\r
886 print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED, error_string(GetLastError()));
\r
887 CloseServiceHandle(services);
\r
891 if (edit_service(service, false)) {
\r
892 DeleteService(service->handle);
\r
893 CloseServiceHandle(services);
\r
897 print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);
\r
900 CloseServiceHandle(services);
\r
905 /* Edit the service. */
\r
906 int edit_service(nssm_service_t *service, bool editing) {
\r
907 if (! service) return 1;
\r
910 The only two valid flags for service type are SERVICE_WIN32_OWN_PROCESS
\r
911 and SERVICE_INTERACTIVE_PROCESS.
\r
913 service->type &= SERVICE_INTERACTIVE_PROCESS;
\r
914 service->type |= SERVICE_WIN32_OWN_PROCESS;
\r
916 /* Startup type. */
\r
917 unsigned long startup;
\r
918 switch (service->startup) {
\r
919 case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;
\r
920 case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;
\r
921 default: startup = SERVICE_AUTO_START;
\r
924 /* Display name. */
\r
925 if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);
\r
928 Username must be NULL if we aren't changing or an account name.
\r
929 We must explicitly use LOCALSYSTEM to change it when we are editing.
\r
930 Password must be NULL if we aren't changing, a password or "".
\r
931 Empty passwords are valid but we won't allow them in the GUI.
\r
933 TCHAR *username = 0;
\r
934 TCHAR *password = 0;
\r
935 if (service->usernamelen) {
\r
936 username = service->username;
\r
937 if (service->passwordlen) password = service->password;
\r
938 else password = _T("");
\r
940 else if (editing) username = NSSM_LOCALSYSTEM_ACCOUNT;
\r
942 if (well_known_username(username)) password = _T("");
\r
944 if (grant_logon_as_service(username)) {
\r
945 print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);
\r
950 if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, service->displayname)) {
\r
951 print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
\r
955 if (service->description[0] || editing) {
\r
956 set_service_description(service->name, service->handle, service->description);
\r
959 SERVICE_DELAYED_AUTO_START_INFO delayed;
\r
960 ZeroMemory(&delayed, sizeof(delayed));
\r
961 if (service->startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;
\r
962 else delayed.fDelayedAutostart = 0;
\r
963 /* Delayed startup isn't supported until Vista. */
\r
964 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {
\r
965 unsigned long error = GetLastError();
\r
966 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
967 if (error != ERROR_INVALID_LEVEL) {
\r
968 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);
\r
972 /* Don't mess with parameters which aren't ours. */
\r
973 if (! service->native) {
\r
974 /* Now we need to put the parameters into the registry */
\r
975 if (create_parameters(service, editing)) {
\r
976 print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
\r
980 set_service_recovery(service);
\r
986 /* Control a service. */
\r
987 int control_service(unsigned long control, int argc, TCHAR **argv) {
\r
988 if (argc < 1) return usage(1);
\r
989 TCHAR *service_name = argv[0];
\r
990 TCHAR canonical_name[SERVICE_NAME_LENGTH];
\r
992 SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);
\r
994 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
998 unsigned long access = SERVICE_QUERY_STATUS;
\r
1000 case NSSM_SERVICE_CONTROL_START:
\r
1001 access |= SERVICE_START;
\r
1004 case SERVICE_CONTROL_CONTINUE:
\r
1005 case SERVICE_CONTROL_PAUSE:
\r
1006 access |= SERVICE_PAUSE_CONTINUE;
\r
1009 case SERVICE_CONTROL_STOP:
\r
1010 access |= SERVICE_STOP;
\r
1013 case NSSM_SERVICE_CONTROL_ROTATE:
\r
1014 access |= SERVICE_USER_DEFINED_CONTROL;
\r
1018 SC_HANDLE service_handle = open_service(services, service_name, access, canonical_name, _countof(canonical_name));
\r
1019 if (! service_handle) {
\r
1020 CloseServiceHandle(services);
\r
1025 unsigned long error;
\r
1026 SERVICE_STATUS service_status;
\r
1027 if (control == NSSM_SERVICE_CONTROL_START) {
\r
1028 unsigned long initial_status = SERVICE_STOPPED;
\r
1029 ret = StartService(service_handle, (unsigned long) argc, (const TCHAR **) argv);
\r
1030 error = GetLastError();
\r
1031 CloseServiceHandle(services);
\r
1033 if (error == ERROR_IO_PENDING) {
\r
1035 Older versions of Windows return immediately with ERROR_IO_PENDING
\r
1036 indicate that the operation is still in progress. Newer versions
\r
1037 will return it if there really is a delay.
\r
1040 error = ERROR_SUCCESS;
\r
1044 int response = await_service_control_response(control, service_handle, &service_status, initial_status);
\r
1045 CloseHandle(service_handle);
\r
1048 print_message(stderr, NSSM_MESSAGE_BAD_CONTROL_RESPONSE, canonical_name, service_status_text(service_status.dwCurrentState), service_control_text(control));
\r
1051 else _tprintf(_T("%s: %s: %s"), canonical_name, service_control_text(control), error_string(error));
\r
1055 CloseHandle(service_handle);
\r
1056 _ftprintf(stderr, _T("%s: %s: %s"), canonical_name, service_control_text(control), error_string(error));
\r
1060 else if (control == SERVICE_CONTROL_INTERROGATE) {
\r
1062 We could actually send an INTERROGATE control but that won't return
\r
1063 any information if the service is stopped and we don't care about
\r
1064 the extra details it might give us in any case. So we'll fake it.
\r
1066 ret = QueryServiceStatus(service_handle, &service_status);
\r
1067 error = GetLastError();
\r
1070 _tprintf(_T("%s\n"), service_status_text(service_status.dwCurrentState));
\r
1074 _ftprintf(stderr, _T("%s: %s\n"), canonical_name, error_string(error));
\r
1079 ret = ControlService(service_handle, control, &service_status);
\r
1080 unsigned long initial_status = service_status.dwCurrentState;
\r
1081 error = GetLastError();
\r
1082 CloseServiceHandle(services);
\r
1084 if (error == ERROR_IO_PENDING) {
\r
1086 error = ERROR_SUCCESS;
\r
1090 int response = await_service_control_response(control, service_handle, &service_status, initial_status);
\r
1091 CloseHandle(service_handle);
\r
1094 print_message(stderr, NSSM_MESSAGE_BAD_CONTROL_RESPONSE, canonical_name, service_status_text(service_status.dwCurrentState), service_control_text(control));
\r
1097 else _tprintf(_T("%s: %s: %s"), canonical_name, service_control_text(control), error_string(error));
\r
1101 CloseHandle(service_handle);
\r
1102 _ftprintf(stderr, _T("%s: %s: %s"), canonical_name, service_control_text(control), error_string(error));
\r
1103 if (error == ERROR_SERVICE_NOT_ACTIVE) {
\r
1104 if (control == SERVICE_CONTROL_SHUTDOWN || control == SERVICE_CONTROL_STOP) return 0;
\r
1111 /* Remove the service */
\r
1112 int remove_service(nssm_service_t *service) {
\r
1113 if (! service) return 1;
\r
1115 /* Open service manager */
\r
1116 SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);
\r
1118 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
1122 /* Try to open the service */
\r
1123 service->handle = open_service(services, service->name, DELETE, service->name, _countof(service->name));
\r
1124 if (! service->handle) {
\r
1125 CloseServiceHandle(services);
\r
1129 /* Get the canonical service name. We open it case insensitively. */
\r
1130 unsigned long bufsize = _countof(service->displayname);
\r
1131 GetServiceDisplayName(services, service->name, service->displayname, &bufsize);
\r
1132 bufsize = _countof(service->name);
\r
1133 GetServiceKeyName(services, service->displayname, service->name, &bufsize);
\r
1135 /* Try to delete the service */
\r
1136 if (! DeleteService(service->handle)) {
\r
1137 print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);
\r
1138 CloseServiceHandle(services);
\r
1143 CloseServiceHandle(services);
\r
1145 print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);
\r
1149 /* Service initialisation */
\r
1150 void WINAPI service_main(unsigned long argc, TCHAR **argv) {
\r
1151 nssm_service_t *service = alloc_nssm_service();
\r
1152 if (! service) return;
\r
1154 if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {
\r
1155 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);
\r
1159 /* We can use a condition variable in a critical section on Vista or later. */
\r
1160 if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;
\r
1161 else use_critical_section = false;
\r
1163 /* Initialise status */
\r
1164 ZeroMemory(&service->status, sizeof(service->status));
\r
1165 service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
\r
1166 service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
\r
1167 service->status.dwWin32ExitCode = NO_ERROR;
\r
1168 service->status.dwServiceSpecificExitCode = 0;
\r
1169 service->status.dwCheckPoint = 0;
\r
1170 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
1172 /* Signal we AREN'T running the server */
\r
1173 service->process_handle = 0;
\r
1176 /* Register control handler */
\r
1177 service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);
\r
1178 if (! service->status_handle) {
\r
1179 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);
\r
1183 log_service_control(service->name, 0, true);
\r
1185 service->status.dwCurrentState = SERVICE_START_PENDING;
\r
1186 service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;
\r
1187 SetServiceStatus(service->status_handle, &service->status);
\r
1190 /* Try to create the exit action parameters; we don't care if it fails */
\r
1191 create_exit_action(service->name, exit_action_strings[0], false);
\r
1193 SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT);
\r
1195 service->handle = open_service(services, service->name, SERVICE_CHANGE_CONFIG, 0, 0);
\r
1196 set_service_recovery(service);
\r
1197 CloseServiceHandle(services);
\r
1201 /* Used for signalling a resume if the service pauses when throttled. */
\r
1202 if (use_critical_section) {
\r
1203 InitializeCriticalSection(&service->throttle_section);
\r
1204 service->throttle_section_initialised = true;
\r
1207 service->throttle_timer = CreateWaitableTimer(0, 1, 0);
\r
1208 if (! service->throttle_timer) {
\r
1209 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);
\r
1213 /* Remember our initial environment. */
\r
1214 service->initial_env = GetEnvironmentStrings();
\r
1216 monitor_service(service);
\r
1219 /* Make sure service recovery actions are taken where necessary */
\r
1220 void set_service_recovery(nssm_service_t *service) {
\r
1221 SERVICE_FAILURE_ACTIONS_FLAG flag;
\r
1222 ZeroMemory(&flag, sizeof(flag));
\r
1223 flag.fFailureActionsOnNonCrashFailures = true;
\r
1225 /* This functionality was added in Vista so the call may fail */
\r
1226 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
\r
1227 unsigned long error = GetLastError();
\r
1228 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
1229 if (error != ERROR_INVALID_LEVEL) {
\r
1230 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);
\r
1235 int monitor_service(nssm_service_t *service) {
\r
1236 /* Set service status to started */
\r
1237 int ret = start_service(service);
\r
1240 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);
\r
1241 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);
\r
1244 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);
\r
1246 /* Monitor service */
\r
1247 if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
\r
1248 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);
\r
1254 TCHAR *service_control_text(unsigned long control) {
\r
1255 switch (control) {
\r
1256 /* HACK: there is no SERVICE_CONTROL_START constant */
\r
1257 case NSSM_SERVICE_CONTROL_START: return _T("START");
\r
1258 case SERVICE_CONTROL_STOP: return _T("STOP");
\r
1259 case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");
\r
1260 case SERVICE_CONTROL_PAUSE: return _T("PAUSE");
\r
1261 case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");
\r
1262 case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");
\r
1263 case NSSM_SERVICE_CONTROL_ROTATE: return _T("ROTATE");
\r
1264 default: return 0;
\r
1268 TCHAR *service_status_text(unsigned long status) {
\r
1270 case SERVICE_STOPPED: return _T("SERVICE_STOPPED");
\r
1271 case SERVICE_START_PENDING: return _T("SERVICE_START_PENDING");
\r
1272 case SERVICE_STOP_PENDING: return _T("SERVICE_STOP_PENDING");
\r
1273 case SERVICE_RUNNING: return _T("SERVICE_RUNNING");
\r
1274 case SERVICE_CONTINUE_PENDING: return _T("SERVICE_CONTINUE_PENDING");
\r
1275 case SERVICE_PAUSE_PENDING: return _T("SERVICE_PAUSE_PENDING");
\r
1276 case SERVICE_PAUSED: return _T("SERVICE_PAUSED");
\r
1277 default: return 0;
\r
1281 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {
\r
1282 TCHAR *text = service_control_text(control);
\r
1283 unsigned long event;
\r
1286 /* "0x" + 8 x hex + NULL */
\r
1287 text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));
\r
1289 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
1292 if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {
\r
1293 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
1294 HeapFree(GetProcessHeap(), 0, text);
\r
1298 event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;
\r
1300 else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;
\r
1301 else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;
\r
1303 log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);
\r
1305 if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {
\r
1306 HeapFree(GetProcessHeap(), 0, text);
\r
1310 /* Service control handler */
\r
1311 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {
\r
1312 nssm_service_t *service = (nssm_service_t *) context;
\r
1314 switch (control) {
\r
1315 case SERVICE_CONTROL_INTERROGATE:
\r
1316 /* We always keep the service status up-to-date so this is a no-op. */
\r
1319 case SERVICE_CONTROL_SHUTDOWN:
\r
1320 case SERVICE_CONTROL_STOP:
\r
1321 log_service_control(service->name, control, true);
\r
1323 We MUST acknowledge the stop request promptly but we're committed to
\r
1324 waiting for the application to exit. Spawn a new thread to wait
\r
1325 while we acknowledge the request.
\r
1327 if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {
\r
1328 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
\r
1331 We couldn't create a thread to tidy up so we'll have to force the tidyup
\r
1332 to complete in time in this thread.
\r
1334 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
1335 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
1336 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
1338 stop_service(service, 0, true, true);
\r
1342 case SERVICE_CONTROL_CONTINUE:
\r
1343 log_service_control(service->name, control, true);
\r
1344 service->throttle = 0;
\r
1345 if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);
\r
1347 if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
\r
1348 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
1349 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
1351 /* We can't continue if the application is running! */
\r
1352 if (! service->process_handle) service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;
\r
1353 service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;
\r
1354 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);
\r
1355 SetServiceStatus(service->status_handle, &service->status);
\r
1358 case SERVICE_CONTROL_PAUSE:
\r
1360 We don't accept pause messages but it isn't possible to register
\r
1361 only for continue messages so we have to handle this case.
\r
1363 log_service_control(service->name, control, false);
\r
1364 return ERROR_CALL_NOT_IMPLEMENTED;
\r
1366 case NSSM_SERVICE_CONTROL_ROTATE:
\r
1367 log_service_control(service->name, control, true);
\r
1368 if (service->rotate_stdout_online == NSSM_ROTATE_ONLINE) service->rotate_stdout_online = NSSM_ROTATE_ONLINE_ASAP;
\r
1369 if (service->rotate_stderr_online == NSSM_ROTATE_ONLINE) service->rotate_stderr_online = NSSM_ROTATE_ONLINE_ASAP;
\r
1373 /* Unknown control */
\r
1374 log_service_control(service->name, control, false);
\r
1375 return ERROR_CALL_NOT_IMPLEMENTED;
\r
1378 /* Start the service */
\r
1379 int start_service(nssm_service_t *service) {
\r
1380 service->stopping = false;
\r
1381 service->allow_restart = true;
\r
1383 if (service->process_handle) return 0;
\r
1385 /* Allocate a STARTUPINFO structure for a new process */
\r
1387 ZeroMemory(&si, sizeof(si));
\r
1388 si.cb = sizeof(si);
\r
1390 /* Allocate a PROCESSINFO structure for the process */
\r
1391 PROCESS_INFORMATION pi;
\r
1392 ZeroMemory(&pi, sizeof(pi));
\r
1394 /* Get startup parameters */
\r
1395 int ret = get_parameters(service, &si);
\r
1397 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);
\r
1398 return stop_service(service, 2, true, true);
\r
1401 /* Launch executable with arguments */
\r
1402 TCHAR cmd[CMD_LENGTH];
\r
1403 if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {
\r
1404 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);
\r
1405 return stop_service(service, 2, true, true);
\r
1408 throttle_restart(service);
\r
1410 /* Set the environment. */
\r
1411 if (service->env) duplicate_environment(service->env);
\r
1412 if (service->env_extra) set_environment_block(service->env_extra);
\r
1414 /* Set up I/O redirection. */
\r
1415 if (get_output_handles(service, &si)) {
\r
1416 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_OUTPUT_HANDLES_FAILED, service->name, 0);
\r
1417 if (! service->no_console) FreeConsole();
\r
1418 close_output_handles(&si);
\r
1419 return stop_service(service, 4, true, true);
\r
1422 bool inherit_handles = false;
\r
1423 if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;
\r
1424 unsigned long flags = service->priority & priority_mask();
\r
1425 if (service->affinity) flags |= CREATE_SUSPENDED;
\r
1426 if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, 0, service->dir, &si, &pi)) {
\r
1427 unsigned long exitcode = 3;
\r
1428 unsigned long error = GetLastError();
\r
1429 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);
\r
1430 close_output_handles(&si);
\r
1431 duplicate_environment(service->initial_env);
\r
1432 return stop_service(service, exitcode, true, true);
\r
1434 service->process_handle = pi.hProcess;
\r
1435 service->pid = pi.dwProcessId;
\r
1437 if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));
\r
1439 close_output_handles(&si);
\r
1441 if (! service->no_console) FreeConsole();
\r
1443 /* Restore our environment. */
\r
1444 duplicate_environment(service->initial_env);
\r
1446 if (service->affinity) {
\r
1448 We are explicitly storing service->affinity as a 64-bit unsigned integer
\r
1449 so that we can parse it regardless of whether we're running in 32-bit
\r
1450 or 64-bit mode. The arguments to SetProcessAffinityMask(), however, are
\r
1451 defined as type DWORD_PTR and hence limited to 32 bits on a 32-bit system
\r
1452 (or when running the 32-bit NSSM).
\r
1454 The result is a lot of seemingly-unnecessary casting throughout the code
\r
1455 and potentially confusion when we actually try to start the service.
\r
1456 Having said that, however, it's unlikely that we're actually going to
\r
1457 run in 32-bit mode on a system which has more than 32 CPUs so the
\r
1458 likelihood of seeing a confusing situation is somewhat diminished.
\r
1460 DWORD_PTR affinity, system_affinity;
\r
1462 if (GetProcessAffinityMask(service->process_handle, &affinity, &system_affinity)) affinity = service->affinity & system_affinity;
\r
1464 affinity = (DWORD_PTR) service->affinity;
\r
1465 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);
\r
1468 if (! SetProcessAffinityMask(service->process_handle, affinity)) {
\r
1469 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_SETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);
\r
1472 ResumeThread(pi.hThread);
\r
1476 Wait for a clean startup before changing the service status to RUNNING
\r
1477 but be mindful of the fact that we are blocking the service control manager
\r
1478 so abandon the wait before too much time has elapsed.
\r
1480 unsigned long delay = service->throttle_delay;
\r
1481 if (delay > NSSM_SERVICE_STATUS_DEADLINE) {
\r
1482 TCHAR delay_milliseconds[16];
\r
1483 _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);
\r
1484 TCHAR deadline_milliseconds[16];
\r
1485 _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);
\r
1486 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);
\r
1487 delay = NSSM_SERVICE_STATUS_DEADLINE;
\r
1489 unsigned long deadline = WaitForSingleObject(service->process_handle, delay);
\r
1491 /* Signal successful start */
\r
1492 service->status.dwCurrentState = SERVICE_RUNNING;
\r
1493 SetServiceStatus(service->status_handle, &service->status);
\r
1495 /* Continue waiting for a clean startup. */
\r
1496 if (deadline == WAIT_TIMEOUT) {
\r
1497 if (service->throttle_delay > delay) {
\r
1498 if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;
\r
1500 else service->throttle = 0;
\r
1503 /* Ensure the restart delay is always applied. */
\r
1504 if (service->restart_delay && ! service->throttle) service->throttle++;
\r
1509 /* Stop the service */
\r
1510 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {
\r
1511 service->allow_restart = false;
\r
1512 if (service->wait_handle) {
\r
1513 UnregisterWait(service->wait_handle);
\r
1514 service->wait_handle = 0;
\r
1517 service->rotate_stdout_online = service->rotate_stderr_online = NSSM_ROTATE_OFFLINE;
\r
1519 if (default_action && ! exitcode && ! graceful) {
\r
1520 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);
\r
1524 /* Signal we are stopping */
\r
1526 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
1527 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
1528 SetServiceStatus(service->status_handle, &service->status);
\r
1531 /* Nothing to do if service isn't running */
\r
1532 if (service->pid) {
\r
1533 /* Shut down service */
\r
1534 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);
\r
1535 kill_process(service, service->process_handle, service->pid, 0);
\r
1537 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);
\r
1539 end_service((void *) service, true);
\r
1541 /* Signal we stopped */
\r
1543 service->status.dwCurrentState = SERVICE_STOPPED;
\r
1545 service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
1546 service->status.dwServiceSpecificExitCode = exitcode;
\r
1549 service->status.dwWin32ExitCode = NO_ERROR;
\r
1550 service->status.dwServiceSpecificExitCode = 0;
\r
1552 SetServiceStatus(service->status_handle, &service->status);
\r
1558 /* Callback function triggered when the server exits */
\r
1559 void CALLBACK end_service(void *arg, unsigned char why) {
\r
1560 nssm_service_t *service = (nssm_service_t *) arg;
\r
1562 if (service->stopping) return;
\r
1564 service->stopping = true;
\r
1566 service->rotate_stdout_online = service->rotate_stderr_online = NSSM_ROTATE_OFFLINE;
\r
1568 /* Use now as a dummy exit time. */
\r
1569 GetSystemTimeAsFileTime(&service->exit_time);
\r
1571 /* Check exit code */
\r
1572 unsigned long exitcode = 0;
\r
1574 if (service->process_handle) {
\r
1575 GetExitCodeProcess(service->process_handle, &exitcode);
\r
1576 /* Check real exit time. */
\r
1577 if (exitcode != STILL_ACTIVE) get_process_exit_time(service->process_handle, &service->exit_time);
\r
1578 CloseHandle(service->process_handle);
\r
1581 service->process_handle = 0;
\r
1584 Log that the service ended BEFORE logging about killing the process
\r
1585 tree. See below for the possible values of the why argument.
\r
1588 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);
\r
1589 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);
\r
1593 if (exitcode == STILL_ACTIVE) exitcode = 0;
\r
1594 if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);
\r
1598 The why argument is true if our wait timed out or false otherwise.
\r
1599 Our wait is infinite so why will never be true when called by the system.
\r
1600 If it is indeed true, assume we were called from stop_service() because
\r
1601 this is a controlled shutdown, and don't take any restart action.
\r
1604 if (! service->allow_restart) return;
\r
1606 /* What action should we take? */
\r
1607 int action = NSSM_EXIT_RESTART;
\r
1608 TCHAR action_string[ACTION_LEN];
\r
1609 bool default_action;
\r
1610 if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {
\r
1611 for (int i = 0; exit_action_strings[i]; i++) {
\r
1612 if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {
\r
1620 /* Try to restart the service or return failure code to service manager */
\r
1621 case NSSM_EXIT_RESTART:
\r
1622 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);
\r
1623 while (monitor_service(service)) {
\r
1624 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);
\r
1629 /* Do nothing, just like srvany would */
\r
1630 case NSSM_EXIT_IGNORE:
\r
1631 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);
\r
1635 /* Tell the service manager we are finished */
\r
1636 case NSSM_EXIT_REALLY:
\r
1637 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);
\r
1638 stop_service(service, exitcode, true, default_action);
\r
1641 /* Fake a crash so pre-Vista service managers will run recovery actions. */
\r
1642 case NSSM_EXIT_UNCLEAN:
\r
1643 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);
\r
1644 stop_service(service, exitcode, false, default_action);
\r
1651 void throttle_restart(nssm_service_t *service) {
\r
1652 /* This can't be a restart if the service is already running. */
\r
1653 if (! service->throttle++) return;
\r
1656 unsigned long throttle_ms = throttle_milliseconds(service->throttle);
\r
1657 TCHAR threshold[8], milliseconds[8];
\r
1659 if (service->restart_delay > throttle_ms) ms = service->restart_delay;
\r
1660 else ms = throttle_ms;
\r
1662 if (service->throttle > 7) service->throttle = 8;
\r
1664 _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);
\r
1666 if (service->throttle == 1 && service->restart_delay > throttle_ms) log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESTART_DELAY, service->name, milliseconds, 0);
\r
1668 _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);
\r
1669 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);
\r
1672 if (use_critical_section) EnterCriticalSection(&service->throttle_section);
\r
1673 else if (service->throttle_timer) {
\r
1674 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
1675 service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);
\r
1676 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
1679 service->status.dwCurrentState = SERVICE_PAUSED;
\r
1680 SetServiceStatus(service->status_handle, &service->status);
\r
1682 if (use_critical_section) {
\r
1683 imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);
\r
1684 LeaveCriticalSection(&service->throttle_section);
\r
1687 if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);
\r
1693 When responding to a stop (or any other) request we need to set dwWaitHint to
\r
1694 the number of milliseconds we expect the operation to take, and optionally
\r
1695 increase dwCheckPoint. If dwWaitHint milliseconds elapses without the
\r
1696 operation completing or dwCheckPoint increasing, the system will consider the
\r
1697 service to be hung.
\r
1699 However the system will consider the service to be hung after 30000
\r
1700 milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not
\r
1701 changed. Therefore if we want to wait longer than that we must periodically
\r
1702 increase dwCheckPoint.
\r
1704 Furthermore, it will consider the service to be hung after 60000 milliseconds
\r
1705 regardless of the value of dwCheckPoint unless dwWaitHint is increased every
\r
1706 time dwCheckPoint is also increased.
\r
1708 Our strategy then is to retrieve the initial dwWaitHint and wait for
\r
1709 NSSM_SERVICE_STATUS_DEADLINE milliseconds. If the process is still running
\r
1710 and we haven't finished waiting we increment dwCheckPoint and add whichever is
\r
1711 smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to
\r
1714 Only doing both these things will prevent the system from killing the service.
\r
1716 Returns: 1 if the wait timed out.
\r
1717 0 if the wait completed.
\r
1720 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {
\r
1721 unsigned long interval;
\r
1722 unsigned long waithint;
\r
1723 unsigned long ret;
\r
1724 unsigned long waited;
\r
1725 TCHAR interval_milliseconds[16];
\r
1726 TCHAR timeout_milliseconds[16];
\r
1727 TCHAR waited_milliseconds[16];
\r
1728 TCHAR *function = function_name;
\r
1730 /* Add brackets to function name. */
\r
1731 size_t funclen = _tcslen(function_name) + 3;
\r
1732 TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));
\r
1734 if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;
\r
1737 _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);
\r
1739 waithint = service->status.dwWaitHint;
\r
1741 while (waited < timeout) {
\r
1742 interval = timeout - waited;
\r
1743 if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;
\r
1745 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
1746 service->status.dwWaitHint += interval;
\r
1747 service->status.dwCheckPoint++;
\r
1748 SetServiceStatus(service->status_handle, &service->status);
\r
1751 _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);
\r
1752 _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);
\r
1753 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
\r
1756 switch (WaitForSingleObject(service->process_handle, interval)) {
\r
1757 case WAIT_OBJECT_0:
\r
1761 case WAIT_TIMEOUT:
\r
1770 waited += interval;
\r
1774 if (func) HeapFree(GetProcessHeap(), 0, func);
\r