1 /* Copyright (c) 2014, 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 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 #include "process_launcher.h"
24
25 #include <algorithm>
26 #include <array>
27 #include <cerrno>
28 #include <chrono>
29 #include <cstdio> // fprintf()
30 #include <iterator> // std::distance
31 #include <stdexcept>
32 #include <string>
33 #include <system_error>
34 #include <thread> // this_thread::sleep_for
35
36 #ifdef _WIN32
37 #include <windows.h>
38 #else
39 #include <csignal>
40 #include <cstdlib>
41 #include <cstring>
42
43 #include <fcntl.h>
44 #include <sys/select.h>
45 #include <sys/types.h>
46 #include <sys/wait.h>
47 #include <unistd.h>
48 #endif
49
50 #include "iostream"
51
52 using namespace std::chrono_literals;
53 using namespace std::string_literals;
54
55 namespace mysql_harness {
56
57 // performance tweaks
58 constexpr auto kTerminateWaitInterval = std::chrono::seconds(10);
59 #ifndef _WIN32
60 /** @brief maximum number of parameters that can be passed to the launched
61 * process */
62 constexpr auto kWaitPidCheckInterval = std::chrono::milliseconds(10);
63 constexpr size_t kMaxLaunchedProcessParams{30};
64 #endif
65
get_cmd_line() const66 std::string SpawnedProcess::get_cmd_line() const {
67 std::string result = executable_path;
68 for (const auto &arg : args) {
69 result += " " + arg;
70 }
71
72 return result;
73 }
74
~ProcessLauncher()75 ProcessLauncher::~ProcessLauncher() {
76 if (is_alive) {
77 try {
78 close();
79 } catch (const std::exception &e) {
80 fprintf(stderr, "Can't stop the alive process %s: %s\n",
81 executable_path.c_str(), e.what());
82 }
83 }
84 }
85
last_error_code()86 static std::error_code last_error_code() noexcept {
87 #ifdef _WIN32
88 return std::error_code{static_cast<int>(GetLastError()),
89 std::system_category()};
90 #else
91 return std::error_code{errno, std::generic_category()};
92 #endif
93 }
94
send_shutdown_event(ShutdownEvent event) const95 std::error_code ProcessLauncher::send_shutdown_event(
96 ShutdownEvent event /* = ShutdownEvent::TERM */) const noexcept {
97 bool ok{false};
98 #ifdef _WIN32
99 switch (event) {
100 case ShutdownEvent::TERM:
101 ok = GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pi.dwProcessId);
102 break;
103 case ShutdownEvent::KILL:
104 ok = TerminateProcess(pi.hProcess, 0);
105 break;
106 }
107 #else
108 switch (event) {
109 case ShutdownEvent::TERM:
110 ok = ::kill(childpid, SIGTERM) == 0;
111 break;
112 case ShutdownEvent::KILL:
113 ok = ::kill(childpid, SIGKILL) == 0;
114 break;
115 }
116 #endif
117
118 return ok ? std::error_code{} : last_error_code();
119 }
120
121 #ifdef _WIN32
122
123 namespace win32 {
124 // reverse of CommandLineToArgv()
cmdline_quote_arg(const std::string & arg)125 std::string cmdline_quote_arg(const std::string &arg) {
126 if (!arg.empty() && (arg.find_first_of(" \t\n\v\"") == arg.npos)) {
127 // no need to quote it
128 return arg;
129 }
130
131 std::string out("\"");
132
133 for (auto it = arg.begin(); it != arg.end(); ++it) {
134 // backslashes are special at the end of the line
135 //
136 // foo\bar -> "foo\\bar"
137 // foobar\ -> "foobar\\"
138 // foobar\\ -> "foobar\\\\"
139 // foobar\" -> "foobar\""
140
141 auto no_backslash_it = std::find_if(
142 it, arg.end(), [](const auto &value) { return value != '\\'; });
143
144 const size_t num_backslash = std::distance(it, no_backslash_it);
145 // move past the backslashes
146 it = no_backslash_it;
147
148 if (it == arg.end()) {
149 // one-or-more backslash to the end
150 //
151 // escape all backslash
152 out.append(num_backslash * 2, '\\');
153
154 // we are at the end, get out
155 break;
156 }
157
158 if (*it == '"') {
159 // one-or-more backslash before "
160 // escape all backslash and "
161 out.append(num_backslash * 2 + 1, '\\');
162 } else {
163 // zero-or-more backslash before non-special char|end
164 // don't escape
165 out.append(num_backslash, '\\');
166 }
167 out.push_back(*it);
168 }
169
170 out.push_back('"');
171
172 return out;
173 }
174
cmdline_from_args(const std::string & executable_path,const std::vector<std::string> & args)175 std::string cmdline_from_args(const std::string &executable_path,
176 const std::vector<std::string> &args) {
177 std::string s = win32::cmdline_quote_arg(executable_path);
178
179 for (const auto &arg : args) {
180 s.append(" ").append(win32::cmdline_quote_arg(arg));
181 }
182
183 return s;
184 }
185
186 } // namespace win32
187
start()188 void ProcessLauncher::start() {
189 SECURITY_ATTRIBUTES saAttr;
190
191 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
192 saAttr.bInheritHandle = TRUE;
193 saAttr.lpSecurityDescriptor = NULL;
194
195 if (!CreatePipe(&child_out_rd, &child_out_wr, &saAttr, 0)) {
196 throw std::system_error(last_error_code(), "Failed to create child_out_rd");
197 }
198
199 if (!SetHandleInformation(child_out_rd, HANDLE_FLAG_INHERIT, 0))
200 throw std::system_error(last_error_code(), "Failed to create child_out_rd");
201
202 // force non blocking IO in Windows
203 // DWORD mode = PIPE_NOWAIT;
204 // BOOL res = SetNamedPipeHandleState(child_out_rd, &mode, NULL, NULL);
205
206 if (!CreatePipe(&child_in_rd, &child_in_wr, &saAttr, 0))
207 throw std::system_error(last_error_code(), "Failed to create child_in_rd");
208
209 if (!SetHandleInformation(child_in_wr, HANDLE_FLAG_INHERIT, 0))
210 throw std::system_error(last_error_code(), "Failed to created child_in_wr");
211
212 std::string arguments = win32::cmdline_from_args(executable_path, args);
213
214 si.cb = sizeof(STARTUPINFO);
215 if (redirect_stderr) si.hStdError = child_out_wr;
216 si.hStdOutput = child_out_wr;
217 si.hStdInput = child_in_rd;
218 si.dwFlags |= STARTF_USESTDHANDLES;
219
220 // as CreateProcess may/will modify the arguments (split filename and args
221 // with a \0) keep a copy of it for error-reporting.
222 std::string create_process_arguments = arguments;
223 BOOL bSuccess =
224 CreateProcess(NULL, // lpApplicationName
225 &create_process_arguments.front(), // lpCommandLine
226 NULL, // lpProcessAttributes
227 NULL, // lpThreadAttributes
228 TRUE, // bInheritHandles
229 CREATE_NEW_PROCESS_GROUP, // dwCreationFlags
230 NULL, // lpEnvironment
231 NULL, // lpCurrentDirectory
232 &si, // lpStartupInfo
233 &pi); // lpProcessInformation
234
235 if (!bSuccess) {
236 throw std::system_error(last_error_code(),
237 "Failed to start process " + arguments);
238 } else {
239 is_alive = true;
240 }
241
242 CloseHandle(child_out_wr);
243 CloseHandle(child_in_rd);
244
245 // DWORD res1 = WaitForInputIdle(pi.hProcess, 100);
246 // res1 = WaitForSingleObject(pi.hThread, 100);
247 }
248
get_pid() const249 uint64_t ProcessLauncher::get_pid() const { return (uint64_t)pi.hProcess; }
250
wait(std::chrono::milliseconds timeout)251 int ProcessLauncher::wait(std::chrono::milliseconds timeout) {
252 DWORD dwExit = 0;
253 BOOL get_ret{FALSE};
254 if ((get_ret = GetExitCodeProcess(pi.hProcess, &dwExit))) {
255 if (dwExit == STILL_ACTIVE) {
256 auto wait_ret = WaitForSingleObject(pi.hProcess, timeout.count());
257 switch (wait_ret) {
258 case WAIT_OBJECT_0:
259 get_ret = GetExitCodeProcess(pi.hProcess, &dwExit);
260 break;
261 case WAIT_TIMEOUT:
262 throw std::system_error(std::make_error_code(std::errc::timed_out),
263 std::string("Timed out waiting " +
264 std::to_string(timeout.count()) +
265 " ms for the process '" +
266 executable_path + "' to exit"));
267 case WAIT_FAILED:
268 throw std::system_error(last_error_code());
269 default:
270 throw std::runtime_error(
271 "Unexpected error while waiting for the process '" +
272 executable_path + "' to finish: " + std::to_string(wait_ret));
273 }
274 }
275 }
276 if (get_ret == FALSE) {
277 auto ec = last_error_code();
278 if (ec != std::error_code(ERROR_INVALID_HANDLE,
279 std::system_category())) // not closed already?
280 throw std::system_error(ec);
281 else
282 dwExit = 128; // Invalid handle
283 }
284 return dwExit;
285 }
286
close()287 int ProcessLauncher::close() {
288 DWORD dwExit;
289 if (GetExitCodeProcess(pi.hProcess, &dwExit)) {
290 if (dwExit == STILL_ACTIVE) {
291 send_shutdown_event(ShutdownEvent::TERM);
292
293 DWORD wait_timeout =
294 std::chrono::duration_cast<std::chrono::milliseconds>(
295 kTerminateWaitInterval)
296 .count();
297 if (WaitForSingleObject(pi.hProcess, wait_timeout) != WAIT_OBJECT_0) {
298 // use the big hammer if that did not work
299 if (send_shutdown_event(ShutdownEvent::KILL))
300 throw std::system_error(last_error_code());
301
302 // wait again, if that fails not much we can do
303 if (WaitForSingleObject(pi.hProcess, wait_timeout) != WAIT_OBJECT_0) {
304 throw std::system_error(last_error_code());
305 }
306 }
307 }
308 } else {
309 if (is_alive) throw std::system_error(last_error_code());
310 }
311
312 if (!CloseHandle(pi.hProcess)) throw std::system_error(last_error_code());
313 if (!CloseHandle(pi.hThread)) throw std::system_error(last_error_code());
314
315 if (!CloseHandle(child_out_rd)) throw std::system_error(last_error_code());
316 if (!child_in_wr_closed && !CloseHandle(child_in_wr))
317 throw std::system_error(last_error_code());
318
319 is_alive = false;
320 return 0;
321 }
322
read(char * buf,size_t count,std::chrono::milliseconds timeout)323 int ProcessLauncher::read(char *buf, size_t count,
324 std::chrono::milliseconds timeout) {
325 DWORD dwBytesRead;
326 DWORD dwBytesAvail;
327
328 // at least 1ms, but max 100ms
329 auto std_interval = std::min(100ms, std::max(timeout / 10, 1ms));
330
331 do {
332 // check if there is data in the pipe before issuing a blocking read
333 BOOL bSuccess =
334 PeekNamedPipe(child_out_rd, NULL, 0, NULL, &dwBytesAvail, NULL);
335
336 if (!bSuccess) {
337 auto ec = last_error_code();
338 if (ec == std::error_code(ERROR_NO_DATA, std::system_category()) ||
339 ec == std::error_code(ERROR_BROKEN_PIPE, std::system_category())) {
340 return EOF;
341 } else {
342 throw std::system_error(last_error_code());
343 }
344 }
345
346 // we got data, let's read it
347 if (dwBytesAvail != 0) break;
348
349 if (timeout.count() == 0) {
350 // no data and time left to wait
351 //
352
353 return 0;
354 }
355
356 auto interval = std::min(timeout, std_interval);
357
358 // just wait the whole timeout and try again
359 std::this_thread::sleep_for(interval);
360
361 timeout -= interval;
362 } while (true);
363
364 BOOL bSuccess = ReadFile(child_out_rd, buf, count, &dwBytesRead, NULL);
365
366 if (bSuccess == FALSE) {
367 auto ec = last_error_code();
368 if (ec == std::error_code(ERROR_NO_DATA, std::system_category()) ||
369 ec == std::error_code(ERROR_BROKEN_PIPE, std::system_category())) {
370 return EOF;
371 } else {
372 throw std::system_error(ec);
373 }
374 }
375
376 return dwBytesRead;
377 }
378
write(const char * buf,size_t count)379 int ProcessLauncher::write(const char *buf, size_t count) {
380 DWORD dwBytesWritten;
381
382 BOOL bSuccess = WriteFile(child_in_wr, buf, count, &dwBytesWritten, NULL);
383 if (!bSuccess) {
384 auto ec = last_error_code();
385 if (ec !=
386 std::error_code(
387 ERROR_NO_DATA,
388 std::system_category())) // otherwise child process just died.
389 throw std::system_error(ec);
390 } else {
391 // When child input buffer is full, this returns zero in NO_WAIT mode.
392 return dwBytesWritten;
393 }
394 return 0; // so the compiler does not cry
395 }
396
end_of_write()397 void ProcessLauncher::end_of_write() {
398 CloseHandle(child_in_wr);
399 child_in_wr_closed = true;
400 }
401
get_fd_write() const402 uint64_t ProcessLauncher::get_fd_write() const { return (uint64_t)child_in_wr; }
403
get_fd_read() const404 uint64_t ProcessLauncher::get_fd_read() const { return (uint64_t)child_out_rd; }
405
406 #else
407
get_params(const std::string & command,const std::vector<std::string> & params_vec)408 static std::array<const char *, kMaxLaunchedProcessParams> get_params(
409 const std::string &command, const std::vector<std::string> ¶ms_vec) {
410 std::array<const char *, kMaxLaunchedProcessParams> result;
411 result[0] = command.c_str();
412
413 size_t i = 1;
414 for (const auto &par : params_vec) {
415 if (i >= kMaxLaunchedProcessParams - 1) {
416 throw std::runtime_error("Too many parameters passed to the " + command);
417 }
418 result[i++] = par.c_str();
419 }
420 result[i] = nullptr;
421
422 return result;
423 }
424
start()425 void ProcessLauncher::start() {
426 if (pipe(fd_in) < 0) {
427 throw std::system_error(last_error_code(),
428 "ProcessLauncher::start() pipe(fd_in)");
429 }
430 if (pipe(fd_out) < 0) {
431 throw std::system_error(last_error_code(),
432 "ProcessLauncher::start() pipe(fd_out)");
433 }
434
435 // Ignore broken pipe signal
436 signal(SIGPIPE, SIG_IGN);
437
438 childpid = fork();
439 if (childpid == -1) {
440 throw std::system_error(last_error_code(),
441 "ProcessLauncher::start() fork()");
442 }
443
444 if (childpid == 0) {
445 #ifdef LINUX
446 prctl(PR_SET_PDEATHSIG, SIGHUP);
447 #endif
448
449 ::close(fd_out[0]);
450 ::close(fd_in[1]);
451 while (dup2(fd_out[1], STDOUT_FILENO) == -1) {
452 auto ec = last_error_code();
453 if (ec == std::errc::interrupted) {
454 continue;
455 } else {
456 throw std::system_error(ec, "ProcessLauncher::start() dup2()");
457 }
458 }
459
460 if (redirect_stderr) {
461 while (dup2(fd_out[1], STDERR_FILENO) == -1) {
462 auto ec = last_error_code();
463 if (ec == std::errc::interrupted) {
464 continue;
465 } else {
466 throw std::system_error(ec, "ProcessLauncher::start() dup2()");
467 }
468 }
469 }
470 while (dup2(fd_in[0], STDIN_FILENO) == -1) {
471 auto ec = last_error_code();
472 if (ec == std::errc::interrupted) {
473 continue;
474 } else {
475 throw std::system_error(ec, "ProcessLauncher::start() dup2()");
476 }
477 }
478
479 fcntl(fd_out[1], F_SETFD, FD_CLOEXEC);
480 fcntl(fd_in[0], F_SETFD, FD_CLOEXEC);
481
482 const auto params_arr = get_params(executable_path, args);
483 execvp(executable_path.c_str(),
484 const_cast<char *const *>(params_arr.data()));
485 // if exec returns, there is an error.
486 auto ec = last_error_code();
487 fprintf(stderr, "%s could not be executed: %s (errno %d)\n",
488 executable_path.c_str(), ec.message().c_str(), ec.value());
489
490 if (ec == std::errc::no_such_file_or_directory) {
491 // we need to identify an ENOENT and since some programs return 2 as
492 // exit-code we need to return a non-existent code, 128 is a general
493 // convention used to indicate a failure to execute another program in a
494 // subprocess
495 exit(128);
496 } else {
497 exit(ec.value());
498 }
499 } else {
500 ::close(fd_out[1]);
501 ::close(fd_in[0]);
502
503 fd_out[1] = -1;
504 fd_in[0] = -1;
505
506 is_alive = true;
507 }
508 }
509
close()510 int ProcessLauncher::close() {
511 int result = 0;
512 if (is_alive) {
513 // only try to kill the pid, if we started it. Not that we hurt someone
514 // else.
515 if (std::error_code ec1 = send_shutdown_event(ShutdownEvent::TERM)) {
516 if (ec1 != std::errc::no_such_process) {
517 throw std::system_error(ec1);
518 }
519 } else {
520 try {
521 // wait for it shutdown before using the big hammer
522 result = wait(kTerminateWaitInterval);
523 } catch (const std::system_error &e) {
524 if (e.code() != std::errc::no_such_process) {
525 std::error_code ec2 = send_shutdown_event(ShutdownEvent::KILL);
526 if (ec2 != std::errc::no_such_process) {
527 throw std::system_error(ec2);
528 }
529 }
530 result = wait();
531 }
532 }
533 }
534
535 if (fd_out[0] != -1) ::close(fd_out[0]);
536 if (fd_in[1] != -1) ::close(fd_in[1]);
537
538 fd_out[0] = -1;
539 fd_in[1] = -1;
540 is_alive = false;
541
542 return result;
543 }
544
end_of_write()545 void ProcessLauncher::end_of_write() {
546 if (fd_in[1] != -1) ::close(fd_in[1]);
547 fd_in[1] = -1;
548 }
549
read(char * buf,size_t count,std::chrono::milliseconds timeout)550 int ProcessLauncher::read(char *buf, size_t count,
551 std::chrono::milliseconds timeout) {
552 int n;
553 fd_set set;
554 struct timeval timeout_tv;
555 memset(&timeout_tv, 0x0, sizeof(timeout_tv));
556 timeout_tv.tv_sec =
557 static_cast<decltype(timeout_tv.tv_sec)>(timeout.count() / 1000);
558 timeout_tv.tv_usec = static_cast<decltype(timeout_tv.tv_usec)>(
559 (timeout.count() % 1000) * 1000);
560
561 FD_ZERO(&set);
562 FD_SET(fd_out[0], &set);
563
564 int res = select(fd_out[0] + 1, &set, nullptr, nullptr, &timeout_tv);
565 if (res < 0) throw std::system_error(last_error_code(), "select()");
566 if (res == 0) return 0;
567
568 if ((n = (int)::read(fd_out[0], buf, count)) >= 0) return n;
569
570 throw std::system_error(last_error_code(), "read");
571 }
572
write(const char * buf,size_t count)573 int ProcessLauncher::write(const char *buf, size_t count) {
574 int n;
575 if ((n = (int)::write(fd_in[1], buf, count)) >= 0) return n;
576
577 auto ec = last_error_code();
578 if (ec == std::errc::broken_pipe) return 0;
579
580 throw std::system_error(ec, "write");
581 }
582
get_pid() const583 uint64_t ProcessLauncher::get_pid() const {
584 static_assert(sizeof(pid_t) <= sizeof(uint64_t),
585 "sizeof(pid_t) > sizeof(uint64_t)");
586 return (uint64_t)childpid;
587 }
588
wait(const std::chrono::milliseconds timeout)589 int ProcessLauncher::wait(const std::chrono::milliseconds timeout) {
590 using namespace std::chrono_literals;
591 auto wait_time = timeout;
592 do {
593 int status;
594
595 pid_t ret = ::waitpid(childpid, &status, WNOHANG);
596
597 if (ret == 0) {
598 auto sleep_for = std::min(wait_time, kWaitPidCheckInterval);
599 if (sleep_for.count() > 0) {
600 std::this_thread::sleep_for(std::chrono::milliseconds(sleep_for));
601 wait_time -= sleep_for;
602 } else {
603 throw std::system_error(std::make_error_code(std::errc::timed_out),
604 std::string("Timed out waiting ") +
605 std::to_string(timeout.count()) +
606 " ms for the process " +
607 std::to_string(childpid) + " to exit");
608 }
609 } else if (ret == -1) {
610 throw std::system_error(
611 last_error_code(),
612 std::string("waiting for process '" + executable_path + "' failed"));
613 } else {
614 if (WIFEXITED(status)) {
615 return WEXITSTATUS(status);
616 } else if (WIFSIGNALED(status)) {
617 std::string msg;
618 std::array<char, 1024> b;
619 int n;
620 while ((n = read(b.data(), b.size(), 100ms)) > 0) {
621 msg.append(b.data(), n);
622 }
623 throw std::runtime_error(std::string("Process '" + executable_path +
624 "' got signal " +
625 std::to_string(WTERMSIG(status))) +
626 ":\n" + msg);
627 } else {
628 // it neither exited, not received a signal.
629 throw std::runtime_error(
630 std::string("Process '" + executable_path + "' ... no idea"));
631 }
632 }
633 } while (true);
634 }
635
get_fd_write() const636 uint64_t ProcessLauncher::get_fd_write() const { return (uint64_t)fd_in[1]; }
637
get_fd_read() const638 uint64_t ProcessLauncher::get_fd_read() const { return (uint64_t)fd_out[0]; }
639
640 #endif
641
kill()642 int ProcessLauncher::kill() { return close(); }
643
644 } // end of namespace mysql_harness
645