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
324 unsigned long depth = k->depth;
\r
326 TCHAR pid_string[16], code[16];
\r
327 _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pid);
\r
328 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), k->exitcode);
\r
329 if (fn == kill_process) log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILLING, k->name, pid_string, code, 0);
\r
331 /* We will need a process handle in order to call TerminateProcess() later. */
\r
332 HANDLE process_handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, false, pid);
\r
333 if (process_handle) {
\r
334 /* Kill this process first, then its descendents. */
\r
335 TCHAR ppid_string[16];
\r
336 _sntprintf_s(ppid_string, _countof(ppid_string), _TRUNCATE, _T("%lu"), ppid);
\r
337 if (fn == kill_process) log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILL_PROCESS_TREE, pid_string, ppid_string, k->name, 0);
\r
338 k->process_handle = process_handle; /* XXX: open directly? */
\r
339 if (! fn(service, k)) {
\r
340 /* Maybe it already died. */
\r
342 if (! GetExitCodeProcess(process_handle, &ret) || ret == STILL_ACTIVE) {
\r
343 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
344 else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_PROCESS_STILL_ACTIVE, k->name, pid_string, NSSM, NSSM_REG_STOP_METHOD_SKIP, 0);
\r
348 CloseHandle(process_handle);
\r
350 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0);
\r
352 /* Get a snapshot of all processes in the system. */
\r
353 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
\r
354 if (snapshot == INVALID_HANDLE_VALUE) {
\r
355 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_PROCESS_FAILED, k->name, error_string(GetLastError()), 0);
\r
360 ZeroMemory(&pe, sizeof(pe));
\r
361 pe.dwSize = sizeof(pe);
\r
363 if (! Process32First(snapshot, &pe)) {
\r
364 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);
\r
365 CloseHandle(snapshot);
\r
369 /* This is a child of the doomed process so kill it. */
\r
371 if (! check_parent(k, &pe, pid)) {
\r
372 k->pid = pe.th32ProcessID;
\r
373 walk_process_tree(service, fn, k, ppid);
\r
378 /* Try to get the next process. */
\r
379 if (! Process32Next(snapshot, &pe)) {
\r
380 unsigned long ret = GetLastError();
\r
381 if (ret == ERROR_NO_MORE_FILES) break;
\r
382 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);
\r
383 CloseHandle(snapshot);
\r
388 if (! check_parent(k, &pe, pid)) {
\r
389 k->pid = pe.th32ProcessID;
\r
390 walk_process_tree(service, fn, k, ppid);
\r
396 CloseHandle(snapshot);
\r
399 void kill_process_tree(kill_t *k, unsigned long ppid) {
\r
400 return walk_process_tree(NULL, kill_process, k, ppid);
\r
403 int print_process(nssm_service_t *service, kill_t *k) {
\r
404 TCHAR exe[EXE_LENGTH];
\r
407 buffer = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (k->depth + 1) * sizeof(TCHAR));
\r
410 for (i = 0; i < k->depth; i++) buffer[i] = _T(' ');
\r
411 buffer[i] = _T('\0');
\r
415 unsigned long size = _countof(exe);
\r
416 if (! imports.QueryFullProcessImageName || ! imports.QueryFullProcessImageName(k->process_handle, 0, exe, &size)) {
\r
418 Fall back to GetModuleFileNameEx(), which won't work for WOW64 processes.
\r
420 if (! GetModuleFileNameEx(k->process_handle, NULL, exe, _countof(exe))) {
\r
421 long error = GetLastError();
\r
422 if (error == ERROR_PARTIAL_COPY) _sntprintf_s(exe, _countof(exe), _TRUNCATE, _T("[WOW64]"));
\r
423 else _sntprintf_s(exe, _countof(exe), _TRUNCATE, _T("???"));
\r
427 _tprintf(_T("% 8lu %s%s\n"), k->pid, buffer ? buffer : _T(""), exe);
\r
429 if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
\r
433 int print_process(kill_t *k) {
\r
434 return print_process(NULL, k);
\r