1. In the parent router process: wait for all of the immediate
[OpenSRF.git] / src / router / osrf_router_main.c
index 706d214..0983991 100644 (file)
-#include "osrf_router.h"
-#include "opensrf/osrfConfig.h"
+/**
+       @file osrf_router_main.c
+       @brief top level of OSRF Router
+
+       This top level loads a configuration file and forks into one or more child processes.
+       Each child process configures itself, daemonizes itself, and then (via a call to
+       osrfRouterRun()) goes into an infinite loop to route messages among clients and servers.
+
+       The first command-line parameter is the name of the configuration file.
+
+       The second command-line parameter is the context -- an XML tag identifying the subset
+       of the configuration file that is relevant to this application (since a configuration
+       file may include information for multiple applications).
+
+       Any subsequent command-line parameters are silently ignored.
+*/
+
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <errno.h>
 #include "opensrf/utils.h"
 #include "opensrf/log.h"
-#include <signal.h>
+#include "opensrf/osrf_list.h"
+#include "opensrf/string_array.h"
+#include "opensrf/osrfConfig.h"
+#include "osrf_router.h"
 
 static osrfRouter* router = NULL;
 
+static volatile sig_atomic_t stop_signal = 0;
+
+static void setupRouter(jsonObject* configChunk);
+
+/**
+       @brief Respond to signal by setting a switch that will interrupt the main loop.
+       @param signo The signal number.
+
+       Signal handler.  We not only interrupt the main loop but also remember the signal
+       number so that we can report it later and re-raise it.
+*/
 void routerSignalHandler( int signo ) {
-       osrfLogWarning( OSRF_LOG_MARK, "Received signal [%d], cleaning up...", signo );
-       osrfConfigCleanup();
-       osrfRouterFree(router);
-       router = NULL;
 
-       // Exit by re-raising the signal so that the parent
-       // process can detect it
-       
-       signal( signo, SIG_DFL );
-       raise( signo );
+       signal( signo, routerSignalHandler );
+       router_stop( router );
+       stop_signal = signo;
 }
 
-static int setupRouter( char* config, char* context );
-
+/**
+       @brief The top-level function of the router program.
+       @param argc Number of items in command line.
+       @param argv Pointer to array of items on command line.
+       @return System return code.
 
+       Load a configuration file, spawn zero or more child processes, and exit.
+*/
 int main( int argc, char* argv[] ) {
 
        if( argc < 3 ) {
-               osrfLogError( OSRF_LOG_MARK,  "Usage: %s <path_to_config_file> <config_context>", argv[0] );
-               exit(0);
+               osrfLogError( OSRF_LOG_MARK,
+                       "Usage: %s <path_to_config_file> <config_context>", argv[0] );
+               exit( EXIT_FAILURE );
+       }
+
+       const char* config_file = argv[1];
+       const char* context = argv[2];
+
+       /* Get a set of router definitions from a config file */
+
+       osrfConfig* cfg = osrfConfigInit(config_file, context);
+       if( NULL == cfg ) {
+               osrfLogError( OSRF_LOG_MARK, "Router can't load config file %s", config_file );
+               exit( EXIT_FAILURE );
+       }
+
+       osrfConfigSetDefaultConfig(cfg);
+       jsonObject* configInfo = osrfConfigGetValueObject(NULL, "/router");
+
+       if( configInfo->size < 1 || NULL == jsonObjectGetIndex( configInfo, 1 ) ) {
+               osrfLogError( OSRF_LOG_MARK, "No routers defined in config file %s, context \"%s\"",
+                       config_file, context );
+               exit( EXIT_FAILURE );
        }
 
-       char* config = strdup( argv[1] );
-       char* context = strdup( argv[2] );
+       /* We're done with the command line now, so we can safely overlay it */
+
        init_proc_title( argc, argv );
        set_proc_title( "OpenSRF Router" );
 
-       int rc = setupRouter( config, context );
-       free(config);
-       free(context);
-       return rc ? EXIT_FAILURE : EXIT_SUCCESS;
+       /* Spawn child process(es) */
+
+       int rc = EXIT_SUCCESS;
+       int parent = 1;    // boolean
+       int i;
+       for(i = 0; i < configInfo->size; i++) {
+               jsonObject* configChunk = jsonObjectGetIndex(configInfo, i);
+               if( ! jsonObjectGetKey( configChunk, "transport" ) )
+               {
+                       // In searching the configuration file for a given context, we may have found a
+                       // spurious hit on an unrelated part of the configuration file that happened to use
+                       // the same XML tag.  In fact this happens routinely in practice.
+
+                       // If we don't see a member for "transport" then this is presumably such a spurious
+                       // hit, so we silently ignore it.
+
+                       // It is also possible that it's the right part of the configuration file but it has a
+                       // typo or other such error, making it look spurious.  In that case, well, too bad.
+                       continue;
+               }
+               if(fork() == 0) { /* create a new child to run this router instance */
+                       setupRouter(configChunk);
+                       parent = 0;
+                       break;  /* We're a child; don't spawn any more children here */
+               }
+       }
+
+       if( parent ) {
+               // Wait for all child processes to terminate.
+               // If any ended abnormally, report it.
+               while( 1 ) {  // Loop until all children terminate
+                       int status;
+                       errno = 0;
+                       pid_t child_pid = wait( &status );
+                       if( -1 == child_pid ) {
+                               // ECHILD means no children are left.  Anything else we ignore.
+                               if( ECHILD == errno )
+                                       break;
+                       } else if( WIFEXITED( status ) ) {
+                               // Relatively normal exit, i.e. via calling exit()
+                               // or _exit(), or by returning from main()
+                               int child_rc = WEXITSTATUS( status );
+                               if( child_rc ) {
+                                       osrfLogWarning( OSRF_LOG_MARK,
+                                               "Child router process %ld exited with return status %d",
+                                               (long) child_pid, child_rc );
+                                       rc = EXIT_FAILURE;
+                               } else {
+                                       ;    // Terminated successfully; silently ignore
+                               }
+                       } else if( WIFSIGNALED( status ) ) {
+                               // Killed by a signal
+                               int signo = WTERMSIG( status );
+                               const char* extra = "";
+#ifdef WCOREDUMP
+                               if( WCOREDUMP( status ) )
+                                       extra = "with core dump ";
+#endif
+                               osrfLogWarning( OSRF_LOG_MARK, "Child router process %ld killed %sby signal %d",
+                                       (long) child_pid, extra, signo );
+
+                               rc = EXIT_FAILURE;
+                       }
+               }
+       }
+
+       if( stop_signal ) {
+               // Interrupted by a signal?  Re-raise so the parent can see it.
+               osrfLogWarning( OSRF_LOG_MARK, "Interrupted by signal %d; re-raising",
+                               (int) stop_signal );
+               signal( stop_signal, SIG_DFL );
+               raise( stop_signal );
+       }
+
+       return rc;
 }
 
-int setupRouter( char* config, char* context ) {
+/**
+       @brief Configure and run a child process.
+       @param configChunk Pointer to a subset of the loaded configuration.
 
-       osrfConfig* cfg = osrfConfigInit( config, context );
-       osrfConfigSetDefaultConfig(cfg);
+       Configure oneself, daemonize, and then call osrfRouterRun() to go into a
+       near-endless loop.  Return when interrupted by a signal, or when something goes wrong.
+*/
+static void setupRouter(jsonObject* configChunk) {
+
+       jsonObject* transport_cfg = jsonObjectGetKey( configChunk, "transport" );
 
-       char* server                    = osrfConfigGetValue(NULL, "/transport/server");
-       char* port                              = osrfConfigGetValue(NULL, "/transport/port");
-       char* username                  = osrfConfigGetValue(NULL, "/transport/username");
-       char* password                  = osrfConfigGetValue(NULL, "/transport/password");
-       char* resource                  = osrfConfigGetValue(NULL, "/transport/resource");
+       const char* server   = jsonObjectGetString( jsonObjectGetKey( transport_cfg, "server" ) );
+       const char* port     = jsonObjectGetString( jsonObjectGetKey( transport_cfg, "port" ) );
+       const char* username = jsonObjectGetString( jsonObjectGetKey( transport_cfg, "username" ) );
+       const char* password = jsonObjectGetString( jsonObjectGetKey( transport_cfg, "password" ) );
+       const char* resource = jsonObjectGetString( jsonObjectGetKey( transport_cfg, "resource" ) );
 
-       /* set up the logger */
-       char* level = osrfConfigGetValue(NULL, "/loglevel");
-       char* log_file = osrfConfigGetValue(NULL, "/logfile");
-       char* facility = osrfConfigGetValue(NULL, "/syslog");
+       const char* level    = jsonObjectGetString( jsonObjectGetKey( configChunk, "loglevel" ) );
+       const char* log_file = jsonObjectGetString( jsonObjectGetKey( configChunk, "logfile" ) );
+       const char* facility = jsonObjectGetString( jsonObjectGetKey( configChunk, "syslog" ) );
 
        int llevel = 1;
        if(level) llevel = atoi(level);
 
-       if(!log_file) { fprintf(stderr, "Log file name not specified\n"); return -1; }
+       if(!log_file)
+       {
+               osrfLogError( OSRF_LOG_MARK, "Log file name not specified for router" );
+               return;
+       }
 
        if(!strcmp(log_file, "syslog")) {
                osrfLogInit( OSRF_LOG_TYPE_SYSLOG, "router", llevel );
@@ -70,62 +203,75 @@ int setupRouter( char* config, char* context ) {
                osrfLogSetFile( log_file );
        }
 
-       free(facility);
-       free(level);
-       free(log_file);
-
-       osrfLogInfo(  OSRF_LOG_MARK, "Router connecting as: server: %s port: %s "
-                       "user: %s resource: %s", server, port, username, resource );
+       osrfLogInfo( OSRF_LOG_MARK, "Router connecting as: server: %s port: %s "
+               "user: %s resource: %s", server, port, username, resource );
 
        int iport = 0;
-       if(port)        iport = atoi( port );
+       if(port)
+               iport = atoi( port );
 
        osrfStringArray* tclients = osrfNewStringArray(4);
        osrfStringArray* tservers = osrfNewStringArray(4);
-       osrfConfigGetValueList(NULL, tservers, "/trusted_domains/server" );
-       osrfConfigGetValueList(NULL, tclients, "/trusted_domains/client" );
+
+       jsonObject* tclientsList = jsonObjectFindPath(configChunk, "/trusted_domains/client");
+       jsonObject* tserversList = jsonObjectFindPath(configChunk, "/trusted_domains/server");
 
        int i;
-       for( i = 0; i != tservers->size; i++ ) 
-               osrfLogInfo( OSRF_LOG_MARK,  "Router adding trusted server: %s", osrfStringArrayGetString( tservers, i ) );
 
-       for( i = 0; i != tclients->size; i++ ) 
-               osrfLogInfo( OSRF_LOG_MARK,  "Router adding trusted client: %s", osrfStringArrayGetString( tclients, i ) );
+       if(tserversList->type == JSON_ARRAY) {
+               for( i = 0; i != tserversList->size; i++ ) {
+                       const char* serverDomain = jsonObjectGetString(jsonObjectGetIndex(tserversList, i));
+                       osrfLogInfo( OSRF_LOG_MARK,  "Router adding trusted server: %s", serverDomain);
+                       osrfStringArrayAdd(tservers, serverDomain);
+               }
+       } else {
+               const char* serverDomain = jsonObjectGetString(tserversList);
+               osrfLogInfo( OSRF_LOG_MARK,  "Router adding trusted server: %s", serverDomain);
+               osrfStringArrayAdd(tservers, serverDomain);
+       }
+
+       if(tclientsList->type == JSON_ARRAY) {
+               for( i = 0; i != tclientsList->size; i++ ) {
+                       const char* clientDomain = jsonObjectGetString(jsonObjectGetIndex(tclientsList, i));
+                       osrfLogInfo( OSRF_LOG_MARK,  "Router adding trusted client: %s", clientDomain);
+                       osrfStringArrayAdd(tclients, clientDomain);
+               }
+       } else {
+               const char* clientDomain = jsonObjectGetString(tclientsList);
+               osrfLogInfo( OSRF_LOG_MARK,  "Router adding trusted client: %s", clientDomain);
+               osrfStringArrayAdd(tclients, clientDomain);
+       }
 
        if( tclients->size == 0 || tservers->size == 0 ) {
-               osrfLogError( OSRF_LOG_MARK, "We need trusted servers and trusted client to run the router...");
+               osrfLogError( OSRF_LOG_MARK,
+                               "We need trusted servers and trusted client to run the router...");
                osrfStringArrayFree( tservers );
                osrfStringArrayFree( tclients );
-               return -1;
+               return;
        }
 
        router = osrfNewRouter( server,
                        username, resource, password, iport, tclients, tservers );
-       
+
        signal(SIGHUP,routerSignalHandler);
        signal(SIGINT,routerSignalHandler);
        signal(SIGTERM,routerSignalHandler);
 
        if( (osrfRouterConnect(router)) != 0 ) {
-               fprintf(stderr, "!!!! Unable to connect router to jabber server %s... exiting", server );
+               osrfLogError( OSRF_LOG_MARK, "Unable to connect router to jabber server %s... exiting",
+                       server );
                osrfRouterFree(router);
-               return -1;
+               return;
        }
 
-       free(server); free(port); 
-       free(username); free(password);
-       free(resource);
+       // Done configuring?  Let's get to work.
 
        daemonize();
        osrfRouterRun( router );
 
-       // Shouldn't get here, since osrfRouterRun()
-       // should go into an infinite loop
-
        osrfRouterFree(router);
        router = NULL;
-       
-       return -1;
-}
-
+       osrfLogInfo( OSRF_LOG_MARK, "Router freed" );
 
+       return;
+}