]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/libopensrf/osrf_system.c
05244a981976969bf0c3cf640165be43a9ef8f5f
[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         daemonize();
250
251         jsonObject* apps = osrf_settings_host_value_object("/activeapps/appname");
252         osrfStringArray* arr = osrfNewStringArray(8);
253
254         if(apps) {
255                 int i = 0;
256
257                 if(apps->type == JSON_STRING) {
258                         osrfStringArrayAdd(arr, jsonObjectGetString(apps));
259
260                 } else {
261                         const jsonObject* app;
262                         while( (app = jsonObjectGetIndex(apps, i++)) )
263                                 osrfStringArrayAdd(arr, jsonObjectGetString(app));
264                 }
265                 jsonObjectFree(apps);
266
267                 const char* appname = NULL;
268                 int first_launch = 1;             // Boolean
269                 i = 0;
270                 while( (appname = osrfStringArrayGetString(arr, i++)) ) {
271
272                         char* lang = osrf_settings_host_value("/apps/%s/language", appname);
273
274                         if(lang && !strcasecmp(lang,"c"))  {
275
276                                 char* libfile = osrf_settings_host_value("/apps/%s/implementation", appname);
277
278                                 if(! (appname && libfile) ) {
279                                         osrfLogWarning( OSRF_LOG_MARK, "Missing appname / libfile in settings config");
280                                         continue;
281                                 }
282
283                                 osrfLogInfo( OSRF_LOG_MARK, "Launching application %s with implementation %s",
284                                                 appname, libfile);
285
286                                 pid_t pid;
287
288                                 if( (pid = fork()) ) {    // if parent
289                                         // store pid in local list for re-launching dead children...
290                                         add_child( pid, appname, libfile );
291                                         osrfLogInfo( OSRF_LOG_MARK, "Running application child %s: process id %ld",
292                                                                  appname, (long) pid );
293
294                                         if( first_launch ) {
295                                                 if( pidfile_name ) {
296                                                         // Write our own PID to a PID file so that somebody can use it to
297                                                         // send us a signal later.  If we don't find any C apps to launch,
298                                                         // then we will quietly exit without writing a PID file, and without
299                                                         // waiting to be killed by a signal.
300
301                                                         FILE* pidfile = fopen( pidfile_name, "w" );
302                                                         if( !pidfile ) {
303                                                                 osrfLogError( OSRF_LOG_MARK, "Unable to open PID file \"%s\": %s",
304                                                                         pidfile_name, strerror( errno ) );
305                                                                 free( pidfile_name );
306                                                                 pidfile_name = NULL;
307                                                                 return -1;
308                                                         } else {
309                                                                 fprintf( pidfile, "%ld\n", (long) getpid() );
310                                                                 fclose( pidfile );
311                                                         }
312                                                 }
313                                                 first_launch = 0;
314                                         }
315
316                                 } else {         // if child, run the application
317
318                                         osrfLogInfo( OSRF_LOG_MARK, " * Running application %s\n", appname);
319                                         if( pidfile_name ) {
320                                                 free( pidfile_name );    // tidy up some debris from the parent
321                                                 pidfile_name = NULL;
322                                         }
323                                         if( osrfAppRegisterApplication( appname, libfile ) == 0 )
324                                                 osrf_prefork_run(appname);
325
326                                         osrfLogDebug( OSRF_LOG_MARK, "Server exiting for app %s and library %s\n",
327                                                         appname, libfile );
328                                         exit(0);
329                                 }
330                         } // language == c
331                 } // end while
332         }
333
334         osrfStringArrayFree(arr);
335
336         signal(SIGTERM, handleKillSignal);
337         signal(SIGINT, handleKillSignal);
338
339         // Wait indefinitely for all the child processes to terminate, or for a signal to
340         // tell us to stop.  When there are no more child processes, wait() returns an
341         // ECHILD error and we break out of the loop.
342         int status;
343         pid_t pid;
344         while( ! sig_caught ) {
345                 pid = wait( &status );
346                 if( -1 == pid ) {
347                         if( errno == ECHILD )
348                                 osrfLogError( OSRF_LOG_MARK, "We have no more live services... exiting" );
349                         else if( errno != EINTR )
350                                 osrfLogError(OSRF_LOG_MARK, "Exiting top-level system loop with error: %s",
351                                                 strerror( errno ) );
352
353                         // Since we're not being killed by a signal as usual, delete the PID file
354                         // so that no one will try to kill us when we're already dead.
355                         if( pidfile_name )
356                                 remove( pidfile_name );
357                         break;
358                 } else {
359                         report_child_status( pid, status );
360                 }
361         }
362
363         reap_children();
364         osrfConfigCleanup();
365         osrf_system_disconnect_client();
366         osrf_settings_free_host_config(NULL);
367         free( pidfile_name );
368         pidfile_name = NULL;
369         return 0;
370 }
371
372 /**
373         @brief Report the exit status of a dead child process, then remove it from the list.
374         @param pid Process ID of the child.
375         @param status Exit status as captured by wait().
376 */
377 static void report_child_status( pid_t pid, int status )
378 {
379         const char* app;
380         const char* libfile;
381         ChildNode* node = seek_child( pid );
382
383         if( node ) {
384                 app     = node->app     ? node->app     : "[unknown]";
385                 libfile = node->libfile ? node->libfile : "[none]";
386         } else
387                 app = libfile = "";
388
389         if( WIFEXITED( status ) )
390         {
391                 int rc = WEXITSTATUS( status );  // return code of child process
392                 if( rc )
393                         osrfLogError( OSRF_LOG_MARK, "Child process %ld (app %s) exited with return code %d",
394                                         (long) pid, app, rc );
395                 else
396                         osrfLogInfo( OSRF_LOG_MARK, "Child process %ld (app %s) exited normally",
397                                         (long) pid, app );
398         }
399         else if( WIFSIGNALED( status ) )
400         {
401                 osrfLogError( OSRF_LOG_MARK, "Child process %ld (app %s) killed by signal %d",
402                                           (long) pid, app, WTERMSIG( status) );
403         }
404         else if( WIFSTOPPED( status ) )
405         {
406                 osrfLogError( OSRF_LOG_MARK, "Child process %ld (app %s) stopped by signal %d",
407                                           (long) pid, app, (int) WSTOPSIG( status ) );
408         }
409
410         delete_child( node );
411 }
412
413 /*----------- Routines to manage list of children --*/
414
415 /**
416         @brief Add a node to the list of child processes.
417         @param pid Process ID of the child process.
418         @param app Name of the child application.
419         @param libfile Name of the shared library where the child process resides.
420 */
421 static void add_child( pid_t pid, const char* app, const char* libfile )
422 {
423         /* Construct new child node */
424
425         ChildNode* node = safe_malloc( sizeof( ChildNode ) );
426
427         node->pid = pid;
428
429         if( app )
430                 node->app = strdup( app );
431         else
432                 node->app = NULL;
433
434         if( libfile )
435                 node->libfile = strdup( libfile );
436         else
437                 node->libfile = NULL;
438
439         /* Add new child node to the head of the list */
440
441         node->pNext = child_list;
442         node->pPrev = NULL;
443
444         if( child_list )
445                 child_list->pPrev = node;
446
447         child_list = node;
448 }
449
450 /**
451         @brief Remove a node from the list of child processes.
452         @param node Pointer to the node to be removed.
453 */
454 static void delete_child( ChildNode* node ) {
455
456         /* Sanity check */
457
458         if( ! node )
459                 return;
460
461         /* Detach the node from the list */
462
463         if( node->pPrev )
464                 node->pPrev->pNext = node->pNext;
465         else
466                 child_list = node->pNext;
467
468         if( node->pNext )
469                 node->pNext->pPrev = node->pPrev;
470
471         /* Deallocate the node and its payload */
472
473         free( node->app );
474         free( node->libfile );
475         free( node );
476 }
477
478 /**
479         @brief Remove all nodes from the list of child processes, rendering it empty.
480 */
481 static void delete_all_children( void ) {
482
483         while( child_list )
484                 delete_child( child_list );
485 }
486
487 /**
488         @brief Find the node for a child process of a given process ID.
489         @param pid The process ID of the child process.
490         @return A pointer to the corresponding node if found; otherwise NULL.
491 */
492 static ChildNode* seek_child( pid_t pid ) {
493
494         /* Return a pointer to the child node for the */
495         /* specified process ID, or NULL if not found */
496
497         ChildNode* node = child_list;
498         while( node ) {
499                 if( node->pid == pid )
500                         break;
501                 else
502                         node = node->pNext;
503         }
504
505         return node;
506 }
507
508 /*----------- End of routines to manage list of children --*/
509
510 /**
511         @brief Bootstrap a generic application from info in the configuration file.
512         @param config_file Name of the configuration file.
513         @param contextnode Name of an aggregate within the configuration file, containing the
514         relevant subset of configuration stuff.
515         @param resource Used to construct a Jabber resource name; may be NULL.
516         @return 1 if successful; zero or -1 if error.
517
518         - Load the configuration file.
519         - Open the log.
520         - Open a connection to Jabber.
521 */
522 int osrfSystemBootstrapClientResc( const char* config_file,
523                 const char* contextnode, const char* resource ) {
524
525         int failure = 0;
526
527         if(osrfSystemGetTransportClient()) {
528                 osrfLogInfo(OSRF_LOG_MARK, "Client is already bootstrapped");
529                 return 1; /* we already have a client connection */
530         }
531
532         if( !( config_file && contextnode ) && ! osrfConfigHasDefaultConfig() ) {
533                 osrfLogError( OSRF_LOG_MARK, "No Config File Specified\n" );
534                 return -1;
535         }
536
537         if( config_file ) {
538                 osrfConfig* cfg = osrfConfigInit( config_file, contextnode );
539                 if(cfg)
540                         osrfConfigSetDefaultConfig(cfg);
541                 else
542                         return 0;   /* Can't load configuration?  Bail out */
543         }
544
545         char* log_file      = osrfConfigGetValue( NULL, "/logfile");
546         if(!log_file) {
547                 fprintf(stderr, "No log file specified in configuration file %s\n",
548                                 config_file);
549                 return -1;
550         }
551
552         char* log_level      = osrfConfigGetValue( NULL, "/loglevel" );
553         osrfStringArray* arr = osrfNewStringArray(8);
554         osrfConfigGetValueList(NULL, arr, "/domain");
555
556         char* username       = osrfConfigGetValue( NULL, "/username" );
557         char* password       = osrfConfigGetValue( NULL, "/passwd" );
558         char* port           = osrfConfigGetValue( NULL, "/port" );
559         char* unixpath       = osrfConfigGetValue( NULL, "/unixpath" );
560         char* facility       = osrfConfigGetValue( NULL, "/syslog" );
561         char* actlog         = osrfConfigGetValue( NULL, "/actlog" );
562
563         /* if we're a source-client, tell the logger */
564         char* isclient = osrfConfigGetValue(NULL, "/client");
565         if( isclient && !strcasecmp(isclient,"true") )
566                 osrfLogSetIsClient(1);
567         free(isclient);
568
569         int llevel = 0;
570         int iport = 0;
571         if(port) iport = atoi(port);
572         if(log_level) llevel = atoi(log_level);
573
574         if(!strcmp(log_file, "syslog")) {
575                 osrfLogInit( OSRF_LOG_TYPE_SYSLOG, contextnode, llevel );
576                 osrfLogSetSyslogFacility(osrfLogFacilityToInt(facility));
577                 if(actlog) osrfLogSetSyslogActFacility(osrfLogFacilityToInt(actlog));
578
579         } else {
580                 osrfLogInit( OSRF_LOG_TYPE_FILE, contextnode, llevel );
581                 osrfLogSetFile( log_file );
582         }
583
584
585         /* Get a domain, if one is specified */
586         const char* domain = osrfStringArrayGetString( arr, 0 ); /* just the first for now */
587         if(!domain) {
588                 fprintf(stderr, "No domain specified in configuration file %s\n", config_file);
589                 osrfLogError( OSRF_LOG_MARK, "No domain specified in configuration file %s\n",
590                                 config_file );
591                 failure = 1;
592         }
593
594         if(!username) {
595                 fprintf(stderr, "No username specified in configuration file %s\n", config_file);
596                 osrfLogError( OSRF_LOG_MARK, "No username specified in configuration file %s\n",
597                                 config_file );
598                 failure = 1;
599         }
600
601         if(!password) {
602                 fprintf(stderr, "No password specified in configuration file %s\n", config_file);
603                 osrfLogError( OSRF_LOG_MARK, "No password specified in configuration file %s\n",
604                                 config_file);
605                 failure = 1;
606         }
607
608         if((iport <= 0) && !unixpath) {
609                 fprintf(stderr, "No unixpath or valid port in configuration file %s\n", config_file);
610                 osrfLogError( OSRF_LOG_MARK, "No unixpath or valid port in configuration file %s\n",
611                         config_file);
612                 failure = 1;
613         }
614
615         if (failure) {
616                 osrfStringArrayFree(arr);
617                 free(log_file);
618                 free(log_level);
619                 free(username);
620                 free(password);
621                 free(port);
622                 free(unixpath);
623                 free(facility);
624                 free(actlog);
625                 return 0;
626         }
627
628         osrfLogInfo( OSRF_LOG_MARK, "Bootstrapping system with domain %s, port %d, and unixpath %s",
629                 domain, iport, unixpath ? unixpath : "(none)" );
630         transport_client* client = client_init( domain, iport, unixpath, 0 );
631
632         char host[HOST_NAME_MAX + 1] = "";
633         gethostname(host, sizeof(host) );
634         host[HOST_NAME_MAX] = '\0';
635
636         char tbuf[32];
637         tbuf[0] = '\0';
638         snprintf(tbuf, 32, "%f", get_timestamp_millis());
639
640         if(!resource) resource = "";
641
642         int len = strlen(resource) + 256;
643         char buf[len];
644         buf[0] = '\0';
645         snprintf(buf, len - 1, "%s_%s_%s_%ld", resource, host, tbuf, (long) getpid() );
646
647         if(client_connect( client, username, password, buf, 10, AUTH_DIGEST )) {
648                 osrfGlobalTransportClient = client;
649         }
650
651         osrfStringArrayFree(arr);
652         free(actlog);
653         free(facility);
654         free(log_level);
655         free(log_file);
656         free(username);
657         free(password);
658         free(port);
659         free(unixpath);
660
661         if(osrfGlobalTransportClient)
662                 return 1;
663
664         return 0;
665 }
666
667 /**
668         @brief Disconnect from Jabber.
669         @return Zero in all cases.
670 */
671 int osrf_system_disconnect_client( void ) {
672         client_disconnect( osrfGlobalTransportClient );
673         client_free( osrfGlobalTransportClient );
674         osrfGlobalTransportClient = NULL;
675         return 0;
676 }
677
678 /**
679         @brief Shut down a laundry list of facilities typically used by servers.
680
681         Things to shut down:
682         - Settings from configuration file
683         - Cache
684         - Connection to Jabber
685         - Settings from settings server
686         - Application sessions
687         - Logs
688 */
689 int osrf_system_shutdown( void ) {
690         if(shutdownComplete)
691                 return 0;
692         else {
693                 osrfConfigCleanup();
694                 osrfCacheCleanup();
695                 osrf_system_disconnect_client();
696                 osrf_settings_free_host_config(NULL);
697                 osrfAppSessionCleanup();
698                 osrfLogCleanup();
699                 shutdownComplete = 1;
700                 return 1;
701         }
702 }