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