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