void Process::finish(int returnCode) { { std::lock_guard<std::mutex> lock(mMutex); mReturn = returnCode; mStdInBuffer.clear(); closeStdIn(CloseForce); mWantStdInClosed = false; if (mMode == Async) { // try to read all remaining data on stdout and stderr handleOutput(mStdOut[0], mStdOutBuffer, mStdOutIndex, mReadyReadStdOut); handleOutput(mStdErr[0], mStdErrBuffer, mStdErrIndex, mReadyReadStdErr); closeStdOut(); closeStdErr(); } else { int w; char q = 'q'; eintrwrap(w, ::write(mSync[1], &q, 1)); eintrwrap(w, ::close(mSync[1])); mSync[1] = -1; } } if (mMode == Async) mFinished(this); }
void Process::event(const Event* event) { if (event->type() == ProcessFinishedEvent::Type) { const ProcessFinishedEvent* pevent = static_cast<const ProcessFinishedEvent*>(event); if (mPid == -1) { error() << "process already finished, pid " << pevent->pid; return; } assert(mPid == pevent->pid); mPid = -1; mReturn = pevent->returnCode; mStdInBuffer.clear(); closeStdIn(); // try to read all remaining data on stdout and stderr handleOutput(mStdOut[0], mStdOutBuffer, mStdOutIndex, mReadyReadStdOut); handleOutput(mStdErr[0], mStdErrBuffer, mStdErrIndex, mReadyReadStdErr); closeStdOut(); closeStdErr(); mFinished(); } else { EventReceiver::event(event); } }
void Process::clear() { // don't clear a running process please assert(mPid == -1); if (mStdIn[0] != -1 && EventLoop::eventLoop()) { // try to finish off any pending writes handleInput(mStdIn[1]); } closeStdIn(CloseForce); closeStdOut(); closeStdErr(); int w; if (mSync[0] != -1) eintrwrap(w, ::close(mSync[0])); if (mSync[1] != -1) eintrwrap(w, ::close(mSync[1])); mReturn = ReturnUnset; mStdInIndex = mStdOutIndex = mStdErrIndex = 0; mWantStdInClosed = false; mMode = Sync; }
Process::~Process() { if (mPid != -1) ProcessThread::removePid(mPid); if (mStdIn[0] != -1) { // try to finish off any pending writes handleInput(mStdIn[1]); } closeStdIn(); closeStdOut(); closeStdErr(); }
Process::~Process() { { std::lock_guard<std::mutex> lock(mMutex); assert(mReturn != ReturnUnset || mPid == -1); } if (mStdIn[0] != -1 && EventLoop::eventLoop()) { // try to finish off any pending writes handleInput(mStdIn[1]); } closeStdIn(CloseForce); closeStdOut(); closeStdErr(); int w; if (mSync[0] != -1) eintrwrap(w, ::close(mSync[0])); if (mSync[1] != -1) eintrwrap(w, ::close(mSync[1])); }
Process::ExecState Process::startInternal(const Path &command, const List<String> &a, const List<String> &environ, int timeout, unsigned execFlags) { mErrorString.clear(); Path cmd = findCommand(command); if (cmd.isEmpty()) { mErrorString = "Command not found"; return Error; } List<String> arguments = a; int err; int closePipe[2]; eintrwrap(err, ::pipe(closePipe)); #ifdef HAVE_CLOEXEC if (!SocketClient::setFlags(closePipe[1], FD_CLOEXEC, F_GETFD, F_SETFD)) { mErrorString = "Unable to set FD_CLOEXEC"; eintrwrap(err, ::close(closePipe[0])); eintrwrap(err, ::close(closePipe[1])); return Error; } #else #warning No CLOEXEC, Process might have problematic behavior #endif eintrwrap(err, ::pipe(mStdIn)); eintrwrap(err, ::pipe(mStdOut)); eintrwrap(err, ::pipe(mStdErr)); if (mMode == Sync) eintrwrap(err, ::pipe(mSync)); const char **args = new const char*[arguments.size() + 2]; // const char* args[arguments.size() + 2]; args[arguments.size() + 1] = 0; args[0] = cmd.nullTerminated(); int pos = 1; for (List<String>::const_iterator it = arguments.begin(); it != arguments.end(); ++it) { args[pos] = it->nullTerminated(); //printf("arg: '%s'\n", args[pos]); ++pos; } const bool hasEnviron = !environ.empty(); const char **env = new const char*[environ.size() + 1]; env[environ.size()] = 0; if (hasEnviron) { pos = 0; //printf("fork, about to exec '%s'\n", cmd.nullTerminated()); for (List<String>::const_iterator it = environ.begin(); it != environ.end(); ++it) { env[pos] = it->nullTerminated(); //printf("env: '%s'\n", env[pos]); ++pos; } } mPid = ::fork(); if (mPid == -1) { //printf("fork, something horrible has happened %d\n", errno); // bail out eintrwrap(err, ::close(mStdIn[1])); eintrwrap(err, ::close(mStdIn[0])); eintrwrap(err, ::close(mStdOut[1])); eintrwrap(err, ::close(mStdOut[0])); eintrwrap(err, ::close(mStdErr[1])); eintrwrap(err, ::close(mStdErr[0])); eintrwrap(err, ::close(closePipe[1])); eintrwrap(err, ::close(closePipe[0])); mErrorString = "Fork failed"; delete[] env; delete[] args; return Error; } else if (mPid == 0) { //printf("fork, in child\n"); // child, should do some error checking here really eintrwrap(err, ::close(closePipe[0])); eintrwrap(err, ::close(mStdIn[1])); eintrwrap(err, ::close(mStdOut[0])); eintrwrap(err, ::close(mStdErr[0])); eintrwrap(err, ::close(STDIN_FILENO)); eintrwrap(err, ::close(STDOUT_FILENO)); eintrwrap(err, ::close(STDERR_FILENO)); eintrwrap(err, ::dup2(mStdIn[0], STDIN_FILENO)); eintrwrap(err, ::close(mStdIn[0])); eintrwrap(err, ::dup2(mStdOut[1], STDOUT_FILENO)); eintrwrap(err, ::close(mStdOut[1])); eintrwrap(err, ::dup2(mStdErr[1], STDERR_FILENO)); eintrwrap(err, ::close(mStdErr[1])); int ret; if (!mChRoot.isEmpty() && ::chroot(mChRoot.constData())) { goto error; } if (!mCwd.isEmpty() && ::chdir(mCwd.constData())) { goto error; } if (hasEnviron) { ret = ::execve(cmd.nullTerminated(), const_cast<char* const*>(args), const_cast<char* const*>(env)); } else { ret = ::execv(cmd.nullTerminated(), const_cast<char* const*>(args)); } // notify the parent process error: const char c = 'c'; eintrwrap(err, ::write(closePipe[1], &c, 1)); eintrwrap(err, ::close(closePipe[1])); ::_exit(1); (void)ret; //printf("fork, exec seemingly failed %d, %d %s\n", ret, errno, strerror(errno)); } else { delete[] env; delete[] args; // parent eintrwrap(err, ::close(closePipe[1])); eintrwrap(err, ::close(mStdIn[0])); eintrwrap(err, ::close(mStdOut[1])); eintrwrap(err, ::close(mStdErr[1])); //printf("fork, in parent\n"); int flags; eintrwrap(flags, fcntl(mStdIn[1], F_GETFL, 0)); eintrwrap(flags, fcntl(mStdIn[1], F_SETFL, flags | O_NONBLOCK)); eintrwrap(flags, fcntl(mStdOut[0], F_GETFL, 0)); eintrwrap(flags, fcntl(mStdOut[0], F_SETFL, flags | O_NONBLOCK)); eintrwrap(flags, fcntl(mStdErr[0], F_GETFL, 0)); eintrwrap(flags, fcntl(mStdErr[0], F_SETFL, flags | O_NONBLOCK)); // block until exec is called in the child or until exec fails { char c; eintrwrap(err, ::read(closePipe[0], &c, 1)); (void)c; if (err == -1) { // bad eintrwrap(err, ::close(closePipe[0])); mErrorString = "Failed to read from closePipe during process start"; mPid = -1; return Error; } else if (err == 0) { // process has started successfully eintrwrap(err, ::close(closePipe[0])); } else if (err == 1) { // process start failed eintrwrap(err, ::close(closePipe[0])); mErrorString = "Process failed to start"; mPid = -1; return Error; } } ProcessThread::addPid(mPid, this, (mMode == Async)); //printf("fork, about to add fds: stdin=%d, stdout=%d, stderr=%d\n", mStdIn[1], mStdOut[0], mStdErr[0]); if (mMode == Async) { if (EventLoop::SharedPtr loop = EventLoop::eventLoop()) { loop->registerSocket(mStdOut[0], EventLoop::SocketRead, std::bind(&Process::processCallback, this, std::placeholders::_1, std::placeholders::_2)); loop->registerSocket(mStdErr[0], EventLoop::SocketRead, std::bind(&Process::processCallback, this, std::placeholders::_1, std::placeholders::_2)); } } else { // select and stuff timeval started, now, *selecttime = 0; if (timeout > 0) { Rct::gettime(&started); now = started; selecttime = &now; Rct::timevalAdd(selecttime, timeout); } if (!(execFlags & NoCloseStdIn)) { closeStdIn(CloseForce); mWantStdInClosed = false; } for (;;) { // set up all the select crap fd_set rfds, wfds; FD_ZERO(&rfds); FD_ZERO(&wfds); int max = 0; FD_SET(mStdOut[0], &rfds); max = std::max(max, mStdOut[0]); FD_SET(mStdErr[0], &rfds); max = std::max(max, mStdErr[0]); FD_SET(mSync[0], &rfds); max = std::max(max, mSync[0]); if (mStdIn[1] != -1) { FD_SET(mStdIn[1], &wfds); max = std::max(max, mStdIn[1]); } int ret; eintrwrap(ret, ::select(max + 1, &rfds, &wfds, 0, selecttime)); if (ret == -1) { // ow mErrorString = "Sync select failed: "; mErrorString += strerror(errno); return Error; } // check fds and stuff if (FD_ISSET(mStdOut[0], &rfds)) handleOutput(mStdOut[0], mStdOutBuffer, mStdOutIndex, mReadyReadStdOut); if (FD_ISSET(mStdErr[0], &rfds)) handleOutput(mStdErr[0], mStdErrBuffer, mStdErrIndex, mReadyReadStdErr); if (mStdIn[1] != -1 && FD_ISSET(mStdIn[1], &wfds)) handleInput(mStdIn[1]); if (FD_ISSET(mSync[0], &rfds)) { // we're done { std::lock_guard<std::mutex> lock(mMutex); assert(mSync[1] == -1); // try to read all remaining data on stdout and stderr handleOutput(mStdOut[0], mStdOutBuffer, mStdOutIndex, mReadyReadStdOut); handleOutput(mStdErr[0], mStdErrBuffer, mStdErrIndex, mReadyReadStdErr); closeStdOut(); closeStdErr(); int w; eintrwrap(w, ::close(mSync[0])); mSync[0] = -1; } mFinished(this); return Done; } if (timeout) { assert(selecttime); Rct::gettime(selecttime); const int lasted = Rct::timevalDiff(selecttime, &started); if (lasted >= timeout) { // timeout, we're done kill(); // attempt to kill mErrorString = "Timed out"; return TimedOut; } *selecttime = started; Rct::timevalAdd(selecttime, timeout); } } } } return Done; }
Error ChildProcess::run() { Error error; // NOTE: if the run method is called from multiple threads in single app // concurrently then a race condition can cause handles to get incorrectly // directed. the workaround suggested by microsoft is to wrap the process // creation code in a critical section. see this article for details: // http://support.microsoft.com/kb/315939 static CriticalSection s_runCriticalSection; CriticalSection::Scope csScope(s_runCriticalSection); // pseudoterminal mode: use winpty to emulate Posix pseudoterminal if (options_.pseudoterminal) { error = pImpl_->pty.start(exe_, args_, options_, &pImpl_->hStdInWrite, &pImpl_->hStdOutRead, &pImpl_->hStdErrRead, &pImpl_->hProcess); if (!error) { pImpl_->pid = ::GetProcessId(pImpl_->hProcess); } return error; } // Standard input pipe HANDLE hStdInRead; if (!::CreatePipe(&hStdInRead, &pImpl_->hStdInWrite, NULL, 0)) return systemError(::GetLastError(), ERROR_LOCATION); CloseHandleOnExitScope closeStdIn(&hStdInRead, ERROR_LOCATION); if (!::SetHandleInformation(hStdInRead, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) return systemError(::GetLastError(), ERROR_LOCATION); // Standard output pipe HANDLE hStdOutWrite; if (!::CreatePipe(&pImpl_->hStdOutRead, &hStdOutWrite, NULL, 0)) return systemError(::GetLastError(), ERROR_LOCATION); CloseHandleOnExitScope closeStdOut(&hStdOutWrite, ERROR_LOCATION); if (!::SetHandleInformation(hStdOutWrite, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) ) return systemError(::GetLastError(), ERROR_LOCATION); // Standard error pipe HANDLE hStdErrWrite; if (!::CreatePipe(&pImpl_->hStdErrRead, &hStdErrWrite, NULL, 0)) return systemError(::GetLastError(), ERROR_LOCATION); CloseHandleOnExitScope closeStdErr(&hStdErrWrite, ERROR_LOCATION); if (!::SetHandleInformation(hStdErrWrite, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) ) return systemError(::GetLastError(), ERROR_LOCATION); // populate startup info STARTUPINFO si = { sizeof(STARTUPINFO) }; si.dwFlags |= STARTF_USESTDHANDLES; si.hStdOutput = hStdOutWrite; si.hStdError = options_.redirectStdErrToStdOut ? hStdOutWrite : hStdErrWrite; si.hStdInput = hStdInRead; HANDLE hStdOutWriteFile = INVALID_HANDLE_VALUE; if (!options_.stdOutFile.empty()) { error = openFile(options_.stdOutFile, true, &hStdOutWriteFile); if (error) return error; si.hStdOutput = hStdOutWriteFile; } CloseHandleOnExitScope closeStdOutFile(&hStdOutWriteFile, ERROR_LOCATION); HANDLE hStdErrWriteFile = INVALID_HANDLE_VALUE; if (!options_.stdErrFile.empty()) { error = openFile(options_.stdErrFile, true, &hStdErrWriteFile); if (error) return error; si.hStdOutput = hStdErrWriteFile; } CloseHandleOnExitScope closeStdErrFile(&hStdErrWriteFile, ERROR_LOCATION); // build command line std::vector<TCHAR> cmdLine; bool exeQuot = std::string::npos != exe_.find(' ') && std::string::npos == exe_.find('"'); if (exeQuot) cmdLine.push_back('"'); std::copy(exe_.begin(), exe_.end(), std::back_inserter(cmdLine)); if (exeQuot) cmdLine.push_back('"'); BOOST_FOREACH(std::string& arg, args_) { cmdLine.push_back(' '); // This is kind of gross. Ideally we would be more deterministic // than this. bool quot = std::string::npos != arg.find(' ') && std::string::npos == arg.find('"'); if (quot) cmdLine.push_back('"'); std::copy(arg.begin(), arg.end(), std::back_inserter(cmdLine)); if (quot) cmdLine.push_back('"'); }