// // process/stdio.hpp // ~~~~~~~~ // // Copyright (c) 2021 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net) // // 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_STDIO_HPP #define BOOST_PROCESS_V2_STDIO_HPP #include #include #include #if defined(BOOST_PROCESS_V2_STANDALONE) #include #else #include #endif #if defined(BOOST_PROCESS_V2_POSIX) #include #include #include #endif BOOST_PROCESS_V2_BEGIN_NAMESPACE namespace detail { #if defined(BOOST_PROCESS_V2_WINDOWS) extern "C" intptr_t _get_osfhandle(int fd); struct handle_closer { handle_closer() = default; handle_closer(bool close) : close(close) {} handle_closer(DWORD flags) : close(false), flags{flags} {} void operator()(HANDLE h) const { if (close) ::CloseHandle(h); else if (flags != 0xFFFFFFFFu) ::SetHandleInformation(h, 0xFFFFFFFFu, flags); } bool close{false}; DWORD flags{0xFFFFFFFFu}; }; template struct process_io_binding { HANDLE prepare() { auto hh = h.get(); ::SetHandleInformation(hh, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); return hh; } std::unique_ptr h{::GetStdHandle(Target), false}; static DWORD get_flags(HANDLE h) { DWORD res; if (!::GetHandleInformation(h, &res)) detail::throw_last_error("get_flags"); return res; } process_io_binding() = default; template process_io_binding(Stream && str, decltype(std::declval().native_handle()) = nullptr) : process_io_binding(str.native_handle()) {} process_io_binding(FILE * f) : process_io_binding(_get_osfhandle(_fileno(f))) {} process_io_binding(HANDLE h) : h{h, get_flags(h)} {} process_io_binding(std::nullptr_t) : process_io_binding(filesystem::path("NUL")) {} template::value>::type> process_io_binding(const T & pth) : h(::CreateFileW( pth.c_str(), Target == STD_INPUT_HANDLE ? GENERIC_READ : GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr ), true) { } template process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_readable_pipe & readable_pipe, typename std::enable_if::type = 0) { BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::native_pipe_handle p[2]; error_code ec; BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::create_pipe(p, ec); if (ec) detail::throw_error(ec, "create_pipe"); h = std::unique_ptr{p[1], true}; readable_pipe.assign(p[0]); } template process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_writable_pipe & writable_pipe, typename std::enable_if::type = 0) { BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::native_pipe_handle p[2]; error_code ec; BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::create_pipe(p, ec); if (ec) detail::throw_error(ec, "create_pipe"); h = std::unique_ptr{p[0], true}; writable_pipe.assign(p[1]); } }; typedef process_io_binding process_input_binding; typedef process_io_binding process_output_binding; typedef process_io_binding process_error_binding; #else template struct process_io_binding { constexpr static int target = Target; int fd{target}; bool fd_needs_closing{false}; error_code ec; ~process_io_binding() { if (fd_needs_closing) ::close(fd); } process_io_binding() = default; template process_io_binding(Stream && str, decltype(std::declval().native_handle()) = -1) : process_io_binding(str.native_handle()) {} process_io_binding(FILE * f) : process_io_binding(fileno(f)) {} process_io_binding(int fd) : fd(fd) {} process_io_binding(std::nullptr_t) : process_io_binding(filesystem::path("/dev/null")) {} process_io_binding(const filesystem::path & pth) : fd(::open(pth.c_str(), Target == STDIN_FILENO ? O_RDONLY : (O_WRONLY | O_CREAT), 0660)), fd_needs_closing(true) { } template process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_readable_pipe & readable_pipe, typename std::enable_if::type = 0) { BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::native_pipe_handle p[2]; BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::create_pipe(p, ec); if (ec) return ; fd = p[1]; if (::fcntl(p[0], F_SETFD, FD_CLOEXEC) == -1) { ec = detail::get_last_error(); return ; } fd_needs_closing = true; readable_pipe.assign(p[0], ec); } template process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_writable_pipe & writable_pipe, typename std::enable_if::type = 0) { BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::native_pipe_handle p[2]; BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::create_pipe(p, ec); if (ec) return ; fd = p[0]; if (::fcntl(p[1], F_SETFD, FD_CLOEXEC) == -1) { ec = detail::get_last_error(); return ; } fd_needs_closing = true; writable_pipe.assign(p[1], ec); } error_code on_setup(posix::default_launcher &, const filesystem::path &, const char * const *) { return ec; } error_code on_exec_setup(posix::default_launcher & launcher, const filesystem::path &, const char * const *) { if (::dup2(fd, target) == -1) return get_last_error(); else return error_code(); } }; typedef process_io_binding process_input_binding; typedef process_io_binding process_output_binding; typedef process_io_binding process_error_binding; #endif } /// The initializer for the stdio of a subprocess /** The subprocess initializer has three members: * * - in for stdin * - out for stdout * - err for stderr * * If the initializer is present all three will be set for the subprocess. * By default they will inherit the stdio handles from the parent process. * This means that this will forward stdio to the subprocess: * * @code {.cpp} * asio::io_context ctx; * v2::process proc(ctx, "/bin/bash", {}, v2::process_stdio{}); * @endcode * * No constructors are provided in order to support designated initializers * in later version of C++. * * * @code {.cpp} * asio::io_context ctx; * /// C++17 * v2::process proc17(ctx, "/bin/bash", {}, v2::process_stdio{.stderr=nullptr}); * /// C++11 & C++14 * v2::process proc17(ctx, "/bin/bash", {}, v2::process_stdio{ {}, {}, nullptr}); * stdin ^ ^ stderr * @endcode * * Valid initializers for any stdio are: * * - `std::nullptr_t` assigning a null-device * - `FILE*` any open file, including `stdin`, `stdout` and `stderr` * - a filesystem::path, which will open a readable or writable depending on the direction of the stream * - `native_handle` any native file handle (`HANDLE` on windows) or file descriptor (`int` on posix) * - any io-object with a .native_handle() function that is comptaiblie with the above. E.g. a asio::ip::tcp::socket * - an asio::basic_writeable_pipe for stdin or asio::basic_readable_pipe for stderr/stdout. * * */ struct process_stdio { detail::process_input_binding in; detail::process_output_binding out; detail::process_error_binding err; #if defined(BOOST_PROCESS_V2_WINDOWS) error_code on_setup(windows::default_launcher & launcher, const filesystem::path &, const std::wstring &) { launcher.startup_info.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; launcher.startup_info.StartupInfo.hStdInput = in.prepare(); launcher.startup_info.StartupInfo.hStdOutput = out.prepare(); launcher.startup_info.StartupInfo.hStdError = err.prepare(); launcher.inherit_handles = true; return error_code {}; }; #else error_code on_exec_setup(posix::default_launcher & launcher, const filesystem::path &, const char * const *) { if (::dup2(in.fd, in.target) == -1) return error_code(errno, system_category()); if (::dup2(out.fd, out.target) == -1) return error_code(errno, system_category()); if (::dup2(err.fd, err.target) == -1) return error_code(errno, system_category()); return error_code {}; }; #endif }; BOOST_PROCESS_V2_END_NAMESPACE #endif // BOOST_PROCESS_V2_STDIO_HPP