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