Skip to content

Commit 0db9119

Browse files
committed
Merge pull request #1170 from dscho/mingw-kill-process
Handle Ctrl+C in Git Bash nicely Signed-off-by: Johannes Schindelin <[email protected]>
2 parents dd4bcb8 + dd5b508 commit 0db9119

File tree

2 files changed

+197
-8
lines changed

2 files changed

+197
-8
lines changed

compat/mingw.c

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "../strbuf.h"
77
#include "../run-command.h"
88
#include "../cache.h"
9+
#include "win32/exit-process.h"
910
#include "win32/lazyload.h"
1011
#include "../config.h"
1112

@@ -1817,16 +1818,28 @@ int mingw_execvp(const char *cmd, char *const *argv)
18171818
int mingw_kill(pid_t pid, int sig)
18181819
{
18191820
if (pid > 0 && sig == SIGTERM) {
1820-
HANDLE h = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
1821-
1822-
if (TerminateProcess(h, -1)) {
1821+
HANDLE h = OpenProcess(PROCESS_CREATE_THREAD |
1822+
PROCESS_QUERY_INFORMATION |
1823+
PROCESS_VM_OPERATION | PROCESS_VM_WRITE |
1824+
PROCESS_VM_READ | PROCESS_TERMINATE,
1825+
FALSE, pid);
1826+
int ret;
1827+
1828+
if (h)
1829+
ret = exit_process(h, 128 + sig);
1830+
else {
1831+
h = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
1832+
if (!h) {
1833+
errno = err_win_to_posix(GetLastError());
1834+
return -1;
1835+
}
1836+
ret = terminate_process_tree(h, 128 + sig);
1837+
}
1838+
if (ret) {
1839+
errno = err_win_to_posix(GetLastError());
18231840
CloseHandle(h);
1824-
return 0;
18251841
}
1826-
1827-
errno = err_win_to_posix(GetLastError());
1828-
CloseHandle(h);
1829-
return -1;
1842+
return ret;
18301843
} else if (pid > 0 && sig == 0) {
18311844
HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
18321845
if (h) {
@@ -3205,6 +3218,14 @@ static void adjust_symlink_flags(void)
32053218

32063219
}
32073220

3221+
static BOOL WINAPI handle_ctrl_c(DWORD ctrl_type)
3222+
{
3223+
if (ctrl_type != CTRL_C_EVENT)
3224+
return FALSE; /* we did not handle this */
3225+
mingw_raise(SIGINT);
3226+
return TRUE; /* we did handle this */
3227+
}
3228+
32083229
#if defined(_MSC_VER)
32093230

32103231
#ifdef _DEBUG
@@ -3243,6 +3264,8 @@ int msc_startup(int argc, wchar_t **w_argv, wchar_t **w_env)
32433264
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
32443265
#endif
32453266

3267+
SetConsoleCtrlHandler(handle_ctrl_c, TRUE);
3268+
32463269
maybe_redirect_std_handles();
32473270
adjust_symlink_flags();
32483271
fsync_object_files = 1;
@@ -3311,6 +3334,8 @@ void mingw_startup(void)
33113334
wchar_t **wenv, **wargv;
33123335
_startupinfo si;
33133336

3337+
SetConsoleCtrlHandler(handle_ctrl_c, TRUE);
3338+
33143339
maybe_redirect_std_handles();
33153340
adjust_symlink_flags();
33163341
fsync_object_files = 1;

compat/win32/exit-process.h

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
#ifndef EXIT_PROCESS_H
2+
#define EXIT_PROCESS_H
3+
4+
/*
5+
* This file contains functions to terminate a Win32 process, as gently as
6+
* possible.
7+
*
8+
* At first, we will attempt to inject a thread that calls ExitProcess(). If
9+
* that fails, we will fall back to terminating the entire process tree.
10+
*
11+
* For simplicity, these functions are marked as file-local.
12+
*/
13+
14+
#include <tlhelp32.h>
15+
16+
/*
17+
* Terminates the process corresponding to the process ID and all of its
18+
* directly and indirectly spawned subprocesses.
19+
*
20+
* This way of terminating the processes is not gentle: the processes get
21+
* no chance of cleaning up after themselves (closing file handles, removing
22+
* .lock files, terminating spawned processes (if any), etc).
23+
*/
24+
static int terminate_process_tree(HANDLE main_process, int exit_status)
25+
{
26+
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
27+
PROCESSENTRY32 entry;
28+
DWORD pids[16384];
29+
int max_len = sizeof(pids) / sizeof(*pids), i, len, ret = 0;
30+
pid_t pid = GetProcessId(main_process);
31+
32+
pids[0] = (DWORD)pid;
33+
len = 1;
34+
35+
/*
36+
* Even if Process32First()/Process32Next() seem to traverse the
37+
* processes in topological order (i.e. parent processes before
38+
* child processes), there is nothing in the Win32 API documentation
39+
* suggesting that this is guaranteed.
40+
*
41+
* Therefore, run through them at least twice and stop when no more
42+
* process IDs were added to the list.
43+
*/
44+
for (;;) {
45+
int orig_len = len;
46+
47+
memset(&entry, 0, sizeof(entry));
48+
entry.dwSize = sizeof(entry);
49+
50+
if (!Process32First(snapshot, &entry))
51+
break;
52+
53+
do {
54+
for (i = len - 1; i >= 0; i--) {
55+
if (pids[i] == entry.th32ProcessID)
56+
break;
57+
if (pids[i] == entry.th32ParentProcessID)
58+
pids[len++] = entry.th32ProcessID;
59+
}
60+
} while (len < max_len && Process32Next(snapshot, &entry));
61+
62+
if (orig_len == len || len >= max_len)
63+
break;
64+
}
65+
66+
for (i = len - 1; i > 0; i--) {
67+
HANDLE process = OpenProcess(PROCESS_TERMINATE, FALSE, pids[i]);
68+
69+
if (process) {
70+
if (!TerminateProcess(process, exit_status))
71+
ret = -1;
72+
CloseHandle(process);
73+
}
74+
}
75+
if (!TerminateProcess(main_process, exit_status))
76+
ret = -1;
77+
CloseHandle(main_process);
78+
79+
return ret;
80+
}
81+
82+
/**
83+
* Determine whether a process runs in the same architecture as the current
84+
* one. That test is required before we assume that GetProcAddress() returns
85+
* a valid address *for the target process*.
86+
*/
87+
static inline int process_architecture_matches_current(HANDLE process)
88+
{
89+
static BOOL current_is_wow = -1;
90+
BOOL is_wow;
91+
92+
if (current_is_wow == -1 &&
93+
!IsWow64Process (GetCurrentProcess(), &current_is_wow))
94+
current_is_wow = -2;
95+
if (current_is_wow == -2)
96+
return 0; /* could not determine current process' WoW-ness */
97+
if (!IsWow64Process (process, &is_wow))
98+
return 0; /* cannot determine */
99+
return is_wow == current_is_wow;
100+
}
101+
102+
/**
103+
* Inject a thread into the given process that runs ExitProcess().
104+
*
105+
* Note: as kernel32.dll is loaded before any process, the other process and
106+
* this process will have ExitProcess() at the same address.
107+
*
108+
* This function expects the process handle to have the access rights for
109+
* CreateRemoteThread(): PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION,
110+
* PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ.
111+
*
112+
* The idea comes from the Dr Dobb's article "A Safer Alternative to
113+
* TerminateProcess()" by Andrew Tucker (July 1, 1999),
114+
* http://www.drdobbs.com/a-safer-alternative-to-terminateprocess/184416547
115+
*
116+
* If this method fails, we fall back to running terminate_process_tree().
117+
*/
118+
static int exit_process(HANDLE process, int exit_code)
119+
{
120+
DWORD code;
121+
122+
if (GetExitCodeProcess(process, &code) && code == STILL_ACTIVE) {
123+
static int initialized;
124+
static LPTHREAD_START_ROUTINE exit_process_address;
125+
PVOID arg = (PVOID)(intptr_t)exit_code;
126+
DWORD thread_id;
127+
HANDLE thread = NULL;
128+
129+
if (!initialized) {
130+
HINSTANCE kernel32 = GetModuleHandle("kernel32");
131+
if (!kernel32)
132+
die("BUG: cannot find kernel32");
133+
exit_process_address = (LPTHREAD_START_ROUTINE)
134+
GetProcAddress(kernel32, "ExitProcess");
135+
initialized = 1;
136+
}
137+
if (!exit_process_address ||
138+
!process_architecture_matches_current(process))
139+
return terminate_process_tree(process, exit_code);
140+
141+
thread = CreateRemoteThread(process, NULL, 0,
142+
exit_process_address,
143+
arg, 0, &thread_id);
144+
if (thread) {
145+
CloseHandle(thread);
146+
/*
147+
* If the process survives for 10 seconds (a completely
148+
* arbitrary value picked from thin air), fall back to
149+
* killing the process tree via TerminateProcess().
150+
*/
151+
if (WaitForSingleObject(process, 10000) ==
152+
WAIT_OBJECT_0) {
153+
CloseHandle(process);
154+
return 0;
155+
}
156+
}
157+
158+
return terminate_process_tree(process, exit_code);
159+
}
160+
161+
return 0;
162+
}
163+
164+
#endif

0 commit comments

Comments
 (0)