整理
This commit is contained in:
367
include/boost/process/v2/stdio.hpp
Normal file
367
include/boost/process/v2/stdio.hpp
Normal file
@@ -0,0 +1,367 @@
|
||||
//
|
||||
// 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 <boost/process/v2/detail/config.hpp>
|
||||
#include <boost/process/v2/detail/last_error.hpp>
|
||||
#include <boost/process/v2/default_launcher.hpp>
|
||||
#include <cstddef>
|
||||
#if defined(BOOST_PROCESS_V2_STANDALONE)
|
||||
#include <asio/connect_pipe.hpp>
|
||||
#else
|
||||
#include <boost/asio/connect_pipe.hpp>
|
||||
#endif
|
||||
|
||||
#if defined(BOOST_PROCESS_V2_POSIX)
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#if defined(BOOST_PROCESS_V2_WINDOWS)
|
||||
#include <io.h>
|
||||
#endif
|
||||
|
||||
BOOST_PROCESS_V2_BEGIN_NAMESPACE
|
||||
|
||||
|
||||
template<typename T>
|
||||
struct is_readable_pipe : std::false_type
|
||||
{
|
||||
};
|
||||
|
||||
template<typename Executor>
|
||||
struct is_readable_pipe<asio::basic_readable_pipe<Executor>> : std::true_type
|
||||
{
|
||||
};
|
||||
|
||||
|
||||
template<typename T>
|
||||
struct is_writable_pipe : std::false_type
|
||||
{
|
||||
};
|
||||
|
||||
template<typename Executor>
|
||||
struct is_writable_pipe<asio::basic_writable_pipe<Executor>> : std::true_type
|
||||
{
|
||||
};
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
|
||||
#if defined(BOOST_PROCESS_V2_WINDOWS)
|
||||
|
||||
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<DWORD Target>
|
||||
struct process_io_binding
|
||||
{
|
||||
HANDLE prepare()
|
||||
{
|
||||
auto hh = h.get();
|
||||
::SetHandleInformation(hh, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
|
||||
return hh;
|
||||
}
|
||||
|
||||
std::unique_ptr<void, handle_closer> h{::GetStdHandle(Target), false};
|
||||
|
||||
static DWORD get_flags(HANDLE h)
|
||||
{
|
||||
DWORD res;
|
||||
if (!::GetHandleInformation(h, &res))
|
||||
{
|
||||
error_code ec;
|
||||
BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec);
|
||||
throw system_error(ec, "get_flags");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
process_io_binding() = default;
|
||||
|
||||
template<typename Stream>
|
||||
process_io_binding(Stream && str, decltype(std::declval<Stream>().native_handle())* = nullptr)
|
||||
: process_io_binding(str.native_handle())
|
||||
{}
|
||||
|
||||
process_io_binding(FILE * f) : process_io_binding(reinterpret_cast<HANDLE>(::_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<typename T, typename = typename std::enable_if<std::is_same<T, filesystem::path>::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<typename ReadablePipe>
|
||||
process_io_binding(ReadablePipe & pipe,
|
||||
typename std::enable_if<is_readable_pipe<ReadablePipe>::value && Target != STD_INPUT_HANDLE>::type * = nullptr)
|
||||
{
|
||||
net::detail::native_pipe_handle p[2];
|
||||
error_code ec;
|
||||
net::detail::create_pipe(p, ec);
|
||||
if (ec)
|
||||
detail::throw_error(ec, "create_pipe");
|
||||
|
||||
h = std::unique_ptr<void, handle_closer>{p[1], true};
|
||||
pipe.assign(p[0]);
|
||||
}
|
||||
|
||||
|
||||
template<typename WritablePipe>
|
||||
process_io_binding(WritablePipe & pipe,
|
||||
typename std::enable_if<is_writable_pipe<WritablePipe>::value && Target == STD_INPUT_HANDLE>::type * = nullptr)
|
||||
{
|
||||
net::detail::native_pipe_handle p[2];
|
||||
error_code ec;
|
||||
net::detail::create_pipe(p, ec);
|
||||
if (ec)
|
||||
detail::throw_error(ec, "create_pipe");
|
||||
|
||||
h = std::unique_ptr<void, handle_closer>{p[0], true};
|
||||
pipe.assign(p[1]);
|
||||
}
|
||||
};
|
||||
|
||||
typedef process_io_binding<STD_INPUT_HANDLE> process_input_binding;
|
||||
typedef process_io_binding<STD_OUTPUT_HANDLE> process_output_binding;
|
||||
typedef process_io_binding<STD_ERROR_HANDLE> process_error_binding;
|
||||
|
||||
#else
|
||||
|
||||
template<int Target>
|
||||
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;
|
||||
process_io_binding(const process_io_binding &) = delete;
|
||||
process_io_binding & operator=(const process_io_binding &) = delete;
|
||||
|
||||
process_io_binding(process_io_binding && other) noexcept
|
||||
: fd(other.fd), fd_needs_closing(other.fd_needs_closing), ec(other.ec)
|
||||
{
|
||||
other.fd = target;
|
||||
other.fd_needs_closing = false;
|
||||
other.ec = {};
|
||||
}
|
||||
|
||||
process_io_binding & operator=(process_io_binding && other) noexcept
|
||||
{
|
||||
if (fd_needs_closing)
|
||||
::close(fd);
|
||||
|
||||
fd = other.fd;
|
||||
fd_needs_closing = other.fd_needs_closing;
|
||||
ec = other.ec;
|
||||
|
||||
other.fd = target;
|
||||
other.fd_needs_closing = false;
|
||||
other.ec = {};
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename Stream>
|
||||
process_io_binding(Stream && str, decltype(std::declval<Stream>().native_handle()) * = nullptr)
|
||||
: 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<typename ReadablePipe>
|
||||
process_io_binding(ReadablePipe & readable_pipe,
|
||||
typename std::enable_if<is_readable_pipe<ReadablePipe>::value && Target != STDIN_FILENO>::type * = nullptr)
|
||||
{
|
||||
net::detail::native_pipe_handle p[2];
|
||||
net::detail::create_pipe(p, ec);
|
||||
if (ec)
|
||||
detail::throw_error(ec, "create_pipe");
|
||||
|
||||
fd = p[1];
|
||||
if (::fcntl(p[0], F_SETFD, FD_CLOEXEC) == -1)
|
||||
{
|
||||
BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec);
|
||||
return ;
|
||||
}
|
||||
fd_needs_closing = true;
|
||||
readable_pipe.assign(p[0], ec);
|
||||
}
|
||||
|
||||
|
||||
template<typename WritablePipe>
|
||||
process_io_binding(WritablePipe & writable_pipe,
|
||||
typename std::enable_if<is_writable_pipe<WritablePipe>::value && Target == STDIN_FILENO>::type * = nullptr)
|
||||
{
|
||||
net::detail::native_pipe_handle p[2];
|
||||
error_code ec;
|
||||
net::detail::create_pipe(p, ec);
|
||||
if (ec)
|
||||
detail::throw_error(ec, "create_pipe");
|
||||
|
||||
fd = p[0];
|
||||
if (::fcntl(p[1], F_SETFD, FD_CLOEXEC) == -1)
|
||||
{
|
||||
BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec);
|
||||
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 &,
|
||||
const filesystem::path &, const char * const *)
|
||||
{
|
||||
if (::dup2(fd, target) == -1)
|
||||
return get_last_error();
|
||||
else
|
||||
return error_code();
|
||||
}
|
||||
};
|
||||
|
||||
typedef process_io_binding<STDIN_FILENO> process_input_binding;
|
||||
typedef process_io_binding<STDOUT_FILENO> process_output_binding;
|
||||
typedef process_io_binding<STDERR_FILENO> 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{.err=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 compatible 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.inherited_handles.reserve(launcher.inherited_handles.size() + 3);
|
||||
launcher.inherited_handles.push_back(launcher.startup_info.StartupInfo.hStdInput);
|
||||
launcher.inherited_handles.push_back(launcher.startup_info.StartupInfo.hStdOutput);
|
||||
launcher.inherited_handles.push_back(launcher.startup_info.StartupInfo.hStdError);
|
||||
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
|
||||
Reference in New Issue
Block a user