OPeNDAP Hyrax Back End Server (BES)  Updated for version 3.8.3
ServerApp.cc
Go to the documentation of this file.
1 // ServerApp.cc
2 
3 // This file is part of bes, A C++ back-end server implementation framework
4 // for the OPeNDAP Data Access Protocol.
5 
6 // Copyright (c) 2004-2009 University Corporation for Atmospheric Research
7 // Author: Patrick West <pwest@ucar.edu> and Jose Garcia <jgarcia@ucar.edu>
8 //
9 // This library is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU Lesser General Public
11 // License as published by the Free Software Foundation; either
12 // version 2.1 of the License, or (at your option) any later version.
13 //
14 // This library is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 // Lesser General Public License for more details.
18 //
19 // You should have received a copy of the GNU Lesser General Public
20 // License along with this library; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 //
23 // You can contact University Corporation for Atmospheric Research at
24 // 3080 Center Green Drive, Boulder, CO 80301
25 
26 // (c) COPYRIGHT University Corporation for Atmospheric Research 2004-2005
27 // Please read the full copyright statement in the file COPYRIGHT_UCAR.
28 //
29 // Authors:
30 // pwest Patrick West <pwest@ucar.edu>
31 // jgarcia Jose Garcia <jgarcia@ucar.edu>
32 
33 #include <unistd.h>
34 #include <signal.h>
35 #include <sys/wait.h> // for wait
36 #include <sys/types.h>
37 
38 #include <iostream>
39 #include <fstream>
40 #include <sstream>
41 #include <cstring>
42 #include <cstdlib>
43 #include <cerrno>
44 
45 using std::cout;
46 using std::cerr;
47 using std::endl;
48 using std::ios;
49 using std::ostringstream;
50 using std::ofstream;
51 
52 #include "config.h"
53 
54 #include "ServerApp.h"
55 #include "ServerExitConditions.h"
56 #include "TheBESKeys.h"
57 #include "BESLog.h"
58 #include "SocketListener.h"
59 #include "TcpSocket.h"
60 #include "UnixSocket.h"
61 #include "BESServerHandler.h"
62 #include "BESError.h"
63 #include "PPTServer.h"
64 #include "BESMemoryManager.h"
65 #include "BESDebug.h"
66 #include "BESCatalogUtils.h"
67 #include "BESServerUtils.h"
68 
69 #include "BESDefaultModule.h"
70 #include "BESXMLDefaultCommands.h"
71 #include "BESDaemonConstants.h"
72 
73 static int session_id = 0;
74 
75 // These are set to 1 by their respective handlers and then processed in the
76 // signal processing loop.
77 static volatile sig_atomic_t sigchild = 0;
78 static volatile sig_atomic_t sigpipe = 0;
79 static volatile sig_atomic_t sigterm = 0;
80 static volatile sig_atomic_t sighup = 0;
81 
82 static string bes_exit_message(int cpid, int stat)
83 {
84  ostringstream oss;
85  oss << "beslistener child pid: " << cpid;
86  if (WIFEXITED(stat)) { // exited via exit()?
87  oss << " exited with status: " << WEXITSTATUS(stat);
88  }
89  else if (WIFSIGNALED(stat)) { // exited via a signal?
90  oss << " exited with signal: " << WTERMSIG(stat);
91 #ifdef WCOREDUMP
92  if (WCOREDUMP(stat))
93  oss << " and a core dump!";
94 #endif
95  }
96  else {
97  oss << " exited, but I have no clue as to why";
98  }
99 
100  return oss.str();
101 }
102 
103 // These two functions duplicate code in daemon.cc
104 static void block_signals()
105 {
106  sigset_t set;
107  sigemptyset (&set);
108  sigaddset(&set, SIGCHLD);
109  sigaddset(&set, SIGHUP);
110  sigaddset(&set, SIGTERM);
111  sigaddset(&set, SIGPIPE);
112 
113  if (sigprocmask(SIG_BLOCK, &set, 0) < 0) {
114  throw BESInternalError(string("sigprocmask error: ") + strerror(errno) + " while trying to block signals.", __FILE__, __LINE__);
115  }
116 }
117 
118 static void unblock_signals()
119 {
120  sigset_t set;
121  sigemptyset (&set);
122  sigaddset(&set, SIGCHLD);
123  sigaddset(&set, SIGHUP);
124  sigaddset(&set, SIGTERM);
125  sigaddset(&set, SIGPIPE);
126 
127  if (sigprocmask(SIG_UNBLOCK, &set, 0) < 0) {
128  throw BESInternalError(string("sigprocmask error: ") + strerror(errno) + " while trying to unblock signals.", __FILE__, __LINE__);
129  }
130 }
131 
132 // I moved the signal handlers here so that signal processing would be simpler
133 // and no library calls would be made to functions that are not 'asynch safe'.
134 // This was the fix for ticket 2025 and friends (the zombie process problem).
135 // jhrg 3/3/14
136 
137 // This is needed so that the master bes listener will get the exit status of
138 // all of the child bes listeners (preventing them from becoming zombies).
139 static void CatchSigChild(int sig)
140 {
141  if (sig == SIGCHLD) {
142  sigchild = 1;
143  }
144 }
145 
146 // If the HUP signal is sent to the master beslistener, it should exit and
147 // return a value indicating to the besdaemon that it should be restarted.
148 // This also has the side-affect of re-reading the configuration file.
149 static void CatchSigHup(int sig)
150 {
151  if (sig == SIGHUP) {
152  sighup = 1;
153  }
154 }
155 
156 static void CatchSigPipe(int sig)
157 {
158  if (sig == SIGPIPE) {
159  sigpipe = 1;
160  }
161 }
162 
163 // This is the default signal sent by 'kill'; when the master beslistener gets
164 // this signal it should stop. besdaemon should not try to start a new
165 // master beslistener.
166 static void CatchSigTerm(int sig)
167 {
168  if (sig == SIGTERM) {
169  sigterm = 1;
170  }
171 }
172 
181 static void register_signal_handlers()
182 {
183  struct sigaction act;
184  sigemptyset(&act.sa_mask);
185  sigaddset(&act.sa_mask, SIGCHLD);
186  sigaddset(&act.sa_mask, SIGPIPE);
187  sigaddset(&act.sa_mask, SIGTERM);
188  sigaddset(&act.sa_mask, SIGHUP);
189  act.sa_flags = 0;
190 #ifdef SA_RESTART
191  BESDEBUG("beslistener", "beslistener: setting restart for sigchld." << endl);
192  act.sa_flags |= SA_RESTART;
193 #endif
194 
195  BESDEBUG("beslistener", "beslistener: Registering signal handlers ... " << endl);
196 
197  act.sa_handler = CatchSigChild;
198  if (sigaction(SIGCHLD, &act, 0))
199  throw BESInternalFatalError("Could not register a handler to catch beslistener child process status.", __FILE__,
200  __LINE__);
201 
202  act.sa_handler = CatchSigPipe;
203  if (sigaction(SIGPIPE, &act, 0) < 0)
204  throw BESInternalFatalError("Could not register a handler to catch beslistener pipe signal.", __FILE__,
205  __LINE__);
206 
207  act.sa_handler = CatchSigTerm;
208  if (sigaction(SIGTERM, &act, 0) < 0)
209  throw BESInternalFatalError("Could not register a handler to catch beslistener terminate signal.", __FILE__,
210  __LINE__);
211 
212  act.sa_handler = CatchSigHup;
213  if (sigaction(SIGHUP, &act, 0) < 0)
214  throw BESInternalFatalError("Could not register a handler to catch beslistener hup signal.", __FILE__,
215  __LINE__);
216 
217  BESDEBUG("beslistener", "beslistener: OK" << endl);
218 }
219 
221  BESModuleApp(), _portVal(0), _gotPort(false), _unixSocket(""), _secure(false), _mypid(0), _ts(0), _us(0), _ps(0)
222 {
223  _mypid = getpid();
224 }
225 
227 {
228  delete TheBESKeys::TheKeys();
229 
231 }
232 
233 int ServerApp::initialize(int argc, char **argv)
234 {
235  int c = 0;
236  bool needhelp = false;
237  string dashi;
238  string dashc;
239  string dashd = "";
240 
241  // If you change the getopt statement below, be sure to make the
242  // corresponding change in daemon.cc and besctl.in
243  while ((c = getopt(argc, argv, "hvsd:c:p:u:i:r:")) != EOF) {
244  switch (c) {
245  case 'i':
246  dashi = optarg;
247  break;
248  case 'c':
249  dashc = optarg;
250  break;
251  case 'r':
252  break; // we can ignore the /var/run directory option here
253  case 'p':
254  _portVal = atoi(optarg);
255  _gotPort = true;
256  break;
257  case 'u':
258  _unixSocket = optarg;
259  break;
260  case 'd':
261  dashd = optarg;
262  // BESDebug::SetUp(optarg);
263  break;
264  case 'v':
266  break;
267  case 's':
268  _secure = true;
269  break;
270  case 'h':
271  case '?':
272  default:
273  needhelp = true;
274  break;
275  }
276  }
277 
278  // before we can do any processing, log any messages, initialize any
279  // modules, do anything, we need to determine where the BES
280  // configuration file lives. From here we get the name of the log
281  // file, group and user id, and information that the modules will
282  // need to run properly.
283 
284  // If the -c option was passed, set the config file name in TheBESKeys
285  if (!dashc.empty()) {
286  TheBESKeys::ConfigFile = dashc;
287  }
288 
289  // If the -c option was not passed, but the -i option
290  // was passed, then use the -i option to construct
291  // the path to the config file
292  if (dashc.empty() && !dashi.empty()) {
293  if (dashi[dashi.length() - 1] != '/') {
294  dashi += '/';
295  }
296  string conf_file = dashi + "etc/bes/bes.conf";
297  TheBESKeys::ConfigFile = conf_file;
298  }
299 
300  if (!dashd.empty()) BESDebug::SetUp(dashd);
301 
302  // register the two debug context for the server and ppt. The
303  // Default Module will register the bes context.
304  BESDebug::Register("server");
305  BESDebug::Register("ppt");
306 
307  // Because we are now running as the user specified in the
308  // configuration file, we won't be able to listen on system ports.
309  // If this is a problem, we may need to move this code above setting
310  // the user and group ids.
311  bool found = false;
312  string port_key = "BES.ServerPort";
313  if (!_gotPort) {
314  string sPort;
315  try {
316  TheBESKeys::TheKeys()->get_value(port_key, sPort, found);
317  }
318  catch (BESError &e) {
319  BESDEBUG("beslistener", "beslistener: FAILED" << endl);
320  string err = (string) "FAILED: " + e.get_message();
321  cerr << err << endl;
322  (*BESLog::TheLog()) << err << endl;
324  }
325  if (found) {
326  _portVal = atoi(sPort.c_str());
327  if (_portVal != 0) {
328  _gotPort = true;
329  }
330  }
331  }
332 
333  found = false;
334  string socket_key = "BES.ServerUnixSocket";
335  if (_unixSocket == "") {
336  try {
337  TheBESKeys::TheKeys()->get_value(socket_key, _unixSocket, found);
338  }
339  catch (BESError &e) {
340  BESDEBUG("server", "beslistener: FAILED" << endl);
341  string err = (string) "FAILED: " + e.get_message();
342  cerr << err << endl;
343  (*BESLog::TheLog()) << err << endl;
345  }
346  }
347 
348  if (!_gotPort && _unixSocket == "") {
349  string msg = "Must specify a tcp port or a unix socket or both\n";
350  msg += "Please specify on the command line with -p <port>";
351  msg += " and/or -u <unix_socket>\n";
352  msg += "Or specify in the bes configuration file with " + port_key + " and/or " + socket_key + "\n";
353  cout << endl << msg;
354  (*BESLog::TheLog()) << msg << endl;
356  }
357 
358  found = false;
359  if (_secure == false) {
360  string key = "BES.ServerSecure";
361  string isSecure;
362  try {
363  TheBESKeys::TheKeys()->get_value(key, isSecure, found);
364  }
365  catch (BESError &e) {
366  BESDEBUG("server", "beslistener: FAILED" << endl);
367  string err = (string) "FAILED: " + e.get_message();
368  cerr << err << endl;
369  (*BESLog::TheLog()) << err << endl;
371  }
372  if (isSecure == "Yes" || isSecure == "YES" || isSecure == "yes") {
373  _secure = true;
374  }
375  }
376 
377  BESDEBUG("beslistener", "beslistener: initializing default module ... " << endl);
378  BESDefaultModule::initialize(argc, argv);
379  BESDEBUG("beslistener", "beslistener: done initializing default module" << endl);
380 
381  BESDEBUG("beslistener", "beslistener: initializing default commands ... " << endl);
383  BESDEBUG("beslistener", "beslistener: done initializing default commands" << endl);
384 
385  // This will load and initialize all of the modules
386  BESDEBUG("beslistener", "beslistener: initializing loaded modules ... " << endl);
387  int ret = BESModuleApp::initialize(argc, argv);
388  BESDEBUG("beslistener", "beslistener: done initializing loaded modules" << endl);
389 
390  BESDEBUG("beslistener", "beslistener: initialized settings:" << *this);
391 
392  if (needhelp) {
394  }
395 
396  // This sets the process group to be ID of this process. All children
397  // will get this GID. Then use killpg() to send a signal to this process
398  // and all of the children.
399  session_id = setsid();
400  BESDEBUG("beslistener", "beslistener: The master beslistener session id (group id): " << session_id << endl);
401 
402  return ret;
403 }
404 
406 {
407  try {
408  BESDEBUG("beslistener", "beslistener: initializing memory pool ... " << endl);
410  BESDEBUG("beslistener", "OK" << endl);
411 
412  SocketListener listener;
413  if (_portVal) {
414  _ts = new TcpSocket(_portVal);
415  listener.listen(_ts);
416 
417  BESDEBUG("beslistener", "beslistener: listening on port (" << _portVal << ")" << endl);
418 
419  BESDEBUG("beslistener", "beslistener: about to write status (4)" << endl);
420  // Write to stdout works because the besdaemon is listening on the
421  // other end of a pipe where the pipe fd[1] has been dup2'd to
422  // stdout. See daemon.cc:start_master_beslistener.
423  // NB BESLISTENER_PIPE_FD is 1 (stdout)
424  int status = BESLISTENER_RUNNING;
425  int res = write(BESLISTENER_PIPE_FD, &status, sizeof(status));
426 
427  BESDEBUG("beslistener", "beslistener: wrote status (" << res << ")" << endl);
428  }
429 
430  if (!_unixSocket.empty()) {
431  _us = new UnixSocket(_unixSocket);
432  listener.listen(_us);
433  BESDEBUG("beslistener", "beslistener: listening on unix socket (" << _unixSocket << ")" << endl);
434  }
435 
436  BESServerHandler handler;
437 
438  _ps = new PPTServer(&handler, &listener, _secure);
439 
440  register_signal_handlers();
441 
442  // Loop forever, processing signals and running the code in PPTServer::initConnection().
443  // NB: The code in initConnection() used to loop forever, but I move that out to here
444  // so the signal handlers could be in this class. The PPTServer::initConnection() method
445  // is also used by daemon.cc but this class (ServerApp; the beslistener) and the besdaemon
446  // need to do different things for the signals like HUP and TERM, so they cannot share
447  // the signal processing code. One fix for the problem described in ticket 2025 was to
448  // move the signal handlers into PPTServer. Changing how the 'forever' loops are organized
449  // and keeping the signal processing code here (and in daemon.cc) is another solution that
450  // preserves the correct behavior of the besdaemon, too. jhrg 3/5/14
451  while (true) {
452  block_signals();
453 
454  if (sigterm | sighup | sigchild | sigpipe) {
455  int stat;
456  pid_t cpid;
457  while ((cpid = wait4(0 /*any child in the process group*/, &stat, WNOHANG, 0/*no rusage*/)) > 0) {
458  _ps->decr_num_children();
459  if (sigpipe) (*BESLog::TheLog()) << "Master listener caught SISPIPE from child: " << cpid << endl;
460 
461  BESDEBUG("ppt2", bes_exit_message(cpid, stat) << "; num children: " << _ps->get_num_children() << endl);
462  }
463  }
464 
465  if (sighup) {
466  BESDEBUG("ppt2", "Master listener caught SIGHUP, exiting with SERVER_EXIT_RESTART" << endl);
467 
468  (*BESLog::TheLog()) << "Master listener caught SIGHUP, exiting with SERVER_EXIT_RESTART" << endl;
469  ::exit(SERVER_EXIT_RESTART);
470  }
471 
472  if (sigterm) {
473  BESDEBUG("ppt2", "Master listener caught SIGTERM, exiting with SERVER_NORMAL_SHUTDOWN" << endl);
474 
475  (*BESLog::TheLog()) << "Master listener caught SIGTERM, exiting with SERVER_NORMAL_SHUTDOWN" << endl;
477  }
478 
479  sigchild = 0; sigpipe = 0; // No need to reset sigterm or sighup.
480  unblock_signals();
481 
482  _ps->initConnection();
483  }
484 
485  _ps->closeConnection();
486  }
487  catch (BESError &se) {
488  BESDEBUG("beslistener", "beslistener: caught BESError (" << se.get_message() << ")" << endl);
489 
490  (*BESLog::TheLog()) << se.get_message() << endl;
491  int status = SERVER_EXIT_FATAL_CANNOT_START;
492  write(BESLISTENER_PIPE_FD, &status, sizeof(status));
493  close(BESLISTENER_PIPE_FD);
494  return 1;
495  }
496  catch (...) {
497  (*BESLog::TheLog()) << "caught unknown exception initializing sockets" << endl;
498  int status = SERVER_EXIT_FATAL_CANNOT_START;
499  write(BESLISTENER_PIPE_FD, &status, sizeof(status));
500  close(BESLISTENER_PIPE_FD);
501  return 1;
502  }
503 
504  close(BESLISTENER_PIPE_FD);
505  return 0;
506 }
507 
509 {
510  pid_t apppid = getpid();
511  if (apppid == _mypid) {
512  // These are all safe to call in a signalhandler
513  if (_ps) {
514  _ps->closeConnection();
515  delete _ps;
516  }
517  if (_ts) {
518  _ts->close();
519  delete _ts;
520  }
521  if (_us) {
522  _us->close();
523  delete _us;
524  }
525 
526  // Do this in the reverse order that it was initialized. So
527  // terminate the loaded modules first, then the default
528  // commands, then the default module.
529 
530  // These are not safe to call in a signal handler
531  BESDEBUG("beslistener", "beslistener: terminating loaded modules ... " << endl);
533  BESDEBUG("beslistener", "beslistener: done terminating loaded modules" << endl);
534 
535  BESDEBUG("beslistener", "beslistener: terminating default commands ... " << endl);
537  BESDEBUG("beslistener", "beslistener: done terminating default commands ... " << endl);
538 
539  BESDEBUG("beslistener", "beslistener: terminating default module ... " << endl);
541  BESDEBUG("beslistener", "beslistener: done terminating default module ... " << endl);
542  }
543  return sig;
544 }
545 
552 void ServerApp::dump(ostream &strm) const
553 {
554  strm << BESIndent::LMarg << "ServerApp::dump - (" << (void *) this << ")" << endl;
556  strm << BESIndent::LMarg << "got port? " << _gotPort << endl;
557  strm << BESIndent::LMarg << "port: " << _portVal << endl;
558  strm << BESIndent::LMarg << "unix socket: " << _unixSocket << endl;
559  strm << BESIndent::LMarg << "is secure? " << _secure << endl;
560  strm << BESIndent::LMarg << "pid: " << _mypid << endl;
561  if (_ts) {
562  strm << BESIndent::LMarg << "tcp socket:" << endl;
564  _ts->dump(strm);
566  }
567  else {
568  strm << BESIndent::LMarg << "tcp socket: null" << endl;
569  }
570  if (_us) {
571  strm << BESIndent::LMarg << "unix socket:" << endl;
573  _us->dump(strm);
575  }
576  else {
577  strm << BESIndent::LMarg << "unix socket: null" << endl;
578  }
579  if (_ps) {
580  strm << BESIndent::LMarg << "ppt server:" << endl;
582  _ps->dump(strm);
584  }
585  else {
586  strm << BESIndent::LMarg << "ppt server: null" << endl;
587  }
588  BESModuleApp::dump(strm);
590 }
591 
592 int main(int argc, char **argv)
593 {
594  try {
595  ServerApp app;
596  return app.main(argc, argv);
597  }
598  catch (BESError &e) {
599  cerr << "Caught unhandled exception: " << endl;
600  cerr << e.get_message() << endl;
601  return 1;
602  }
603  catch (...) {
604  cerr << "Caught unhandled, unknown exception" << endl;
605  return 1;
606  }
607  return 0;
608 }
609 
virtual ~ServerApp()
Definition: ServerApp.cc:226
#define SERVER_EXIT_FATAL_CANNOT_START
exception thrown if an internal error is found and is fatal to the BES
exception thrown if inernal error encountered
static void SetUp(const string &values)
Sets up debugging for the bes.
Definition: BESDebug.cc:68
virtual void dump(std::ostream &strm) const
dumps information about this object
Definition: TcpSocket.cc:584
#define BESLISTENER_RUNNING
static int initialize(int argc, char **argv)
void unblock_signals()
See block_signals()
Definition: daemon.cc:191
#define SERVER_EXIT_NORMAL_SHUTDOWN
virtual int main(int argC, char **argV)
main method of the BES application
Definition: BESBaseApp.cc:74
virtual void dump(ostream &strm) const
dumps information about this object
void decr_num_children()
Definition: PPTServer.h:63
static BESMemoryGlobalArea * initialize_memory_pool()
virtual void dump(ostream &strm) const
dumps information about this object
Definition: ServerApp.cc:552
#define BESLISTENER_PIPE_FD
virtual int terminate(int sig=0)
clean up after the application
static int terminate(void)
Removes the default set of BES XML commands from the list of possible commands.
static void show_usage(const string &app_name)
static void Indent()
Definition: BESIndent.cc:38
string appName(void) const
Returns the name of the application.
Definition: BESApp.h:132
virtual string get_message()
get the error message for this exception
Definition: BESError.h:94
static int initialize(int argc, char **argv)
Loads the default set of BES XML commands.
virtual void initConnection()
Using the info passed into the SocketLister, wait for an inbound request (see SocketListener::accept(...
Definition: PPTServer.cc:139
Abstract exception class for the BES with basic string message.
Definition: BESError.h:51
virtual void closeConnection()
Definition: PPTServer.cc:169
static int terminate(void)
static ostream & LMarg(ostream &strm)
Definition: BESIndent.cc:73
virtual int terminate(int sig=0)
clean up after the application
Definition: ServerApp.cc:508
virtual void dump(ostream &strm) const
dumps information about this object
Definition: UnixSocket.cc:268
virtual int initialize(int argC, char **argV)
Load and initialize any BES modules.
Definition: ServerApp.cc:233
Base application object for all BES applications.
Definition: BESModuleApp.h:59
virtual void close()
Definition: Socket.cc:76
virtual int initialize(int argC, char **argV)
Load and initialize any BES modules.
Definition: BESModuleApp.cc:70
void get_value(const string &s, string &val, bool &found)
Retrieve the value of a given key, if set.
Definition: BESKeys.cc:453
static void show_version(const string &app_name)
int main(int argc, char **argv)
Definition: ServerApp.cc:592
static BESLog * TheLog()
Definition: BESLog.cc:347
void block_signals()
For code that must use signals to stop and start the master listener, block signals being delivered t...
Definition: daemon.cc:177
int get_num_children()
Definition: PPTServer.h:61
#define BESDEBUG(x, y)
macro used to send debug information to the debug stream
Definition: BESDebug.h:64
static void delete_all_catalogs()
#define SERVER_EXIT_RESTART
static void UnIndent()
Definition: BESIndent.cc:44
virtual void listen(Socket *s)
static BESKeys * TheKeys()
Definition: TheBESKeys.cc:48
static void Register(const string &flagName)
register the specified debug flag
Definition: BESDebug.h:138
static BESApp * TheApplication(void)
Returns the BESApp application object for this application.
Definition: BESApp.h:138
virtual void close()
Definition: UnixSocket.cc:231
virtual int run()
the applications functionality is implemented in the run method
Definition: ServerApp.cc:405
static string ConfigFile
Definition: TheBESKeys.h:50
virtual void dump(ostream &strm) const
dumps information about this object
Definition: PPTServer.cc:277