Skip to content

Commit daa03e3

Browse files
authored
Merge pull request #15 from dscho/dscho/ctrl-c-fixup
Ctrl+C revisited
2 parents 7509443 + f4a52b9 commit daa03e3

File tree

7 files changed

+190
-67
lines changed

7 files changed

+190
-67
lines changed

winsup/cygwin/common.din

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ sys_errlist = _sys_errlist DATA
3333
sys_nerr = _sys_nerr DATA
3434
sys_sigabbrev DATA
3535
sys_siglist DATA
36-
kill_process_tree DATA
3736

3837
# Exported functions
3938
_Exit SIGFE

winsup/cygwin/exceptions.cc

Lines changed: 2 additions & 4 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__
@@ -1547,10 +1548,7 @@ sigpacket::process ()
15471548
if (have_execed)
15481549
{
15491550
sigproc_printf ("terminating captive process");
1550-
if ((sigExeced = si.si_signo) == SIGINT)
1551-
kill_process_tree (GetProcessId (ch_spawn), sigExeced = si.si_signo);
1552-
else
1553-
TerminateProcess (ch_spawn, sigExeced = si.si_signo);
1551+
exit_process (ch_spawn, 128 + (sigExeced = si.si_signo));
15541552
}
15551553
/* Dispatch to the appropriate function. */
15561554
sigproc_printf ("signal %d, signal handler %p", si.si_signo, handler);
Lines changed: 170 additions & 0 deletions
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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -410,12 +410,11 @@ int siginterrupt (int, int);
410410
#ifdef __INSIDE_CYGWIN__
411411
extern const char *sys_sigabbrev[];
412412
extern const char *sys_siglist[];
413-
extern void kill_process_tree(pid_t pid, int sig);
414413
#else
415414
extern const char __declspec(dllimport) *sys_sigabbrev[];
416415
extern const char __declspec(dllimport) *sys_siglist[];
417-
extern void __declspec(dllimport) kill_process_tree(pid_t pid, int sig);
418416
#endif
417+
void kill_process_tree(pid_t pid, int sig);
419418

420419
#ifdef __cplusplus
421420
}

winsup/cygwin/include/cygwin/version.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -474,13 +474,12 @@ details. */
474474
307: Export timingsafe_bcmp, timingsafe_memcmp.
475475
308: Export dladdr.
476476
309: Export getloadavg.
477-
310: Export kill_process_tree.
478477
479478
Note that we forgot to bump the api for ualarm, strtoll, strtoull,
480479
sigaltstack, sethostname. */
481480

482481
#define CYGWIN_VERSION_API_MAJOR 0
483-
#define CYGWIN_VERSION_API_MINOR 310
482+
#define CYGWIN_VERSION_API_MINOR 309
484483

485484
/* There is also a compatibity version number associated with the shared memory
486485
regions. It is incremented when incompatible changes are made to the shared

winsup/cygwin/signal.cc

Lines changed: 0 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ Cygwin license. Please consult the file "CYGWIN_LICENSE" for
1010
details. */
1111

1212
#include "winsup.h"
13-
#include <tlhelp32.h>
1413
#include <stdlib.h>
1514
#include <sys/cygwin.h>
1615
#include "pinfo.h"
@@ -359,62 +358,6 @@ killpg (pid_t pgrp, int sig)
359358
return kill (-pgrp, sig);
360359
}
361360

362-
/**
363-
* Terminates the process corresponding to the process ID and all of its
364-
* directly and indirectly spawned subprocesses.
365-
*/
366-
extern "C" void
367-
kill_process_tree(pid_t pid, int sig)
368-
{
369-
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
370-
PROCESSENTRY32 entry;
371-
DWORD pids[16384];
372-
int max_len = sizeof(pids) / sizeof(*pids), i, len;
373-
374-
pids[0] = (DWORD) pid;
375-
len = 1;
376-
377-
/*
378-
* Even if Process32First()/Process32Next() seem to traverse the
379-
* processes in topological order (i.e. parent processes before
380-
* child processes), there is nothing in the Win32 API documentation
381-
* suggesting that this is guaranteed.
382-
*
383-
* Therefore, run through them at least twice and stop when no more
384-
* process IDs were added to the list.
385-
*/
386-
for (;;) {
387-
int orig_len = len;
388-
389-
memset(&entry, 0, sizeof(entry));
390-
entry.dwSize = sizeof(entry);
391-
392-
if (!Process32First(snapshot, &entry))
393-
break;
394-
395-
do {
396-
for (i = len - 1; i >= 0; i--) {
397-
if (pids[i] == entry.th32ProcessID)
398-
break;
399-
if (pids[i] == entry.th32ParentProcessID)
400-
pids[len++] = entry.th32ProcessID;
401-
}
402-
} while (len < max_len && Process32Next(snapshot, &entry));
403-
404-
if (orig_len == len || len >= max_len)
405-
break;
406-
}
407-
408-
for (i = len - 1; i >= 0; i--) {
409-
HANDLE process = OpenProcess(PROCESS_TERMINATE, FALSE, pids[i]);
410-
411-
if (process) {
412-
TerminateProcess(process, sig << 8);
413-
CloseHandle(process);
414-
}
415-
}
416-
}
417-
418361
extern "C" void
419362
abort (void)
420363
{

winsup/utils/kill.cc

Lines changed: 16 additions & 1 deletion
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

@@ -173,7 +174,21 @@ forcekill (int pid, int sig, int wait)
173174
if (!wait || WaitForSingleObject (h, 200) != WAIT_OBJECT_0)
174175
{
175176
if (sig == SIGINT || sig == SIGTERM)
176-
kill_process_tree (dwpid, sig);
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+
}
177192
else if (sig && !TerminateProcess (h, sig << 8)
178193
&& WaitForSingleObject (h, 200) != WAIT_OBJECT_0)
179194
fprintf (stderr, "%s: couldn't kill pid %u, %u\n",

0 commit comments

Comments
 (0)