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