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