Kea 2.7.5
process_spawn.cc
Go to the documentation of this file.
1// Copyright (C) 2015-2024 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
12
13#include <functional>
14#include <map>
15#include <mutex>
16
17#include <cstring>
18#include <signal.h>
19#include <stdlib.h>
20#include <errno.h>
21#include <unistd.h>
22#include <sys/stat.h>
23#include <sys/wait.h>
24
25#include <boost/make_shared.hpp>
26
27using namespace std;
28namespace ph = std::placeholders;
29
30extern char **environ;
31
32namespace isc {
33namespace asiolink {
34
37
40 }
41
44
47};
48
50typedef boost::shared_ptr<ProcessState> ProcessStatePtr;
51
54typedef std::map<pid_t, ProcessStatePtr> ProcessStates;
55
56class processSpawnImpl;
57
60typedef std::map<const ProcessSpawnImpl*, ProcessStates> ProcessCollection;
61
75class ProcessSpawnImpl : boost::noncopyable {
76public:
77
87 const std::string& executable,
88 const ProcessArgs& args,
89 const ProcessEnvVars& vars,
90 const bool inherit_env);
91
94
96 std::string getCommandLine() const;
97
112 pid_t spawn(bool dismiss);
113
118 bool isRunning(const pid_t pid) const;
119
123 bool isAnyRunning() const;
124
133 int getExitStatus(const pid_t pid) const;
134
142 void clearState(const pid_t pid);
143
144private:
145
151 class IOSignalSetInitializer {
152 private:
153
157 IOSignalSetInitializer(IOServicePtr io_service) : io_service_(io_service) {
158 if (!io_service) {
159 isc_throw(ProcessSpawnError, "NULL IOService instance");
160 }
161 io_signal_set_ =
162 boost::make_shared<IOSignalSet>(io_service,
163 std::bind(&ProcessSpawnImpl::waitForProcess, ph::_1,
164 /* pid = */ -1, /* sync = */ false));
165 io_signal_set_->add(SIGCHLD);
166 }
167
169 ~IOSignalSetInitializer() {
170 io_signal_set_->remove(SIGCHLD);
171 io_signal_set_.reset();
172 io_service_->stopAndPoll();
173 }
174
175 public:
176
182 static void initIOSignalSet(IOServicePtr io_service);
183
184 private:
185
187 asiolink::IOServicePtr io_service_;
188
190 IOSignalSetPtr io_signal_set_;
191 };
192
206 char* allocateInternal(const std::string& src);
207
216 static void waitForProcess(int signum, pid_t const wpid = -1,
217 bool const sync = false);
218
220 static ProcessCollection process_collection_;
221
225
227 std::string executable_;
228
230 boost::shared_ptr<char*[]> args_;
231
233 boost::shared_ptr<char*[]> vars_;
234
236 typedef boost::shared_ptr<char[]> CStringPtr;
237
239 std::vector<CStringPtr> storage_;
240
242 bool store_;
243
245 static std::mutex mutex_;
246};
247
248ProcessCollection ProcessSpawnImpl::process_collection_;
249std::mutex ProcessSpawnImpl::mutex_;
250
251void ProcessSpawnImpl::IOSignalSetInitializer::initIOSignalSet(IOServicePtr io_service) {
252 static IOSignalSetInitializer init(io_service);
253}
254
256 const std::string& executable,
257 const ProcessArgs& args,
258 const ProcessEnvVars& vars,
259 const bool inherit_env)
260 : mode_(mode), executable_(executable), args_(new char*[args.size() + 2]),
261 store_(false) {
262
263 // Size of vars except the trailing null
264 size_t vars_size;
265 if (inherit_env) {
266 size_t i(0);
267 while (environ[i]) {
268 ++i;
269 }
270 vars_size = i + vars.size();
271 } else {
272 vars_size = vars.size();
273 }
274
275 vars_ = boost::shared_ptr<char*[]>(new char*[vars_size + 1]);
276
277 struct stat st;
278
279 if (stat(executable_.c_str(), &st)) {
280 isc_throw(ProcessSpawnError, "File not found: " << executable_);
281 }
282
283 if (!(st.st_mode & S_IEXEC)) {
284 isc_throw(ProcessSpawnError, "File not executable: " << executable_);
285 }
286
287 // Conversion of the arguments to the C-style array we start by setting
288 // all pointers within an array to NULL to indicate that they haven't
289 // been allocated yet.
290 memset(args_.get(), 0, (args.size() + 2) * sizeof(char*));
291 memset(vars_.get(), 0, (vars_size + 1) * sizeof(char*));
292 // By convention, the first argument points to an executable name.
293 args_[0] = allocateInternal(executable_);
294 // Copy arguments to the array.
295 for (size_t i = 1; i <= args.size(); ++i) {
296 args_[i] = allocateInternal(args[i - 1]);
297 }
298 // Copy environment variables to the array.
299 size_t i(0);
300 if (inherit_env) {
301 while (environ[i]) {
302 vars_[i] = allocateInternal(environ[i]);
303 ++i;
304 }
305 }
306 for (size_t j = 0; j < vars.size(); ++j) {
307 vars_[i + j] = allocateInternal(vars[j]);
308 }
309}
310
312 if (store_) {
313 lock_guard<std::mutex> lk(mutex_);
314 process_collection_.erase(this);
315 }
316}
317
318std::string
320 std::ostringstream s;
321 s << executable_;
322 // Start with index 1, because the first argument duplicates the
323 // path to the executable. Note, that even if there are no parameters
324 // the minimum size of the table is 2.
325 int i = 1;
326 while (args_[i] != NULL) {
327 s << " " << args_[i];
328 ++i;
329 }
330 return (s.str());
331}
332
333pid_t
335 lock_guard<std::mutex> lk(mutex_);
336 if (mode_ == ProcessSpawn::ASYNC) {
337 ProcessSpawnImpl::IOSignalSetInitializer::initIOSignalSet(ProcessSpawn::getIOService());
338 }
339 // Create the child
340 pid_t pid = fork();
341 if (pid < 0) {
342 isc_throw(ProcessSpawnError, "unable to fork current process");
343
344 } else if (pid == 0) {
345 // Reset masked signals for the child process.
346 sigset_t sset;
347 sigemptyset(&sset);
348 pthread_sigmask(SIG_SETMASK, &sset, 0);
349 // Run the executable.
350 execve(executable_.c_str(), args_.get(), vars_.get());
351 // We may end up here if the execve failed, e.g. as a result
352 // of issue with permissions or invalid executable name.
353 _exit(EXIT_FAILURE);
354 }
355
356 // We're in the parent process.
357 if (!dismiss) {
358 store_ = true;
359 process_collection_[this].insert(std::pair<pid_t, ProcessStatePtr>(pid, ProcessStatePtr(new ProcessState())));
360 }
361
362 if (mode_ == ProcessSpawn::SYNC) {
363 waitForProcess(SIGCHLD, pid, /* sync = */ true);
364 }
365
366 return (pid);
367}
368
369bool
370ProcessSpawnImpl::isRunning(const pid_t pid) const {
371 lock_guard<std::mutex> lk(mutex_);
372 ProcessStates::const_iterator proc;
373 if (process_collection_.find(this) == process_collection_.end() ||
374 (proc = process_collection_[this].find(pid)) == process_collection_[this].end()) {
375 isc_throw(InvalidOperation, "the status of process with pid '" << pid << "' cannot be returned");
376 }
377 return (proc->second->running_);
378}
379
380bool
382 lock_guard<std::mutex> lk(mutex_);
383 if (process_collection_.find(this) != process_collection_.end()) {
384 for (auto const& proc : process_collection_[this]) {
385 if (proc.second->running_) {
386 return (true);
387 }
388 }
389 }
390 return (false);
391}
392
393int
394ProcessSpawnImpl::getExitStatus(const pid_t pid) const {
395 lock_guard<std::mutex> lk(mutex_);
396 ProcessStates::const_iterator proc;
397 if (process_collection_.find(this) == process_collection_.end() ||
398 (proc = process_collection_[this].find(pid)) == process_collection_[this].end()) {
399 isc_throw(InvalidOperation, "the status of process with pid '" << pid << "' cannot be returned");
400 }
401 return (WEXITSTATUS(proc->second->status_));
402}
403
404char*
405ProcessSpawnImpl::allocateInternal(const std::string& src) {
406 const size_t src_len = src.length();
407 storage_.push_back(CStringPtr(new char[src_len + 1]));
408 // Allocate the C-string with one byte more for the null termination.
409 char* dest = storage_[storage_.size() - 1].get();
410 // copy doesn't append the null at the end.
411 src.copy(dest, src_len);
412 // Append null on our own.
413 dest[src_len] = '\0';
414 return (dest);
415}
416
417void
418ProcessSpawnImpl::waitForProcess(int /* signum */,
419 pid_t const wpid /* = -1 */,
420 bool sync /* = false */) {
421 // In synchronous mode, the lock is taken by the caller function
422 // spawn(), so do not deadlock.
423 unique_lock<std::mutex> lk{mutex_, std::defer_lock};
424 if (!sync) {
425 lk.lock();
426 }
427 for (;;) {
428 int status = 0;
429 // When called synchronously, this function needs to be blocking.
430 // When called asynchronously, the first IO context event is for
431 // receiving the SIGCHLD signal which itself is blocking,
432 // hence no need to block here too.
433 pid_t pid = waitpid(wpid, &status, sync ? 0 : WNOHANG);
434 if (pid < 0) {
435 if (!sync) {
436 break;
437 }
438 if (errno == EINTR) {
439 // On some systems that call sigaction wihout SA_RESTART by default, signals make
440 // waitpid exit with EINTR. In this situation, if sync mode is enabled, we're
441 // interested in another round of waitpid.
442 continue;
443 }
444 isc_throw(InvalidOperation, "process with pid " << wpid << " has returned " << pid
445 << " from waitpid in sync mode, errno: "
446 << strerror(errno));
447 } else if (pid == 0) {
448 if (!sync) {
449 break;
450 }
451 } else /* if (pid > 0) */ {
452 for (auto const& instance : process_collection_) {
453 auto const& proc = instance.second.find(pid);
456 if (proc != instance.second.end()) {
457 proc->second->status_ = status;
458 proc->second->running_ = false;
459 }
460 }
461 if (sync) {
462 // Collected process status. Can exit loop.
463 break;
464 }
465 }
466 }
467}
468
469void
471 if (isRunning(pid)) {
472 isc_throw(InvalidOperation, "unable to remove the status for the "
473 "process (pid: " << pid << ") which is still running");
474 }
475 lock_guard<std::mutex> lk(mutex_);
476 if (process_collection_.find(this) != process_collection_.end()) {
477 process_collection_[this].erase(pid);
478 }
479}
480
481IOServicePtr ProcessSpawn::io_service_;
482
484 const std::string& executable,
485 const ProcessArgs& args,
486 const ProcessEnvVars& vars,
487 const bool inherit_env /* = false */)
488 : impl_(new ProcessSpawnImpl(mode, executable, args, vars, inherit_env)) {
489}
490
491std::string
493 return (impl_->getCommandLine());
494}
495
496pid_t
497ProcessSpawn::spawn(bool dismiss) {
498 return (impl_->spawn(dismiss));
499}
500
501bool
502ProcessSpawn::isRunning(const pid_t pid) const {
503 return (impl_->isRunning(pid));
504}
505
506bool
508 return (impl_->isAnyRunning());
509}
510
511int
512ProcessSpawn::getExitStatus(const pid_t pid) const {
513 return (impl_->getExitStatus(pid));
514}
515
516void
517ProcessSpawn::clearState(const pid_t pid) {
518 return (impl_->clearState(pid));
519}
520
521} // namespace asiolink
522} // namespace isc
A generic exception that is thrown if a function is called in a prohibited way.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
Defines the logger used by the top-level component of kea-lfc.
char ** environ