3 extern imports_t imports;
5 int get_process_creation_time(HANDLE process_handle, FILETIME *ft) {
6 FILETIME creation_time, exit_time, kernel_time, user_time;
8 if (! GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time, &user_time)) {
9 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSTIMES_FAILED, error_string(GetLastError()), 0);
13 memmove(ft, &creation_time, sizeof(creation_time));
18 int get_process_exit_time(HANDLE process_handle, FILETIME *ft) {
19 FILETIME creation_time, exit_time, kernel_time, user_time;
21 if (! GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time, &user_time)) {
22 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSTIMES_FAILED, error_string(GetLastError()), 0);
26 memmove(ft, &exit_time, sizeof(exit_time));
31 int check_parent(nssm_service_t *service, PROCESSENTRY32 *pe, unsigned long ppid) {
32 /* Check parent process ID matches. */
33 if (pe->th32ParentProcessID != ppid) return 1;
36 Process IDs can be reused so do a sanity check by making sure the child
37 has been running for less time than the parent.
38 Though unlikely, it's possible that the parent exited and its process ID
39 was already reused, so we'll also compare against its exit time.
41 HANDLE process_handle = OpenProcess(PROCESS_QUERY_INFORMATION, false, pe->th32ProcessID);
42 if (! process_handle) {
44 _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pe->th32ProcessID);
45 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, service->name, error_string(GetLastError()), 0);
50 if (get_process_creation_time(process_handle, &ft)) {
51 CloseHandle(process_handle);
55 CloseHandle(process_handle);
57 /* Verify that the parent's creation time is not later. */
58 if (CompareFileTime(&service->creation_time, &ft) > 0) return 4;
60 /* Verify that the parent's exit time is not earlier. */
61 if (CompareFileTime(&service->exit_time, &ft) < 0) return 5;
66 /* Send some window messages and hope the window respects one or more. */
67 int CALLBACK kill_window(HWND window, LPARAM arg) {
68 kill_t *k = (kill_t *) arg;
71 if (! GetWindowThreadProcessId(window, &pid)) return 1;
72 if (pid != k->pid) return 1;
74 /* First try sending WM_CLOSE to request that the window close. */
75 k->signalled |= PostMessage(window, WM_CLOSE, k->exitcode, 0);
78 Then tell the window that the user is logging off and it should exit
79 without worrying about saving any data.
81 k->signalled |= PostMessage(window, WM_ENDSESSION, 1, ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL | ENDSESSION_LOGOFF);
87 Try to post a message to the message queues of threads associated with the
88 given process ID. Not all threads have message queues so there's no
89 guarantee of success, and we don't want to be left waiting for unsignalled
90 processes so this function returns only true if at least one thread was
93 int kill_threads(TCHAR *service_name, kill_t *k) {
96 /* Get a snapshot of all threads in the system. */
97 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
99 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_THREAD_FAILED, service_name, error_string(GetLastError()), 0);
104 ZeroMemory(&te, sizeof(te));
105 te.dwSize = sizeof(te);
107 if (! Thread32First(snapshot, &te)) {
108 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, service_name, error_string(GetLastError()), 0);
109 CloseHandle(snapshot);
113 /* This thread belongs to the doomed process so signal it. */
114 if (te.th32OwnerProcessID == k->pid) {
115 ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);
119 /* Try to get the next thread. */
120 if (! Thread32Next(snapshot, &te)) {
121 unsigned long error = GetLastError();
122 if (error == ERROR_NO_MORE_FILES) break;
123 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, service_name, error_string(GetLastError()), 0);
124 CloseHandle(snapshot);
128 if (te.th32OwnerProcessID == k->pid) {
129 ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);
133 CloseHandle(snapshot);
138 /* Give the process a chance to die gracefully. */
139 int kill_process(nssm_service_t *service, HANDLE process_handle, unsigned long pid, unsigned long exitcode) {
140 /* Shouldn't happen. */
141 if (! service) return 1;
143 if (! process_handle) return 1;
146 if (GetExitCodeProcess(process_handle, &ret)) {
147 if (ret != STILL_ACTIVE) return 1;
150 kill_t k = { pid, exitcode, 0 };
152 /* Close the stdin pipe. */
153 if (service->stdin_pipe) {
154 CloseHandle(service->stdin_pipe);
155 service->stdin_pipe = 0;
156 if (! await_shutdown(service, _T(__FUNCTION__), service->kill_console_delay)) return 1;
159 /* Try to send a Control-C event to the console. */
160 if (service->stop_method & NSSM_STOP_METHOD_CONSOLE) {
161 if (! kill_console(service)) return 1;
165 Try to post messages to the windows belonging to the given process ID.
166 If the process is a console application it won't have any windows so there's
167 no guarantee of success.
169 if (service->stop_method & NSSM_STOP_METHOD_WINDOW) {
170 EnumWindows((WNDENUMPROC) kill_window, (LPARAM) &k);
172 if (! await_shutdown(service, _T(__FUNCTION__), service->kill_window_delay)) return 1;
177 Try to post messages to any thread message queues associated with the
178 process. Console applications might have them (but probably won't) so
179 there's still no guarantee of success.
181 if (service->stop_method & NSSM_STOP_METHOD_THREADS) {
182 if (kill_threads(service->name, &k)) {
183 if (! await_shutdown(service, _T(__FUNCTION__), service->kill_threads_delay)) return 1;
187 /* We tried being nice. Time for extreme prejudice. */
188 if (service->stop_method & NSSM_STOP_METHOD_TERMINATE) {
189 return TerminateProcess(service->process_handle, exitcode);
195 /* Simulate a Control-C event to our console (shared with the app). */
196 int kill_console(nssm_service_t *service) {
199 if (! service) return 1;
201 /* Check we loaded AttachConsole(). */
202 if (! imports.AttachConsole) return 4;
204 /* Try to attach to the process's console. */
205 if (! imports.AttachConsole(service->pid)) {
206 ret = GetLastError();
209 case ERROR_INVALID_HANDLE:
210 /* The app doesn't have a console. */
213 case ERROR_GEN_FAILURE:
214 /* The app already exited. */
217 case ERROR_ACCESS_DENIED:
219 /* We already have a console. */
220 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ATTACHCONSOLE_FAILED, service->name, error_string(ret), 0);
225 /* Ignore the event ourselves. */
227 if (! SetConsoleCtrlHandler(0, TRUE)) {
228 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETCONSOLECTRLHANDLER_FAILED, service->name, error_string(GetLastError()), 0);
232 /* Send the event. */
234 if (! GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) {
235 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GENERATECONSOLECTRLEVENT_FAILED, service->name, error_string(GetLastError()), 0);
240 /* Detach from the console. */
241 if (! FreeConsole()) {
242 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_FREECONSOLE_FAILED, service->name, error_string(GetLastError()), 0);
245 /* Wait for process to exit. */
246 if (await_shutdown(service, _T(__FUNCTION__), service->kill_console_delay)) ret = 6;
251 void kill_process_tree(nssm_service_t *service, unsigned long pid, unsigned long exitcode, unsigned long ppid) {
252 /* Shouldn't happen unless the service failed to start. */
255 TCHAR pid_string[16], code[16];
256 _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pid);
257 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);
258 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILLING, service->name, pid_string, code, 0);
260 /* Get a snapshot of all processes in the system. */
261 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
263 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_PROCESS_FAILED, service->name, error_string(GetLastError()), 0);
268 ZeroMemory(&pe, sizeof(pe));
269 pe.dwSize = sizeof(pe);
271 if (! Process32First(snapshot, &pe)) {
272 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, service->name, error_string(GetLastError()), 0);
273 CloseHandle(snapshot);
277 /* This is a child of the doomed process so kill it. */
278 if (! check_parent(service, &pe, pid)) kill_process_tree(service, pe.th32ProcessID, exitcode, ppid);
281 /* Try to get the next process. */
282 if (! Process32Next(snapshot, &pe)) {
283 unsigned long ret = GetLastError();
284 if (ret == ERROR_NO_MORE_FILES) break;
285 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, service->name, error_string(GetLastError()), 0);
286 CloseHandle(snapshot);
290 if (! check_parent(service, &pe, pid)) kill_process_tree(service, pe.th32ProcessID, exitcode, ppid);
293 CloseHandle(snapshot);
295 /* We will need a process handle in order to call TerminateProcess() later. */
296 HANDLE process_handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, false, pid);
297 if (! process_handle) {
298 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, service->name, error_string(GetLastError()), 0);
302 TCHAR ppid_string[16];
303 _sntprintf_s(ppid_string, _countof(ppid_string), _TRUNCATE, _T("%lu"), ppid);
304 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILL_PROCESS_TREE, pid_string, ppid_string, service->name, 0);
305 if (! kill_process(service, process_handle, pid, exitcode)) {
306 /* Maybe it already died. */
308 if (! GetExitCodeProcess(process_handle, &ret) || ret == STILL_ACTIVE) {
309 if (service->stop_method & NSSM_STOP_METHOD_TERMINATE) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_TERMINATEPROCESS_FAILED, pid_string, service->name, error_string(GetLastError()), 0);
310 else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_PROCESS_STILL_ACTIVE, service->name, pid_string, NSSM, NSSM_REG_STOP_METHOD_SKIP, 0);
314 CloseHandle(process_handle);