| #include "utils.h" |
| |
| #include <errno.h> |
| #include <signal.h> |
| #include <stdarg.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/time.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| |
| namespace libexperiments_utils { |
| |
| void log(const char* cstr, ...) { |
| va_list va; |
| va_start(va, cstr); |
| vprintf(cstr, va); |
| va_end(va); |
| printf("\n"); |
| fflush(stdout); |
| } |
| |
| void log_perror(int err, const char* cstr, ...) { |
| va_list va; |
| va_start(va, cstr); |
| vprintf(cstr, va); |
| va_end(va); |
| char strerrbuf[1024] = {'\0'}; |
| printf("'%s'[%d]\n", strerror_r(err, strerrbuf, sizeof(strerrbuf)), err); |
| fflush(stdout); |
| } |
| |
| uint64_t us_elapse(uint64_t start_time_us) { |
| struct timespec tv; |
| clock_gettime(CLOCK_MONOTONIC, &tv); |
| return tv.tv_sec * kUsecsPerSec + tv.tv_nsec / kNsecsPerUsec - start_time_us; |
| } |
| |
| void us_sleep(uint64_t usecs) { |
| uint64_t nsecs = kNsecsPerUsec * usecs; |
| struct timespec tv; |
| // tv_nsec field must be [0..kNsecsPerSec-1] |
| tv.tv_sec = nsecs / kNsecsPerSec; |
| tv.tv_nsec = nsecs % kNsecsPerSec; |
| nanosleep(&tv, NULL); |
| } |
| |
| // Maximum output (stdout+stderr) accepted by run_cmd() |
| const int kMaxRunCmdOutput = 4 * 1024 * 1024; |
| |
| static int nice_snprintf(char *str, size_t size, const char *format, ...) { |
| va_list ap; |
| int bi; |
| va_start(ap, format); |
| // http://stackoverflow.com/a/100991 |
| bi = vsnprintf(str, size, format, ap); |
| va_end(ap); |
| if (bi > size) { |
| // From printf(3): |
| // "snprintf() [returns] the number of characters (not including the |
| // trailing '\0') which would have been written to the final string |
| // if enough space had been available" [printf(3)] |
| bi = size; |
| } |
| return bi; |
| } |
| |
| /* snprintf's a run_cmd command */ |
| int snprintf_cmd(char *buf, int bufsize, const std::vector<std::string> &cmd) { |
| int bi = 0; |
| |
| // ensure we always return something valid |
| buf[0] = '\0'; |
| for (const auto &item : cmd) { |
| bool blanks = (item.find_first_of(" \n\r\t") != std::string::npos); |
| if (blanks) |
| bi += nice_snprintf(buf+bi, bufsize-bi, "\""); |
| for (int i = 0; i < item.length() && bi < bufsize; ++i) { |
| if (isprint(item.at(i))) |
| bi += nice_snprintf(buf+bi, bufsize-bi, "%c", item.at(i)); |
| else if (item.at(i) == '\n') |
| bi += nice_snprintf(buf+bi, bufsize-bi, "\\n"); |
| else |
| bi += nice_snprintf(buf+bi, bufsize-bi, "\\x%02x", item.at(i)); |
| } |
| if (blanks) |
| bi += nice_snprintf(buf+bi, bufsize-bi, "\""); |
| bi += nice_snprintf(buf+bi, bufsize-bi, " "); |
| } |
| return bi; |
| } |
| |
| int run_cmd(const std::vector<std::string> &cmd, const std::string &in, |
| int *status, |
| std::ostream *out, |
| std::ostream *err, |
| int64_t timeout_usec) { |
| if (cmd.empty() || cmd[0].empty()) { |
| *status = -1; |
| return -1; |
| } |
| |
| int pipe_in[2]; |
| int pipe_out[2]; |
| int pipe_err[2]; |
| int pid; |
| |
| // init the 3 pipes |
| int ret; |
| for (auto the_pipe : { pipe_in, pipe_out, pipe_err }) { |
| if ((ret = pipe(the_pipe)) < 0) { |
| log_perror(errno, "run_cmd:Error-pipe failed-"); |
| return -1; |
| } |
| } |
| |
| char cmd_buf[1024]; |
| snprintf_cmd(cmd_buf, sizeof(cmd_buf), cmd); |
| log("run_cmd:running command: %s", cmd_buf); |
| |
| pid = fork(); |
| if (pid == 0) { |
| // child: set stdin/stdout/stderr |
| dup2(pipe_in[0], STDIN_FILENO); |
| dup2(pipe_out[1], STDOUT_FILENO); |
| dup2(pipe_err[1], STDERR_FILENO); |
| |
| // close unused pipe ends |
| close(pipe_in[1]); |
| close(pipe_out[0]); |
| close(pipe_err[0]); |
| close(pipe_in[0]); |
| close(pipe_out[1]); |
| close(pipe_err[1]); |
| |
| // convert strings to "const char *" and "char * []" |
| const char *file = cmd[0].c_str(); |
| char *argv[cmd.size() + 1]; |
| for (int i = 0; i < cmd.size(); ++i) |
| argv[i] = const_cast<char *>(cmd[i].c_str()); |
| argv[cmd.size()] = NULL; |
| // run command |
| execvp(file, argv); |
| // exec() functions return only if an error has occurred |
| _exit(errno); |
| } |
| |
| // parent: close unused pipe ends |
| close(pipe_in[0]); |
| close(pipe_out[1]); |
| close(pipe_err[1]); |
| // process stdin |
| if (!in.empty()) { |
| if ((ret = write(pipe_in[1], in.c_str(), in.length())) < in.length()) { |
| log_perror(errno, "run_cmd:Error-write() failed-"); |
| // kill the child |
| kill(pid, SIGKILL); |
| wait(NULL); |
| return -4; |
| } |
| } |
| close(pipe_in[1]); |
| |
| // start reading stdout/stderr |
| struct FancyPipe { |
| int fd; |
| std::ostream *stream_ptr; |
| } fancypipes[] = { |
| { pipe_out[0], out }, |
| { pipe_err[0], err }, |
| }; |
| fd_set fdread; |
| char buf[1024]; |
| |
| int total_output = 0; |
| int retcode = 0; |
| while (fancypipes[0].fd >= 0 || fancypipes[1].fd >= 0) { |
| if (total_output > kMaxRunCmdOutput) { |
| log("run_cmd:Error-command output is too large (%i bytes > %i)", |
| total_output, kMaxRunCmdOutput); |
| // kill the child |
| kill(pid, SIGKILL); |
| retcode = -3; |
| break; |
| } |
| FD_ZERO(&fdread); |
| int max_fd = -1; |
| struct timeval tv, *timeout = NULL; |
| if (timeout_usec >= 0) { |
| tv.tv_sec = timeout_usec / 1000000; |
| tv.tv_usec = (timeout_usec % 1000000) * 1000000; |
| timeout = &tv; |
| } |
| for (const auto &fancypipe : fancypipes) { |
| if (fancypipe.fd >= 0) { |
| FD_SET(fancypipe.fd, &fdread); |
| max_fd = MAX(max_fd, fancypipe.fd); |
| } |
| } |
| int select_ret = select(max_fd + 1, &fdread, NULL, NULL, timeout); |
| if (select_ret == 0) { |
| // timeout |
| log("run_cmd:Error-command timed out"); |
| // kill the child |
| kill(pid, SIGKILL); |
| retcode = -2; |
| break; |
| } else if (select_ret == -1) { |
| if (errno == EINTR) { |
| // interrupted by signal |
| retcode = -1; |
| break; |
| } |
| log_perror(errno, "run_cmd:Error-pipe select failed-"); |
| retcode = -1; |
| break; |
| } |
| for (auto &fancypipe : fancypipes) { |
| if (fancypipe.fd >= 0 && FD_ISSET(fancypipe.fd, &fdread)) { |
| ssize_t len; |
| len = read(fancypipe.fd, buf, sizeof(buf)); |
| if (len <= 0) { |
| close(fancypipe.fd); |
| fancypipe.fd = -1; |
| if (len < 0) |
| retcode = -1; |
| continue; |
| } |
| total_output += len; |
| if (fancypipe.stream_ptr) |
| fancypipe.stream_ptr->write(buf, len); |
| } |
| } |
| } |
| |
| *status = 0; |
| wait(status); |
| // interpret child exit status |
| if (WIFEXITED(*status)) |
| *status = WEXITSTATUS(*status); |
| return retcode; |
| } |
| |
| // |
| // String printf functions, ported from stringprintf.cc/h |
| // |
| |
| void StringAppendV(std::string* dst, const char* format, va_list ap) { |
| // First try with a small fixed size buffer |
| static const int kSpaceLength = 1024; |
| char space[kSpaceLength]; |
| |
| // It's possible for methods that use a va_list to invalidate |
| // the data in it upon use. The fix is to make a copy |
| // of the structure before using it and use that copy instead. |
| va_list backup_ap; |
| va_copy(backup_ap, ap); |
| int result = vsnprintf(space, kSpaceLength, format, backup_ap); |
| va_end(backup_ap); |
| |
| if (result < kSpaceLength) { |
| if (result >= 0) { |
| // Normal case -- everything fit. |
| dst->append(space, result); |
| return; |
| } |
| if (result < 0) { |
| // Just an error. |
| return; |
| } |
| } |
| |
| // Increase the buffer size to the size requested by vsnprintf, |
| // plus one for the closing \0. |
| int length = result+1; |
| char* buf = new char[length]; |
| |
| // Restore the va_list before we use it again |
| va_copy(backup_ap, ap); |
| result = vsnprintf(buf, length, format, backup_ap); |
| va_end(backup_ap); |
| |
| if (result >= 0 && result < length) { |
| // It fit |
| dst->append(buf, result); |
| } |
| delete[] buf; |
| } |
| |
| std::string StringPrintf(const char* format, ...) { |
| va_list ap; |
| va_start(ap, format); |
| std::string result; |
| StringAppendV(&result, format, ap); |
| va_end(ap); |
| return result; |
| } |
| |
| } // namespace libexperiments_utils |