stdio.hpp 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  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> & readable_pipe,
  89. typename std::enable_if<Target != STD_INPUT_HANDLE, Executor*>::type = 0)
  90. {
  91. BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::native_pipe_handle p[2];
  92. error_code ec;
  93. BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::create_pipe(p, ec);
  94. if (ec)
  95. detail::throw_error(ec, "create_pipe");
  96. h = std::unique_ptr<void, handle_closer>{p[1], true};
  97. readable_pipe.assign(p[0]);
  98. }
  99. template<typename Executor>
  100. process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_writable_pipe<Executor> & writable_pipe,
  101. typename std::enable_if<Target == STD_INPUT_HANDLE, Executor*>::type = 0)
  102. {
  103. BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::native_pipe_handle p[2];
  104. error_code ec;
  105. BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::create_pipe(p, ec);
  106. if (ec)
  107. detail::throw_error(ec, "create_pipe");
  108. h = std::unique_ptr<void, handle_closer>{p[0], true};
  109. writable_pipe.assign(p[1]);
  110. }
  111. };
  112. typedef process_io_binding<STD_INPUT_HANDLE> process_input_binding;
  113. typedef process_io_binding<STD_OUTPUT_HANDLE> process_output_binding;
  114. typedef process_io_binding<STD_ERROR_HANDLE> process_error_binding;
  115. #else
  116. template<int Target>
  117. struct process_io_binding
  118. {
  119. constexpr static int target = Target;
  120. int fd{target};
  121. bool fd_needs_closing{false};
  122. error_code ec;
  123. ~process_io_binding()
  124. {
  125. if (fd_needs_closing)
  126. ::close(fd);
  127. }
  128. process_io_binding() = default;
  129. template<typename Stream>
  130. process_io_binding(Stream && str, decltype(std::declval<Stream>().native_handle()) = -1)
  131. : process_io_binding(str.native_handle())
  132. {}
  133. process_io_binding(FILE * f) : process_io_binding(fileno(f)) {}
  134. process_io_binding(int fd) : fd(fd) {}
  135. process_io_binding(std::nullptr_t) : process_io_binding(filesystem::path("/dev/null")) {}
  136. process_io_binding(const filesystem::path & pth)
  137. : fd(::open(pth.c_str(),
  138. Target == STDIN_FILENO ? O_RDONLY : (O_WRONLY | O_CREAT),
  139. 0660)), fd_needs_closing(true)
  140. {
  141. }
  142. template<typename Executor>
  143. process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_readable_pipe<Executor> & readable_pipe,
  144. typename std::enable_if<Target != STDIN_FILENO, Executor*>::type = 0)
  145. {
  146. BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::native_pipe_handle p[2];
  147. BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::create_pipe(p, ec);
  148. if (ec)
  149. return ;
  150. fd = p[1];
  151. if (::fcntl(p[0], F_SETFD, FD_CLOEXEC) == -1)
  152. {
  153. BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec)
  154. return ;
  155. }
  156. fd_needs_closing = true;
  157. readable_pipe.assign(p[0], ec);
  158. }
  159. template<typename Executor>
  160. process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_writable_pipe<Executor> & writable_pipe,
  161. typename std::enable_if<Target == STDIN_FILENO, Executor*>::type = 0)
  162. {
  163. BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::native_pipe_handle p[2];
  164. BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::create_pipe(p, ec);
  165. if (ec)
  166. return ;
  167. fd = p[0];
  168. if (::fcntl(p[1], F_SETFD, FD_CLOEXEC) == -1)
  169. {
  170. BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec)
  171. return ;
  172. }
  173. fd_needs_closing = true;
  174. writable_pipe.assign(p[1], ec);
  175. }
  176. error_code on_setup(posix::default_launcher &,
  177. const filesystem::path &, const char * const *)
  178. {
  179. return ec;
  180. }
  181. error_code on_exec_setup(posix::default_launcher & launcher,
  182. const filesystem::path &, const char * const *)
  183. {
  184. if (::dup2(fd, target) == -1)
  185. return get_last_error();
  186. else
  187. return error_code();
  188. }
  189. };
  190. typedef process_io_binding<STDIN_FILENO> process_input_binding;
  191. typedef process_io_binding<STDOUT_FILENO> process_output_binding;
  192. typedef process_io_binding<STDERR_FILENO> process_error_binding;
  193. #endif
  194. }
  195. /// The initializer for the stdio of a subprocess
  196. /** The subprocess initializer has three members:
  197. *
  198. * - in for stdin
  199. * - out for stdout
  200. * - err for stderr
  201. *
  202. * If the initializer is present all three will be set for the subprocess.
  203. * By default they will inherit the stdio handles from the parent process.
  204. * This means that this will forward stdio to the subprocess:
  205. *
  206. * @code {.cpp}
  207. * asio::io_context ctx;
  208. * v2::process proc(ctx, "/bin/bash", {}, v2::process_stdio{});
  209. * @endcode
  210. *
  211. * No constructors are provided in order to support designated initializers
  212. * in later version of C++.
  213. *
  214. * * @code {.cpp}
  215. * asio::io_context ctx;
  216. * /// C++17
  217. * v2::process proc17(ctx, "/bin/bash", {}, v2::process_stdio{.stderr=nullptr});
  218. * /// C++11 & C++14
  219. * v2::process proc17(ctx, "/bin/bash", {}, v2::process_stdio{ {}, {}, nullptr});
  220. * stdin ^ ^ stderr
  221. * @endcode
  222. *
  223. * Valid initializers for any stdio are:
  224. *
  225. * - `std::nullptr_t` assigning a null-device
  226. * - `FILE*` any open file, including `stdin`, `stdout` and `stderr`
  227. * - a filesystem::path, which will open a readable or writable depending on the direction of the stream
  228. * - `native_handle` any native file handle (`HANDLE` on windows) or file descriptor (`int` on posix)
  229. * - any io-object with a .native_handle() function that is compatible with the above. E.g. a asio::ip::tcp::socket
  230. * - an asio::basic_writeable_pipe for stdin or asio::basic_readable_pipe for stderr/stdout.
  231. *
  232. *
  233. */
  234. struct process_stdio
  235. {
  236. detail::process_input_binding in;
  237. detail::process_output_binding out;
  238. detail::process_error_binding err;
  239. #if defined(BOOST_PROCESS_V2_WINDOWS)
  240. error_code on_setup(windows::default_launcher & launcher, const filesystem::path &, const std::wstring &)
  241. {
  242. launcher.startup_info.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
  243. launcher.startup_info.StartupInfo.hStdInput = in.prepare();
  244. launcher.startup_info.StartupInfo.hStdOutput = out.prepare();
  245. launcher.startup_info.StartupInfo.hStdError = err.prepare();
  246. launcher.inherit_handles = true;
  247. return error_code {};
  248. };
  249. #else
  250. error_code on_exec_setup(posix::default_launcher & launcher, const filesystem::path &, const char * const *)
  251. {
  252. if (::dup2(in.fd, in.target) == -1)
  253. return error_code(errno, system_category());
  254. if (::dup2(out.fd, out.target) == -1)
  255. return error_code(errno, system_category());
  256. if (::dup2(err.fd, err.target) == -1)
  257. return error_code(errno, system_category());
  258. return error_code {};
  259. };
  260. #endif
  261. };
  262. BOOST_PROCESS_V2_END_NAMESPACE
  263. #endif // BOOST_PROCESS_V2_STDIO_HPP