DaZeus  2.0
 All Classes Namespaces Files Functions Variables Enumerations Enumerator Friends Macros
pluginmonitor.cpp
Go to the documentation of this file.
1 
6 #include "pluginmonitor.h"
7 #include "utils.h"
8 #include "config.h"
9 #include <unistd.h>
10 #include <signal.h>
11 #include <sys/stat.h>
12 #include <sys/wait.h>
13 #include <time.h>
14 #include <assert.h>
15 #include <iostream>
16 #include <errno.h>
17 #include <string.h>
18 
19 #define PLUGIN_EXIT_VALUE_CHDIR -7
20 #define PLUGIN_EXIT_VALUE_EXEC -8
21 
22 // after an hour, consider a failure as a stand-alone fault, don't link it
23 // to earlier failures anymore
24 #define PLUGIN_RUNTIME_RESET_FAILURE 3600
25 
26 // maximum time to wait between plugin restarts
27 #define PLUGIN_RUNTIME_MAX_WAIT_TIME 300
28 
29 
30 namespace dazeus {
31  struct PluginState {
32  PluginState(PluginConfig *c, std::string network)
33  : config(c), will_autostart(true), pid(0), network(network)
34  , num_failures(0), last_start(0) {
35  assert(config != NULL);
36  assert(config->per_network || network.length() == 0);
37  assert(!config->per_network || network.length() > 0);
38  }
40 
43  pid_t pid;
44  std::string network;
46  time_t last_start;
47  };
48 }
49 
50 bool directory_exists(std::string dir) {
51  struct stat buf;
52  int res = stat(dir.c_str(), &buf);
53  if(res == -1) return false;
54  return (buf.st_mode & S_IFDIR) == S_IFDIR;
55 }
56 
57 bool file_exists(std::string file) {
58  struct stat buf;
59  int res = stat(file.c_str(), &buf);
60  if(res == -1) return false;
61  return (buf.st_mode & S_IFREG) == S_IFREG;
62 }
63 
64 bool executable_exists(std::string file) {
65  return access(file.c_str(), X_OK) == 0;
66 }
67 
68 dazeus::PluginMonitor::PluginMonitor(SocketConfig *socket, std::string plugindir,
69  const std::vector<PluginConfig*> &plugins,
70  const std::vector<NetworkConfig*> &networks)
71 : pluginDirectory_(plugindir)
72 , socket_(socket)
73 , should_run_(1)
74 {
75  std::vector<PluginConfig*>::const_iterator it;
76  for(it = plugins.begin(); it != plugins.end(); ++it) {
77  PluginConfig *config = *it;
78  assert(config->name.length() > 0);
79 
80  std::vector<PluginState*>::const_iterator sit;
81  for(sit = state_.begin(); sit != state_.end(); ++sit) {
82  if((*sit)->config->name == config->name) {
83  throw std::runtime_error("Multiple plugins exist with name " + config->name);
84  }
85  }
86  if(config->per_network) {
87  std::vector<NetworkConfig*>::const_iterator nit;
88  for(nit = networks.begin(); nit != networks.end(); ++nit) {
89  PluginState *s = new PluginState(config, (*nit)->name);
90  state_.push_back(s);
91  }
92  } else {
93  PluginState *s = new PluginState(config, "");
94  state_.push_back(s);
95  }
96  }
97 }
98 
100  std::vector<PluginState*>::iterator it;
101 
102  // Try a graceful stop of all plugins...
103  for(it = state_.begin(); it != state_.end(); ++it) {
104  if((*it)->pid != 0)
105  stop_plugin(*it, false);
106  }
107 
108  sleep(2);
109  runOnce();
110 
111  for(it = state_.begin(); it != state_.end(); ++it) {
112  if((*it)->pid != 0)
113  stop_plugin(*it, true);
114  }
115 
116  sleep(2);
117  runOnce();
118 
119  for(it = state_.begin(); it != state_.end(); ++it) {
120  if((*it)->pid != 0) {
121  std::cerr << "In PluginMonitor destructor, failed to stop plugin " << (*it)->config->name << std::endl;
122  }
123  delete *it;
124  }
125 }
126 
127 void dazeus::PluginMonitor::stop_plugin(PluginState *state, bool hard) {
128  assert(state != NULL);
129  assert(state->pid != 0);
130  PluginConfig *config = state->config;
131  assert(config != NULL);
132 
133  int signal = hard ? SIGKILL : SIGINT;
134 
135  if(kill(state->pid, signal) < 0) {
136  std::cerr << "Failed to kill plugin " << config->name << ": " << strerror(errno) << std::endl;
137  }
138  // If succesfully killed, runOnce() will set the pid to 0
139 }
140 
141 void dazeus::PluginMonitor::plugin_failed(PluginState *state, bool permanent) {
142  if(permanent) {
143  state->will_autostart = false;
144  } else {
145  state->num_failures++;
146  }
147 }
148 
149 bool dazeus::PluginMonitor::start_plugin(PluginState *state) {
150  assert(state != NULL);
151  assert(state->pid == 0);
152  PluginConfig *config = state->config;
153  assert(config != NULL);
154 
155  state->last_start = time(NULL);
156 
157  // Configuration loading
158  std::string path = config->path;
159  if(path.length() == 0) {
160  // If not set, defaults to the name of the plugin
161  path = config->name;
162  }
163  if(path[0] != '/') {
164  // If relative, counts from the Plugins directory as set in
165  // dazeus.conf.
166  path = pluginDirectory_ + "/" + path;
167  }
168 
169  std::string executable = config->executable;
170  if(executable.length() == 0) {
171  // If not set, defaults to the name of the plugin.
172  executable = config->name;
173  }
174 
175  std::vector<std::string> arguments;
176  std::string current_arg;
177  bool in_quotes = false;
178  bool in_doublequotes = false;
179  bool in_escape = false;
180  for(unsigned i = 0; i < config->parameters.length(); ++i) {
181  char c = config->parameters[i];
182  if(in_escape) {
183  current_arg += c;
184  in_escape = false;
185  }
186  else if(in_quotes || in_doublequotes) {
187  assert(!(in_quotes && in_doublequotes));
188  if((in_quotes && c == '\'') || (in_doublequotes && c == '"')) {
189  in_quotes = in_doublequotes = false;
190  arguments.push_back(current_arg);
191  current_arg.clear();
192  } else {
193  current_arg += c;
194  }
195  }
196  else if(c == '\'') {
197  in_quotes = true;
198  }
199  else if(c == '"') {
200  in_doublequotes = true;
201  }
202  else if(isspace(c)) {
203  if(current_arg.length() > 0) {
204  arguments.push_back(current_arg);
205  current_arg.clear();
206  }
207  }
208  else if(c == '%') {
209  if(i + 1 == config->parameters.length()) {
210  current_arg += '%';
211  } else {
212  char d = config->parameters[i+1];
213  switch(d) {
214  case 's': current_arg += socket_->toString(); ++i; break;
215  case 'n': current_arg += state->network; ++i; break;
216  default: current_arg += '%'; break;
217  }
218  }
219  }
220  else {
221  current_arg += c;
222  }
223  }
224  if(current_arg.length() > 0) {
225  arguments.push_back(current_arg);
226  }
227 
228  // Sanity checking (to decrease the chance of errors in the child)
229  if(!directory_exists(path)) {
230  std::cerr << "Failed to run plugin " << config->name << ": its directory does not exist" << std::endl;
231  plugin_failed(state);
232  return false;
233  }
234 
235  std::string full_executable = executable;
236  if(full_executable[0] != '/') {
237  // relative path
238  full_executable = path + '/' + executable;
239  }
240  if(!file_exists(full_executable)) {
241  std::cerr << "Failed to run plugin " << config->name << ": its executable does not exist" << std::endl;
242  plugin_failed(state);
243  return false;
244  } else if(!executable_exists(full_executable)) {
245  std::cerr << "Failed to run plugin " << config->name << ": its executable is not executable" << std::endl;
246  plugin_failed(state);
247  return false;
248  }
249 
250  pid_t res = fork_plugin(path, arguments, executable);
251  if(res == -1) {
252  std::cerr << "Failed to run plugin " << config->name << ": " << strerror(errno) << std::endl;
253  plugin_failed(state);
254  return false;
255  }
256 
257  // we are the parent, plugin is running
258  state->pid = res;
259  std::cout << "Plugin " << config->name << " started, PID " << state->pid << std::endl;
260  return true;
261 }
262 
263 pid_t dazeus::PluginMonitor::fork_plugin(const std::string path, const std::vector<std::string> arguments, const std::string executable) {
264  pid_t res = fork();
265  if(res != 0) {
266  return res;
267  }
268 
269  // we are the child; chdir() and execve()
270  // path executable per_network parameters
271  if(chdir(path.c_str()) < 0) {
273  }
274 
275  // prepare *char[] arguments
276  char **child_argv = (char**)malloc(arguments.size() * sizeof(char*) + 2);
277  child_argv[0] = strdup(executable.c_str());
278  for(unsigned i = 0; i < arguments.size(); ++i) {
279  child_argv[i + 1] = strdup(arguments[i].c_str());
280  }
281  child_argv[arguments.size() + 1] = NULL;
282 
283  // execv() takes a path; it never queries PATH, even if the first parameter
284  // does not contain any slashes.
285  execv(executable.c_str(), child_argv);
287 }
288 
290  if(!should_run_) {
291  return;
292  }
293 
294  // First, block CHLD signals while we process childs
295  sigset_t signalblock;
296  sigemptyset(&signalblock);
297  sigaddset(&signalblock, SIGCHLD);
298  sigprocmask(SIG_BLOCK, &signalblock, NULL);
299 
300  // Process died childs
301  pid_t child;
302  int child_status;
303  std::vector<PluginState*>::iterator it;
304  errno = 0;
305  while((child = wait4(-1, &child_status, WNOHANG, NULL))) {
306  if(child == -1 && errno == ECHILD) {
307  // no child processes
308  break;
309  } else if(child == -1) {
310  std::cerr << "wait4() returned an error: " << strerror(errno) << std::endl;
311  break;
312  }
313 
314  PluginState *state = NULL;
315  for(it = state_.begin(); it != state_.end(); ++it) {
316  if((*it)->pid == child) {
317  state = *it;
318  break;
319  }
320  }
321  if(state == NULL) {
322  std::cerr << "wait4() returned a PID that is not ours: " << child << std::endl;
323  continue;
324  }
325 
326  assert(state->config);
327 
328  if(WIFEXITED(child_status)) {
329  std::cerr << "Plugin " << state->config->name << " exited with code " << WEXITSTATUS(child_status) << std::endl;
330  } else if(WIFSIGNALED(child_status)) {
331  std::string coredumped = WCOREDUMP(child_status) ? " (core dumped)" : "";
332  std::cerr << "Plugin " << state->config->name << " killed by signal " << WTERMSIG(child_status) << coredumped << std::endl;
333  } else {
334  std::cerr << "Plugin signaled but did not quit? Ignoring..." << std::endl;
335  continue;
336  }
337 
338  state->pid = 0;
339  if(state->last_start + PLUGIN_RUNTIME_RESET_FAILURE < time(NULL)) {
340  // more than PLUGIN_RUNTIME_RESET_FAILURE seconds have passed
341  // since the last start attempt; assume the last start was succesful
342  // up to now and reset num_failures
343  state->num_failures = 0;
344  }
345  state->num_failures++;
346  }
347 
348  // Process plugins that should start now
349  bool waiting_plugin = false;
350  for(it = state_.begin(); it != state_.end(); ++it) {
351  PluginState *state = *it;
352  assert(state != NULL);
353 
354  if(!state->will_autostart) {
355  // never start it anyway
356  continue;
357  }
358 
359  if(state->pid != 0) {
360  // it's running fine
361  continue;
362  }
363 
364  int wait_seconds = 0;
365  if(state->num_failures > 0) {
366  wait_seconds = 5 * (1 << (state->num_failures - 1));
367  }
368  if(wait_seconds > PLUGIN_RUNTIME_MAX_WAIT_TIME) {
369  wait_seconds = PLUGIN_RUNTIME_MAX_WAIT_TIME;
370  }
371 
372  if(state->last_start + wait_seconds > time(NULL)) {
373  // The new starting point hasn't passed yet, don't
374  // re-start the plugin yet
375  waiting_plugin = true;
376  continue;
377  }
378 
379  // See if we can auto-run it
380  if(!start_plugin(state)) {
381  waiting_plugin = true;
382  }
383  }
384 
385  // Only run again if there is a plugin waiting to be started again
386  should_run_ = waiting_plugin;
387 
388  // Allow CHLD signals to interrupt us again
389  sigprocmask(SIG_UNBLOCK, &signalblock, NULL);
390 }