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