// Copyright (c) 2022 Klemens D. Morgenstern // // 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) #ifndef BOOST_PROCESS_V2_POSIX_DEFAULT_LAUNCHER #define BOOST_PROCESS_V2_POSIX_DEFAULT_LAUNCHER #include #include #include #include #if defined(BOOST_PROCESS_V2_STANDALONE) #include #include #include #include #include #else #include #include #include #include #include #endif #include #include #if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__APPLE__) || defined(__MACH__) extern "C" { extern char **environ; } #endif BOOST_PROCESS_V2_BEGIN_NAMESPACE template struct basic_process; namespace posix { namespace detail { struct base {}; struct derived : base {}; template inline error_code invoke_on_setup(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), Init && init, base && ) { return error_code{}; } template inline auto invoke_on_setup(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), Init && init, derived && ) -> decltype(init.on_setup(launcher, executable, cmd_line)) { return init.on_setup(launcher, executable, cmd_line); } template inline error_code on_setup(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line)) { return error_code{}; } template inline error_code on_setup(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), Init1 && init1, Inits && ... inits) { auto ec = invoke_on_setup(launcher, executable, cmd_line, init1, derived{}); if (ec) return ec; else return on_setup(launcher, executable, cmd_line, inits...); } template inline void invoke_on_error(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), const error_code & ec, Init && init, base && ) { } template inline auto invoke_on_error(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), const error_code & ec, Init && init, derived && ) -> decltype(init.on_error(launcher, ec, executable, cmd_line, ec)) { init.on_error(launcher, executable, cmd_line, ec); } template inline void on_error(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), const error_code & ec) { } template inline void on_error(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), const error_code & ec, Init1 && init1, Inits && ... inits) { invoke_on_error(launcher, executable, cmd_line, ec, init1, derived{}); on_error(launcher, executable, cmd_line, ec, inits...); } template inline void invoke_on_success(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), Init && init, base && ) { } template inline auto invoke_on_success(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), Init && init, derived && ) -> decltype(init.on_success(launcher, executable, cmd_line)) { init.on_success(launcher, executable, cmd_line); } template inline void on_success(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line)) { } template inline void on_success(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), Init1 && init1, Inits && ... inits) { invoke_on_success(launcher, executable, cmd_line, init1, derived{}); on_success(launcher, executable, cmd_line, inits...); } template inline void invoke_on_fork_error(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), const error_code & ec, Init && init, base && ) { } template inline auto invoke_on_fork_error(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), const error_code & ec, Init && init, derived && ) -> decltype(init.on_fork_error(launcher, ec, executable, cmd_line, ec)) { init.on_fork_error(launcher, executable, cmd_line, ec); } template inline void on_fork_error(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), const error_code & ec) { } template inline void on_fork_error(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), const error_code & ec, Init1 && init1, Inits && ... inits) { invoke_on_fork_error(launcher, executable, cmd_line, ec, init1, derived{}); on_fork_error(launcher, executable, cmd_line, ec, inits...); } template inline void invoke_on_fork_success(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), Init && init, base && ) { } template inline auto invoke_on_fork_success(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), Init && init, derived && ) -> decltype(init.on_fork_success(launcher, executable, cmd_line)) { init.on_fork_success(launcher, executable, cmd_line); } template inline void on_fork_success(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line)) { } template inline void on_fork_success(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), Init1 && init1, Inits && ... inits) { invoke_on_fork_success(launcher, executable, cmd_line, init1, derived{}); on_fork_success(launcher, executable, cmd_line, inits...); } template inline error_code invoke_on_exec_setup(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), Init && init, base && ) { return error_code{}; } template inline auto invoke_on_exec_setup(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), Init && init, derived && ) -> decltype(init.on_exec_setup(launcher, executable, cmd_line)) { return init.on_exec_setup(launcher, executable, cmd_line); } template inline error_code on_exec_setup(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line)) { return error_code{}; } template inline error_code on_exec_setup(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), Init1 && init1, Inits && ... inits) { auto ec = invoke_on_exec_setup(launcher, executable, cmd_line, init1, derived{}); if (ec) return ec; else return on_exec_setup(launcher, executable, cmd_line, inits...); } template inline void invoke_on_exec_error(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), const error_code & ec, Init && init, base && ) { } template inline auto invoke_on_exec_error(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), const error_code & ec, Init && init, derived && ) -> decltype(init.on_exec_error(launcher, ec, executable, cmd_line, ec)) { init.on_exec_error(launcher, executable, cmd_line, ec); } template inline void on_exec_error(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), const error_code & ec) { } template inline void on_exec_error(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), const error_code & ec, Init1 && init1, Inits && ... inits) { invoke_on_exec_error(launcher, executable, cmd_line, ec, init1, derived{}); on_exec_error(launcher, executable, cmd_line, ec, inits...); } } /// The default launcher for processes on windows. struct default_launcher { /// The pointer to the environment forwarded to the subprocess. const char * const * env = ::environ; /// The pid of the subprocess - will be assigned after fork. int pid = -1; /// The whitelist for file descriptors. std::vector fd_whitelist = {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}; default_launcher() = default; template auto operator()(ExecutionContext & context, const typename std::enable_if::value, filesystem::path >::type & executable, Args && args, Inits && ... inits ) -> basic_process { error_code ec; auto proc = (*this)(context, ec, executable, std::forward(args), std::forward(inits)...); if (ec) asio::detail::throw_error(ec, "default_launcher"); return proc; } template auto operator()(ExecutionContext & context, error_code & ec, const typename std::enable_if::value, filesystem::path >::type & executable, Args && args, Inits && ... inits ) -> basic_process { return (*this)(context.get_executor(), executable, std::forward(args), std::forward(inits)...); } template auto operator()(Executor exec, const typename std::enable_if< BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor::value || BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor::value, filesystem::path >::type & executable, Args && args, Inits && ... inits ) -> basic_process { error_code ec; auto proc = (*this)(std::move(exec), ec, executable, std::forward(args), std::forward(inits)...); if (ec) asio::detail::throw_error(ec, "default_launcher"); return proc; } template auto operator()(Executor exec, error_code & ec, const typename std::enable_if< BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor::value || BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor::value, filesystem::path >::type & executable, Args && args, Inits && ... inits ) -> basic_process { auto argv = this->build_argv_(executable, std::forward(args)); { pipe_guard pg; if (::pipe(pg.p)) { ec.assign(errno, system_category()); return basic_process{exec}; } if (::fcntl(pg.p[1], F_SETFD, FD_CLOEXEC)) { ec.assign(errno, system_category()); return basic_process{exec}; } ec = detail::on_setup(*this, executable, argv, inits ...); if (ec) { detail::on_error(*this, executable, argv, ec, inits...); return basic_process(exec); } auto & ctx = BOOST_PROCESS_V2_ASIO_NAMESPACE::query( exec, BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::context); ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_prepare); pid = ::fork(); if (pid == -1) { ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_parent); detail::on_fork_error(*this, executable, argv, ec, inits...); detail::on_error(*this, executable, argv, ec, inits...); ec.assign(errno, system_category()); return basic_process{exec}; } else if (pid == 0) { ::close(pg.p[0]); ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_child); ec = detail::on_exec_setup(*this, executable, argv, inits...); if (!ec) { fd_whitelist.push_back(pg.p[1]); close_all_fds(ec); } if (!ec) ::execve(executable.c_str(), const_cast(argv), const_cast(env)); ignore_unused(::write(pg.p[1], &errno, sizeof(int))); ec.assign(errno, system_category()); detail::on_exec_error(*this, executable, argv, ec, inits...); ::exit(EXIT_FAILURE); return basic_process{exec}; } ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_parent); ::close(pg.p[1]); pg.p[1] = -1; int child_error{0}; int count = -1; while ((count = ::read(pg.p[0], &child_error, sizeof(child_error))) == -1) { int err = errno; if ((err != EAGAIN) && (err != EINTR)) { ec.assign(err, system_category()); break; } } if (count != 0) ec.assign(child_error, system_category()); if (ec) { detail::on_error(*this, executable, argv, ec, inits...); return basic_process{exec}; } } basic_process proc(exec, pid); detail::on_success(*this, executable, argv, ec, inits...); return proc; } protected: void ignore_unused(std::size_t ) {} void close_all_fds(error_code & ec) { std::sort(fd_whitelist.begin(), fd_whitelist.end()); detail::close_all(fd_whitelist, ec); fd_whitelist = {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}; } struct pipe_guard { int p[2]; pipe_guard() : p{-1,-1} {} ~pipe_guard() { if (p[0] != -1) ::close(p[0]); if (p[1] != -1) ::close(p[1]); } }; //if we need to allocate something std::vector argv_buffer_; std::vector argv_; template const char * const * build_argv_(const filesystem::path & pt, const Args & args, typename std::enable_if< std::is_convertible< decltype(*std::begin(std::declval())), cstring_ref>::value>::type * = nullptr) { const auto arg_cnt = std::distance(std::begin(args), std::end(args)); argv_.reserve(arg_cnt + 2); argv_.push_back(pt.native().data()); for (auto && arg : args) argv_.push_back(arg.c_str()); argv_.push_back(nullptr); return argv_.data(); } template const char * const * build_argv_(const filesystem::path & pt, const Args & args, typename std::enable_if< !std::is_convertible< decltype(*std::begin(std::declval())), cstring_ref>::value>::type * = nullptr) { const auto arg_cnt = std::distance(std::begin(args), std::end(args)); argv_.reserve(arg_cnt + 2); argv_buffer_.reserve(arg_cnt); argv_.push_back(pt.native().data()); using char_type = typename decay()))[0])>::type; for (basic_string_view arg : args) argv_buffer_.push_back(v2::detail::conv_string(arg.data(), arg.size())); for (auto && arg : argv_buffer_) argv_.push_back(arg.c_str()); argv_.push_back(nullptr); return argv_.data(); } }; } BOOST_PROCESS_V2_END_NAMESPACE #endif //BOOST_PROCESS_V2_POSIX_DEFAULT_LAUNCHER