Kea 2.2.0
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
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#include <boost/make_shared.hpp>
24
25using namespace std;
26namespace ph = std::placeholders;
27
28namespace isc {
29namespace asiolink {
30
33
36 }
37
40
43};
44
46typedef boost::shared_ptr<ProcessState> ProcessStatePtr;
47
50typedef std::map<pid_t, ProcessStatePtr> ProcessStates;
51
53
56typedef std::map<const ProcessSpawnImpl*, ProcessStates> ProcessCollection;
57
71class ProcessSpawnImpl : boost::noncopyable {
72public:
73
81 const std::string& executable,
82 const ProcessArgs& args,
83 const ProcessEnvVars& vars);
84
87
89 std::string getCommandLine() const;
90
105 pid_t spawn(bool dismiss);
106
111 bool isRunning(const pid_t pid) const;
112
116 bool isAnyRunning() const;
117
126 int getExitStatus(const pid_t pid) const;
127
135 void clearState(const pid_t pid);
136
137private:
138
144 class IOSignalSetInitializer {
145 private:
146
150 IOSignalSetInitializer(IOServicePtr io_service) {
151 if (!io_service) {
152 isc_throw(ProcessSpawnError, "NULL IOService instance");
153 }
154 io_signal_set_ = boost::make_shared<IOSignalSet>(io_service,
155 std::bind(&ProcessSpawnImpl::waitForProcess, ph::_1));
156 io_signal_set_->add(SIGCHLD);
157 }
158
160 ~IOSignalSetInitializer() {
161 io_signal_set_->remove(SIGCHLD);
162 }
163
164 public:
165
171 static void initIOSignalSet(IOServicePtr io_service);
172
173 private:
174
176 IOSignalSetPtr io_signal_set_;
177 };
178
192 char* allocateInternal(const std::string& src);
193
201 static bool waitForProcess(int signum);
202
204 static ProcessCollection process_collection_;
205
207 std::string executable_;
208
210 boost::shared_ptr<char*[]> args_;
211
213 boost::shared_ptr<char*[]> vars_;
214
216 typedef boost::shared_ptr<char[]> CStringPtr;
217
219 std::vector<CStringPtr> storage_;
220
222 bool store_;
223
225 static std::mutex mutex_;
226
228 IOServicePtr io_service_;
229};
230
231ProcessCollection ProcessSpawnImpl::process_collection_;
232std::mutex ProcessSpawnImpl::mutex_;
233
234void ProcessSpawnImpl::IOSignalSetInitializer::initIOSignalSet(IOServicePtr io_service) {
235 static IOSignalSetInitializer init(io_service);
236}
237
239 const std::string& executable,
240 const ProcessArgs& args,
241 const ProcessEnvVars& vars)
242 : executable_(executable), args_(new char*[args.size() + 2]),
243 vars_(new char*[vars.size() + 1]), store_(false), io_service_(io_service) {
244
245 struct stat st;
246
247 if (stat(executable_.c_str(), &st)) {
248 isc_throw(ProcessSpawnError, "File not found: " << executable_);
249 }
250
251 if (!(st.st_mode & S_IEXEC)) {
252 isc_throw(ProcessSpawnError, "File not executable: " << executable_);
253 }
254
255 // Conversion of the arguments to the C-style array we start by setting
256 // all pointers within an array to NULL to indicate that they haven't
257 // been allocated yet.
258 memset(args_.get(), 0, (args.size() + 2) * sizeof(char*));
259 memset(vars_.get(), 0, (vars.size() + 1) * sizeof(char*));
260 // By convention, the first argument points to an executable name.
261 args_[0] = allocateInternal(executable_);
262 // Copy arguments to the array.
263 for (int i = 1; i <= args.size(); ++i) {
264 args_[i] = allocateInternal(args[i - 1]);
265 }
266 // Copy environment variables to the array.
267 for (int i = 0; i < vars.size(); ++i) {
268 vars_[i] = allocateInternal(vars[i]);
269 }
270}
271
273 if (store_) {
274 lock_guard<std::mutex> lk(mutex_);
275 process_collection_.erase(this);
276 }
277}
278
279std::string
281 std::ostringstream s;
282 s << executable_;
283 // Start with index 1, because the first argument duplicates the
284 // path to the executable. Note, that even if there are no parameters
285 // the minimum size of the table is 2.
286 int i = 1;
287 while (args_[i] != NULL) {
288 s << " " << args_[i];
289 ++i;
290 }
291 return (s.str());
292}
293
294pid_t
296 lock_guard<std::mutex> lk(mutex_);
297 ProcessSpawnImpl::IOSignalSetInitializer::initIOSignalSet(io_service_);
298 // Create the child
299 pid_t pid = fork();
300 if (pid < 0) {
301 isc_throw(ProcessSpawnError, "unable to fork current process");
302
303 } else if (pid == 0) {
304 // Reset masked signals for the child process.
305 sigset_t sset;
306 sigemptyset(&sset);
307 pthread_sigmask(SIG_SETMASK, &sset, 0);
308 // Run the executable.
309 execve(executable_.c_str(), args_.get(), vars_.get());
310 // We may end up here if the execve failed, e.g. as a result
311 // of issue with permissions or invalid executable name.
312 _exit(EXIT_FAILURE);
313 }
314
315 // We're in the parent process.
316 if (!dismiss) {
317 store_ = true;
318 process_collection_[this].insert(std::pair<pid_t, ProcessStatePtr>(pid, ProcessStatePtr(new ProcessState())));
319 }
320 return (pid);
321}
322
323bool
324ProcessSpawnImpl::isRunning(const pid_t pid) const {
325 lock_guard<std::mutex> lk(mutex_);
326 ProcessStates::const_iterator proc;
327 if (process_collection_.find(this) == process_collection_.end() ||
328 (proc = process_collection_[this].find(pid)) == process_collection_[this].end()) {
329 isc_throw(BadValue, "the process with the pid '" << pid
330 << "' hasn't been spawned and it status cannot be"
331 " returned");
332 }
333 return (proc->second->running_);
334}
335
336bool
338 lock_guard<std::mutex> lk(mutex_);
339 if (process_collection_.find(this) != process_collection_.end()) {
340 for (auto const& proc : process_collection_[this]) {
341 if (proc.second->running_) {
342 return (true);
343 }
344 }
345 }
346 return (false);
347}
348
349int
350ProcessSpawnImpl::getExitStatus(const pid_t pid) const {
351 lock_guard<std::mutex> lk(mutex_);
352 ProcessStates::const_iterator proc;
353 if (process_collection_.find(this) == process_collection_.end() ||
354 (proc = process_collection_[this].find(pid)) == process_collection_[this].end()) {
355 isc_throw(InvalidOperation, "the process with the pid '" << pid
356 << "' hasn't been spawned and it status cannot be"
357 " returned");
358 }
359 return (WEXITSTATUS(proc->second->status_));
360}
361
362char*
363ProcessSpawnImpl::allocateInternal(const std::string& src) {
364 const size_t src_len = src.length();
365 storage_.push_back(CStringPtr(new char[src_len + 1]));
366 // Allocate the C-string with one byte more for the null termination.
367 char* dest = storage_[storage_.size() - 1].get();
368 // copy doesn't append the null at the end.
369 src.copy(dest, src_len);
370 // Append null on our own.
371 dest[src_len] = '\0';
372 return (dest);
373}
374
375bool
376ProcessSpawnImpl::waitForProcess(int) {
377 lock_guard<std::mutex> lk(mutex_);
378 for (;;) {
379 int status = 0;
380 pid_t pid = waitpid(-1, &status, WNOHANG);
381 if (pid <= 0) {
382 break;
383 }
384 for (auto const& instance : process_collection_) {
385 auto const& proc = instance.second.find(pid);
388 if (proc != instance.second.end()) {
389 // In this order please
390 proc->second->status_ = status;
391 proc->second->running_ = false;
392 }
393 }
394 }
395 return (true);
396}
397
398void
400 if (isRunning(pid)) {
401 isc_throw(InvalidOperation, "unable to remove the status for the"
402 "process (pid: " << pid << ") which is still running");
403 }
404 lock_guard<std::mutex> lk(mutex_);
405 if (process_collection_.find(this) != process_collection_.end()) {
406 process_collection_[this].erase(pid);
407 }
408}
409
411 const std::string& executable,
412 const ProcessArgs& args,
413 const ProcessEnvVars& vars)
414 : impl_(new ProcessSpawnImpl(io_service, executable, args, vars)) {
415}
416
417std::string
419 return (impl_->getCommandLine());
420}
421
422pid_t
423ProcessSpawn::spawn(bool dismiss) {
424 return (impl_->spawn(dismiss));
425}
426
427bool
428ProcessSpawn::isRunning(const pid_t pid) const {
429 return (impl_->isRunning(pid));
430}
431
432bool
434 return (impl_->isAnyRunning());
435}
436
437int
438ProcessSpawn::getExitStatus(const pid_t pid) const {
439 return (impl_->getExitStatus(pid));
440}
441
442void
443ProcessSpawn::clearState(const pid_t pid) {
444 return (impl_->clearState(pid));
445}
446
447} // namespace asiolink
448} // namespace isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
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.