static void dumpFileDescriptorInfoWithLs(AbortHandlerState &state, char *end) { pid_t pid; int status; pid = asyncFork(); if (pid == 0) { closeAllFileDescriptors(2, true); // The '-v' is for natural sorting on Linux. On BSD -v means something else but it's harmless. execlp("ls", "ls", "-lv", state.messageBuf, (const char * const) 0); _exit(1); } else if (pid == -1) { safePrintErr("ERROR: Could not fork a process to dump file descriptor information!\n"); } else if (waitpid(pid, &status, 0) != pid || status != 0) { safePrintErr("ERROR: Could not run 'ls' to dump file descriptor information!\n"); } }
static void dumpFileDescriptorInfoWithLsof(AbortHandlerState &state, void *userData) { char *end; end = state.messageBuf; end = appendULL(end, state.pid); *end = '\0'; closeAllFileDescriptors(2, true); execlp("lsof", "lsof", "-p", state.messageBuf, "-nP", (const char * const) 0); end = state.messageBuf; end = appendText(end, "ERROR: cannot execute command 'lsof': errno="); end = appendULL(end, errno); end = appendText(end, "\n"); write_nowarn(STDERR_FILENO, state.messageBuf, end - state.messageBuf); _exit(1); }
static void dumpWithCrashWatch(AbortHandlerState &state) { char *messageBuf = state.messageBuf; const char *pidStr = messageBuf; char *end = messageBuf; end = appendULL(end, (unsigned long long) state.pid); *end = '\0'; pid_t child = asyncFork(); if (child == 0) { closeAllFileDescriptors(2, true); execlp("crash-watch", "crash-watch", "--dump", pidStr, (char * const) 0); if (errno == ENOENT) { safePrintErr("Crash-watch is not installed. Please install it with 'gem install crash-watch' " "or download it from https://github.com/FooBarWidget/crash-watch.\n"); } else { int e = errno; end = messageBuf; end = appendText(end, "crash-watch is installed, but it could not be executed! "); end = appendText(end, "(execlp() returned errno="); end = appendULL(end, e); end = appendText(end, ") Please check your file permissions or something.\n"); write_nowarn(STDERR_FILENO, messageBuf, end - messageBuf); } _exit(1); } else if (child == -1) { int e = errno; end = messageBuf; end = appendText(end, "Could not execute crash-watch: fork() failed with errno="); end = appendULL(end, e); end = appendText(end, "\n"); write_nowarn(STDERR_FILENO, messageBuf, end - messageBuf); } else { waitpid(child, NULL, 0); } }
void threadMain() { while (!boost::this_thread::interruption_requested()) { syscalls::sleep(60 * 60); begin_touch: boost::this_thread::disable_interruption di; boost::this_thread::disable_syscall_interruption dsi; // Fork a process which touches everything in the server instance dir. pid_t pid = syscalls::fork(); if (pid == 0) { // Child int prio, ret, e; closeAllFileDescriptors(2); // Make process nicer. do { prio = getpriority(PRIO_PROCESS, getpid()); } while (prio == -1 && errno == EINTR); if (prio != -1) { prio++; if (prio > 20) { prio = 20; } do { ret = setpriority(PRIO_PROCESS, getpid(), prio); } while (ret == -1 && errno == EINTR); } else { perror("getpriority"); } do { ret = chdir(wo->instanceDir->getPath().c_str()); } while (ret == -1 && errno == EINTR); if (ret == -1) { e = errno; fprintf(stderr, "chdir(\"%s\") failed: %s (%d)\n", wo->instanceDir->getPath().c_str(), strerror(e), e); fflush(stderr); _exit(1); } restoreOomScore(agentsOptions); execlp("/bin/sh", "/bin/sh", "-c", "find . | xargs touch", (char *) 0); e = errno; fprintf(stderr, "Cannot execute 'find . | xargs touch': %s (%d)\n", strerror(e), e); fflush(stderr); _exit(1); } else if (pid == -1) { // Error P_WARN("Could not touch the server instance directory because " "fork() failed. Retrying in 2 minutes..."); boost::this_thread::restore_interruption si(di); boost::this_thread::restore_syscall_interruption rsi(dsi); syscalls::sleep(60 * 2); goto begin_touch; } else { syscalls::waitpid(pid, NULL, 0); } } }
static void abortHandler(int signo, siginfo_t *info, void *ctx) { AbortHandlerState state; state.pid = getpid(); state.signo = signo; state.info = info; pid_t child; time_t t = time(NULL); char crashLogFile[256]; abortHandlerCalled++; if (abortHandlerCalled > 1) { // The abort handler itself crashed! char *end = state.messageBuf; end = appendText(end, "[ origpid="); end = appendULL(end, (unsigned long long) state.pid); end = appendText(end, ", pid="); end = appendULL(end, (unsigned long long) getpid()); end = appendText(end, ", timestamp="); end = appendULL(end, (unsigned long long) t); if (abortHandlerCalled == 2) { // This is the first time it crashed. end = appendText(end, " ] Abort handler crashed! signo="); end = appendSignalName(end, state.signo); end = appendText(end, ", reason="); end = appendSignalReason(end, state.info); end = appendText(end, "\n"); write_nowarn(STDERR_FILENO, state.messageBuf, end - state.messageBuf); // Run default signal handler. raise(signo); } else { // This is the second time it crashed, meaning it failed to // invoke the default signal handler to abort the process! end = appendText(end, " ] Abort handler crashed again! Force exiting this time. signo="); end = appendSignalName(end, state.signo); end = appendText(end, ", reason="); end = appendSignalReason(end, state.info); end = appendText(end, "\n"); write_nowarn(STDERR_FILENO, state.messageBuf, end - state.messageBuf); _exit(1); } return; } if (emergencyPipe1[0] != -1) { close(emergencyPipe1[0]); } if (emergencyPipe1[1] != -1) { close(emergencyPipe1[1]); } if (emergencyPipe2[0] != -1) { close(emergencyPipe2[0]); } if (emergencyPipe2[1] != -1) { close(emergencyPipe2[1]); } emergencyPipe1[0] = emergencyPipe1[1] = -1; emergencyPipe2[0] = emergencyPipe2[1] = -1; /* We want to dump the entire crash log to both stderr and a log file. * We use 'tee' for this. */ if (createCrashLogFile(crashLogFile, t)) { forkAndRedirectToTee(crashLogFile); } char *end = state.messagePrefix; end = appendText(end, "[ pid="); end = appendULL(end, (unsigned long long) state.pid); *end = '\0'; end = state.messageBuf; end = appendText(end, state.messagePrefix); end = appendText(end, ", timestamp="); end = appendULL(end, (unsigned long long) t); end = appendText(end, " ] Process aborted! signo="); end = appendSignalName(end, state.signo); end = appendText(end, ", reason="); end = appendSignalReason(end, state.info); end = appendText(end, ", randomSeed="); end = appendULL(end, (unsigned long long) randomSeed); end = appendText(end, "\n"); write_nowarn(STDERR_FILENO, state.messageBuf, end - state.messageBuf); end = state.messageBuf; if (*crashLogFile != '\0') { end = appendText(end, state.messagePrefix); end = appendText(end, " ] Crash log dumped to "); end = appendText(end, crashLogFile); end = appendText(end, "\n"); } else { end = appendText(end, state.messagePrefix); end = appendText(end, " ] Could not create crash log file, so dumping to stderr only.\n"); } write_nowarn(STDERR_FILENO, state.messageBuf, end - state.messageBuf); if (beepOnAbort) { end = state.messageBuf; end = appendText(end, state.messagePrefix); end = appendText(end, " ] PASSENGER_BEEP_ON_ABORT on, executing beep...\n"); write_nowarn(STDERR_FILENO, state.messageBuf, end - state.messageBuf); child = asyncFork(); if (child == 0) { closeAllFileDescriptors(2, true); #ifdef __APPLE__ execlp("osascript", "osascript", "-e", "beep 2", (const char * const) 0); safePrintErr("Cannot execute 'osascript' command\n"); #else execlp("beep", "beep", (const char * const) 0); safePrintErr("Cannot execute 'beep' command\n"); #endif _exit(1); } else if (child == -1) { int e = errno; end = state.messageBuf; end = appendText(end, state.messagePrefix); end = appendText(end, " ] Could fork a child process for invoking a beep: fork() failed with errno="); end = appendULL(end, e); end = appendText(end, "\n"); write_nowarn(STDERR_FILENO, state.messageBuf, end - state.messageBuf); } } if (stopOnAbort) { end = state.messageBuf; end = appendText(end, state.messagePrefix); end = appendText(end, " ] PASSENGER_STOP_ON_ABORT on, so process stopped. Send SIGCONT when you want to continue.\n"); write_nowarn(STDERR_FILENO, state.messageBuf, end - state.messageBuf); raise(SIGSTOP); } // It isn't safe to call any waiting functions in this signal handler, // not even read() and waitpid() even though they're async signal safe. // So we fork a child process and let it dump as much diagnostics as possible // instead of doing it in this process. child = asyncFork(); if (child == 0) { // Sleep for a short while to allow the parent process to raise SIGSTOP. // usleep() and nanosleep() aren't async signal safe so we use select() // instead. struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 100000; select(0, NULL, NULL, NULL, &tv); resetSignalHandlersAndMask(); child = asyncFork(); if (child == 0) { // OS X: for some reason the SIGPIPE handler may be reset to default after forking. // Later in this program we're going to pipe backtrace_symbols_fd() into the backtrace // sanitizer, which may fail, and we don't want the diagnostics process to crash // with SIGPIPE as a result, so we ignore SIGPIPE again. ignoreSigpipe(); dumpDiagnostics(state); // The child process may or may or may not resume the original process. // We do it ourselves just to be sure. kill(state.pid, SIGCONT); _exit(0); } else if (child == -1) { int e = errno; end = state.messageBuf; end = appendText(end, state.messagePrefix); end = appendText(end, "] Could fork a child process for dumping diagnostics: fork() failed with errno="); end = appendULL(end, e); end = appendText(end, "\n"); write_nowarn(STDERR_FILENO, state.messageBuf, end - state.messageBuf); _exit(1); } else { // Exit immediately so that child process is adopted by init process. _exit(0); } } else if (child == -1) { int e = errno; end = state.messageBuf; end = appendText(end, state.messagePrefix); end = appendText(end, " ] Could fork a child process for dumping diagnostics: fork() failed with errno="); end = appendULL(end, e); end = appendText(end, "\n"); write_nowarn(STDERR_FILENO, state.messageBuf, end - state.messageBuf); } else { raise(SIGSTOP); // Will continue after the child process has done its job. } // Run default signal handler. raise(signo); }
// This function is performed in a child process. static void dumpDiagnostics(AbortHandlerState &state) { char *messageBuf = state.messageBuf; char *end; pid_t pid; int status; end = messageBuf; end = appendText(end, state.messagePrefix); end = appendText(end, " ] Date, uname and ulimits:\n"); write_nowarn(STDERR_FILENO, messageBuf, end - messageBuf); // Dump human-readable time string and string. pid = asyncFork(); if (pid == 0) { closeAllFileDescriptors(2, true); execlp("date", "date", (const char * const) 0); _exit(1); } else if (pid == -1) { safePrintErr("ERROR: Could not fork a process to dump the time!\n"); } else if (waitpid(pid, &status, 0) != pid || status != 0) { safePrintErr("ERROR: Could not run 'date'!\n"); } // Dump system uname. pid = asyncFork(); if (pid == 0) { closeAllFileDescriptors(2, true); execlp("uname", "uname", "-mprsv", (const char * const) 0); _exit(1); } else if (pid == -1) { safePrintErr("ERROR: Could not fork a process to dump the uname!\n"); } else if (waitpid(pid, &status, 0) != pid || status != 0) { safePrintErr("ERROR: Could not run 'uname -mprsv'!\n"); } // Dump ulimit. pid = asyncFork(); if (pid == 0) { closeAllFileDescriptors(2, true); execlp("ulimit", "ulimit", "-a", (const char * const) 0); // On Linux 'ulimit' is a shell builtin, not a command. execlp("/bin/sh", "/bin/sh", "-c", "ulimit -a", (const char * const) 0); _exit(1); } else if (pid == -1) { safePrintErr("ERROR: Could not fork a process to dump the ulimit!\n"); } else if (waitpid(pid, &status, 0) != pid || status != 0) { safePrintErr("ERROR: Could not run 'ulimit -a'!\n"); } end = messageBuf; end = appendText(end, state.messagePrefix); end = appendText(end, " ] Phusion Passenger version: " PASSENGER_VERSION "\n"); write_nowarn(STDERR_FILENO, messageBuf, end - messageBuf); if (lastAssertionFailure.filename != NULL) { end = messageBuf; end = appendText(end, state.messagePrefix); end = appendText(end, " ] Last assertion failure: ("); end = appendText(end, lastAssertionFailure.expression); end = appendText(end, "), "); if (lastAssertionFailure.function != NULL) { end = appendText(end, "function "); end = appendText(end, lastAssertionFailure.function); end = appendText(end, ", "); } end = appendText(end, "file "); end = appendText(end, lastAssertionFailure.filename); end = appendText(end, ", line "); end = appendULL(end, lastAssertionFailure.line); end = appendText(end, ".\n"); write_nowarn(STDERR_FILENO, messageBuf, end - messageBuf); } // It is important that writing the message and the backtrace are two // seperate operations because it's not entirely clear whether the // latter is async signal safe and thus can crash. end = messageBuf; end = appendText(end, state.messagePrefix); #ifdef LIBC_HAS_BACKTRACE_FUNC end = appendText(end, " ] libc backtrace available!\n"); #else end = appendText(end, " ] libc backtrace not available.\n"); #endif write_nowarn(STDERR_FILENO, messageBuf, end - messageBuf); #ifdef LIBC_HAS_BACKTRACE_FUNC runInSubprocessWithTimeLimit(state, dumpBacktrace, NULL, 4000); #endif safePrintErr("--------------------------------------\n"); if (customDiagnosticsDumper != NULL) { end = messageBuf; end = appendText(end, state.messagePrefix); end = appendText(end, " ] Dumping additional diagnostical information...\n"); write_nowarn(STDERR_FILENO, messageBuf, end - messageBuf); safePrintErr("--------------------------------------\n"); runInSubprocessWithTimeLimit(state, runCustomDiagnosticsDumper, NULL, 2000); safePrintErr("--------------------------------------\n"); } dumpFileDescriptorInfo(state); safePrintErr("--------------------------------------\n"); if (shouldDumpWithCrashWatch) { end = messageBuf; end = appendText(end, state.messagePrefix); #ifdef LIBC_HAS_BACKTRACE_FUNC end = appendText(end, " ] Dumping a more detailed backtrace with crash-watch...\n"); #else end = appendText(end, " ] Dumping a backtrace with crash-watch...\n"); #endif write_nowarn(STDERR_FILENO, messageBuf, end - messageBuf); dumpWithCrashWatch(state); } else { write_nowarn(STDERR_FILENO, "\n", 1); } }
static void dumpBacktrace(AbortHandlerState &state, void *userData) { void *backtraceStore[512]; int frames = backtrace(backtraceStore, sizeof(backtraceStore) / sizeof(void *)); char *end = state.messageBuf; end = appendText(end, "--------------------------------------\n"); end = appendText(end, "[ pid="); end = appendULL(end, (unsigned long long) state.pid); end = appendText(end, " ] Backtrace with "); end = appendULL(end, (unsigned long long) frames); end = appendText(end, " frames:\n"); write_nowarn(STDERR_FILENO, state.messageBuf, end - state.messageBuf); if (backtraceSanitizerCommand != NULL) { int p[2]; if (pipe(p) == -1) { int e = errno; end = state.messageBuf; end = appendText(end, "Could not dump diagnostics through backtrace sanitizer: pipe() failed with errno="); end = appendULL(end, e); end = appendText(end, "\n"); end = appendText(end, "Falling back to writing to stderr directly...\n"); write_nowarn(STDERR_FILENO, state.messageBuf, end - state.messageBuf); backtrace_symbols_fd(backtraceStore, frames, STDERR_FILENO); return; } pid_t pid = asyncFork(); if (pid == 0) { const char *pidStr = end = state.messageBuf; end = appendULL(end, (unsigned long long) state.pid); *end = '\0'; end++; close(p[1]); dup2(p[0], STDIN_FILENO); closeAllFileDescriptors(2, true); char *command = end; end = appendText(end, "exec "); end = appendText(end, backtraceSanitizerCommand); if (backtraceSanitizerPassProgramInfo) { end = appendText(end, " \""); end = appendText(end, argv0); end = appendText(end, "\" "); end = appendText(end, pidStr); } *end = '\0'; end++; execlp("/bin/sh", "/bin/sh", "-c", command, (const char * const) 0); end = state.messageBuf; end = appendText(end, "ERROR: cannot execute '"); end = appendText(end, backtraceSanitizerCommand); end = appendText(end, "' for sanitizing the backtrace, trying 'cat'...\n"); write_nowarn(STDERR_FILENO, state.messageBuf, end - state.messageBuf); execlp("cat", "cat", (const char * const) 0); execlp("/bin/cat", "cat", (const char * const) 0); execlp("/usr/bin/cat", "cat", (const char * const) 0); safePrintErr("ERROR: cannot execute 'cat'\n"); _exit(1); } else if (pid == -1) { close(p[0]); close(p[1]); int e = errno; end = state.messageBuf; end = appendText(end, "Could not dump diagnostics through backtrace sanitizer: fork() failed with errno="); end = appendULL(end, e); end = appendText(end, "\n"); end = appendText(end, "Falling back to writing to stderr directly...\n"); write_nowarn(STDERR_FILENO, state.messageBuf, end - state.messageBuf); backtrace_symbols_fd(backtraceStore, frames, STDERR_FILENO); } else { int status = -1; close(p[0]); backtrace_symbols_fd(backtraceStore, frames, p[1]); close(p[1]); if (waitpid(pid, &status, 0) == -1 || status != 0) { end = state.messageBuf; end = appendText(end, "ERROR: cannot execute '"); end = appendText(end, backtraceSanitizerCommand); end = appendText(end, "' for sanitizing the backtrace, writing to stderr directly...\n"); write_nowarn(STDERR_FILENO, state.messageBuf, end - state.messageBuf); backtrace_symbols_fd(backtraceStore, frames, STDERR_FILENO); } } } else { backtrace_symbols_fd(backtraceStore, frames, STDERR_FILENO); } }
/** * Starts the agent process. May throw arbitrary exceptions. */ virtual pid_t start() { this_thread::disable_interruption di; this_thread::disable_syscall_interruption dsi; string exeFilename = getExeFilename(); SocketPair fds; int e, ret; pid_t pid; /* Create feedback fd for this agent process. We'll send some startup * arguments to this agent process through this fd, and we'll receive * startup information through it as well. */ fds = createUnixSocketPair(); pid = syscalls::fork(); if (pid == 0) { // Child /* Make sure file descriptor FEEDBACK_FD refers to the newly created * feedback fd (fds[1]) and close all other file descriptors. * In this child process we don't care about the original FEEDBACK_FD * (which is the Watchdog's communication channel to the agents starter.) * * fds[1] is guaranteed to be != FEEDBACK_FD because the watchdog * is started with FEEDBACK_FD already assigned. */ syscalls::close(fds[0]); if (syscalls::dup2(fds[1], FEEDBACK_FD) == -1) { /* Something went wrong, report error through feedback fd. */ e = errno; try { writeArrayMessage(fds[1], "system error before exec", "dup2() failed", toString(e).c_str(), NULL); _exit(1); } catch (...) { fprintf(stderr, "Passenger Watchdog: dup2() failed: %s (%d)\n", strerror(e), e); fflush(stderr); _exit(1); } } closeAllFileDescriptors(FEEDBACK_FD); /* Become the process group leader so that the watchdog can kill the * agent as well as all its descendant processes. */ setpgid(getpid(), getpid()); setOomScore(oldOomScore); try { execProgram(); } catch (...) { fprintf(stderr, "PassengerWatchdog: execProgram() threw an exception\n"); fflush(stderr); _exit(1); } e = errno; try { writeArrayMessage(FEEDBACK_FD, "exec error", toString(e).c_str(), NULL); } catch (...) { fprintf(stderr, "Passenger Watchdog: could not execute %s: %s (%d)\n", exeFilename.c_str(), strerror(e), e); fflush(stderr); } _exit(1); } else if (pid == -1) { // Error e = errno; throw SystemException("Cannot fork a new process", e); } else { // Parent FileDescriptor feedbackFd = fds[0]; vector<string> args; fds[1].close(); this_thread::restore_interruption ri(di); this_thread::restore_syscall_interruption rsi(dsi); ScopeGuard failGuard(boost::bind(killAndWait, pid)); /* Send startup arguments. Ignore EPIPE and ECONNRESET here * because the child process might have sent an feedback message * without reading startup arguments. */ try { sendStartupArguments(pid, feedbackFd); } catch (const SystemException &ex) { if (ex.code() != EPIPE && ex.code() != ECONNRESET) { throw SystemException(string("Unable to start the ") + name() + ": an error occurred while sending startup arguments", ex.code()); } } // Now read its feedback. try { ret = readArrayMessage(feedbackFd, args); } catch (const SystemException &e) { if (e.code() == ECONNRESET) { ret = false; } else { throw SystemException(string("Unable to start the ") + name() + ": unable to read its startup information", e.code()); } } if (!ret) { this_thread::disable_interruption di2; this_thread::disable_syscall_interruption dsi2; int status; /* The feedback fd was prematurely closed for an unknown reason. * Did the agent process crash? * * We use timedWaitPid() here because if the process crashed * because of an uncaught exception, the file descriptor * might be closed before the process has printed an error * message, so we give it some time to print the error * before we kill it. */ ret = timedWaitPid(pid, &status, 5000); if (ret == 0) { /* Doesn't look like it; it seems it's still running. * We can't do anything without proper feedback so kill * the agent process and throw an exception. */ failGuard.runNow(); throw RuntimeException(string("Unable to start the ") + name() + ": it froze and reported an unknown error during its startup"); } else if (ret != -1 && WIFSIGNALED(status)) { /* Looks like a crash which caused a signal. */ throw RuntimeException(string("Unable to start the ") + name() + ": it seems to have been killed with signal " + getSignalName(WTERMSIG(status)) + " during startup"); } else if (ret == -1) { /* Looks like it exited after detecting an error. */ throw RuntimeException(string("Unable to start the ") + name() + ": it seems to have crashed during startup for an unknown reason"); } else { /* Looks like it exited after detecting an error, but has an exit code. */ throw RuntimeException(string("Unable to start the ") + name() + ": it seems to have crashed during startup for an unknown reason, " "with exit code " + toString(WEXITSTATUS(status))); } } if (args[0] == "system error before exec") { throw SystemException(string("Unable to start the ") + name() + ": " + args[1], atoi(args[2])); } else if (args[0] == "exec error") { e = atoi(args[1]); if (e == ENOENT) { throw RuntimeException(string("Unable to start the ") + name() + " because its executable (" + getExeFilename() + ") " "doesn't exist. This probably means that your " "Phusion Passenger installation is broken or " "incomplete. Please reinstall Phusion Passenger"); } else { throw SystemException(string("Unable to start the ") + name() + " because exec(\"" + getExeFilename() + "\") failed", atoi(args[1])); } } else if (!processStartupInfo(pid, feedbackFd, args)) { throw RuntimeException(string("The ") + name() + " sent an unknown startup info message '" + args[0] + "'"); } lock_guard<boost::mutex> l(lock); this->feedbackFd = feedbackFd; this->pid = pid; failGuard.clear(); return pid; } }
/** * Call another command and get stdout as a result. */ inline std::string call(const std::string& cmd, const std::vector<std::string> &args) { cybozu::FileStat stat = cybozu::FilePath(cmd).stat(); if (!stat.exists()) { throw std::runtime_error("command not found:" + cmd); } if (!stat.isExecutable()) { throw std::runtime_error("command not executable:" + cmd); } Pipe pipe0, pipe1; pid_t cpid; cpid = ::fork(); if (cpid < 0) { throw std::runtime_error("fork() failed."); } if (cpid == 0) { /* child process */ /* bind pipe and stdout. */ pipe0.closeR(); pipe0.dupW(1); pipe1.closeR(); pipe1.dupW(2); try { redirectToNullDev(0); } catch (...) { ::exit(1); } closeAllFileDescriptors(); std::vector<char *> argv = prepareArgv(cmd, args); ::execv(cmd.c_str(), &argv[0]); } /* parent process. */ /* Read the stdout/stderr of the child process. */ pipe0.closeW(); pipe1.closeW(); std::string stdOutStr, stdErrStr; std::exception_ptr epOut, epErr; std::thread th0(streamToStr, pipe0.fdR(), std::ref(stdOutStr), std::ref(epOut)); std::thread th1(streamToStr, pipe1.fdR(), std::ref(stdErrStr), std::ref(epErr)); th0.join(); th1.join(); /* wait for done */ int status; ::waitpid(cpid, &status, 0); /* handle errors */ if (status != 0) { std::string msg("child process has returned non-zero:"); msg += cybozu::util::formatString("%d\n", status); msg += "cmd:" + cmd + "\n"; msg += "args:"; for (const std::string &arg : args) msg += arg + " "; msg += "\nstderr:" + stdErrStr; throw std::runtime_error(msg); } if (epOut) std::rethrow_exception(epOut); if (epErr) std::rethrow_exception(epErr); return stdOutStr; }