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> &params_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