Skip to content

Commit 8316956

Browse files
committed
Merge pull request #6 from dscho/ctrl-c
When interrupting Win32 processes, kill their child processes, too
2 parents 3a38677 + 24cbca5 commit 8316956

File tree

4 files changed

+203
-6
lines changed

4 files changed

+203
-6
lines changed

winsup/cygwin/exceptions.cc

Lines changed: 18 additions & 2 deletions
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), 0);
1565+
break;
1566+
}
15511567
}
15521568
/* Dispatch to the appropriate function. */
15531569
sigproc_printf ("signal %d, signal handler %p", si.si_signo, handler);
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
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+
* If appropriate, we will attempt to generate a console Ctrl event.
9+
* Otherwise 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
19+
*
20+
* This way of terminating the processes is not gentle: the process gets
21+
* no chance of cleaning up after itself (closing file handles, removing
22+
* .lock files, terminating spawned processes (if any), etc).
23+
*/
24+
static int
25+
terminate_process(HANDLE process, int exit_code)
26+
{
27+
return int(TerminateProcess (process, exit_code));
28+
}
29+
30+
/**
31+
* Terminates the process corresponding to the process ID and all of its
32+
* directly and indirectly spawned subprocesses using the provided
33+
* terminate callback function
34+
*/
35+
static int
36+
terminate_process_tree(HANDLE main_process, int exit_code, int (*terminate)(HANDLE, int))
37+
{
38+
HANDLE snapshot = CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, 0);
39+
PROCESSENTRY32 entry;
40+
DWORD pids[16384];
41+
int max_len = sizeof (pids) / sizeof (*pids), i, len, ret = 0;
42+
pid_t pid = GetProcessId (main_process);
43+
44+
pids[0] = (DWORD) pid;
45+
len = 1;
46+
47+
/*
48+
* Even if Process32First()/Process32Next() seem to traverse the
49+
* processes in topological order (i.e. parent processes before
50+
* child processes), there is nothing in the Win32 API documentation
51+
* suggesting that this is guaranteed.
52+
*
53+
* Therefore, run through them at least twice and stop when no more
54+
* process IDs were added to the list.
55+
*/
56+
for (;;)
57+
{
58+
int orig_len = len;
59+
pid_t cyg_pid;
60+
61+
memset (&entry, 0, sizeof (entry));
62+
entry.dwSize = sizeof (entry);
63+
64+
if (!Process32First (snapshot, &entry))
65+
break;
66+
67+
do
68+
{
69+
for (i = len - 1; i >= 0; i--)
70+
{
71+
cyg_pid = cygwin_winpid_to_pid(entry.th32ProcessID);
72+
if (cyg_pid > -1)
73+
{
74+
kill(cyg_pid, exit_code);
75+
continue;
76+
}
77+
if (pids[i] == entry.th32ProcessID)
78+
break;
79+
if (pids[i] == entry.th32ParentProcessID)
80+
pids[len++] = entry.th32ProcessID;
81+
}
82+
}
83+
while (len < max_len && Process32Next (snapshot, &entry));
84+
85+
if (orig_len == len || len >= max_len)
86+
break;
87+
}
88+
89+
for (i = len - 1; i >= 0; i--)
90+
{
91+
HANDLE process = i == 0 ? main_process :
92+
OpenProcess (PROCESS_TERMINATE, FALSE, pids[i]);
93+
94+
if (process)
95+
{
96+
if (!(*terminate) (process, exit_code))
97+
ret = -1;
98+
CloseHandle (process);
99+
}
100+
}
101+
102+
return ret;
103+
}
104+
105+
/**
106+
* For SIGINT/SIGTERM, call GenerateConsoleCtrlEven(). Otherwise fall back to
107+
* running terminate_process_tree().
108+
*/
109+
static int
110+
exit_process(HANDLE process, int exit_code, int okay_to_kill_this_process)
111+
{
112+
DWORD code;
113+
114+
if (GetExitCodeProcess (process, &code) && code == STILL_ACTIVE)
115+
{
116+
int signal = exit_code & 0x7f;
117+
if (signal == SIGINT || signal == SIGTERM)
118+
{
119+
#ifndef __INSIDE_CYGWIN__
120+
if (!okay_to_kill_this_process)
121+
return -1;
122+
FreeConsole();
123+
if (!AttachConsole(GetProcessId(process)))
124+
return -1;
125+
if (GenerateConsoleCtrlEvent(signal == SIGINT ? CTRL_C_EVENT : CTRL_BREAK_EVENT, 0))
126+
return 0;
127+
#else
128+
path_conv helper ("/bin/kill.exe");
129+
if (helper.exists ())
130+
{
131+
STARTUPINFOW si = {};
132+
PROCESS_INFORMATION pi;
133+
size_t len = helper.get_wide_win32_path_len ();
134+
WCHAR cmd[len + (2 * strlen (" -f -32 0xffffffff")) + 1];
135+
WCHAR title[] = L"kill";
136+
137+
helper.get_wide_win32_path (cmd);
138+
__small_swprintf (cmd + len, L" -f -%d %d", signal, (int)GetProcessId(ch_spawn));
139+
140+
si.cb = sizeof (si);
141+
si.dwFlags = STARTF_USESHOWWINDOW;
142+
si.wShowWindow = SW_HIDE;
143+
si.lpTitle = title;
144+
145+
/* Create a new hidden process. Use the two event handles as
146+
argv[1] and argv[2]. */
147+
BOOL x = CreateProcessW (NULL, cmd, &sec_none_nih, &sec_none_nih,
148+
true, CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
149+
if (x)
150+
{
151+
CloseHandle (pi.hThread);
152+
if (WaitForSingleObject (pi.hProcess, 10000) == WAIT_OBJECT_0)
153+
{
154+
CloseHandle (pi.hProcess);
155+
return 0;
156+
}
157+
CloseHandle (pi.hProcess);
158+
}
159+
}
160+
#endif
161+
}
162+
163+
return terminate_process_tree (process, exit_code, terminate_process);
164+
}
165+
166+
return -1;
167+
}
168+
169+
#endif

winsup/cygwin/include/cygwin/signal.h

Lines changed: 1 addition & 0 deletions
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

Lines changed: 15 additions & 4 deletions
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,20 @@ 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",
177-
prog_name, (unsigned) dwpid, (unsigned int) GetLastError ());
175+
{
176+
HANDLE cur = GetCurrentProcess (), h2;
177+
/* duplicate handle with access rights required for exit_process() */
178+
if (DuplicateHandle (cur, h, cur, &h2, PROCESS_CREATE_THREAD |
179+
PROCESS_QUERY_INFORMATION |
180+
PROCESS_VM_OPERATION |
181+
PROCESS_VM_WRITE | PROCESS_VM_READ |
182+
PROCESS_TERMINATE, FALSE, 0))
183+
{
184+
CloseHandle(h);
185+
h = h2;
186+
}
187+
exit_process (h2, 128 + sig, 1);
188+
}
178189
CloseHandle (h);
179190
}
180191

0 commit comments

Comments
 (0)