1 // Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
2 //
3 // This program is free software; you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License, version 2.0,
5 // as published by the Free Software Foundation.
6 //
7 // This program is also distributed with certain software (including
8 // but not limited to OpenSSL) that is licensed under separate terms,
9 // as designated in a particular file or component or in included license
10 // documentation. The authors of MySQL hereby grant you an additional
11 // permission to link the program and your derivative works with the
12 // separately licensed software that they have included with MySQL.
13 //
14 // This program is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 // GNU General Public License, version 2.0, for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with this program; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
22
23 /// @file safe_process_win.cc
24 ///
25 /// Utility program that encapsulates process creation, monitoring
26 /// and bulletproof process cleanup.
27 ///
28 /// Usage:
29 /// safe_process [options to safe_process] -- progname arg1 ... argn
30 ///
31 /// To safeguard mysqld you would invoke safe_process with a few options
32 /// for safe_process itself followed by a double dash to indicate start
33 /// of the command line for the program you really want to start.
34 ///
35 /// $> safe_process --output=output.log -- mysqld --datadir=var/data1 ...
36 ///
37 /// This would redirect output to output.log and then start mysqld,
38 /// once it has done that it will continue to monitor the child as well
39 /// as the parent.
40 ///
41 /// The safe_process then checks the follwing things:
42 /// 1. Child exits, propagate the childs return code to the parent
43 /// by exiting with the same return code as the child.
44 ///
45 /// 2. Parent dies, immediately kill the child and exit, thus the
46 /// parent does not need to properly cleanup any child, it is handled
47 /// automatically.
48 ///
49 /// 3. Signal's recieced by the process will trigger same action as 2)
50 ///
51 /// 4. The named event "safe_process[pid]" can be signaled and will
52 /// trigger same action as 2)
53 ///
54 /// WARNING
55 /// Be careful when using ProcessExplorer, since it will open a handle
56 /// to each process(and maybe also the Job), the process spawned by
57 /// safe_process will not be closed off when safe_process is killed.
58
59 #include <signal.h>
60 #include <windows.h>
61
62 // Needs to be included after <windows.h>.
63 #include <tlhelp32.h>
64
65 #include <cstdio>
66 #include <cstring>
67 #include <string>
68
69 enum { PARENT, CHILD, EVENT, NUM_HANDLES };
70 HANDLE shutdown_event;
71
72 static char safe_process_name[32];
73 static int verbose = 0;
74
message(const char * fmt,...)75 static void message(const char *fmt, ...) {
76 if (!verbose) return;
77 va_list args;
78 std::fprintf(stderr, "%s: ", safe_process_name);
79 va_start(args, fmt);
80 std::vfprintf(stderr, fmt, args);
81 std::fprintf(stderr, "\n");
82 va_end(args);
83 fflush(stderr);
84 }
85
die(const char * fmt,...)86 static void die(const char *fmt, ...) {
87 DWORD last_err = GetLastError();
88
89 va_list args;
90 std::fprintf(stderr, "%s: FATAL ERROR, ", safe_process_name);
91 va_start(args, fmt);
92 std::vfprintf(stderr, fmt, args);
93 std::fprintf(stderr, "\n");
94 va_end(args);
95
96 if (last_err) {
97 char *message_text;
98 if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
99 FORMAT_MESSAGE_ALLOCATE_BUFFER |
100 FORMAT_MESSAGE_IGNORE_INSERTS,
101 NULL, last_err, 0, (LPSTR)&message_text, 0, NULL)) {
102 std::fprintf(stderr, "error: %d, %s\n", last_err, message_text);
103 LocalFree(message_text);
104 } else {
105 // FormatMessage failed, print error code only
106 std::fprintf(stderr, "error:%d\n", last_err);
107 }
108 }
109
110 fflush(stderr);
111 std::exit(1);
112 }
113
get_parent_pid(DWORD pid)114 DWORD get_parent_pid(DWORD pid) {
115 PROCESSENTRY32 pe32;
116 pe32.dwSize = sizeof(PROCESSENTRY32);
117
118 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
119 if (snapshot == INVALID_HANDLE_VALUE) die("CreateToolhelp32Snapshot failed");
120
121 if (!Process32First(snapshot, &pe32)) {
122 CloseHandle(snapshot);
123 die("Process32First failed");
124 }
125
126 DWORD parent_pid = -1;
127 do {
128 if (pe32.th32ProcessID == pid) parent_pid = pe32.th32ParentProcessID;
129 } while (Process32Next(snapshot, &pe32));
130
131 CloseHandle(snapshot);
132
133 if (parent_pid == -1) die("Could not find parent pid");
134
135 return parent_pid;
136 }
137
handle_signal(int signal)138 void handle_signal(int signal) {
139 message("Got signal: %d", signal);
140 if (SetEvent(shutdown_event) == 0) {
141 // exit safe_process and (hopefully) kill off the child
142 die("Failed to SetEvent");
143 }
144 }
145
146 /** Sets the append flag (FILE_APPEND_DATA) so that the handle inherited by the
147 * child process will be in append mode. This is in contrast to the C runtime
148 * flag that is set by f(re)open methods and is not communicated to OS, i.e. is
149 * local to process and is lost in the context of the child process. This method
150 * allows several processes to append a common file concurrently, without the
151 * safe_process child overwriting what others append.
152 * A bug to MSVCRT was submitted:
153 https://developercommunity.visualstudio.com/content/problem/921279/createprocess-does-not-inherit-fappend-flags-set-b.html
154 */
fix_file_append_flag_inheritance(DWORD std_handle)155 void fix_file_append_flag_inheritance(DWORD std_handle) {
156 HANDLE old_handle = GetStdHandle(std_handle);
157 HANDLE new_handle = ReOpenFile(old_handle, FILE_APPEND_DATA,
158 FILE_SHARE_WRITE | FILE_SHARE_READ, 0);
159 if (new_handle != INVALID_HANDLE_VALUE) {
160 SetHandleInformation(new_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
161 SetStdHandle(std_handle, new_handle);
162 CloseHandle(old_handle);
163 }
164 }
165
main(int argc,const char ** argv)166 int main(int argc, const char **argv) {
167 DWORD pid = GetCurrentProcessId();
168 sprintf(safe_process_name, "safe_process[%d]", pid);
169
170 // Create an event for the signal handler
171 if ((shutdown_event = CreateEvent(NULL, TRUE, FALSE, safe_process_name)) ==
172 NULL)
173 die("Failed to create shutdown_event");
174
175 HANDLE wait_handles[NUM_HANDLES] = {0};
176 wait_handles[EVENT] = shutdown_event;
177
178 signal(SIGINT, handle_signal);
179 signal(SIGBREAK, handle_signal);
180 signal(SIGTERM, handle_signal);
181
182 message("Started");
183
184 BOOL nocore = FALSE;
185 DWORD parent_pid = get_parent_pid(pid);
186 char child_args[8192] = {0};
187 std::string exe_name;
188
189 // Parse arguments
190 for (int i = 1; i < argc; i++) {
191 const char *arg = argv[i];
192 char *to = child_args;
193
194 if (strcmp(arg, "--") == 0 && strlen(arg) == 2) {
195 // Got the "--" delimiter
196 if (i >= argc) die("No real args -> nothing to do");
197
198 // Copy the remaining args to child_arg
199 for (int j = i + 1; j < argc; j++) {
200 arg = argv[j];
201 if (strchr(arg, ' ') && arg[0] != '\"' && arg[strlen(arg)] != '\"') {
202 // Quote arg that contains spaces and are not quoted already
203 to += std::snprintf(to, child_args + sizeof(child_args) - to,
204 "\"%s\" ", arg);
205 } else {
206 to += std::snprintf(to, child_args + sizeof(child_args) - to, "%s ",
207 arg);
208 }
209 }
210
211 // Check if executable is mysqltest.exe client
212 if (exe_name.compare("mysqltest") == 0) {
213 char safe_process_pid[32];
214
215 // Pass safeprocess PID to mysqltest which is used to create an event
216 std::sprintf(safe_process_pid, "--safe-process-pid=%d", pid);
217 to += std::snprintf(to, child_args + sizeof(child_args) - to, "%s ",
218 safe_process_pid);
219 }
220 break;
221 } else {
222 if (strcmp(arg, "--verbose") == 0)
223 verbose++;
224 else if (strncmp(arg, "--parent-pid", 10) == 0) {
225 // Override parent_pid with a value provided by user
226 const char *start;
227 if ((start = strstr(arg, "=")) == NULL)
228 die("Could not find start of option value in '%s'", arg);
229 // Step past '='
230 start++;
231 if ((parent_pid = atoi(start)) == 0)
232 die("Invalid value '%s' passed to --parent-id", start);
233 } else if (strcmp(arg, "--nocore") == 0) {
234 nocore = TRUE;
235 } else if (strncmp(arg, "--env ", 6) == 0) {
236 putenv(strdup(arg + 6));
237 } else if (std::strncmp(arg, "--safe-process-name", 19) == 0) {
238 exe_name = arg + 20;
239 } else
240 die("Unknown option: %s", arg);
241 }
242 }
243
244 if (*child_args == '\0') die("nothing to do");
245
246 // Open a handle to the parent process
247 message("parent_pid: %d", parent_pid);
248 if (parent_pid == pid) die("parent_pid is equal to own pid!");
249
250 if ((wait_handles[PARENT] = OpenProcess(SYNCHRONIZE, FALSE, parent_pid)) ==
251 NULL)
252 die("Failed to open parent process with pid: %d", parent_pid);
253
254 // Create the child process in a job
255 JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {0};
256 STARTUPINFO si = {0};
257 si.cb = sizeof(si);
258
259 // Create the job object to make it possible to kill the process
260 // and all of it's children in one go.
261 HANDLE job_handle;
262 if ((job_handle = CreateJobObject(NULL, NULL)) == NULL)
263 die("CreateJobObject failed");
264
265 // Make all processes associated with the job terminate when the
266 // last handle to the job is closed.
267 #ifndef JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
268 #define JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE 0x00002000
269 #endif
270
271 jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
272 if (SetInformationJobObject(job_handle, JobObjectExtendedLimitInformation,
273 &jeli, sizeof(jeli)) == 0)
274 message("SetInformationJobObject failed, continue anyway...");
275
276 // Avoid popup box
277 if (nocore)
278 SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX |
279 SEM_NOOPENFILEERRORBOX);
280
281 fix_file_append_flag_inheritance(STD_OUTPUT_HANDLE);
282 fix_file_append_flag_inheritance(STD_ERROR_HANDLE);
283
284 #if 0
285 // Setup stdin, stdout and stderr redirect
286 si.dwFlags= STARTF_USESTDHANDLES;
287 si.hStdInput= GetStdHandle(STD_INPUT_HANDLE);
288 si.hStdOutput= GetStdHandle(STD_OUTPUT_HANDLE);
289 si.hStdError= GetStdHandle(STD_ERROR_HANDLE);
290 #endif
291
292 // Create the process suspended to make sure it's assigned to the
293 // Job before it creates any process of it's own.
294 //
295 // Allow the new process to break away from any job that this
296 // process is part of so that it can be assigned to the new JobObject
297 // we just created. This is safe since the new JobObject is created with
298 // the JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flag, making sure it will be
299 // terminated when the last handle to it is closed(which is owned by
300 // this process).
301 //
302 // If breakaway from job fails on some reason, fallback is to create a
303 // new process group. Process groups also allow to kill process and its
304 // descedants, subject to some restrictions (processes have to run within
305 // the same console,and must not ignore CTRL_BREAK)
306 DWORD create_flags[] = {CREATE_BREAKAWAY_FROM_JOB, CREATE_NEW_PROCESS_GROUP,
307 0};
308
309 BOOL jobobject_assigned = FALSE;
310 BOOL process_created = FALSE;
311 PROCESS_INFORMATION process_info = {0};
312
313 for (int i = 0; i < sizeof(create_flags) / sizeof(create_flags[0]); i++) {
314 process_created = CreateProcess(
315 NULL, (LPSTR)child_args, NULL, NULL, TRUE, // Inherit handles
316 CREATE_SUSPENDED | create_flags[i], NULL, NULL, &si, &process_info);
317 if (process_created) {
318 jobobject_assigned =
319 AssignProcessToJobObject(job_handle, process_info.hProcess);
320 break;
321 }
322 }
323
324 if (!process_created) {
325 die("CreateProcess failed");
326 }
327
328 ResumeThread(process_info.hThread);
329 CloseHandle(process_info.hThread);
330 wait_handles[CHILD] = process_info.hProcess;
331
332 message("Started child %d", process_info.dwProcessId);
333
334 // Monitor loop
335 DWORD child_exit_code = 1;
336 DWORD wait_res =
337 WaitForMultipleObjects(NUM_HANDLES, wait_handles, FALSE, INFINITE);
338 switch (wait_res) {
339 case WAIT_OBJECT_0 + PARENT:
340 message("Parent exit");
341 break;
342 case WAIT_OBJECT_0 + CHILD:
343 if (GetExitCodeProcess(wait_handles[CHILD], &child_exit_code) == 0)
344 message("Child exit: could not get exit_code");
345 else
346 message("Child exit: exit_code: %d", child_exit_code);
347 break;
348 case WAIT_OBJECT_0 + EVENT:
349 message("Wake up from shutdown_event");
350 break;
351 default:
352 message("Unexpected result %d from WaitForMultipleObjects", wait_res);
353 break;
354 }
355
356 message("Exiting, child: %d", process_info.dwProcessId);
357
358 if (TerminateJobObject(job_handle, 201) == 0)
359 message("TerminateJobObject failed");
360
361 CloseHandle(job_handle);
362 message("Job terminated and closed");
363
364 if (!jobobject_assigned) {
365 GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, process_info.dwProcessId);
366 TerminateProcess(process_info.hProcess, 202);
367 }
368
369 if (wait_res != WAIT_OBJECT_0 + CHILD) {
370 // The child has not yet returned, wait for it
371 message("waiting for child to exit");
372
373 if ((wait_res = WaitForSingleObject(wait_handles[CHILD], INFINITE)) !=
374 WAIT_OBJECT_0) {
375 message("child wait failed: %d", wait_res);
376 } else {
377 message("child wait succeeded");
378 }
379 // Child's exit code should now be 201, no need to get it
380 }
381
382 message("Closing handles");
383 for (int i = 0; i < NUM_HANDLES; i++) CloseHandle(wait_handles[i]);
384
385 message("Exiting, exit_code: %d", child_exit_code);
386 std::exit(child_exit_code);
387 }
388