Fawkes API  Fawkes Development Version
run.cpp
1 
2 /***************************************************************************
3  * run.cpp - Fawkes run functions
4  *
5  * Created: Wed May 04 23:23:23 2011
6  * Copyright 2006-2011 Tim Niemueller [www.niemueller.de]
7  *
8  ****************************************************************************/
9 
10 /* This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version. A runtime exception applies to
14  * this software (see LICENSE.GPL_WRE file mentioned below for details).
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU Library General Public License for more details.
20  *
21  * Read the full text in the LICENSE.GPL_WRE file in the doc directory.
22  */
23 
24 #include <baseapp/run.h>
25 #include <baseapp/daemonize.h>
26 #include <baseapp/main_thread.h>
27 #include <baseapp/thread_manager.h>
28 
29 #include <core/threading/thread.h>
30 
31 #include <blackboard/local.h>
32 #include <config/sqlite.h>
33 #include <config/yaml.h>
34 #include <config/net_handler.h>
35 #include <utils/ipc/shm.h>
36 #include <utils/system/argparser.h>
37 #include <logging/multi.h>
38 #include <logging/console.h>
39 #include <logging/liblogger.h>
40 #include <logging/factory.h>
41 #include <logging/network.h>
42 #ifdef HAVE_LOGGING_FD_REDIRECT
43 # include <logging/fd_redirect.h>
44 #endif
45 #include <utils/time/clock.h>
46 #include <utils/time/time.h>
47 #include <netcomm/fawkes/network_manager.h>
48 #include <plugin/manager.h>
49 #include <plugin/net/handler.h>
50 #include <aspect/manager.h>
51 #ifdef HAVE_TF
52 # include <tf/transform_listener.h>
53 # include <tf/transformer.h>
54 #endif
55 
56 #include <sys/types.h>
57 #include <sys/stat.h>
58 #include <pwd.h>
59 #include <grp.h>
60 #include <cstdlib>
61 #include <cstdio>
62 #include <cstring>
63 #include <unistd.h>
64 #include <signal.h>
65 #include <fnmatch.h>
66 
67 namespace fawkes {
68  namespace runtime {
69 #if 0 /* just to make Emacs auto-indent happy */
70  }
71 }
72 #endif
73 
74 ArgumentParser * argument_parser = NULL;
75 FawkesMainThread * main_thread = NULL;
76 MultiLogger * logger = NULL;
77 NetworkLogger * network_logger = NULL;
78 BlackBoard * blackboard = NULL;
79 Configuration * config = NULL;
80 PluginManager * plugin_manager = NULL;
81 AspectManager * aspect_manager = NULL;
82 ThreadManager * thread_manager = NULL;
83 FawkesNetworkManager * network_manager = NULL;
84 ConfigNetworkHandler * nethandler_config = NULL;
85 PluginNetworkHandler * nethandler_plugin = NULL;
86 Clock * clock = NULL;
87 SharedMemoryRegistry * shm_registry;
88 InitOptions * init_options = NULL;
89 tf::Transformer * tf_transformer = NULL;
90 tf::TransformListener * tf_listener = NULL;
91 Time * start_time = NULL;
92 #ifdef HAVE_LOGGING_FD_REDIRECT
93 LogFileDescriptorToLog * log_fd_redirect_stderr_ = NULL;
94 LogFileDescriptorToLog * log_fd_redirect_stdout_ = NULL;
95 #endif
96 
97 // this is NOT shared to the outside
98 FawkesMainThread::Runner * runner = NULL;
99 
100 bool
101 init(int argc, char **argv, int & retval)
102 {
103  return init(InitOptions(argc, argv), retval);
104 }
105 
106 
107 bool
108 init(InitOptions options, int & retval)
109 {
110  init_options = new InitOptions(options);
111 
112  if (init_options->show_help()) return true;
113 
114  if ( options.daemonize() ) {
115  fawkes::daemon::init(options.daemon_pid_file(), options.basename());
116  if (options.daemonize_kill()) {
117  fawkes::daemon::kill();
118  retval = 0;
119  return false;
120  } else if (options.daemonize_status()) {
121  retval = fawkes::daemon::running() ? 0 : 1;
122  return false;
123  } else {
124  if (fawkes::daemon::start()) {
125  retval = 0;
126  return false;
127  }
128  }
129  }
130 
131  // *** set user group if requested
132  const char *user = NULL;
133  const char *group = NULL;
134  if (options.has_username()) {
135  user = options.username();
136  }
137  if (options.has_groupname()) {
138  group = options.groupname();
139  }
140 
141  if (user != NULL) {
142  struct passwd *pw;
143  if (! (pw = getpwnam(user))) {
144  printf("Failed to find user %s, check -u argument.\n", user);
145  retval = 203;
146  return false;
147  }
148  int r = 0;
149  r = setreuid(pw->pw_uid, pw->pw_uid);
150  if (r < 0) {
151  perror("Failed to drop privileges (user)");
152  }
153  }
154 
155  if (group != NULL) {
156  struct group *gr;
157  if (! (gr = getgrnam(group))) {
158  printf("Failed to find group %s, check -g argument.\n", user);
159  retval = 204;
160  return false;
161  }
162  int r = 0;
163  r = setregid(gr->gr_gid, gr->gr_gid);
164  if (r < 0) {
165  perror("Failed to drop privileges (group)");
166  }
167  }
168 
169  // *** setup base thread and shm registry
171 
172  shm_registry = NULL;
173  struct passwd *uid_pw = getpwuid(getuid());
174  if (uid_pw == NULL) {
175  shm_registry = new SharedMemoryRegistry();
176  } else {
177  char *registry_name;
178  if (asprintf(&registry_name, USER_SHM_NAME, uid_pw->pw_name) == -1) {
179  shm_registry = new SharedMemoryRegistry();
180  } else {
181  shm_registry = new SharedMemoryRegistry(registry_name);
182  free(registry_name);
183  }
184  }
185 
186  if (! shm_registry) {
187  throw Exception("Failed to create shared memory registry");
188  }
189 
190  // *** setup logging
191  if (options.has_loggers()) {
192  try {
193  logger = LoggerFactory::multilogger_instance(options.loggers());
194  } catch (Exception &e) {
195  e.append("Initializing multi logger failed");
196  throw;
197  }
198  } else {
199  logger = new MultiLogger(new ConsoleLogger());
200  }
201 
202  logger->set_loglevel(options.log_level());
203  LibLogger::init(logger);
204 
205  // *** Prepare home dir directory, just in case
206  const char *homedir = getenv("HOME");
207  if (homedir) {
208  char *userdir;
209  if (asprintf(&userdir, "%s/%s", homedir, USERDIR) != -1) {
210  if (access(userdir, W_OK) != 0) {
211  if (mkdir(userdir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1) {
212  logger->log_warn("FawkesMainThread", "Failed to create .fawkes "
213  "directory %s, trying without", userdir);
214  }
215  }
216  free(userdir);
217  }
218  }
219 
220  // *** setup config
221 
222  SQLiteConfiguration *sqconfig = NULL;
223 
224  if (options.config_file() &&
225  fnmatch("*.sql", options.config_file(), FNM_PATHNAME) == 0)
226  {
227  sqconfig = new SQLiteConfiguration(CONFDIR);
228  config = sqconfig;
229  } else {
230  config = new YamlConfiguration(CONFDIR);
231  }
232 
233  config->load(options.config_file());
234 
235  if (sqconfig) {
236  try {
237  SQLiteConfiguration::SQLiteValueIterator *i = sqconfig->modified_iterator();
238  while (i->next()) {
239  std::string modtype = i->get_modtype();
240  if (modtype == "changed") {
241  logger->log_warn("FawkesMainThread", "Default config value CHANGED: %s"
242  "(was: %s now: %s)", i->path(),
243  i->get_oldvalue().c_str(), i->get_as_string().c_str());
244  } else if (modtype == "erased") {
245  logger->log_warn("FawkesMainThread", "Default config value ERASED: %s",
246  i->path());
247  } else {
248  logger->log_debug("FawkesMainThread", "Default config value ADDED: %s "
249  "(value: %s)", i->path(), i->get_as_string().c_str());
250  }
251  }
252  delete i;
253  } catch (Exception &e) {
254  logger->log_warn("FawkesMainThread", "Failed to read modified default "
255  "config values, no dump?");
256  }
257  }
258 
259  if (! options.has_loggers()) {
260  // Allow configuration override from config
261  if (config->exists("/fawkes/mainapp/loggers")) {
262  try {
263  std::string loggers = config->get_string("/fawkes/mainapp/loggers");
264  MultiLogger *new_logger =
265  LoggerFactory::multilogger_instance(loggers.c_str(), options.log_level());
266  logger = new_logger;
268  LibLogger::init(new_logger);
269  } catch (Exception &e) {
270  logger->log_warn("FawkesMainThread", "Loggers set in config file, "
271  "but failed to read, exception follows.");
272  logger->log_warn("FawkesMainThread", e);
273  }
274  }
275  }
276 
277  if (config->exists("/fawkes/mainapp/log_stderr_as_warn")) {
278  try {
279  bool log_stderr_as_warn = config->get_bool("/fawkes/mainapp/log_stderr_as_warn");
280  if (log_stderr_as_warn) {
281 #ifdef HAVE_LOGGING_FD_REDIRECT
282  log_fd_redirect_stderr_ =
283  new LogFileDescriptorToLog(STDERR_FILENO, logger, "stderr", Logger::LL_WARN);
284 #else
285  logger->log_warn("FawkesMainThread", "stderr log redirection enabled but not available at compile time");
286 #endif
287  }
288  } catch (Exception &e) {} // ignored
289  }
290 
291  // *** Determine network parameters
292  bool enable_ipv4 = true;
293  bool enable_ipv6 = true;
294  std::string listen_ipv4;
295  std::string listen_ipv6;
296  unsigned int net_tcp_port = 1910;
297  std::string net_service_name = "Fawkes on %h";
298  if (options.has_net_tcp_port()) {
299  net_tcp_port = options.net_tcp_port();
300  } else {
301  try {
302  net_tcp_port = config->get_uint("/network/fawkes/tcp_port");
303  } catch (Exception &e) {} // ignore, we stick with the default
304  }
305 
306  if (options.has_net_service_name()) {
307  net_service_name = options.net_service_name();
308  } else {
309  try {
310  net_service_name = config->get_string("/network/fawkes/service_name");
311  } catch (Exception &e) {} // ignore, we stick with the default
312  }
313 
314  if (net_tcp_port > 65535) {
315  logger->log_warn("FawkesMainThread", "Invalid port '%u', using 1910",
316  net_tcp_port);
317  net_tcp_port = 1910;
318  }
319 
320  try {
321  enable_ipv4 = config->get_bool("/network/ipv4/enable");
322  } catch (Exception &e) {} // ignore, we stick with the default
323  try {
324  enable_ipv6 = config->get_bool("/network/ipv6/enable");
325  } catch (Exception &e) {} // ignore, we stick with the default
326 
327  try {
328  listen_ipv4 = config->get_string("/network/ipv4/listen");
329  } catch (Exception &e) {} // ignore, we stick with the default
330  try {
331  listen_ipv6 = config->get_string("/network/ipv6/listen");
332  } catch (Exception &e) {} // ignore, we stick with the default
333 
334  if (! enable_ipv4) {
335  logger->log_warn("FawkesMainThread", "Disabling IPv4 support");
336  }
337  if (! enable_ipv6) {
338  logger->log_warn("FawkesMainThread", "Disabling IPv6 support");
339  }
340  if (! listen_ipv4.empty()) {
341  logger->log_info("FawkesMainThread", "Listening on IPv4 address %s", listen_ipv4.c_str());
342  }
343  if (! listen_ipv6.empty()) {
344  logger->log_info("FawkesMainThread", "Listening on IPv6 address %s", listen_ipv4.c_str());
345  }
346 
347  // *** Setup blackboard
348  std::string bb_magic_token = "";
349  unsigned int bb_size = 2097152;
350  try {
351  bb_magic_token = config->get_string("/fawkes/mainapp/blackboard_magic_token");
352  logger->log_info("FawkesMainApp", "BlackBoard magic token defined. "
353  "Using shared memory BlackBoard.");
354  } catch (Exception &e) {
355  // ignore
356  }
357  try {
358  bb_size = config->get_uint("/fawkes/mainapp/blackboard_size");
359  } catch (Exception &e) {
360  logger->log_warn("FawkesMainApp", "BlackBoard size not defined. "
361  "Will use %u, saving to default DB", bb_size);
362  config->set_default_uint("/fawkes/mainapp/blackboard_size", bb_size);
363  }
364 
365  // Cleanup stale BlackBoard shared memory segments if requested
366  if ( options.bb_cleanup()) {
367  LocalBlackBoard::cleanup(bb_magic_token.c_str(),
368  /* output with lister? */ true);
370  }
371 
372  LocalBlackBoard *lbb = NULL;
373  if ( bb_magic_token == "") {
374  lbb = new LocalBlackBoard(bb_size);
375  } else {
376  lbb = new LocalBlackBoard(bb_size, bb_magic_token.c_str());
377  }
378  blackboard = lbb;
379 
380 #ifdef HAVE_TF
381  tf_transformer = new tf::Transformer();
382  tf_listener = new tf::TransformListener(blackboard, tf_transformer);
383 #endif
384 
385  aspect_manager = new AspectManager();
386  thread_manager = new ThreadManager(aspect_manager, aspect_manager);
387 
388  plugin_manager = new PluginManager(thread_manager, config,
389  "/fawkes/meta_plugins/",
390  options.plugin_module_flags(),
391  options.init_plugin_cache());
392  network_manager = new FawkesNetworkManager(thread_manager,
393  enable_ipv4, enable_ipv6,
394  listen_ipv4, listen_ipv6,
395  net_tcp_port,
396  net_service_name.c_str());
397  nethandler_config = new ConfigNetworkHandler(config,
398  network_manager->hub());
399 
400  nethandler_plugin = new PluginNetworkHandler(plugin_manager,
401  network_manager->hub());
402  nethandler_plugin->start();
403 
404  network_logger = new NetworkLogger(network_manager->hub(),
405  logger->loglevel());
406  logger->add_logger(network_logger);
407 
408  clock = Clock::instance();
409  start_time = new Time(clock);
410 
411  lbb->start_nethandler(network_manager->hub());
412 
413 
414  // *** Create main thread, but do not start, yet
415  main_thread = new fawkes::FawkesMainThread(config, logger,
416  thread_manager,
417  plugin_manager,
418  options.load_plugin_list(),
419  options.default_plugin());
420 
421  aspect_manager->register_default_inifins(blackboard,
422  thread_manager->aspect_collector(),
423  config, logger, clock,
424  network_manager->hub(),
425  main_thread, logger,
426  thread_manager,
427  network_manager->nnresolver(),
428  network_manager->service_publisher(),
429  network_manager->service_browser(),
430  plugin_manager, tf_transformer);
431 
432  retval = 0;
433  return true;
434 }
435 
436 void
437 cleanup()
438 {
439  if (init_options->daemonize()) {
440  fawkes::daemon::cleanup();
441  }
442 
443  if (nethandler_plugin) {
444  nethandler_plugin->cancel();
445  nethandler_plugin->join();
446  }
447 
448  if (logger) {
449  // Must delete network logger first since network manager
450  // has to die before the LibLogger is finalized.
451  logger->remove_logger(network_logger);
452  delete network_logger;
453  }
454 
455  delete nethandler_config;
456  delete nethandler_plugin;
457  delete plugin_manager;
458  delete main_thread;
459 #ifdef HAVE_TF
460  delete tf_listener;
461  delete tf_transformer;
462 #endif
463  delete blackboard;
464  delete config;
465  delete argument_parser;
466  delete init_options;
467  delete network_manager;
468  delete thread_manager;
469  delete aspect_manager;
470  delete shm_registry;
471 #ifdef HAVE_LOGGING_FD_REDIRECT
472  delete log_fd_redirect_stderr_;
473  delete log_fd_redirect_stdout_;
474 #endif
475 
476  main_thread = NULL;
477  argument_parser = NULL;
478  init_options = NULL;
479  nethandler_config = NULL;
480  nethandler_plugin = NULL;
481  plugin_manager = NULL;
482  network_manager = NULL;
483  config = NULL;
484  thread_manager = NULL;
485  aspect_manager = NULL;
486  shm_registry = NULL;
487  blackboard = NULL;
488 #ifdef HAVE_LOGGING_FD_REDIRECT
489  log_fd_redirect_stderr_ = NULL;
490  log_fd_redirect_stdout_ = NULL;
491 #endif
492 
493  // implicitly frees multi_logger and all sub-loggers
495  logger = NULL;
496 
497  delete start_time;
498  start_time = NULL;
499  Clock::finalize();
500  clock = NULL;
501 
502  try {
504  } catch (Exception &e) {} // ignored, can fire on show_help
505 
506  // should be last, because of not disabled this hosts the
507  // default signal handlers
508  delete runner;
509  runner = 0;
510 }
511 
512 void
513 run()
514 {
515  if (init_options->show_help()) {
516  print_usage(init_options->basename());
517  return;
518  }
519 
520  bool defsigs = init_options->default_signal_handlers();
521  runner = new FawkesMainThread::Runner(main_thread, defsigs);
522 
523  try {
524  runner->run();
525  } catch (Exception &e) {
526  printf("Running Fawkes failed\n");
527  e.print_trace();
528  }
529 }
530 
531 
532 /** Quit Fawkes.
533  * You can call this from within Fawkes to quit Fawkes. Use with extreme care an
534  * only rarely.
535  * This sends SIGINT to the local process. This triggers the quit routine but also
536  * takes a currently running init into account. This is prone to the same potential
537  * problems as a SIGINT received otherwise, e.g. a never-ending thread blocking
538  * the main thread from cancelling.
539  */
540 void
541 quit()
542 {
543  kill(getpid(), SIGINT);
544 }
545 
546 void
547 print_usage(const char *progname)
548 {
549  printf("Fawkes Main Application - Usage Instructions\n"
550  "================================================"
551  "===============================\n"
552  "Usage: %s [options] [plugins]\n"
553  "where\n"
554  " [plugins] is a space-separated list of plugins to load on startup in given order\n"
555  " [options] is one or more of:\n"
556  " -h These help instructions\n"
557  " -C Cleanup old BB and shared memory segments\n"
558  " -c config file Configuration file to load.\n"
559  " Examples: default.sql or config.yaml\n"
560  " -d Enable debug output\n"
561  " -q[qqq] Quiet mode, -q omits debug, -qq debug and"
562  "info,\n "
563  "-qqq omit debug, info and warn, -qqqq no output\n"
564  " -l level Set log level directly mutually exclusive"
565  "with -q,\n "
566  "level is one of debug, info, warn, error, or none\n"
567  " -L loggers Define loggers. By default this setting is"
568  "read from\n "
569  "config (console logger if unset). Format is:\n"
570  " logger:args[;logger2:args2[!...]]\n"
571  " Currently supported:\n"
572  " console, file:file.log, network logger always added\n"
573  " -p plugins List of plugins to load on startup in given order\n"
574  " -P port TCP port to listen on for Fawkes network connections.\n"
575  " --net-service-name=name mDNS service name to use.\n"
576  " -u user Drop privileges and run as given user.\n"
577  " -g group Drop privileges and run as given group.\n"
578 #ifdef HAVE_LIBDAEMON
579  " -D[pid file] Run daemonized in the background, pid file "
580  "is optional,\n "
581  "default is /var/run/fawkes.pid, must be absolute path.\n"
582  " -D[pid file] -k Kill a daemonized Fawkes running in the"
583  "background\n"
584  " -D[pid file] -s Check status of daemon.\n"
585 #endif
586  "\n", progname);
587 }
588 
589 
590 /** Get Fawkes uptime.
591  * Returns the time in seconds since Fawkes was started.
592  * This creates a new time for the system clock and subtracts the start time.
593  * @return time in seconds since Fawkes was started
594  */
595 float
596 uptime()
597 {
598  if (start_time) {
599  fawkes::Time now(clock);
600  return now - start_time;
601  } else {
602  return 0.0;
603  }
604 }
605 
606 } // end namespace runtime
607 } // end namespace fawkes
static void finalize()
Delete internal logger.
Definition: liblogger.cpp:83
virtual void load(const char *file_path)=0
Load configuration.
static Clock * instance()
Clock initializer.
Definition: clock.cpp:65
static void cleanup(const char *magic_token, bool use_lister=false)
Cleanup orphaned BlackBoard segments.
Definition: local.cpp:177
Fawkes library namespace.
virtual bool get_bool(const char *path)=0
Get value from configuration which is of type bool.
warning, should be investigated but software still functions, an example is that something was reques...
Definition: logger.h:48
A class for handling time.
Definition: time.h:91
static void init_main()
Initialize Thread wrapper instance for main thread.
Definition: thread.cpp:1271
static MultiLogger * multilogger_instance(const char *as, Logger::LogLevel default_ll=Logger::LL_DEBUG)
Create MultiLogger instance.
Definition: factory.cpp:143
InitOptions & default_signal_handlers(bool enable)
Set default signal handlers.
virtual void set_loglevel(LogLevel level)
Sets the log level.
Definition: multi.cpp:146
void register_default_inifins(BlackBoard *blackboard, ThreadCollector *collector, Configuration *config, Logger *logger, Clock *clock, FawkesNetworkHub *fnethub, MainLoopEmployer *mloop_employer, LoggerEmployer *logger_employer, BlockedTimingExecutor *btexec, NetworkNameResolver *nnresolver, ServicePublisher *service_publisher, ServiceBrowser *service_browser, PluginManager *pmanager, tf::Transformer *tf_listener)
Register default aspect initializer/finalizer.
Definition: manager.cpp:226
virtual void set_default_uint(const char *path, unsigned int uint)=0
Set new default value in configuration of type unsigned int.
static void destroy_main()
Destroy main thread wrapper instance.
Definition: thread.cpp:1285
virtual void log_warn(const char *component, const char *format,...)
Log warning message.
Definition: multi.cpp:227
InitOptions & show_help(bool show_help)
Set to show help.
void add_logger(Logger *logger)
Add a logger.
Definition: multi.cpp:115
static void finalize()
Finalize.
Definition: clock.cpp:77
virtual void log_info(const char *component, const char *format,...)
Log informational message.
Definition: multi.cpp:205
static void init(MultiLogger *multi_logger=NULL)
Initialize logger.
Definition: liblogger.cpp:63
void run()
Run main thread.
virtual bool exists(const char *path)=0
Check if a given value exists.
virtual unsigned int get_uint(const char *path)=0
Get value from configuration which is of type unsigned int.
const char * basename() const
Get program basename.
virtual LogLevel loglevel()
Get log level.
Definition: logger.cpp:254
Fawkes default main thread.
Definition: main_thread.h:58
ThreadCollector * aspect_collector() const
Get a thread collector to be used for an aspect initializer.
virtual void log_debug(const char *component, const char *format,...)
Log debug message.
Definition: multi.cpp:183
virtual std::string get_string(const char *path)=0
Get value from configuration which is of type string.
void start(bool wait=true)
Call this method to start the thread.
Definition: thread.cpp:511
InitOptions & daemonize(bool daemonize, bool kill=false, bool status=false, const char *pid_file=0)
Set daemonization options.
static void cleanup(const char *name=0)
Cleanup existing shared memory segments.