3 extern imports_t imports;
\r
5 HANDLE get_debug_token() {
\r
8 if (! OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, false, &token)) {
\r
9 error = GetLastError();
\r
10 if (error == ERROR_NO_TOKEN) {
\r
11 (void) ImpersonateSelf(SecurityImpersonation);
\r
12 (void) OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, false, &token);
\r
15 if (! token) return INVALID_HANDLE_VALUE;
\r
17 TOKEN_PRIVILEGES privileges, old;
\r
18 unsigned long size = sizeof(TOKEN_PRIVILEGES);
\r
20 if (! LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {
\r
22 return INVALID_HANDLE_VALUE;
\r
25 privileges.PrivilegeCount = 1;
\r
26 privileges.Privileges[0].Luid = luid;
\r
27 privileges.Privileges[0].Attributes = 0;
\r
29 if (! AdjustTokenPrivileges(token, false, &privileges, size, &old, &size)) {
\r
31 return INVALID_HANDLE_VALUE;
\r
34 old.PrivilegeCount = 1;
\r
35 old.Privileges[0].Luid = luid;
\r
36 old.Privileges[0].Attributes |= SE_PRIVILEGE_ENABLED;
\r
38 if (! AdjustTokenPrivileges(token, false, &old, size, NULL, NULL)) {
\r
40 return INVALID_HANDLE_VALUE;
\r
46 void service_kill_t(nssm_service_t *service, kill_t *k) {
\r
47 if (! service) return;
\r
50 ZeroMemory(k, sizeof(*k));
\r
51 k->name = service->name;
\r
52 k->process_handle = service->process_handle;
\r
53 k->pid = service->pid;
\r
54 k->exitcode = service->exitcode;
\r
55 k->stop_method = service->stop_method;
\r
56 k->kill_console_delay = service->kill_console_delay;
\r
57 k->kill_window_delay = service->kill_window_delay;
\r
58 k->kill_threads_delay = service->kill_threads_delay;
\r
59 k->status_handle = service->status_handle;
\r
60 k->status = &service->status;
\r
61 k->creation_time = service->creation_time;
\r
62 k->exit_time = service->exit_time;
\r
65 int get_process_creation_time(HANDLE process_handle, FILETIME *ft) {
\r
66 FILETIME creation_time, exit_time, kernel_time, user_time;
\r
68 if (! GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time, &user_time)) {
\r
69 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSTIMES_FAILED, error_string(GetLastError()), 0);
\r
73 memmove(ft, &creation_time, sizeof(creation_time));
\r
78 int get_process_exit_time(HANDLE process_handle, FILETIME *ft) {
\r
79 FILETIME creation_time, exit_time, kernel_time, user_time;
\r
81 if (! GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time, &user_time)) {
\r
82 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSTIMES_FAILED, error_string(GetLastError()), 0);
\r
86 if (! (exit_time.dwLowDateTime || exit_time.dwHighDateTime)) return 2;
\r
87 memmove(ft, &exit_time, sizeof(exit_time));
\r
92 int check_parent(kill_t *k, PROCESSENTRY32 *pe, unsigned long ppid) {
\r
93 /* Check parent process ID matches. */
\r
94 if (pe->th32ParentProcessID != ppid) return 1;
\r
97 Process IDs can be reused so do a sanity check by making sure the child
\r
98 has been running for less time than the parent.
\r
99 Though unlikely, it's possible that the parent exited and its process ID
\r
100 was already reused, so we'll also compare against its exit time.
\r
102 HANDLE process_handle = OpenProcess(PROCESS_QUERY_INFORMATION, false, pe->th32ProcessID);
\r
103 if (! process_handle) {
\r
104 TCHAR pid_string[16];
\r
105 _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pe->th32ProcessID);
\r
106 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0);
\r
111 if (get_process_creation_time(process_handle, &ft)) {
\r
112 CloseHandle(process_handle);
\r
116 CloseHandle(process_handle);
\r
118 /* Verify that the parent's creation time is not later. */
\r
119 if (CompareFileTime(&k->creation_time, &ft) > 0) return 4;
\r
121 /* Verify that the parent's exit time is not earlier. */
\r
122 if (CompareFileTime(&k->exit_time, &ft) < 0) return 5;
\r
127 /* Send some window messages and hope the window respects one or more. */
\r
128 int CALLBACK kill_window(HWND window, LPARAM arg) {
\r
129 kill_t *k = (kill_t *) arg;
\r
132 if (! GetWindowThreadProcessId(window, &pid)) return 1;
\r
133 if (pid != k->pid) return 1;
\r
135 /* First try sending WM_CLOSE to request that the window close. */
\r
136 k->signalled |= PostMessage(window, WM_CLOSE, k->exitcode, 0);
\r
139 Then tell the window that the user is logging off and it should exit
\r
140 without worrying about saving any data.
\r
142 k->signalled |= PostMessage(window, WM_ENDSESSION, 1, ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL | ENDSESSION_LOGOFF);
\r
148 Try to post a message to the message queues of threads associated with the
\r
149 given process ID. Not all threads have message queues so there's no
\r
150 guarantee of success, and we don't want to be left waiting for unsignalled
\r
151 processes so this function returns only true if at least one thread was
\r
152 successfully prodded.
\r
154 int kill_threads(nssm_service_t *service, kill_t *k) {
\r
157 /* Get a snapshot of all threads in the system. */
\r
158 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
\r
159 if (snapshot == INVALID_HANDLE_VALUE) {
\r
160 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_THREAD_FAILED, k->name, error_string(GetLastError()), 0);
\r
165 ZeroMemory(&te, sizeof(te));
\r
166 te.dwSize = sizeof(te);
\r
168 if (! Thread32First(snapshot, &te)) {
\r
169 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);
\r
170 CloseHandle(snapshot);
\r
174 /* This thread belongs to the doomed process so signal it. */
\r
175 if (te.th32OwnerProcessID == k->pid) {
\r
176 ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);
\r
180 /* Try to get the next thread. */
\r
181 if (! Thread32Next(snapshot, &te)) {
\r
182 unsigned long error = GetLastError();
\r
183 if (error == ERROR_NO_MORE_FILES) break;
\r
184 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);
\r
185 CloseHandle(snapshot);
\r
189 if (te.th32OwnerProcessID == k->pid) {
\r
190 ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);
\r
194 CloseHandle(snapshot);
\r
199 int kill_threads(kill_t *k) {
\r
200 return kill_threads(NULL, k);
\r
203 /* Give the process a chance to die gracefully. */
\r
204 int kill_process(nssm_service_t *service, kill_t *k) {
\r
208 if (GetExitCodeProcess(k->process_handle, &ret)) {
\r
209 if (ret != STILL_ACTIVE) return 1;
\r
212 /* Try to send a Control-C event to the console. */
\r
213 if (k->stop_method & NSSM_STOP_METHOD_CONSOLE) {
\r
214 if (! kill_console(k)) return 1;
\r
218 Try to post messages to the windows belonging to the given process ID.
\r
219 If the process is a console application it won't have any windows so there's
\r
220 no guarantee of success.
\r
222 if (k->stop_method & NSSM_STOP_METHOD_WINDOW) {
\r
223 EnumWindows((WNDENUMPROC) kill_window, (LPARAM) k);
\r
224 if (k->signalled) {
\r
225 if (! await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_window_delay)) return 1;
\r
231 Try to post messages to any thread message queues associated with the
\r
232 process. Console applications might have them (but probably won't) so
\r
233 there's still no guarantee of success.
\r
235 if (k->stop_method & NSSM_STOP_METHOD_THREADS) {
\r
236 if (kill_threads(k)) {
\r
237 if (! await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_threads_delay)) return 1;
\r
241 /* We tried being nice. Time for extreme prejudice. */
\r
242 if (k->stop_method & NSSM_STOP_METHOD_TERMINATE) {
\r
243 return TerminateProcess(k->process_handle, k->exitcode);
\r
249 int kill_process(kill_t *k) {
\r
250 return kill_process(NULL, k);
\r
253 /* Simulate a Control-C event to our console (shared with the app). */
\r
254 int kill_console(nssm_service_t *service, kill_t *k) {
\r
259 /* Check we loaded AttachConsole(). */
\r
260 if (! imports.AttachConsole) return 4;
\r
262 /* Try to attach to the process's console. */
\r
263 if (! imports.AttachConsole(k->pid)) {
\r
264 ret = GetLastError();
\r
267 case ERROR_INVALID_HANDLE:
\r
268 /* The app doesn't have a console. */
\r
271 case ERROR_GEN_FAILURE:
\r
272 /* The app already exited. */
\r
275 case ERROR_ACCESS_DENIED:
\r
277 /* We already have a console. */
\r
278 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ATTACHCONSOLE_FAILED, k->name, error_string(ret), 0);
\r
283 /* Ignore the event ourselves. */
\r
285 BOOL ignored = SetConsoleCtrlHandler(0, TRUE);
\r
287 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETCONSOLECTRLHANDLER_FAILED, k->name, error_string(GetLastError()), 0);
\r
291 /* Send the event. */
\r
293 if (! GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) {
\r
294 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GENERATECONSOLECTRLEVENT_FAILED, k->name, error_string(GetLastError()), 0);
\r
299 /* Detach from the console. */
\r
300 if (! FreeConsole()) {
\r
301 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_FREECONSOLE_FAILED, k->name, error_string(GetLastError()), 0);
\r
304 /* Wait for process to exit. */
\r
305 if (await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_console_delay)) ret = 6;
\r
307 /* Remove our handler. */
\r
308 if (ignored && ! SetConsoleCtrlHandler(0, FALSE)) {
\r
309 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETCONSOLECTRLHANDLER_FAILED, k->name, error_string(GetLastError()), 0);
\r
315 int kill_console(kill_t *k) {
\r
316 return kill_console(NULL, k);
\r
319 void walk_process_tree(nssm_service_t *service, walk_function_t fn, kill_t *k, unsigned long ppid) {
\r
321 /* Shouldn't happen unless the service failed to start. */
\r
322 if (! k->pid) return; /* XXX: needed? */
\r
323 unsigned long pid = k->pid;
\r
325 TCHAR pid_string[16], code[16];
\r
326 _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pid);
\r
327 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), k->exitcode);
\r
328 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILLING, k->name, pid_string, code, 0);
\r
330 /* We will need a process handle in order to call TerminateProcess() later. */
\r
331 HANDLE process_handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, false, pid);
\r
332 if (process_handle) {
\r
333 /* Kill this process first, then its descendents. */
\r
334 TCHAR ppid_string[16];
\r
335 _sntprintf_s(ppid_string, _countof(ppid_string), _TRUNCATE, _T("%lu"), ppid);
\r
336 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILL_PROCESS_TREE, pid_string, ppid_string, k->name, 0);
\r
337 k->process_handle = process_handle; /* XXX: open directly? */
\r
338 if (! fn(service, k)) {
\r
339 /* Maybe it already died. */
\r
341 if (! GetExitCodeProcess(process_handle, &ret) || ret == STILL_ACTIVE) {
\r
342 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
343 else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_PROCESS_STILL_ACTIVE, k->name, pid_string, NSSM, NSSM_REG_STOP_METHOD_SKIP, 0);
\r
347 CloseHandle(process_handle);
\r
349 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0);
\r
351 /* Get a snapshot of all processes in the system. */
\r
352 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
\r
353 if (snapshot == INVALID_HANDLE_VALUE) {
\r
354 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_PROCESS_FAILED, k->name, error_string(GetLastError()), 0);
\r
359 ZeroMemory(&pe, sizeof(pe));
\r
360 pe.dwSize = sizeof(pe);
\r
362 if (! Process32First(snapshot, &pe)) {
\r
363 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);
\r
364 CloseHandle(snapshot);
\r
368 /* This is a child of the doomed process so kill it. */
\r
369 if (! check_parent(k, &pe, pid)) {
\r
370 k->pid = pe.th32ProcessID;
\r
371 walk_process_tree(service, fn, k, ppid);
\r
376 /* Try to get the next process. */
\r
377 if (! Process32Next(snapshot, &pe)) {
\r
378 unsigned long ret = GetLastError();
\r
379 if (ret == ERROR_NO_MORE_FILES) break;
\r
380 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);
\r
381 CloseHandle(snapshot);
\r
385 if (! check_parent(k, &pe, pid)) {
\r
386 k->pid = pe.th32ProcessID;
\r
387 walk_process_tree(service, fn, k, ppid);
\r
392 CloseHandle(snapshot);
\r
395 void kill_process_tree(kill_t *k, unsigned long ppid) {
\r
396 return walk_process_tree(NULL, kill_process, k, ppid);
\r