#include #include #include #include #include #include #include #include "process.h" #include "error.h" Process::Process(const string_view execname) : execname(execname) {} void Process::redirectStderr(const string_view fname) { stderrRedirect = fname; } void Process::run() { int stderrfd = -1; if (stderrRedirect) { stderrfd = open(stderrRedirect->data(), O_WRONLY|O_CREAT|O_TRUNC, 0644); if (stderrfd < 0) { perror("open"); cout << endl << "ERROR: Cannot open player log file '" << *stderrRedirect << "'" << endl; throw StopCompetitionError(); } } int pipefds[2]; if (pipe(pipefds) < 0) { perror("pipe"); exit(1); } infd = pipefds[1]; int child_in = pipefds[0]; if (pipe(pipefds) < 0) { perror("pipe"); exit(1); } outfd = pipefds[0]; int child_out = pipefds[1]; pid = fork(); if (pid < 0) { perror("fork"); exit(1); } if (pid == 0) { if (stderrRedirect) dup2(stderrfd, STDERR_FILENO); dup2(child_in, STDIN_FILENO); dup2(child_out, STDOUT_FILENO); close(infd); close(outfd); close(child_in); close(child_out); execl(execname.data(), execname.data(), NULL); cerr << endl << "ERROR: Error executing player file '" << execname << "'" << endl; exit(255); } if (stderrfd >= 0) close(stderrfd); close(child_in); close(child_out); } int Process::waitPoll() { if (pid == -1) return exitStatus; while (true) { int status; int ret = waitpid(pid, &status, WNOHANG); if (ret < 0) { if (errno == ECHILD) return exitStatus; perror("waitpid"); return -1; } if (ret == 0) { // Nothing changed yet return -1; } if (WIFEXITED(status)) { cleanup(); exitStatus = WEXITSTATUS(status); return exitStatus; } if (WIFSIGNALED(status)) { cleanup(); exitStatus = 1000 + WTERMSIG(status); return exitStatus; } } } int Process::wait() { if (pid == -1) return exitStatus; while (true) { int status; if (waitpid(pid, &status, 0) < 0) { if (errno == EINTR) continue; if (errno == ECHILD) return exitStatus; perror("waitpid"); return -1; } if (WIFEXITED(status)) { cleanup(); exitStatus = WEXITSTATUS(status); return exitStatus; } if (WIFSIGNALED(status)) { cleanup(); exitStatus = 1000 + WTERMSIG(status); return exitStatus; } } } void Process::stop() { if (pid != -1) kill(pid, SIGSTOP); } void Process::unStop() { if (pid != -1) kill(pid, SIGCONT); } bool Process::writeLine(const string_view line, bool allowBrokenPipe) { if (pid == -1) return false; string str; str.reserve(line.size() + 1); str += line; str += '\n'; size_t cursor = 0; while (cursor < str.size()) { ssize_t nw = write(infd, str.data() + cursor, str.size() - cursor); if (nw < 0) { if (errno == EINTR) continue; if (allowBrokenPipe && errno == EPIPE) continue; perror("write"); return false; } cursor += nw; } return true; } optional Process::readLine() { if (pid == -1) { cout << "-- readLine on pid==-1 --" << endl; return nullopt; } size_t idx = readBuf.find('\n'); if (idx != string::npos) { string res = readBuf.substr(0, idx); readBuf = readBuf.substr(idx + 1); return res; } while (true) { string s(1024, '\0'); ssize_t nr = read(outfd, &s[0], s.size()); if (nr < 0) { if (errno == EINTR) continue; perror("read"); return nullopt; } if (nr == 0) { // EOF cout << "-- eof in readLine --" << endl; return nullopt; } s.resize(nr); idx = s.find('\n'); if (idx != string::npos) { string res = readBuf + s.substr(0, idx); readBuf = s.substr(idx + 1); return res; } readBuf += s; } } optional Process::readLineTimeout(size_t millis) { if (pid == -1) { cout << "-- readLineTimeout on pid==-1 --" << endl; return nullopt; } size_t idx = readBuf.find('\n'); if (idx != string::npos) { string res = readBuf.substr(0, idx); readBuf = readBuf.substr(idx + 1); return res; } struct timeval start; gettimeofday(&start, nullptr); struct timeval totaltime; totaltime.tv_sec = millis / 1000; totaltime.tv_usec = millis % 1000 * 1000; struct timeval left = totaltime; while (true) { string s(1024, '\0'); ssize_t nr; fd_set inset; FD_ZERO(&inset); FD_SET(outfd, &inset); int ret = select(outfd + 1, &inset, nullptr, nullptr, &left); if (ret < 0) { if (errno == EINTR) goto continue_adjust_time_left; perror("select"); return nullopt; } if (!FD_ISSET(outfd, &inset)) goto continue_adjust_time_left; nr = read(outfd, &s[0], s.size()); if (nr < 0) { if (errno == EINTR) goto continue_adjust_time_left; perror("read"); return nullopt; } if (nr == 0) { // EOF cout << "-- eof in readLine --" << endl; return nullopt; } s.resize(nr); idx = s.find('\n'); if (idx != string::npos) { string res = readBuf + s.substr(0, idx); readBuf = s.substr(idx + 1); return res; } readBuf += s; continue_adjust_time_left: struct timeval now, delta; gettimeofday(&now, nullptr); timersub(&now, &start, &delta); timersub(&totaltime, &delta, &left); struct timeval zero; timerclear(&zero); // Not greater, so less-equal; c.f. manpage if (!timercmp(&left, &zero, >)) { return nullopt; // Exceeded timeout } } } int Process::terminate() { // TODO: send only SIGTERM, then wait a little while, then send // SIGKILL if necessary if (pid != -1) { int ret = waitPoll(); if (ret == -1) { kill(pid, SIGKILL); // force kill ret = wait(); } cleanup(); return ret; } return exitStatus; } pid_t Process::getPid() const { return pid; } void Process::cleanup() { pid = -1; close(infd); close(outfd); }