Kea  1.5.0
process_spawn.cc
Go to the documentation of this file.
1 // Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #include <config.h>
8 
10 #include <util/process_spawn.h>
11 #include <util/signal_set.h>
12 #include <boost/bind.hpp>
13 #include <map>
14 #include <signal.h>
15 #include <stdlib.h>
16 #include <errno.h>
17 #include <unistd.h>
18 #include <sys/wait.h>
19 
20 namespace isc {
21 namespace util {
22 
24 struct ProcessState {
25 
27  ProcessState() : running_(true), status_(0) {
28  }
29 
31  bool running_;
32 
34  int status_;
35 };
36 
37 typedef std::map<pid_t, ProcessState> ProcessStates;
38 
52 class ProcessSpawnImpl : boost::noncopyable {
53 public:
54 
59  ProcessSpawnImpl(const std::string& executable,
60  const ProcessArgs& args);
61 
64 
66  std::string getCommandLine() const;
67 
80  pid_t spawn();
81 
86  bool isRunning(const pid_t pid) const;
87 
91  bool isAnyRunning() const;
92 
101  int getExitStatus(const pid_t pid) const;
102 
110  void clearState(const pid_t pid);
111 
112 private:
113 
125  char* allocateArg(const std::string& src) const;
126 
134  bool waitForProcess(int signum);
135 
137  SignalSetPtr signals_;
138 
140  ProcessStates process_state_;
141 
143  std::string executable_;
144 
146  char** args_;
147 };
148 
149 ProcessSpawnImpl::ProcessSpawnImpl(const std::string& executable,
150  const ProcessArgs& args)
151  : signals_(new SignalSet(SIGCHLD)), process_state_(),
152  executable_(executable), args_(new char*[args.size() + 2]) {
153  // Set the handler which is invoked immediately when the signal
154  // is received.
155  signals_->setOnReceiptHandler(boost::bind(&ProcessSpawnImpl::waitForProcess,
156  this, _1));
157  // Conversion of the arguments to the C-style array we start by setting
158  // all pointers within an array to NULL to indicate that they haven't
159  // been allocated yet.
160  memset(args_, 0, (args.size() + 2) * sizeof(char*));
161  // By convention, the first argument points to an executable name.
162  args_[0] = allocateArg(executable_);
163  // Copy arguments to the array.
164  for (int i = 1; i <= args.size(); ++i) {
165  args_[i] = allocateArg(args[i-1]);
166  }
167 }
168 
170  int i = 0;
171  // Deallocate strings in the array of arguments.
172  while (args_[i] != NULL) {
173  delete[] args_[i];
174  ++i;
175  }
176  // Deallocate the array.
177  delete[] args_;
178 }
179 
180 std::string
182  std::ostringstream s;
183  s << executable_;
184  // Start with index 1, because the first argument duplicates the
185  // path to the executable. Note, that even if there are no parameters
186  // the minimum size of the table is 2.
187  int i = 1;
188  while (args_[i] != NULL) {
189  s << " " << args_[i];
190  ++i;
191  }
192  return (s.str());
193 }
194 
195 pid_t
197  // Protect us against SIGCHLD signals
198  sigset_t sset;
199  sigset_t osset;
200  sigemptyset(&sset);
201  sigaddset(&sset, SIGCHLD);
202  pthread_sigmask(SIG_BLOCK, &sset, &osset);
203  if (sigismember(&osset, SIGCHLD)) {
205  "spawn() called from a thread where SIGCHLD is blocked");
206  }
207 
208  // Create the child
209  pid_t pid = fork();
210  if (pid < 0) {
211  isc_throw(ProcessSpawnError, "unable to fork current process");
212 
213  } else if (pid == 0) {
214  // We're in the child process.
215  sigprocmask(SIG_SETMASK, &osset, 0);
216  // Run the executable.
217  if (execvp(executable_.c_str(), args_) != 0) {
218  // We may end up here if the execvp failed, e.g. as a result
219  // of issue with permissions or invalid executable name.
220  _exit(EXIT_FAILURE);
221  }
222  // Process finished, exit the child process.
223  _exit(EXIT_SUCCESS);
224  }
225 
226  // We're in the parent process.
227  try {
228  process_state_.insert(
229  std::pair<pid_t, ProcessState>(pid, ProcessState()));
230  } catch(...) {
231  pthread_sigmask(SIG_SETMASK, &osset, 0);
232  throw;
233  }
234  pthread_sigmask(SIG_SETMASK, &osset, 0);
235  return (pid);
236 }
237 
238 bool
239 ProcessSpawnImpl::isRunning(const pid_t pid) const {
240  ProcessStates::const_iterator proc = process_state_.find(pid);
241  if (proc == process_state_.end()) {
242  isc_throw(BadValue, "the process with the pid '" << pid
243  << "' hasn't been spawned and it status cannot be"
244  " returned");
245  }
246  return (proc->second.running_);
247 }
248 
249 bool
251  for (ProcessStates::const_iterator proc = process_state_.begin();
252  proc != process_state_.end(); ++proc) {
253  if (proc->second.running_) {
254  return (true);
255  }
256  }
257  return (false);
258 }
259 
260 int
261 ProcessSpawnImpl::getExitStatus(const pid_t pid) const {
262  ProcessStates::const_iterator proc = process_state_.find(pid);
263  if (proc == process_state_.end()) {
264  isc_throw(InvalidOperation, "the process with the pid '" << pid
265  << "' hasn't been spawned and it status cannot be"
266  " returned");
267  }
268  return (WEXITSTATUS(proc->second.status_));
269 }
270 
271 char*
272 ProcessSpawnImpl::allocateArg(const std::string& src) const {
273  const size_t src_len = src.length();
274  // Allocate the C-string with one byte more for the null termination.
275  char* dest = new char[src_len + 1];
276  // copy doesn't append the null at the end.
277  src.copy(dest, src_len);
278  // Append null on our own.
279  dest[src_len] = '\0';
280  return (dest);
281 }
282 
283 bool
284 ProcessSpawnImpl::waitForProcess(int signum) {
285  // We're only interested in SIGCHLD.
286  if (signum != SIGCHLD) {
287  return (false);
288  }
289 
290  // Need to store current value of errno, so we could restore it
291  // after this signal handler does his work.
292  int errno_value = errno;
293 
294  for (;;) {
295  int status = 0;
296  pid_t pid = waitpid(-1, &status, WNOHANG);
297  if (pid <= 0) {
298  break;
299  }
300  ProcessStates::iterator proc = process_state_.find(pid);
303  if (proc != process_state_.end()) {
304  // In this order please
305  proc->second.status_ = status;
306  proc->second.running_ = false;
307  }
308  }
309 
310  // Need to restore previous value of errno. We called waitpid(),
311  // which likely indicated its result by setting errno to ECHILD.
312  // This is a signal handler, which can be called while virtually
313  // any other code being run. If we're unlucky, we could receive a
314  // signal when running a code that is about to check errno. As a
315  // result the code would detect errno=ECHILD in places which are
316  // completely unrelated to child or processes in general.
317  errno = errno_value;
318 
319  return (true);
320 }
321 
322 void
324  if (isRunning(pid)) {
325  isc_throw(InvalidOperation, "unable to remove the status for the"
326  "process (pid: " << pid << ") which is still running");
327  }
328  process_state_.erase(pid);
329 }
330 
331 ProcessSpawn::ProcessSpawn(const std::string& executable,
332  const ProcessArgs& args)
333  : impl_(new ProcessSpawnImpl(executable, args)) {
334 }
335 
337  delete impl_;
338 }
339 
340 std::string
342  return (impl_->getCommandLine());
343 }
344 
345 pid_t
347  return (impl_->spawn());
348 }
349 
350 bool
351 ProcessSpawn::isRunning(const pid_t pid) const {
352  return (impl_->isRunning(pid));
353 }
354 
355 bool
357  return (impl_->isAnyRunning());
358 }
359 
360 int
361 ProcessSpawn::getExitStatus(const pid_t pid) const {
362  return (impl_->getExitStatus(pid));
363 }
364 
365 void
366 ProcessSpawn::clearState(const pid_t pid) {
367  return (impl_->clearState(pid));
368 }
369 
370 }
371 }
boost::shared_ptr< SignalSet > SignalSetPtr
Pointer to the isc::util::SignalSet.
Definition: signal_set.h:43
bool running_
true until the exit status is collected
ProcessSpawnImpl(const std::string &executable, const ProcessArgs &args)
Constructor.
std::string getCommandLine() const
Returns full command line, including arguments, for the process.
Exception thrown when error occurs during spawning a process.
Definition: process_spawn.h:20
bool isRunning(const pid_t pid) const
Checks if the process is still running.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
pid_t spawn()
Spawn the new process.
bool isRunning(const pid_t pid) const
Checks if the process is still running.
bool isAnyRunning() const
Checks if any of the spawned processes is still running.
void clearState(const pid_t pid)
Removes the status of the process with a specified PID.
std::vector< std::string > ProcessArgs
Type of the container holding arguments of the executable being run as a background process.
Definition: process_spawn.h:28
int status_
0 or the exit status
Implementation of the ProcessSpawn class.
Represents a collection of signals handled in a customized way.
Definition: signal_set.h:87
Defines the logger used by the top-level component of kea-dhcp-ddns.
A generic exception that is thrown if a function is called in a prohibited way.
ProcessState()
Constructor.
int getExitStatus(const pid_t pid) const
Returns exit status of the process.
pid_t spawn()
Spawn the new process.
std::map< pid_t, ProcessState > ProcessStates
ProcessSpawn(const std::string &executable, const ProcessArgs &args=ProcessArgs())
Constructor.
bool isAnyRunning() const
Checks if any of the spawned processes is still running.
Type for process state.
void clearState(const pid_t pid)
Removes the status of the process with a specified PID.
int getExitStatus(const pid_t pid) const
Returns exit status of the process.
std::string getCommandLine() const
Returns full command line, including arguments, for the process.