/* The software in this package is distributed under the GNU General Public License version 2 (with a special exception described below). A copy of GNU General Public License (GPL) is included in this distribution, in the file COPYING.GPL. As a special exception, if other files instantiate templates or use macros or inline functions from this file, or you compile this file and link it with other works to produce a work based on this file, this file does not by itself cause the resulting work to be covered by the GNU General Public License. However the source code for this file must still be made available in accordance with section (3) of the GNU General Public License. This exception does not invalidate any other reasons why a work based on this file might be covered by the GNU General Public License. */ /*************************************************************************** inpipestream.cpp - C++ streambuffer wrapper ------------------- begin : Thu Dec 27 2001 copyright : (C) 2001 by Intra2net AG ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include "exception.hxx" #include "stringfunc.hxx" #include "pipestream.hxx" /** @brief runs command and returns it's output as string * @param command the full command with all parameters * @param rescode struct containing the return code, if the program exited normally and so on * @param flags runtime control flags (stdio streams, environment, path lookup). * @returns the output (stdout) of the called program */ template std::string capture_exec(CmdT command, ExecResult &rescode, const int flags) { std::string output; bool exit_set = false; int exit_status_waitpid; // set the results to false until we are sure we have proper values rescode.normal_exit = false; rescode.terminated_by_signal = false; try { { inpipestream ips(command, flags); ips.store_exit_status(&exit_set, &exit_status_waitpid); char buffer[2048]; while (ips.good()) { ips.read(buffer, sizeof(buffer)); output.append(buffer, ips.gcount()); } } // exit_status_waitpid only valid after destruction of the inpipestream if (exit_set) { rescode.normal_exit = WIFEXITED(exit_status_waitpid); if (rescode.normal_exit) rescode.return_code = WEXITSTATUS(exit_status_waitpid); rescode.terminated_by_signal = WIFSIGNALED(exit_status_waitpid); if (rescode.terminated_by_signal) rescode.signal = WTERMSIG(exit_status_waitpid); } } catch (pipestream_error &e) { rescode.error_message = e.what(); } return output; } /** @brief Instantiation of \c capture_exec for STL string arguments. * Caveat emptor: this will cause the backing stream to use \c * popen(3). To avoid shelling out, please refer to one of the * variants that allow passing an argument list. * * @param command String specifying the shell expression to be executed. * @param res (Out parameter) Store information about the termination * state in this struct. * * @returns Result of \c stdout. Note that due to the use of \c * popen, the correct way to collect stderr output as * well is to use shell redirection inside the expression * passed. */ std::string capture_exec (const std::string &command, ExecResult &res) { return capture_exec(command, res, capture_flag::dflt); } /** @brief Instantiation of \c capture_exec for argument lists. The * pipestream used to run the command will not shell out. * One of \c out or \c err must be set. * * @param command List of \c char* specifying the \c argv array of the * command to run. Note that the binary to executed is * assumed to be present at index 0 and that the input * is properly \c NULL terminated. * @param res (Out parameter) Store information about the termination * state in this struct. * @param flags Runtime control flags (stdio streams, environment, path * lookup). * * @returns Captured output, combined into one string. */ std::string capture_exec (const char *const *command, ExecResult &res, const int flags) { return capture_exec(command, res, flags); } /** @brief Instantiation of \c capture_exec for argument lists. The * pipestream used to run the command will not shell out. * One of \c out or \c err must be set. * * @param command String vector specifying the \c argv array of the * command to run. Note that the binary to executed is * assumed to be present at index 0. * @param res (Out parameter) Store information about the termination * state in this struct. * @param flags Runtime control flags (stdio streams, environment, path * lookup). * * @returns Captured output, combined into one string. */ std::string capture_exec (const std::vector &command, ExecResult &res, const int flags) { return capture_exec &> (command, res, flags); } #define PIPE_CTOR_FAIL(where) \ do { \ throw EXCEPTION (pipestream_error, \ std::string (where) + ": error " \ + I2n::to_string (errno) \ + " (" + std::string (strerror (errno)) + ")"); \ } while (0) /** @brief Convert a string vector to a refcounted \c char** * that is \c NULL terminated for use with e. g. \c execve(2). * * @param command List of arguments including the binary at index 0. * * @returns A \c boost::shared_array of pointers to the * arguments plus a trailing \c NULL. Note that * while the array itself is refcounted, the * pointees are assumed owned by the caller and * *not copyied*. I. e. they lose validity if the * original strings are freed. */ static boost::shared_array mk_argv (const std::vector &command) { char **ret = NULL; try { ret = new char *[command.size () * sizeof (ret[0]) + 1]; } catch (std::bad_alloc &) { return boost::shared_array (); } size_t cur = 0; BOOST_FOREACH(const std::string &arg, command) { /* * Casting away constness is safe since the data is always * kept alive until after exec(). */ ret [cur++] = const_cast (arg.c_str ()); } ret [cur] = NULL; return boost::shared_array (ret); } /** @brief Helper for redirecting a file descriptor to \c /dev/null. * This will only acquire an fd the first time it is called * or if it is called after unsuccessfully attempting to * acquire one. * * @param fd The open file descriptor to operate on. * @param save_errno Out parameter: stores errno here after a syscall failure. * * @returns \c true on success, \c false otherwise (the call to * either \c open(2) or \c dup2(2) failed), with errno * communicated through saved_errno. */ static bool redirect_devnull (const int fd, int &save_errno) { static int nullfd = -1; errno = 0; if (nullfd == -1 && (nullfd = open ("/dev/null", O_RDWR)) == -1) { save_errno = errno; return false; } errno = 0; if (dup2 (nullfd, fd) == -1) { save_errno = errno; return false; } return true; } /** @brief Helper aggregating common code for the shell-free ctors. * * @param argv Argument list prepared for \c execve(2). * @param flags Control the runtime behavior wrt. stdio streams, \c * *envp, and path search. One of \c collect_out or * \c collect_err is mandatory. All other flags are * optional. Pipebuf creation with fail with \c EINVAL * if that constraint is violated. * * @returns A \c FILE* handle for streaming if successful, \c NULL * otherwise. * * Error handling strategy: * * - receive all errors from child as ints through a cloexec pipe; * - in the child, write error conditions always to pipe first, * then try to emit a more verbose log message; * - in the parent, throw on error indicating the child errno. * * Note that the error-pipe approach is robust due to guarantees by both * standard (POSIX) and implementation (Linux) of pipes: The read(2) from * the error channel will block until the pipe is either closed or written to; * hence no need to check for EAGAIN. Those writes are guaranteed to be atomic * because sizeof(errno) is less than PIPE_BUF; hence we can disregard EINTR. A * pipe whose write end (i.e. in the child) has been closed (by the kernel * because execve(2) was successful) will always indicate EOF by returning * zero, hence we know precisely whether everything went well or not. Cf. * pipe(7), sections “I/O on pipes and FIFOs” and “PIPE_BUF”, as well as * Kerrisk (2010), section 44.10, p. 917f. */ std::pair inpipebuf::init_without_shell (const char *const *argv, const int flags) const { FILE *pipeobj = NULL; int pipefd [2]; /* for reading output from the child */ int errfd [2]; /* for determining a successful exec() */ sigset_t oldmask, newmask; char *const *envp = flags & capture_flag::env_passthru ? environ : NULL; if (!(flags & capture_flag::collect_any)) { errno = EINVAL; PIPE_CTOR_FAIL("ctor"); } /* * The error pipe must be openend with *O_CLOEXEC* set. We also open * the data pipe with close-on-exec and remove that bit only in the child. * The rationale is preventing the read fd from passed on if the parent * later re-forks another child: we intend it to be read from this (master) * process alone. */ errno = 0; if ( ::pipe2 (pipefd, O_CLOEXEC) == -1 || ::pipe2 (errfd , O_CLOEXEC) == -1) { PIPE_CTOR_FAIL("pipe2"); } sigfillset (&newmask); sigprocmask (SIG_SETMASK, &newmask, &oldmask); errno = 0; pid_t childpid = fork (); switch (childpid) { case -1: { sigprocmask (SIG_SETMASK, &oldmask, NULL); PIPE_CTOR_FAIL("fork"); break; } case 0: { /* * Close read ends of error and data channels: the child is assumed * to write exclusively. */ close (pipefd [0]); close (errfd [0]); /* * Remove cloexec bit from the write end of the pipe (this is the * only flag with F_SETFD). */ fcntl (pipefd [1], F_SETFD, 0); /* * Prevent the child from receiving more privileges than the * parent. This concerns mainly suid binaries. */ errno = 0; if ( flags & capture_flag::no_new_privs && prctl (PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) { (void)write (errfd [1], (char *)&errno, sizeof(errno)); exit (EXIT_FAILURE); } int save_errno = 0; /* * Assign /dev/null if asked to close one of the streams, else * dup() it onto the pipe. */ if (!(flags & capture_flag::collect_out)) { if (!redirect_devnull (STDOUT_FILENO, save_errno)) { (void)write (errfd [1], (char *)&save_errno, sizeof(save_errno)); exit (EXIT_FAILURE); } } else if (dup2 (pipefd[1], STDOUT_FILENO) == -1) { (void)write (errfd [1], (char *)&save_errno, sizeof(save_errno)); exit (EXIT_FAILURE); } if (!(flags & capture_flag::collect_err)) { if (!redirect_devnull (STDERR_FILENO, save_errno)) { (void)write (errfd [1], (char *)&save_errno, sizeof(save_errno)); exit (EXIT_FAILURE); } } else if (dup2 (pipefd[1], STDERR_FILENO) == -1) { (void)write (errfd [1], (char *)&save_errno, sizeof(save_errno)); exit (EXIT_FAILURE); } /* * Close the write end of the pipe now that we have dup()’ed it * onto the stdio fds. The parent will now receive EOF on the pipe * when these fds are both closed. */ close (pipefd [1]); /* * Stop blocking signals so the child starts out with a sane * environment. */ sigprocmask (SIG_SETMASK, &oldmask, NULL); errno = 0; if (flags & capture_flag::search_path) { execvpe (argv [0], const_cast (argv), envp); } else { execve (argv [0], const_cast (argv), envp); } /* * At this point, the call to execv[p]e() failed. Thus the error * pipe is still opened and we forward the errno through it. */ (void)write (errfd [1], (char *)&errno, sizeof(errno)); exit (EXIT_FAILURE); break; } default: { break; } } /* * The parent is assumed to only consume data from either pipe, never * write. */ close (pipefd [1]); close (errfd [1]); /* * Check whether the child exec()’ed by reading from the error pipe. * The call to read(2) will block, uninterruptible due to signals being * blocked. If all went well, the read(2) will return zero bytes and we can * ditch the error channel. * * Otherwise either the read(2) failed or we actually received something * through the error pipe. Both cases are treated as errors and cause an * exit from the ctor. */ char buf [sizeof (errno)]; int ret; memset (buf, 0, sizeof (buf)); errno = 0; if ((ret = read (errfd [0], buf, sizeof (buf))) != 0) { close (pipefd [0]); close (errfd [0]); sigprocmask (SIG_SETMASK, &oldmask, NULL); if (ret == - 1) { /* read(2) failed */ PIPE_CTOR_FAIL("read"); } else { /* * We received data on the error channel indicating the child * process never successfully exec()’ed. We grab the error code * from the buffer and bail. */ errno = *((int *)&buf[0]); PIPE_CTOR_FAIL("child failed to exec()"); } } /* * read(2) yielded zero bytes; it’s safe to use the pipe so close our end * and continue. */ close (errfd [0]); sigprocmask (SIG_SETMASK, &oldmask, NULL); errno = 0; if ((pipeobj = fdopen (pipefd [0], "r")) == NULL) { close (pipefd [0]); PIPE_CTOR_FAIL("fdopen"); } return std::make_pair (childpid, pipeobj); } inpipebuf::inpipebuf(const char *const *command, const int flags) : pipe (NULL) /* brr: shadowing global ident */ , pid (-1) , status_set (NULL) , exit_status (NULL) { if (command == NULL || command [0] == NULL) { PIPE_CTOR_FAIL("command"); } std::pair tmp = this->init_without_shell (command, flags); this->pid = tmp.first; /* no std::tie :/ */ this->pipe = tmp.second; setg (&buffer, &buffer, &buffer); } inpipebuf::inpipebuf(const std::vector &command, const int flags) : pipe (NULL) /* brr: shadowing global ident */ , pid (-1) , status_set (NULL) , exit_status (NULL) { if (command.empty ()) { PIPE_CTOR_FAIL("command"); } const boost::shared_array argv = mk_argv (command); if (!argv) { PIPE_CTOR_FAIL("malloc"); } std::pair tmp = this->init_without_shell (argv.get (), flags); this->pid = tmp.first; this->pipe = tmp.second; setg (&buffer, &buffer, &buffer); } inpipebuf::inpipebuf(const std::string& command, const int _ignored_flags) : pid (-1) , status_set (NULL) , exit_status (NULL) { pipe = popen (command.c_str(), "r"); if (pipe == NULL) throw EXCEPTION (pipestream_error, "can't open program or permission denied"); // force underflow setg (&buffer, &buffer, &buffer); } inpipebuf::~inpipebuf() { if (pipe != NULL) { int status; if (this->pid == -1) { errno = 0; status = pclose (pipe); if (status != -1) { if (exit_status != NULL) { *exit_status = status; if (status_set != NULL) { *status_set = true; } } } } else { errno = 0; status = fclose (pipe); if (status != EOF) { if (exit_status != NULL) { *exit_status = status; /* might be overwritten below */ if (status_set != NULL) { *status_set = true; } } } errno = 0; while (waitpid (this->pid, &status, 0) == -1) { if (errno != EINTR) { status = -1; break; } } if (status != 0 && exit_status != NULL) { *exit_status = status; /* might overwrite pipe status above */ if (status_set != NULL) { *status_set = true; } } } pipe = NULL; } } /** note: exit status only available after destruction */ void inpipebuf::store_exit_status(bool *_status_set, int *_exit_status) { status_set = _status_set; exit_status = _exit_status; } inpipebuf::int_type inpipebuf::underflow() { if (gptr() < egptr()) return traits_type::to_int_type(*gptr()); buffer = fgetc (pipe); if (feof (pipe)) { // ERROR or EOF return EOF; } setg (&buffer, &buffer, &buffer+sizeof(char)); return traits_type::to_int_type(*gptr()); } outpipebuf::outpipebuf(const std::string& command) { status_set = NULL; exit_status = NULL; pipe = popen (command.c_str(), "w"); if (pipe == NULL) throw EXCEPTION (pipestream_error, "can't open program or permission denied"); } outpipebuf::~outpipebuf() { if (pipe != NULL) { int pclose_exit = pclose (pipe); if (exit_status && pclose_exit != -1) { if (status_set) *status_set = true; *exit_status = pclose_exit; } pipe = NULL; } } /** note: exit status only available after destruction */ void outpipebuf::store_exit_status(bool *_status_set, int *_exit_status) { status_set = _status_set; exit_status = _exit_status; } outpipebuf::int_type outpipebuf::overflow(int_type c) { if (c != EOF) { if (fputc(c,pipe)==EOF) return EOF; } return c; } std::streamsize outpipebuf::xsputn(const char* s, std::streamsize num) { return fwrite(s,num,1,pipe); }