Fixed crash when stopping the service.
[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, 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   /*
150     Try to post messages to the windows belonging to the given process ID.
151     If the process is a console application it won't have any windows so there's
152     no guarantee of success.
153   */
154   EnumWindows((WNDENUMPROC) kill_window, (LPARAM) &k);
155   if (k.signalled) {
156     if (! WaitForSingleObject(process_handle, NSSM_KILL_WINDOW_GRACE_PERIOD)) return 1;
157   }
158
159   /*
160     Try to post messages to any thread message queues associated with the
161     process.  Console applications might have them (but probably won't) so
162     there's still no guarantee of success.
163   */
164   if (kill_threads(service_name, &k)) {
165     if (! WaitForSingleObject(process_handle, NSSM_KILL_THREADS_GRACE_PERIOD)) return 1;
166   }
167
168   /* We tried being nice.  Time for extreme prejudice. */
169   return TerminateProcess(process_handle, exitcode);
170 }
171
172 void kill_process_tree(char *service_name, unsigned long pid, unsigned long exitcode, unsigned long ppid, FILETIME *parent_creation_time, FILETIME *parent_exit_time) {
173   /* Shouldn't happen unless the service failed to start. */
174   if (! pid) return;
175
176   char pid_string[16], code[16];
177   _snprintf(pid_string, sizeof(pid_string), "%d", pid);
178   _snprintf(code, sizeof(code), "%d", exitcode);
179   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILLING, service_name, pid_string, code, 0);
180
181   /* Get a snapshot of all processes in the system. */
182   HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
183   if (! snapshot) {
184     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_PROCESS_FAILED, service_name, error_string(GetLastError()), 0);
185     return;
186   }
187
188   PROCESSENTRY32 pe;
189   ZeroMemory(&pe, sizeof(pe));
190   pe.dwSize = sizeof(pe);
191
192   if (! Process32First(snapshot, &pe)) {
193     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, service_name, error_string(GetLastError()), 0);
194     CloseHandle(snapshot);
195     return;
196   }
197
198   /* This is a child of the doomed process so kill it. */
199   if (! check_parent(service_name, &pe, pid, parent_creation_time, parent_exit_time)) kill_process_tree(service_name, pe.th32ProcessID, exitcode, ppid, parent_creation_time, parent_exit_time);
200
201   while (true) {
202     /* Try to get the next process. */
203     if (! Process32Next(snapshot, &pe)) {
204       unsigned long ret = GetLastError();
205       if (ret == ERROR_NO_MORE_FILES) break;
206       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, service_name, error_string(GetLastError()), 0);
207       CloseHandle(snapshot);
208       return;
209     }
210
211     if (! check_parent(service_name, &pe, pid, parent_creation_time, parent_exit_time)) kill_process_tree(service_name, pe.th32ProcessID, exitcode, ppid, parent_creation_time, parent_exit_time);
212   }
213
214   CloseHandle(snapshot);
215
216   /* We will need a process handle in order to call TerminateProcess() later. */
217   HANDLE process_handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, false, pid);
218   if (! process_handle) {
219     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, service_name, error_string(GetLastError()), 0);
220     return;
221   }
222
223   char ppid_string[16];
224   _snprintf(ppid_string, sizeof(ppid_string), "%d", ppid);
225   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILL_PROCESS_TREE, pid_string, ppid_string, service_name, 0);
226   if (! kill_process(service_name, process_handle, pid, exitcode)) {
227     /* Maybe it already died. */
228     unsigned long ret;
229     if (! GetExitCodeProcess(process_handle, &ret) || ret == STILL_ACTIVE) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_TERMINATEPROCESS_FAILED, pid_string, service_name, error_string(GetLastError()), 0);
230   }
231
232   CloseHandle(process_handle);
233 }