You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

495 lines
17 KiB

//
// 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 <boost/process/environment.hpp>
#include <boost/process/detail/file_handle.hpp>
#include <boost/process/detail/pipe.hpp>
#include <boost/process/detail/stream_info.hpp>
#include <boost/scoped_array.hpp>
#include <boost/assert.hpp>
#include <boost/system/system_error.hpp>
#include <boost/throw_exception.hpp>
#include <map>
#include <utility>
#include <string>
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <unistd.h>
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 <class Arguments>
inline std::pair<std::size_t, char**> 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<std::size_t, char**>(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<int, stream_info> 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 <class Executable, class Arguments>
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<bool> 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<std::size_t, char**> 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