Fawkes API Fawkes Development Version
proc.cpp
1
2/***************************************************************************
3 * proc.cpp - Sub-process facilities
4 *
5 * Created: Mon Aug 18 16:56:46 2014
6 * Copyright 2014 Tim Niemueller [www.niemueller.de]
7 ****************************************************************************/
8
9/* This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Library General Public License for more details.
18 *
19 * Read the full text in the LICENSE.GPL file in the doc directory.
20 */
21
22#include "proc.h"
23
24#include <core/exception.h>
25
26#include <boost/bind/bind.hpp>
27
28#ifdef HAVE_LIBDAEMON
29# include <libdaemon/dfork.h>
30#endif
31#include <sys/types.h>
32#include <sys/wait.h>
33
34#include <string>
35#include <unistd.h>
36
37namespace fawkes {
38
39/** @class SubProcess <libs/utils/sub_process/proc.h>
40 * Sub-process execution with stdin/stdout/stderr redirection.
41 * This class executes a sub-process and monitors it and supports redirecting
42 * stdout/stderr to a logger.
43 * @author Tim Niemueller
44 */
45
46/** Constructor.
47 * @param progname name of program, component name for logging
48 * @param file file to execute, can be a program in the path or a
49 * fully qualified path
50 * @param argv array of arguments for the process, the last element
51 * must be NULL
52 * @param envp array of environment variables for the process, the
53 * last element must be NULL. Can be NULL to omit.
54 */
55SubProcess::SubProcess(const char *progname,
56 const char *file,
57 const char *argv[],
58 const char *envp[])
59: progname_(progname),
60 io_service_work_(io_service_),
61 logger_(NULL),
62 sd_stdin_(io_service_),
63 sd_stdout_(io_service_),
64 sd_stderr_(io_service_),
65 exit_status_(-1)
66{
67 io_service_thread_ = std::thread([this]() { this->io_service_.run(); });
68 run_proc(file, argv, envp);
69}
70
71/** Constructor.
72 * @param progname name of program, component name for logging
73 * @param file file to execute, can be a program in the path or a
74 * fully qualified path
75 * @param argv array of arguments for the process, the last element
76 * must be NULL
77 * @param envp array of environment variables for the process, the
78 * last element must be NULL. Can be NULL to omit.
79 * @param logger logger to redirect stdout and stderr to
80 */
81SubProcess::SubProcess(const char * progname,
82 const char * file,
83 const char * argv[],
84 const char * envp[],
85 fawkes::Logger *logger)
86: progname_(progname),
87 io_service_work_(io_service_),
88 logger_(logger),
89 sd_stdin_(io_service_),
90 sd_stdout_(io_service_),
91 sd_stderr_(io_service_),
92 exit_status_(-1)
93{
94 io_service_thread_ = std::thread([this]() { this->io_service_.run(); });
95 run_proc(file, argv, envp);
96}
97
98/** Constructor.
99 * @param progname name of program, component name for logging
100 * @param file file to execute, can be a program in the path or a
101 * fully qualified path
102 * @param argv array of arguments for the process, the last element
103 * must be NULL
104 * @param envp array of environment variables for the process, the
105 * last element must be NULL. Can be NULL to omit.
106 */
107SubProcess::SubProcess(const std::string & progname,
108 const std::string & file,
109 const std::vector<std::string> &argv,
110 const std::vector<std::string> &envp)
111: progname_(progname),
112 io_service_work_(io_service_),
113 logger_(NULL),
114 sd_stdin_(io_service_),
115 sd_stdout_(io_service_),
116 sd_stderr_(io_service_),
117 exit_status_(-1)
118{
119 io_service_thread_ = std::thread([this]() { this->io_service_.run(); });
120
121 const char *argvc[argv.size() + 1];
122 for (size_t i = 0; i < argv.size(); ++i) {
123 argvc[i] = argv[i].c_str();
124 }
125 argvc[argv.size()] = NULL;
126 const char *envpc[envp.size() + 1];
127 for (size_t i = 0; i < envp.size(); ++i) {
128 envpc[i] = envp[i].c_str();
129 }
130 envpc[envp.size()] = NULL;
131 run_proc(file.c_str(), argvc, envpc);
132}
133
134/** Constructor.
135 * @param progname name of program, component name for logging
136 * @param file file to execute, can be a program in the path or a
137 * fully qualified path
138 * @param argv array of arguments for the process, the last element
139 * must be NULL
140 * @param envp array of environment variables for the process, the
141 * last element must be NULL. Can be NULL to omit.
142 * @param logger logger to redirect stdout and stderr to
143 */
144SubProcess::SubProcess(const std::string & progname,
145 const std::string & file,
146 const std::vector<std::string> &argv,
147 const std::vector<std::string> &envp,
148 fawkes::Logger * logger)
149: progname_(progname),
150 io_service_work_(io_service_),
151 logger_(logger),
152 sd_stdin_(io_service_),
153 sd_stdout_(io_service_),
154 sd_stderr_(io_service_),
155 exit_status_(-1)
156{
157 io_service_thread_ = std::thread([this]() { this->io_service_.run(); });
158
159 const char *argvc[argv.size() + 1];
160 for (size_t i = 0; i < argv.size(); ++i) {
161 argvc[i] = argv[i].c_str();
162 }
163 argvc[argv.size()] = NULL;
164 if (envp.empty()) {
165 run_proc(file.c_str(), argvc, NULL);
166 } else {
167 const char *envpc[envp.size() + 1];
168 for (size_t i = 0; i < envp.size(); ++i) {
169 envpc[i] = envp[i].c_str();
170 }
171 envpc[envp.size()] = NULL;
172 run_proc(file.c_str(), argvc, envpc);
173 }
174}
175
176/** Destructor. */
178{
179 this->kill(SIGTERM);
180 io_service_.stop();
181 io_service_thread_.join();
182}
183
184/** Send a signal to the process.
185 * @param signum signal number
186 */
187void
189{
190 if (pid_ > 0)
191 ::kill(pid_, signum);
192}
193
194pid_t
195SubProcess::run_proc(const char *file,
196 const char *argv[],
197 const char *envp[],
198 int & pipe_stdin_w,
199 int & pipe_stdout_r,
200 int & pipe_stderr_r)
201{
202 int pipe_stdin[2];
203 int pipe_stdout[2];
204 int pipe_stderr[2];
205
206 if (pipe(pipe_stdin) < 0) {
207 throw Exception(errno, "Failed to create OpenPRS stdin pipe (%s)", file);
208 }
209 if (pipe(pipe_stdout) < 0) {
210 close(pipe_stdin[0]);
211 close(pipe_stdin[1]);
212 throw Exception(errno, "Failed to create OpenPRS stdout pipe (%s)", file);
213 }
214 if (pipe(pipe_stderr) < 0) {
215 close(pipe_stdin[0]);
216 close(pipe_stdin[1]);
217 close(pipe_stdout[0]);
218 close(pipe_stdout[1]);
219 throw Exception(errno, "Failed to create OpenPRS stderr pipe (%s)", file);
220 }
221
222 pid_t pid = fork();
223 if (pid < 0) { // fail
224 close(pipe_stdin[0]);
225 close(pipe_stdin[1]);
226 close(pipe_stdout[0]);
227 close(pipe_stdout[1]);
228 close(pipe_stderr[0]);
229 close(pipe_stderr[1]);
230 throw Exception(errno, "Failed to fork for OpenPRS %s", file);
231 } else if (pid) { // parent
232 close(pipe_stdin[0]);
233 close(pipe_stdout[1]);
234 close(pipe_stderr[1]);
235
236 pipe_stdin_w = pipe_stdin[1];
237 pipe_stdout_r = pipe_stdout[0];
238 pipe_stderr_r = pipe_stderr[0];
239
240 return pid;
241 } else { // child
242#ifdef HAVE_LIBDAEMON
243 daemon_close_all(STDIN_FILENO,
244 STDOUT_FILENO,
245 STDERR_FILENO,
246 pipe_stdin[0],
247 pipe_stdout[1],
248 pipe_stderr[1],
249 -1);
250#endif
251
252 if (dup2(pipe_stdin[0], STDIN_FILENO) == -1) {
253 perror("Failed to dup stdin");
254 ::exit(-1);
255 }
256 if (dup2(pipe_stdout[1], STDOUT_FILENO) == -1) {
257 perror("Failed to dup stdout");
258 ::exit(-1);
259 }
260 if (dup2(pipe_stderr[1], STDERR_FILENO) == -1) {
261 perror("Failed to dup stderr");
262 ::exit(-1);
263 }
264
265 close(pipe_stdin[0]);
266 close(pipe_stdout[0]);
267 close(pipe_stderr[0]);
268 close(pipe_stdin[1]);
269 close(pipe_stdout[1]);
270 close(pipe_stderr[1]);
271
272#ifdef __FreeBSD__
273 if (envp) {
274 execve(file, (char *const *)argv, (char *const *)envp);
275 } else {
276 execv(file, (char *const *)argv);
277 }
278#else
279 execvpe(file, (char *const *)argv, envp ? (char *const *)envp : environ);
280#endif
281
282 // execvpe only returns on error, which is when we should exit
283 perror("Failed to execute command");
284 ::exit(-2);
285 }
286}
287
288void
289SubProcess::run_proc(const char *file, const char *argv[], const char *envp[])
290{
291 pid_ = run_proc(file, argv, envp, pipe_stdin_w_, pipe_stdout_r_, pipe_stderr_r_);
292
293 sd_stdin_.assign(dup(pipe_stdin_w_));
294 sd_stdout_.assign(dup(pipe_stdout_r_));
295 sd_stderr_.assign(dup(pipe_stderr_r_));
296
297 if (logger_) {
298 start_log(progname_.c_str(), Logger::LL_INFO, sd_stdout_, buf_stdout_);
299 start_log(progname_.c_str(), Logger::LL_WARN, sd_stderr_, buf_stderr_);
300 }
301}
302
303void
304SubProcess::start_log(const char * logname,
305 Logger::LogLevel log_level,
306 boost::asio::posix::stream_descriptor &sd,
307 boost::asio::streambuf & buf)
308{
309 boost::asio::async_read_until(sd,
310 buf,
311 '\n',
312 boost::bind(&SubProcess::handle_log_line,
313 this,
314 logname,
315 log_level,
316 boost::ref(sd),
317 boost::ref(buf),
318 boost::asio::placeholders::error,
319 boost::asio::placeholders::bytes_transferred));
320}
321
322void
323SubProcess::handle_log_line(const char * logname,
324 Logger::LogLevel log_level,
325 boost::asio::posix::stream_descriptor &sd,
326 boost::asio::streambuf & buf,
327 boost::system::error_code ec,
328 size_t bytes_read)
329{
330 if (ec) {
331 if (ec == boost::asio::error::eof) {
332 // stop logging
333 return;
334 } else {
335 logger_->log_error(logname,
336 "Failed to read log line %i (%s), continuing",
337 ec.value(),
338 ec.message().c_str());
339 }
340 } else {
341 std::string line;
342 std::istream in_stream(&buf);
343 std::getline(in_stream, line);
344 logger_->log(log_level, logname, "%s", line.c_str());
345 }
346 start_log(logname, log_level, sd, buf);
347}
348
349/** Check if process is alive.
350 * @return true if process is alive, false otherwise
351 */
352bool
354{
355 check_proc();
356 return pid_ > 0;
357}
358
359/** Get exit status of process once it ended.
360 * It is an error to call this on a sub-process which is still alive.
361 * @return exit status of process
362 * @exception Exception if called while process is still alive
363 */
364int
366{
367 if (alive()) {
368 throw Exception("Cannot get status while process still alive");
369 }
370 return exit_status_;
371}
372
373/** Check if the process is still alive. */
374void
376{
377 if (pid_ > 0) {
378 int status = 0;
379 if (waitpid(pid_, &status, WUNTRACED | WCONTINUED | WNOHANG) > 0) {
380 if (WIFEXITED(status)) {
381 exit_status_ = WEXITSTATUS(status);
382 if (exit_status_ != 0) {
383 logger_->log_error(progname_.c_str(),
384 "PID %i exited, status=%d",
385 pid_,
386 WEXITSTATUS(status));
387 }
388 pid_ = -1;
389 } else if (WIFSIGNALED(status)) {
390 logger_->log_error(progname_.c_str(),
391 "PID %i killed by signal %s",
392 pid_,
393 strsignal(WTERMSIG(status)));
394 pid_ = -1;
395 } else if (WIFSTOPPED(status)) {
396 logger_->log_warn(progname_.c_str(),
397 "PID %i stopped by signal %s",
398 pid_,
399 strsignal(WSTOPSIG(status)));
400 } else if (WIFCONTINUED(status)) {
401 logger_->log_warn(progname_.c_str(), "PID %i continued", pid_);
402 }
403 }
404 }
405}
406
407} // end namespace fawkes
Base class for exceptions in Fawkes.
Definition: exception.h:36
Interface for logging.
Definition: logger.h:42
virtual void log_warn(const char *component, const char *format,...)=0
Log warning message.
virtual void log(LogLevel level, const char *component, const char *format,...)
Log message of given log level.
Definition: logger.cpp:326
virtual void log_error(const char *component, const char *format,...)=0
Log error message.
LogLevel
Log level.
Definition: logger.h:51
@ LL_INFO
informational output about normal procedures
Definition: logger.h:53
@ LL_WARN
warning, should be investigated but software still functions, an example is that something was reques...
Definition: logger.h:54
bool alive()
Check if process is alive.
Definition: proc.cpp:353
int pipe_stderr_r() const
Get stderr pipe file descriptor.
Definition: proc.h:83
~SubProcess()
Destructor.
Definition: proc.cpp:177
int exit_status()
Get exit status of process once it ended.
Definition: proc.cpp:365
int pipe_stdin_w() const
Get stdin pipe file descriptor.
Definition: proc.h:67
void check_proc()
Check if the process is still alive.
Definition: proc.cpp:375
int pipe_stdout_r() const
Get stdout pipe file descriptor.
Definition: proc.h:75
pid_t pid() const
Get PID of sub-process.
Definition: proc.h:59
void kill(int signum)
Send a signal to the process.
Definition: proc.cpp:188
SubProcess(const char *progname, const char *file, const char *argv[], const char *envp[])
Constructor.
Definition: proc.cpp:55
Fawkes library namespace.