libmoost
|
00001 /* vim:set ts=3 sw=3 sts=3 et: */ 00028 #ifndef FM_LAST_MOOST_SERVICE_SKELETON_H_ 00029 #define FM_LAST_MOOST_SERVICE_SKELETON_H_ 00030 00147 #include <iostream> 00148 #include <sstream> 00149 #include <string> 00150 #include <stdexcept> 00151 00152 #include <boost/program_options.hpp> 00153 #include <boost/shared_ptr.hpp> 00154 #include <boost/noncopyable.hpp> 00155 #include <boost/filesystem.hpp> 00156 00157 #include "../logging.hpp" 00158 #include "../process/service.hpp" 00159 #include "../process/ownership.hpp" 00160 #include "standard_options.hpp" 00161 #include "../utils/assert.hpp" 00162 00163 namespace moost { namespace service { 00164 00168 class NoProcessOwnershipPolicy 00169 { 00170 public: 00171 void add_options(boost::program_options::options_description&, option_validator&) 00172 { 00173 } 00174 00175 void validate_options(const boost::program_options::variables_map&) const 00176 { 00177 } 00178 00179 void change_ownership(const boost::program_options::variables_map&) 00180 { 00181 } 00182 }; 00183 00187 class UidGidProcessOwnershipPolicy 00188 { 00189 private: 00190 moost::process::ownership own; 00191 std::string m_uid; 00192 std::string m_gid; 00193 00194 public: 00195 void add_options(boost::program_options::options_description& od, option_validator&) 00196 { 00197 od.add_options() 00198 ("uid", boost::program_options::value<std::string>(&m_uid), "run process as this user") 00199 ("gid", boost::program_options::value<std::string>(&m_gid), "run process as this group") 00200 ; 00201 } 00202 00203 void validate_options(const boost::program_options::variables_map& vm) const 00204 { 00205 if (own.is_superuser() && vm.count("uid") == 0) 00206 { 00207 throw std::runtime_error("please use --uid option when running as root"); 00208 } 00209 00210 if (vm.count("uid") == 0 && vm.count("gid") != 0) 00211 { 00212 throw std::runtime_error("cannot use --gid without --uid option"); 00213 } 00214 } 00215 00216 void change_ownership(const boost::program_options::variables_map& vm) 00217 { 00218 if (vm.count("uid")) 00219 { 00220 own.drop_privileges(m_uid, m_gid); 00221 } 00222 } 00223 }; 00224 00228 class NoLoggingPolicy 00229 { 00230 public: 00231 typedef moost::process::NoConsoleLoggerPolicy DefaultConsoleLoggerPolicy; 00232 00233 void set_init_verbose(bool) 00234 { 00235 } 00236 00237 void add_options(boost::program_options::options_description&, option_validator&) 00238 { 00239 } 00240 00241 void validate_options(const boost::program_options::variables_map&) const 00242 { 00243 } 00244 00245 bool initialise(const std::string&, const boost::program_options::variables_map&) 00246 { 00247 return false; 00248 } 00249 00250 template <class T> 00251 void configure_service(T&) const 00252 { 00253 } 00254 }; 00255 00259 class MoostLoggingPolicy 00260 { 00261 private: 00262 std::string m_log_level; 00263 std::string m_log_config; 00264 bool m_verbose; 00265 00266 public: 00267 typedef moost::process::MoostStandardConsoleLoggerPolicy DefaultConsoleLoggerPolicy; 00268 00269 MoostLoggingPolicy() 00270 : m_verbose(false) 00271 { 00272 } 00273 00274 void set_init_verbose(bool verbose) 00275 { 00276 m_verbose = verbose; 00277 } 00278 00279 void add_options(boost::program_options::options_description& od, option_validator& opt_validator) 00280 { 00281 standard_options(od, opt_validator) 00282 ("log-level,l", boost::program_options::value<std::string>(&m_log_level)->default_value("info"), "default shell log level") 00283 ("logging-config", boost::program_options::value<std::string>(&m_log_config), "logging configuration file", validator::file(m_log_config)) 00284 ; 00285 } 00286 00287 void validate_options(const boost::program_options::variables_map&) const 00288 { 00289 log4cxx::LevelPtr invalid_level(new log4cxx::Level(-1, LOG4CXX_STR("INVALID"), 7 )); 00290 log4cxx::LevelPtr level = log4cxx::Level::toLevel(m_log_level, invalid_level); 00291 00292 if (level == invalid_level) 00293 { 00294 throw std::runtime_error("invalid log level"); 00295 } 00296 00297 if (!m_log_config.empty() && !boost::filesystem::exists(m_log_config)) 00298 { 00299 throw std::runtime_error("logging configuration file does not exist: " + m_log_config); 00300 } 00301 } 00302 00303 bool initialise(const std::string& program_name, const boost::program_options::variables_map&) 00304 { 00305 moost::logging::global & glog = moost::logging::global_singleton::instance(); 00306 00307 glog.attach_ostream(std::cout); 00308 00309 if (!(m_log_config.empty() ? glog.enable(program_name) 00310 : glog.enable(boost::filesystem::path(m_log_config)))) 00311 { 00312 // the root (default) logger was not configured from a config file so we'll do our own thing with it 00313 00314 #if defined(NDEBUG) && !defined(SERVICE_SKELETON_NOCFG_LOG_ALL) 00315 MLOG_SET_DEFAULT_LEVEL_INFO(); 00316 #else 00317 MLOG_SET_DEFAULT_LEVEL_ALL(); 00318 #endif 00319 } 00320 00321 return true; 00322 } 00323 00324 template <class T> 00325 void configure_service(T& service) const 00326 { 00327 service.set_log_level(m_log_level); 00328 service.set_appender_factory(moost::service::appender_factory_ptr(new moost::service::log4cxx_appender_factory(m_log_level))); 00329 } 00330 }; 00331 00335 template <class ServiceT, 00336 class ProcessOwnershipPolicy = UidGidProcessOwnershipPolicy, 00337 class LoggingPolicy = MoostLoggingPolicy, 00338 class ConsoleLoggerPolicy = typename LoggingPolicy::DefaultConsoleLoggerPolicy> 00339 class skeleton : public boost::noncopyable 00340 { 00341 private: 00342 bool m_logging_enabled; 00343 bool m_options_valid; 00344 std::string m_pidfile; 00345 unsigned short m_shell_port; 00346 boost::program_options::variables_map m_opt_varmap; 00347 ProcessOwnershipPolicy m_ownership; 00348 LoggingPolicy m_logging; 00349 std::string m_program_name; 00350 00351 typedef moost::process::service<ServiceT, ConsoleLoggerPolicy> ProcessServiceType; 00352 00353 class child_init_func 00354 { 00355 public: 00356 bool operator()() 00357 { 00358 m_log_enabled = m_log.initialise(m_program_name, m_varmap); 00359 m_log.configure_service(m_svc); 00360 return false; 00361 } 00362 00363 child_init_func(ProcessServiceType& svc, LoggingPolicy& log, bool& log_enabled, const std::string& program_name, const boost::program_options::variables_map& varmap) 00364 : m_svc(svc) 00365 , m_log(log) 00366 , m_log_enabled(log_enabled) 00367 , m_program_name(program_name) 00368 , m_varmap(varmap) 00369 { 00370 } 00371 00372 private: 00373 ProcessServiceType& m_svc; 00374 LoggingPolicy& m_log; 00375 bool& m_log_enabled; 00376 const std::string m_program_name; 00377 const boost::program_options::variables_map& m_varmap; 00378 }; 00379 00380 protected: 00381 typedef ServiceT ServiceType; 00382 00388 virtual void add_options(boost::program_options::options_description&) 00389 { 00390 } 00391 00397 virtual void add_options(boost::program_options::options_description&, option_validator&) 00398 { 00399 } 00400 00407 virtual void validate_options() const 00408 { 00409 } 00410 00414 virtual std::string name() const = 0; 00415 00419 virtual std::string version() const = 0; 00420 00424 virtual std::string description() const = 0; 00425 00429 virtual std::string copyright() const = 0; 00430 00437 virtual boost::shared_ptr<ServiceType> create_service_instance() = 0; 00438 00445 virtual std::string help_header() const 00446 { 00447 std::ostringstream oss; 00448 oss << "\r\n" << name() << ": " << description() << "\r\n\r\n"; 00449 oss << "Version: " << version() << " :: Build: " << __DATE__ << " (" << __TIME__ << ") :: (c) " 00450 << copyright() << "\r\n\r\n"; 00451 return oss.str(); 00452 } 00453 00459 bool logging_enabled() const 00460 { 00461 return m_logging_enabled; 00462 } 00463 00469 const boost::program_options::variables_map& options() const 00470 { 00471 if (!m_options_valid) 00472 { 00473 throw std::runtime_error("(BUG) attempt to access options map before initialisation"); 00474 } 00475 00476 return m_opt_varmap; 00477 } 00478 00482 bool running_as_daemon() const 00483 { 00484 return options().count("daemon") != 0; 00485 } 00486 00490 void process_cmdline_options(boost::program_options::options_description& cmdline_options, option_validator& opt_validator, int argc, char **argv) 00491 { 00492 add_options(cmdline_options); 00493 add_options(cmdline_options, opt_validator); 00494 00495 standard_options(cmdline_options, opt_validator) 00496 .port("shell-port", "remote shell port when daemonised", m_shell_port) 00497 ("pidfile", boost::program_options::value<std::string>(&m_pidfile), "pidfile location") 00498 ("noerr,n", "shut up stderr (for thrift)") 00499 ("help,h", "output help message and exit") 00500 ("version", "output service version and exit") 00501 ("daemon", "daemonise (fork to background)") 00502 ; 00503 00504 m_ownership.add_options(cmdline_options, opt_validator); 00505 m_logging.add_options(cmdline_options, opt_validator); 00506 00507 boost::program_options::store(boost::program_options::parse_command_line(argc, argv, cmdline_options), m_opt_varmap); 00508 boost::program_options::notify(m_opt_varmap); 00509 m_options_valid = true; 00510 } 00511 00515 int safe_main(int argc, char **argv, bool noargs) 00516 { 00517 m_program_name = argv[0]; 00518 00519 // process command line options first 00520 00521 boost::program_options::options_description cmdline_options; 00522 option_validator opt_validator; 00523 process_cmdline_options(cmdline_options, opt_validator, argc, argv); 00524 00525 if (options().count("help") || (!noargs && argc == 1)) 00526 { 00527 std::cout << help_header() << cmdline_options << std::endl; 00528 return 0; // nothing more to do here 00529 } 00530 00531 if (options().count("version")) 00532 { 00533 std::cout << version() << std::endl; 00534 return 0; // nothing more to do here 00535 } 00536 00537 // some basic command line options consistency checks 00538 00539 if (running_as_daemon() && m_shell_port == 0) 00540 { 00541 throw std::runtime_error("please use --shell-port option when running with --daemon"); 00542 } 00543 00544 moost::utils::assert_absolute_path(m_pidfile, "pidfile"); 00545 00546 // service specific checks 00547 00548 validator::constraints_map_t opt_constraints; 00549 00550 if (running_as_daemon()) 00551 { 00552 opt_constraints["absolute_filenames"] = "1"; 00553 } 00554 00555 opt_validator(options(), opt_constraints); 00556 m_ownership.validate_options(options()); 00557 m_logging.validate_options(options()); 00558 validate_options(); 00559 00560 // configure logging initialisation verbosity 00561 00562 m_logging.set_init_verbose(!running_as_daemon()); 00563 00564 // change ownership according to policy 00565 00566 m_ownership.change_ownership(options()); 00567 00568 run(); 00569 00570 return 0; // apparently, we've managed to finish cleanly 00571 } 00572 00580 virtual void run() 00581 { 00582 run_service(); 00583 } 00584 00591 void run_service() 00592 { 00593 ProcessServiceType service(create_service_instance()); 00594 00595 child_init_func child_init(service, m_logging, m_logging_enabled, m_program_name, options()); 00596 00597 service.set_child_init_func(boost::ref(child_init)); 00598 00599 if (options().count("noerr")) 00600 { 00601 service.set_default_stderr_state(false); 00602 } 00603 00604 if (!m_pidfile.empty()) 00605 { 00606 service.set_pidfile(m_pidfile); 00607 } 00608 00609 if (m_shell_port) 00610 { 00611 service.set_shell_port(m_shell_port); 00612 } 00613 00614 service.run(running_as_daemon()); 00615 } 00616 00617 public: 00618 skeleton() 00619 : m_logging_enabled(false) 00620 , m_options_valid(false) 00621 , m_shell_port(0) 00622 { 00623 } 00624 00625 virtual ~skeleton() 00626 { 00627 } 00628 00636 virtual int main(int argc, char **argv, bool noargs) 00637 { 00638 try 00639 { 00640 return safe_main(argc, argv, noargs); 00641 } 00642 catch (const std::exception& e) 00643 { 00644 if (logging_enabled()) 00645 { 00646 MLOG_DEFAULT_FATAL(e.what()); 00647 } 00648 00649 throw; // will be caught by main template below 00650 } 00651 catch (...) 00652 { 00653 if (logging_enabled()) 00654 { 00655 MLOG_DEFAULT_FATAL("unknown exception caught"); 00656 } 00657 00658 throw; // will be caught by main template below 00659 } 00660 00661 return 1; 00662 } 00663 }; 00664 00665 template <class SkeletonT> 00666 int main(int argc, char **argv, bool noargs = false) 00667 { 00668 // This try/catch catches exceptions during construction of the skeleton instance 00669 // (those should be really rare). 00670 00671 // Any other exceptions will be caught by the skeleton's main() implementation. 00672 00673 try 00674 { 00675 return SkeletonT().main(argc, argv, noargs); 00676 } 00677 catch (const std::exception& e) 00678 { 00679 std::cerr << "ERROR: " << e.what() << std::endl; 00680 } 00681 catch (...) 00682 { 00683 std::cerr << "ERROR: unknown exception caught" << std::endl; 00684 } 00685 00686 return 1; 00687 } 00688 00689 } } 00690 00691 #endif