1 // -*- c++ -*-
2 //---------------------------------------------------------------------------
3 //                            GenServer.cpp
4 //---------------------------------------------------------------------------
5 //  Copyright (c) 1997-2004,2005 by Vladislav Grinchenko
6 //
7 //  This library is free software; you can redistribute it and/or
8 //  modify it under the terms of the GNU Library General Public
9 //  License as published by the Free Software Foundation; either
10 //  version 2 of the License, or (at your option) any later version.
11 //---------------------------------------------------------------------------
12 
13 /*
14   [a   e g ijk   o qr tu wxy ]
15   [ABC EFGHIJK MNOPQR TUVWXYZ]
16 
17 " Standard command-line arguments:                                           \n"
18 "                                                                            \n"
19 "  -b, --daemon BOOL        - Run process as true UNIX daemon                \n"
20 "  -l, --pidfile PATH       - The process ID is written to the lockfile PATH \n"
21 "                             instead of default ~/.{procname}.pid           \n"
22 "  -L, --ommit-pidfile BOOL - Do not create PID lockfile                     \n"
23 "  -d, --log-stdout BOOL    - Write debug to standard output                 \n"
24 "  -D, --log-file NAME      - Write debug to NAME file                       \n"
25 "  -z, --log-size NUM       - Maximum size debug file can reach              \n"
26 "                             (default is 10Mb)                              \n"
27 "  -c, --log-level NUM      - Log verbosity                                  \n"
28 "  -s, --with-log-server BOOL - Redirect log messages to the log server      \n"
29 "  -S, --log-server NAME    - Define assa-logd server address                \n"
30 "                             (default: assalogd@localhost)                  \n"
31 "  -m, --mask MASK          - Mask (default: ALL = 0x7fffffff)               \n"
32 "  -p, --port NAME          - The TCP/IP port NAME (default - procname)      \n"
33 "  -n, --instance NUM       - Process instance NUM (default - none)          \n"
34 "  -f, --config-file NAME   - Alternative config file NAME                   \n"
35 "  -h, --help               - Print this message                             \n"
36 "  -v, --version            - Print version number                           \n"
37 "                                                                            \n"
38 " NOTE: BOOL value is either 'yes' or 'no'                                   \n"
39 */
40 //------------------------------------------------------------------------------
41 
42 #include <sys/types.h>			// stat(2)
43 #include <sys/stat.h>			// stat(2)
44 #include <unistd.h>				// stat(2)
45 
46 #ifdef __CYGWIN32__				// to resolve h_errno dependency
47 #  include <errno.h>
48 #  include <netdb.h>
49 #endif
50 
51 #include "assa/GenServer.h"
52 #include "assa/CommonUtils.h"
53 
54 using namespace ASSA;
55 
GenServer()56 GenServer::GenServer ()
57 	:
58     m_log_size        (10485760), // 10 Mb
59     m_instance        (-1),
60 	m_with_log_server ("no"),
61 	m_log_server      ("assalogd@"),
62     m_mask            (ALL),
63     m_graceful_quit   (false),
64     m_version         ("unknown"),
65     m_revision        (0),
66     m_author          ("John Doe"),
67     m_help_msg        ("No help available"),
68     m_log_flag        (KEEPLOG),
69     m_log_stdout      ("no"),
70     m_daemon          ("no"),
71     m_ommit_pidfile   ("no"),
72 	m_log_level       (-1),
73     m_help_flag       (false),
74     m_version_flag    (false),
75 	m_exit_value      (0)
76 {
77     add_flag_opt ('h', "help",       &m_help_flag);
78     add_flag_opt ('v', "version",    &m_version_flag);
79 
80     add_opt ('d', "log-stdout",      &m_log_stdout);
81     add_opt ('b', "daemon",          &m_daemon);
82     add_opt ('L', "ommit-pidfile",   &m_ommit_pidfile);
83 	add_opt ('s', "with-log-server", &m_with_log_server);
84     add_opt ('m', "mask",            &m_mask);
85     add_opt ('D', "log-file",        &m_log_file);
86     add_opt ('f', "config-file",     &m_config_file);
87     add_opt ('n', "instance",        &m_instance);
88     add_opt ('p', "port",            &m_port);
89     add_opt ('z', "log-size",        &m_log_size);
90     add_opt ('l', "pidfile",         &m_pidfile);
91 	add_opt ('S', "log-server",      &m_log_server);
92 	add_opt ('c', "log-level",       &m_log_level);
93 
94 	/** Form a valid log server address
95 	 */
96 	char hn[64];
97 	::gethostname (hn, sizeof (hn)-1);
98 	m_log_server += hn;
99 }
100 
101 /** Reactor needs to *detach* itself from the Logger
102  *  before releasing memory. Otherwise, a race condition
103  *  between Logger (singleton) and GenServer (singleton)
104  *  might yield core dump if Reactor was destroyed
105  *  before Logger. Since Reactor is *attached* to the Logger
106  *  with Logger::log_open () for the assa-logd connection, it is
107  *  Reactor's responsibility to *detach* first. But, we only care
108  *  about GenServer's Reactor. All others (such as those used by
109  *  Connector and Acceptor classes) should not.
110  */
111 GenServer::
~GenServer()112 ~GenServer ()
113 {
114     Log::log_close ();
115 }
116 
117 //------------------------------------------------------------------------------
118 //    Get command line process name parse command line arguments
119 //    request internals initialization.
120 //------------------------------------------------------------------------------
121 
122 void
123 GenServer::
init(int * argc,char * argv[],const char * ht_)124 init (int* argc, char* argv [], const char* ht_)
125 {
126     char* cp = argv [0];
127     m_help_msg = ht_;
128 
129     /**
130 	 * Solaris x86 whole path is returned.
131 	 * Scan through the path and get the process name.
132      */
133 	if (strchr(cp, ASSA_DIR_SEPARATOR)) {
134 		cp += strlen(argv[0]); // position at the end
135 		while (*cp-- != ASSA_DIR_SEPARATOR) {
136 			;
137 		}
138 		cp += 2;
139     }
140 
141 #if defined (WIN32)				// get rid of '.exe'
142 	char* extidx = cp;
143 	while (*extidx) {
144 		if (*extidx == '.') {
145 			*extidx = '\0';
146 			break;
147 		}
148 		extidx++;
149 	}
150 #endif
151 	m_cmdline_name = cp;
152 
153     if (!parse_args ((const char **)argv)) {
154 		std::cerr << "Error in arguments: " << get_opt_error () << std::endl;
155 		std::cerr << "Try '" << argv[0] << " --help' for details.\n";
156 		exit (1);
157     }
158 
159     if (m_help_flag) {
160 		display_help ();
161 		exit (0);
162     }
163 
164     if (m_version_flag) {
165 		std::cerr << '\n' << argv[0] << " " << get_version () << '\n' << '\n'
166 			 << "Written by " << m_author << "\n\n";
167 		exit (0);
168     }
169 
170 	/** Convert relative paths of all filepath options to
171 		absolute paths.
172 	*/
173 	std::string s;
174 
175 	if (m_default_config_file.size ()) {
176 		s = ASSA::Utils::strenv (m_default_config_file.c_str ());
177 		m_default_config_file = s;
178 	}
179 
180 	if (m_config_file.size ()) {
181 		s = ASSA::Utils::strenv (m_config_file.c_str ());
182 		m_config_file = s;
183 	}
184 
185 	if (m_log_file.size ()) {
186 		s = ASSA::Utils::strenv (m_log_file.c_str ());
187 		m_log_file = s;
188 	}
189 
190 	if (m_pidfile.size ()) {
191 		s = ASSA::Utils::strenv (m_pidfile.c_str ());
192 		m_pidfile = s;
193 	}
194 
195 	/** Daemonize the process if asked
196 	 */
197     if (m_daemon == "yes") {
198 		assert(become_daemon ());
199     }
200 
201     /** Setting defaults if required
202 	 */
203     char instbuf[16];		// INT_MAX   [-]2147483647
204     sprintf(instbuf, "%d", m_instance);
205 
206     if (m_proc_name.length() == 0) {
207 		m_proc_name = m_cmdline_name;
208 
209 		if (m_instance != -1) {
210 			m_proc_name += instbuf;
211 		}
212     }
213     if (m_port.length() == 0) {
214 		m_port = m_proc_name;
215     }
216 
217 #if !defined(WIN32)
218     /** Setup signal handling.
219 	 *  Ignore SIGHUP, SIGPIPE, SIGCHLD, SIGCLD, SIGALRM by default.
220 	 */
221     SigAction ignore_act( SIG_IGN );
222 
223     /**
224 	 * SIGHUP is generated by terminal driver (see termio(7I) for
225 	 * details) in response to modem hangup (or closing terminal
226 	 * session). I ignore it here with the assumption that GenServer
227 	 * is alway a daemon process that doesn't have associated
228 	 * controlling terminal anyway.
229      */
230     ignore_act.register_action( SIGHUP );
231 
232     ignore_act.register_action( SIGPIPE );
233     ignore_act.register_action( SIGCHLD );
234 #if !(defined (__FreeBSD__) || defined(__FreeBSD_kernel__) \
235 	  || defined (__NetBSD__) || defined (__DragonFly__))
236     ignore_act.register_action( SIGCLD );
237 #endif
238     ignore_act.register_action( SIGALRM );
239 
240     /**
241 	 * Catch SIGPOLL - sigPOLL handler just does nothing except
242 	 * of catching signal.
243 	 */
244     m_sig_dispatcher.install ( ASSAIOSIG, &m_sig_poll );
245 
246     /**
247 	 * SIGINT is generated by the terminal driver when an interrupt
248 	 * key is pressed (DELETE or Ctrl-C). It is sent to all processes
249 	 * associated with the controlling terminal. We terminate process
250 	 * in this case.
251      */
252     m_sig_dispatcher.install ( SIGINT, (EventHandler*) this );
253 
254     /**
255 	 * Catch and handle SIGTERM signals.
256 	 * is the termination signal sent by kill command by default
257 	 * or internally as a part of fatal application exception handling
258 	 * to properly terminate GenServer process.
259      */
260     m_sig_dispatcher.install ( SIGTERM, (EventHandler*) this );
261 
262 #endif // !defined(WIN32)
263 
264     /** Initialize other internal stuff.
265 	 */
266     init_internals ();
267 }
268 
269 void
270 GenServer::
init_internals()271 init_internals ()
272 {
273     static const char self[] = "GenServer::init_internals";
274 
275     /** Set standard configuration file name.
276 	 *  For POSIX systems, it is $HOME/.procname.
277 	 *  For Win32, it is $cwd/procname.ini.
278 	 */
279 #if defined (WIN32)
280     m_default_config_file = this->get_cmdline_name () + ".ini";
281 #else
282     m_default_config_file = "$HOME/." + this->get_cmdline_name ();
283 	m_default_config_file = Utils::strenv (m_default_config_file.c_str ());
284 #endif
285 
286     /** Remove existing log file if requested. Unlinking /dev/null
287 		character device and replacing it with a regular file leads
288 		to the system crash during consecutive reboots.
289 		See also assa/FileLogger.cpp.
290 	 */
291     if (m_log_flag == RMLOG && m_log_stdout == "no")
292 	{
293 		struct stat fst;
294 		if (::stat (m_log_file.c_str(), &fst) == 0)
295 		{
296 			if (S_ISREG (fst.st_mode)) {
297 				::unlink (m_log_file.c_str());
298 			}
299 		}
300     }
301 
302 	/** Open logging facility:
303 	 *
304 	 *  --log-stdout="yes" takes precedence over
305 	 *  --with-log-server="yes" which takes precedence over
306 	 *  --log-file=/path/to/log
307 	 */
308 
309 	Log::set_app_name (get_proc_name ());
310 
311     if (m_log_stdout == "yes") {
312 		Log::open_log_stdout (m_mask);
313     }
314 	else {
315 		if (m_with_log_server == "yes") {
316 			Log::open_log_server (m_log_server,
317 								  m_log_file.c_str(),
318 								  get_reactor (),
319 								  m_mask,
320 								  m_log_size) ;
321 		}
322 		else {
323 			Log::open_log_file (m_log_file.c_str(),	m_mask, m_log_size);
324 		}
325     }
326 
327     trace(self);
328 
329     if (m_ommit_pidfile == "no")
330 	{
331 		if (m_pidfile.size () == 0) {
332 			string s ("~/." + m_proc_name + ".pid");
333 			m_pidfile = ASSA::Utils::strenv (s.c_str ());
334 		}
335 		if (! m_pidfile_lock.lock (m_pidfile)) {
336 			DL((ASSAERR,"Failed to lock PID file: %s\n",
337 				m_pidfile_lock.get_error_msg ()));
338 			exit (1);
339 		}
340     }
341 
342     DL((APP,"\n"                                                        ));
343     DL((APP,"========================================================\n"));
344     DL((APP,"||         Server configuration settings              ||\n"));
345     DL((APP,"========================================================\n"));
346     DL((APP," cmd_line_name       = '%s'\n", m_cmdline_name.c_str()   ));
347     DL((APP," name                = '%s'\n", m_proc_name.c_str()      ));
348     DL((APP," default config file = '%s'\n", m_default_config_file.c_str()));
349     DL((APP," config file         = '%s'\n", m_config_file.c_str()    ));
350     DL((APP," mask                = 0x%X\n", m_mask                   ));
351     dump ();
352     DL((APP,"========================================================\n"));
353     DL((APP,"\n"));
354 }
355 
356 bool
357 GenServer::
become_daemon()358 become_daemon ()
359 {
360 #if defined(WIN32)
361     return true;
362 #else
363     Fork f (Fork::LEAVE_ALONE, Fork::IGNORE_STATUS);
364 
365     if (!f.isChild ()) {	// parent exits
366 		exit (0);
367     }
368 
369     int size = 1024;
370     int i = 0;
371     pid_t nullfd;
372 
373     for (i = 0; i < size; i++) {
374 		(void) close (i);
375     }
376 
377     nullfd = open ("/dev/null", O_WRONLY | O_CREAT, 0666);
378     if (nullfd == -1) {
379 		syslog (LOG_ERR,"failed to open \"/dev/null\"");
380 		return false;
381     }
382 
383     (void) dup2 (nullfd, 1);
384     (void) dup2 (nullfd, 2);
385     (void) close (nullfd);
386 
387     if ( setsid() == -1 ) {
388 		syslog (LOG_ERR,"setsid() failed");
389 		return false;
390     }
391 
392     /*---
393       Changing to root directory would be the right thing to do for a
394       server (so that it wouldn't possibly depend on any mounted file
395       systems. But, in practice, it might cause a lot of problems.
396       ---*/
397 #if 0
398 	if ( chdir("/") == -1 ) {
399 		return false;
400 	}
401 #endif
402     return (true);
403 
404 #endif  // defined(WIN32)
405 }
406 
407 int
408 GenServer::
handle_signal(int signum_)409 handle_signal (int signum_)
410 {
411     trace("GenServer::handle_signal");
412     std::ostringstream m;
413 
414     switch (signum_)
415 	{
416 		case SIGTERM: m << "SIGTERM signal caugth. "; break;
417 		case SIGINT:  m << "SIGINT signal caugth. "; break;
418 		default:      m << "Unexpected signal caugth.";
419 	}
420     m << "Signal # " << signum_ << std::ends;
421     DL((APP,"%s\n", m.str ().c_str () ));
422     DL((APP,"Initiating shutdown sequence...\n"));
423 
424     fatal_signal_hook ();
425 
426     DL((APP, "Shutdown sequence completed - Exiting !\n"));
427 
428  	/* Calling stop_service () triggers a call to Reactor::stopReactor()
429  	   with subsequent call to Reactor::removeIOHandler() and then
430 	   EventHandler::handle_close(). If EventHandler is in the middle
431 	   of the *slow* system call such as read(2), handle_close() will
432 	   destry EventHandler, and after cotrol is returned from
433 	   GenServer::handle_signal(), *slow* system call is restarted
434 	   and proceeds to operate on the memory that has been deleted already.
435 
436 	   Calling Reactor::deactivate() instead delays memory release.
437 	*/
438 	get_reactor()->deactivate ();
439 	m_graceful_quit = true;
440 
441     return 0;
442 }
443 
444