#include "symlinks.h"
#include "trace2.h"
#include "win32.h"
+#include "win32/exit-process.h"
#include "win32/lazyload.h"
#include "wrapper.h"
#include <aclapi.h>
int mingw_kill(pid_t pid, int sig)
{
if (pid > 0 && sig == SIGTERM) {
- HANDLE h = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
-
- if (TerminateProcess(h, -1)) {
+ HANDLE h = OpenProcess(PROCESS_CREATE_THREAD |
+ PROCESS_QUERY_INFORMATION |
+ PROCESS_VM_OPERATION | PROCESS_VM_WRITE |
+ PROCESS_VM_READ | PROCESS_TERMINATE,
+ FALSE, pid);
+ int ret;
+
+ if (h)
+ ret = exit_process(h, 128 + sig);
+ else {
+ h = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
+ if (!h) {
+ errno = err_win_to_posix(GetLastError());
+ return -1;
+ }
+ ret = terminate_process_tree(h, 128 + sig);
+ }
+ if (ret) {
+ errno = err_win_to_posix(GetLastError());
CloseHandle(h);
- return 0;
}
-
- errno = err_win_to_posix(GetLastError());
- CloseHandle(h);
- return -1;
+ return ret;
} else if (pid > 0 && sig == 0) {
HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
if (h) {
--- /dev/null
+#ifndef EXIT_PROCESS_H
+#define EXIT_PROCESS_H
+
+/*
+ * This file contains functions to terminate a Win32 process, as gently as
+ * possible.
+ *
+ * At first, we will attempt to inject a thread that calls ExitProcess(). If
+ * that fails, we will fall back to terminating the entire process tree.
+ *
+ * For simplicity, these functions are marked as file-local.
+ */
+
+#include <tlhelp32.h>
+
+/*
+ * Terminates the process corresponding to the process ID and all of its
+ * directly and indirectly spawned subprocesses.
+ *
+ * This way of terminating the processes is not gentle: the processes get
+ * no chance of cleaning up after themselves (closing file handles, removing
+ * .lock files, terminating spawned processes (if any), etc).
+ */
+static int terminate_process_tree(HANDLE main_process, int exit_status)
+{
+ HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ PROCESSENTRY32 entry;
+ DWORD pids[16384];
+ int max_len = sizeof(pids) / sizeof(*pids), i, len, ret = 0;
+ pid_t pid = GetProcessId(main_process);
+
+ pids[0] = (DWORD)pid;
+ len = 1;
+
+ /*
+ * Even if Process32First()/Process32Next() seem to traverse the
+ * processes in topological order (i.e. parent processes before
+ * child processes), there is nothing in the Win32 API documentation
+ * suggesting that this is guaranteed.
+ *
+ * Therefore, run through them at least twice and stop when no more
+ * process IDs were added to the list.
+ */
+ for (;;) {
+ int orig_len = len;
+
+ memset(&entry, 0, sizeof(entry));
+ entry.dwSize = sizeof(entry);
+
+ if (!Process32First(snapshot, &entry))
+ break;
+
+ do {
+ for (i = len - 1; i >= 0; i--) {
+ if (pids[i] == entry.th32ProcessID)
+ break;
+ if (pids[i] == entry.th32ParentProcessID)
+ pids[len++] = entry.th32ProcessID;
+ }
+ } while (len < max_len && Process32Next(snapshot, &entry));
+
+ if (orig_len == len || len >= max_len)
+ break;
+ }
+
+ for (i = len - 1; i > 0; i--) {
+ HANDLE process = OpenProcess(PROCESS_TERMINATE, FALSE, pids[i]);
+
+ if (process) {
+ if (!TerminateProcess(process, exit_status))
+ ret = -1;
+ CloseHandle(process);
+ }
+ }
+ if (!TerminateProcess(main_process, exit_status))
+ ret = -1;
+ CloseHandle(main_process);
+
+ return ret;
+}
+
+/**
+ * Determine whether a process runs in the same architecture as the current
+ * one. That test is required before we assume that GetProcAddress() returns
+ * a valid address *for the target process*.
+ */
+static inline int process_architecture_matches_current(HANDLE process)
+{
+ static BOOL current_is_wow = -1;
+ BOOL is_wow;
+
+ if (current_is_wow == -1 &&
+ !IsWow64Process (GetCurrentProcess(), ¤t_is_wow))
+ current_is_wow = -2;
+ if (current_is_wow == -2)
+ return 0; /* could not determine current process' WoW-ness */
+ if (!IsWow64Process (process, &is_wow))
+ return 0; /* cannot determine */
+ return is_wow == current_is_wow;
+}
+
+/**
+ * Inject a thread into the given process that runs ExitProcess().
+ *
+ * Note: as kernel32.dll is loaded before any process, the other process and
+ * this process will have ExitProcess() at the same address.
+ *
+ * This function expects the process handle to have the access rights for
+ * CreateRemoteThread(): PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION,
+ * PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ.
+ *
+ * The idea comes from the Dr Dobb's article "A Safer Alternative to
+ * TerminateProcess()" by Andrew Tucker (July 1, 1999),
+ * http://www.drdobbs.com/a-safer-alternative-to-terminateprocess/184416547
+ *
+ * If this method fails, we fall back to running terminate_process_tree().
+ */
+static int exit_process(HANDLE process, int exit_code)
+{
+ DWORD code;
+
+ if (GetExitCodeProcess(process, &code) && code == STILL_ACTIVE) {
+ static int initialized;
+ static LPTHREAD_START_ROUTINE exit_process_address;
+ PVOID arg = (PVOID)(intptr_t)exit_code;
+ DWORD thread_id;
+ HANDLE thread = NULL;
+
+ if (!initialized) {
+ HINSTANCE kernel32 = GetModuleHandleA("kernel32");
+ if (!kernel32)
+ die("BUG: cannot find kernel32");
+ exit_process_address =
+ (LPTHREAD_START_ROUTINE)(void (*)(void))
+ GetProcAddress(kernel32, "ExitProcess");
+ initialized = 1;
+ }
+ if (!exit_process_address ||
+ !process_architecture_matches_current(process))
+ return terminate_process_tree(process, exit_code);
+
+ thread = CreateRemoteThread(process, NULL, 0,
+ exit_process_address,
+ arg, 0, &thread_id);
+ if (thread) {
+ CloseHandle(thread);
+ /*
+ * If the process survives for 10 seconds (a completely
+ * arbitrary value picked from thin air), fall back to
+ * killing the process tree via TerminateProcess().
+ */
+ if (WaitForSingleObject(process, 10000) ==
+ WAIT_OBJECT_0) {
+ CloseHandle(process);
+ return 0;
+ }
+ }
+
+ return terminate_process_tree(process, exit_code);
+ }
+
+ return 0;
+}
+
+#endif