Silicium
async_process.hpp
Go to the documentation of this file.
1 #ifndef SILICIUM_ASYNC_PROCESS_HPP
2 #define SILICIUM_ASYNC_PROCESS_HPP
3 
4 #include <silicium/os_string.hpp>
8 #include <silicium/error_or.hpp>
9 #include <silicium/posix/pipe.hpp>
10 #include <silicium/observable/virtualized.hpp>
11 #include <silicium/observable/spawn_coroutine.hpp>
12 #include <silicium/observable/spawn_observable.hpp>
13 #include <silicium/observable/thread.hpp>
14 #include <silicium/observable/ref.hpp>
15 #include <silicium/sink/buffering_sink.hpp>
17 #include <silicium/asio/posting_observable.hpp>
18 #include <silicium/asio/process_output.hpp>
20 
21 #ifndef _WIN32
22 # include <fcntl.h>
23 # include <sys/wait.h>
24 # include <sys/prctl.h>
25 #endif
26 
27 //TODO: avoid the Boost filesystem operations that require exceptions
28 #define SILICIUM_HAS_LAUNCH_PROCESS SILICIUM_HAS_EXCEPTIONS
29 
30 namespace Si
31 {
33  {
35 
37  std::vector<os_string> arguments;
38 
41  };
42 
44  {
45  process_handle process;
46 #ifndef _WIN32
48 #endif
49 
50  async_process() BOOST_NOEXCEPT
51  {
52  }
53 
54 #ifdef _WIN32
55  explicit async_process(process_handle process) BOOST_NOEXCEPT
56  : process(std::move(process))
57  {
58  }
59 #else
60  explicit async_process(process_handle process, file_handle child_error) BOOST_NOEXCEPT
61  : process(std::move(process))
62  , child_error(std::move(child_error))
63  {
64  }
65 #endif
66 
67 #if SILICIUM_COMPILER_GENERATES_MOVES
68  async_process(async_process &&) BOOST_NOEXCEPT = default;
69  async_process &operator = (async_process &&)BOOST_NOEXCEPT = default;
70 #else
71  async_process(async_process &&other) BOOST_NOEXCEPT
72  : process(std::move(other.process))
73 #ifndef _WIN32
74  , child_error(std::move(other.child_error))
75 #endif
76  {
77  }
78 
79  async_process &operator = (async_process &&other) BOOST_NOEXCEPT
80  {
81  process = std::move(other.process);
82 #ifndef _WIN32
83  child_error = std::move(other.child_error);
84 #endif
85  return *this;
86  }
87 
90  public:
91 #endif
92 
93  ~async_process() BOOST_NOEXCEPT
94  {
95  }
96 
97  error_or<int> wait_for_exit() BOOST_NOEXCEPT
98  {
99 #ifndef _WIN32
100  int error = 0;
101  ssize_t read_error = read(child_error.handle, &error, sizeof(error));
102  if (read_error < 0)
103  {
104  return get_last_error();
105  }
106  if (read_error != 0)
107  {
108  assert(read_error == sizeof(error));
109  return boost::system::error_code(error, boost::system::system_category());
110  }
111 #endif
112  return process.wait_for_exit();
113  }
114  };
115 
117  {
118  inherit,
119  no_inherit
120  };
121 
122 #if SILICIUM_HAS_LAUNCH_PROCESS
123 
124 #ifdef _WIN32
125  namespace detail
126  {
127  inline os_string build_command_line(std::vector<os_string> const &arguments)
128  {
129  os_string command_line;
130  for (auto a = begin(arguments); a != end(arguments); ++a)
131  {
132  if (a != begin(arguments))
133  {
134  command_line += L" ";
135  }
136  command_line += *a;
137  }
138  return command_line;
139  }
140  }
141 
142  inline error_or<async_process> launch_process(
143  async_process_parameters parameters,
144  native_file_descriptor standard_input,
145  native_file_descriptor standard_output,
146  native_file_descriptor standard_error,
147  std::vector<std::pair<os_char const *, os_char const *>> environment,
148  environment_inheritance inheritance)
149  {
150  std::vector<os_string> all_arguments;
151  all_arguments.emplace_back(L"\"" + parameters.executable.underlying().wstring() + L"\"");
152  all_arguments.insert(all_arguments.end(), parameters.arguments.begin(), parameters.arguments.end());
153  win32::winapi_string command_line = detail::build_command_line(all_arguments);
154 
155  SECURITY_ATTRIBUTES security{};
156  security.nLength = sizeof(security);
157  security.bInheritHandle = TRUE;
158 
159  STARTUPINFOW startup{};
160  startup.cb = sizeof(startup);
161  startup.dwFlags |= STARTF_USESTDHANDLES;
162  startup.hStdError = standard_error;
163  startup.hStdInput = standard_input;
164  startup.hStdOutput = standard_output;
165 
166  DWORD flags = CREATE_NO_WINDOW;
167  std::vector<WCHAR> environment_block;
168  if (!environment.empty() || (inheritance == environment_inheritance::no_inherit))
169  {
170  flags |= CREATE_UNICODE_ENVIRONMENT;
171 
172  //@environment will contain pointers into this block of memory:
173  std::vector<os_char> mutable_parent_variables;
174 
175  switch (inheritance)
176  {
178  {
179  os_char const * const parent_variables = GetEnvironmentStringsW();
180  os_char const *terminator_found = parent_variables;
181  while (*terminator_found != L'\0')
182  {
183  terminator_found += wcslen(terminator_found) + 1;
184  }
185  mutable_parent_variables.assign(parent_variables, terminator_found + 1);
186  for (auto i = mutable_parent_variables.begin(); *i != L'\0';)
187  {
188  auto assign = std::find(i, mutable_parent_variables.end(), L'=');
189  if (assign == i)
190  {
191  //There is a variable called "=C:" that is generated by Windows (1).
192  //The equality sign is not allowed in a variable name (2), but
193  //they do it anyway.
194  //(1) https://msdn.microsoft.com/en-us/library/windows/desktop/ms682425%28v=vs.85%29.aspx
195  //(2) https://msdn.microsoft.com/en-us/library/windows/desktop/ms682653%28v=vs.85%29.aspx
196  //The solution here: Treat any leading equality sign as a part of the name
197  //to keep this simply.
198  assign = std::find(assign + 1, mutable_parent_variables.end(), L'=');
199  }
200  *assign = L'\0';
201  os_char const * const key = &*i;
202  os_char const * const value = (&*assign) + 1;
203  environment.emplace_back(std::make_pair(key, value));
204  i = assign + 1 + wcslen(value) + 1;
205  }
206  break;
207  }
208 
210  {
211  break;
212  }
213  }
214 
215  typedef std::pair<os_char const *, os_char const *> environment_entry;
216  std::sort(environment.begin(), environment.end(), [](environment_entry const &left, environment_entry const &right)
217  {
218  return (wcscmp(left.first, right.first) < 0);
219  });
220  for (environment_entry const &entry : environment)
221  {
222  environment_block.insert(environment_block.end(), entry.first, entry.first + wcslen(entry.first));
223  environment_block.emplace_back('=');
224  std::size_t const zero_terminated = 1;
225  environment_block.insert(environment_block.end(), entry.second, entry.second + wcslen(entry.second) + zero_terminated);
226  }
227  if (environment_block.empty())
228  {
229  {
230  auto const key_and_assignment = Si::make_c_str_range(L"=C:=");
231  environment_block.insert(environment_block.end(), key_and_assignment.begin(), key_and_assignment.end());
232  }
233  std::size_t const begin_of_value = environment_block.size();
234  std::size_t estimated_value_size = 10;
235  for (;;)
236  {
237  std::size_t const size_including_buffer = std::max(environment_block.size(), begin_of_value + estimated_value_size);
238  environment_block.resize(size_including_buffer);
239  DWORD const buffer_size = static_cast<DWORD>(size_including_buffer - begin_of_value);
240  DWORD const actual_value_size = GetEnvironmentVariableW(
241  L"=C:",
242  environment_block.data() + begin_of_value,
243  buffer_size);
244  if (actual_value_size <= buffer_size)
245  {
246  environment_block.resize(begin_of_value + actual_value_size + 1);
247  break;
248  }
249  estimated_value_size = actual_value_size;
250  }
251  }
252  environment_block.emplace_back(L'\0');
253  }
254 
255  PROCESS_INFORMATION process{};
256  if (!CreateProcessW(
257  parameters.executable.c_str(), &command_line[0], &security, nullptr, TRUE,
258  flags, environment_block.empty() ? NULL : environment_block.data(),
259  parameters.current_path.c_str(), &startup, &process))
260  {
261  return get_last_error();
262  }
263 
264  win32::unique_handle thread_closer(process.hThread);
265  process_handle process_closer(process.hProcess);
266  return async_process(std::move(process_closer));
267  }
268 #else
269  inline error_or<async_process> launch_process(
270  async_process_parameters parameters,
271  native_file_descriptor standard_input,
272  native_file_descriptor standard_output,
273  native_file_descriptor standard_error,
274  std::vector<std::pair<os_char const *, os_char const *>> environment,
275  environment_inheritance inheritance)
276  {
277  auto executable = parameters.executable.underlying();
278  auto arguments = parameters.arguments;
279  std::vector<char *> argument_pointers;
280  argument_pointers.emplace_back(const_cast<char *>(executable.c_str()));
281  std::transform(begin(arguments), end(arguments), std::back_inserter(argument_pointers), [](Si::noexcept_string &arg)
282  {
283  return &arg[0];
284  });
285  argument_pointers.emplace_back(nullptr);
286 
287  pipe child_error = make_pipe().get();
288 
289  pid_t const forked = fork();
290  if (forked < 0)
291  {
292  return boost::system::error_code(errno, boost::system::system_category());
293  }
294 
295  //child
296  if (forked == 0)
297  {
298  auto const fail_with_error = [&child_error](int error) SILICIUM_NORETURN
299  {
300  ssize_t written = write(child_error.write.handle, &error, sizeof(error));
301  if (written != sizeof(error))
302  {
303  _exit(1);
304  }
305  child_error.write.close();
306  _exit(0);
307  };
308 
309  auto const fail = [fail_with_error]() SILICIUM_NORETURN
310  {
311  fail_with_error(errno);
312  };
313 
314  if (dup2(standard_output, STDOUT_FILENO) < 0)
315  {
316  fail();
317  }
318  if (dup2(standard_error, STDERR_FILENO) < 0)
319  {
320  fail();
321  }
322  if (dup2(standard_input, STDIN_FILENO) < 0)
323  {
324  fail();
325  }
326 
327  child_error.read.close();
328 
329  boost::system::error_code ec = detail::set_close_on_exec(child_error.write.handle);
330  if (ec)
331  {
332  fail_with_error(ec.value());
333  }
334 
335  boost::filesystem::current_path(parameters.current_path.to_boost_path(), ec);
336  if (ec)
337  {
338  fail_with_error(ec.value());
339  }
340 
341  //close inherited file descriptors
342  long max_fd = sysconf(_SC_OPEN_MAX);
343  for (int i = 3; i < max_fd; ++i)
344  {
345  if (i == child_error.write.handle)
346  {
347  continue;
348  }
349  close(i); //ignore errors because we will close many non-file-descriptors
350  }
351 
352  //kill the child when the parent exits
353  if (prctl(PR_SET_PDEATHSIG, SIGHUP) < 0)
354  {
355  fail();
356  }
357 
358  switch (inheritance)
359  {
361  for (auto const &var : environment)
362  {
363  int result = setenv(var.first, var.second, 1);
364  if (result != 0)
365  {
366  fail_with_error(errno);
367  }
368  }
369  execvp(parameters.executable.c_str(), argument_pointers.data());
370  fail();
371  break;
372 
374  {
375  std::vector<char *> environment_for_exec;
376  for (auto const &entry : environment)
377  {
378  auto const first_length = std::strlen(entry.first);
379  auto const second_length = std::strlen(entry.second);
380  char * const formatted = new char[first_length + 1 + second_length + 1];
381  std::copy_n(entry.first, first_length, formatted);
382  formatted[first_length] = '=';
383  std::copy_n(entry.second, second_length, formatted + first_length + 1);
384  formatted[first_length + 1 + second_length] = '\0';
385  environment_for_exec.emplace_back(formatted);
386  }
387  environment_for_exec.emplace_back(nullptr);
388  execvpe(parameters.executable.c_str(), argument_pointers.data(), environment_for_exec.data());
389  fail();
390  break;
391  }
392  }
393 
395  }
396 
397  //parent
398  else
399  {
400  return async_process(process_handle(forked), std::move(child_error.read));
401  }
402  }
403 #endif
404 
405 #endif
406 
407 #ifdef _WIN32
408  namespace win32
409  {
410  template <class ByteSink>
411  void copy_whole_pipe(HANDLE pipe_in, ByteSink &&sink_out)
412  {
413  auto buffered_out = make_buffering_sink(std::forward<ByteSink>(sink_out));
414  for (;;)
415  {
416  auto buffer = buffered_out.make_append_space((std::numeric_limits<DWORD>::max)());
417  DWORD read_bytes = 0;
418  DWORD available = 0;
419  DWORD left = 0;
420  BOOL const peeked = PeekNamedPipe(pipe_in, buffer.begin(), static_cast<DWORD>(buffer.size()), &read_bytes, &available, &left);
421  if (!peeked)
422  {
423  auto error = ::GetLastError();
424  if (error == ERROR_BROKEN_PIPE)
425  {
426  buffered_out.make_append_space(read_bytes);
427  buffered_out.flush_append_space();
428  break;
429  }
430  throw boost::system::system_error(error, boost::system::native_ecat);
431  }
432  if (available == 0)
433  {
434  auto buffer = buffered_out.make_append_space(1);
435  DWORD read_bytes = 0;
436  BOOL const read_result = ReadFile(pipe_in, buffer.begin(), static_cast<DWORD>(buffer.size()), &read_bytes, nullptr);
437  if (read_result)
438  {
439  buffered_out.flush_append_space();
440  continue;
441  }
442  else
443  {
444  auto error = ::GetLastError();
445  if (error == ERROR_BROKEN_PIPE)
446  {
447  buffered_out.make_append_space(read_bytes);
448  buffered_out.flush_append_space();
449  break;
450  }
451  throw boost::system::system_error(error, boost::system::native_ecat);
452  }
453  }
454  if (ReadFile(pipe_in, buffer.begin(), available, &read_bytes, nullptr))
455  {
456  assert(available == read_bytes);
457  buffered_out.make_append_space(read_bytes);
458  buffered_out.flush_append_space();
459  }
460  else
461  {
463  }
464  }
465  buffered_out.flush();
466  }
467  }
468 #endif
469  namespace experimental
470  {
471 #define SILICIUM_HAS_EXPERIMENTAL_READ_FROM_ANONYMOUS_PIPE SILICIUM_HAS_THREAD_OBSERVABLE
472 
473 #if SILICIUM_HAS_EXPERIMENTAL_READ_FROM_ANONYMOUS_PIPE
474  //TODO: find a more generic API for reading from a pipe portably
475  template <class CharSink>
476  void read_from_anonymous_pipe(boost::asio::io_service &io, CharSink &&destination, Si::file_handle file)
477  {
478 #ifdef _WIN32
479  auto copyable_file = Si::to_shared(std::move(file));
480  auto work = std::make_shared<boost::asio::io_service::work>(io);
481  Si::spawn_observable(
482  Si::asio::make_posting_observable(
483  io,
484  Si::make_thread_observable<Si::std_threading>([work, copyable_file, destination]()
485  {
486  Si::win32::copy_whole_pipe(copyable_file->handle, destination);
487  return Si::nothing();
488  })
489  )
490  );
491 #elif SILICIUM_HAS_SPAWN_COROUTINE
492  auto copyable_file = Si::to_shared(std::move(file));
493  Si::spawn_coroutine([&io, destination, copyable_file](Si::spawn_context yield)
494  {
495  Si::process_output output_reader(Si::make_unique<Si::process_output::stream>(io, copyable_file->handle));
496  copyable_file->release();
497  for (;;)
498  {
499  auto piece = yield.get_one(Si::ref(output_reader));
500  assert(piece);
501  if (piece->is_error())
502  {
503  break;
504  }
505  Si::memory_range data = piece->get();
506  if (data.empty())
507  {
508  break;
509  }
510  Si::append(destination, data);
511  }
512  });
513 #else
514  typedef typename std::decay<CharSink>::type clean_destination;
515  struct pipe_reader : std::enable_shared_from_this<pipe_reader>
516  {
517  pipe_reader(boost::asio::io_service &io, Si::file_handle file, clean_destination destination)
518  : m_output(Si::make_unique<Si::process_output::stream>(io, file.handle))
519  , m_destination(std::move(destination))
520  {
521  file.release();
522  }
523 
524  void start()
525  {
526  auto this_ = this->shared_from_this();
527  m_output.async_get_one(Si::make_function_observer([this_](optional<error_or<memory_range>> piece)
528  {
529  assert(piece);
530  if (piece->is_error())
531  {
532  return;
533  }
534  Si::memory_range data = piece->get();
535  if (data.empty())
536  {
537  return;
538  }
539  Si::append(this_->m_destination, data);
540  this_->start();
541  }));
542  }
543 
544  private:
545 
546  Si::process_output m_output;
547  clean_destination m_destination;
548  };
549  auto reader = std::make_shared<pipe_reader>(io, std::move(file), std::forward<CharSink>(destination));
550  reader->start();
551 #endif
552  }
553 #endif
554  }
555 }
556 
557 #endif
Definition: config.hpp:160
std::remove_reference< T >::type && move(T &&ref)
Definition: move.hpp:10
async_process(async_process &&other) BOOST_NOEXCEPT
Definition: async_process.hpp:71
Definition: absolute_path.hpp:21
native_file_descriptor handle
Definition: file_handle.hpp:12
~async_process() BOOST_NOEXCEPT
Definition: async_process.hpp:93
Definition: async_process.hpp:43
async_process() BOOST_NOEXCEPT
Definition: async_process.hpp:50
error_or< std::size_t > read(native_file_descriptor file, mutable_memory_range destination)
Definition: read_file.hpp:10
environment_inheritance
Definition: async_process.hpp:116
auto to_shared(T &&t) -> std::shared_ptr< typename std::decay< T >::type >
Definition: to_shared.hpp:10
std::vector< os_string > arguments
the values for the child's argv[1...]
Definition: async_process.hpp:37
Definition: absolute_path.hpp:352
error_or< int > wait_for_exit() BOOST_NOEXCEPT
Definition: async_process.hpp:97
Definition: absolute_path.hpp:19
Si::absolute_path current_path
must be an existing path, otherwise the child cannot launch properly
Definition: async_process.hpp:40
BOOST_CONSTEXPR Iterator const & end(iterator_range< Iterator > const &range)
Definition: iterator_range.hpp:136
auto make_c_str_range(C const *str) -> iterator_range< C const * >
Definition: memory_range.hpp:64
boost::container::string noexcept_string
Definition: noexcept_string.hpp:26
async_process(process_handle process, file_handle child_error) BOOST_NOEXCEPT
Definition: async_process.hpp:60
process_handle process
Definition: async_process.hpp:45
void throw_last_error()
Definition: throw_last_error.hpp:13
char os_char
Definition: os_string.hpp:19
native_file_descriptor release() BOOST_NOEXCEPT
Definition: file_handle.hpp:47
BOOST_CONSTEXPR Iterator const & begin(iterator_range< Iterator > const &range)
Definition: iterator_range.hpp:123
Definition: iterator_range.hpp:26
#define SILICIUM_UNREACHABLE()
Definition: config.hpp:44
file_handle child_error
Definition: async_process.hpp:47
async_process & operator=(async_process &&other) BOOST_NOEXCEPT
Definition: async_process.hpp:79
SILICIUM_USE_RESULT error_or< std::size_t > write(native_file_descriptor file, memory_range data)
Definition: write.hpp:13
BOOST_CONSTEXPR bool empty() const BOOST_NOEXCEPT
Definition: iterator_range.hpp:71
Si::absolute_path executable
Definition: async_process.hpp:34
#define SILICIUM_DELETED_FUNCTION(f)
Definition: config.hpp:111
#define SILICIUM_NORETURN
Definition: config.hpp:63
Definition: error_or.hpp:48
Definition: file_handle.hpp:10
SILICIUM_USE_RESULT boost::system::error_code get_last_error()
Definition: get_last_error.hpp:16
noexcept_string os_string
Definition: os_string.hpp:28
Definition: async_process.hpp:32