Handle running without administrator privileges.
[nssm.git] / service.cpp
1 #include "nssm.h"\r
2 \r
3 bool is_admin;\r
4 SERVICE_STATUS service_status;\r
5 SERVICE_STATUS_HANDLE service_handle;\r
6 HANDLE process_handle;\r
7 HANDLE wait_handle;\r
8 unsigned long pid;\r
9 static char service_name[SERVICE_NAME_LENGTH];\r
10 char exe[EXE_LENGTH];\r
11 char flags[CMD_LENGTH];\r
12 char dir[MAX_PATH];\r
13 bool stopping;\r
14 unsigned long throttle_delay;\r
15 HANDLE throttle_timer;\r
16 LARGE_INTEGER throttle_duetime;\r
17 \r
18 static enum { NSSM_EXIT_RESTART, NSSM_EXIT_IGNORE, NSSM_EXIT_REALLY, NSSM_EXIT_UNCLEAN } exit_actions;\r
19 static const char *exit_action_strings[] = { "Restart", "Ignore", "Exit", "Suicide", 0 };\r
20 \r
21 static unsigned long throttle;\r
22 \r
23 static inline int throttle_milliseconds() {\r
24   /* pow() operates on doubles. */\r
25   int ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;\r
26   return ret * 1000;\r
27 }\r
28 \r
29 /* Connect to the service manager */\r
30 SC_HANDLE open_service_manager() {\r
31   SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);\r
32   if (! ret) {\r
33     if (is_admin) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENSCMANAGER_FAILED, 0);\r
34     return 0;\r
35   }\r
36 \r
37   return ret;\r
38 }\r
39 \r
40 /* About to install the service */\r
41 int pre_install_service(int argc, char **argv) {\r
42   /* Show the dialogue box if we didn't give the */\r
43   if (argc < 2) return nssm_gui(IDD_INSTALL, argv[0]);\r
44 \r
45   /* Arguments are optional */\r
46   char *flags;\r
47   size_t flagslen = 0;\r
48   size_t s = 0;\r
49   int i;\r
50   for (i = 2; i < argc; i++) flagslen += strlen(argv[i]) + 1;\r
51   if (! flagslen) flagslen = 1;\r
52 \r
53   flags = (char *) HeapAlloc(GetProcessHeap(), 0, flagslen);\r
54   if (! flags) {\r
55     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "flags", "pre_install_service()", 0);\r
56     return 2;\r
57   }\r
58   ZeroMemory(flags, flagslen);\r
59 \r
60   /*\r
61     This probably isn't UTF8-safe and should use std::string or something\r
62     but it's been broken for the best part of a decade and due for a rewrite\r
63     anyway so it'll do as a quick-'n'-dirty fix.  Note that we don't free\r
64     the flags buffer but as the program exits that isn't a big problem.\r
65   */\r
66   for (i = 2; i < argc; i++) {\r
67     size_t len = strlen(argv[i]);\r
68     memmove(flags + s, argv[i], len);\r
69     s += len;\r
70     if (i < argc - 1) flags[s++] = ' ';\r
71   }\r
72 \r
73   return install_service(argv[0], argv[1], flags);\r
74 }\r
75 \r
76 /* About to remove the service */\r
77 int pre_remove_service(int argc, char **argv) {\r
78   /* Show dialogue box if we didn't pass service name and "confirm" */\r
79   if (argc < 2) return nssm_gui(IDD_REMOVE, argv[0]);\r
80   if (str_equiv(argv[1], "confirm")) return remove_service(argv[0]);\r
81   fprintf(stderr, "To remove a service without confirmation: nssm remove <servicename> confirm\n");\r
82   return 100;\r
83 }\r
84 \r
85 /* Install the service */\r
86 int install_service(char *name, char *exe, char *flags) {\r
87   /* Open service manager */\r
88   SC_HANDLE services = open_service_manager();\r
89   if (! services) {\r
90     fprintf(stderr, "Error opening service manager!\n");\r
91     return 2;\r
92   }\r
93   \r
94   /* Get path of this program */\r
95   char path[MAX_PATH];\r
96   GetModuleFileName(0, path, MAX_PATH);\r
97 \r
98   /* Construct command */\r
99   char command[CMD_LENGTH];\r
100   size_t pathlen = strlen(path);\r
101   if (pathlen + 1 >= VALUE_LENGTH) {\r
102     fprintf(stderr, "The full path to " NSSM " is too long!\n");\r
103     return 3;\r
104   }\r
105   if (_snprintf(command, sizeof(command), "\"%s\"", path) < 0) {\r
106     fprintf(stderr, "Out of memory for ImagePath!\n");\r
107     return 4;\r
108   }\r
109 \r
110   /* Work out directory name */\r
111   size_t len = strlen(exe);\r
112   size_t i;\r
113   for (i = len; i && exe[i] != '\\' && exe[i] != '/'; i--);\r
114   char dir[MAX_PATH];\r
115   memmove(dir, exe, i);\r
116   dir[i] = '\0';\r
117 \r
118   /* Create the service */\r
119   SC_HANDLE service = CreateService(services, name, name, SC_MANAGER_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, command, 0, 0, 0, 0, 0);\r
120   if (! service) {\r
121     fprintf(stderr, "Error creating service!\n");\r
122     CloseServiceHandle(services);\r
123     return 5;\r
124   }\r
125 \r
126   /* Now we need to put the parameters into the registry */\r
127   if (create_parameters(name, exe, flags, dir)) {\r
128     fprintf(stderr, "Error setting startup parameters for the service!\n");\r
129     DeleteService(service);\r
130     CloseServiceHandle(services);\r
131     return 6;\r
132   }\r
133 \r
134   set_service_recovery(service, name);\r
135 \r
136   /* Cleanup */\r
137   CloseServiceHandle(service);\r
138   CloseServiceHandle(services);\r
139 \r
140   printf("Service \"%s\" installed successfully!\n", name);\r
141   return 0;\r
142 }\r
143 \r
144 /* Remove the service */\r
145 int remove_service(char *name) {\r
146   /* Open service manager */\r
147   SC_HANDLE services = open_service_manager();\r
148   if (! services) {\r
149     fprintf(stderr, "Error opening service manager!\n");\r
150     return 2;\r
151   }\r
152   \r
153   /* Try to open the service */\r
154   SC_HANDLE service = OpenService(services, name, SC_MANAGER_ALL_ACCESS);\r
155   if (! service) {\r
156     fprintf(stderr, "Can't open service!");\r
157     CloseServiceHandle(services);\r
158     return 3;\r
159   }\r
160 \r
161   /* Try to delete the service */\r
162   if (! DeleteService(service)) {\r
163     fprintf(stderr, "Error deleting service!\n");\r
164     CloseServiceHandle(service);\r
165     CloseServiceHandle(services);\r
166     return 4;\r
167   }\r
168 \r
169   /* Cleanup */\r
170   CloseServiceHandle(service);\r
171   CloseServiceHandle(services);\r
172 \r
173   printf("Service \"%s\" removed successfully!\n", name);\r
174   return 0;\r
175 }\r
176 \r
177 /* Service initialisation */\r
178 void WINAPI service_main(unsigned long argc, char **argv) {\r
179   if (_snprintf(service_name, sizeof(service_name), "%s", argv[0]) < 0) {\r
180     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "service_name", "service_main()", 0);\r
181     return;\r
182   }\r
183 \r
184   /* Initialise status */\r
185   ZeroMemory(&service_status, sizeof(service_status));\r
186   service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;\r
187   service_status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;\r
188   service_status.dwWin32ExitCode = NO_ERROR;\r
189   service_status.dwServiceSpecificExitCode = 0;\r
190   service_status.dwCheckPoint = 0;\r
191   service_status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
192 \r
193   /* Signal we AREN'T running the server */\r
194   process_handle = 0;\r
195   pid = 0;\r
196 \r
197   /* Register control handler */\r
198   service_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, 0);\r
199   if (! service_handle) {\r
200     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);\r
201     return;\r
202   }\r
203 \r
204   log_service_control(service_name, 0, true);\r
205 \r
206   service_status.dwCurrentState = SERVICE_START_PENDING;\r
207   service_status.dwWaitHint = throttle_delay + NSSM_WAITHINT_MARGIN;\r
208   SetServiceStatus(service_handle, &service_status);\r
209 \r
210   if (is_admin) {\r
211     /* Try to create the exit action parameters; we don't care if it fails */\r
212     create_exit_action(argv[0], exit_action_strings[0]);\r
213 \r
214     set_service_recovery(0, service_name);\r
215   }\r
216 \r
217   /* Used for signalling a resume if the service pauses when throttled. */\r
218   throttle_timer = CreateWaitableTimer(0, 1, 0);\r
219   if (! throttle_timer) {\r
220     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service_name, error_string(GetLastError()), 0);\r
221   }\r
222 \r
223   monitor_service();\r
224 }\r
225 \r
226 /* Make sure service recovery actions are taken where necessary */\r
227 void set_service_recovery(SC_HANDLE service, char *service_name) {\r
228   SC_HANDLE services = 0;\r
229 \r
230   if (! service) {\r
231     services = open_service_manager();\r
232     if (! services) return;\r
233 \r
234     service = OpenService(services, service_name, SC_MANAGER_ALL_ACCESS);\r
235     if (! service) return;\r
236   }\r
237 \r
238   SERVICE_FAILURE_ACTIONS_FLAG flag;\r
239   ZeroMemory(&flag, sizeof(flag));\r
240   flag.fFailureActionsOnNonCrashFailures = true;\r
241 \r
242   /* This functionality was added in Vista so the call may fail */\r
243   if (! ChangeServiceConfig2(service, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {\r
244     unsigned long error = GetLastError();\r
245     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
246     if (error != ERROR_INVALID_LEVEL) {\r
247       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CHANGESERVICECONFIG2_FAILED, service_name, error_string(error), 0);\r
248     }\r
249   }\r
250 \r
251   if (services) {\r
252     CloseServiceHandle(service);\r
253     CloseServiceHandle(services);\r
254   }\r
255 }\r
256 \r
257 int monitor_service() {\r
258   /* Set service status to started */\r
259   int ret = start_service();\r
260   if (ret) {\r
261     char code[16];\r
262     _snprintf(code, sizeof(code), "%d", ret);\r
263     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, exe, service_name, ret, 0);\r
264     return ret;\r
265   }\r
266   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, exe, flags, service_name, dir, 0);\r
267 \r
268   /* Monitor service service */\r
269   if (! RegisterWaitForSingleObject(&wait_handle, process_handle, end_service, (void *) pid, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {\r
270     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service_name, exe, error_string(GetLastError()), 0);\r
271   }\r
272 \r
273   return 0;\r
274 }\r
275 \r
276 char *service_control_text(unsigned long control) {\r
277   switch (control) {\r
278     /* HACK: there is no SERVICE_CONTROL_START constant */\r
279     case 0: return "START";\r
280     case SERVICE_CONTROL_STOP: return "STOP";\r
281     case SERVICE_CONTROL_SHUTDOWN: return "SHUTDOWN";\r
282     case SERVICE_CONTROL_PAUSE: return "PAUSE";\r
283     case SERVICE_CONTROL_CONTINUE: return "CONTINUE";\r
284     case SERVICE_CONTROL_INTERROGATE: return "INTERROGATE";\r
285     default: return 0;\r
286   }\r
287 }\r
288 \r
289 void log_service_control(char *service_name, unsigned long control, bool handled) {\r
290   char *text = service_control_text(control);\r
291   unsigned long event;\r
292 \r
293   if (! text) {\r
294     /* "0x" + 8 x hex + NULL */\r
295     text = (char *) HeapAlloc(GetProcessHeap(), 0, 11);\r
296     if (! text) {\r
297       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control", 0);\r
298       return;\r
299     }\r
300     if (_snprintf(text, 11, "0x%08x", control) < 0) {\r
301       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control", 0);\r
302       HeapFree(GetProcessHeap(), 0, text);\r
303       return;\r
304     }\r
305 \r
306     event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;\r
307   }\r
308   else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;\r
309   else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;\r
310 \r
311   log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);\r
312 \r
313   if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {\r
314     HeapFree(GetProcessHeap(), 0, text);\r
315   }\r
316 }\r
317 \r
318 /* Service control handler */\r
319 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {\r
320   switch (control) {\r
321     case SERVICE_CONTROL_SHUTDOWN:\r
322     case SERVICE_CONTROL_STOP:\r
323       log_service_control(service_name, control, true);\r
324       stop_service(0, true, true);\r
325       return NO_ERROR;\r
326 \r
327     case SERVICE_CONTROL_CONTINUE:\r
328       log_service_control(service_name, control, true);\r
329       if (! throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;\r
330       throttle = 0;\r
331       ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));\r
332       SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);\r
333       service_status.dwCurrentState = SERVICE_CONTINUE_PENDING;\r
334       service_status.dwWaitHint = throttle_milliseconds() + NSSM_WAITHINT_MARGIN;\r
335       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service_name, 0);\r
336       SetServiceStatus(service_handle, &service_status);\r
337       return NO_ERROR;\r
338 \r
339     case SERVICE_CONTROL_PAUSE:\r
340       /*\r
341         We don't accept pause messages but it isn't possible to register\r
342         only for continue messages so we have to handle this case.\r
343       */\r
344       log_service_control(service_name, control, false);\r
345       return ERROR_CALL_NOT_IMPLEMENTED;\r
346   }\r
347 \r
348   /* Unknown control */\r
349   log_service_control(service_name, control, false);\r
350   return ERROR_CALL_NOT_IMPLEMENTED;\r
351 }\r
352 \r
353 /* Start the service */\r
354 int start_service() {\r
355   stopping = false;\r
356 \r
357   if (process_handle) return 0;\r
358 \r
359   /* Allocate a STARTUPINFO structure for a new process */\r
360   STARTUPINFO si;\r
361   ZeroMemory(&si, sizeof(si));\r
362   si.cb = sizeof(si);\r
363 \r
364   /* Allocate a PROCESSINFO structure for the process */\r
365   PROCESS_INFORMATION pi;\r
366   ZeroMemory(&pi, sizeof(pi));\r
367 \r
368   /* Get startup parameters */\r
369   char *env = 0;\r
370   int ret = get_parameters(service_name, exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir), &env, &throttle_delay);\r
371   if (ret) {\r
372     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service_name, 0);\r
373     return stop_service(2, true, true);\r
374   }\r
375 \r
376   /* Launch executable with arguments */\r
377   char cmd[CMD_LENGTH];\r
378   if (_snprintf(cmd, sizeof(cmd), "\"%s\" %s", exe, flags) < 0) {\r
379     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "command line", "start_service", 0);\r
380     return stop_service(2, true, true);\r
381   }\r
382 \r
383   throttle_restart();\r
384 \r
385   if (! CreateProcess(0, cmd, 0, 0, false, 0, env, dir, &si, &pi)) {\r
386     unsigned long error = GetLastError();\r
387     if (error == ERROR_INVALID_PARAMETER && env) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service_name, exe, NSSM_REG_ENV, 0);\r
388     else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service_name, exe, error_string(error), 0);\r
389     return stop_service(3, true, true);\r
390   }\r
391   process_handle = pi.hProcess;\r
392   pid = pi.dwProcessId;\r
393 \r
394   /* Signal successful start */\r
395   service_status.dwCurrentState = SERVICE_RUNNING;\r
396   SetServiceStatus(service_handle, &service_status);\r
397 \r
398   /* Wait for a clean startup. */\r
399   if (WaitForSingleObject(process_handle, throttle_delay) == WAIT_TIMEOUT) throttle = 0;\r
400 \r
401   return 0;\r
402 }\r
403 \r
404 /* Stop the service */\r
405 int stop_service(unsigned long exitcode, bool graceful, bool default_action) {\r
406   if (default_action && ! exitcode && ! graceful) {\r
407     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_GRACEFUL_SUICIDE, service_name, 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
408     graceful = true;\r
409   }\r
410 \r
411   /* Signal we are stopping */\r
412   if (graceful) {\r
413     service_status.dwCurrentState = SERVICE_STOP_PENDING;\r
414     service_status.dwWaitHint = NSSM_KILL_WINDOW_GRACE_PERIOD + NSSM_KILL_THREADS_GRACE_PERIOD + NSSM_WAITHINT_MARGIN;\r
415     SetServiceStatus(service_handle, &service_status);\r
416   }\r
417 \r
418   /* Nothing to do if server isn't running */\r
419   if (pid) {\r
420     /* Shut down server */\r
421     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service_name, exe, 0);\r
422     kill_process(service_name, process_handle, pid, 0);\r
423     process_handle = 0;\r
424   }\r
425   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);\r
426 \r
427   end_service((void *) pid, true);\r
428 \r
429   /* Signal we stopped */\r
430   if (graceful) {\r
431     service_status.dwCurrentState = SERVICE_STOPPED;\r
432     if (exitcode) {\r
433       service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
434       service_status.dwServiceSpecificExitCode = exitcode;\r
435     }\r
436     else {\r
437       service_status.dwWin32ExitCode = NO_ERROR;\r
438       service_status.dwServiceSpecificExitCode = 0;\r
439     }\r
440     SetServiceStatus(service_handle, &service_status);\r
441   }\r
442 \r
443   return exitcode;\r
444 }\r
445 \r
446 /* Callback function triggered when the server exits */\r
447 void CALLBACK end_service(void *arg, unsigned char why) {\r
448   if (stopping) return;\r
449 \r
450   stopping = true;\r
451 \r
452   pid = (unsigned long) arg;\r
453 \r
454   /* Check exit code */\r
455   unsigned long exitcode = 0;\r
456   GetExitCodeProcess(process_handle, &exitcode);\r
457 \r
458   /* Clean up. */\r
459   kill_process_tree(service_name, pid, exitcode, pid);\r
460 \r
461   /*\r
462     The why argument is true if our wait timed out or false otherwise.\r
463     Our wait is infinite so why will never be true when called by the system.\r
464     If it is indeed true, assume we were called from stop_service() because\r
465     this is a controlled shutdown, and don't take any restart action.\r
466   */\r
467   if (why) return;\r
468 \r
469   char code[16];\r
470   _snprintf(code, sizeof(code), "%d", exitcode);\r
471   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, exe, service_name, code, 0);\r
472 \r
473   /* What action should we take? */\r
474   int action = NSSM_EXIT_RESTART;\r
475   unsigned char action_string[ACTION_LEN];\r
476   bool default_action;\r
477   if (! get_exit_action(service_name, &exitcode, action_string, &default_action)) {\r
478     for (int i = 0; exit_action_strings[i]; i++) {\r
479       if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
480         action = i;\r
481         break;\r
482       }\r
483     }\r
484   }\r
485 \r
486   process_handle = 0;\r
487   pid = 0;\r
488   switch (action) {\r
489     /* Try to restart the service or return failure code to service manager */\r
490     case NSSM_EXIT_RESTART:\r
491       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service_name, code, exit_action_strings[action], exe, 0);\r
492       while (monitor_service()) {\r
493         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, exe, service_name, 0);\r
494         Sleep(30000);\r
495       }\r
496     break;\r
497 \r
498     /* Do nothing, just like srvany would */\r
499     case NSSM_EXIT_IGNORE:\r
500       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service_name, code, exit_action_strings[action], exe, 0);\r
501       Sleep(INFINITE);\r
502     break;\r
503 \r
504     /* Tell the service manager we are finished */\r
505     case NSSM_EXIT_REALLY:\r
506       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service_name, code, exit_action_strings[action], 0);\r
507       stop_service(exitcode, true, default_action);\r
508     break;\r
509 \r
510     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
511     case NSSM_EXIT_UNCLEAN:\r
512       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service_name, code, exit_action_strings[action], 0);\r
513       exit(stop_service(exitcode, false, default_action));\r
514     break;\r
515   }\r
516 }\r
517 \r
518 void throttle_restart() {\r
519   /* This can't be a restart if the service is already running. */\r
520   if (! throttle++) return;\r
521 \r
522   int ms = throttle_milliseconds();\r
523 \r
524   if (throttle > 7) throttle = 8;\r
525 \r
526   char threshold[8], milliseconds[8];\r
527   _snprintf(threshold, sizeof(threshold), "%d", throttle_delay);\r
528   _snprintf(milliseconds, sizeof(milliseconds), "%d", ms);\r
529   log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service_name, threshold, milliseconds, 0);\r
530 \r
531   if (throttle_timer) {\r
532     ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));\r
533     throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
534     SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);\r
535   }\r
536 \r
537   service_status.dwCurrentState = SERVICE_PAUSED;\r
538   SetServiceStatus(service_handle, &service_status);\r
539 \r
540   if (throttle_timer) WaitForSingleObject(throttle_timer, INFINITE);\r
541   else Sleep(ms);\r
542 }\r