c9eb7d3bfaa2863745486b504cb6562a93211759
[OpenSRF.git] / src / libopensrf / osrf_system.c
1 /**
2         @file osrf_system.c
3         @brief Launch a collection of servers.
4 */
5
6 #include <sys/types.h>
7 #include <sys/time.h>
8 #include <unistd.h>
9 #include <stdlib.h>
10 #include <stdio.h>
11 #include <string.h>
12 #include <sys/select.h>
13 #include <sys/wait.h>
14 #include <signal.h>
15
16 #include "opensrf/utils.h"
17 #include "opensrf/log.h"
18 #include "opensrf/osrf_system.h"
19 #include "opensrf/osrf_application.h"
20 #include "opensrf/osrf_prefork.h"
21
22 #ifndef HOST_NAME_MAX
23 #define HOST_NAME_MAX 256
24 #endif
25
26 static void report_child_status( pid_t pid, int status );
27 struct child_node;
28 typedef struct child_node ChildNode;
29
30 /**
31         @brief Represents a child process.
32 */
33 struct child_node
34 {
35         ChildNode* pNext;  /**< Linkage pointer for doubly linked list. */
36         ChildNode* pPrev;  /**< Linkage pointer for doubly linked list. */
37         pid_t pid;         /**< Process ID of the child process. */
38         char* app;
39         char* libfile;
40 };
41
42 /** List of child processes. */
43 static ChildNode* child_list;
44
45 /** Pointer to the global transport_client; i.e. our connection to Jabber. */
46 static transport_client* osrfGlobalTransportClient = NULL;
47
48 /** Switch to be set by signal handler */
49 static volatile sig_atomic_t sig_caught;
50
51 /** Boolean: set to true when we finish shutting down. */
52 static int shutdownComplete = 0;
53
54 /** Name of file to which to write the process ID of the child process */
55 char* pidfile_name = NULL;
56
57 static void add_child( pid_t pid, const char* app, const char* libfile );
58 static void delete_child( ChildNode* node );
59 static void delete_all_children( void );
60 static ChildNode* seek_child( pid_t pid );
61
62 /**
63         @brief Wait on all dead child processes so that they won't be zombies.
64 */
65 static void reap_children( void ) {
66         if( sig_caught ) {
67                 if( SIGTERM == sig_caught || SIGINT == sig_caught ) {
68                         osrfLogInfo( OSRF_LOG_MARK, "Killed by %s; terminating",
69                                         SIGTERM == sig_caught ? "SIGTERM" : "SIGINT" );
70                 } else
71                         osrfLogInfo( OSRF_LOG_MARK, "Killed by signal %d; terminating", (int) sig_caught );
72         }
73
74         // If we caught a signal, then the signal handler already did a kill().
75         // If we didn't, then do the kill() now.
76         if( ! sig_caught )
77                 kill( 0, SIGTERM );
78
79         sleep(1); /* Give the children a chance to die before we reap them. */
80
81         // Wait for each dead child.  The WNOHANG option means to return immediately if
82         // there are no dead children, instead of waiting for them to die.  It is therefore
83         // possible for a child still to be alive when we exit this function, either because
84         // it intercepted the SIGTERM and ignored it, or because it took longer to die than
85         // the time we gave it.
86         pid_t child_pid;
87         while( (child_pid = waitpid(-1, NULL, WNOHANG)) > 0 )
88                 osrfLogInfo(OSRF_LOG_MARK, "Killed child %d", child_pid);
89
90         // Remove all nodes from the list of child processes.
91         delete_all_children();
92 }
93
94 /**
95         @brief Signal handler for SIGTERM and SIGINT.
96
97         Kill all child processes, and set a switch so that we'll know that the signal arrived.
98 */
99 static void handleKillSignal( int signo ) {
100         // First ignore SIGTERM.  Otherwise we would send SIGTERM to ourself, intercept it,
101         // and kill() again in an endless loop.
102         signal( SIGTERM, SIG_IGN );
103
104         //Kill all child processes.  This is safe to do in a signal handler, because POSIX
105         // specifies that kill() is reentrant.  It is necessary because, if we did the kill()
106         // only in reap_children() (above), then there would be a narrow window of vulnerability
107         // in the main loop: if the signal arrives between checking sig_caught and calling wait(),
108         // we would wait indefinitely for a child to die on its own.
109         kill( 0, SIGTERM );
110         sig_caught = signo;
111 }
112
113 /**
114         @brief Return a pointer to the global transport_client.
115         @return Pointer to the global transport_client, or NULL.
116
117         A given process needs only one connection to Jabber, so we keep it a pointer to it at
118         file scope.  This function returns that pointer.
119
120         If the connection has been opened by a previous call to osrfSystemBootstrapClientResc(),
121         return the pointer.  Otherwise return NULL.
122 */
123 transport_client* osrfSystemGetTransportClient( void ) {
124         return osrfGlobalTransportClient;
125 }
126
127 /**
128         @brief Save a copy of a file name to be used for writing a process ID.
129         @param name Designated file name, or NULL.
130
131         Save a file name for later use in saving a process ID.  If @a name is NULL, leave
132         the file name NULL.
133
134         When the parent process spawns a child, the child becomes a daemon.  The parent writes the
135         child's process ID to the PID file, if one has been designated, so that some other process
136         can retrieve the PID later and kill the daemon.
137 */
138 void osrfSystemSetPidFile( const char* name ) {
139         if( pidfile_name )
140                 free( pidfile_name );
141
142         if( name )
143                 pidfile_name = strdup( name );
144         else
145                 pidfile_name = NULL;
146 }
147
148 /**
149         @brief Discard the global transport_client, but without disconnecting from Jabber.
150
151         To be called by a child process in order to disregard the parent's connection without
152         disconnecting it, since disconnecting would disconnect the parent as well.
153 */
154 void osrfSystemIgnoreTransportClient() {
155         client_discard( osrfGlobalTransportClient );
156         osrfGlobalTransportClient = NULL;
157 }
158
159 /**
160         @brief Bootstrap a generic application from info in the configuration file.
161         @param config_file Name of the configuration file.
162         @param contextnode Name of an aggregate within the configuration file, containing the
163         relevant subset of configuration stuff.
164         @return 1 if successful; zero or -1 if error.
165
166         - Load the configuration file.
167         - Open the log.
168         - Open a connection to Jabber.
169
170         A thin wrapper for osrfSystemBootstrapClientResc, passing it NULL for a resource.
171 */
172 int osrf_system_bootstrap_client( char* config_file, char* contextnode ) {
173         return osrfSystemBootstrapClientResc(config_file, contextnode, NULL);
174 }
175
176 /**
177         @brief Connect to one or more cache servers.
178         @return Zero in all cases.
179 */
180 int osrfSystemInitCache( void ) {
181
182         jsonObject* cacheServers = osrf_settings_host_value_object("/cache/global/servers/server");
183         char* maxCache = osrf_settings_host_value("/cache/global/max_cache_time");
184
185         if( cacheServers && maxCache) {
186
187                 if( cacheServers->type == JSON_ARRAY ) {
188                         int i;
189                         const char* servers[cacheServers->size];
190                         for( i = 0; i != cacheServers->size; i++ ) {
191                                 servers[i] = jsonObjectGetString( jsonObjectGetIndex(cacheServers, i) );
192                                 osrfLogInfo( OSRF_LOG_MARK, "Adding cache server %s", servers[i]);
193                         }
194                         osrfCacheInit( servers, cacheServers->size, atoi(maxCache) );
195
196                 } else {
197                         const char* servers[] = { jsonObjectGetString(cacheServers) };
198                         osrfLogInfo( OSRF_LOG_MARK, "Adding cache server %s", servers[0]);
199                         osrfCacheInit( servers, 1, atoi(maxCache) );
200                 }
201
202         } else {
203                 osrfLogError( OSRF_LOG_MARK,  "Missing config value for /cache/global/servers/server _or_ "
204                         "/cache/global/max_cache_time");
205         }
206
207         jsonObjectFree( cacheServers );
208         return 0;
209 }
210
211 /**
212         @brief Launch a collection of servers, as defined by the settings server.
213         @param hostname Full network name of the host where the process is running; or
214         'localhost' will do.
215         @param configfile Name of the configuration file; normally '/openils/conf/opensrf_core.xml'.
216         @param contextNode Name of an aggregate within the configuration file, containing the
217         relevant subset of configuration stuff.
218         @return - Zero if successful, or -1 if not.
219 */
220 int osrfSystemBootstrap( const char* hostname, const char* configfile,
221                 const char* contextNode ) {
222         if( !(hostname && configfile && contextNode) )
223                 return -1;
224
225         // Load the conguration, open the log, open a connection to Jabber
226         if(!osrfSystemBootstrapClientResc(configfile, contextNode, "settings_grabber" )) {
227                 osrfLogError( OSRF_LOG_MARK,
228                         "Unable to bootstrap for host %s from configuration file %s",
229                         hostname, configfile );
230                 return -1;
231         }
232
233         shutdownComplete = 0;
234
235         // Get a list of applications to launch from the settings server
236         int retcode = osrf_settings_retrieve(hostname);
237         osrf_system_disconnect_client();
238
239         if( retcode ) {
240                 osrfLogError( OSRF_LOG_MARK,
241                         "Unable to retrieve settings for host %s from configuration file %s",
242                         hostname, configfile );
243                 return -1;
244         }
245
246         // Turn into a daemon.  The parent forks and exits.  Only the
247         // child returns, with the standard streams (stdin, stdout, and
248         // stderr) redirected to /dev/null.
249         FILE* pidfile = NULL;
250         if( pidfile_name ) {
251                 pidfile = fopen( pidfile_name, "w" );
252                 if( !pidfile ) {
253                         osrfLogError( OSRF_LOG_MARK, "Unable to open PID file \"%s\": %s",
254                                 pidfile_name, strerror( errno ) );
255                         free( pidfile_name );
256                         pidfile_name = NULL;
257                         return -1;
258                 }
259         }
260         daemonize_write_pid( pidfile );
261         if( pidfile ) {
262                 fclose( pidfile );
263                 free( pidfile_name );
264                 pidfile_name = NULL;
265         }
266
267         jsonObject* apps = osrf_settings_host_value_object("/activeapps/appname");
268         osrfStringArray* arr = osrfNewStringArray(8);
269
270         if(apps) {
271                 int i = 0;
272
273                 if(apps->type == JSON_STRING) {
274                         osrfStringArrayAdd(arr, jsonObjectGetString(apps));
275
276                 } else {
277                         const jsonObject* app;
278                         while( (app = jsonObjectGetIndex(apps, i++)) )
279                                 osrfStringArrayAdd(arr, jsonObjectGetString(app));
280                 }
281                 jsonObjectFree(apps);
282
283                 const char* appname = NULL;
284                 i = 0;
285                 while( (appname = osrfStringArrayGetString(arr, i++)) ) {
286
287                         char* lang = osrf_settings_host_value("/apps/%s/language", appname);
288
289                         if(lang && !strcasecmp(lang,"c"))  {
290
291                                 char* libfile = osrf_settings_host_value("/apps/%s/implementation", appname);
292
293                                 if(! (appname && libfile) ) {
294                                         osrfLogWarning( OSRF_LOG_MARK, "Missing appname / libfile in settings config");
295                                         continue;
296                                 }
297
298                                 osrfLogInfo( OSRF_LOG_MARK, "Launching application %s with implementation %s",
299                                                 appname, libfile);
300
301                                 pid_t pid;
302
303                                 if( (pid = fork()) ) {    // if parent
304                                         // store pid in local list for re-launching dead children...
305                                         add_child( pid, appname, libfile );
306                                         osrfLogInfo( OSRF_LOG_MARK, "Running application child %s: process id %ld",
307                                                                  appname, (long) pid );
308
309                                 } else {         // if child, run the application
310
311                                         osrfLogInfo( OSRF_LOG_MARK, " * Running application %s\n", appname);
312                                         if( osrfAppRegisterApplication( appname, libfile ) == 0 )
313                                                 osrf_prefork_run(appname);
314
315                                         osrfLogDebug( OSRF_LOG_MARK, "Server exiting for app %s and library %s\n",
316                                                         appname, libfile );
317                                         exit(0);
318                                 }
319                         } // language == c
320                 } // end while
321         }
322
323         osrfStringArrayFree(arr);
324
325         signal(SIGTERM, handleKillSignal);
326         signal(SIGINT, handleKillSignal);
327
328         // Wait indefinitely for all the child processes to terminate, or for a signal to
329         // tell us to stop.  When there are no more child processes, wait() returns an
330         // ECHILD error and we break out of the loop.
331         int status;
332         pid_t pid;
333         while( ! sig_caught ) {
334                 pid = wait( &status );
335                 if( -1 == pid ) {
336                         if( errno == ECHILD )
337                                 osrfLogError( OSRF_LOG_MARK, "We have no more live services... exiting" );
338                         else if( errno != EINTR )
339                                 osrfLogError(OSRF_LOG_MARK, "Exiting top-level system loop with error: %s",
340                                                 strerror( errno ) );
341
342                         break;
343                 } else {
344                         report_child_status( pid, status );
345                 }
346         }
347
348         reap_children();
349         osrfConfigCleanup();
350         osrf_system_disconnect_client();
351         osrf_settings_free_host_config(NULL);
352         return 0;
353 }
354
355 /**
356         @brief Report the exit status of a dead child process, then remove it from the list.
357         @param pid Process ID of the child.
358         @param status Exit status as captured by wait().
359 */
360 static void report_child_status( pid_t pid, int status )
361 {
362         const char* app;
363         const char* libfile;
364         ChildNode* node = seek_child( pid );
365
366         if( node ) {
367                 app     = node->app     ? node->app     : "[unknown]";
368                 libfile = node->libfile ? node->libfile : "[none]";
369         } else
370                 app = libfile = "";
371
372         if( WIFEXITED( status ) )
373         {
374                 int rc = WEXITSTATUS( status );  // return code of child process
375                 if( rc )
376                         osrfLogError( OSRF_LOG_MARK, "Child process %ld (app %s) exited with return code %d",
377                                         (long) pid, app, rc );
378                 else
379                         osrfLogInfo( OSRF_LOG_MARK, "Child process %ld (app %s) exited normally",
380                                         (long) pid, app );
381         }
382         else if( WIFSIGNALED( status ) )
383         {
384                 osrfLogError( OSRF_LOG_MARK, "Child process %ld (app %s) killed by signal %d",
385                                           (long) pid, app, WTERMSIG( status) );
386         }
387         else if( WIFSTOPPED( status ) )
388         {
389                 osrfLogError( OSRF_LOG_MARK, "Child process %ld (app %s) stopped by signal %d",
390                                           (long) pid, app, (int) WSTOPSIG( status ) );
391         }
392
393         delete_child( node );
394 }
395
396 /*----------- Routines to manage list of children --*/
397
398 /**
399         @brief Add a node to the list of child processes.
400         @param pid Process ID of the child process.
401         @param app Name of the child application.
402         @param libfile Name of the shared library where the child process resides.
403 */
404 static void add_child( pid_t pid, const char* app, const char* libfile )
405 {
406         /* Construct new child node */
407
408         ChildNode* node = safe_malloc( sizeof( ChildNode ) );
409
410         node->pid = pid;
411
412         if( app )
413                 node->app = strdup( app );
414         else
415                 node->app = NULL;
416
417         if( libfile )
418                 node->libfile = strdup( libfile );
419         else
420                 node->libfile = NULL;
421
422         /* Add new child node to the head of the list */
423
424         node->pNext = child_list;
425         node->pPrev = NULL;
426
427         if( child_list )
428                 child_list->pPrev = node;
429
430         child_list = node;
431 }
432
433 /**
434         @brief Remove a node from the list of child processes.
435         @param node Pointer to the node to be removed.
436 */
437 static void delete_child( ChildNode* node ) {
438
439         /* Sanity check */
440
441         if( ! node )
442                 return;
443
444         /* Detach the node from the list */
445
446         if( node->pPrev )
447                 node->pPrev->pNext = node->pNext;
448         else
449                 child_list = node->pNext;
450
451         if( node->pNext )
452                 node->pNext->pPrev = node->pPrev;
453
454         /* Deallocate the node and its payload */
455
456         free( node->app );
457         free( node->libfile );
458         free( node );
459 }
460
461 /**
462         @brief Remove all nodes from the list of child processes, rendering it empty.
463 */
464 static void delete_all_children( void ) {
465
466         while( child_list )
467                 delete_child( child_list );
468 }
469
470 /**
471         @brief Find the node for a child process of a given process ID.
472         @param pid The process ID of the child process.
473         @return A pointer to the corresponding node if found; otherwise NULL.
474 */
475 static ChildNode* seek_child( pid_t pid ) {
476
477         /* Return a pointer to the child node for the */
478         /* specified process ID, or NULL if not found */
479
480         ChildNode* node = child_list;
481         while( node ) {
482                 if( node->pid == pid )
483                         break;
484                 else
485                         node = node->pNext;
486         }
487
488         return node;
489 }
490
491 /*----------- End of routines to manage list of children --*/
492
493 /**
494         @brief Bootstrap a generic application from info in the configuration file.
495         @param config_file Name of the configuration file.
496         @param contextnode Name of an aggregate within the configuration file, containing the
497         relevant subset of configuration stuff.
498         @param resource Used to construct a Jabber resource name; may be NULL.
499         @return 1 if successful; zero or -1 if error.
500
501         - Load the configuration file.
502         - Open the log.
503         - Open a connection to Jabber.
504 */
505 int osrfSystemBootstrapClientResc( const char* config_file,
506                 const char* contextnode, const char* resource ) {
507
508         int failure = 0;
509
510         if(osrfSystemGetTransportClient()) {
511                 osrfLogInfo(OSRF_LOG_MARK, "Client is already bootstrapped");
512                 return 1; /* we already have a client connection */
513         }
514
515         if( !( config_file && contextnode ) && ! osrfConfigHasDefaultConfig() ) {
516                 osrfLogError( OSRF_LOG_MARK, "No Config File Specified\n" );
517                 return -1;
518         }
519
520         if( config_file ) {
521                 osrfConfig* cfg = osrfConfigInit( config_file, contextnode );
522                 if(cfg)
523                         osrfConfigSetDefaultConfig(cfg);
524                 else
525                         return 0;   /* Can't load configuration?  Bail out */
526         }
527
528         char* log_file      = osrfConfigGetValue( NULL, "/logfile");
529         if(!log_file) {
530                 fprintf(stderr, "No log file specified in configuration file %s\n",
531                                 config_file);
532                 return -1;
533         }
534
535         char* log_level      = osrfConfigGetValue( NULL, "/loglevel" );
536         osrfStringArray* arr = osrfNewStringArray(8);
537         osrfConfigGetValueList(NULL, arr, "/domain");
538
539         char* username       = osrfConfigGetValue( NULL, "/username" );
540         char* password       = osrfConfigGetValue( NULL, "/passwd" );
541         char* port           = osrfConfigGetValue( NULL, "/port" );
542         char* unixpath       = osrfConfigGetValue( NULL, "/unixpath" );
543         char* facility       = osrfConfigGetValue( NULL, "/syslog" );
544         char* actlog         = osrfConfigGetValue( NULL, "/actlog" );
545
546         /* if we're a source-client, tell the logger */
547         char* isclient = osrfConfigGetValue(NULL, "/client");
548         if( isclient && !strcasecmp(isclient,"true") )
549                 osrfLogSetIsClient(1);
550         free(isclient);
551
552         int llevel = 0;
553         int iport = 0;
554         if(port) iport = atoi(port);
555         if(log_level) llevel = atoi(log_level);
556
557         if(!strcmp(log_file, "syslog")) {
558                 osrfLogInit( OSRF_LOG_TYPE_SYSLOG, contextnode, llevel );
559                 osrfLogSetSyslogFacility(osrfLogFacilityToInt(facility));
560                 if(actlog) osrfLogSetSyslogActFacility(osrfLogFacilityToInt(actlog));
561
562         } else {
563                 osrfLogInit( OSRF_LOG_TYPE_FILE, contextnode, llevel );
564                 osrfLogSetFile( log_file );
565         }
566
567
568         /* Get a domain, if one is specified */
569         const char* domain = osrfStringArrayGetString( arr, 0 ); /* just the first for now */
570         if(!domain) {
571                 fprintf(stderr, "No domain specified in configuration file %s\n", config_file);
572                 osrfLogError( OSRF_LOG_MARK, "No domain specified in configuration file %s\n",
573                                 config_file );
574                 failure = 1;
575         }
576
577         if(!username) {
578                 fprintf(stderr, "No username specified in configuration file %s\n", config_file);
579                 osrfLogError( OSRF_LOG_MARK, "No username specified in configuration file %s\n",
580                                 config_file );
581                 failure = 1;
582         }
583
584         if(!password) {
585                 fprintf(stderr, "No password specified in configuration file %s\n", config_file);
586                 osrfLogError( OSRF_LOG_MARK, "No password specified in configuration file %s\n",
587                                 config_file);
588                 failure = 1;
589         }
590
591         if((iport <= 0) && !unixpath) {
592                 fprintf(stderr, "No unixpath or valid port in configuration file %s\n", config_file);
593                 osrfLogError( OSRF_LOG_MARK, "No unixpath or valid port in configuration file %s\n",
594                         config_file);
595                 failure = 1;
596         }
597
598         if (failure) {
599                 osrfStringArrayFree(arr);
600                 free(log_file);
601                 free(log_level);
602                 free(username);
603                 free(password);
604                 free(port);
605                 free(unixpath);
606                 free(facility);
607                 free(actlog);
608                 return 0;
609         }
610
611         osrfLogInfo( OSRF_LOG_MARK, "Bootstrapping system with domain %s, port %d, and unixpath %s",
612                 domain, iport, unixpath ? unixpath : "(none)" );
613         transport_client* client = client_init( domain, iport, unixpath, 0 );
614
615         char host[HOST_NAME_MAX + 1] = "";
616         gethostname(host, sizeof(host) );
617         host[HOST_NAME_MAX] = '\0';
618
619         char tbuf[32];
620         tbuf[0] = '\0';
621         snprintf(tbuf, 32, "%f", get_timestamp_millis());
622
623         if(!resource) resource = "";
624
625         int len = strlen(resource) + 256;
626         char buf[len];
627         buf[0] = '\0';
628         snprintf(buf, len - 1, "%s_%s_%s_%ld", resource, host, tbuf, (long) getpid() );
629
630         if(client_connect( client, username, password, buf, 10, AUTH_DIGEST )) {
631                 osrfGlobalTransportClient = client;
632         }
633
634         osrfStringArrayFree(arr);
635         free(actlog);
636         free(facility);
637         free(log_level);
638         free(log_file);
639         free(username);
640         free(password);
641         free(port);
642         free(unixpath);
643
644         if(osrfGlobalTransportClient)
645                 return 1;
646
647         return 0;
648 }
649
650 /**
651         @brief Disconnect from Jabber.
652         @return Zero in all cases.
653 */
654 int osrf_system_disconnect_client( void ) {
655         client_disconnect( osrfGlobalTransportClient );
656         client_free( osrfGlobalTransportClient );
657         osrfGlobalTransportClient = NULL;
658         return 0;
659 }
660
661 /**
662         @brief Shut down a laundry list of facilities typically used by servers.
663
664         Things to shut down:
665         - Settings from configuration file
666         - Cache
667         - Connection to Jabber
668         - Settings from settings server
669         - Application sessions
670         - Logs
671 */
672 int osrf_system_shutdown( void ) {
673         if(shutdownComplete)
674                 return 0;
675         else {
676                 osrfConfigCleanup();
677                 osrfCacheCleanup();
678                 osrf_system_disconnect_client();
679                 osrf_settings_free_host_config(NULL);
680                 osrfAppSessionCleanup();
681                 osrfLogCleanup();
682                 shutdownComplete = 1;
683                 return 1;
684         }
685 }