3 extern imports_t imports;
\r
5 void service_kill_t(nssm_service_t *service, kill_t *k) {
\r
6 if (! service) return;
\r
9 ZeroMemory(k, sizeof(*k));
\r
10 k->name = service->name;
\r
11 k->process_handle = service->process_handle;
\r
12 k->pid = service->pid;
\r
13 k->exitcode = service->exitcode;
\r
14 k->stop_method = service->stop_method;
\r
15 k->kill_console_delay = service->kill_console_delay;
\r
16 k->kill_window_delay = service->kill_window_delay;
\r
17 k->kill_threads_delay = service->kill_threads_delay;
\r
18 k->status_handle = service->status_handle;
\r
19 k->status = &service->status;
\r
20 k->creation_time = service->creation_time;
\r
21 k->exit_time = service->exit_time;
\r
24 int get_process_creation_time(HANDLE process_handle, FILETIME *ft) {
\r
25 FILETIME creation_time, exit_time, kernel_time, user_time;
\r
27 if (! GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time, &user_time)) {
\r
28 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSTIMES_FAILED, error_string(GetLastError()), 0);
\r
32 memmove(ft, &creation_time, sizeof(creation_time));
\r
37 int get_process_exit_time(HANDLE process_handle, FILETIME *ft) {
\r
38 FILETIME creation_time, exit_time, kernel_time, user_time;
\r
40 if (! GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time, &user_time)) {
\r
41 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSTIMES_FAILED, error_string(GetLastError()), 0);
\r
45 if (! (exit_time.dwLowDateTime || exit_time.dwHighDateTime)) return 2;
\r
46 memmove(ft, &exit_time, sizeof(exit_time));
\r
51 int check_parent(kill_t *k, PROCESSENTRY32 *pe, unsigned long ppid) {
\r
52 /* Check parent process ID matches. */
\r
53 if (pe->th32ParentProcessID != ppid) return 1;
\r
56 Process IDs can be reused so do a sanity check by making sure the child
\r
57 has been running for less time than the parent.
\r
58 Though unlikely, it's possible that the parent exited and its process ID
\r
59 was already reused, so we'll also compare against its exit time.
\r
61 HANDLE process_handle = OpenProcess(PROCESS_QUERY_INFORMATION, false, pe->th32ProcessID);
\r
62 if (! process_handle) {
\r
63 TCHAR pid_string[16];
\r
64 _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pe->th32ProcessID);
\r
65 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0);
\r
70 if (get_process_creation_time(process_handle, &ft)) {
\r
71 CloseHandle(process_handle);
\r
75 CloseHandle(process_handle);
\r
77 /* Verify that the parent's creation time is not later. */
\r
78 if (CompareFileTime(&k->creation_time, &ft) > 0) return 4;
\r
80 /* Verify that the parent's exit time is not earlier. */
\r
81 if (CompareFileTime(&k->exit_time, &ft) < 0) return 5;
\r
86 /* Send some window messages and hope the window respects one or more. */
\r
87 int CALLBACK kill_window(HWND window, LPARAM arg) {
\r
88 kill_t *k = (kill_t *) arg;
\r
91 if (! GetWindowThreadProcessId(window, &pid)) return 1;
\r
92 if (pid != k->pid) return 1;
\r
94 /* First try sending WM_CLOSE to request that the window close. */
\r
95 k->signalled |= PostMessage(window, WM_CLOSE, k->exitcode, 0);
\r
98 Then tell the window that the user is logging off and it should exit
\r
99 without worrying about saving any data.
\r
101 k->signalled |= PostMessage(window, WM_ENDSESSION, 1, ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL | ENDSESSION_LOGOFF);
\r
107 Try to post a message to the message queues of threads associated with the
\r
108 given process ID. Not all threads have message queues so there's no
\r
109 guarantee of success, and we don't want to be left waiting for unsignalled
\r
110 processes so this function returns only true if at least one thread was
\r
111 successfully prodded.
\r
113 int kill_threads(nssm_service_t *service, kill_t *k) {
\r
116 /* Get a snapshot of all threads in the system. */
\r
117 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
\r
118 if (snapshot == INVALID_HANDLE_VALUE) {
\r
119 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_THREAD_FAILED, k->name, error_string(GetLastError()), 0);
\r
124 ZeroMemory(&te, sizeof(te));
\r
125 te.dwSize = sizeof(te);
\r
127 if (! Thread32First(snapshot, &te)) {
\r
128 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);
\r
129 CloseHandle(snapshot);
\r
133 /* This thread belongs to the doomed process so signal it. */
\r
134 if (te.th32OwnerProcessID == k->pid) {
\r
135 ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);
\r
139 /* Try to get the next thread. */
\r
140 if (! Thread32Next(snapshot, &te)) {
\r
141 unsigned long error = GetLastError();
\r
142 if (error == ERROR_NO_MORE_FILES) break;
\r
143 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);
\r
144 CloseHandle(snapshot);
\r
148 if (te.th32OwnerProcessID == k->pid) {
\r
149 ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);
\r
153 CloseHandle(snapshot);
\r
158 int kill_threads(kill_t *k) {
\r
159 return kill_threads(NULL, k);
\r
162 /* Give the process a chance to die gracefully. */
\r
163 int kill_process(nssm_service_t *service, kill_t *k) {
\r
167 if (GetExitCodeProcess(k->process_handle, &ret)) {
\r
168 if (ret != STILL_ACTIVE) return 1;
\r
171 /* Try to send a Control-C event to the console. */
\r
172 if (k->stop_method & NSSM_STOP_METHOD_CONSOLE) {
\r
173 if (! kill_console(k)) return 1;
\r
177 Try to post messages to the windows belonging to the given process ID.
\r
178 If the process is a console application it won't have any windows so there's
\r
179 no guarantee of success.
\r
181 if (k->stop_method & NSSM_STOP_METHOD_WINDOW) {
\r
182 EnumWindows((WNDENUMPROC) kill_window, (LPARAM) k);
\r
183 if (k->signalled) {
\r
184 if (! await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_window_delay)) return 1;
\r
190 Try to post messages to any thread message queues associated with the
\r
191 process. Console applications might have them (but probably won't) so
\r
192 there's still no guarantee of success.
\r
194 if (k->stop_method & NSSM_STOP_METHOD_THREADS) {
\r
195 if (kill_threads(k)) {
\r
196 if (! await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_threads_delay)) return 1;
\r
200 /* We tried being nice. Time for extreme prejudice. */
\r
201 if (k->stop_method & NSSM_STOP_METHOD_TERMINATE) {
\r
202 return TerminateProcess(k->process_handle, k->exitcode);
\r
208 int kill_process(kill_t *k) {
\r
209 return kill_process(NULL, k);
\r
212 /* Simulate a Control-C event to our console (shared with the app). */
\r
213 int kill_console(nssm_service_t *service, kill_t *k) {
\r
218 /* Check we loaded AttachConsole(). */
\r
219 if (! imports.AttachConsole) return 4;
\r
221 /* Try to attach to the process's console. */
\r
222 if (! imports.AttachConsole(k->pid)) {
\r
223 ret = GetLastError();
\r
226 case ERROR_INVALID_HANDLE:
\r
227 /* The app doesn't have a console. */
\r
230 case ERROR_GEN_FAILURE:
\r
231 /* The app already exited. */
\r
234 case ERROR_ACCESS_DENIED:
\r
236 /* We already have a console. */
\r
237 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ATTACHCONSOLE_FAILED, k->name, error_string(ret), 0);
\r
242 /* Ignore the event ourselves. */
\r
244 BOOL ignored = SetConsoleCtrlHandler(0, TRUE);
\r
246 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETCONSOLECTRLHANDLER_FAILED, k->name, error_string(GetLastError()), 0);
\r
250 /* Send the event. */
\r
252 if (! GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) {
\r
253 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GENERATECONSOLECTRLEVENT_FAILED, k->name, error_string(GetLastError()), 0);
\r
258 /* Detach from the console. */
\r
259 if (! FreeConsole()) {
\r
260 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_FREECONSOLE_FAILED, k->name, error_string(GetLastError()), 0);
\r
263 /* Wait for process to exit. */
\r
264 if (await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_console_delay)) ret = 6;
\r
266 /* Remove our handler. */
\r
267 if (ignored && ! SetConsoleCtrlHandler(0, FALSE)) {
\r
268 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETCONSOLECTRLHANDLER_FAILED, k->name, error_string(GetLastError()), 0);
\r
274 int kill_console(kill_t *k) {
\r
275 return kill_console(NULL, k);
\r
278 void kill_process_tree(nssm_service_t * service, kill_t *k, unsigned long ppid) {
\r
280 /* Shouldn't happen unless the service failed to start. */
\r
281 if (! k->pid) return; /* XXX: needed? */
\r
282 unsigned long pid = k->pid;
\r
284 TCHAR pid_string[16], code[16];
\r
285 _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pid);
\r
286 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), k->exitcode);
\r
287 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILLING, k->name, pid_string, code, 0);
\r
289 /* We will need a process handle in order to call TerminateProcess() later. */
\r
290 HANDLE process_handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, false, pid);
\r
291 if (process_handle) {
\r
292 /* Kill this process first, then its descendents. */
\r
293 TCHAR ppid_string[16];
\r
294 _sntprintf_s(ppid_string, _countof(ppid_string), _TRUNCATE, _T("%lu"), ppid);
\r
295 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILL_PROCESS_TREE, pid_string, ppid_string, k->name, 0);
\r
296 k->process_handle = process_handle; /* XXX: open directly? */
\r
297 if (! kill_process(k)) {
\r
298 /* Maybe it already died. */
\r
300 if (! GetExitCodeProcess(process_handle, &ret) || ret == STILL_ACTIVE) {
\r
301 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
302 else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_PROCESS_STILL_ACTIVE, k->name, pid_string, NSSM, NSSM_REG_STOP_METHOD_SKIP, 0);
\r
306 CloseHandle(process_handle);
\r
308 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0);
\r
310 /* Get a snapshot of all processes in the system. */
\r
311 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
\r
312 if (snapshot == INVALID_HANDLE_VALUE) {
\r
313 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_PROCESS_FAILED, k->name, error_string(GetLastError()), 0);
\r
318 ZeroMemory(&pe, sizeof(pe));
\r
319 pe.dwSize = sizeof(pe);
\r
321 if (! Process32First(snapshot, &pe)) {
\r
322 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);
\r
323 CloseHandle(snapshot);
\r
327 /* This is a child of the doomed process so kill it. */
\r
328 if (! check_parent(k, &pe, pid)) {
\r
329 k->pid = pe.th32ProcessID;
\r
330 kill_process_tree(k, ppid);
\r
335 /* Try to get the next process. */
\r
336 if (! Process32Next(snapshot, &pe)) {
\r
337 unsigned long ret = GetLastError();
\r
338 if (ret == ERROR_NO_MORE_FILES) break;
\r
339 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);
\r
340 CloseHandle(snapshot);
\r
344 if (! check_parent(k, &pe, pid)) {
\r
345 k->pid = pe.th32ProcessID;
\r
346 kill_process_tree(k, ppid);
\r
351 CloseHandle(snapshot);
\r
354 void kill_process_tree(kill_t *k, unsigned long ppid) {
\r
355 return kill_process_tree(NULL, k, ppid);
\r