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 
37 namespace 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  */
55 SubProcess::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  */
81 SubProcess::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  */
107 SubProcess::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  */
144 SubProcess::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  */
187 void
188 SubProcess::kill(int signum)
189 {
190  if (pid_ > 0)
191  ::kill(pid_, signum);
192 }
193 
194 pid_t
195 SubProcess::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 
288 void
289 SubProcess::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 
303 void
304 SubProcess::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 
322 void
323 SubProcess::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  */
352 bool
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  */
364 int
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. */
374 void
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.