1 // Copyright (c) 2017 Klemens D. Morgenstern
2 //
3 // Distributed under the Boost Software License, Version 1.0. (See accompanying
4 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5
6
7 #ifndef BOOST_PROCESS_DETAIL_POSIX_SIGCHLD_SERVICE_HPP_
8 #define BOOST_PROCESS_DETAIL_POSIX_SIGCHLD_SERVICE_HPP_
9
10 #include <boost/asio/dispatch.hpp>
11 #include <boost/asio/post.hpp>
12 #include <boost/asio/signal_set.hpp>
13 #include <boost/asio/strand.hpp>
14 #include <boost/optional.hpp>
15 #include <signal.h>
16 #include <functional>
17 #include <sys/wait.h>
18
19 namespace boost { namespace process { namespace detail { namespace posix {
20
21 class sigchld_service : public boost::asio::detail::service_base<sigchld_service>
22 {
23 boost::asio::strand<boost::asio::io_context::executor_type> _strand{get_io_context().get_executor()};
24 boost::asio::signal_set _signal_set{get_io_context(), SIGCHLD};
25
26 std::vector<std::pair<::pid_t, std::function<void(int, std::error_code)>>> _receivers;
27 inline void _handle_signal(const boost::system::error_code & ec);
28 public:
sigchld_service(boost::asio::io_context & io_context)29 sigchld_service(boost::asio::io_context & io_context)
30 : boost::asio::detail::service_base<sigchld_service>(io_context)
31 {
32 }
33
34 template <typename SignalHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(SignalHandler,void (int,std::error_code))35 BOOST_ASIO_INITFN_RESULT_TYPE(SignalHandler,
36 void (int, std::error_code))
37 async_wait(::pid_t pid, SignalHandler && handler)
38 {
39 boost::asio::async_completion<
40 SignalHandler, void(boost::system::error_code)> init{handler};
41
42 auto & h = init.completion_handler;
43 boost::asio::dispatch(
44 _strand,
45 [this, pid, h]
46 {
47 //check if the child actually is running first
48 int status;
49 auto pid_res = ::waitpid(pid, &status, WNOHANG);
50 if (pid_res < 0)
51 h(-1, get_last_error());
52 else if ((pid_res == pid) && (WIFEXITED(status) || WIFSIGNALED(status)))
53 h(status, {}); //successfully exited already
54 else //still running
55 {
56 if (_receivers.empty())
57 _signal_set.async_wait(
58 [this](const boost::system::error_code &ec, int)
59 {
60 boost::asio::dispatch(_strand, [this, ec]{this->_handle_signal(ec);});
61 });
62 _receivers.emplace_back(pid, h);
63 }
64 });
65
66 return init.result.get();
67 }
shutdown()68 void shutdown() override
69 {
70 _receivers.clear();
71 }
72
cancel()73 void cancel()
74 {
75 _signal_set.cancel();
76 }
cancel(boost::system::error_code & ec)77 void cancel(boost::system::error_code & ec)
78 {
79 _signal_set.cancel(ec);
80 }
81 };
82
83
_handle_signal(const boost::system::error_code & ec)84 void sigchld_service::_handle_signal(const boost::system::error_code & ec)
85 {
86 std::error_code ec_{ec.value(), std::system_category()};
87
88 if (ec_)
89 {
90 for (auto & r : _receivers)
91 r.second(-1, ec_);
92 return;
93 }
94
95 for (auto & r : _receivers) {
96 int status;
97 int pid = ::waitpid(r.first, &status, WNOHANG);
98 if (pid < 0) {
99 // error (eg: the process no longer exists)
100 r.second(-1, get_last_error());
101 r.first = 0; // mark for deletion
102 } else if (pid == r.first) {
103 r.second(status, ec_);
104 r.first = 0; // mark for deletion
105 }
106 // otherwise the process is still around
107 }
108
109 _receivers.erase(std::remove_if(_receivers.begin(), _receivers.end(),
110 [](const std::pair<::pid_t, std::function<void(int, std::error_code)>> & p)
111 {
112 return p.first == 0;
113 }),
114 _receivers.end());
115
116 if (!_receivers.empty())
117 {
118 _signal_set.async_wait(
119 [this](const boost::system::error_code & ec, int)
120 {
121 boost::asio::post(_strand, [this, ec]{this->_handle_signal(ec);});
122 });
123 }
124 }
125
126
127 }
128 }
129 }
130 }
131
132
133
134
135 #endif
136