Skip to content

Commit 0b90c6a

Browse files
committed
Merge pull request #6 from dscho/ctrl-c
When interrupting Win32 processes, kill their child processes, too
2 parents 44d9907 + 65e334c commit 0b90c6a

File tree

4 files changed

+211
-5
lines changed

4 files changed

+211
-5
lines changed

winsup/cygwin/exceptions.cc

+18-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ details. */
2727
#include "child_info.h"
2828
#include "ntdll.h"
2929
#include "exception.h"
30+
#include "cygwin/exit_process.h"
3031

3132
/* Definitions for code simplification */
3233
#ifdef __x86_64__
@@ -1546,8 +1547,23 @@ sigpacket::process ()
15461547
dosig:
15471548
if (have_execed)
15481549
{
1549-
sigproc_printf ("terminating captive process");
1550-
TerminateProcess (ch_spawn, sigExeced = si.si_signo);
1550+
switch (si.si_signo)
1551+
{
1552+
case SIGUSR1:
1553+
case SIGUSR2:
1554+
case SIGCONT:
1555+
case SIGSTOP:
1556+
case SIGTSTP:
1557+
case SIGTTIN:
1558+
case SIGTTOU:
1559+
system_printf ("Suppressing signal %d to win32 process (pid %u)",
1560+
(int)si.si_signo, (unsigned int)GetProcessId(ch_spawn));
1561+
goto done;
1562+
default:
1563+
sigproc_printf ("terminating captive process");
1564+
exit_process (ch_spawn, 128 + (sigExeced = si.si_signo));
1565+
break;
1566+
}
15511567
}
15521568
/* Dispatch to the appropriate function. */
15531569
sigproc_printf ("signal %d, signal handler %p", si.si_signo, handler);
+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
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+
* As we do not want to export this function in the MSYS2 runtime, these
12+
* functions are marked as file-local.
13+
*/
14+
15+
#include <tlhelp32.h>
16+
17+
/**
18+
* Terminates the process corresponding to the process ID and all of its
19+
* directly and indirectly spawned subprocesses.
20+
*
21+
* This way of terminating the processes is not gentle: the processes get
22+
* no chance of cleaning up after themselves (closing file handles, removing
23+
* .lock files, terminating spawned processes (if any), etc).
24+
*/
25+
static int
26+
terminate_process_tree(HANDLE main_process, int exit_code)
27+
{
28+
HANDLE snapshot = CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, 0);
29+
PROCESSENTRY32 entry;
30+
DWORD pids[16384];
31+
int max_len = sizeof (pids) / sizeof (*pids), i, len, ret = 0;
32+
pid_t pid = GetProcessId (main_process);
33+
34+
pids[0] = (DWORD) pid;
35+
len = 1;
36+
37+
/*
38+
* Even if Process32First()/Process32Next() seem to traverse the
39+
* processes in topological order (i.e. parent processes before
40+
* child processes), there is nothing in the Win32 API documentation
41+
* suggesting that this is guaranteed.
42+
*
43+
* Therefore, run through them at least twice and stop when no more
44+
* process IDs were added to the list.
45+
*/
46+
for (;;)
47+
{
48+
int orig_len = len;
49+
50+
memset (&entry, 0, sizeof (entry));
51+
entry.dwSize = sizeof (entry);
52+
53+
if (!Process32First (snapshot, &entry))
54+
break;
55+
56+
do
57+
{
58+
for (i = len - 1; i >= 0; i--)
59+
{
60+
if (pids[i] == entry.th32ProcessID)
61+
break;
62+
if (pids[i] == entry.th32ParentProcessID)
63+
pids[len++] = entry.th32ProcessID;
64+
}
65+
}
66+
while (len < max_len && Process32Next (snapshot, &entry));
67+
68+
if (orig_len == len || len >= max_len)
69+
break;
70+
}
71+
72+
for (i = len - 1; i >= 0; i--)
73+
{
74+
HANDLE process = i == 0 ? main_process :
75+
OpenProcess (PROCESS_TERMINATE, FALSE, pids[i]);
76+
77+
if (process)
78+
{
79+
if (!TerminateProcess (process, exit_code))
80+
ret = -1;
81+
CloseHandle (process);
82+
}
83+
}
84+
85+
return ret;
86+
}
87+
88+
/**
89+
* Determine whether a process runs in the same architecture as the current
90+
* one. That test is required before we assume that GetProcAddress() returns
91+
* a valid address *for the target process*.
92+
*/
93+
static inline bool
94+
process_architecture_matches_current(HANDLE process)
95+
{
96+
static BOOL current_is_wow = -1;
97+
BOOL is_wow;
98+
99+
if (current_is_wow == -1 &&
100+
!IsWow64Process (GetCurrentProcess (), &current_is_wow))
101+
current_is_wow = -2;
102+
if (current_is_wow == -2)
103+
return false; /* could not determine current process' WoW-ness */
104+
if (!IsWow64Process (process, &is_wow))
105+
return false; /* cannot determine */
106+
return is_wow == current_is_wow;
107+
}
108+
109+
/**
110+
* Inject a thread into the given process that runs ExitProcess().
111+
*
112+
* Note: as kernel32.dll is loaded before any process, the other process and
113+
* this process will have ExitProcess() at the same address.
114+
*
115+
* This function expects the process handle to have the access rights for
116+
* CreateRemoteThread(): PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION,
117+
* PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ.
118+
*
119+
* The idea comes from the Dr Dobb's article "A Safer Alternative to
120+
* TerminateProcess()" by Andrew Tucker (July 1, 1999),
121+
* http://www.drdobbs.com/a-safer-alternative-to-terminateprocess/184416547
122+
*
123+
* If this method fails, we fall back to running terminate_process_tree().
124+
*/
125+
static int
126+
exit_process(HANDLE process, int exit_code)
127+
{
128+
DWORD code;
129+
130+
if (GetExitCodeProcess (process, &code) && code == STILL_ACTIVE)
131+
{
132+
/*
133+
* We cannot determine the address of ExitProcess() for a process
134+
* that does not match the current architecture (e.g. for a 32-bit
135+
* process when we're running in 64-bit mode).
136+
*/
137+
if (process_architecture_matches_current (process))
138+
{
139+
static LPTHREAD_START_ROUTINE exit_process_address;
140+
if (!exit_process_address)
141+
{
142+
HINSTANCE kernel32 = GetModuleHandle ("kernel32");
143+
exit_process_address = (LPTHREAD_START_ROUTINE)
144+
GetProcAddress (kernel32, "ExitProcess");
145+
}
146+
DWORD thread_id;
147+
HANDLE thread = !exit_process_address ? NULL :
148+
CreateRemoteThread (process, NULL, 0, exit_process_address,
149+
(PVOID)exit_code, 0, &thread_id);
150+
151+
if (thread)
152+
{
153+
CloseHandle (thread);
154+
/*
155+
* Wait 10 seconds (arbitrary constant) for the process to
156+
* finish; After that grace period, fall back to terminating
157+
* non-gently.
158+
*/
159+
if (WaitForSingleObject (process, 10000) == WAIT_OBJECT_0)
160+
return 0;
161+
}
162+
}
163+
164+
return terminate_process_tree (process, exit_code);
165+
}
166+
167+
return -1;
168+
}
169+
170+
#endif

winsup/cygwin/include/cygwin/signal.h

+1
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,7 @@ extern const char *sys_siglist[];
441441
extern const char __declspec(dllimport) *sys_sigabbrev[];
442442
extern const char __declspec(dllimport) *sys_siglist[];
443443
#endif
444+
void kill_process_tree(pid_t pid, int sig);
444445

445446
#ifdef __cplusplus
446447
}

winsup/utils/kill.cc

+22-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ details. */
1717
#include <cygwin/version.h>
1818
#include <getopt.h>
1919
#include <limits.h>
20+
#include <cygwin/exit_process.h>
2021

2122
static char *prog_name;
2223

@@ -171,10 +172,28 @@ forcekill (int pid, int sig, int wait)
171172
return;
172173
}
173174
if (!wait || WaitForSingleObject (h, 200) != WAIT_OBJECT_0)
174-
if (sig && !TerminateProcess (h, sig << 8)
175-
&& WaitForSingleObject (h, 200) != WAIT_OBJECT_0)
176-
fprintf (stderr, "%s: couldn't kill pid %u, %u\n",
175+
{
176+
if (sig == SIGINT || sig == SIGTERM)
177+
{
178+
HANDLE cur = GetCurrentProcess (), h2;
179+
/* duplicate handle with access rights required for exit_process() */
180+
if (DuplicateHandle (cur, h, cur, &h2, PROCESS_CREATE_THREAD |
181+
PROCESS_QUERY_INFORMATION |
182+
PROCESS_VM_OPERATION |
183+
PROCESS_VM_WRITE | PROCESS_VM_READ |
184+
PROCESS_TERMINATE, FALSE, 0))
185+
{
186+
exit_process (h2, 128 + sig);
187+
CloseHandle (h2);
188+
}
189+
else
190+
terminate_process_tree(h, 128 + sig);
191+
}
192+
else if (sig && !TerminateProcess (h, sig << 8)
193+
&& WaitForSingleObject (h, 200) != WAIT_OBJECT_0)
194+
fprintf (stderr, "%s: couldn't kill pid %u, %u\n",
177195
prog_name, (unsigned) dwpid, (unsigned int) GetLastError ());
196+
}
178197
CloseHandle (h);
179198
}
180199

0 commit comments

Comments
 (0)