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