LP#1343578: Perl/C syslog "logtag" additions.
[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 osrfStringArray* log_protect_arr = NULL;
27
28 /** Pointer to the global transport_client; i.e. our connection to Jabber. */
29 static transport_client* osrfGlobalTransportClient = NULL;
30
31 /** Boolean: set to true when we finish shutting down. */
32 static int shutdownComplete = 0;
33
34 /** Returns the full path to the pid file for the service */
35 static char* get_pid_file(const char* path, const char* service);
36
37 static int stop_service(const char* path, const char* service);
38
39 /**
40         @brief Return a pointer to the global transport_client.
41         @return Pointer to the global transport_client, or NULL.
42
43         A given process needs only one connection to Jabber, so we keep it a pointer to it at
44         file scope.  This function returns that pointer.
45
46         If the connection has been opened by a previous call to osrfSystemBootstrapClientResc(),
47         return the pointer.  Otherwise return NULL.
48 */
49 transport_client* osrfSystemGetTransportClient( void ) {
50         return osrfGlobalTransportClient;
51 }
52
53 /**
54         @brief Discard the global transport_client, but without disconnecting from Jabber.
55
56         To be called by a child process in order to disregard the parent's connection without
57         disconnecting it, since disconnecting would disconnect the parent as well.
58 */
59 void osrfSystemIgnoreTransportClient() {
60         client_discard( osrfGlobalTransportClient );
61         osrfGlobalTransportClient = NULL;
62 }
63
64 /**
65         @brief Bootstrap a generic application from info in the configuration file.
66         @param config_file Name of the configuration file.
67         @param contextnode Name of an aggregate within the configuration file, containing the
68         relevant subset of configuration stuff.
69         @return 1 if successful; zero or -1 if error.
70
71         - Load the configuration file.
72         - Open the log.
73         - Open a connection to Jabber.
74
75         A thin wrapper for osrfSystemBootstrapClientResc, passing it NULL for a resource.
76 */
77 int osrf_system_bootstrap_client( char* config_file, char* contextnode ) {
78         return osrfSystemBootstrapClientResc(config_file, contextnode, NULL);
79 }
80
81 /**
82         @brief Connect to one or more cache servers.
83         @return Zero in all cases.
84 */
85 int osrfSystemInitCache( void ) {
86
87         jsonObject* cacheServers = osrf_settings_host_value_object("/cache/global/servers/server");
88         char* maxCache = osrf_settings_host_value("/cache/global/max_cache_time");
89
90         if( cacheServers && maxCache) {
91
92                 if( cacheServers->type == JSON_ARRAY ) {
93                         int i;
94                         const char* servers[cacheServers->size];
95                         for( i = 0; i != cacheServers->size; i++ ) {
96                                 servers[i] = jsonObjectGetString( jsonObjectGetIndex(cacheServers, i) );
97                                 osrfLogInfo( OSRF_LOG_MARK, "Adding cache server %s", servers[i]);
98                         }
99                         osrfCacheInit( servers, cacheServers->size, atoi(maxCache) );
100
101                 } else {
102                         const char* servers[] = { jsonObjectGetString(cacheServers) };
103                         osrfLogInfo( OSRF_LOG_MARK, "Adding cache server %s", servers[0]);
104                         osrfCacheInit( servers, 1, atoi(maxCache) );
105                 }
106
107         } else {
108                 osrfLogError( OSRF_LOG_MARK,  "Missing config value for /cache/global/servers/server _or_ "
109                         "/cache/global/max_cache_time");
110         }
111
112         jsonObjectFree( cacheServers );
113         return 0;
114 }
115
116 static char* get_pid_file(const char* piddir, const char* service) {
117     int nsize = strlen(piddir) + strlen(service) + 6;
118     char pfname[nsize];
119     snprintf(pfname, nsize, "%s/%s.pid", piddir, service);
120     pfname[nsize-1] = '\0';
121     return strdup(pfname);
122 }
123
124 // TERM the process and delete the PID file
125 static int stop_service(const char* piddir, const char* service) {
126     char pidstr[16];
127     char* pidfile_name = get_pid_file(piddir, service);
128     FILE* pidfile = fopen(pidfile_name, "r");
129
130     osrfLogInfo(OSRF_LOG_MARK, "Stopping service %s", service);
131
132     if (pidfile) {
133
134         if (fgets(pidstr, 16, pidfile) != NULL) {
135             long pid = atol(pidstr);
136
137             if (pid) {
138                 // we have a PID, now send the TERM signal the process
139                 fprintf(stdout, 
140                     "* stopping service pid=%ld %s\n", pid, service);
141                 kill(pid, SIGTERM);
142             }
143
144         } else {
145             osrfLogWarning(OSRF_LOG_MARK,
146                 "Unable to read pid file %s", pidfile_name);
147         }
148
149         fclose(pidfile);
150
151         if (unlink(pidfile_name) != 0) {
152             osrfLogError(OSRF_LOG_MARK, 
153                 "Unable to delete pid file %s", pidfile_name);
154         }
155
156     } else {
157         osrfLogWarning(OSRF_LOG_MARK, 
158             "Unable to open pidfile %s for reading", pidfile_name);
159     }
160     
161     free(pidfile_name);
162     return 0;
163 }
164
165 /**
166         @brief Launch one or more opensrf services
167         @param hostname Full network name of the host where the process is 
168         running; or 'localhost' will do.
169         @param config Name of the configuration file; 
170         normally '/openils/conf/opensrf_core.xml'.
171         @param context Name of an aggregate within the configuration file, 
172         containing the relevant subset of configuration stuff.
173     @param piddir Name of the PID path the PID file directory
174     @param action Name of action.  Options include start, start_all, stop,
175         and stop_all
176     @param service Name of the service to start/stop.  If no value is 
177         specified, all C-based services are affected
178         @return - Zero if successful, or -1 if not.
179 */
180
181 // if service is null, all services are started
182 int osrf_system_service_ctrl(  
183         const char* hostname, const char* config, 
184         const char* context, const char* piddir, 
185         const char* action, const char* service) {
186     
187     // Load the conguration, open the log, open a connection to Jabber
188     if (!osrfSystemBootstrapClientResc(config, context, "c_launcher")) {
189         osrfLogError(OSRF_LOG_MARK,
190             "Unable to bootstrap for host %s from configuration file %s",
191             hostname, config);
192         return -1;
193     }
194     
195     // Get the list of applications from the settings server
196     // sometimes the network / settings server is slow to get going, s
197     // so give it a few tries before giving up.
198     int j;
199     int retcode;
200     for (j = 0; j < 3; j++) {
201         retcode = osrf_settings_retrieve(hostname);
202         if (retcode == 0) break; // success
203         osrfLogInfo(OSRF_LOG_MARK, 
204             "Unable to retrieve settings from settings server, retrying..");
205         sleep(1);
206     }
207
208     // all done talking to the network
209     osrf_system_disconnect_client();
210
211     if (retcode) {
212         osrfLogWarning(OSRF_LOG_MARK, "Unable to retrieve settings for "
213             "host %s from configuration file %s", hostname, config);
214         // this usually means settings server isn't running, which can happen
215         // for a variety of reasons.  Log the problem then exit cleanly.
216         return 0;
217     }
218
219     jsonObject* apps = osrf_settings_host_value_object("/activeapps/appname");
220
221     if (!apps) {
222         osrfLogInfo(OSRF_LOG_MARK, "OpenSRF-C found no apps to run");
223         osrfConfigCleanup();
224         osrf_settings_free_host_config(NULL);
225     }
226
227     osrfStringArray* arr = osrfNewStringArray(8);
228     int i = 0;
229
230     if(apps->type == JSON_STRING) {
231         osrfStringArrayAdd(arr, jsonObjectGetString(apps));
232
233     } else {
234         const jsonObject* app;
235         while( (app = jsonObjectGetIndex(apps, i++)) )
236             osrfStringArrayAdd(arr, jsonObjectGetString(app));
237     }
238     jsonObjectFree(apps);
239
240     i = 0;
241     const char* appname = NULL;
242     while ((appname = osrfStringArrayGetString(arr, i++))) {
243
244         if (!appname) {
245             osrfLogWarning(OSRF_LOG_MARK, 
246                 "Invalid service name at index %d", i);
247             continue;
248         }
249
250         char* lang = osrf_settings_host_value("/apps/%s/language", appname);
251
252         // this is not a C service, skip it.
253         if (!lang || strcasecmp(lang, "c")) continue;
254
255         // caller requested a specific service, but not this one
256         if (service && strcmp(service, appname))
257             continue;
258
259         // stop service(s)
260         if (!strncmp(action, "stop", 4)) {
261             stop_service(piddir, appname);
262             continue;
263         }
264
265         pid_t pid;
266         if ((pid = fork())) {
267             // parent process forks the Listener, logs the PID to stdout, 
268             // then goes about its business
269             fprintf(stdout, 
270                 "* starting service pid=%ld %s\n", (long) pid, appname);
271             continue;
272         }
273
274         // this is the top-level Listener process.  It's responsible
275         // for managing all of the processes related to a given service.
276         daemonize();
277
278         char* libfile = osrf_settings_host_value(
279             "/apps/%s/implementation", appname);
280
281         if (!libfile) {
282             osrfLogError(OSRF_LOG_MARK, 
283                 "Service %s has no implemention", appname);
284             exit(1);
285         }
286
287         osrfLogInfo(OSRF_LOG_MARK, 
288             "Launching application %s with implementation %s",
289             appname, libfile);
290
291         // write the PID of our newly detached process to the PID file
292         // pid file name is /path/to/dir/<service>.pid
293         char* pidfile_name = get_pid_file(piddir, appname);
294         FILE* pidfile = fopen(pidfile_name, "w");
295         if (pidfile) {
296             osrfLogDebug(OSRF_LOG_MARK, 
297                 "Writing PID %ld for service %s", (long) getpid(), appname);
298             fprintf(pidfile, "%ld\n", (long) getpid());
299             fclose(pidfile);
300         } else {
301             osrfLogError(OSRF_LOG_MARK, 
302                 "Unable to open PID file '%s': %s", 
303                     pidfile_name, strerror(errno));
304             exit(1);
305         }
306         free(pidfile_name);
307
308         if (osrfAppRegisterApplication(appname, libfile) == 0)
309             osrf_prefork_run(appname);
310
311         osrfLogInfo(OSRF_LOG_MARK, 
312             "Prefork Server exiting for service %s and implementation %s\n", 
313             appname, libfile);
314
315         exit(0);
316
317     } // service name loop
318
319     // main process can now go away
320     osrfStringArrayFree(arr);
321     osrfConfigCleanup();
322     osrf_settings_free_host_config(NULL);
323
324     return 0;
325 }
326
327 /**
328         @brief Bootstrap a generic application from info in the configuration file.
329         @param config_file Name of the configuration file.
330         @param contextnode Name of an aggregate within the configuration file, containing the
331         relevant subset of configuration stuff.
332         @param resource Used to construct a Jabber resource name; may be NULL.
333         @return 1 if successful; zero or -1 if error.
334
335         - Load the configuration file.
336         - Open the log.
337         - Open a connection to Jabber.
338 */
339 int osrfSystemBootstrapClientResc( const char* config_file,
340                 const char* contextnode, const char* resource ) {
341
342         int failure = 0;
343
344         if(osrfSystemGetTransportClient()) {
345                 osrfLogInfo(OSRF_LOG_MARK, "Client is already bootstrapped");
346                 return 1; /* we already have a client connection */
347         }
348
349         if( !( config_file && contextnode ) && ! osrfConfigHasDefaultConfig() ) {
350                 osrfLogError( OSRF_LOG_MARK, "No Config File Specified\n" );
351                 return -1;
352         }
353
354         if( config_file ) {
355                 osrfConfig* cfg = osrfConfigInit( config_file, contextnode );
356                 if(cfg)
357                         osrfConfigSetDefaultConfig(cfg);
358                 else
359                         return 0;   /* Can't load configuration?  Bail out */
360
361                 // fetch list of configured log redaction marker strings
362                 log_protect_arr = osrfNewStringArray(8);
363                 osrfConfig* cfg_shared = osrfConfigInit(config_file, "shared");
364                 osrfConfigGetValueList( cfg_shared, log_protect_arr, "/log_protect/match_string" );
365         }
366
367         char* log_file      = osrfConfigGetValue( NULL, "/logfile");
368         if(!log_file) {
369                 fprintf(stderr, "No log file specified in configuration file %s\n",
370                                 config_file);
371                 return -1;
372         }
373
374         char* log_level      = osrfConfigGetValue( NULL, "/loglevel" );
375         osrfStringArray* arr = osrfNewStringArray(8);
376         osrfConfigGetValueList(NULL, arr, "/domain");
377
378         char* username       = osrfConfigGetValue( NULL, "/username" );
379         char* password       = osrfConfigGetValue( NULL, "/passwd" );
380         char* port           = osrfConfigGetValue( NULL, "/port" );
381         char* unixpath       = osrfConfigGetValue( NULL, "/unixpath" );
382         char* facility       = osrfConfigGetValue( NULL, "/syslog" );
383         char* actlog         = osrfConfigGetValue( NULL, "/actlog" );
384         char* logtag         = osrfConfigGetValue( NULL, "/logtag" );
385
386         /* if we're a source-client, tell the logger */
387         char* isclient = osrfConfigGetValue(NULL, "/client");
388         if( isclient && !strcasecmp(isclient,"true") )
389                 osrfLogSetIsClient(1);
390         free(isclient);
391
392         int llevel = 0;
393         int iport = 0;
394         if(port) iport = atoi(port);
395         if(log_level) llevel = atoi(log_level);
396
397         if(!strcmp(log_file, "syslog")) {
398                 if(logtag) osrfLogSetLogTag(logtag);
399                 osrfLogInit( OSRF_LOG_TYPE_SYSLOG, contextnode, llevel );
400                 osrfLogSetSyslogFacility(osrfLogFacilityToInt(facility));
401                 if(actlog) osrfLogSetSyslogActFacility(osrfLogFacilityToInt(actlog));
402
403         } else {
404                 osrfLogInit( OSRF_LOG_TYPE_FILE, contextnode, llevel );
405                 osrfLogSetFile( log_file );
406         }
407
408
409         /* Get a domain, if one is specified */
410         const char* domain = osrfStringArrayGetString( arr, 0 ); /* just the first for now */
411         if(!domain) {
412                 fprintf(stderr, "No domain specified in configuration file %s\n", config_file);
413                 osrfLogError( OSRF_LOG_MARK, "No domain specified in configuration file %s\n",
414                                 config_file );
415                 failure = 1;
416         }
417
418         if(!username) {
419                 fprintf(stderr, "No username specified in configuration file %s\n", config_file);
420                 osrfLogError( OSRF_LOG_MARK, "No username specified in configuration file %s\n",
421                                 config_file );
422                 failure = 1;
423         }
424
425         if(!password) {
426                 fprintf(stderr, "No password specified in configuration file %s\n", config_file);
427                 osrfLogError( OSRF_LOG_MARK, "No password specified in configuration file %s\n",
428                                 config_file);
429                 failure = 1;
430         }
431
432         if((iport <= 0) && !unixpath) {
433                 fprintf(stderr, "No unixpath or valid port in configuration file %s\n", config_file);
434                 osrfLogError( OSRF_LOG_MARK, "No unixpath or valid port in configuration file %s\n",
435                         config_file);
436                 failure = 1;
437         }
438
439         if (failure) {
440                 osrfStringArrayFree(arr);
441                 free(log_file);
442                 free(log_level);
443                 free(username);
444                 free(password);
445                 free(port);
446                 free(unixpath);
447                 free(facility);
448                 free(actlog);
449                 free(logtag);
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                 osrfGlobalTransportClient = client;
474         }
475
476         osrfStringArrayFree(arr);
477         free(actlog);
478         free(facility);
479         free(log_level);
480         free(log_file);
481         free(username);
482         free(password);
483         free(port);
484         free(unixpath);
485
486         if(osrfGlobalTransportClient)
487                 return 1;
488
489         return 0;
490 }
491
492 /**
493         @brief Disconnect from Jabber.
494         @return Zero in all cases.
495 */
496 int osrf_system_disconnect_client( void ) {
497         client_disconnect( osrfGlobalTransportClient );
498         client_free( osrfGlobalTransportClient );
499         osrfGlobalTransportClient = NULL;
500         return 0;
501 }
502
503 /**
504         @brief Shut down a laundry list of facilities typically used by servers.
505
506         Things to shut down:
507         - Settings from configuration file
508         - Cache
509         - Connection to Jabber
510         - Settings from settings server
511         - Application sessions
512         - Logs
513 */
514 int osrf_system_shutdown( void ) {
515         if(shutdownComplete)
516                 return 0;
517         else {
518                 osrfConfigCleanup();
519                 osrfCacheCleanup();
520                 osrf_system_disconnect_client();
521                 osrf_settings_free_host_config(NULL);
522                 osrfAppSessionCleanup();
523                 osrfLogCleanup();
524                 shutdownComplete = 1;
525                 return 1;
526         }
527 }