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(), ¤t_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 = GetModuleHandleA("kernel32");
131
+ if (!kernel32)
132
+ die("BUG: cannot find kernel32");
133
+ exit_process_address =
134
+ (LPTHREAD_START_ROUTINE)(void (*)(void))
135
+ GetProcAddress(kernel32, "ExitProcess");
136
+ initialized = 1;
137
+ }
138
+ if (!exit_process_address ||
139
+ !process_architecture_matches_current(process))
140
+ return terminate_process_tree(process, exit_code);
141
+
142
+ thread = CreateRemoteThread(process, NULL, 0,
143
+ exit_process_address,
144
+ arg, 0, &thread_id);
145
+ if (thread) {
146
+ CloseHandle(thread);
147
+ /*
148
+ * If the process survives for 10 seconds (a completely
149
+ * arbitrary value picked from thin air), fall back to
150
+ * killing the process tree via TerminateProcess().
151
+ */
152
+ if (WaitForSingleObject(process, 10000) ==
153
+ WAIT_OBJECT_0) {
154
+ CloseHandle(process);
155
+ return 0;
156
+ }
157
+ }
158
+
159
+ return terminate_process_tree(process, exit_code);
160
+ }
161
+
162
+ return 0;
163
+}
164
+
165
+#endif