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;
8 int get_process_creation_time(HANDLE process_handle, FILETIME *ft) {
9 FILETIME creation_time, exit_time, kernel_time, user_time;
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);
16 memmove(ft, &creation_time, sizeof(creation_time));
21 int get_process_exit_time(HANDLE process_handle, FILETIME *ft) {
22 FILETIME creation_time, exit_time, kernel_time, user_time;
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);
29 memmove(ft, &exit_time, sizeof(exit_time));
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;
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.
44 HANDLE process_handle = OpenProcess(PROCESS_QUERY_INFORMATION, false, pe->th32ProcessID);
45 if (! process_handle) {
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);
53 if (get_process_creation_time(process_handle, &ft)) {
54 CloseHandle(process_handle);
58 CloseHandle(process_handle);
60 /* Verify that the parent's creation time is not later. */
61 if (CompareFileTime(pft, &ft) > 0) return 4;
63 /* Verify that the parent's exit time is not earlier. */
64 if (CompareFileTime(exit_time, &ft) < 0) return 5;
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;
74 if (! GetWindowThreadProcessId(window, &pid)) return 1;
75 if (pid != k->pid) return 1;
77 /* First try sending WM_CLOSE to request that the window close. */
78 k->signalled |= PostMessage(window, WM_CLOSE, k->exitcode, 0);
81 Then tell the window that the user is logging off and it should exit
82 without worrying about saving any data.
84 k->signalled |= PostMessage(window, WM_ENDSESSION, 1, ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL | ENDSESSION_LOGOFF);
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
96 int kill_threads(char *service_name, kill_t *k) {
99 /* Get a snapshot of all threads in the system. */
100 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
102 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_THREAD_FAILED, service_name, error_string(GetLastError()), 0);
107 ZeroMemory(&te, sizeof(te));
108 te.dwSize = sizeof(te);
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);
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);
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);
131 if (te.th32OwnerProcessID == k->pid) {
132 ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);
136 CloseHandle(snapshot);
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. */
145 if (! process_handle) return 1;
148 if (GetExitCodeProcess(process_handle, &ret)) {
149 if (ret != STILL_ACTIVE) return 1;
152 kill_t k = { pid, exitcode, 0 };
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;
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.
164 if (stop_method & NSSM_STOP_METHOD_WINDOW) {
165 EnumWindows((WNDENUMPROC) kill_window, (LPARAM) &k);
167 if (! await_shutdown(__FUNCTION__, service_name, service_handle, service_status, process_handle, kill_window_delay)) return 1;
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.
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;
182 /* We tried being nice. Time for extreme prejudice. */
183 if (stop_method & NSSM_STOP_METHOD_TERMINATE) {
184 return TerminateProcess(process_handle, exitcode);
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) {
194 /* Check we loaded AttachConsole(). */
195 if (! imports.AttachConsole) return 4;
197 /* Try to attach to the process's console. */
198 if (! imports.AttachConsole(pid)) {
199 ret = GetLastError();
202 case ERROR_INVALID_HANDLE:
203 /* The app doesn't have a console. */
206 case ERROR_GEN_FAILURE:
207 /* The app already exited. */
210 case ERROR_ACCESS_DENIED:
212 /* We already have a console. */
213 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ATTACHCONSOLE_FAILED, service_name, error_string(ret), 0);
218 /* Ignore the event ourselves. */
220 if (! SetConsoleCtrlHandler(0, TRUE)) {
221 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETCONSOLECTRLHANDLER_FAILED, service_name, error_string(GetLastError()), 0);
225 /* Send the event. */
227 if (! GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) {
228 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GENERATECONSOLECTRLEVENT_FAILED, service_name, error_string(GetLastError()), 0);
233 /* Detach from the console. */
234 if (! FreeConsole()) {
235 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_FREECONSOLE_FAILED, service_name, error_string(GetLastError()), 0);
238 /* Wait for process to exit. */
239 if (await_shutdown(__FUNCTION__, service_name, service_handle, service_status, process_handle, kill_console_delay)) ret = 6;
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. */
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);
253 /* Get a snapshot of all processes in the system. */
254 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
256 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_PROCESS_FAILED, service_name, error_string(GetLastError()), 0);
261 ZeroMemory(&pe, sizeof(pe));
262 pe.dwSize = sizeof(pe);
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);
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);
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);
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);
286 CloseHandle(snapshot);
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);
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. */
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);
307 CloseHandle(process_handle);