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