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