// // Boost.Process // ~~~~~~~~~~~~~ // // Copyright (c) 2006, 2007 Julio M. Merino Vidal // Copyright (c) 2008, 2009 Boris Schaeling // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // /** * \file boost/process/detail/posix_ops.hpp * * Provides some convenience functions to start processes under POSIX * operating systems. */ #ifndef BOOST_PROCESS_DETAIL_POSIX_OPS_HPP #define BOOST_PROCESS_DETAIL_POSIX_OPS_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace process { namespace detail { /** * Converts the command line to an array of C strings. * * Converts the command line's list of arguments to the format expected * by the \a argv parameter in the POSIX execve() system call. * * This operation is only available on POSIX systems. * * \return The first argument of the pair is an integer that indicates * how many strings are stored in the second argument. The * second argument is a NULL-terminated, dynamically allocated * array of dynamically allocated strings holding the arguments * to the executable. The caller is responsible of freeing them. */ template inline std::pair collection_to_posix_argv(const Arguments &args) { std::size_t nargs = args.size(); BOOST_ASSERT(nargs > 0); char **argv = new char*[nargs + 1]; typename Arguments::size_type i = 0; for (typename Arguments::const_iterator it = args.begin(); it != args.end(); ++it) { argv[i] = new char[it->size() + 1]; std::strncpy(argv[i], it->c_str(), it->size() + 1); ++i; } argv[nargs] = 0; return std::pair(nargs, argv); } /** * Converts an environment to a char** table as used by execve(). * * Converts the environment's contents to the format used by the * execve() system call. The returned char** array is allocated * in dynamic memory; the caller must free it when not used any * more. Each entry is also allocated in dynamic memory and is a * NULL-terminated string of the form var=value; these must also be * released by the caller. * * \return A dynamically allocated char** array that represents * the environment's content. Each array entry is a * NULL-terminated string of the form var=value. */ inline char **environment_to_envp(const environment &env) { char **envp = new char*[env.size() + 1]; environment::size_type i = 0; for (environment::const_iterator it = env.begin(); it != env.end(); ++it) { std::string s = it->first + "=" + it->second; envp[i] = new char[s.size() + 1]; std::strncpy(envp[i], s.c_str(), s.size() + 1); ++i; } envp[i] = 0; return envp; } /** * Holds a mapping between native file descriptors and their corresponding * pipes to set up communication between the parent and the %child process. */ typedef std::map info_map; /** * Helper class to configure a POSIX %child. * * This helper class is used to hold all the attributes that configure a * new POSIX %child process and to centralize all the actions needed to * make them effective. * * All its fields are public for simplicity. It is only intended for * internal use and it is heavily coupled with the Context * implementations. */ struct posix_setup { /** * The work directory. * * This string specifies the directory in which the %child process * starts execution. It cannot be empty. */ std::string work_directory; /** * The chroot directory, if any. * * Specifies the directory in which the %child process is chrooted * before execution. Empty if this feature is not desired. */ std::string chroot; /** * The user credentials. * * UID that specifies the user credentials to use to run the %child * process. Defaults to the current UID. */ uid_t uid; /** * The effective user credentials. * * EUID that specifies the effective user credentials to use to run * the %child process. Defaults to the current EUID. */ uid_t euid; /** * The group credentials. * * GID that specifies the group credentials to use to run the %child * process. Defaults to the current GID. */ gid_t gid; /** * The effective group credentials. * * EGID that specifies the effective group credentials to use to run * the %child process. Defaults to the current EGID. */ gid_t egid; /** * Creates a new properties set. * * Creates a new object that has sensible default values for all the * properties. */ posix_setup() : uid(::getuid()), euid(::geteuid()), gid(::getgid()), egid(::getegid()) { } /** * Sets up the execution environment. * * Modifies the current execution environment (that of the %child) so * that the properties become effective. * * \throw boost::system::system_error If any error ocurred during * environment configuration. The child process should abort * execution if this happens because its start conditions * cannot be met. */ void operator()() const { if (!chroot.empty() && ::chroot(chroot.c_str()) == -1) boost::throw_exception(boost::system::system_error(boost::system::error_code(errno, boost::system::get_system_category()), "boost::process::detail::posix_setup: chroot(2) failed")); if (gid != ::getgid() && ::setgid(gid) == -1) boost::throw_exception(boost::system::system_error(boost::system::error_code(errno, boost::system::get_system_category()), "boost::process::detail::posix_setup: setgid(2) failed")); if (egid != ::getegid() && ::setegid(egid) == -1) boost::throw_exception(boost::system::system_error(boost::system::error_code(errno, boost::system::get_system_category()), "boost::process::detail::posix_setup: setegid(2) failed")); if (uid != ::getuid() && ::setuid(uid) == -1) boost::throw_exception(boost::system::system_error(boost::system::error_code(errno, boost::system::get_system_category()), "boost::process::detail::posix_setup: setuid(2) failed")); if (euid != ::geteuid() && ::seteuid(euid) == -1) boost::throw_exception(boost::system::system_error(boost::system::error_code(errno, boost::system::get_system_category()), "boost::process::detail::posix_setup: seteuid(2) failed")); BOOST_ASSERT(!work_directory.empty()); if (::chdir(work_directory.c_str()) == -1) boost::throw_exception(boost::system::system_error(boost::system::error_code(errno, boost::system::get_system_category()), "boost::process::detail::posix_setup: chdir(2) failed")); } }; /** * Configures child process' input streams. * * Sets up the current process' input streams to behave according to the * information in the \a info map. \a closeflags is modified to reflect * those descriptors that should not be closed because they where modified * by the function. * * Modifies the current execution environment, so this should only be run * on the child process after the fork(2) has happened. * * \throw boost::system::system_error If any error occurs during the * configuration. */ inline void setup_input(info_map &info, bool *closeflags, int maxdescs) { for (info_map::iterator it = info.begin(); it != info.end(); ++it) { int d = it->first; stream_info &si = it->second; BOOST_ASSERT(d < maxdescs); closeflags[d] = false; switch (si.type_) { case stream_info::use_file: { int fd = ::open(si.file_.c_str(), O_RDONLY); if (fd == -1) boost::throw_exception(boost::system::system_error(boost::system::error_code(errno, boost::system::get_system_category()), "boost::process::detail::setup_input: open(2) of " + si.file_ + " failed")); if (fd != d) { file_handle h(fd); h.posix_remap(d); h.release(); } break; } case stream_info::use_handle: { if (si.handle_.get() != d) si.handle_.posix_remap(d); break; } case stream_info::use_pipe: { si.pipe_->wend().close(); if (d != si.pipe_->rend().get()) si.pipe_->rend().posix_remap(d); break; } default: { BOOST_ASSERT(si.type_ == stream_info::inherit); break; } } } } /** * Configures child process' output streams. * * Sets up the current process' output streams to behave according to the * information in the \a info map. \a closeflags is modified to reflect * those descriptors that should not be closed because they where * modified by the function. * * Modifies the current execution environment, so this should only be run * on the child process after the fork(2) has happened. * * \throw boost::system::system_error If any error occurs during the * configuration. */ inline void setup_output(info_map &info, bool *closeflags, int maxdescs) { for (info_map::iterator it = info.begin(); it != info.end(); ++it) { int d = it->first; stream_info &si = it->second; BOOST_ASSERT(d < maxdescs); closeflags[d] = false; switch (si.type_) { case stream_info::redirect: { break; } case stream_info::use_file: { int fd = ::open(si.file_.c_str(), O_WRONLY); if (fd == -1) boost::throw_exception(boost::system::system_error(boost::system::error_code(errno, boost::system::get_system_category()), "boost::process::detail::setup_output: open(2) of " + si.file_ + " failed")); if (fd != d) { file_handle h(fd); h.posix_remap(d); h.release(); } break; } case stream_info::use_handle: { if (si.handle_.get() != d) si.handle_.posix_remap(d); break; } case stream_info::use_pipe: { si.pipe_->rend().close(); if (d != si.pipe_->wend().get()) si.pipe_->wend().posix_remap(d); break; } default: { BOOST_ASSERT(si.type_ == stream_info::inherit); break; } } } for (info_map::const_iterator it = info.begin(); it != info.end(); ++it) { int d = it->first; const stream_info &si = it->second; if (si.type_ == stream_info::redirect) file_handle::posix_dup(si.desc_to_, d).release(); } } /** * Starts a new child process in a POSIX operating system. * * This helper functions is provided to simplify the Context's task when * it comes to starting up a new process in a POSIX operating system. * The function hides all the details of the fork/exec pair of calls as * well as all the setup of communication pipes and execution environment. * * \param exe The executable to spawn the child process. * \param args The arguments for the executable. * \param env The environment variables that the new child process * receives. * \param infoin A map describing all input file descriptors to be * redirected. * \param infoout A map describing all output file descriptors to be * redirected. * \param setup A helper object used to configure the child's execution * environment. * \return The new process' PID. The caller is responsible of creating * an appropriate Child representation for it. */ template inline pid_t posix_start(const Executable &exe, const Arguments &args, const environment &env, info_map &infoin, info_map &infoout, const posix_setup &setup) { pid_t pid = ::fork(); if (pid == -1) boost::throw_exception(boost::system::system_error(boost::system::error_code(errno, boost::system::get_system_category()), "boost::process::detail::posix_start: fork(2) failed")); else if (pid == 0) { #if defined(F_MAXFD) int maxdescs = ::fcntl(-1, F_MAXFD, 0); if (maxdescs == -1) maxdescs = ::sysconf(_SC_OPEN_MAX); #else int maxdescs = ::sysconf(_SC_OPEN_MAX); #endif if (maxdescs == -1) maxdescs = 1024; try { boost::scoped_array closeflags(new bool[maxdescs]); for (int i = 0; i < maxdescs; ++i) closeflags[i] = true; setup_input(infoin, closeflags.get(), maxdescs); setup_output(infoout, closeflags.get(), maxdescs); for (int i = 0; i < maxdescs; ++i) { if (closeflags[i]) ::close(i); } setup(); } catch (const boost::system::system_error &e) { ::write(STDERR_FILENO, e.what(), std::strlen(e.what())); ::write(STDERR_FILENO, "\n", 1); std::exit(EXIT_FAILURE); } std::pair argcv = collection_to_posix_argv(args); char **envp = environment_to_envp(env); ::execve(exe.c_str(), argcv.second, envp); boost::system::system_error e(boost::system::error_code(errno, boost::system::get_system_category()), "boost::process::detail::posix_start: execve(2) failed"); for (std::size_t i = 0; i < argcv.first; ++i) delete[] argcv.second[i]; delete[] argcv.second; for (std::size_t i = 0; i < env.size(); ++i) delete[] envp[i]; delete[] envp; ::write(STDERR_FILENO, e.what(), std::strlen(e.what())); ::write(STDERR_FILENO, "\n", 1); std::exit(EXIT_FAILURE); } BOOST_ASSERT(pid > 0); for (info_map::iterator it = infoin.begin(); it != infoin.end(); ++it) { stream_info &si = it->second; if (si.type_ == stream_info::use_pipe) si.pipe_->rend().close(); } for (info_map::iterator it = infoout.begin(); it != infoout.end(); ++it) { stream_info &si = it->second; if (si.type_ == stream_info::use_pipe) si.pipe_->wend().close(); } return pid; } /** * Locates a communication pipe and returns one of its endpoints. * * Given a \a info map, and a file descriptor \a desc, searches for its * communicataion pipe in the map and returns one of its endpoints as * indicated by the \a out flag. This is intended to be used by a * parent process after a fork(2) call. * * \pre If the info map contains the given descriptor, it is configured * to use a pipe. * \post The info map does not contain the given descriptor. * \return If the file descriptor is found in the map, returns the pipe's * read end if out is true; otherwise its write end. If the * descriptor is not found returns an invalid file handle. */ inline file_handle posix_info_locate_pipe(info_map &info, int desc, bool out) { file_handle fh; info_map::iterator it = info.find(desc); if (it != info.end()) { stream_info &si = it->second; if (si.type_ == stream_info::use_pipe) { fh = out ? si.pipe_->rend().release() : si.pipe_->wend().release(); BOOST_ASSERT(fh.valid()); } info.erase(it); } return fh; } } } } #endif