stdio.hpp 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. //
  2. // process/stdio.hpp
  3. // ~~~~~~~~
  4. //
  5. // Copyright (c) 2021 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net)
  6. //
  7. // Distributed under the Boost Software License, Version 1.0. (See accompanying
  8. // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  9. #ifndef BOOST_PROCESS_V2_STDIO_HPP
  10. #define BOOST_PROCESS_V2_STDIO_HPP
  11. #include <boost/process/v2/detail/config.hpp>
  12. #include <boost/process/v2/detail/last_error.hpp>
  13. #include <boost/process/v2/default_launcher.hpp>
  14. #include <cstddef>
  15. #if defined(BOOST_PROCESS_V2_STANDALONE)
  16. #include <asio/connect_pipe.hpp>
  17. #else
  18. #include <boost/asio/connect_pipe.hpp>
  19. #endif
  20. #if defined(BOOST_PROCESS_V2_POSIX)
  21. #include <fcntl.h>
  22. #include <stdio.h>
  23. #include <unistd.h>
  24. #endif
  25. BOOST_PROCESS_V2_BEGIN_NAMESPACE
  26. namespace detail
  27. {
  28. #if defined(BOOST_PROCESS_V2_WINDOWS)
  29. extern "C" intptr_t _get_osfhandle(int fd);
  30. struct handle_closer
  31. {
  32. handle_closer() = default;
  33. handle_closer(bool close) : close(close) {}
  34. handle_closer(DWORD flags) : close(false), flags{flags} {}
  35. void operator()(HANDLE h) const
  36. {
  37. if (close)
  38. ::CloseHandle(h);
  39. else if (flags != 0xFFFFFFFFu)
  40. ::SetHandleInformation(h, 0xFFFFFFFFu, flags);
  41. }
  42. bool close{false};
  43. DWORD flags{0xFFFFFFFFu};
  44. };
  45. template<DWORD Target>
  46. struct process_io_binding
  47. {
  48. HANDLE prepare()
  49. {
  50. auto hh = h.get();
  51. ::SetHandleInformation(hh, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
  52. return hh;
  53. }
  54. std::unique_ptr<void, handle_closer> h{::GetStdHandle(Target), false};
  55. static DWORD get_flags(HANDLE h)
  56. {
  57. DWORD res;
  58. if (!::GetHandleInformation(h, &res))
  59. {
  60. error_code ec;
  61. BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec);
  62. throw system_error(ec, "get_flags");
  63. }
  64. return res;
  65. }
  66. process_io_binding() = default;
  67. template<typename Stream>
  68. process_io_binding(Stream && str, decltype(std::declval<Stream>().native_handle()) = nullptr)
  69. : process_io_binding(str.native_handle())
  70. {}
  71. process_io_binding(FILE * f) : process_io_binding(_get_osfhandle(_fileno(f))) {}
  72. process_io_binding(HANDLE h) : h{h, get_flags(h)} {}
  73. process_io_binding(std::nullptr_t) : process_io_binding(filesystem::path("NUL")) {}
  74. template<typename T, typename = typename std::enable_if<std::is_same<T, filesystem::path>::value>::type>
  75. process_io_binding(const T & pth)
  76. : h(::CreateFileW(
  77. pth.c_str(),
  78. Target == STD_INPUT_HANDLE ? GENERIC_READ : GENERIC_WRITE,
  79. FILE_SHARE_READ | FILE_SHARE_WRITE,
  80. nullptr,
  81. OPEN_ALWAYS,
  82. FILE_ATTRIBUTE_NORMAL,
  83. nullptr
  84. ), true)
  85. {
  86. }
  87. template<typename Executor>
  88. process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_readable_pipe<Executor> & pipe)
  89. {
  90. if (Target == STD_INPUT_HANDLE)
  91. {
  92. auto h_ = pipe.native_handle();
  93. h = std::unique_ptr<void, handle_closer>{h_, get_flags(h_)};
  94. return ;
  95. }
  96. BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::native_pipe_handle p[2];
  97. error_code ec;
  98. BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::create_pipe(p, ec);
  99. if (ec)
  100. detail::throw_error(ec, "create_pipe");
  101. h = std::unique_ptr<void, handle_closer>{p[1], true};
  102. pipe.assign(p[0]);
  103. }
  104. template<typename Executor>
  105. process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_writable_pipe<Executor> & pipe)
  106. {
  107. if (Target != STD_INPUT_HANDLE)
  108. {
  109. auto h_ = pipe.native_handle();
  110. h = std::unique_ptr<void, handle_closer>{h_, get_flags(h_)};
  111. return ;
  112. }
  113. BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::native_pipe_handle p[2];
  114. error_code ec;
  115. BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::create_pipe(p, ec);
  116. if (ec)
  117. detail::throw_error(ec, "create_pipe");
  118. h = std::unique_ptr<void, handle_closer>{p[0], true};
  119. pipe.assign(p[1]);
  120. }
  121. };
  122. typedef process_io_binding<STD_INPUT_HANDLE> process_input_binding;
  123. typedef process_io_binding<STD_OUTPUT_HANDLE> process_output_binding;
  124. typedef process_io_binding<STD_ERROR_HANDLE> process_error_binding;
  125. #else
  126. template<int Target>
  127. struct process_io_binding
  128. {
  129. constexpr static int target = Target;
  130. int fd{target};
  131. bool fd_needs_closing{false};
  132. error_code ec;
  133. ~process_io_binding()
  134. {
  135. if (fd_needs_closing)
  136. ::close(fd);
  137. }
  138. process_io_binding() = default;
  139. template<typename Stream>
  140. process_io_binding(Stream && str, decltype(std::declval<Stream>().native_handle()) = -1)
  141. : process_io_binding(str.native_handle())
  142. {}
  143. process_io_binding(FILE * f) : process_io_binding(fileno(f)) {}
  144. process_io_binding(int fd) : fd(fd) {}
  145. process_io_binding(std::nullptr_t) : process_io_binding(filesystem::path("/dev/null")) {}
  146. process_io_binding(const filesystem::path & pth)
  147. : fd(::open(pth.c_str(),
  148. Target == STDIN_FILENO ? O_RDONLY : (O_WRONLY | O_CREAT),
  149. 0660)), fd_needs_closing(true)
  150. {
  151. }
  152. template<typename Executor>
  153. process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_readable_pipe<Executor> & readable_pipe)
  154. {
  155. if (Target == STDIN_FILENO)
  156. {
  157. fd = readable_pipe.native_handle();
  158. return ;
  159. }
  160. BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::native_pipe_handle p[2];
  161. BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::create_pipe(p, ec);
  162. if (ec)
  163. detail::throw_error(ec, "create_pipe");
  164. fd = p[1];
  165. if (::fcntl(p[0], F_SETFD, FD_CLOEXEC) == -1)
  166. {
  167. BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec)
  168. return ;
  169. }
  170. fd_needs_closing = true;
  171. readable_pipe.assign(p[0], ec);
  172. }
  173. template<typename Executor>
  174. process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_writable_pipe<Executor> & writable_pipe)
  175. {
  176. if (Target != STDIN_FILENO)
  177. {
  178. fd = writable_pipe.native_handle();
  179. return ;
  180. }
  181. BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::native_pipe_handle p[2];
  182. error_code ec;
  183. BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::create_pipe(p, ec);
  184. if (ec)
  185. detail::throw_error(ec, "create_pipe");
  186. fd = p[0];
  187. if (::fcntl(p[1], F_SETFD, FD_CLOEXEC) == -1)
  188. {
  189. BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec)
  190. return ;
  191. }
  192. fd_needs_closing = true;
  193. writable_pipe.assign(p[1], ec);
  194. }
  195. error_code on_setup(posix::default_launcher &,
  196. const filesystem::path &, const char * const *)
  197. {
  198. return ec;
  199. }
  200. error_code on_exec_setup(posix::default_launcher & launcher,
  201. const filesystem::path &, const char * const *)
  202. {
  203. if (::dup2(fd, target) == -1)
  204. return get_last_error();
  205. else
  206. return error_code();
  207. }
  208. };
  209. typedef process_io_binding<STDIN_FILENO> process_input_binding;
  210. typedef process_io_binding<STDOUT_FILENO> process_output_binding;
  211. typedef process_io_binding<STDERR_FILENO> process_error_binding;
  212. #endif
  213. }
  214. /// The initializer for the stdio of a subprocess
  215. /** The subprocess initializer has three members:
  216. *
  217. * - in for stdin
  218. * - out for stdout
  219. * - err for stderr
  220. *
  221. * If the initializer is present all three will be set for the subprocess.
  222. * By default they will inherit the stdio handles from the parent process.
  223. * This means that this will forward stdio to the subprocess:
  224. *
  225. * @code {.cpp}
  226. * asio::io_context ctx;
  227. * v2::process proc(ctx, "/bin/bash", {}, v2::process_stdio{});
  228. * @endcode
  229. *
  230. * No constructors are provided in order to support designated initializers
  231. * in later version of C++.
  232. *
  233. * * @code {.cpp}
  234. * asio::io_context ctx;
  235. * /// C++17
  236. * v2::process proc17(ctx, "/bin/bash", {}, v2::process_stdio{.stderr=nullptr});
  237. * /// C++11 & C++14
  238. * v2::process proc17(ctx, "/bin/bash", {}, v2::process_stdio{ {}, {}, nullptr});
  239. * stdin ^ ^ stderr
  240. * @endcode
  241. *
  242. * Valid initializers for any stdio are:
  243. *
  244. * - `std::nullptr_t` assigning a null-device
  245. * - `FILE*` any open file, including `stdin`, `stdout` and `stderr`
  246. * - a filesystem::path, which will open a readable or writable depending on the direction of the stream
  247. * - `native_handle` any native file handle (`HANDLE` on windows) or file descriptor (`int` on posix)
  248. * - any io-object with a .native_handle() function that is compatible with the above. E.g. a asio::ip::tcp::socket
  249. * - an asio::basic_writeable_pipe for stdin or asio::basic_readable_pipe for stderr/stdout.
  250. *
  251. *
  252. */
  253. struct process_stdio
  254. {
  255. detail::process_input_binding in;
  256. detail::process_output_binding out;
  257. detail::process_error_binding err;
  258. #if defined(BOOST_PROCESS_V2_WINDOWS)
  259. error_code on_setup(windows::default_launcher & launcher, const filesystem::path &, const std::wstring &)
  260. {
  261. launcher.startup_info.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
  262. launcher.startup_info.StartupInfo.hStdInput = in.prepare();
  263. launcher.startup_info.StartupInfo.hStdOutput = out.prepare();
  264. launcher.startup_info.StartupInfo.hStdError = err.prepare();
  265. launcher.inherit_handles = true;
  266. return error_code {};
  267. };
  268. #else
  269. error_code on_exec_setup(posix::default_launcher & launcher, const filesystem::path &, const char * const *)
  270. {
  271. if (::dup2(in.fd, in.target) == -1)
  272. return error_code(errno, system_category());
  273. if (::dup2(out.fd, out.target) == -1)
  274. return error_code(errno, system_category());
  275. if (::dup2(err.fd, err.target) == -1)
  276. return error_code(errno, system_category());
  277. return error_code {};
  278. };
  279. #endif
  280. };
  281. BOOST_PROCESS_V2_END_NAMESPACE
  282. #endif // BOOST_PROCESS_V2_STDIO_HPP