process-cpp  3.0.0
A simple convenience library for handling processes in C++11.
child_process.cpp
Go to the documentation of this file.
1 /*
2  * Copyright © 2013 Canonical Ltd.
3  *
4  * This program is free software: you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License version 3,
6  * as published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  *
16  * Authored by: Thomas Voß <thomas.voss@canonical.com>
17  */
18 
20 
21 #include <boost/iostreams/device/file_descriptor.hpp>
22 #include <boost/iostreams/stream.hpp>
23 
24 #include <atomic>
25 #include <fstream>
26 #include <mutex>
27 #include <unordered_map>
28 
29 #include <fcntl.h>
30 #include <poll.h>
31 #include <unistd.h>
32 
33 #include <sys/eventfd.h>
34 #include <sys/signalfd.h>
35 
36 namespace io = boost::iostreams;
37 
38 namespace
39 {
40 
41 struct DeathObserverImpl : public core::posix::ChildProcess::DeathObserver
42 {
43  DeathObserverImpl(const std::shared_ptr<core::posix::SignalTrap>& trap)
44  : on_sig_child_connection
45  {
46  trap->signal_raised().connect([this](core::posix::Signal signal)
47  {
48  switch (signal)
49  {
51  on_sig_child();
52  break;
53  default:
54  break;
55  }
56  })
57  }
58  {
59  if (!trap->has(core::posix::Signal::sig_chld))
60  throw std::logic_error(
61  "DeathObserver::DeathObserverImpl: Given SignalTrap"
62  " instance does not trap Signal::sig_chld.");
63  }
64 
65  bool add(const core::posix::ChildProcess& process) override
66  {
67  if (process.pid() == -1)
68  return false;
69 
70  std::lock_guard<std::mutex> lg(guard);
71 
72  bool added = false;
73  auto new_process = std::make_pair(process.pid(), process);
74  std::tie(std::ignore, added) = children.insert(new_process);
75 
76  if (added)
77  {
78  // The process may have died between it's instantiation and it
79  // being added to the children map. Check that it's still alive.
80  int status{-1};
81  if (::waitpid(process.pid(), &status, WNOHANG) != 0) // child no longer alive
82  {
83  // we missed the SIGCHLD signal so we must now manually
84  // inform our subscribers.
85  signals.child_died(new_process.second);
86  children.erase(new_process.first);
87  return false;
88  }
89  }
90 
91  return added;
92  }
93 
94  bool has(const core::posix::ChildProcess& process) const override
95  {
96  std::lock_guard<std::mutex> lg(guard);
97  return children.count(process.pid()) > 0;
98  }
99 
100  const core::Signal<core::posix::ChildProcess>& child_died() const override
101  {
102  return signals.child_died;
103  }
104 
105  void on_sig_child() override
106  {
107  pid_t pid{-1}; int status{-1};
108  while (true)
109  {
110  pid = ::waitpid(0, &status, WNOHANG);
111 
112  if (pid == -1)
113  {
114  if (errno == ECHILD)
115  {
116  break; // No more children
117  }
118  continue; // Ignore stray SIGCHLD signals
119  }
120  else if (pid == 0)
121  {
122  break; // No more children
123  }
124  else
125  {
126  std::lock_guard<std::mutex> lg(guard);
127  auto it = children.find(pid);
128 
129  if (it != children.end())
130  {
131  if (WIFSIGNALED(status) || WIFEXITED(status))
132  {
133  signals.child_died(it->second);
134  children.erase(it);
135  }
136  }
137  }
138  }
139  }
140 
141  mutable std::mutex guard;
142  std::unordered_map<pid_t, core::posix::ChildProcess> children;
143  core::ScopedConnection on_sig_child_connection;
144  struct
145  {
146  core::Signal<core::posix::ChildProcess> child_died;
147  } signals;
148 };
149 }
150 
151 std::unique_ptr<core::posix::ChildProcess::DeathObserver>
153  std::shared_ptr<core::posix::SignalTrap> trap)
154 {
155  static std::atomic<bool> has_been_created_once{false};
156 
157  if (has_been_created_once.exchange(true))
158  throw std::runtime_error
159  {
160  "DeathObserver::create_once_with_signal_trap: "
161  "Cannot create more than one instance."
162  };
163 
164  try
165  {
166  std::unique_ptr<core::posix::ChildProcess::DeathObserver> result
167  {
168  new DeathObserverImpl{trap}
169  };
170 
171  return result;
172  } catch(...)
173  {
174  // We make sure that a throwing c'tor does not impact our ability to
175  // retry creation of a DeathObserver instance.
176  has_been_created_once.store(false);
177 
178  std::rethrow_exception(std::current_exception());
179  }
180 
181  assert(false && "We should never reach here.");
182 
183  // Silence the compiler.
184  return std::unique_ptr<core::posix::ChildProcess::DeathObserver>{};
185 }
186 
187 namespace core
188 {
189 namespace posix
190 {
191 ChildProcess::Pipe ChildProcess::Pipe::invalid()
192 {
193  static Pipe p;
194  static std::once_flag flag;
195 
196  std::call_once(flag, [&]() { p.close_read_fd(); p.close_write_fd(); });
197 
198  return p;
199 }
200 
201 ChildProcess::Pipe::Pipe()
202 {
203  int rc = ::pipe(fds);
204 
205  if (rc == -1)
206  throw std::system_error(errno, std::system_category());
207 }
208 
209 ChildProcess::Pipe::Pipe(const ChildProcess::Pipe& rhs) : fds{-1, -1}
210 {
211  if (rhs.fds[0] != -1)
212  fds[0] = ::dup(rhs.fds[0]);
213 
214  if (rhs.fds[1] != -1)
215  fds[1] = ::dup(rhs.fds[1]);
216 }
217 
218 ChildProcess::Pipe::~Pipe()
219 {
220  if (fds[0] != -1)
221  ::close(fds[0]);
222  if (fds[1] != -1)
223  ::close(fds[1]);
224 }
225 
226 int ChildProcess::Pipe::read_fd() const
227 {
228  return fds[0];
229 }
230 
231 void ChildProcess::Pipe::close_read_fd()
232 {
233  if (fds[0] != -1)
234  {
235  ::close(fds[0]);
236  fds[0] = -1;
237  }
238 }
239 
240 int ChildProcess::Pipe::write_fd() const
241 {
242  return fds[1];
243 }
244 
245 void ChildProcess::Pipe::close_write_fd()
246 {
247  if (fds[1] != -1)
248  {
249  ::close(fds[1]);
250  fds[1] = -1;
251  }
252 }
253 
254 ChildProcess::Pipe& ChildProcess::Pipe::operator=(const ChildProcess::Pipe& rhs)
255 {
256  if (fds[0] != -1)
257  ::close(fds[0]);
258  if (fds[1] != -1)
259  ::close(fds[1]);
260 
261  if (rhs.fds[0] != -1)
262  fds[0] = ::dup(rhs.fds[0]);
263  else
264  fds[0] = -1;
265  if (rhs.fds[1] != -1)
266  fds[1] = ::dup(rhs.fds[1]);
267  else
268  fds[1] = -1;
269 
270  return *this;
271 }
272 
274 {
275  // stdin and stdout are always "relative" to the childprocess, i.e., we
276  // write to stdin of the child process and read from its stdout.
277  Private(pid_t pid,
278  const ChildProcess::Pipe& stderr,
279  const ChildProcess::Pipe& stdin,
280  const ChildProcess::Pipe& stdout)
281  : pipes{stderr, stdin, stdout},
282  serr(pipes.stderr.read_fd(), io::never_close_handle),
283  sin(pipes.stdin.write_fd(), io::never_close_handle),
284  sout(pipes.stdout.read_fd(), io::never_close_handle),
285  cerr(&serr),
286  cin(&sin),
287  cout(&sout),
288  original_parent_pid(::getpid()),
289  original_child_pid(pid)
290  {
291  }
292 
294  {
295  // Check if we are in the original parent process.
296  if (original_parent_pid == getpid())
297  {
298  // If so, check if we are considering a valid pid here.
299  // If so, we kill the original child.
300  if (original_child_pid != -1)
301  ::kill(original_child_pid, SIGKILL);
302  }
303  }
304 
305  struct
306  {
307  ChildProcess::Pipe stdin;
308  ChildProcess::Pipe stdout;
309  ChildProcess::Pipe stderr;
310  } pipes;
311  io::stream_buffer<io::file_descriptor_source> serr;
312  io::stream_buffer<io::file_descriptor_sink> sin;
313  io::stream_buffer<io::file_descriptor_source> sout;
314  std::istream cerr;
315  std::ostream cin;
316  std::istream cout;
317 
318  // We need to store the original parent pid as we might have been forked
319  // and with our automatic cleanup in place, it might happen that the d'tor
320  // is called from the child process.
323 };
324 
325 ChildProcess ChildProcess::invalid()
326 {
327  // We take the init process as child.
328  static const pid_t invalid_pid = 1;
329  return ChildProcess(invalid_pid, Pipe::invalid(), Pipe::invalid(), Pipe::invalid());
330 }
331 
332 ChildProcess::ChildProcess(pid_t pid,
333  const ChildProcess::Pipe& stdin_pipe,
334  const ChildProcess::Pipe& stdout_pipe,
335  const ChildProcess::Pipe& stderr_pipe)
336  : Process(pid),
337  d(new Private{pid, stdin_pipe, stdout_pipe, stderr_pipe})
338 {
339 }
340 
341 ChildProcess::~ChildProcess()
342 {
343 }
344 
345 wait::Result ChildProcess::wait_for(const wait::Flags& flags)
346 {
347  int status = -1;
348  pid_t result_pid = ::waitpid(pid(), std::addressof(status), static_cast<int>(flags));
349 
350  if (result_pid == -1)
351  throw std::system_error(errno, std::system_category());
352 
353  wait::Result result;
354 
355  if (result_pid == 0)
356  {
357  result.status = wait::Result::Status::no_state_change;
358  return result;
359  }
360 
361  if (WIFEXITED(status))
362  {
363  result.status = wait::Result::Status::exited;
364  result.detail.if_exited.status = static_cast<exit::Status>(WEXITSTATUS(status));
365  } else if (WIFSIGNALED(status))
366  {
367  result.status = wait::Result::Status::signaled;
368  result.detail.if_signaled.signal = static_cast<Signal>(WTERMSIG(status));
369  result.detail.if_signaled.core_dumped = WCOREDUMP(status);
370  } else if (WIFSTOPPED(status))
371  {
372  result.status = wait::Result::Status::stopped;
373  result.detail.if_stopped.signal = static_cast<Signal>(WSTOPSIG(status));
374  } else if (WIFCONTINUED(status))
375  {
376  result.status = wait::Result::Status::continued;
377  }
378 
379  return result;
380 }
381 
382 std::istream& ChildProcess::cerr()
383 {
384  return d->cerr;
385 }
386 
387 std::ostream& ChildProcess::cin()
388 {
389  return d->cin;
390 }
391 
392 std::istream& ChildProcess::cout()
393 {
394  return d->cout;
395 }
396 }
397 }
io::stream_buffer< io::file_descriptor_sink > sin
virtual bool add(const ChildProcess &child)=0
add adds the specified child to the list of observed child processes.
Status
The Status enum wrap&#39;s the posix exit status.
Definition: exit.h:33
Flags
Flags enumerates different behavior when waiting for a child process to change state.
Definition: wait.h:42
struct core::posix::wait::Result::@4::@7 if_stopped
The Process class models a process and possible operations on it.
Definition: process.h:44
union core::posix::wait::Result::@4 detail
Union of result-specific details.
Private(pid_t pid, const ChildProcess::Pipe &stderr, const ChildProcess::Pipe &stdin, const ChildProcess::Pipe &stdout)
virtual void on_sig_child()=0
Checks and reaps all child processes registered with the observer instance.
struct core::posix::wait::Result::@4::@6 if_signaled
static std::unique_ptr< DeathObserver > create_once_with_signal_trap(std::shared_ptr< SignalTrap > trap)
Creates the unique instance of class DeathObserver.
enum core::posix::wait::Result::Status status
CORE_POSIX_DLL_PUBLIC std::istream & cin() noexcept(true)
Access this process&#39;s stdin.
The Result struct encapsulates the result of waiting for a process state change.
Definition: wait.h:54
Signal
The Signal enum collects the most common POSIX signals.
Definition: signal.h:38
The DeathObserver class observes child process&#39; states and emits a signal when a monitored child has ...
Definition: child_process.h:56
CORE_POSIX_DLL_PUBLIC std::ostream & cerr() noexcept(true)
Access this process&#39;s stderr.
io::stream_buffer< io::file_descriptor_source > serr
virtual pid_t pid() const
Query the pid of the process.
Definition: process.cpp:59
struct core::posix::wait::Result::@4::@5 if_exited
The Process class models a child process of this process.
Definition: child_process.h:43
virtual bool has(const ChildProcess &child) const =0
has checks whether the specified child is observed.
CORE_POSIX_DLL_PUBLIC std::ostream & cout() noexcept(true)
Access this process&#39;s stdout.
io::stream_buffer< io::file_descriptor_source > sout
virtual const core::Signal< ChildProcess > & child_died() const =0
child_died is emitted whenever an observed child ceases to exist.