Tinkering with comments and white space; nothing substantive.
[OpenSRF.git] / src / libopensrf / osrf_system.c
1 #include <sys/types.h>
2 #include <sys/time.h>
3 #include <unistd.h>
4 #include <stdlib.h>
5 #include <stdio.h>
6 #include <string.h>
7 #include <sys/select.h>
8 #include <sys/wait.h>
9 #include <signal.h>
10
11 #include "opensrf/utils.h"
12 #include "opensrf/log.h"
13 #include "opensrf/osrf_system.h"
14 #include "opensrf/osrf_application.h"
15 #include "opensrf/osrf_prefork.h"
16
17 #ifndef HOST_NAME_MAX
18 #define HOST_NAME_MAX 256
19 #endif
20
21 static void report_child_status( pid_t pid, int status );
22 struct child_node;
23 typedef struct child_node ChildNode;
24
25 static void handleKillSignal(int signo) {
26         /* we are the top-level process and we've been
27                 killed. Kill all of our children */
28         kill(0, SIGTERM);
29         sleep(1); /* give the children a chance to die before we reap them */
30         pid_t child_pid;
31         int status;
32         while( (child_pid=waitpid(-1,&status,WNOHANG)) > 0)
33                 osrfLogInfo(OSRF_LOG_MARK, "Killed child %d", child_pid);
34         _exit(0);
35 }
36
37 struct child_node
38 {
39         ChildNode* pNext;
40         ChildNode* pPrev;
41         pid_t pid;
42         char* app;
43         char* libfile;
44 };
45
46 static ChildNode* child_list;
47
48 /** Pointer to the global transport_client; i.e. our connection to Jabber. */
49 static transport_client* osrfGlobalTransportClient = NULL;
50
51 static void add_child( pid_t pid, const char* app, const char* libfile );
52 static void delete_child( ChildNode* node );
53 static void delete_all_children( void );
54 static ChildNode* seek_child( pid_t pid );
55
56 transport_client* osrfSystemGetTransportClient( void ) {
57         return osrfGlobalTransportClient;
58 }
59
60 void osrfSystemIgnoreTransportClient() {
61         osrfGlobalTransportClient = NULL;
62 }
63
64 int osrf_system_bootstrap_client( char* config_file, char* contextnode ) {
65         return osrfSystemBootstrapClientResc(config_file, contextnode, NULL);
66 }
67
68 int osrfSystemInitCache( void ) {
69
70         jsonObject* cacheServers = osrf_settings_host_value_object("/cache/global/servers/server");
71         char* maxCache = osrf_settings_host_value("/cache/global/max_cache_time");
72
73         if( cacheServers && maxCache) {
74
75                 if( cacheServers->type == JSON_ARRAY ) {
76                         int i;
77                         const char* servers[cacheServers->size];
78                         for( i = 0; i != cacheServers->size; i++ ) {
79                                 servers[i] = jsonObjectGetString( jsonObjectGetIndex(cacheServers, i) );
80                                 osrfLogInfo( OSRF_LOG_MARK, "Adding cache server %s", servers[i]);
81                         }
82                         osrfCacheInit( servers, cacheServers->size, atoi(maxCache) );
83
84                 } else {
85                         const char* servers[] = { jsonObjectGetString(cacheServers) };
86                         osrfLogInfo( OSRF_LOG_MARK, "Adding cache server %s", servers[0]);
87                         osrfCacheInit( servers, 1, atoi(maxCache) );
88                 }
89
90         } else {
91                 osrfLogError( OSRF_LOG_MARK,  "Missing config value for /cache/global/servers/server _or_ "
92                         "/cache/global/max_cache_time");
93         }
94
95         jsonObjectFree( cacheServers );
96         return 0;
97 }
98
99
100 /**
101         @brief Launch a collection of servers, as defined by the settings server.
102         @param hostname Full network name of the host where the process is running; or
103         'localhost' will do.
104         @param configfile Name of the configuration file; normally '/openils/conf/opensrf_core.xml'.
105         @param contextNode Name of an aggregate within the configuration file, containing the
106         relevant subset of configuration stuff.
107         @return - Zero if successful, or -1 if not.
108 */
109 int osrfSystemBootstrap( const char* hostname, const char* configfile,
110                 const char* contextNode ) {
111         if( !(hostname && configfile && contextNode) )
112                 return -1;
113
114         // Load the conguration, open the log, open a connection to Jabber
115         if(!osrfSystemBootstrapClientResc(configfile, contextNode, "settings_grabber" )) {
116                 osrfLogError( OSRF_LOG_MARK,
117                         "Unable to bootstrap for host %s from configuration file %s",
118                         hostname, configfile );
119                 return -1;
120         }
121
122         // Get a list of applications to launch from the settings server
123         int retcode = osrf_settings_retrieve(hostname);
124         osrf_system_disconnect_client();
125
126         if( retcode ) {
127                 osrfLogError( OSRF_LOG_MARK,
128                         "Unable to retrieve settings for host %s from configuration file %s",
129                         hostname, configfile );
130                 return -1;
131         }
132
133         // Turn into a daemon.  The parent forks and exits.  Only the
134         // child returns, with the standard streams (stdin, stdout, and
135         // stderr) redirected to /dev/null.
136         daemonize();
137
138         jsonObject* apps = osrf_settings_host_value_object("/activeapps/appname");
139         osrfStringArray* arr = osrfNewStringArray(8);
140
141         if(apps) {
142                 int i = 0;
143
144                 if(apps->type == JSON_STRING) {
145                         osrfStringArrayAdd(arr, jsonObjectGetString(apps));
146
147                 } else {
148                         const jsonObject* app;
149                         while( (app = jsonObjectGetIndex(apps, i++)) )
150                                 osrfStringArrayAdd(arr, jsonObjectGetString(app));
151                 }
152                 jsonObjectFree(apps);
153
154                 const char* appname = NULL;
155                 i = 0;
156                 while( (appname = osrfStringArrayGetString(arr, i++)) ) {
157
158                         char* lang = osrf_settings_host_value("/apps/%s/language", appname);
159
160                         if(lang && !strcasecmp(lang,"c"))  {
161
162                                 char* libfile = osrf_settings_host_value("/apps/%s/implementation", appname);
163
164                                 if(! (appname && libfile) ) {
165                                         osrfLogWarning( OSRF_LOG_MARK, "Missing appname / libfile in settings config");
166                                         continue;
167                                 }
168
169                                 osrfLogInfo( OSRF_LOG_MARK, "Launching application %s with implementation %s",
170                                                 appname, libfile);
171
172                                 pid_t pid;
173
174                                 if( (pid = fork()) ) {    // if parent
175                                         // store pid in local list for re-launching dead children...
176                                         add_child( pid, appname, libfile );
177                                         osrfLogInfo( OSRF_LOG_MARK, "Running application child %s: process id %ld",
178                                                                  appname, (long) pid );
179
180                                 } else {         // if child, run the application
181
182                                         osrfLogInfo( OSRF_LOG_MARK, " * Running application %s\n", appname);
183                                         if( osrfAppRegisterApplication( appname, libfile ) == 0 )
184                                                 osrf_prefork_run(appname);
185
186                                         osrfLogDebug( OSRF_LOG_MARK, "Server exiting for app %s and library %s\n",
187                                                         appname, libfile );
188                                         exit(0);
189                                 }
190                         } // language == c
191                 }
192         } // should we do something if there are no apps? does the wait(NULL) below do that for us?
193
194         osrfStringArrayFree(arr);
195
196         signal(SIGTERM, handleKillSignal);
197         signal(SIGINT, handleKillSignal);
198
199         while(1) {
200                 errno = 0;
201                 int status;
202                 pid_t pid = wait( &status );
203                 if(-1 == pid) {
204                         if(errno == ECHILD)
205                                 osrfLogError(OSRF_LOG_MARK, "We have no more live services... exiting");
206                         else
207                                 osrfLogError(OSRF_LOG_MARK, "Exiting top-level system loop with error: %s",
208                                                 strerror(errno));
209                         break;
210                 } else {
211                         report_child_status( pid, status );
212                 }
213         }
214
215         delete_all_children();
216         return 0;
217 }
218
219
220 static void report_child_status( pid_t pid, int status )
221 {
222         const char* app;
223         const char* libfile;
224         ChildNode* node = seek_child( pid );
225
226         if( node ) {
227                 app     = node->app     ? node->app     : "[unknown]";
228                 libfile = node->libfile ? node->libfile : "[none]";
229         } else
230                 app = libfile = NULL;
231
232         if( WIFEXITED( status ) )
233         {
234                 int rc = WEXITSTATUS( status );  // return code of child process
235                 if( rc )
236                         osrfLogError( OSRF_LOG_MARK, "Child process %ld (app %s) exited with return code %d",
237                                         (long) pid, app, rc );
238                 else
239                         osrfLogInfo( OSRF_LOG_MARK, "Child process %ld (app %s) exited normally",
240                                         (long) pid, app );
241         }
242         else if( WIFSIGNALED( status ) )
243         {
244                 osrfLogError( OSRF_LOG_MARK, "Child process %ld (app %s) killed by signal %d",
245                                           (long) pid, app, WTERMSIG( status) );
246         }
247         else if( WIFSTOPPED( status ) )
248         {
249                 osrfLogError( OSRF_LOG_MARK, "Child process %ld (app %s) stopped by signal %d",
250                                           (long) pid, app, (int) WSTOPSIG( status ) );
251         }
252
253         delete_child( node );
254 }
255
256 /*----------- Routines to manage list of children --*/
257
258 static void add_child( pid_t pid, const char* app, const char* libfile )
259 {
260         /* Construct new child node */
261
262         ChildNode* node = safe_malloc( sizeof( ChildNode ) );
263
264         node->pid = pid;
265
266         if( app )
267                 node->app = strdup( app );
268         else
269                 node->app = NULL;
270
271         if( libfile )
272                 node->libfile = strdup( libfile );
273         else
274                 node->libfile = NULL;
275
276         /* Add new child node to the head of the list */
277
278         node->pNext = child_list;
279         node->pPrev = NULL;
280
281         if( child_list )
282                 child_list->pPrev = node;
283
284         child_list = node;
285 }
286
287 static void delete_child( ChildNode* node ) {
288
289         /* Sanity check */
290
291         if( ! node )
292                 return;
293
294         /* Detach the node from the list */
295
296         if( node->pPrev )
297                 node->pPrev->pNext = node->pNext;
298         else
299                 child_list = node->pNext;
300
301         if( node->pNext )
302                 node->pNext->pPrev = node->pPrev;
303
304         /* Deallocate the node and its payload */
305
306         free( node->app );
307         free( node->libfile );
308         free( node );
309 }
310
311 static void delete_all_children( void ) {
312
313         while( child_list )
314                 delete_child( child_list );
315 }
316
317 static ChildNode* seek_child( pid_t pid ) {
318
319         /* Return a pointer to the child node for the */
320         /* specified process ID, or NULL if not found */
321
322         ChildNode* node = child_list;
323         while( node ) {
324                 if( node->pid == pid )
325                         break;
326                 else
327                         node = node->pNext;
328         }
329
330         return node;
331 }
332
333 /*----------- End of routines to manage list of children --*/
334
335 /**
336         @brief Bootstrap a generic application from info in the configuration file.
337         @param config_file Name of the configuration file.
338         @param context_node Name of an aggregate within the configuration file, containing the
339         relevant subset of configuration stuff.
340         @param resource Used to construct a Jabber resource name; may be NULL.
341         @return 1 if successful; zero or -1 if error.
342
343         - Load the configuration file.
344         - Open the log.
345         - Open a connection to Jabber.
346 */
347 int osrfSystemBootstrapClientResc( const char* config_file,
348                 const char* contextnode, const char* resource ) {
349
350         int failure = 0;
351
352         if(osrfSystemGetTransportClient()) {
353                 osrfLogInfo(OSRF_LOG_MARK, "Client is already bootstrapped");
354                 return 1; /* we already have a client connection */
355         }
356
357         if( !( config_file && contextnode ) && ! osrfConfigHasDefaultConfig() ) {
358                 osrfLogError( OSRF_LOG_MARK, "No Config File Specified\n" );
359                 return -1;
360         }
361
362         if( config_file ) {
363                 osrfConfig* cfg = osrfConfigInit( config_file, contextnode );
364                 if(cfg)
365                         osrfConfigSetDefaultConfig(cfg);
366                 else
367                         return 0;   /* Can't load configuration?  Bail out */
368         }
369
370         char* log_file      = osrfConfigGetValue( NULL, "/logfile");
371         if(!log_file) {
372                 fprintf(stderr, "No log file specified in configuration file %s\n",
373                                 config_file);
374                 return -1;
375         }
376
377         char* log_level      = osrfConfigGetValue( NULL, "/loglevel" );
378         osrfStringArray* arr = osrfNewStringArray(8);
379         osrfConfigGetValueList(NULL, arr, "/domain");
380
381         char* username       = osrfConfigGetValue( NULL, "/username" );
382         char* password       = osrfConfigGetValue( NULL, "/passwd" );
383         char* port           = osrfConfigGetValue( NULL, "/port" );
384         char* unixpath       = osrfConfigGetValue( NULL, "/unixpath" );
385         char* facility       = osrfConfigGetValue( NULL, "/syslog" );
386         char* actlog         = osrfConfigGetValue( NULL, "/actlog" );
387
388         /* if we're a source-client, tell the logger */
389         char* isclient = osrfConfigGetValue(NULL, "/client");
390         if( isclient && !strcasecmp(isclient,"true") )
391                 osrfLogSetIsClient(1);
392         free(isclient);
393
394         int llevel = 0;
395         int iport = 0;
396         if(port) iport = atoi(port);
397         if(log_level) llevel = atoi(log_level);
398
399         if(!strcmp(log_file, "syslog")) {
400                 osrfLogInit( OSRF_LOG_TYPE_SYSLOG, contextnode, llevel );
401                 osrfLogSetSyslogFacility(osrfLogFacilityToInt(facility));
402                 if(actlog) osrfLogSetSyslogActFacility(osrfLogFacilityToInt(actlog));
403
404         } else {
405                 osrfLogInit( OSRF_LOG_TYPE_FILE, contextnode, llevel );
406                 osrfLogSetFile( log_file );
407         }
408
409
410         /* Get a domain, if one is specified */
411         const char* domain = osrfStringArrayGetString( arr, 0 ); /* just the first for now */
412         if(!domain) {
413                 fprintf(stderr, "No domain specified in configuration file %s\n", config_file);
414                 osrfLogError( OSRF_LOG_MARK, "No domain specified in configuration file %s\n",
415                                 config_file );
416                 failure = 1;
417         }
418
419         if(!username) {
420                 fprintf(stderr, "No username specified in configuration file %s\n", config_file);
421                 osrfLogError( OSRF_LOG_MARK, "No username specified in configuration file %s\n",
422                                 config_file );
423                 failure = 1;
424         }
425
426         if(!password) {
427                 fprintf(stderr, "No password specified in configuration file %s\n", config_file);
428                 osrfLogError( OSRF_LOG_MARK, "No password specified in configuration file %s\n",
429                                 config_file);
430                 failure = 1;
431         }
432
433         if((iport <= 0) && !unixpath) {
434                 fprintf(stderr, "No unixpath or valid port in configuration file %s\n", config_file);
435                 osrfLogError( OSRF_LOG_MARK, "No unixpath or valid port in configuration file %s\n",
436                         config_file);
437                 failure = 1;
438         }
439
440         if (failure) {
441                 osrfStringArrayFree(arr);
442                 free(log_file);
443                 free(log_level);
444                 free(username);
445                 free(password);
446                 free(port);
447                 free(unixpath);
448                 free(facility);
449                 free(actlog);
450                 return 0;
451         }
452
453         osrfLogInfo( OSRF_LOG_MARK, "Bootstrapping system with domain %s, port %d, and unixpath %s",
454                 domain, iport, unixpath ? unixpath : "(none)" );
455         transport_client* client = client_init( domain, iport, unixpath, 0 );
456
457         char host[HOST_NAME_MAX + 1] = "";
458         gethostname(host, sizeof(host) );
459         host[HOST_NAME_MAX] = '\0';
460
461         char tbuf[32];
462         tbuf[0] = '\0';
463         snprintf(tbuf, 32, "%f", get_timestamp_millis());
464
465         if(!resource) resource = "";
466
467         int len = strlen(resource) + 256;
468         char buf[len];
469         buf[0] = '\0';
470         snprintf(buf, len - 1, "%s_%s_%s_%ld", resource, host, tbuf, (long) getpid() );
471
472         if(client_connect( client, username, password, buf, 10, AUTH_DIGEST )) {
473                 /* child nodes will leak the parents client... but we can't free
474                         it without disconnecting the parents client :( */
475                 osrfGlobalTransportClient = client;
476         }
477
478         osrfStringArrayFree(arr);
479         free(actlog);
480         free(facility);
481         free(log_level);
482         free(log_file);
483         free(username);
484         free(password);
485         free(port);
486         free(unixpath);
487
488         if(osrfGlobalTransportClient)
489                 return 1;
490
491         return 0;
492 }
493
494 /**
495         @brief Disconnect from Jabber.
496         @return Zero in all cases.
497 */
498 int osrf_system_disconnect_client( void ) {
499         client_disconnect( osrfGlobalTransportClient );
500         client_free( osrfGlobalTransportClient );
501         osrfGlobalTransportClient = NULL;
502         return 0;
503 }
504
505 static int shutdownComplete = 0;
506 int osrf_system_shutdown( void ) {
507         if(shutdownComplete) return 0;
508         osrfConfigCleanup();
509         osrfCacheCleanup();
510         osrf_system_disconnect_client();
511         osrf_settings_free_host_config(NULL);
512         osrfAppSessionCleanup();
513         osrfLogCleanup();
514         shutdownComplete = 1;
515         return 1;
516 }