Log that we received control messages.
[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   return;\r
229 \r
230   SERVICE_FAILURE_ACTIONS_FLAG flag;\r
231   ZeroMemory(&flag, sizeof(flag));\r
232   flag.fFailureActionsOnNonCrashFailures = true;\r
233 \r
234   /* This functionality was added in Vista so the call may fail */\r
235   ChangeServiceConfig2(service, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag);\r
236 }\r
237 \r
238 int monitor_service() {\r
239   /* Set service status to started */\r
240   int ret = start_service();\r
241   if (ret) {\r
242     char code[16];\r
243     _snprintf(code, sizeof(code), "%d", ret);\r
244     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, exe, service_name, ret, 0);\r
245     return ret;\r
246   }\r
247   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, exe, flags, service_name, dir, 0);\r
248 \r
249   /* Monitor service service */\r
250   if (! RegisterWaitForSingleObject(&wait_handle, process_handle, end_service, (void *) pid, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {\r
251     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service_name, exe, error_string(GetLastError()), 0);\r
252   }\r
253 \r
254   return 0;\r
255 }\r
256 \r
257 char *service_control_text(unsigned long control) {\r
258   switch (control) {\r
259     /* HACK: there is no SERVICE_CONTROL_START constant */\r
260     case 0: return "START";\r
261     case SERVICE_CONTROL_STOP: return "STOP";\r
262     case SERVICE_CONTROL_SHUTDOWN: return "SHUTDOWN";\r
263     case SERVICE_CONTROL_PAUSE: return "PAUSE";\r
264     case SERVICE_CONTROL_CONTINUE: return "CONTINUE";\r
265     case SERVICE_CONTROL_INTERROGATE: return "INTERROGATE";\r
266     default: return 0;\r
267   }\r
268 }\r
269 \r
270 void log_service_control(char *service_name, unsigned long control, bool handled) {\r
271   char *text = service_control_text(control);\r
272   unsigned long event;\r
273 \r
274   if (! text) {\r
275     /* "0x" + 8 x hex + NULL */\r
276     text = (char *) HeapAlloc(GetProcessHeap(), 0, 11);\r
277     if (! text) {\r
278       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control", 0);\r
279       return;\r
280     }\r
281     if (_snprintf(text, 11, "0x%08x", control) < 0) {\r
282       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control", 0);\r
283       HeapFree(GetProcessHeap(), 0, text);\r
284       return;\r
285     }\r
286 \r
287     event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;\r
288   }\r
289   else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;\r
290   else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;\r
291 \r
292   log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);\r
293 \r
294   if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {\r
295     HeapFree(GetProcessHeap(), 0, text);\r
296   }\r
297 }\r
298 \r
299 /* Service control handler */\r
300 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {\r
301   switch (control) {\r
302     case SERVICE_CONTROL_SHUTDOWN:\r
303     case SERVICE_CONTROL_STOP:\r
304       log_service_control(service_name, control, true);\r
305       stop_service(0, true, true);\r
306       return NO_ERROR;\r
307 \r
308     case SERVICE_CONTROL_CONTINUE:\r
309       log_service_control(service_name, control, true);\r
310       if (! throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;\r
311       throttle = 0;\r
312       ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));\r
313       SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);\r
314       service_status.dwCurrentState = SERVICE_CONTINUE_PENDING;\r
315       service_status.dwWaitHint = throttle_milliseconds() + NSSM_WAITHINT_MARGIN;\r
316       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service_name, 0);\r
317       SetServiceStatus(service_handle, &service_status);\r
318       return NO_ERROR;\r
319 \r
320     case SERVICE_CONTROL_PAUSE:\r
321       /*\r
322         We don't accept pause messages but it isn't possible to register\r
323         only for continue messages so we have to handle this case.\r
324       */\r
325       log_service_control(service_name, control, false);\r
326       return ERROR_CALL_NOT_IMPLEMENTED;\r
327   }\r
328 \r
329   /* Unknown control */\r
330   log_service_control(service_name, control, false);\r
331   return ERROR_CALL_NOT_IMPLEMENTED;\r
332 }\r
333 \r
334 /* Start the service */\r
335 int start_service() {\r
336   stopping = false;\r
337 \r
338   if (process_handle) return 0;\r
339 \r
340   /* Allocate a STARTUPINFO structure for a new process */\r
341   STARTUPINFO si;\r
342   ZeroMemory(&si, sizeof(si));\r
343   si.cb = sizeof(si);\r
344 \r
345   /* Allocate a PROCESSINFO structure for the process */\r
346   PROCESS_INFORMATION pi;\r
347   ZeroMemory(&pi, sizeof(pi));\r
348 \r
349   /* Get startup parameters */\r
350   char *env = 0;\r
351   int ret = get_parameters(service_name, exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir), &env, &throttle_delay);\r
352   if (ret) {\r
353     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service_name, 0);\r
354     return stop_service(2, true, true);\r
355   }\r
356 \r
357   /* Launch executable with arguments */\r
358   char cmd[CMD_LENGTH];\r
359   if (_snprintf(cmd, sizeof(cmd), "\"%s\" %s", exe, flags) < 0) {\r
360     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "command line", "start_service", 0);\r
361     return stop_service(2, true, true);\r
362   }\r
363 \r
364   throttle_restart();\r
365 \r
366   if (! CreateProcess(0, cmd, 0, 0, false, 0, env, dir, &si, &pi)) {\r
367     unsigned long error = GetLastError();\r
368     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
369     else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service_name, exe, error_string(error), 0);\r
370     return stop_service(3, true, true);\r
371   }\r
372   process_handle = pi.hProcess;\r
373   pid = pi.dwProcessId;\r
374 \r
375   /* Signal successful start */\r
376   service_status.dwCurrentState = SERVICE_RUNNING;\r
377   SetServiceStatus(service_handle, &service_status);\r
378 \r
379   /* Wait for a clean startup. */\r
380   if (WaitForSingleObject(process_handle, throttle_delay) == WAIT_TIMEOUT) throttle = 0;\r
381 \r
382   return 0;\r
383 }\r
384 \r
385 /* Stop the service */\r
386 int stop_service(unsigned long exitcode, bool graceful, bool default_action) {\r
387   if (default_action && ! exitcode && ! graceful) {\r
388     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
389     graceful = true;\r
390   }\r
391 \r
392   /* Signal we are stopping */\r
393   if (graceful) {\r
394     service_status.dwCurrentState = SERVICE_STOP_PENDING;\r
395     service_status.dwWaitHint = NSSM_KILL_WINDOW_GRACE_PERIOD + NSSM_KILL_THREADS_GRACE_PERIOD + NSSM_WAITHINT_MARGIN;\r
396     SetServiceStatus(service_handle, &service_status);\r
397   }\r
398 \r
399   /* Nothing to do if server isn't running */\r
400   if (pid) {\r
401     /* Shut down server */\r
402     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service_name, exe, 0);\r
403     kill_process(service_name, process_handle, pid, 0);\r
404     process_handle = 0;\r
405   }\r
406   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);\r
407 \r
408   end_service((void *) pid, true);\r
409 \r
410   /* Signal we stopped */\r
411   if (graceful) {\r
412     service_status.dwCurrentState = SERVICE_STOPPED;\r
413     if (exitcode) {\r
414       service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
415       service_status.dwServiceSpecificExitCode = exitcode;\r
416     }\r
417     else {\r
418       service_status.dwWin32ExitCode = NO_ERROR;\r
419       service_status.dwServiceSpecificExitCode = 0;\r
420     }\r
421     SetServiceStatus(service_handle, &service_status);\r
422   }\r
423 \r
424   return exitcode;\r
425 }\r
426 \r
427 /* Callback function triggered when the server exits */\r
428 void CALLBACK end_service(void *arg, unsigned char why) {\r
429   if (stopping) return;\r
430 \r
431   stopping = true;\r
432 \r
433   pid = (unsigned long) arg;\r
434 \r
435   /* Check exit code */\r
436   unsigned long exitcode = 0;\r
437   GetExitCodeProcess(process_handle, &exitcode);\r
438 \r
439   /* Clean up. */\r
440   kill_process_tree(service_name, pid, exitcode, pid);\r
441 \r
442   /*\r
443     The why argument is true if our wait timed out or false otherwise.\r
444     Our wait is infinite so why will never be true when called by the system.\r
445     If it is indeed true, assume we were called from stop_service() because\r
446     this is a controlled shutdown, and don't take any restart action.\r
447   */\r
448   if (why) return;\r
449 \r
450   char code[16];\r
451   _snprintf(code, sizeof(code), "%d", exitcode);\r
452   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, exe, service_name, code, 0);\r
453 \r
454   /* What action should we take? */\r
455   int action = NSSM_EXIT_RESTART;\r
456   unsigned char action_string[ACTION_LEN];\r
457   bool default_action;\r
458   if (! get_exit_action(service_name, &exitcode, action_string, &default_action)) {\r
459     for (int i = 0; exit_action_strings[i]; i++) {\r
460       if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
461         action = i;\r
462         break;\r
463       }\r
464     }\r
465   }\r
466 \r
467   process_handle = 0;\r
468   pid = 0;\r
469   switch (action) {\r
470     /* Try to restart the service or return failure code to service manager */\r
471     case NSSM_EXIT_RESTART:\r
472       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service_name, code, exit_action_strings[action], exe, 0);\r
473       while (monitor_service()) {\r
474         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, exe, service_name, 0);\r
475         Sleep(30000);\r
476       }\r
477     break;\r
478 \r
479     /* Do nothing, just like srvany would */\r
480     case NSSM_EXIT_IGNORE:\r
481       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service_name, code, exit_action_strings[action], exe, 0);\r
482       Sleep(INFINITE);\r
483     break;\r
484 \r
485     /* Tell the service manager we are finished */\r
486     case NSSM_EXIT_REALLY:\r
487       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service_name, code, exit_action_strings[action], 0);\r
488       stop_service(exitcode, true, default_action);\r
489     break;\r
490 \r
491     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
492     case NSSM_EXIT_UNCLEAN:\r
493       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service_name, code, exit_action_strings[action], 0);\r
494       exit(stop_service(exitcode, false, default_action));\r
495     break;\r
496   }\r
497 }\r
498 \r
499 void throttle_restart() {\r
500   /* This can't be a restart if the service is already running. */\r
501   if (! throttle++) return;\r
502 \r
503   int ms = throttle_milliseconds();\r
504 \r
505   if (throttle > 7) throttle = 8;\r
506 \r
507   char threshold[8], milliseconds[8];\r
508   _snprintf(threshold, sizeof(threshold), "%d", throttle_delay);\r
509   _snprintf(milliseconds, sizeof(milliseconds), "%d", ms);\r
510   log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service_name, threshold, milliseconds, 0);\r
511 \r
512   if (throttle_timer) {\r
513     ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));\r
514     throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
515     SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);\r
516   }\r
517 \r
518   service_status.dwCurrentState = SERVICE_PAUSED;\r
519   SetServiceStatus(service_handle, &service_status);\r
520 \r
521   if (throttle_timer) WaitForSingleObject(throttle_timer, INFINITE);\r
522   else Sleep(ms);\r
523 }\r