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