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                     {
52                         auto ec = get_last_error();
53                         boost::asio::post(
54                                 _strand,
55                                 [pid_res, ec, h]
56                                 {
57                                     h(pid_res, ec);
58                                 });
59                     }
60                     else if ((pid_res == pid) && (WIFEXITED(status) || WIFSIGNALED(status)))
61                         boost::asio::post(
62                                 _strand,
63                                 [status, h]
64                                 {
65                                     h(status, {}); //successfully exited already
66                                 });
67                     else //still running
68                     {
69                         if (_receivers.empty())
70                             _signal_set.async_wait(
71                                     [this](const boost::system::error_code &ec, int)
72                                     {
73                                         boost::asio::dispatch(_strand, [this, ec]{this->_handle_signal(ec);});
74                                     });
75                         _receivers.emplace_back(pid, h);
76                     }
77                 });
78 
79         return init.result.get();
80     }
shutdown()81     void shutdown() override
82     {
83         _receivers.clear();
84     }
85 
cancel()86     void cancel()
87     {
88         _signal_set.cancel();
89     }
cancel(boost::system::error_code & ec)90     void cancel(boost::system::error_code & ec)
91     {
92         _signal_set.cancel(ec);
93     }
94 };
95 
96 
_handle_signal(const boost::system::error_code & ec)97 void sigchld_service::_handle_signal(const boost::system::error_code & ec)
98 {
99     std::error_code ec_{ec.value(), std::system_category()};
100 
101     if (ec_)
102     {
103         for (auto & r : _receivers)
104             r.second(-1, ec_);
105         return;
106     }
107 
108     for (auto & r : _receivers) {
109         int status;
110         int pid = ::waitpid(r.first, &status, WNOHANG);
111         if (pid < 0) {
112             // error (eg: the process no longer exists)
113             r.second(-1, get_last_error());
114             r.first = 0; // mark for deletion
115         } else if (pid == r.first) {
116             r.second(status, ec_);
117             r.first = 0; // mark for deletion
118         }
119         // otherwise the process is still around
120     }
121 
122     _receivers.erase(std::remove_if(_receivers.begin(), _receivers.end(),
123             [](const std::pair<::pid_t, std::function<void(int, std::error_code)>> & p)
124             {
125                 return p.first == 0;
126             }),
127             _receivers.end());
128 
129     if (!_receivers.empty())
130     {
131         _signal_set.async_wait(
132             [this](const boost::system::error_code & ec, int)
133             {
134                 boost::asio::post(_strand, [this, ec]{this->_handle_signal(ec);});
135             });
136     }
137 }
138 
139 
140 }
141 }
142 }
143 }
144 
145 
146 
147 
148 #endif
149