Fixed print_message() when out of memory.
[nssm.git] / service.cpp
1 #include "nssm.h"\r
2 \r
3 extern const TCHAR *exit_action_strings[];\r
4 \r
5 bool is_admin;\r
6 bool use_critical_section;\r
7 \r
8 extern imports_t imports;\r
9 \r
10 const TCHAR *exit_action_strings[] = { _T("Restart"), _T("Ignore"), _T("Exit"), _T("Suicide"), 0 };\r
11 \r
12 static inline int throttle_milliseconds(unsigned long throttle) {\r
13   /* pow() operates on doubles. */\r
14   int ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;\r
15   return ret * 1000;\r
16 }\r
17 \r
18 /*\r
19   Wrapper to be called in a new thread so that we can acknowledge a STOP\r
20   control immediately.\r
21 */\r
22 static unsigned long WINAPI shutdown_service(void *arg) {\r
23   return stop_service((nssm_service_t *) arg, 0, true, true);\r
24 }\r
25 \r
26 /* Connect to the service manager */\r
27 SC_HANDLE open_service_manager() {\r
28   SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);\r
29   if (! ret) {\r
30     if (is_admin) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENSCMANAGER_FAILED, 0);\r
31     return 0;\r
32   }\r
33 \r
34   return ret;\r
35 }\r
36 \r
37 /* Set default values which aren't zero. */\r
38 void set_nssm_service_defaults(nssm_service_t *service) {\r
39   if (! service) return;\r
40 \r
41   service->type = SERVICE_WIN32_OWN_PROCESS;\r
42   service->stdin_sharing = NSSM_STDIN_SHARING;\r
43   service->stdin_disposition = NSSM_STDIN_DISPOSITION;\r
44   service->stdin_flags = NSSM_STDIN_FLAGS;\r
45   service->stdout_sharing = NSSM_STDOUT_SHARING;\r
46   service->stdout_disposition = NSSM_STDOUT_DISPOSITION;\r
47   service->stdout_flags = NSSM_STDOUT_FLAGS;\r
48   service->stderr_sharing = NSSM_STDERR_SHARING;\r
49   service->stderr_disposition = NSSM_STDERR_DISPOSITION;\r
50   service->stderr_flags = NSSM_STDERR_FLAGS;\r
51   service->throttle_delay = NSSM_RESET_THROTTLE_RESTART;\r
52   service->stop_method = ~0;\r
53   service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
54   service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
55   service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
56 }\r
57 \r
58 /* Allocate and zero memory for a service. */\r
59 nssm_service_t *alloc_nssm_service() {\r
60   nssm_service_t *service = (nssm_service_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(nssm_service_t));\r
61   if (! service) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("alloc_nssm_service()"), 0);\r
62   return service;\r
63 }\r
64 \r
65 /* Free memory for a service. */\r
66 void cleanup_nssm_service(nssm_service_t *service) {\r
67   if (! service) return;\r
68   if (service->username) HeapFree(GetProcessHeap(), 0, service->username);\r
69   if (service->password) {\r
70     SecureZeroMemory(service->password, service->passwordlen);\r
71     HeapFree(GetProcessHeap(), 0, service->password);\r
72   }\r
73   if (service->env) HeapFree(GetProcessHeap(), 0, service->env);\r
74   if (service->env_extra) HeapFree(GetProcessHeap(), 0, service->env_extra);\r
75   if (service->handle) CloseServiceHandle(service->handle);\r
76   if (service->process_handle) CloseHandle(service->process_handle);\r
77   if (service->wait_handle) UnregisterWait(service->process_handle);\r
78   if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);\r
79   if (service->throttle_timer) CloseHandle(service->throttle_timer);\r
80   HeapFree(GetProcessHeap(), 0, service);\r
81 }\r
82 \r
83 /* About to install the service */\r
84 int pre_install_service(int argc, TCHAR **argv) {\r
85   nssm_service_t *service = alloc_nssm_service();\r
86   set_nssm_service_defaults(service);\r
87   if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);\r
88 \r
89   /* Show the dialogue box if we didn't give the service name and path */\r
90   if (argc < 2) return nssm_gui(IDD_INSTALL, service);\r
91 \r
92   if (! service) {\r
93     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("service"), _T("pre_install_service()"));\r
94     return 1;\r
95   }\r
96   _sntprintf_s(service->exe, _countof(service->exe), _TRUNCATE, _T("%s"), argv[1]);\r
97 \r
98   /* Arguments are optional */\r
99   size_t flagslen = 0;\r
100   size_t s = 0;\r
101   int i;\r
102   for (i = 2; i < argc; i++) flagslen += _tcslen(argv[i]) + 1;\r
103   if (! flagslen) flagslen = 1;\r
104   if (flagslen > _countof(service->flags)) {\r
105     print_message(stderr, NSSM_MESSAGE_FLAGS_TOO_LONG);\r
106     return 2;\r
107   }\r
108 \r
109   for (i = 2; i < argc; i++) {\r
110     size_t len = _tcslen(argv[i]);\r
111     memmove(service->flags + s, argv[i], len * sizeof(TCHAR));\r
112     s += len;\r
113     if (i < argc - 1) service->flags[s++] = _T(' ');\r
114   }\r
115 \r
116   /* Work out directory name */\r
117   _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe);\r
118   strip_basename(service->dir);\r
119 \r
120   int ret = install_service(service);\r
121   cleanup_nssm_service(service);\r
122   return ret;\r
123 }\r
124 \r
125 /* About to edit the service. */\r
126 int pre_edit_service(int argc, TCHAR **argv) {\r
127   /* Require service name. */\r
128   if (argc < 1) return usage(1);\r
129 \r
130   nssm_service_t *service = alloc_nssm_service();\r
131   _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);\r
132 \r
133   /* Open service manager */\r
134   SC_HANDLE services = open_service_manager();\r
135   if (! services) {\r
136     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
137     return 2;\r
138   }\r
139 \r
140   /* Try to open the service */\r
141   service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);\r
142   if (! service->handle) {\r
143     CloseServiceHandle(services);\r
144     print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);\r
145     return 3;\r
146   }\r
147 \r
148   /* Get system details. */\r
149   unsigned long bufsize;\r
150   unsigned long error;\r
151   QUERY_SERVICE_CONFIG *qsc;\r
152 \r
153   QueryServiceConfig(service->handle, 0, 0, &bufsize);\r
154   error = GetLastError();\r
155   if (error == ERROR_INSUFFICIENT_BUFFER) {\r
156     qsc = (QUERY_SERVICE_CONFIG *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufsize);\r
157     if (! qsc) {\r
158       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("QUERY_SERVICE_CONFIG"), _T("pre_edit_service()"), 0);\r
159       return 4;\r
160     }\r
161   }\r
162   else {\r
163     CloseHandle(service->handle);\r
164     CloseServiceHandle(services);\r
165     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service->name, error_string(error), 0);\r
166     return 4;\r
167   }\r
168 \r
169   if (! QueryServiceConfig(service->handle, qsc, bufsize, &bufsize)) {\r
170     HeapFree(GetProcessHeap(), 0, qsc);\r
171     CloseHandle(service->handle);\r
172     CloseServiceHandle(services);\r
173     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service->name, error_string(GetLastError()), 0);\r
174     return 4;\r
175   }\r
176 \r
177   service->type = qsc->dwServiceType;\r
178   if (! (service->type & SERVICE_WIN32_OWN_PROCESS)) {\r
179     HeapFree(GetProcessHeap(), 0, qsc);\r
180     CloseHandle(service->handle);\r
181     CloseServiceHandle(services);\r
182     print_message(stderr, NSSM_MESSAGE_CANNOT_EDIT, service->name, _T("SERVICE_WIN32_OWN_PROCESS"), 0);\r
183     return 3;\r
184   }\r
185 \r
186   switch (qsc->dwStartType) {\r
187     case SERVICE_DEMAND_START: service->startup = NSSM_STARTUP_MANUAL; break;\r
188     case SERVICE_DISABLED: service->startup = NSSM_STARTUP_DISABLED; break;\r
189     default: service->startup = NSSM_STARTUP_AUTOMATIC;\r
190   }\r
191   if (! str_equiv(qsc->lpServiceStartName, NSSM_LOCALSYSTEM_ACCOUNT)) {\r
192     size_t len = _tcslen(qsc->lpServiceStartName);\r
193     service->username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(TCHAR));\r
194     if (service->username) {\r
195       memmove(service->username, qsc->lpServiceStartName, (len + 1) * sizeof(TCHAR));\r
196       service->usernamelen = (unsigned long) len;\r
197     }\r
198     else {\r
199       HeapFree(GetProcessHeap(), 0, qsc);\r
200       CloseHandle(service->handle);\r
201       CloseServiceHandle(services);\r
202       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("username"), _T("pre_edit_service()"));\r
203       return 4;\r
204     }\r
205   }\r
206   _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), qsc->lpDisplayName);\r
207 \r
208   /* Get the canonical service name. We open it case insensitively. */\r
209   bufsize = _countof(service->name);\r
210   GetServiceKeyName(services, service->displayname, service->name, &bufsize);\r
211 \r
212   /* Remember the executable in case it isn't NSSM. */\r
213   _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), qsc->lpBinaryPathName);\r
214   HeapFree(GetProcessHeap(), 0, qsc);\r
215 \r
216   /* Get extended system details. */\r
217   if (service->startup == NSSM_STARTUP_AUTOMATIC) {\r
218     QueryServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, 0, 0, &bufsize);\r
219     error = GetLastError();\r
220     if (error == ERROR_INSUFFICIENT_BUFFER) {\r
221       SERVICE_DELAYED_AUTO_START_INFO *info = (SERVICE_DELAYED_AUTO_START_INFO *) HeapAlloc(GetProcessHeap(), 0, bufsize);\r
222       if (! info) {\r
223         CloseHandle(service->handle);\r
224         CloseServiceHandle(services);\r
225         print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_DELAYED_AUTO_START_INFO"), _T("pre_edit_service()"));\r
226         return 5;\r
227       }\r
228 \r
229       if (QueryServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, (unsigned char *) info, bufsize, &bufsize)) {\r
230         if (info->fDelayedAutostart) service->startup = NSSM_STARTUP_DELAYED;\r
231       }\r
232       else {\r
233         error = GetLastError();\r
234         if (error != ERROR_INVALID_LEVEL) {\r
235           CloseHandle(service->handle);\r
236           CloseServiceHandle(services);\r
237           print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));\r
238           return 5;\r
239         }\r
240       }\r
241     }\r
242     else if (error != ERROR_INVALID_LEVEL) {\r
243       CloseHandle(service->handle);\r
244       CloseServiceHandle(services);\r
245       print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_DELAYED_AUTO_START_INFO"), error_string(error));\r
246       return 5;\r
247     }\r
248   }\r
249 \r
250   QueryServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, 0, 0, &bufsize);\r
251   error = GetLastError();\r
252   if (error == ERROR_INSUFFICIENT_BUFFER) {\r
253     SERVICE_DESCRIPTION *description = (SERVICE_DESCRIPTION *) HeapAlloc(GetProcessHeap(), 0, bufsize);\r
254     if (! description) {\r
255       CloseHandle(service->handle);\r
256       CloseServiceHandle(services);\r
257       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_CONFIG_DESCRIPTION"), _T("pre_edit_service()"));\r
258       return 6;\r
259     }\r
260 \r
261     if (QueryServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, (unsigned char *) description, bufsize, &bufsize)) {\r
262      if (description->lpDescription) _sntprintf_s(service->description, _countof(service->description), _TRUNCATE, _T("%s"), description->lpDescription);\r
263       HeapFree(GetProcessHeap(), 0, description);\r
264     }\r
265     else {\r
266       HeapFree(GetProcessHeap(), 0, description);\r
267       CloseHandle(service->handle);\r
268       CloseServiceHandle(services);\r
269       print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));\r
270       return 6;\r
271     }\r
272   }\r
273   else {\r
274     CloseHandle(service->handle);\r
275     CloseServiceHandle(services);\r
276     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));\r
277     return 6;\r
278   }\r
279 \r
280   /* Get NSSM details. */\r
281   get_parameters(service, 0);\r
282 \r
283   CloseServiceHandle(services);\r
284 \r
285   if (! service->exe[0]) {\r
286     print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE, service->name, NSSM, service->image);\r
287     service->native = true;\r
288   }\r
289 \r
290   nssm_gui(IDD_EDIT, service);\r
291 \r
292   return 0;\r
293 }\r
294 \r
295 /* About to remove the service */\r
296 int pre_remove_service(int argc, TCHAR **argv) {\r
297   nssm_service_t *service = alloc_nssm_service();\r
298   set_nssm_service_defaults(service);\r
299   if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);\r
300 \r
301   /* Show dialogue box if we didn't pass service name and "confirm" */\r
302   if (argc < 2) return nssm_gui(IDD_REMOVE, service);\r
303   if (str_equiv(argv[1], _T("confirm"))) {\r
304     int ret = remove_service(service);\r
305     cleanup_nssm_service(service);\r
306     return ret;\r
307   }\r
308   print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);\r
309   return 100;\r
310 }\r
311 \r
312 /* Install the service */\r
313 int install_service(nssm_service_t *service) {\r
314   if (! service) return 1;\r
315 \r
316   /* Open service manager */\r
317   SC_HANDLE services = open_service_manager();\r
318   if (! services) {\r
319     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
320     cleanup_nssm_service(service);\r
321     return 2;\r
322   }\r
323 \r
324   /* Get path of this program */\r
325   GetModuleFileName(0, service->image, _countof(service->image));\r
326 \r
327   /* Create the service - settings will be changed in edit_service() */\r
328   service->handle = CreateService(services, service->name, service->name, SC_MANAGER_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, service->image, 0, 0, 0, 0, 0);\r
329   if (! service->handle) {\r
330     print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);\r
331     CloseServiceHandle(services);\r
332     return 5;\r
333   }\r
334 \r
335   if (edit_service(service, false)) {\r
336     DeleteService(service->handle);\r
337     CloseServiceHandle(services);\r
338     return 6;\r
339   }\r
340 \r
341   print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);\r
342 \r
343   /* Cleanup */\r
344   CloseServiceHandle(services);\r
345 \r
346   return 0;\r
347 }\r
348 \r
349 /* Edit the service. */\r
350 int edit_service(nssm_service_t *service, bool editing) {\r
351   if (! service) return 1;\r
352 \r
353   /*\r
354     The only two valid flags for service type are SERVICE_WIN32_OWN_PROCESS\r
355     and SERVICE_INTERACTIVE_PROCESS.\r
356   */\r
357   service->type &= SERVICE_INTERACTIVE_PROCESS;\r
358   service->type |= SERVICE_WIN32_OWN_PROCESS;\r
359 \r
360   /* Startup type. */\r
361   unsigned long startup;\r
362   switch (service->startup) {\r
363     case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;\r
364     case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;\r
365     default: startup = SERVICE_AUTO_START;\r
366   }\r
367 \r
368   /* Display name. */\r
369   if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);\r
370 \r
371   /*\r
372     Username must be NULL if we aren't changing or an account name.\r
373     We must explicitly user LOCALSYSTEM to change it when we are editing.\r
374     Password must be NULL if we aren't changing, a password or "".\r
375     Empty passwords are valid but we won't allow them in the GUI.\r
376   */\r
377   TCHAR *username = 0;\r
378   TCHAR *password = 0;\r
379   if (service->usernamelen) {\r
380     username = service->username;\r
381     if (service->passwordlen) password = service->password;\r
382     else password = _T("");\r
383   }\r
384   else if (editing) username = NSSM_LOCALSYSTEM_ACCOUNT;\r
385 \r
386   if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, service->displayname)) {\r
387     print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));\r
388     return 5;\r
389   }\r
390 \r
391   if (service->description[0] || editing) {\r
392     SERVICE_DESCRIPTION description;\r
393     ZeroMemory(&description, sizeof(description));\r
394     if (service->description[0]) description.lpDescription = service->description;\r
395     else description.lpDescription = 0;\r
396     if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, &description)) {\r
397       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DESCRIPTION_FAILED, service->name, error_string(GetLastError()), 0);\r
398     }\r
399   }\r
400 \r
401   SERVICE_DELAYED_AUTO_START_INFO delayed;\r
402   ZeroMemory(&delayed, sizeof(delayed));\r
403   if (service->startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;\r
404   else delayed.fDelayedAutostart = 0;\r
405   /* Delayed startup isn't supported until Vista. */\r
406   if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {\r
407     unsigned long error = GetLastError();\r
408     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
409     if (error != ERROR_INVALID_LEVEL) {\r
410       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);\r
411     }\r
412   }\r
413 \r
414   /* Don't mess with parameters which aren't ours. */\r
415   if (! service->native) {\r
416     /* Now we need to put the parameters into the registry */\r
417     if (create_parameters(service, editing)) {\r
418       print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);\r
419       return 6;\r
420     }\r
421 \r
422     set_service_recovery(service);\r
423   }\r
424 \r
425   return 0;\r
426 }\r
427 \r
428 /* Remove the service */\r
429 int remove_service(nssm_service_t *service) {\r
430   if (! service) return 1;\r
431 \r
432   /* Open service manager */\r
433   SC_HANDLE services = open_service_manager();\r
434   if (! services) {\r
435     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
436     return 2;\r
437   }\r
438 \r
439   /* Try to open the service */\r
440   service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);\r
441   if (! service->handle) {\r
442     print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);\r
443     CloseServiceHandle(services);\r
444     return 3;\r
445   }\r
446 \r
447   /* Get the canonical service name. We open it case insensitively. */\r
448   unsigned long bufsize = _countof(service->displayname);\r
449   GetServiceDisplayName(services, service->name, service->displayname, &bufsize);\r
450   bufsize = _countof(service->name);\r
451   GetServiceKeyName(services, service->displayname, service->name, &bufsize);\r
452 \r
453   /* Try to delete the service */\r
454   if (! DeleteService(service->handle)) {\r
455     print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);\r
456     CloseServiceHandle(services);\r
457     return 4;\r
458   }\r
459 \r
460   /* Cleanup */\r
461   CloseServiceHandle(services);\r
462 \r
463   print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);\r
464   return 0;\r
465 }\r
466 \r
467 /* Service initialisation */\r
468 void WINAPI service_main(unsigned long argc, TCHAR **argv) {\r
469   nssm_service_t *service = alloc_nssm_service();\r
470   if (! service) return;\r
471 \r
472   if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {\r
473     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);\r
474     return;\r
475   }\r
476 \r
477   /* We can use a condition variable in a critical section on Vista or later. */\r
478   if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;\r
479   else use_critical_section = false;\r
480 \r
481   /* Initialise status */\r
482   ZeroMemory(&service->status, sizeof(service->status));\r
483   service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;\r
484   service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;\r
485   service->status.dwWin32ExitCode = NO_ERROR;\r
486   service->status.dwServiceSpecificExitCode = 0;\r
487   service->status.dwCheckPoint = 0;\r
488   service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
489 \r
490   /* Signal we AREN'T running the server */\r
491   service->process_handle = 0;\r
492   service->pid = 0;\r
493 \r
494   /* Register control handler */\r
495   service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);\r
496   if (! service->status_handle) {\r
497     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);\r
498     return;\r
499   }\r
500 \r
501   log_service_control(service->name, 0, true);\r
502 \r
503   service->status.dwCurrentState = SERVICE_START_PENDING;\r
504   service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;\r
505   SetServiceStatus(service->status_handle, &service->status);\r
506 \r
507   if (is_admin) {\r
508     /* Try to create the exit action parameters; we don't care if it fails */\r
509     create_exit_action(service->name, exit_action_strings[0], false);\r
510 \r
511     SC_HANDLE services = open_service_manager();\r
512     if (services) {\r
513       service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);\r
514       set_service_recovery(service);\r
515       CloseServiceHandle(services);\r
516     }\r
517   }\r
518 \r
519   /* Used for signalling a resume if the service pauses when throttled. */\r
520   if (use_critical_section) {\r
521     InitializeCriticalSection(&service->throttle_section);\r
522     service->throttle_section_initialised = true;\r
523   }\r
524   else {\r
525     service->throttle_timer = CreateWaitableTimer(0, 1, 0);\r
526     if (! service->throttle_timer) {\r
527       log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);\r
528     }\r
529   }\r
530 \r
531   monitor_service(service);\r
532 }\r
533 \r
534 /* Make sure service recovery actions are taken where necessary */\r
535 void set_service_recovery(nssm_service_t *service) {\r
536   SERVICE_FAILURE_ACTIONS_FLAG flag;\r
537   ZeroMemory(&flag, sizeof(flag));\r
538   flag.fFailureActionsOnNonCrashFailures = true;\r
539 \r
540   /* This functionality was added in Vista so the call may fail */\r
541   if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {\r
542     unsigned long error = GetLastError();\r
543     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
544     if (error != ERROR_INVALID_LEVEL) {\r
545       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);\r
546     }\r
547   }\r
548 }\r
549 \r
550 int monitor_service(nssm_service_t *service) {\r
551   /* Set service status to started */\r
552   int ret = start_service(service);\r
553   if (ret) {\r
554     TCHAR code[16];\r
555     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);\r
556     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);\r
557     return ret;\r
558   }\r
559   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);\r
560 \r
561   /* Monitor service */\r
562   if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {\r
563     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);\r
564   }\r
565 \r
566   return 0;\r
567 }\r
568 \r
569 TCHAR *service_control_text(unsigned long control) {\r
570   switch (control) {\r
571     /* HACK: there is no SERVICE_CONTROL_START constant */\r
572     case 0: return _T("START");\r
573     case SERVICE_CONTROL_STOP: return _T("STOP");\r
574     case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");\r
575     case SERVICE_CONTROL_PAUSE: return _T("PAUSE");\r
576     case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");\r
577     case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");\r
578     default: return 0;\r
579   }\r
580 }\r
581 \r
582 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {\r
583   TCHAR *text = service_control_text(control);\r
584   unsigned long event;\r
585 \r
586   if (! text) {\r
587     /* "0x" + 8 x hex + NULL */\r
588     text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));\r
589     if (! text) {\r
590       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
591       return;\r
592     }\r
593     if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {\r
594       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
595       HeapFree(GetProcessHeap(), 0, text);\r
596       return;\r
597     }\r
598 \r
599     event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;\r
600   }\r
601   else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;\r
602   else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;\r
603 \r
604   log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);\r
605 \r
606   if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {\r
607     HeapFree(GetProcessHeap(), 0, text);\r
608   }\r
609 }\r
610 \r
611 /* Service control handler */\r
612 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {\r
613   nssm_service_t *service = (nssm_service_t *) context;\r
614 \r
615   switch (control) {\r
616     case SERVICE_CONTROL_INTERROGATE:\r
617       /* We always keep the service status up-to-date so this is a no-op. */\r
618       return NO_ERROR;\r
619 \r
620     case SERVICE_CONTROL_SHUTDOWN:\r
621     case SERVICE_CONTROL_STOP:\r
622       log_service_control(service->name, control, true);\r
623       /*\r
624         We MUST acknowledge the stop request promptly but we're committed to\r
625         waiting for the application to exit.  Spawn a new thread to wait\r
626         while we acknowledge the request.\r
627       */\r
628       if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {\r
629         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);\r
630 \r
631         /*\r
632           We couldn't create a thread to tidy up so we'll have to force the tidyup\r
633           to complete in time in this thread.\r
634         */\r
635         service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
636         service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
637         service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
638 \r
639         stop_service(service, 0, true, true);\r
640       }\r
641       return NO_ERROR;\r
642 \r
643     case SERVICE_CONTROL_CONTINUE:\r
644       log_service_control(service->name, control, true);\r
645       service->throttle = 0;\r
646       if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);\r
647       else {\r
648         if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;\r
649         ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
650         SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
651       }\r
652       service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;\r
653       service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;\r
654       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);\r
655       SetServiceStatus(service->status_handle, &service->status);\r
656       return NO_ERROR;\r
657 \r
658     case SERVICE_CONTROL_PAUSE:\r
659       /*\r
660         We don't accept pause messages but it isn't possible to register\r
661         only for continue messages so we have to handle this case.\r
662       */\r
663       log_service_control(service->name, control, false);\r
664       return ERROR_CALL_NOT_IMPLEMENTED;\r
665   }\r
666 \r
667   /* Unknown control */\r
668   log_service_control(service->name, control, false);\r
669   return ERROR_CALL_NOT_IMPLEMENTED;\r
670 }\r
671 \r
672 /* Start the service */\r
673 int start_service(nssm_service_t *service) {\r
674   service->stopping = false;\r
675   service->allow_restart = true;\r
676 \r
677   if (service->process_handle) return 0;\r
678 \r
679   /* Allocate a STARTUPINFO structure for a new process */\r
680   STARTUPINFO si;\r
681   ZeroMemory(&si, sizeof(si));\r
682   si.cb = sizeof(si);\r
683 \r
684   /* Allocate a PROCESSINFO structure for the process */\r
685   PROCESS_INFORMATION pi;\r
686   ZeroMemory(&pi, sizeof(pi));\r
687 \r
688   /* Get startup parameters */\r
689   int ret = get_parameters(service, &si);\r
690   if (ret) {\r
691     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);\r
692     return stop_service(service, 2, true, true);\r
693   }\r
694 \r
695   /* Launch executable with arguments */\r
696   TCHAR cmd[CMD_LENGTH];\r
697   if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {\r
698     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);\r
699     close_output_handles(&si);\r
700     return stop_service(service, 2, true, true);\r
701   }\r
702 \r
703   throttle_restart(service);\r
704 \r
705   bool inherit_handles = false;\r
706   if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;\r
707   unsigned long flags = 0;\r
708 #ifdef UNICODE\r
709   flags |= CREATE_UNICODE_ENVIRONMENT;\r
710 #endif\r
711   if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {\r
712     unsigned long exitcode = 3;\r
713     unsigned long error = GetLastError();\r
714     if (error == ERROR_INVALID_PARAMETER && service->env) {\r
715       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);\r
716       if (test_environment(service->env)) exitcode = 4;\r
717     }\r
718     else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);\r
719     close_output_handles(&si);\r
720     return stop_service(service, exitcode, true, true);\r
721   }\r
722   service->process_handle = pi.hProcess;\r
723   service->pid = pi.dwProcessId;\r
724 \r
725   if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));\r
726 \r
727   close_output_handles(&si);\r
728 \r
729   /*\r
730     Wait for a clean startup before changing the service status to RUNNING\r
731     but be mindful of the fact that we are blocking the service control manager\r
732     so abandon the wait before too much time has elapsed.\r
733   */\r
734   unsigned long delay = service->throttle_delay;\r
735   if (delay > NSSM_SERVICE_STATUS_DEADLINE) {\r
736     TCHAR delay_milliseconds[16];\r
737     _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);\r
738     TCHAR deadline_milliseconds[16];\r
739     _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);\r
740     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);\r
741     delay = NSSM_SERVICE_STATUS_DEADLINE;\r
742   }\r
743   unsigned long deadline = WaitForSingleObject(service->process_handle, delay);\r
744 \r
745   /* Signal successful start */\r
746   service->status.dwCurrentState = SERVICE_RUNNING;\r
747   SetServiceStatus(service->status_handle, &service->status);\r
748 \r
749   /* Continue waiting for a clean startup. */\r
750   if (deadline == WAIT_TIMEOUT) {\r
751     if (service->throttle_delay > delay) {\r
752       if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;\r
753     }\r
754     else service->throttle = 0;\r
755   }\r
756 \r
757   return 0;\r
758 }\r
759 \r
760 /* Stop the service */\r
761 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {\r
762   service->allow_restart = false;\r
763   if (service->wait_handle) {\r
764     UnregisterWait(service->wait_handle);\r
765     service->wait_handle = 0;\r
766   }\r
767 \r
768   if (default_action && ! exitcode && ! graceful) {\r
769     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
770     graceful = true;\r
771   }\r
772 \r
773   /* Signal we are stopping */\r
774   if (graceful) {\r
775     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
776     service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
777     SetServiceStatus(service->status_handle, &service->status);\r
778   }\r
779 \r
780   /* Nothing to do if service isn't running */\r
781   if (service->pid) {\r
782     /* Shut down service */\r
783     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);\r
784     kill_process(service, service->process_handle, service->pid, 0);\r
785   }\r
786   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);\r
787 \r
788   end_service((void *) service, true);\r
789 \r
790   /* Signal we stopped */\r
791   if (graceful) {\r
792     service->status.dwCurrentState = SERVICE_STOPPED;\r
793     if (exitcode) {\r
794       service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
795       service->status.dwServiceSpecificExitCode = exitcode;\r
796     }\r
797     else {\r
798       service->status.dwWin32ExitCode = NO_ERROR;\r
799       service->status.dwServiceSpecificExitCode = 0;\r
800     }\r
801     SetServiceStatus(service->status_handle, &service->status);\r
802   }\r
803 \r
804   return exitcode;\r
805 }\r
806 \r
807 /* Callback function triggered when the server exits */\r
808 void CALLBACK end_service(void *arg, unsigned char why) {\r
809   nssm_service_t *service = (nssm_service_t *) arg;\r
810 \r
811   if (service->stopping) return;\r
812 \r
813   service->stopping = true;\r
814 \r
815   /* Check exit code */\r
816   unsigned long exitcode = 0;\r
817   TCHAR code[16];\r
818   if (service->process_handle) {\r
819     GetExitCodeProcess(service->process_handle, &exitcode);\r
820     if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);\r
821     CloseHandle(service->process_handle);\r
822   }\r
823   else GetSystemTimeAsFileTime(&service->exit_time);\r
824 \r
825   service->process_handle = 0;\r
826 \r
827   /*\r
828     Log that the service ended BEFORE logging about killing the process\r
829     tree.  See below for the possible values of the why argument.\r
830   */\r
831   if (! why) {\r
832     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);\r
833     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);\r
834   }\r
835 \r
836   /* Clean up. */\r
837   if (exitcode == STILL_ACTIVE) exitcode = 0;\r
838   if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);\r
839   service->pid = 0;\r
840 \r
841   /*\r
842     The why argument is true if our wait timed out or false otherwise.\r
843     Our wait is infinite so why will never be true when called by the system.\r
844     If it is indeed true, assume we were called from stop_service() because\r
845     this is a controlled shutdown, and don't take any restart action.\r
846   */\r
847   if (why) return;\r
848   if (! service->allow_restart) return;\r
849 \r
850   /* What action should we take? */\r
851   int action = NSSM_EXIT_RESTART;\r
852   TCHAR action_string[ACTION_LEN];\r
853   bool default_action;\r
854   if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {\r
855     for (int i = 0; exit_action_strings[i]; i++) {\r
856       if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
857         action = i;\r
858         break;\r
859       }\r
860     }\r
861   }\r
862 \r
863   switch (action) {\r
864     /* Try to restart the service or return failure code to service manager */\r
865     case NSSM_EXIT_RESTART:\r
866       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);\r
867       while (monitor_service(service)) {\r
868         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);\r
869         Sleep(30000);\r
870       }\r
871     break;\r
872 \r
873     /* Do nothing, just like srvany would */\r
874     case NSSM_EXIT_IGNORE:\r
875       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);\r
876       Sleep(INFINITE);\r
877     break;\r
878 \r
879     /* Tell the service manager we are finished */\r
880     case NSSM_EXIT_REALLY:\r
881       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);\r
882       stop_service(service, exitcode, true, default_action);\r
883     break;\r
884 \r
885     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
886     case NSSM_EXIT_UNCLEAN:\r
887       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);\r
888       stop_service(service, exitcode, false, default_action);\r
889       free_imports();\r
890       exit(exitcode);\r
891     break;\r
892   }\r
893 }\r
894 \r
895 void throttle_restart(nssm_service_t *service) {\r
896   /* This can't be a restart if the service is already running. */\r
897   if (! service->throttle++) return;\r
898 \r
899   int ms = throttle_milliseconds(service->throttle);\r
900 \r
901   if (service->throttle > 7) service->throttle = 8;\r
902 \r
903   TCHAR threshold[8], milliseconds[8];\r
904   _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);\r
905   _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);\r
906   log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);\r
907 \r
908   if (use_critical_section) EnterCriticalSection(&service->throttle_section);\r
909   else if (service->throttle_timer) {\r
910     ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
911     service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
912     SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
913   }\r
914 \r
915   service->status.dwCurrentState = SERVICE_PAUSED;\r
916   SetServiceStatus(service->status_handle, &service->status);\r
917 \r
918   if (use_critical_section) {\r
919     imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);\r
920     LeaveCriticalSection(&service->throttle_section);\r
921   }\r
922   else {\r
923     if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);\r
924     else Sleep(ms);\r
925   }\r
926 }\r
927 \r
928 /*\r
929   When responding to a stop (or any other) request we need to set dwWaitHint to\r
930   the number of milliseconds we expect the operation to take, and optionally\r
931   increase dwCheckPoint.  If dwWaitHint milliseconds elapses without the\r
932   operation completing or dwCheckPoint increasing, the system will consider the\r
933   service to be hung.\r
934 \r
935   However the system will consider the service to be hung after 30000\r
936   milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not\r
937   changed.  Therefore if we want to wait longer than that we must periodically\r
938   increase dwCheckPoint.\r
939 \r
940   Furthermore, it will consider the service to be hung after 60000 milliseconds\r
941   regardless of the value of dwCheckPoint unless dwWaitHint is increased every\r
942   time dwCheckPoint is also increased.\r
943 \r
944   Our strategy then is to retrieve the initial dwWaitHint and wait for\r
945   NSSM_SERVICE_STATUS_DEADLINE milliseconds.  If the process is still running\r
946   and we haven't finished waiting we increment dwCheckPoint and add whichever is\r
947   smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to\r
948   dwWaitHint.\r
949 \r
950   Only doing both these things will prevent the system from killing the service.\r
951 \r
952   Returns: 1 if the wait timed out.\r
953            0 if the wait completed.\r
954           -1 on error.\r
955 */\r
956 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {\r
957   unsigned long interval;\r
958   unsigned long waithint;\r
959   unsigned long ret;\r
960   unsigned long waited;\r
961   TCHAR interval_milliseconds[16];\r
962   TCHAR timeout_milliseconds[16];\r
963   TCHAR waited_milliseconds[16];\r
964   TCHAR *function = function_name;\r
965 \r
966   /* Add brackets to function name. */\r
967   size_t funclen = _tcslen(function_name) + 3;\r
968   TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));\r
969   if (func) {\r
970     if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;\r
971   }\r
972 \r
973   _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);\r
974 \r
975   waithint = service->status.dwWaitHint;\r
976   waited = 0;\r
977   while (waited < timeout) {\r
978     interval = timeout - waited;\r
979     if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;\r
980 \r
981     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
982     service->status.dwWaitHint += interval;\r
983     service->status.dwCheckPoint++;\r
984     SetServiceStatus(service->status_handle, &service->status);\r
985 \r
986     if (waited) {\r
987       _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);\r
988       _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);\r
989       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);\r
990     }\r
991 \r
992     switch (WaitForSingleObject(service->process_handle, interval)) {\r
993       case WAIT_OBJECT_0:\r
994         ret = 0;\r
995         goto awaited;\r
996 \r
997       case WAIT_TIMEOUT:\r
998         ret = 1;\r
999       break;\r
1000 \r
1001       default:\r
1002         ret = -1;\r
1003         goto awaited;\r
1004     }\r
1005 \r
1006     waited += interval;\r
1007   }\r
1008 \r
1009 awaited:\r
1010   if (func) HeapFree(GetProcessHeap(), 0, func);\r
1011 \r
1012   return ret;\r
1013 }\r