Command to dump service configuration.
[nssm.git] / process.cpp
1 #include "nssm.h"\r
2 \r
3 extern imports_t imports;\r
4 \r
5 void service_kill_t(nssm_service_t *service, kill_t *k) {\r
6   if (! service) return;\r
7   if (! k) return;\r
8 \r
9   ZeroMemory(k, sizeof(*k));\r
10   k->name = service->name;\r
11   k->process_handle = service->process_handle;\r
12   k->pid = service->pid;\r
13   k->exitcode = service->exitcode;\r
14   k->stop_method = service->stop_method;\r
15   k->kill_console_delay = service->kill_console_delay;\r
16   k->kill_window_delay = service->kill_window_delay;\r
17   k->kill_threads_delay = service->kill_threads_delay;\r
18   k->status_handle = service->status_handle;\r
19   k->status = &service->status;\r
20   k->creation_time = service->creation_time;\r
21   k->exit_time = service->exit_time;\r
22 }\r
23 \r
24 int get_process_creation_time(HANDLE process_handle, FILETIME *ft) {\r
25   FILETIME creation_time, exit_time, kernel_time, user_time;\r
26 \r
27   if (! GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time, &user_time)) {\r
28     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSTIMES_FAILED, error_string(GetLastError()), 0);\r
29     return 1;\r
30   }\r
31 \r
32   memmove(ft, &creation_time, sizeof(creation_time));\r
33 \r
34   return 0;\r
35 }\r
36 \r
37 int get_process_exit_time(HANDLE process_handle, FILETIME *ft) {\r
38   FILETIME creation_time, exit_time, kernel_time, user_time;\r
39 \r
40   if (! GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time, &user_time)) {\r
41     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSTIMES_FAILED, error_string(GetLastError()), 0);\r
42     return 1;\r
43   }\r
44 \r
45   if (! (exit_time.dwLowDateTime || exit_time.dwHighDateTime)) return 2;\r
46   memmove(ft, &exit_time, sizeof(exit_time));\r
47 \r
48   return 0;\r
49 }\r
50 \r
51 int check_parent(kill_t *k, PROCESSENTRY32 *pe, unsigned long ppid) {\r
52   /* Check parent process ID matches. */\r
53   if (pe->th32ParentProcessID != ppid) return 1;\r
54 \r
55   /*\r
56     Process IDs can be reused so do a sanity check by making sure the child\r
57     has been running for less time than the parent.\r
58     Though unlikely, it's possible that the parent exited and its process ID\r
59     was already reused, so we'll also compare against its exit time.\r
60   */\r
61   HANDLE process_handle = OpenProcess(PROCESS_QUERY_INFORMATION, false, pe->th32ProcessID);\r
62   if (! process_handle) {\r
63     TCHAR pid_string[16];\r
64     _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pe->th32ProcessID);\r
65     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0);\r
66     return 2;\r
67   }\r
68 \r
69   FILETIME ft;\r
70   if (get_process_creation_time(process_handle, &ft)) {\r
71     CloseHandle(process_handle);\r
72     return 3;\r
73   }\r
74 \r
75   CloseHandle(process_handle);\r
76 \r
77   /* Verify that the parent's creation time is not later. */\r
78   if (CompareFileTime(&k->creation_time, &ft) > 0) return 4;\r
79 \r
80   /* Verify that the parent's exit time is not earlier. */\r
81   if (CompareFileTime(&k->exit_time, &ft) < 0) return 5;\r
82 \r
83   return 0;\r
84 }\r
85 \r
86 /* Send some window messages and hope the window respects one or more. */\r
87 int CALLBACK kill_window(HWND window, LPARAM arg) {\r
88   kill_t *k = (kill_t *) arg;\r
89 \r
90   unsigned long pid;\r
91   if (! GetWindowThreadProcessId(window, &pid)) return 1;\r
92   if (pid != k->pid) return 1;\r
93 \r
94   /* First try sending WM_CLOSE to request that the window close. */\r
95   k->signalled |= PostMessage(window, WM_CLOSE, k->exitcode, 0);\r
96 \r
97   /*\r
98     Then tell the window that the user is logging off and it should exit\r
99     without worrying about saving any data.\r
100   */\r
101   k->signalled |= PostMessage(window, WM_ENDSESSION, 1, ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL | ENDSESSION_LOGOFF);\r
102 \r
103   return 1;\r
104 }\r
105 \r
106 /*\r
107   Try to post a message to the message queues of threads associated with the\r
108   given process ID.  Not all threads have message queues so there's no\r
109   guarantee of success, and we don't want to be left waiting for unsignalled\r
110   processes so this function returns only true if at least one thread was\r
111   successfully prodded.\r
112 */\r
113 int kill_threads(nssm_service_t *service, kill_t *k) {\r
114   int ret = 0;\r
115 \r
116   /* Get a snapshot of all threads in the system. */\r
117   HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);\r
118   if (snapshot == INVALID_HANDLE_VALUE) {\r
119     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_THREAD_FAILED, k->name, error_string(GetLastError()), 0);\r
120     return 0;\r
121   }\r
122 \r
123   THREADENTRY32 te;\r
124   ZeroMemory(&te, sizeof(te));\r
125   te.dwSize = sizeof(te);\r
126 \r
127   if (! Thread32First(snapshot, &te)) {\r
128     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);\r
129     CloseHandle(snapshot);\r
130     return 0;\r
131   }\r
132 \r
133   /* This thread belongs to the doomed process so signal it. */\r
134   if (te.th32OwnerProcessID == k->pid) {\r
135     ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);\r
136   }\r
137 \r
138   while (true) {\r
139     /* Try to get the next thread. */\r
140     if (! Thread32Next(snapshot, &te)) {\r
141       unsigned long error = GetLastError();\r
142       if (error == ERROR_NO_MORE_FILES) break;\r
143       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);\r
144       CloseHandle(snapshot);\r
145       return ret;\r
146     }\r
147 \r
148     if (te.th32OwnerProcessID == k->pid) {\r
149       ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);\r
150     }\r
151   }\r
152 \r
153   CloseHandle(snapshot);\r
154 \r
155   return ret;\r
156 }\r
157 \r
158 int kill_threads(kill_t *k) {\r
159   return kill_threads(NULL, k);\r
160 }\r
161 \r
162 /* Give the process a chance to die gracefully. */\r
163 int kill_process(nssm_service_t *service, kill_t *k) {\r
164   if (! k) return 1;\r
165 \r
166   unsigned long ret;\r
167   if (GetExitCodeProcess(k->process_handle, &ret)) {\r
168     if (ret != STILL_ACTIVE) return 1;\r
169   }\r
170 \r
171   /* Try to send a Control-C event to the console. */\r
172   if (k->stop_method & NSSM_STOP_METHOD_CONSOLE) {\r
173     if (! kill_console(k)) return 1;\r
174   }\r
175 \r
176   /*\r
177     Try to post messages to the windows belonging to the given process ID.\r
178     If the process is a console application it won't have any windows so there's\r
179     no guarantee of success.\r
180   */\r
181   if (k->stop_method & NSSM_STOP_METHOD_WINDOW) {\r
182     EnumWindows((WNDENUMPROC) kill_window, (LPARAM) k);\r
183     if (k->signalled) {\r
184       if (! await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_window_delay)) return 1;\r
185       k->signalled = 0;\r
186     }\r
187   }\r
188 \r
189   /*\r
190     Try to post messages to any thread message queues associated with the\r
191     process.  Console applications might have them (but probably won't) so\r
192     there's still no guarantee of success.\r
193   */\r
194   if (k->stop_method & NSSM_STOP_METHOD_THREADS) {\r
195     if (kill_threads(k)) {\r
196       if (! await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_threads_delay)) return 1;\r
197     }\r
198   }\r
199 \r
200   /* We tried being nice.  Time for extreme prejudice. */\r
201   if (k->stop_method & NSSM_STOP_METHOD_TERMINATE) {\r
202     return TerminateProcess(k->process_handle, k->exitcode);\r
203   }\r
204 \r
205   return 0;\r
206 }\r
207 \r
208 int kill_process(kill_t *k) {\r
209   return kill_process(NULL, k);\r
210 }\r
211 \r
212 /* Simulate a Control-C event to our console (shared with the app). */\r
213 int kill_console(nssm_service_t *service, kill_t *k) {\r
214   unsigned long ret;\r
215 \r
216   if (! k) return 1;\r
217 \r
218   /* Check we loaded AttachConsole(). */\r
219   if (! imports.AttachConsole) return 4;\r
220 \r
221   /* Try to attach to the process's console. */\r
222   if (! imports.AttachConsole(k->pid)) {\r
223     ret = GetLastError();\r
224 \r
225     switch (ret) {\r
226       case ERROR_INVALID_HANDLE:\r
227         /* The app doesn't have a console. */\r
228         return 1;\r
229 \r
230       case ERROR_GEN_FAILURE:\r
231         /* The app already exited. */\r
232         return 2;\r
233 \r
234       case ERROR_ACCESS_DENIED:\r
235       default:\r
236         /* We already have a console. */\r
237         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ATTACHCONSOLE_FAILED, k->name, error_string(ret), 0);\r
238         return 3;\r
239     }\r
240   }\r
241 \r
242   /* Ignore the event ourselves. */\r
243   ret = 0;\r
244   BOOL ignored = SetConsoleCtrlHandler(0, TRUE);\r
245   if (! ignored) {\r
246     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETCONSOLECTRLHANDLER_FAILED, k->name, error_string(GetLastError()), 0);\r
247     ret = 4;\r
248   }\r
249 \r
250   /* Send the event. */\r
251   if (! ret) {\r
252     if (! GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) {\r
253       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GENERATECONSOLECTRLEVENT_FAILED, k->name, error_string(GetLastError()), 0);\r
254       ret = 5;\r
255     }\r
256   }\r
257 \r
258   /* Detach from the console. */\r
259   if (! FreeConsole()) {\r
260     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_FREECONSOLE_FAILED, k->name, error_string(GetLastError()), 0);\r
261   }\r
262 \r
263   /* Wait for process to exit. */\r
264   if (await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_console_delay)) ret = 6;\r
265 \r
266   /* Remove our handler. */\r
267   if (ignored && ! SetConsoleCtrlHandler(0, FALSE)) {\r
268     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETCONSOLECTRLHANDLER_FAILED, k->name, error_string(GetLastError()), 0);\r
269   }\r
270 \r
271   return ret;\r
272 }\r
273 \r
274 int kill_console(kill_t *k) {\r
275   return kill_console(NULL, k);\r
276 }\r
277 \r
278 void kill_process_tree(nssm_service_t * service, kill_t *k, unsigned long ppid) {\r
279   if (! k) return;\r
280   /* Shouldn't happen unless the service failed to start. */\r
281   if (! k->pid) return; /* XXX: needed? */\r
282   unsigned long pid = k->pid;\r
283 \r
284   TCHAR pid_string[16], code[16];\r
285   _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pid);\r
286   _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), k->exitcode);\r
287   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILLING, k->name, pid_string, code, 0);\r
288 \r
289   /* We will need a process handle in order to call TerminateProcess() later. */\r
290   HANDLE process_handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, false, pid);\r
291   if (process_handle) {\r
292     /* Kill this process first, then its descendents. */\r
293     TCHAR ppid_string[16];\r
294     _sntprintf_s(ppid_string, _countof(ppid_string), _TRUNCATE, _T("%lu"), ppid);\r
295     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILL_PROCESS_TREE, pid_string, ppid_string, k->name, 0);\r
296     k->process_handle = process_handle; /* XXX: open directly? */\r
297     if (! kill_process(k)) {\r
298       /* Maybe it already died. */\r
299       unsigned long ret;\r
300       if (! GetExitCodeProcess(process_handle, &ret) || ret == STILL_ACTIVE) {\r
301         if (k->stop_method & NSSM_STOP_METHOD_TERMINATE) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_TERMINATEPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0);\r
302         else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_PROCESS_STILL_ACTIVE, k->name, pid_string, NSSM, NSSM_REG_STOP_METHOD_SKIP, 0);\r
303       }\r
304     }\r
305 \r
306     CloseHandle(process_handle);\r
307   }\r
308   else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0);\r
309 \r
310   /* Get a snapshot of all processes in the system. */\r
311   HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);\r
312   if (snapshot == INVALID_HANDLE_VALUE) {\r
313     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_PROCESS_FAILED, k->name, error_string(GetLastError()), 0);\r
314     return;\r
315   }\r
316 \r
317   PROCESSENTRY32 pe;\r
318   ZeroMemory(&pe, sizeof(pe));\r
319   pe.dwSize = sizeof(pe);\r
320 \r
321   if (! Process32First(snapshot, &pe)) {\r
322     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);\r
323     CloseHandle(snapshot);\r
324     return;\r
325   }\r
326 \r
327   /* This is a child of the doomed process so kill it. */\r
328   if (! check_parent(k, &pe, pid)) {\r
329     k->pid = pe.th32ProcessID;\r
330     kill_process_tree(k, ppid);\r
331   }\r
332   k->pid = pid;\r
333 \r
334   while (true) {\r
335     /* Try to get the next process. */\r
336     if (! Process32Next(snapshot, &pe)) {\r
337       unsigned long ret = GetLastError();\r
338       if (ret == ERROR_NO_MORE_FILES) break;\r
339       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);\r
340       CloseHandle(snapshot);\r
341       return;\r
342     }\r
343 \r
344     if (! check_parent(k, &pe, pid)) {\r
345       k->pid = pe.th32ProcessID;\r
346       kill_process_tree(k, ppid);\r
347     }\r
348     k->pid = pid;\r
349   }\r
350 \r
351   CloseHandle(snapshot);\r
352 }\r
353 \r
354 void kill_process_tree(kill_t *k, unsigned long ppid) {\r
355   return kill_process_tree(NULL, k, ppid);\r
356 }\r