LP#1343578: Perl/C syslog "logtag" additions.
[OpenSRF.git] / src / router / osrf_router_main.c
1 /**
2         @file osrf_router_main.c
3         @brief top level of OSRF Router
4
5         This top level loads a configuration file and forks into one or more child processes.
6         Each child process configures itself, daemonizes itself, and then (via a call to
7         osrfRouterRun()) goes into an infinite loop to route messages among clients and servers.
8
9         The first command-line parameter is the name of the configuration file.
10
11         The second command-line parameter is the context -- an XML tag identifying the subset
12         of the configuration file that is relevant to this application (since a configuration
13         file may include information for multiple applications).
14
15         Any subsequent command-line parameters are silently ignored.
16 */
17
18 #include <signal.h>
19 #include <sys/types.h>
20 #include <sys/wait.h>
21 #include <sys/mman.h>
22 #include <errno.h>
23 #include "opensrf/utils.h"
24 #include "opensrf/log.h"
25 #include "opensrf/osrf_list.h"
26 #include "opensrf/string_array.h"
27 #include "opensrf/osrfConfig.h"
28 #include "osrf_router.h"
29
30 static osrfRouter* router = NULL;
31
32 static volatile sig_atomic_t stop_signal = 0;
33
34 static void setupRouter( const jsonObject* configChunk, int configPos );
35
36 /* I think it's important these following things not be static */
37 pid_t* daemon_pid_list;
38 size_t  daemon_pid_list_size;
39
40
41 void storeRouterDaemonPid( pid_t, int );
42
43 /**
44         @brief Respond to signal by setting a switch that will interrupt the main loop.
45         @param signo The signal number.
46
47         Signal handler.  We not only interrupt the main loop but also remember the signal
48         number so that we can report it later and re-raise it.
49 */
50 void routerSignalHandler( int signo ) {
51
52         signal( signo, routerSignalHandler );
53         router_stop( router );
54         stop_signal = signo;
55 }
56
57 /**
58         @brief The top-level function of the router program.
59         @param argc Number of items in command line.
60         @param argv Pointer to array of items on command line.
61         @return System return code.
62
63         Load a configuration file, spawn zero or more child processes, and exit.
64 */
65 int main( int argc, char* argv[] ) {
66
67         if( argc < 3 ) {
68                 osrfLogError( OSRF_LOG_MARK,
69                         "Usage: %s <path_to_config_file> <config_context> [pid_file]",
70                         argv[0] );
71                 exit( EXIT_FAILURE );
72         }
73
74         const char* config_file = argv[1];
75         const char* context = argv[2];
76         char* pid_file = argc >= 4 ? strdup(argv[3]) : NULL;
77
78         /* Get a set of router definitions from a config file */
79
80         osrfConfig* cfg = osrfConfigInit(config_file, context);
81         if( NULL == cfg ) {
82                 osrfLogError( OSRF_LOG_MARK, "Router can't load config file %s", config_file );
83                 exit( EXIT_FAILURE );
84         }
85
86         osrfConfigSetDefaultConfig(cfg);
87         jsonObject* configInfo = osrfConfigGetValueObject(NULL, "/router");
88
89         if( configInfo->size < 1 || NULL == jsonObjectGetIndex( configInfo, 1 ) ) {
90                 osrfLogError( OSRF_LOG_MARK, "No routers defined in config file %s, context \"%s\"",
91                         config_file, context );
92                 exit( EXIT_FAILURE );
93         }
94
95         /* We're done with the command line now, so we can safely overlay it */
96
97         init_proc_title( argc, argv );
98         set_proc_title( "OpenSRF Router" );
99
100         /* Set up some shared memory so after some forking(), our children
101          * can tell us about all our grandchildren's PIDs, and we can write
102          * them to a file.
103          */
104         daemon_pid_list_size = configInfo->size * sizeof(pid_t);
105         daemon_pid_list = mmap(
106                 NULL, daemon_pid_list_size, PROT_READ | PROT_WRITE,
107                 MAP_ANONYMOUS | MAP_SHARED, -1 , 0
108         );
109
110         if ( daemon_pid_list == MAP_FAILED ) {
111                 osrfLogError(
112                         OSRF_LOG_MARK,
113                         "mmap() for router daemon PID list failed: %s",
114                         strerror(errno)
115                 );
116                 exit( EXIT_FAILURE );
117         }
118
119         memset( daemon_pid_list, 0, daemon_pid_list_size );
120
121         /* Spawn child process(es) */
122
123         int rc = EXIT_SUCCESS;
124         int parent = 1;    // boolean
125         int i;
126
127         FILE *pid_fp;
128
129         for(i = 0; i < configInfo->size; i++) {
130                 const jsonObject* configChunk = jsonObjectGetIndex( configInfo, i );
131                 if( ! jsonObjectGetKeyConst( configChunk, "transport" ) )
132                 {
133                         // In searching the configuration file for a given context, we may have found a
134                         // spurious hit on an unrelated part of the configuration file that happened to use
135                         // the same XML tag.  In fact this happens routinely in practice.
136
137                         // If we don't see a member for "transport" then this is presumably such a spurious
138                         // hit, so we silently ignore it.
139
140                         // It is also possible that it's the right part of the configuration file but it has a
141                         // typo or other such error, making it look spurious.  In that case, well, too bad.
142                         continue;
143                 }
144
145                 if(fork() == 0) { /* create a new child to run this router instance */
146                         free( pid_file );   /* we don't need this as a child */
147                         setupRouter(configChunk, i);
148                         parent = 0;
149                         break;  /* We're a child; don't spawn any more children here */
150                 }
151         }
152
153         if( parent ) {
154                 // Wait for all child processes to terminate; report their fates
155                 while( 1 ) {  // Loop until all children terminate
156                         int status;
157                         errno = 0;
158                         pid_t child_pid = wait( &status );
159                         if( -1 == child_pid ) {
160                                 // ECHILD means no children are left.  Anything else we ignore.
161                                 if( ECHILD == errno )
162                                         break;
163                         } else if( WIFEXITED( status ) ) {
164                                 // Relatively normal exit, i.e. via calling exit()
165                                 // or _exit(), or by returning from main()
166                                 int child_rc = WEXITSTATUS( status );
167                                 if( child_rc ) {
168                                         osrfLogWarning( OSRF_LOG_MARK, 
169                                                 "Child router process %ld exited with return status %d",
170                                                 (long) child_pid, child_rc );
171                                         rc = EXIT_FAILURE;
172                                 } else {
173                                         ;    // Terminated successfully; silently ignore
174                                 }
175                         } else if( WIFSIGNALED( status ) ) {
176                                 // Killed by a signal
177                                 int signo = WTERMSIG( status );
178                                 const char* extra = "";
179 #ifdef WCOREDUMP
180                                 if( WCOREDUMP( status ) )
181                                         extra = "with core dump ";
182 #endif
183                                 osrfLogWarning( OSRF_LOG_MARK, "Child router process %ld killed %sby signal %d",
184                                         (long) child_pid, extra, signo );
185
186                                 rc = EXIT_FAILURE;
187                         }
188                 }
189
190                 /* If rc is still EXIT_SUCCESS after the preceding loop,
191                  * all our children have spawned grandchildren and will have
192                  * reported their IDs via our shared memory daemon_pid_list
193                  * buffer by now.
194                  *
195                  * A note about that list: it's going to have one pid_t-sized
196                  * slot for every configInfo chunk in the config.  Commonly,
197                  * this code sees empty chunks or chunks that don't correspond
198                  * to full router configs, so the slots for these unused chunks
199                  * get left at zero.  We skip those zeros both when writing the
200                  * PID file and when reporting to the log.
201                  * */
202                 if ( rc == EXIT_SUCCESS ) {
203                         if ( pid_file ) {
204                                 if ( (pid_fp = fopen(pid_file, "w")) ) {
205                                         for (i = 0; i < configInfo->size; i++) {
206                                                 if ( daemon_pid_list[i] > 0 )
207                                                         fprintf(pid_fp, "%d\n", daemon_pid_list[i]);
208                                         }
209                                         fclose(pid_fp);
210                                 } else {
211                                         osrfLogWarning(OSRF_LOG_MARK,
212                                                 "Tried to write PID file at %s but couldn't: %s",
213                                                 pid_file, strerror(errno));
214                                 }
215                                 free( pid_file );
216                         }
217
218 //                      for (i = 0; i < configInfo->size; i++) {
219 //                              if ( daemon_pid_list[i] > 0 ) {
220 //                                      osrfLogInfo(OSRF_LOG_MARK,
221 //                                              "Reporting a grandchild with PID %d", daemon_pid_list[i]);
222 //                              }
223 //                      }
224                 }
225                 munmap( daemon_pid_list, daemon_pid_list_size );
226         }
227
228         if( stop_signal ) {
229                 // Interrupted by a signal?  Re-raise so the parent can see it.
230                 osrfLogDebug(OSRF_LOG_MARK,
231                         "Router received signal %d; re-raising", (int) stop_signal);
232                 signal( stop_signal, SIG_DFL );
233                 raise( stop_signal );
234         }
235
236         return rc;
237 }
238
239 /**
240         @brief Configure and run a child process.
241         @param configChunk Pointer to a subset of the loaded configuration.
242         @param configPos Index of configChunk in its original containing array, doubling as index for storing PID of child from daemonization (the original process' grandchild)
243
244         Configure oneself, daemonize, and then call osrfRouterRun() to go into a
245         near-endless loop.  Return when interrupted by a signal, or when something goes wrong.
246 */
247 static void setupRouter( const jsonObject* configChunk, int configPos ) {
248
249         const jsonObject* transport_cfg = jsonObjectGetKeyConst( configChunk, "transport" );
250
251         const char* server   = jsonObjectGetString( jsonObjectGetKeyConst( transport_cfg, "server" ));
252         const char* port     = jsonObjectGetString( jsonObjectGetKeyConst( transport_cfg, "port" ));
253         const char* username = jsonObjectGetString( jsonObjectGetKeyConst( transport_cfg, "username" ));
254         const char* password = jsonObjectGetString( jsonObjectGetKeyConst( transport_cfg, "password" ));
255         const char* resource = jsonObjectGetString( jsonObjectGetKeyConst( transport_cfg, "resource" ));
256
257         const char* level    = jsonObjectGetString( jsonObjectGetKeyConst( configChunk, "loglevel" ));
258         const char* log_file = jsonObjectGetString( jsonObjectGetKeyConst( configChunk, "logfile" ));
259         const char* log_tag  = jsonObjectGetString( jsonObjectGetKeyConst( configChunk, "logtag" ));
260         const char* facility = jsonObjectGetString( jsonObjectGetKeyConst( configChunk, "syslog" ));
261
262         int llevel = 1;
263         if(level) llevel = atoi(level);
264
265         if(!log_file)
266         {
267                 osrfLogError( OSRF_LOG_MARK, "Log file name not specified for router" );
268                 return;
269         }
270
271         if(!strcmp(log_file, "syslog")) {
272                 if(log_tag) osrfLogSetLogTag(log_tag);
273                 osrfLogInit( OSRF_LOG_TYPE_SYSLOG, "router", llevel );
274                 osrfLogSetSyslogFacility(osrfLogFacilityToInt(facility));
275
276         } else {
277                 osrfLogInit( OSRF_LOG_TYPE_FILE, "router", llevel );
278                 osrfLogSetFile( log_file );
279         }
280
281         osrfLogInfo( OSRF_LOG_MARK, "Router connecting as: server: %s port: %s "
282                 "user: %s resource: %s", server, port, username, resource );
283
284         int iport = 0;
285         if(port)
286                 iport = atoi( port );
287
288         osrfStringArray* tclients = osrfNewStringArray(4);
289         osrfStringArray* tservers = osrfNewStringArray(4);
290
291         jsonObject* tclientsList = jsonObjectFindPath(configChunk, "/trusted_domains/client");
292         jsonObject* tserversList = jsonObjectFindPath(configChunk, "/trusted_domains/server");
293
294         int i;
295
296         if(tserversList->type == JSON_ARRAY) {
297                 for( i = 0; i != tserversList->size; i++ ) {
298                         const char* serverDomain = jsonObjectGetString(jsonObjectGetIndex(tserversList, i));
299                         osrfLogInfo( OSRF_LOG_MARK,  "Router adding trusted server: %s", serverDomain);
300                         osrfStringArrayAdd(tservers, serverDomain);
301                 }
302         } else {
303                 const char* serverDomain = jsonObjectGetString(tserversList);
304                 osrfLogInfo( OSRF_LOG_MARK,  "Router adding trusted server: %s", serverDomain);
305                 osrfStringArrayAdd(tservers, serverDomain);
306         }
307
308         if(tclientsList->type == JSON_ARRAY) {
309                 for( i = 0; i != tclientsList->size; i++ ) {
310                         const char* clientDomain = jsonObjectGetString(jsonObjectGetIndex(tclientsList, i));
311                         osrfLogInfo( OSRF_LOG_MARK,  "Router adding trusted client: %s", clientDomain);
312                         osrfStringArrayAdd(tclients, clientDomain);
313                 }
314         } else {
315                 const char* clientDomain = jsonObjectGetString(tclientsList);
316                 osrfLogInfo( OSRF_LOG_MARK,  "Router adding trusted client: %s", clientDomain);
317                 osrfStringArrayAdd(tclients, clientDomain);
318         }
319
320
321         if( tclients->size == 0 || tservers->size == 0 ) {
322                 osrfLogError( OSRF_LOG_MARK,
323                                 "We need trusted servers and trusted client to run the router...");
324                 osrfStringArrayFree( tservers );
325                 osrfStringArrayFree( tclients );
326                 return;
327         }
328
329         router = osrfNewRouter( server,
330                         username, resource, password, iport, tclients, tservers );
331
332         signal(SIGHUP,routerSignalHandler);
333         signal(SIGINT,routerSignalHandler);
334         signal(SIGTERM,routerSignalHandler);
335
336         if( (osrfRouterConnect(router)) != 0 ) {
337                 osrfLogError( OSRF_LOG_MARK, "Unable to connect router to jabber server %s... exiting",
338                         server );
339                 osrfRouterFree(router);
340                 return;
341         }
342
343         // Done configuring?  Let's get to work.
344
345         daemonizeWithCallback( storeRouterDaemonPid, configPos );
346         osrfRouterRun( router );
347
348         osrfRouterFree(router);
349         router = NULL;
350         osrfLogInfo( OSRF_LOG_MARK, "Router freed" );
351
352         return;
353 }
354
355 void storeRouterDaemonPid( pid_t p, int i ) {
356         if( i != -1 )
357                 daemon_pid_list[i] = p;
358 }