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.hpp>
27 
28 #ifdef HAVE_LIBDAEMON
29 # include <libdaemon/dfork.h>
30 #endif
31 #include <sys/types.h>
32 #include <sys/wait.h>
33 #include <string>
34 
35 namespace fawkes {
36 #if 0 /* just to make Emacs auto-indent happy */
37 }
38 #endif
39 
40 /** @class SubProcess <plugins/openprs/utils/proc.h>
41  * Sub-process execution with stdin/stdout/stderr redirection.
42  * This class executes a sub-process and monitors it and supports redirecting
43  * stdout/stderr to a logger.
44  * @author Tim Niemueller
45  */
46 
47 /** Constructor.
48  * @param progname name of program, component name for logging
49  * @param file file to execute, can be a program in the path or a
50  * fully qualified path
51  * @param argv array of arguments for the process, the last element
52  * must be NULL
53  * @param envp array of environment variables for the process, the
54  * last element must be NULL. Can be NULL to omit.
55  */
56 SubProcess::SubProcess(const char *progname, const char *file, const char *argv[], const char *envp[])
57  : progname_(progname),
58  io_service_work_(io_service_), logger_(NULL),
59  sd_stdin_(io_service_), sd_stdout_(io_service_), sd_stderr_(io_service_)
60 {
61  io_service_thread_ = std::thread([this]() { this->io_service_.run(); });
62  run_proc(file, argv, envp);
63 }
64 
65 
66 /** Constructor.
67  * @param progname name of program, component name for logging
68  * @param file file to execute, can be a program in the path or a
69  * fully qualified path
70  * @param argv array of arguments for the process, the last element
71  * must be NULL
72  * @param envp array of environment variables for the process, the
73  * last element must be NULL. Can be NULL to omit.
74  * @param logger logger to redirect stdout and stderr to
75  */
76 SubProcess::SubProcess(const char *progname, const char *file, const char *argv[], const char *envp[],
77  fawkes::Logger *logger)
78  : progname_(progname),
79  io_service_work_(io_service_), logger_(logger),
80  sd_stdin_(io_service_), sd_stdout_(io_service_), sd_stderr_(io_service_)
81 {
82  io_service_thread_ = std::thread([this]() { this->io_service_.run(); });
83  run_proc(file, argv, envp);
84 }
85 
86 
87 /** Destructor. */
89 {
90  this->kill(SIGTERM);
91  io_service_.stop();
92  io_service_thread_.join();
93 }
94 
95 
96 /** Send a signal to the process.
97  * @param signum signal number
98  */
99 void
100 SubProcess::kill(int signum)
101 {
102  if (pid_ > 0) ::kill(pid_, signum);
103 }
104 
105 
106 pid_t
107 SubProcess::run_proc(const char *file, const char *argv[], const char *envp[],
108  int & pipe_stdin_w, int & pipe_stdout_r, int & pipe_stderr_r)
109 {
110  int pipe_stdin[2];
111  int pipe_stdout[2];
112  int pipe_stderr[2];
113 
114  if (pipe(pipe_stdin) < 0) {
115  throw Exception(errno, "Failed to create OpenPRS stdin pipe (%s)", file);
116  }
117  if (pipe(pipe_stdout) < 0) {
118  close(pipe_stdin[0]);
119  close(pipe_stdin[1]);
120  throw Exception(errno, "Failed to create OpenPRS stdout pipe (%s)", file);
121  }
122  if (pipe(pipe_stderr) < 0) {
123  close(pipe_stdin[0]);
124  close(pipe_stdin[1]);
125  close(pipe_stdout[0]);
126  close(pipe_stdout[1]);
127  throw Exception(errno, "Failed to create OpenPRS stderr pipe (%s)", file);
128  }
129 
130  pid_t pid = fork();
131  if (pid < 0) { // fail
132  close(pipe_stdin[0]);
133  close(pipe_stdin[1]);
134  close(pipe_stdout[0]);
135  close(pipe_stdout[1]);
136  close(pipe_stderr[0]);
137  close(pipe_stderr[1]);
138  throw Exception(errno, "Failed to fork for OpenPRS %s", file);
139  } else if (pid) { // parent
140  close(pipe_stdin[0]);
141  close(pipe_stdout[1]);
142  close(pipe_stderr[1]);
143 
144  pipe_stdin_w = pipe_stdin[1];
145  pipe_stdout_r = pipe_stdout[0];
146  pipe_stderr_r = pipe_stderr[0];
147 
148  return pid;
149  } else { // child
150 #ifdef HAVE_LIBDAEMON
151  daemon_close_all(STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO,
152  pipe_stdin[0], pipe_stdout[1], pipe_stderr[1], -1);
153 #endif
154 
155  if (dup2(pipe_stdin[0], STDIN_FILENO) == -1) {
156  perror("Failed to dup stdin");
157  ::exit(-1);
158  }
159  if (dup2(pipe_stdout[1], STDOUT_FILENO) == -1) {
160  perror("Failed to dup stdout");
161  ::exit(-1);
162  }
163  if (dup2(pipe_stderr[1], STDERR_FILENO) == -1) {
164  perror("Failed to dup stderr");
165  ::exit(-1);
166  }
167 
168  close(pipe_stdin[0]);
169  close(pipe_stdout[0]);
170  close(pipe_stderr[0]);
171  close(pipe_stdin[1]);
172  close(pipe_stdout[1]);
173  close(pipe_stderr[1]);
174 
175  execvpe(file, (char * const *)argv, envp ? (char * const *)envp : environ);
176 
177  // execvpe only returns on error, which is when we should exit
178  perror("Failed to execute command");
179  ::exit(-2);
180  }
181 }
182 
183 
184 void
185 SubProcess::run_proc(const char *file, const char *argv[], const char *envp[])
186 {
187  pid_ = run_proc(file, argv, envp,
188  pipe_stdin_w_, pipe_stdout_r_, pipe_stderr_r_);
189 
190  sd_stdin_.assign(dup(pipe_stdin_w_));
191  sd_stdout_.assign(dup(pipe_stdout_r_));
192  sd_stderr_.assign(dup(pipe_stderr_r_));
193 
194  if (logger_) {
195  start_log(progname_.c_str(), Logger::LL_INFO, sd_stdout_, buf_stdout_);
196  start_log(progname_.c_str(), Logger::LL_WARN, sd_stderr_, buf_stderr_);
197  }
198 }
199 
200 
201 void
202 SubProcess::start_log(const char *logname, Logger::LogLevel log_level,
203  boost::asio::posix::stream_descriptor &sd, boost::asio::streambuf &buf)
204 {
205  boost::asio::async_read_until(sd, buf, '\n',
206  boost::bind(
207  &SubProcess::handle_log_line, this,
208  logname, log_level, boost::ref(sd), boost::ref(buf),
209  boost::asio::placeholders::error,
210  boost::asio::placeholders::bytes_transferred
211  ));
212 }
213 
214 
215 void
216 SubProcess::handle_log_line(const char *logname, Logger::LogLevel log_level,
217  boost::asio::posix::stream_descriptor &sd, boost::asio::streambuf &buf,
218  boost::system::error_code ec, size_t bytes_read)
219 {
220  if (ec) {
221  if (ec == boost::asio::error::eof) {
222  // stop logging
223  return;
224  } else {
225  logger_->log_error(logname, "Failed to read log line %i (%s), continuing", ec.value(),
226  ec.message().c_str());
227  }
228  } else {
229  std::string line;
230  std::istream in_stream(&buf);
231  std::getline(in_stream, line);
232  logger_->log(log_level, logname, "%s", line.c_str());
233  }
234  start_log(logname, log_level, sd, buf);
235 }
236 
237 
238 /** Check if the process is still alive. */
239 void
241 {
242  if (pid_ > 0) {
243  int status = 0;
244  if (waitpid(pid_, &status, WUNTRACED | WCONTINUED | WNOHANG) > 0) {
245  if (WIFEXITED(status)) {
246  logger_->log_error(progname_.c_str(), "PID %i exited, status=%d",
247  pid_, WEXITSTATUS(status));
248  pid_ = -1;
249  } else if (WIFSIGNALED(status)) {
250  logger_->log_error(progname_.c_str(), "PID %i killed by signal %s",
251  pid_, strsignal(WTERMSIG(status)));
252  pid_ = -1;
253  } else if (WIFSTOPPED(status)) {
254  logger_->log_warn(progname_.c_str(), "PID %i stopped by signal %s",
255  pid_, strsignal(WSTOPSIG(status)));
256  } else if (WIFCONTINUED(status)) {
257  logger_->log_warn(progname_.c_str(), "PID %i continued", pid_);
258  }
259  }
260  }
261 }
262 
263 } // end namespace fawkes
LogLevel
Log level.
Definition: logger.h:45
informational output about normal procedures
Definition: logger.h:47
SubProcess(const char *progname, const char *file, const char *argv[], const char *envp[])
Constructor.
Definition: proc.cpp:56
Fawkes library namespace.
int pipe_stdout_r() const
Get stdout pipe file descriptor.
Definition: proc.h:59
void kill(int signum)
Send a signal to the process.
Definition: proc.cpp:100
warning, should be investigated but software still functions, an example is that something was reques...
Definition: logger.h:48
void check_proc()
Check if the process is still alive.
Definition: proc.cpp:240
int pipe_stderr_r() const
Get stderr pipe file descriptor.
Definition: proc.h:64
Base class for exceptions in Fawkes.
Definition: exception.h:36
int pipe_stdin_w() const
Get stdin pipe file descriptor.
Definition: proc.h:54
~SubProcess()
Destructor.
Definition: proc.cpp:88
virtual void log_warn(const char *component, const char *format,...)=0
Log warning message.
virtual void log_error(const char *component, const char *format,...)=0
Log error message.
pid_t pid() const
Get PID of sub-process.
Definition: proc.h:49
virtual void log(LogLevel level, const char *component, const char *format,...)
Log message of given log level.
Definition: logger.cpp:315
Interface for logging.
Definition: logger.h:34