3 int get_process_creation_time(HANDLE process_handle, FILETIME *ft) {
4 FILETIME creation_time, exit_time, kernel_time, user_time;
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);
11 memmove(ft, &creation_time, sizeof(creation_time));
16 int get_process_exit_time(HANDLE process_handle, FILETIME *ft) {
17 FILETIME creation_time, exit_time, kernel_time, user_time;
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);
24 memmove(ft, &exit_time, sizeof(exit_time));
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;
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.
39 HANDLE process_handle = OpenProcess(PROCESS_QUERY_INFORMATION, false, pe->th32ProcessID);
40 if (! process_handle) {
42 _snprintf_s(pid_string, sizeof(pid_string), _TRUNCATE, "%d", pe->th32ProcessID);
43 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, service_name, error_string(GetLastError()), 0);
48 if (get_process_creation_time(process_handle, &ft)) {
49 CloseHandle(process_handle);
53 CloseHandle(process_handle);
55 /* Verify that the parent's creation time is not later. */
56 if (CompareFileTime(pft, &ft) > 0) return 4;
58 /* Verify that the parent's exit time is not earlier. */
59 if (CompareFileTime(exit_time, &ft) < 0) return 5;
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;
69 if (! GetWindowThreadProcessId(window, &pid)) return 1;
70 if (pid != k->pid) return 1;
72 /* First try sending WM_CLOSE to request that the window close. */
73 k->signalled |= PostMessage(window, WM_CLOSE, k->exitcode, 0);
76 Then tell the window that the user is logging off and it should exit
77 without worrying about saving any data.
79 k->signalled |= PostMessage(window, WM_ENDSESSION, 1, ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL | ENDSESSION_LOGOFF);
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
91 int kill_threads(char *service_name, kill_t *k) {
94 /* Get a snapshot of all threads in the system. */
95 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
97 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_THREAD_FAILED, service_name, error_string(GetLastError()), 0);
102 ZeroMemory(&te, sizeof(te));
103 te.dwSize = sizeof(te);
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);
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);
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);
126 if (te.th32OwnerProcessID == k->pid) {
127 ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);
131 CloseHandle(snapshot);
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. */
140 if (! process_handle) return 1;
143 if (GetExitCodeProcess(process_handle, &ret)) {
144 if (ret != STILL_ACTIVE) return 1;
147 kill_t k = { pid, exitcode, 0 };
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;
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.
159 if (stop_method & NSSM_STOP_METHOD_WINDOW) {
160 EnumWindows((WNDENUMPROC) kill_window, (LPARAM) &k);
162 if (! WaitForSingleObject(process_handle, NSSM_KILL_WINDOW_GRACE_PERIOD)) return 1;
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.
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;
177 /* We tried being nice. Time for extreme prejudice. */
178 if (stop_method & NSSM_STOP_METHOD_TERMINATE) {
179 return TerminateProcess(process_handle, exitcode);
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) {
189 /* Try to attach to the process's console. */
190 if (! AttachConsole(pid)) {
191 ret = GetLastError();
194 case ERROR_INVALID_HANDLE:
195 /* The app doesn't have a console. */
198 case ERROR_GEN_FAILURE:
199 /* The app already exited. */
202 case ERROR_ACCESS_DENIED:
204 /* We already have a console. */
205 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ATTACHCONSOLE_FAILED, service_name, error_string(ret), 0);
210 /* Ignore the event ourselves. */
212 if (! SetConsoleCtrlHandler(0, TRUE)) {
213 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETCONSOLECTRLHANDLER_FAILED, service_name, error_string(GetLastError()), 0);
217 /* Send the event. */
219 if (! GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) {
220 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GENERATECONSOLECTRLEVENT_FAILED, service_name, error_string(GetLastError()), 0);
225 /* Detach from the console. */
226 if (! FreeConsole()) {
227 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_FREECONSOLE_FAILED, service_name, error_string(GetLastError()), 0);
230 /* Wait for process to exit. */
231 if (WaitForSingleObject(process_handle, NSSM_KILL_CONSOLE_GRACE_PERIOD)) return 6;
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. */
240 char pid_string[16], code[16];
241 _snprintf_s(pid_string, sizeof(pid_string), _TRUNCATE, "%d", pid);
242 _snprintf_s(code, sizeof(code), _TRUNCATE, "%d", exitcode);
243 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILLING, service_name, pid_string, code, 0);
245 /* Get a snapshot of all processes in the system. */
246 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
248 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_PROCESS_FAILED, service_name, error_string(GetLastError()), 0);
253 ZeroMemory(&pe, sizeof(pe));
254 pe.dwSize = sizeof(pe);
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);
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);
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);
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);
278 CloseHandle(snapshot);
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);
287 char ppid_string[16];
288 _snprintf_s(ppid_string, sizeof(ppid_string), _TRUNCATE, "%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. */
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);
299 CloseHandle(process_handle);