Kea 1.5.0
process_spawn.cc
Go to the documentation of this file.
1// Copyright (C) 2015 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
10#include <util/process_spawn.h>
11#include <util/signal_set.h>
12#include <boost/bind.hpp>
13#include <map>
14#include <signal.h>
15#include <stdlib.h>
16#include <errno.h>
17#include <unistd.h>
18#include <sys/wait.h>
19
20namespace isc {
21namespace util {
22
25
28 }
29
32
35};
36
37typedef std::map<pid_t, ProcessState> ProcessStates;
38
52class ProcessSpawnImpl : boost::noncopyable {
53public:
54
59 ProcessSpawnImpl(const std::string& executable,
60 const ProcessArgs& args);
61
64
66 std::string getCommandLine() const;
67
80 pid_t spawn();
81
86 bool isRunning(const pid_t pid) const;
87
91 bool isAnyRunning() const;
92
101 int getExitStatus(const pid_t pid) const;
102
110 void clearState(const pid_t pid);
111
112private:
113
125 char* allocateArg(const std::string& src) const;
126
134 bool waitForProcess(int signum);
135
137 SignalSetPtr signals_;
138
140 ProcessStates process_state_;
141
143 std::string executable_;
144
146 char** args_;
147};
148
149ProcessSpawnImpl::ProcessSpawnImpl(const std::string& executable,
150 const ProcessArgs& args)
151 : signals_(new SignalSet(SIGCHLD)), process_state_(),
152 executable_(executable), args_(new char*[args.size() + 2]) {
153 // Set the handler which is invoked immediately when the signal
154 // is received.
155 signals_->setOnReceiptHandler(boost::bind(&ProcessSpawnImpl::waitForProcess,
156 this, _1));
157 // Conversion of the arguments to the C-style array we start by setting
158 // all pointers within an array to NULL to indicate that they haven't
159 // been allocated yet.
160 memset(args_, 0, (args.size() + 2) * sizeof(char*));
161 // By convention, the first argument points to an executable name.
162 args_[0] = allocateArg(executable_);
163 // Copy arguments to the array.
164 for (int i = 1; i <= args.size(); ++i) {
165 args_[i] = allocateArg(args[i-1]);
166 }
167}
168
170 int i = 0;
171 // Deallocate strings in the array of arguments.
172 while (args_[i] != NULL) {
173 delete[] args_[i];
174 ++i;
175 }
176 // Deallocate the array.
177 delete[] args_;
178}
179
180std::string
182 std::ostringstream s;
183 s << executable_;
184 // Start with index 1, because the first argument duplicates the
185 // path to the executable. Note, that even if there are no parameters
186 // the minimum size of the table is 2.
187 int i = 1;
188 while (args_[i] != NULL) {
189 s << " " << args_[i];
190 ++i;
191 }
192 return (s.str());
193}
194
195pid_t
197 // Protect us against SIGCHLD signals
198 sigset_t sset;
199 sigset_t osset;
200 sigemptyset(&sset);
201 sigaddset(&sset, SIGCHLD);
202 pthread_sigmask(SIG_BLOCK, &sset, &osset);
203 if (sigismember(&osset, SIGCHLD)) {
205 "spawn() called from a thread where SIGCHLD is blocked");
206 }
207
208 // Create the child
209 pid_t pid = fork();
210 if (pid < 0) {
211 isc_throw(ProcessSpawnError, "unable to fork current process");
212
213 } else if (pid == 0) {
214 // We're in the child process.
215 sigprocmask(SIG_SETMASK, &osset, 0);
216 // Run the executable.
217 if (execvp(executable_.c_str(), args_) != 0) {
218 // We may end up here if the execvp failed, e.g. as a result
219 // of issue with permissions or invalid executable name.
220 _exit(EXIT_FAILURE);
221 }
222 // Process finished, exit the child process.
223 _exit(EXIT_SUCCESS);
224 }
225
226 // We're in the parent process.
227 try {
228 process_state_.insert(
229 std::pair<pid_t, ProcessState>(pid, ProcessState()));
230 } catch(...) {
231 pthread_sigmask(SIG_SETMASK, &osset, 0);
232 throw;
233 }
234 pthread_sigmask(SIG_SETMASK, &osset, 0);
235 return (pid);
236}
237
238bool
239ProcessSpawnImpl::isRunning(const pid_t pid) const {
240 ProcessStates::const_iterator proc = process_state_.find(pid);
241 if (proc == process_state_.end()) {
242 isc_throw(BadValue, "the process with the pid '" << pid
243 << "' hasn't been spawned and it status cannot be"
244 " returned");
245 }
246 return (proc->second.running_);
247}
248
249bool
251 for (ProcessStates::const_iterator proc = process_state_.begin();
252 proc != process_state_.end(); ++proc) {
253 if (proc->second.running_) {
254 return (true);
255 }
256 }
257 return (false);
258}
259
260int
261ProcessSpawnImpl::getExitStatus(const pid_t pid) const {
262 ProcessStates::const_iterator proc = process_state_.find(pid);
263 if (proc == process_state_.end()) {
264 isc_throw(InvalidOperation, "the process with the pid '" << pid
265 << "' hasn't been spawned and it status cannot be"
266 " returned");
267 }
268 return (WEXITSTATUS(proc->second.status_));
269}
270
271char*
272ProcessSpawnImpl::allocateArg(const std::string& src) const {
273 const size_t src_len = src.length();
274 // Allocate the C-string with one byte more for the null termination.
275 char* dest = new char[src_len + 1];
276 // copy doesn't append the null at the end.
277 src.copy(dest, src_len);
278 // Append null on our own.
279 dest[src_len] = '\0';
280 return (dest);
281}
282
283bool
284ProcessSpawnImpl::waitForProcess(int signum) {
285 // We're only interested in SIGCHLD.
286 if (signum != SIGCHLD) {
287 return (false);
288 }
289
290 // Need to store current value of errno, so we could restore it
291 // after this signal handler does his work.
292 int errno_value = errno;
293
294 for (;;) {
295 int status = 0;
296 pid_t pid = waitpid(-1, &status, WNOHANG);
297 if (pid <= 0) {
298 break;
299 }
300 ProcessStates::iterator proc = process_state_.find(pid);
303 if (proc != process_state_.end()) {
304 // In this order please
305 proc->second.status_ = status;
306 proc->second.running_ = false;
307 }
308 }
309
310 // Need to restore previous value of errno. We called waitpid(),
311 // which likely indicated its result by setting errno to ECHILD.
312 // This is a signal handler, which can be called while virtually
313 // any other code being run. If we're unlucky, we could receive a
314 // signal when running a code that is about to check errno. As a
315 // result the code would detect errno=ECHILD in places which are
316 // completely unrelated to child or processes in general.
317 errno = errno_value;
318
319 return (true);
320}
321
322void
324 if (isRunning(pid)) {
325 isc_throw(InvalidOperation, "unable to remove the status for the"
326 "process (pid: " << pid << ") which is still running");
327 }
328 process_state_.erase(pid);
329}
330
331ProcessSpawn::ProcessSpawn(const std::string& executable,
332 const ProcessArgs& args)
333 : impl_(new ProcessSpawnImpl(executable, args)) {
334}
335
337 delete impl_;
338}
339
340std::string
342 return (impl_->getCommandLine());
343}
344
345pid_t
347 return (impl_->spawn());
348}
349
350bool
351ProcessSpawn::isRunning(const pid_t pid) const {
352 return (impl_->isRunning(pid));
353}
354
355bool
357 return (impl_->isAnyRunning());
358}
359
360int
361ProcessSpawn::getExitStatus(const pid_t pid) const {
362 return (impl_->getExitStatus(pid));
363}
364
365void
366ProcessSpawn::clearState(const pid_t pid) {
367 return (impl_->clearState(pid));
368}
369
370}
371}
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.
Exception thrown when error occurs during spawning a process.
Definition: process_spawn.h:20
Implementation of the ProcessSpawn class.
pid_t spawn()
Spawn the new process.
void clearState(const pid_t pid)
Removes the status of the process with a specified PID.
bool isAnyRunning() const
Checks if any of the spawned processes is still running.
ProcessSpawnImpl(const std::string &executable, const ProcessArgs &args)
Constructor.
std::string getCommandLine() const
Returns full command line, including arguments, for the process.
bool isRunning(const pid_t pid) const
Checks if the process is still running.
int getExitStatus(const pid_t pid) const
Returns exit status of the process.
bool isRunning(const pid_t pid) const
Checks if the process is still running.
bool isAnyRunning() const
Checks if any of the spawned processes is still running.
ProcessSpawn(const std::string &executable, const ProcessArgs &args=ProcessArgs())
Constructor.
int getExitStatus(const pid_t pid) const
Returns exit status of the process.
std::string getCommandLine() const
Returns full command line, including arguments, for the process.
pid_t spawn()
Spawn the new process.
void clearState(const pid_t pid)
Removes the status of the process with a specified PID.
Represents a collection of signals handled in a customized way.
Definition: signal_set.h:87
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
std::map< pid_t, ProcessState > ProcessStates
boost::shared_ptr< SignalSet > SignalSetPtr
Pointer to the isc::util::SignalSet.
Definition: signal_set.h:45
std::vector< std::string > ProcessArgs
Type of the container holding arguments of the executable being run as a background process.
Definition: process_spawn.h:32
Defines the logger used by the top-level component of kea-dhcp-ddns.
Type for process state.
int status_
0 or the exit status
ProcessState()
Constructor.
bool running_
true until the exit status is collected