Kea 3.1.3
process_spawn.cc
Go to the documentation of this file.
1// Copyright (C) 2015-2025 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
98 std::string getCommandLine(std::unordered_set<std::string> redact_args = {}) const;
99
114 pid_t spawn(bool dismiss);
115
120 bool isRunning(const pid_t pid) const;
121
125 bool isAnyRunning() const;
126
135 int getExitStatus(const pid_t pid) const;
136
144 void clearState(const pid_t pid);
145
146private:
147
153 class IOSignalSetInitializer {
154 private:
155
159 IOSignalSetInitializer(IOServicePtr io_service) : io_service_(io_service) {
160 if (!io_service) {
161 isc_throw(ProcessSpawnError, "NULL IOService instance");
162 }
163 io_signal_set_ =
164 boost::make_shared<IOSignalSet>(io_service,
165 std::bind(&ProcessSpawnImpl::waitForProcess, ph::_1,
166 /* pid = */ -1, /* sync = */ false));
167 io_signal_set_->add(SIGCHLD);
168 }
169
171 ~IOSignalSetInitializer() {
172 io_signal_set_->remove(SIGCHLD);
173 io_signal_set_.reset();
174 io_service_->stopAndPoll();
175 }
176
177 public:
178
184 static void initIOSignalSet(IOServicePtr io_service);
185
186 private:
187
189 asiolink::IOServicePtr io_service_;
190
192 IOSignalSetPtr io_signal_set_;
193 };
194
208 char* allocateInternal(const std::string& src);
209
218 static void waitForProcess(int signum, pid_t const wpid = -1,
219 bool const sync = false);
220
222 static ProcessCollection process_collection_;
223
227
229 std::string executable_;
230
232 boost::shared_ptr<char*[]> args_;
233
235 boost::shared_ptr<char*[]> vars_;
236
238 typedef boost::shared_ptr<char[]> CStringPtr;
239
241 std::vector<CStringPtr> storage_;
242
244 bool store_;
245
247 static std::mutex mutex_;
248};
249
250ProcessCollection ProcessSpawnImpl::process_collection_;
251std::mutex ProcessSpawnImpl::mutex_;
252
253void ProcessSpawnImpl::IOSignalSetInitializer::initIOSignalSet(IOServicePtr io_service) {
254 static IOSignalSetInitializer init(io_service);
255}
256
258 const std::string& executable,
259 const ProcessArgs& args,
260 const ProcessEnvVars& vars,
261 const bool inherit_env)
262 : mode_(mode), executable_(executable), args_(new char*[args.size() + 2]),
263 store_(false) {
264
265 // Size of vars except the trailing null
266 size_t vars_size;
267 if (inherit_env) {
268 size_t i(0);
269 while (environ[i]) {
270 ++i;
271 }
272 vars_size = i + vars.size();
273 } else {
274 vars_size = vars.size();
275 }
276
277 vars_ = boost::shared_ptr<char*[]>(new char*[vars_size + 1]);
278
279 struct stat st;
280
281 if (stat(executable_.c_str(), &st)) {
282 isc_throw(ProcessSpawnError, "File not found: " << executable_);
283 }
284
285 if (!(st.st_mode & S_IEXEC)) {
286 isc_throw(ProcessSpawnError, "File not executable: " << executable_);
287 }
288
289 // Conversion of the arguments to the C-style array we start by setting
290 // all pointers within an array to NULL to indicate that they haven't
291 // been allocated yet.
292 memset(args_.get(), 0, (args.size() + 2) * sizeof(char*));
293 memset(vars_.get(), 0, (vars_size + 1) * sizeof(char*));
294 // By convention, the first argument points to an executable name.
295 args_[0] = allocateInternal(executable_);
296 // Copy arguments to the array.
297 for (size_t i = 1; i <= args.size(); ++i) {
298 args_[i] = allocateInternal(args[i - 1]);
299 }
300 // Copy environment variables to the array.
301 size_t i(0);
302 if (inherit_env) {
303 while (environ[i]) {
304 vars_[i] = allocateInternal(environ[i]);
305 ++i;
306 }
307 }
308 for (size_t j = 0; j < vars.size(); ++j) {
309 vars_[i + j] = allocateInternal(vars[j]);
310 }
311}
312
314 if (store_) {
315 lock_guard<std::mutex> lk(mutex_);
316 process_collection_.erase(this);
317 }
318}
319
320std::string
321ProcessSpawnImpl::getCommandLine(std::unordered_set<std::string> redact_args /* = {} */) const {
322 std::ostringstream s;
323 s << executable_;
324 // Start with index 1, because the first argument duplicates the
325 // path to the executable. Note, that even if there are no parameters
326 // the minimum size of the table is 2.
327 int i = 1;
328 bool redact_next = false;
329 while (args_[i] != NULL) {
330 if (redact_next) {
331 s << " " << "*****";
332 redact_next = false;
333 } else {
334 if (redact_args.count(args_[i])) {
335 redact_next = true;
336 }
337
338 s << " " << args_[i];
339 }
340
341 ++i;
342 }
343 return (s.str());
344}
345
346pid_t
348 lock_guard<std::mutex> lk(mutex_);
349 if (mode_ == ProcessSpawn::ASYNC) {
350 ProcessSpawnImpl::IOSignalSetInitializer::initIOSignalSet(ProcessSpawn::getIOService());
351 }
352 // Create the child
353 pid_t pid = fork();
354 if (pid < 0) {
355 isc_throw(ProcessSpawnError, "unable to fork current process");
356
357 } else if (pid == 0) {
358 // Reset masked signals for the child process.
359 sigset_t sset;
360 sigemptyset(&sset);
361 pthread_sigmask(SIG_SETMASK, &sset, 0);
362 // Run the executable.
363 execve(executable_.c_str(), args_.get(), vars_.get());
364 // We may end up here if the execve failed, e.g. as a result
365 // of issue with permissions or invalid executable name.
366 _exit(EXIT_FAILURE);
367 }
368
369 // We're in the parent process.
370 if (!dismiss) {
371 store_ = true;
372 process_collection_[this].insert(std::pair<pid_t, ProcessStatePtr>(pid, ProcessStatePtr(new ProcessState())));
373 }
374
375 if (mode_ == ProcessSpawn::SYNC) {
376 waitForProcess(SIGCHLD, pid, /* sync = */ true);
377 }
378
379 return (pid);
380}
381
382bool
383ProcessSpawnImpl::isRunning(const pid_t pid) const {
384 lock_guard<std::mutex> lk(mutex_);
385 ProcessStates::const_iterator proc;
386 if (process_collection_.find(this) == process_collection_.end() ||
387 (proc = process_collection_[this].find(pid)) == process_collection_[this].end()) {
388 isc_throw(InvalidOperation, "the status of process with pid '" << pid << "' cannot be returned");
389 }
390 return (proc->second->running_);
391}
392
393bool
395 lock_guard<std::mutex> lk(mutex_);
396 if (process_collection_.find(this) != process_collection_.end()) {
397 for (auto const& proc : process_collection_[this]) {
398 if (proc.second->running_) {
399 return (true);
400 }
401 }
402 }
403 return (false);
404}
405
406int
407ProcessSpawnImpl::getExitStatus(const pid_t pid) const {
408 lock_guard<std::mutex> lk(mutex_);
409 ProcessStates::const_iterator proc;
410 if (process_collection_.find(this) == process_collection_.end() ||
411 (proc = process_collection_[this].find(pid)) == process_collection_[this].end()) {
412 isc_throw(InvalidOperation, "the status of process with pid '" << pid << "' cannot be returned");
413 }
414 return (WEXITSTATUS(proc->second->status_));
415}
416
417char*
418ProcessSpawnImpl::allocateInternal(const std::string& src) {
419 const size_t src_len = src.length();
420 storage_.push_back(CStringPtr(new char[src_len + 1]));
421 // Allocate the C-string with one byte more for the null termination.
422 char* dest = storage_[storage_.size() - 1].get();
423 // copy doesn't append the null at the end.
424 src.copy(dest, src_len);
425 // Append null on our own.
426 dest[src_len] = '\0';
427 return (dest);
428}
429
430void
431ProcessSpawnImpl::waitForProcess(int /* signum */,
432 pid_t const wpid /* = -1 */,
433 bool sync /* = false */) {
434 // In synchronous mode, the lock is taken by the caller function
435 // spawn(), so do not deadlock.
436 unique_lock<std::mutex> lk{mutex_, std::defer_lock};
437 if (!sync) {
438 lk.lock();
439 }
440 for (;;) {
441 int status = 0;
442 // When called synchronously, this function needs to be blocking.
443 // When called asynchronously, the first IO context event is for
444 // receiving the SIGCHLD signal which itself is blocking,
445 // hence no need to block here too.
446 pid_t pid = waitpid(wpid, &status, sync ? 0 : WNOHANG);
447 if (pid < 0) {
448 if (!sync) {
449 break;
450 }
451 if (errno == EINTR) {
452 // On some systems that call sigaction wihout SA_RESTART by default, signals make
453 // waitpid exit with EINTR. In this situation, if sync mode is enabled, we're
454 // interested in another round of waitpid.
455 continue;
456 }
457 isc_throw(InvalidOperation, "process with pid " << wpid << " has returned " << pid
458 << " from waitpid in sync mode, errno: "
459 << strerror(errno));
460 } else if (pid == 0) {
461 if (!sync) {
462 break;
463 }
464 } else /* if (pid > 0) */ {
465 for (auto const& instance : process_collection_) {
466 auto const& proc = instance.second.find(pid);
469 if (proc != instance.second.end()) {
470 proc->second->status_ = status;
471 proc->second->running_ = false;
472 }
473 }
474 if (sync) {
475 // Collected process status. Can exit loop.
476 break;
477 }
478 }
479 }
480}
481
482void
484 if (isRunning(pid)) {
485 isc_throw(InvalidOperation, "unable to remove the status for the "
486 "process (pid: " << pid << ") which is still running");
487 }
488 lock_guard<std::mutex> lk(mutex_);
489 if (process_collection_.find(this) != process_collection_.end()) {
490 process_collection_[this].erase(pid);
491 }
492}
493
494IOServicePtr ProcessSpawn::io_service_;
495
497 const std::string& executable,
498 const ProcessArgs& args,
499 const ProcessEnvVars& vars,
500 const bool inherit_env /* = false */)
501 : impl_(new ProcessSpawnImpl(mode, executable, args, vars, inherit_env)) {
502}
503
504std::string
505ProcessSpawn::getCommandLine(std::unordered_set<std::string> redact_args /* = {} */) const {
506 return (impl_->getCommandLine(redact_args));
507}
508
509pid_t
510ProcessSpawn::spawn(bool dismiss) {
511 return (impl_->spawn(dismiss));
512}
513
514bool
515ProcessSpawn::isRunning(const pid_t pid) const {
516 return (impl_->isRunning(pid));
517}
518
519bool
521 return (impl_->isAnyRunning());
522}
523
524int
525ProcessSpawn::getExitStatus(const pid_t pid) const {
526 return (impl_->getExitStatus(pid));
527}
528
529void
530ProcessSpawn::clearState(const pid_t pid) {
531 return (impl_->clearState(pid));
532}
533
534} // namespace asiolink
535} // 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