X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=src%2Fceph%2Fsrc%2Fcommon%2FSubProcess.h;fp=src%2Fceph%2Fsrc%2Fcommon%2FSubProcess.h;h=ce9559c5eeca10dfec77eb15888237431d19a358;hb=812ff6ca9fcd3e629e49d4328905f33eee8ca3f5;hp=0000000000000000000000000000000000000000;hpb=15280273faafb77777eab341909a3f495cf248d9;p=stor4nfv.git diff --git a/src/ceph/src/common/SubProcess.h b/src/ceph/src/common/SubProcess.h new file mode 100644 index 0000000..ce9559c --- /dev/null +++ b/src/ceph/src/common/SubProcess.h @@ -0,0 +1,488 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph distributed storage system + * + * Copyright (C) 2015 Mirantis Inc + * + * Author: Mykola Golub + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + */ + +#ifndef SUB_PROCESS_H +#define SUB_PROCESS_H + +#include +#include +#include +#include +#include +#include +#include +#if defined(__FreeBSD__) +#include +#include +#endif + +/** + * SubProcess: + * A helper class to spawn a subprocess. + * + * Example: + * + * SubProcess cat("cat", SubProcess::PIPE, SubProcess::PIPE); + * if (cat.spawn() != 0) { + * std::cerr << "cat failed: " << cat.err() << std::endl; + * return false; + * } + * write_to_fd(cat.get_stdout(), "hello world!\n"); + * cat.close_stdout(); + * read_from_fd(cat.get_stdin(), buf); + * if (cat.join() != 0) { + * std::cerr << cat.err() << std::endl; + * return false; + * } + */ + +class SubProcess { +public: + enum std_fd_op{ + KEEP, + CLOSE, + PIPE + }; +public: + SubProcess(const char *cmd, + std_fd_op stdin_op = CLOSE, + std_fd_op stdout_op = CLOSE, + std_fd_op stderr_op = CLOSE); + virtual ~SubProcess(); + + void add_cmd_args(const char *arg, ...); + void add_cmd_arg(const char *arg); + + virtual int spawn(); // Returns 0 on success or -errno on failure. + virtual int join(); // Returns exit code (0 on success). + + bool is_spawned() const { return pid > 0; } + + int get_stdin() const; + int get_stdout() const; + int get_stderr() const; + + void close_stdin(); + void close_stdout(); + void close_stderr(); + + void kill(int signo = SIGTERM) const; + + const std::string err() const; + +protected: + bool is_child() const { return pid == 0; } + virtual void exec(); + +private: + void close(int &fd); + +protected: + std::string cmd; + std::vector cmd_args; + std_fd_op stdin_op; + std_fd_op stdout_op; + std_fd_op stderr_op; + int stdin_pipe_out_fd; + int stdout_pipe_in_fd; + int stderr_pipe_in_fd; + int pid; + std::ostringstream errstr; +}; + +class SubProcessTimed : public SubProcess { +public: + SubProcessTimed(const char *cmd, std_fd_op stdin_op = CLOSE, + std_fd_op stdout_op = CLOSE, std_fd_op stderr_op = CLOSE, + int timeout = 0, int sigkill = SIGKILL); + +protected: + void exec() override; + +private: + int timeout; + int sigkill; +}; + +inline SubProcess::SubProcess(const char *cmd_, std_fd_op stdin_op_, std_fd_op stdout_op_, std_fd_op stderr_op_) : + cmd(cmd_), + cmd_args(), + stdin_op(stdin_op_), + stdout_op(stdout_op_), + stderr_op(stderr_op_), + stdin_pipe_out_fd(-1), + stdout_pipe_in_fd(-1), + stderr_pipe_in_fd(-1), + pid(-1), + errstr() { +} + +inline SubProcess::~SubProcess() { + assert(!is_spawned()); + assert(stdin_pipe_out_fd == -1); + assert(stdout_pipe_in_fd == -1); + assert(stderr_pipe_in_fd == -1); +} + +inline void SubProcess::add_cmd_args(const char *arg, ...) { + assert(!is_spawned()); + + va_list ap; + va_start(ap, arg); + const char *p = arg; + do { + add_cmd_arg(p); + p = va_arg(ap, const char*); + } while (p != NULL); + va_end(ap); +} + +inline void SubProcess::add_cmd_arg(const char *arg) { + assert(!is_spawned()); + + cmd_args.push_back(arg); +} + +inline int SubProcess::get_stdin() const { + assert(is_spawned()); + assert(stdin_op == PIPE); + + return stdin_pipe_out_fd; +} + +inline int SubProcess::get_stdout() const { + assert(is_spawned()); + assert(stdout_op == PIPE); + + return stdout_pipe_in_fd; +} + +inline int SubProcess::get_stderr() const { + assert(is_spawned()); + assert(stderr_op == PIPE); + + return stderr_pipe_in_fd; +} + +inline void SubProcess::close(int &fd) { + if (fd == -1) + return; + + ::close(fd); + fd = -1; +} + +inline void SubProcess::close_stdin() { + assert(is_spawned()); + assert(stdin_op == PIPE); + + close(stdin_pipe_out_fd); +} + +inline void SubProcess::close_stdout() { + assert(is_spawned()); + assert(stdout_op == PIPE); + + close(stdout_pipe_in_fd); +} + +inline void SubProcess::close_stderr() { + assert(is_spawned()); + assert(stderr_op == PIPE); + + close(stderr_pipe_in_fd); +} + +inline void SubProcess::kill(int signo) const { + assert(is_spawned()); + + int ret = ::kill(pid, signo); + assert(ret == 0); +} + +inline const std::string SubProcess::err() const { + return errstr.str(); +} + +class fd_buf : public std::streambuf { + int fd; +public: + fd_buf (int fd) : fd(fd) + {} +protected: + int_type overflow (int_type c) override { + if (c == EOF) return EOF; + char buf = c; + if (write (fd, &buf, 1) != 1) { + return EOF; + } + return c; + } + std::streamsize xsputn (const char* s, std::streamsize count) override { + return write(fd, s, count); + } +}; + +inline int SubProcess::spawn() { + assert(!is_spawned()); + assert(stdin_pipe_out_fd == -1); + assert(stdout_pipe_in_fd == -1); + assert(stderr_pipe_in_fd == -1); + + enum { IN = 0, OUT = 1 }; + + int ipipe[2], opipe[2], epipe[2]; + + ipipe[0] = ipipe[1] = opipe[0] = opipe[1] = epipe[0] = epipe[1] = -1; + + int ret = 0; + + if ((stdin_op == PIPE && ::pipe(ipipe) == -1) || + (stdout_op == PIPE && ::pipe(opipe) == -1) || + (stderr_op == PIPE && ::pipe(epipe) == -1)) { + ret = -errno; + errstr << "pipe failed: " << cpp_strerror(errno); + goto fail; + } + + pid = fork(); + + if (pid > 0) { // Parent + stdin_pipe_out_fd = ipipe[OUT]; close(ipipe[IN ]); + stdout_pipe_in_fd = opipe[IN ]; close(opipe[OUT]); + stderr_pipe_in_fd = epipe[IN ]; close(epipe[OUT]); + return 0; + } + + if (pid == 0) { // Child + close(ipipe[OUT]); + close(opipe[IN ]); + close(epipe[IN ]); + + if (ipipe[IN] != -1 && ipipe[IN] != STDIN_FILENO) { + ::dup2(ipipe[IN], STDIN_FILENO); + close(ipipe[IN]); + } + if (opipe[OUT] != -1 && opipe[OUT] != STDOUT_FILENO) { + ::dup2(opipe[OUT], STDOUT_FILENO); + close(opipe[OUT]); + static fd_buf buf(STDOUT_FILENO); + std::cout.rdbuf(&buf); + } + if (epipe[OUT] != -1 && epipe[OUT] != STDERR_FILENO) { + ::dup2(epipe[OUT], STDERR_FILENO); + close(epipe[OUT]); + static fd_buf buf(STDERR_FILENO); + std::cerr.rdbuf(&buf); + } + + int maxfd = sysconf(_SC_OPEN_MAX); + if (maxfd == -1) + maxfd = 16384; + for (int fd = 0; fd <= maxfd; fd++) { + if (fd == STDIN_FILENO && stdin_op != CLOSE) + continue; + if (fd == STDOUT_FILENO && stdout_op != CLOSE) + continue; + if (fd == STDERR_FILENO && stderr_op != CLOSE) + continue; + ::close(fd); + } + + exec(); + ceph_abort(); // Never reached + } + + ret = -errno; + errstr << "fork failed: " << cpp_strerror(errno); + +fail: + close(ipipe[0]); + close(ipipe[1]); + close(opipe[0]); + close(opipe[1]); + close(epipe[0]); + close(epipe[1]); + + return ret; +} + +inline void SubProcess::exec() { + assert(is_child()); + + std::vector args; + args.push_back(cmd.c_str()); + for (std::vector::iterator i = cmd_args.begin(); + i != cmd_args.end(); + i++) { + args.push_back(i->c_str()); + } + args.push_back(NULL); + + int ret = execvp(cmd.c_str(), (char * const *)&args[0]); + assert(ret == -1); + + std::cerr << cmd << ": exec failed: " << cpp_strerror(errno) << "\n"; + _exit(EXIT_FAILURE); +} + +inline int SubProcess::join() { + assert(is_spawned()); + + close(stdin_pipe_out_fd); + close(stdout_pipe_in_fd); + close(stderr_pipe_in_fd); + + int status; + + while (waitpid(pid, &status, 0) == -1) + assert(errno == EINTR); + + pid = -1; + + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != EXIT_SUCCESS) + errstr << cmd << ": exit status: " << WEXITSTATUS(status); + return WEXITSTATUS(status); + } + if (WIFSIGNALED(status)) { + errstr << cmd << ": got signal: " << WTERMSIG(status); + return 128 + WTERMSIG(status); + } + errstr << cmd << ": waitpid: unknown status returned\n"; + return EXIT_FAILURE; +} + +inline SubProcessTimed::SubProcessTimed(const char *cmd, std_fd_op stdin_op, + std_fd_op stdout_op, std_fd_op stderr_op, + int timeout_, int sigkill_) : + SubProcess(cmd, stdin_op, stdout_op, stderr_op), + timeout(timeout_), + sigkill(sigkill_) { +} + +static bool timedout = false; // only used after fork +static void timeout_sighandler(int sig) { + timedout = true; +} +static void dummy_sighandler(int sig) {} + +inline void SubProcessTimed::exec() { + assert(is_child()); + + if (timeout <= 0) { + SubProcess::exec(); + ceph_abort(); // Never reached + } + + sigset_t mask, oldmask; + int pid; + + // Restore default action for SIGTERM in case the parent process decided + // to ignore it. + if (signal(SIGTERM, SIG_DFL) == SIG_ERR) { + std::cerr << cmd << ": signal failed: " << cpp_strerror(errno) << "\n"; + goto fail_exit; + } + // Because SIGCHLD is ignored by default, setup dummy handler for it, + // so we can mask it. + if (signal(SIGCHLD, dummy_sighandler) == SIG_ERR) { + std::cerr << cmd << ": signal failed: " << cpp_strerror(errno) << "\n"; + goto fail_exit; + } + // Setup timeout handler. + if (signal(SIGALRM, timeout_sighandler) == SIG_ERR) { + std::cerr << cmd << ": signal failed: " << cpp_strerror(errno) << "\n"; + goto fail_exit; + } + // Block interesting signals. + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + sigaddset(&mask, SIGCHLD); + sigaddset(&mask, SIGALRM); + if (sigprocmask(SIG_SETMASK, &mask, &oldmask) == -1) { + std::cerr << cmd << ": sigprocmask failed: " << cpp_strerror(errno) << "\n"; + goto fail_exit; + } + + pid = fork(); + + if (pid == -1) { + std::cerr << cmd << ": fork failed: " << cpp_strerror(errno) << "\n"; + goto fail_exit; + } + + if (pid == 0) { // Child + // Restore old sigmask. + if (sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1) { + std::cerr << cmd << ": sigprocmask failed: " << cpp_strerror(errno) << "\n"; + goto fail_exit; + } + (void)setpgid(0, 0); // Become process group leader. + SubProcess::exec(); + ceph_abort(); // Never reached + } + + // Parent + (void)alarm(timeout); + + for (;;) { + int signo; + if (sigwait(&mask, &signo) == -1) { + std::cerr << cmd << ": sigwait failed: " << cpp_strerror(errno) << "\n"; + goto fail_exit; + } + switch (signo) { + case SIGCHLD: + int status; + if (waitpid(pid, &status, WNOHANG) == -1) { + std::cerr << cmd << ": waitpid failed: " << cpp_strerror(errno) << "\n"; + goto fail_exit; + } + if (WIFEXITED(status)) + _exit(WEXITSTATUS(status)); + if (WIFSIGNALED(status)) + _exit(128 + WTERMSIG(status)); + std::cerr << cmd << ": unknown status returned\n"; + goto fail_exit; + case SIGINT: + case SIGTERM: + // Pass SIGINT and SIGTERM, which are usually used to terminate + // a process, to the child. + if (::kill(pid, signo) == -1) { + std::cerr << cmd << ": kill failed: " << cpp_strerror(errno) << "\n"; + goto fail_exit; + } + continue; + case SIGALRM: + std::cerr << cmd << ": timed out (" << timeout << " sec)\n"; + if (::killpg(pid, sigkill) == -1) { + std::cerr << cmd << ": kill failed: " << cpp_strerror(errno) << "\n"; + goto fail_exit; + } + continue; + default: + std::cerr << cmd << ": sigwait: invalid signal: " << signo << "\n"; + goto fail_exit; + } + } + +fail_exit: + _exit(EXIT_FAILURE); +} + +#endif