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 if (! (exit_time.dwLowDateTime || exit_time.dwHighDateTime)) return 2;
27 memmove(ft, &exit_time, sizeof(exit_time));
32 int check_parent(nssm_service_t *service, PROCESSENTRY32 *pe, unsigned long ppid) {
33 /* Check parent process ID matches. */
34 if (pe->th32ParentProcessID != ppid) return 1;
37 Process IDs can be reused so do a sanity check by making sure the child
38 has been running for less time than the parent.
39 Though unlikely, it's possible that the parent exited and its process ID
40 was already reused, so we'll also compare against its exit time.
42 HANDLE process_handle = OpenProcess(PROCESS_QUERY_INFORMATION, false, pe->th32ProcessID);
43 if (! process_handle) {
45 _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pe->th32ProcessID);
46 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, service->name, error_string(GetLastError()), 0);
51 if (get_process_creation_time(process_handle, &ft)) {
52 CloseHandle(process_handle);
56 CloseHandle(process_handle);
58 /* Verify that the parent's creation time is not later. */
59 if (CompareFileTime(&service->creation_time, &ft) > 0) return 4;
61 /* Verify that the parent's exit time is not earlier. */
62 if (CompareFileTime(&service->exit_time, &ft) < 0) return 5;
67 /* Send some window messages and hope the window respects one or more. */
68 int CALLBACK kill_window(HWND window, LPARAM arg) {
69 kill_t *k = (kill_t *) arg;
72 if (! GetWindowThreadProcessId(window, &pid)) return 1;
73 if (pid != k->pid) return 1;
75 /* First try sending WM_CLOSE to request that the window close. */
76 k->signalled |= PostMessage(window, WM_CLOSE, k->exitcode, 0);
79 Then tell the window that the user is logging off and it should exit
80 without worrying about saving any data.
82 k->signalled |= PostMessage(window, WM_ENDSESSION, 1, ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL | ENDSESSION_LOGOFF);
88 Try to post a message to the message queues of threads associated with the
89 given process ID. Not all threads have message queues so there's no
90 guarantee of success, and we don't want to be left waiting for unsignalled
91 processes so this function returns only true if at least one thread was
94 int kill_threads(TCHAR *service_name, kill_t *k) {
97 /* Get a snapshot of all threads in the system. */
98 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
100 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_THREAD_FAILED, service_name, error_string(GetLastError()), 0);
105 ZeroMemory(&te, sizeof(te));
106 te.dwSize = sizeof(te);
108 if (! Thread32First(snapshot, &te)) {
109 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, service_name, error_string(GetLastError()), 0);
110 CloseHandle(snapshot);
114 /* This thread belongs to the doomed process so signal it. */
115 if (te.th32OwnerProcessID == k->pid) {
116 ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);
120 /* Try to get the next thread. */
121 if (! Thread32Next(snapshot, &te)) {
122 unsigned long error = GetLastError();
123 if (error == ERROR_NO_MORE_FILES) break;
124 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, service_name, error_string(GetLastError()), 0);
125 CloseHandle(snapshot);
129 if (te.th32OwnerProcessID == k->pid) {
130 ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);
134 CloseHandle(snapshot);
139 /* Give the process a chance to die gracefully. */
140 int kill_process(nssm_service_t *service, HANDLE process_handle, unsigned long pid, unsigned long exitcode) {
141 /* Shouldn't happen. */
142 if (! service) return 1;
144 if (! process_handle) return 1;
147 if (GetExitCodeProcess(process_handle, &ret)) {
148 if (ret != STILL_ACTIVE) return 1;
151 kill_t k = { pid, exitcode, 0 };
153 /* Try to send a Control-C event to the console. */
154 if (service->stop_method & NSSM_STOP_METHOD_CONSOLE) {
155 if (! kill_console(service, &k)) return 1;
159 Try to post messages to the windows belonging to the given process ID.
160 If the process is a console application it won't have any windows so there's
161 no guarantee of success.
163 if (service->stop_method & NSSM_STOP_METHOD_WINDOW) {
164 EnumWindows((WNDENUMPROC) kill_window, (LPARAM) &k);
166 if (! await_shutdown(service, _T(__FUNCTION__), service->kill_window_delay)) return 1;
171 Try to post messages to any thread message queues associated with the
172 process. Console applications might have them (but probably won't) so
173 there's still no guarantee of success.
175 if (service->stop_method & NSSM_STOP_METHOD_THREADS) {
176 if (kill_threads(service->name, &k)) {
177 if (! await_shutdown(service, _T(__FUNCTION__), service->kill_threads_delay)) return 1;
181 /* We tried being nice. Time for extreme prejudice. */
182 if (service->stop_method & NSSM_STOP_METHOD_TERMINATE) {
183 return TerminateProcess(service->process_handle, exitcode);
189 /* Simulate a Control-C event to our console (shared with the app). */
190 int kill_console(nssm_service_t *service, kill_t *k) {
193 if (! service) return 1;
195 /* Check we loaded AttachConsole(). */
196 if (! imports.AttachConsole) return 4;
198 /* Try to attach to the process's console. */
199 if (! imports.AttachConsole(k->pid)) {
200 ret = GetLastError();
203 case ERROR_INVALID_HANDLE:
204 /* The app doesn't have a console. */
207 case ERROR_GEN_FAILURE:
208 /* The app already exited. */
211 case ERROR_ACCESS_DENIED:
213 /* We already have a console. */
214 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ATTACHCONSOLE_FAILED, service->name, error_string(ret), 0);
219 /* Ignore the event ourselves. */
221 if (! SetConsoleCtrlHandler(0, TRUE)) {
222 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETCONSOLECTRLHANDLER_FAILED, service->name, error_string(GetLastError()), 0);
226 /* Send the event. */
228 if (! GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) {
229 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GENERATECONSOLECTRLEVENT_FAILED, service->name, error_string(GetLastError()), 0);
234 /* Detach from the console. */
235 if (! FreeConsole()) {
236 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_FREECONSOLE_FAILED, service->name, error_string(GetLastError()), 0);
239 /* Wait for process to exit. */
240 if (await_shutdown(service, _T(__FUNCTION__), service->kill_console_delay)) ret = 6;
245 void kill_process_tree(nssm_service_t *service, unsigned long pid, unsigned long exitcode, unsigned long ppid) {
246 /* Shouldn't happen unless the service failed to start. */
249 TCHAR pid_string[16], code[16];
250 _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pid);
251 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);
252 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILLING, service->name, pid_string, code, 0);
254 /* Get a snapshot of all processes in the system. */
255 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
257 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_PROCESS_FAILED, service->name, error_string(GetLastError()), 0);
262 ZeroMemory(&pe, sizeof(pe));
263 pe.dwSize = sizeof(pe);
265 if (! Process32First(snapshot, &pe)) {
266 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, service->name, error_string(GetLastError()), 0);
267 CloseHandle(snapshot);
271 /* This is a child of the doomed process so kill it. */
272 if (! check_parent(service, &pe, pid)) kill_process_tree(service, pe.th32ProcessID, exitcode, ppid);
275 /* Try to get the next process. */
276 if (! Process32Next(snapshot, &pe)) {
277 unsigned long ret = GetLastError();
278 if (ret == ERROR_NO_MORE_FILES) break;
279 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, service->name, error_string(GetLastError()), 0);
280 CloseHandle(snapshot);
284 if (! check_parent(service, &pe, pid)) kill_process_tree(service, pe.th32ProcessID, exitcode, ppid);
287 CloseHandle(snapshot);
289 /* We will need a process handle in order to call TerminateProcess() later. */
290 HANDLE process_handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, false, pid);
291 if (! process_handle) {
292 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, service->name, error_string(GetLastError()), 0);
296 TCHAR ppid_string[16];
297 _sntprintf_s(ppid_string, _countof(ppid_string), _TRUNCATE, _T("%lu"), ppid);
298 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILL_PROCESS_TREE, pid_string, ppid_string, service->name, 0);
299 if (! kill_process(service, process_handle, pid, exitcode)) {
300 /* Maybe it already died. */
302 if (! GetExitCodeProcess(process_handle, &ret) || ret == STILL_ACTIVE) {
303 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);
304 else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_PROCESS_STILL_ACTIVE, service->name, pid_string, NSSM, NSSM_REG_STOP_METHOD_SKIP, 0);
308 CloseHandle(process_handle);