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