libmoost
/home/mhx/git/github/libmoost/include/moost/service/skeleton.hpp
Go to the documentation of this file.
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