Kea  1.9.9-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 using namespace std;
24 namespace ph = std::placeholders;
25 
26 namespace isc {
27 namespace asiolink {
28 
30 struct ProcessState {
31 
33  ProcessState() : running_(true), status_(0) {
34  }
35 
37  bool running_;
38 
40  int status_;
41 };
42 
44 typedef boost::shared_ptr<ProcessState> ProcessStatePtr;
45 
48 typedef std::map<pid_t, ProcessStatePtr> ProcessStates;
49 
51 
54 typedef std::map<const ProcessSpawnImpl*, ProcessStates> ProcessCollection;
55 
69 class ProcessSpawnImpl : boost::noncopyable {
70 public:
71 
78  ProcessSpawnImpl(IOServicePtr io_service,
79  const std::string& executable,
80  const ProcessArgs& args,
81  const ProcessEnvVars& vars);
82 
85 
87  std::string getCommandLine() const;
88 
103  pid_t spawn(bool dismiss);
104 
109  bool isRunning(const pid_t pid) const;
110 
114  bool isAnyRunning() const;
115 
124  int getExitStatus(const pid_t pid) const;
125 
133  void clearState(const pid_t pid);
134 
135 private:
136 
150  char* allocateInternal(const std::string& src);
151 
159  static bool waitForProcess(int signum);
160 
162  static ProcessCollection process_collection_;
163 
165  std::string executable_;
166 
168  boost::shared_ptr<char*[]> args_;
169 
171  boost::shared_ptr<char*[]> vars_;
172 
174  typedef boost::shared_ptr<char[]> CStringPtr;
175 
177  std::vector<CStringPtr> storage_;
178 
180  bool store_;
181 
183  static std::mutex mutex_;
184 
186  IOSignalSetPtr io_signal_set_;
187 };
188 
189 ProcessCollection ProcessSpawnImpl::process_collection_;
190 std::mutex ProcessSpawnImpl::mutex_;
191 
192 ProcessSpawnImpl::ProcessSpawnImpl(IOServicePtr io_service,
193  const std::string& executable,
194  const ProcessArgs& args,
195  const ProcessEnvVars& vars)
196  : executable_(executable), args_(new char*[args.size() + 2]),
197  vars_(new char*[vars.size() + 1]), store_(false),
198  io_signal_set_(new IOSignalSet(io_service,
199  std::bind(&ProcessSpawnImpl::waitForProcess,
200  ph::_1))) {
201  struct stat st;
202 
203  if (stat(executable_.c_str(), &st)) {
204  isc_throw(ProcessSpawnError, "File not found: " << executable_);
205  }
206 
207  if (!(st.st_mode & S_IEXEC)) {
208  isc_throw(ProcessSpawnError, "File not executable: " << executable_);
209  }
210 
211  io_signal_set_->add(SIGCHLD);
212 
213  // Conversion of the arguments to the C-style array we start by setting
214  // all pointers within an array to NULL to indicate that they haven't
215  // been allocated yet.
216  memset(args_.get(), 0, (args.size() + 2) * sizeof(char*));
217  memset(vars_.get(), 0, (vars.size() + 1) * sizeof(char*));
218  // By convention, the first argument points to an executable name.
219  args_[0] = allocateInternal(executable_);
220  // Copy arguments to the array.
221  for (int i = 1; i <= args.size(); ++i) {
222  args_[i] = allocateInternal(args[i - 1]);
223  }
224  // Copy environment variables to the array.
225  for (int i = 0; i < vars.size(); ++i) {
226  vars_[i] = allocateInternal(vars[i]);
227  }
228 }
229 
231  io_signal_set_->remove(SIGCHLD);
232  if (store_) {
233  lock_guard<std::mutex> lk(mutex_);
234  process_collection_.erase(this);
235  }
236 }
237 
238 std::string
240  std::ostringstream s;
241  s << executable_;
242  // Start with index 1, because the first argument duplicates the
243  // path to the executable. Note, that even if there are no parameters
244  // the minimum size of the table is 2.
245  int i = 1;
246  while (args_[i] != NULL) {
247  s << " " << args_[i];
248  ++i;
249  }
250  return (s.str());
251 }
252 
253 pid_t
254 ProcessSpawnImpl::spawn(bool dismiss) {
255  lock_guard<std::mutex> lk(mutex_);
256  // Create the child
257  pid_t pid = fork();
258  if (pid < 0) {
259  isc_throw(ProcessSpawnError, "unable to fork current process");
260 
261  } else if (pid == 0) {
262  // Run the executable.
263  execve(executable_.c_str(), args_.get(), vars_.get());
264  // We may end up here if the execve failed, e.g. as a result
265  // of issue with permissions or invalid executable name.
266  _exit(EXIT_FAILURE);
267  }
268 
269  // We're in the parent process.
270  if (!dismiss) {
271  store_ = true;
272  process_collection_[this].insert(std::pair<pid_t, ProcessStatePtr>(pid, ProcessStatePtr(new ProcessState())));
273  }
274  return (pid);
275 }
276 
277 bool
278 ProcessSpawnImpl::isRunning(const pid_t pid) const {
279  lock_guard<std::mutex> lk(mutex_);
280  ProcessStates::const_iterator proc;
281  if (process_collection_.find(this) == process_collection_.end() ||
282  (proc = process_collection_[this].find(pid)) == process_collection_[this].end()) {
283  isc_throw(BadValue, "the process with the pid '" << pid
284  << "' hasn't been spawned and it status cannot be"
285  " returned");
286  }
287  return (proc->second->running_);
288 }
289 
290 bool
292  lock_guard<std::mutex> lk(mutex_);
293  if (process_collection_.find(this) != process_collection_.end()) {
294  for (auto const& proc : process_collection_[this]) {
295  if (proc.second->running_) {
296  return (true);
297  }
298  }
299  }
300  return (false);
301 }
302 
303 int
304 ProcessSpawnImpl::getExitStatus(const pid_t pid) const {
305  lock_guard<std::mutex> lk(mutex_);
306  ProcessStates::const_iterator proc;
307  if (process_collection_.find(this) == process_collection_.end() ||
308  (proc = process_collection_[this].find(pid)) == process_collection_[this].end()) {
309  isc_throw(InvalidOperation, "the process with the pid '" << pid
310  << "' hasn't been spawned and it status cannot be"
311  " returned");
312  }
313  return (WEXITSTATUS(proc->second->status_));
314 }
315 
316 char*
317 ProcessSpawnImpl::allocateInternal(const std::string& src) {
318  const size_t src_len = src.length();
319  storage_.push_back(CStringPtr(new char[src_len + 1]));
320  // Allocate the C-string with one byte more for the null termination.
321  char* dest = storage_[storage_.size() - 1].get();
322  // copy doesn't append the null at the end.
323  src.copy(dest, src_len);
324  // Append null on our own.
325  dest[src_len] = '\0';
326  return (dest);
327 }
328 
329 bool
330 ProcessSpawnImpl::waitForProcess(int) {
331  lock_guard<std::mutex> lk(mutex_);
332  for (;;) {
333  int status = 0;
334  pid_t pid = waitpid(-1, &status, WNOHANG);
335  if (pid <= 0) {
336  break;
337  }
338  for (auto const& instance : process_collection_) {
339  auto const& proc = instance.second.find(pid);
342  if (proc != instance.second.end()) {
343  // In this order please
344  proc->second->status_ = status;
345  proc->second->running_ = false;
346  }
347  }
348  }
349  return (true);
350 }
351 
352 void
354  if (isRunning(pid)) {
355  isc_throw(InvalidOperation, "unable to remove the status for the"
356  "process (pid: " << pid << ") which is still running");
357  }
358  lock_guard<std::mutex> lk(mutex_);
359  if (process_collection_.find(this) != process_collection_.end()) {
360  process_collection_[this].erase(pid);
361  }
362 }
363 
365  const std::string& executable,
366  const ProcessArgs& args,
367  const ProcessEnvVars& vars)
368  : impl_(new ProcessSpawnImpl(io_service, executable, args, vars)) {
369 }
370 
371 std::string
373  return (impl_->getCommandLine());
374 }
375 
376 pid_t
377 ProcessSpawn::spawn(bool dismiss) {
378  return (impl_->spawn(dismiss));
379 }
380 
381 bool
382 ProcessSpawn::isRunning(const pid_t pid) const {
383  return (impl_->isRunning(pid));
384 }
385 
386 bool
388  return (impl_->isAnyRunning());
389 }
390 
391 int
392 ProcessSpawn::getExitStatus(const pid_t pid) const {
393  return (impl_->getExitStatus(pid));
394 }
395 
396 void
397 ProcessSpawn::clearState(const pid_t pid) {
398  return (impl_->clearState(pid));
399 }
400 
401 } // namespace asiolink
402 } // 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-dhcp-ddns.
A generic exception that is thrown if a function is called in a prohibited way.