stdio.hpp 10 KB

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