Skip to content

Commit 2860b93

Browse files
author
Git for Windows Build Agent
committed
Merge pull request #1170 from dscho/mingw-kill-process
Handle Ctrl+C in Git Bash nicely
2 parents 338119c + f6c6616 commit 2860b93

File tree

2 files changed

+224
-8
lines changed

2 files changed

+224
-8
lines changed

compat/mingw.c

+64-8
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

1011
#define HCAST(type, handle) ((type)(intptr_t)handle)
1112

@@ -1498,10 +1499,43 @@ struct pinfo_t {
14981499
static struct pinfo_t *pinfo = NULL;
14991500
CRITICAL_SECTION pinfo_cs;
15001501

1502+
#ifndef SIGRTMAX
1503+
#define SIGRTMAX 63
1504+
#endif
1505+
1506+
static void kill_child_processes_on_signal(void)
1507+
{
1508+
DWORD status;
1509+
1510+
/*
1511+
* Only continue if the process was terminated by a signal, as
1512+
* indicated by the exit status (128 + sig_no).
1513+
*
1514+
* As we are running in an atexit() handler, the exit code has been
1515+
* set at this stage by the ExitProcess() function already.
1516+
*/
1517+
if (!GetExitCodeProcess(GetCurrentProcess(), &status) ||
1518+
status <= 128 || status > 128 + SIGRTMAX)
1519+
return;
1520+
1521+
EnterCriticalSection(&pinfo_cs);
1522+
1523+
while (pinfo) {
1524+
struct pinfo_t *info = pinfo;
1525+
pinfo = pinfo->next;
1526+
exit_process(info->proc, status);
1527+
CloseHandle(info->proc);
1528+
free(info);
1529+
}
1530+
1531+
LeaveCriticalSection(&pinfo_cs);
1532+
}
1533+
15011534
static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaenv,
15021535
const char *dir,
15031536
int prepend_cmd, int fhin, int fhout, int fherr)
15041537
{
1538+
static int atexit_handler_initialized;
15051539
STARTUPINFOW si;
15061540
PROCESS_INFORMATION pi;
15071541
struct strbuf args;
@@ -1511,6 +1545,17 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
15111545
HANDLE cons;
15121546
const char *strace_env;
15131547

1548+
if (!atexit_handler_initialized) {
1549+
atexit_handler_initialized = 1;
1550+
/*
1551+
* On Windows, there is no POSIX signaling. Instead, we inject
1552+
* a thread calling ExitProcess(128 + sig_no); and that calls
1553+
* the *atexit* handlers. Catch this condition and kill child
1554+
* processes with the same signal.
1555+
*/
1556+
atexit(kill_child_processes_on_signal);
1557+
}
1558+
15141559
do_unset_environment_variables();
15151560

15161561
/* Determine whether or not we are associated to a console */
@@ -1752,16 +1797,27 @@ int mingw_execvp(const char *cmd, char *const *argv)
17521797
int mingw_kill(pid_t pid, int sig)
17531798
{
17541799
if (pid > 0 && sig == SIGTERM) {
1755-
HANDLE h = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
1756-
1757-
if (TerminateProcess(h, -1)) {
1758-
CloseHandle(h);
1759-
return 0;
1800+
HANDLE h = OpenProcess(PROCESS_CREATE_THREAD |
1801+
PROCESS_QUERY_INFORMATION |
1802+
PROCESS_VM_OPERATION | PROCESS_VM_WRITE |
1803+
PROCESS_VM_READ | PROCESS_TERMINATE,
1804+
FALSE, pid);
1805+
int ret;
1806+
1807+
if (h)
1808+
ret = exit_process(h, 128 + sig);
1809+
else {
1810+
h = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
1811+
if (!h) {
1812+
errno = err_win_to_posix(GetLastError());
1813+
return -1;
1814+
}
1815+
ret = terminate_process_tree(h, 128 + sig);
17601816
}
1761-
1762-
errno = err_win_to_posix(GetLastError());
17631817
CloseHandle(h);
1764-
return -1;
1818+
if (ret)
1819+
errno = err_win_to_posix(GetLastError());
1820+
return ret;
17651821
} else if (pid > 0 && sig == 0) {
17661822
HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
17671823
if (h) {

compat/win32/exit-process.h

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
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 = pids[i] == pid ? main_process :
68+
OpenProcess(PROCESS_TERMINATE, FALSE, pids[i]);
69+
70+
if (process) {
71+
if (!TerminateProcess(process, exit_status << 8))
72+
ret = -1;
73+
CloseHandle(process);
74+
}
75+
}
76+
77+
return ret;
78+
}
79+
80+
/**
81+
* Determine whether a process runs in the same architecture as the current
82+
* one. That test is required before we assume that GetProcAddress() returns
83+
* a valid address *for the target process*.
84+
*/
85+
static inline int process_architecture_matches_current(HANDLE process)
86+
{
87+
static BOOL current_is_wow = -1;
88+
BOOL is_wow;
89+
90+
if (current_is_wow == -1 &&
91+
!IsWow64Process (GetCurrentProcess(), &current_is_wow))
92+
current_is_wow = -2;
93+
if (current_is_wow == -2)
94+
return 0; /* could not determine current process' WoW-ness */
95+
if (!IsWow64Process (process, &is_wow))
96+
return 0; /* cannot determine */
97+
return is_wow == current_is_wow;
98+
}
99+
100+
/**
101+
* Inject a thread into the given process that runs ExitProcess().
102+
*
103+
* Note: as kernel32.dll is loaded before any process, the other process and
104+
* this process will have ExitProcess() at the same address.
105+
*
106+
* This function expects the process handle to have the access rights for
107+
* CreateRemoteThread(): PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION,
108+
* PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ.
109+
*
110+
* The idea comes from the Dr Dobb's article "A Safer Alternative to
111+
* TerminateProcess()" by Andrew Tucker (July 1, 1999),
112+
* http://www.drdobbs.com/a-safer-alternative-to-terminateprocess/184416547
113+
*
114+
* If this method fails, we fall back to running terminate_process_tree().
115+
*/
116+
static int exit_process(HANDLE process, int exit_code)
117+
{
118+
DWORD code;
119+
120+
if (GetExitCodeProcess(process, &code) && code == STILL_ACTIVE) {
121+
static int initialized;
122+
static LPTHREAD_START_ROUTINE exit_process_address;
123+
PVOID arg = (PVOID)(intptr_t)exit_code;
124+
DWORD thread_id;
125+
HANDLE thread = NULL;
126+
127+
if (!initialized) {
128+
HINSTANCE kernel32 = GetModuleHandle("kernel32");
129+
if (!kernel32)
130+
die("BUG: cannot find kernel32");
131+
exit_process_address = (LPTHREAD_START_ROUTINE)
132+
GetProcAddress(kernel32, "ExitProcess");
133+
initialized = 1;
134+
}
135+
if (!exit_process_address ||
136+
!process_architecture_matches_current(process))
137+
return terminate_process_tree(process, exit_code);
138+
139+
thread = CreateRemoteThread(process, NULL, 0,
140+
exit_process_address,
141+
arg, 0, &thread_id);
142+
if (thread) {
143+
CloseHandle(thread);
144+
/*
145+
* If the process survives for 10 seconds (a completely
146+
* arbitrary value picked from thin air), fall back to
147+
* killing the process tree via TerminateProcess().
148+
*/
149+
if (WaitForSingleObject(process, 10000) ==
150+
WAIT_OBJECT_0)
151+
return 0;
152+
}
153+
154+
return terminate_process_tree(process, exit_code);
155+
}
156+
157+
return 0;
158+
}
159+
160+
#endif

0 commit comments

Comments
 (0)