Kea  2.1.7-git
process_spawn.cc
Go to the documentation of this file.
1 // Copyright (C) 2015-2021 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 <asiolink/process_spawn.h>
11 #include <exceptions/exceptions.h>
12 #include <cstring>
13 #include <functional>
14 #include <map>
15 #include <mutex>
16 #include <signal.h>
17 #include <stdlib.h>
18 #include <errno.h>
19 #include <unistd.h>
20 #include <sys/stat.h>
21 #include <sys/wait.h>
22 
23 #include <boost/make_shared.hpp>
24 
25 using namespace std;
26 namespace ph = std::placeholders;
27 
28 namespace isc {
29 namespace asiolink {
30 
32 struct ProcessState {
33 
35  ProcessState() : running_(true), status_(0) {
36  }
37 
39  bool running_;
40 
42  int status_;
43 };
44 
46 typedef boost::shared_ptr<ProcessState> ProcessStatePtr;
47 
50 typedef std::map<pid_t, ProcessStatePtr> ProcessStates;
51 
53 
56 typedef std::map<const ProcessSpawnImpl*, ProcessStates> ProcessCollection;
57 
71 class ProcessSpawnImpl : boost::noncopyable {
72 public:
73 
80  ProcessSpawnImpl(IOServicePtr io_service,
81  const std::string& executable,
82  const ProcessArgs& args,
83  const ProcessEnvVars& vars);
84 
87 
89  std::string getCommandLine() const;
90 
105  pid_t spawn(bool dismiss);
106 
111  bool isRunning(const pid_t pid) const;
112 
116  bool isAnyRunning() const;
117 
126  int getExitStatus(const pid_t pid) const;
127 
135  void clearState(const pid_t pid);
136 
137 private:
138 
144  class IOSignalSetInitializer {
145  private:
146 
150  IOSignalSetInitializer(IOServicePtr io_service) {
151  if (!io_service) {
152  isc_throw(ProcessSpawnError, "NULL IOService instance");
153  }
154  io_signal_set_ = boost::make_shared<IOSignalSet>(io_service,
155  std::bind(&ProcessSpawnImpl::waitForProcess, ph::_1));
156  io_signal_set_->add(SIGCHLD);
157  }
158 
160  ~IOSignalSetInitializer() {
161  io_signal_set_->remove(SIGCHLD);
162  }
163 
164  public:
165 
171  static void initIOSignalSet(IOServicePtr io_service);
172 
173  private:
174 
176  IOSignalSetPtr io_signal_set_;
177  };
178 
192  char* allocateInternal(const std::string& src);
193 
201  static bool waitForProcess(int signum);
202 
204  static ProcessCollection process_collection_;
205 
207  std::string executable_;
208 
210  boost::shared_ptr<char*[]> args_;
211 
213  boost::shared_ptr<char*[]> vars_;
214 
216  typedef boost::shared_ptr<char[]> CStringPtr;
217 
219  std::vector<CStringPtr> storage_;
220 
222  bool store_;
223 
225  static std::mutex mutex_;
226 
228  IOServicePtr io_service_;
229 };
230 
231 ProcessCollection ProcessSpawnImpl::process_collection_;
232 std::mutex ProcessSpawnImpl::mutex_;
233 
234 void ProcessSpawnImpl::IOSignalSetInitializer::initIOSignalSet(IOServicePtr io_service) {
235  static IOSignalSetInitializer init(io_service);
236 }
237 
238 ProcessSpawnImpl::ProcessSpawnImpl(IOServicePtr io_service,
239  const std::string& executable,
240  const ProcessArgs& args,
241  const ProcessEnvVars& vars)
242  : executable_(executable), args_(new char*[args.size() + 2]),
243  vars_(new char*[vars.size() + 1]), store_(false), io_service_(io_service) {
244 
245  struct stat st;
246 
247  if (stat(executable_.c_str(), &st)) {
248  isc_throw(ProcessSpawnError, "File not found: " << executable_);
249  }
250 
251  if (!(st.st_mode & S_IEXEC)) {
252  isc_throw(ProcessSpawnError, "File not executable: " << executable_);
253  }
254 
255  // Conversion of the arguments to the C-style array we start by setting
256  // all pointers within an array to NULL to indicate that they haven't
257  // been allocated yet.
258  memset(args_.get(), 0, (args.size() + 2) * sizeof(char*));
259  memset(vars_.get(), 0, (vars.size() + 1) * sizeof(char*));
260  // By convention, the first argument points to an executable name.
261  args_[0] = allocateInternal(executable_);
262  // Copy arguments to the array.
263  for (int i = 1; i <= args.size(); ++i) {
264  args_[i] = allocateInternal(args[i - 1]);
265  }
266  // Copy environment variables to the array.
267  for (int i = 0; i < vars.size(); ++i) {
268  vars_[i] = allocateInternal(vars[i]);
269  }
270 }
271 
273  if (store_) {
274  lock_guard<std::mutex> lk(mutex_);
275  process_collection_.erase(this);
276  }
277 }
278 
279 std::string
281  std::ostringstream s;
282  s << executable_;
283  // Start with index 1, because the first argument duplicates the
284  // path to the executable. Note, that even if there are no parameters
285  // the minimum size of the table is 2.
286  int i = 1;
287  while (args_[i] != NULL) {
288  s << " " << args_[i];
289  ++i;
290  }
291  return (s.str());
292 }
293 
294 pid_t
295 ProcessSpawnImpl::spawn(bool dismiss) {
296  lock_guard<std::mutex> lk(mutex_);
297  ProcessSpawnImpl::IOSignalSetInitializer::initIOSignalSet(io_service_);
298  // Create the child
299  pid_t pid = fork();
300  if (pid < 0) {
301  isc_throw(ProcessSpawnError, "unable to fork current process");
302 
303  } else if (pid == 0) {
304  // Reset masked signals for the child process.
305  sigset_t sset;
306  sigemptyset(&sset);
307  pthread_sigmask(SIG_SETMASK, &sset, 0);
308  // Run the executable.
309  execve(executable_.c_str(), args_.get(), vars_.get());
310  // We may end up here if the execve failed, e.g. as a result
311  // of issue with permissions or invalid executable name.
312  _exit(EXIT_FAILURE);
313  }
314 
315  // We're in the parent process.
316  if (!dismiss) {
317  store_ = true;
318  process_collection_[this].insert(std::pair<pid_t, ProcessStatePtr>(pid, ProcessStatePtr(new ProcessState())));
319  }
320  return (pid);
321 }
322 
323 bool
324 ProcessSpawnImpl::isRunning(const pid_t pid) const {
325  lock_guard<std::mutex> lk(mutex_);
326  ProcessStates::const_iterator proc;
327  if (process_collection_.find(this) == process_collection_.end() ||
328  (proc = process_collection_[this].find(pid)) == process_collection_[this].end()) {
329  isc_throw(BadValue, "the process with the pid '" << pid
330  << "' hasn't been spawned and it status cannot be"
331  " returned");
332  }
333  return (proc->second->running_);
334 }
335 
336 bool
338  lock_guard<std::mutex> lk(mutex_);
339  if (process_collection_.find(this) != process_collection_.end()) {
340  for (auto const& proc : process_collection_[this]) {
341  if (proc.second->running_) {
342  return (true);
343  }
344  }
345  }
346  return (false);
347 }
348 
349 int
350 ProcessSpawnImpl::getExitStatus(const pid_t pid) const {
351  lock_guard<std::mutex> lk(mutex_);
352  ProcessStates::const_iterator proc;
353  if (process_collection_.find(this) == process_collection_.end() ||
354  (proc = process_collection_[this].find(pid)) == process_collection_[this].end()) {
355  isc_throw(InvalidOperation, "the process with the pid '" << pid
356  << "' hasn't been spawned and it status cannot be"
357  " returned");
358  }
359  return (WEXITSTATUS(proc->second->status_));
360 }
361 
362 char*
363 ProcessSpawnImpl::allocateInternal(const std::string& src) {
364  const size_t src_len = src.length();
365  storage_.push_back(CStringPtr(new char[src_len + 1]));
366  // Allocate the C-string with one byte more for the null termination.
367  char* dest = storage_[storage_.size() - 1].get();
368  // copy doesn't append the null at the end.
369  src.copy(dest, src_len);
370  // Append null on our own.
371  dest[src_len] = '\0';
372  return (dest);
373 }
374 
375 bool
376 ProcessSpawnImpl::waitForProcess(int) {
377  lock_guard<std::mutex> lk(mutex_);
378  for (;;) {
379  int status = 0;
380  pid_t pid = waitpid(-1, &status, WNOHANG);
381  if (pid <= 0) {
382  break;
383  }
384  for (auto const& instance : process_collection_) {
385  auto const& proc = instance.second.find(pid);
388  if (proc != instance.second.end()) {
389  // In this order please
390  proc->second->status_ = status;
391  proc->second->running_ = false;
392  }
393  }
394  }
395  return (true);
396 }
397 
398 void
400  if (isRunning(pid)) {
401  isc_throw(InvalidOperation, "unable to remove the status for the"
402  "process (pid: " << pid << ") which is still running");
403  }
404  lock_guard<std::mutex> lk(mutex_);
405  if (process_collection_.find(this) != process_collection_.end()) {
406  process_collection_[this].erase(pid);
407  }
408 }
409 
411  const std::string& executable,
412  const ProcessArgs& args,
413  const ProcessEnvVars& vars)
414  : impl_(new ProcessSpawnImpl(io_service, executable, args, vars)) {
415 }
416 
417 std::string
419  return (impl_->getCommandLine());
420 }
421 
422 pid_t
423 ProcessSpawn::spawn(bool dismiss) {
424  return (impl_->spawn(dismiss));
425 }
426 
427 bool
428 ProcessSpawn::isRunning(const pid_t pid) const {
429  return (impl_->isRunning(pid));
430 }
431 
432 bool
434  return (impl_->isAnyRunning());
435 }
436 
437 int
438 ProcessSpawn::getExitStatus(const pid_t pid) const {
439  return (impl_->getExitStatus(pid));
440 }
441 
442 void
443 ProcessSpawn::clearState(const pid_t pid) {
444  return (impl_->clearState(pid));
445 }
446 
447 } // namespace asiolink
448 } // namespace isc
STL namespace.
#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...
Defines the logger used by the top-level component of kea-lfc.
A generic exception that is thrown if a function is called in a prohibited way.