// -*- c++ -*- //--------------------------------------------------------------------------- // GenServer.cpp //--------------------------------------------------------------------------- // Copyright (c) 1997-2004,2005 by Vladislav Grinchenko // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. //--------------------------------------------------------------------------- /* [a e g ijk o qr tu wxy ] [ABC EFGHIJK MNOPQR TUVWXYZ] " Standard command-line arguments: \n" " \n" " -b, --daemon BOOL - Run process as true UNIX daemon \n" " -l, --pidfile PATH - The process ID is written to the lockfile PATH \n" " instead of default ~/.{procname}.pid \n" " -L, --ommit-pidfile BOOL - Do not create PID lockfile \n" " -d, --log-stdout BOOL - Write debug to standard output \n" " -D, --log-file NAME - Write debug to NAME file \n" " -z, --log-size NUM - Maximum size debug file can reach \n" " (default is 10Mb) \n" " -c, --log-level NUM - Log verbosity \n" " -s, --with-log-server BOOL - Redirect log messages to the log server \n" " -S, --log-server NAME - Define assa-logd server address \n" " (default: assalogd@localhost) \n" " -m, --mask MASK - Mask (default: ALL = 0x7fffffff) \n" " -p, --port NAME - The TCP/IP port NAME (default - procname) \n" " -n, --instance NUM - Process instance NUM (default - none) \n" " -f, --config-file NAME - Alternative config file NAME \n" " -h, --help - Print this message \n" " -v, --version - Print version number \n" " \n" " NOTE: BOOL value is either 'yes' or 'no' \n" */ //------------------------------------------------------------------------------ #include // stat(2) #include // stat(2) #include // stat(2) #ifdef __CYGWIN32__ // to resolve h_errno dependency # include # include #endif #include "assa/GenServer.h" #include "assa/CommonUtils.h" using namespace ASSA; GenServer::GenServer () : m_log_size (10485760), // 10 Mb m_instance (-1), m_with_log_server ("no"), m_log_server ("assalogd@"), m_mask (ALL), m_graceful_quit (false), m_version ("unknown"), m_revision (0), m_author ("John Doe"), m_help_msg ("No help available"), m_log_flag (KEEPLOG), m_log_stdout ("no"), m_daemon ("no"), m_ommit_pidfile ("no"), m_log_level (-1), m_help_flag (false), m_version_flag (false), m_exit_value (0) { add_flag_opt ('h', "help", &m_help_flag); add_flag_opt ('v', "version", &m_version_flag); add_opt ('d', "log-stdout", &m_log_stdout); add_opt ('b', "daemon", &m_daemon); add_opt ('L', "ommit-pidfile", &m_ommit_pidfile); add_opt ('s', "with-log-server", &m_with_log_server); add_opt ('m', "mask", &m_mask); add_opt ('D', "log-file", &m_log_file); add_opt ('f', "config-file", &m_config_file); add_opt ('n', "instance", &m_instance); add_opt ('p', "port", &m_port); add_opt ('z', "log-size", &m_log_size); add_opt ('l', "pidfile", &m_pidfile); add_opt ('S', "log-server", &m_log_server); add_opt ('c', "log-level", &m_log_level); /** Form a valid log server address */ char hn[64]; ::gethostname (hn, sizeof (hn)-1); m_log_server += hn; } //------------------------------------------------------------------------------ // Get command line process name parse command line arguments // request internals initialization. //------------------------------------------------------------------------------ void GenServer:: init (int* argc, char* argv [], const char* ht_) { char* cp = argv [0]; m_help_msg = ht_; /** * Solaris x86 whole path is returned. * Scan through the path and get the process name. */ if (strchr(cp, ASSA_DIR_SEPARATOR)) { cp += strlen(argv[0]); // position at the end while (*cp-- != ASSA_DIR_SEPARATOR) { ; } cp += 2; } #if defined (WIN32) // get rid of '.exe' char* extidx = cp; while (*extidx) { if (*extidx == '.') { *extidx = '\0'; break; } extidx++; } #endif m_cmdline_name = cp; if (!parse_args ((const char **)argv)) { std::cerr << "Error in arguments: " << get_opt_error () << std::endl; std::cerr << "Try '" << argv[0] << " --help' for details.\n"; exit (1); } if (m_help_flag) { display_help (); exit (0); } if (m_version_flag) { std::cerr << '\n' << argv[0] << " " << get_version () << '\n' << '\n' << "Written by " << m_author << "\n\n"; exit (0); } if (m_daemon == "yes") { assert(become_daemon ()); } /** Setting defaults if required */ char instbuf[16]; // INT_MAX [-]2147483647 sprintf(instbuf, "%d", m_instance); if (m_proc_name.length() == 0) { m_proc_name = m_cmdline_name; if (m_instance != -1) { m_proc_name += instbuf; } } if (m_port.length() == 0) { m_port = m_proc_name; } #if !defined(WIN32) /** Setup signal handling. * Ignore SIGHUP, SIGPIPE, SIGCHLD, SIGCLD, SIGALRM by default. */ SigAction ignore_act( SIG_IGN ); /** * SIGHUP is generated by terminal driver (see termio(7I) for * details) in response to modem hangup (or closing terminal * session). I ignore it here with the assumption that GenServer * is alway a daemon process that doesn't have associated * controlling terminal anyway. */ ignore_act.register_action( SIGHUP ); ignore_act.register_action( SIGPIPE ); ignore_act.register_action( SIGCHLD ); #if !(defined (__FreeBSD__) || defined(__FreeBSD_kernel__) \ || defined (__NetBSD__)) ignore_act.register_action( SIGCLD ); #endif ignore_act.register_action( SIGALRM ); /** * Catch SIGPOLL - sigPOLL handler just does nothing except * of catching signal. */ m_sig_dispatcher.install ( ASSAIOSIG, &m_sig_poll ); /** * SIGINT is generated by the terminal driver when an interrupt * key is pressed (DELETE or Ctrl-C). It is sent to all processes * associated with the controlling terminal. We terminate process * in this case. */ m_sig_dispatcher.install ( SIGINT, (EventHandler*) this ); /** * Catch and handle SIGTERM signals. * is the termination signal sent by kill command by default * or internally as a part of fatal application exception handling * to properly terminate GenServer process. */ m_sig_dispatcher.install ( SIGTERM, (EventHandler*) this ); #endif // !defined(WIN32) /** Initialize other internal stuff. */ init_internals (); } void GenServer:: init_internals () { static const char self[] = "GenServer::init_internals"; /** Set standard configuration file name. * For POSIX systems, it is $HOME/.procname. * For Win32, it is $cwd/procname.ini. */ #if defined (WIN32) m_default_config_file = this->get_cmdline_name () + ".ini"; #else m_default_config_file = "$HOME/." + this->get_cmdline_name (); m_default_config_file = Utils::strenv (m_default_config_file.c_str ()); #endif /** Remove existing log file if requested. Unlinking /dev/null character device and replacing it with a regular file leads to the system crash during consecutive reboots. See also assa/FileLogger.cpp. */ if (m_log_flag == RMLOG && m_log_stdout == "no") { struct stat fst; if (::stat (m_log_file.c_str(), &fst) == 0) { if (S_ISREG (fst.st_mode)) { ::unlink (m_log_file.c_str()); } } } /** Open logging facility: * * --log-stdout="yes" takes precedence over * --with-log-server="yes" which takes precedence over * --log-file=/path/to/log */ Log::set_app_name (get_proc_name ()); if (m_log_stdout == "yes") { Log::open_log_stdout (m_mask); } else { if (m_with_log_server == "yes") { Log::open_log_server (m_log_server, m_log_file.c_str(), get_reactor (), m_mask, m_log_size) ; } else { Log::open_log_file (m_log_file.c_str(), m_mask, m_log_size); } } trace(self); if (m_ommit_pidfile == "no") { if (m_pidfile.size () == 0) { m_pidfile = "~/." + m_proc_name + ".pid"; } if (! m_pidfile_lock.lock (m_pidfile)) { DL((ASSAERR,"Failed to lock PID file: %s\n", m_pidfile_lock.get_error_msg ())); exit (1); } } DL((APP,"\n" )); DL((APP,"========================================================\n")); DL((APP,"|| Server configuration settings ||\n")); DL((APP,"========================================================\n")); DL((APP," cmd_line_name = '%s'\n", m_cmdline_name.c_str() )); DL((APP," name = '%s'\n", m_proc_name.c_str() )); DL((APP," default config file = '%s'\n", m_default_config_file.c_str())); DL((APP," config file = '%s'\n", m_config_file.c_str() )); DL((APP," mask = 0x%X\n", m_mask )); dump (); DL((APP,"========================================================\n")); DL((APP,"\n")); } bool GenServer:: become_daemon () { #if defined(WIN32) return true; #else Fork f (Fork::LEAVE_ALONE, Fork::IGNORE_STATUS); if (!f.isChild ()) { // parent exits exit (0); } int size = 1024; int i = 0; pid_t nullfd; for (i = 0; i < size; i++) { (void) close (i); } nullfd = open ("/dev/null", O_WRONLY | O_CREAT, 0666); if (nullfd == -1) { syslog (LOG_ERR,"failed to open \"/dev/null\""); return false; } (void) dup2 (nullfd, 1); (void) dup2 (nullfd, 2); (void) close (nullfd); if ( setsid() == -1 ) { syslog (LOG_ERR,"setsid() failed"); return false; } /*--- Changing to root directory would be the right thing to do for a server (so that it wouldn't possibly depend on any mounted file systems. But, in practice, it might cause a lot of problems. ---*/ #if 0 if ( chdir("/") == -1 ) { return false; } #endif return (true); #endif // defined(WIN32) } int GenServer:: handle_signal (int signum_) { trace("GenServer::handle_signal"); std::ostringstream m; switch (signum_) { case SIGTERM: m << "SIGTERM signal caugth. "; break; case SIGINT: m << "SIGINT signal caugth. "; break; default: m << "Unexpected signal caugth."; } m << "Signal # " << signum_ << std::ends; DL((APP,"%s\n", m.str ().c_str () )); DL((APP,"Initiating shutdown sequence...\n")); fatal_signal_hook (); DL((APP, "Shutdown sequence completed - Exiting !\n")); /* Calling stop_service () triggers a call to Reactor::stopReactor() with subsequent call to Reactor::removeIOHandler() and then EventHandler::handle_close(). If EventHandler is in the middle of the *slow* system call such as read(2), handle_close() will destry EventHandler, and after cotrol is returned from GenServer::handle_signal(), *slow* system call is restarted and proceeds to operate on the memory that has been deleted already. Calling Reactor::deactivate() instead delays memory release. */ get_reactor()->deactivate (); m_graceful_quit = true; return 0; }