stdio.hpp
8.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
//
// 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/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
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<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))
detail::throw_last_error("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(_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 Executor>
process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_readable_pipe<Executor> & readable_pipe,
typename std::enable_if<Target != STD_INPUT_HANDLE, Executor*>::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<void, handle_closer>{p[1], true};
readable_pipe.assign(p[0]);
}
template<typename Executor>
process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_writable_pipe<Executor> & writable_pipe,
typename std::enable_if<Target == STD_INPUT_HANDLE, Executor*>::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<void, handle_closer>{p[0], true};
writable_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;
template<typename Stream>
process_io_binding(Stream && str, decltype(std::declval<Stream>().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<typename Executor>
process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_readable_pipe<Executor> & readable_pipe,
typename std::enable_if<Target != STDIN_FILENO, Executor*>::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<typename Executor>
process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_writable_pipe<Executor> & writable_pipe,
typename std::enable_if<Target == STDIN_FILENO, Executor*>::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<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{.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