LP1204123 Improved per-service control (C/Perl)
authorBill Erickson <berick@esilibrary.com>
Wed, 31 Jul 2013 21:43:12 +0000 (17:43 -0400)
committerJason Stephenson <jstephenson@mvlc.org>
Wed, 4 Sep 2013 15:07:59 +0000 (11:07 -0400)
C opensrf no longer mantains a single master process.  Instead, like
Perl, each Listener process writes its own PID file and can be managed
individually.  Related to this, much code was cleaned up in
osrf_system.c.

osrf_ctl.sh now has the ability to stop/start individual services for
Perl and C, using the new -s <service> option.  Examples:

osrf_ctl.sh -l -a restart_c -s opensrf.math
osrf_ctl.sh -l -a restart_perl -s opensrf.settings

Signed-off-by: Bill Erickson <berick@esilibrary.com>
Signed-off-by: Jason Stephenson <jstephenson@mvlc.org>

bin/osrf_ctl.sh.in
include/opensrf/osrf_system.h
src/libopensrf/opensrf.c
src/libopensrf/osrf_prefork.c
src/libopensrf/osrf_system.c

index 18838cd..78995ab 100755 (executable)
@@ -214,6 +214,7 @@ start_python() {
 Python processes are either already running, or were not correctly shut down.
 Now clearing any stale PID files and restarting Perl processes.
 EOF
+    rm $PID_OSRF_PYTHON;
     smart_clear;
     fi;
 
@@ -247,37 +248,96 @@ start_perl() {
 Perl processes are either already running, or were not correctly shut down.
 Now clearing any stale PID files and restarting Perl processes.
 EOF
+    rm $PID_OSRF_PERL;
     smart_clear;
     fi;
+
+    PL_ARGS="--verbose --pid-dir $OPT_PID_DIR --config $OPT_CONFIG --settings-startup-pause 3";
+
+    if [ -n "$OPT_SERVICE" ]; then 
+        PL_ARGS="$PL_ARGS --action start --service $OPT_SERVICE";
+    else
+        PL_ARGS="$PL_ARGS --action start_all";
+    fi;
     
-    opensrf-perl.pl --verbose --pid-dir $OPT_PID_DIR \
-        --config $OPT_CONFIG --action start_all --settings-startup-pause 3
-    [ "$?" = "0" ] && echo "Perl Started" > $PID_OSRF_PERL;  #Dummy pid file, removed when a proper shutdown happens.
+    echo "opensrf-perl.pl $PL_ARGS";
+    opensrf-perl.pl $PL_ARGS;
+
+    #Dummy pid file, removed when a proper shutdown happens.
+    [ "$?" = "0" ] && echo "Perl Started" > $PID_OSRF_PERL;  
        return 0;
 }
 
 stop_perl() {
     echo "Stopping OpenSRF Perl";
-    opensrf-perl.pl --verbose --pid-dir $OPT_PID_DIR --config $OPT_CONFIG --action stop_all
+
+    PL_ARGS="--verbose --pid-dir $OPT_PID_DIR --config $OPT_CONFIG"
+
+    if [ -n "$OPT_SERVICE" ]; then 
+        PL_ARGS="$PL_ARGS --action stop --service $OPT_SERVICE";
+    else
+        PL_ARGS="$PL_ARGS --action stop_all";
+    fi;
+
+    echo "opensrf-perl.pl $PL_ARGS";
+    opensrf-perl.pl $PL_ARGS;
+
+    # remove the dummy PID file
     [ -e $PID_OSRF_PERL ] && rm $PID_OSRF_PERL;
        sleep 1;
        return 0;
 }
 
 start_c() {
+    echo "Starting OpenSRF C";
+
+    if [ -e $PID_OSRF_C ]; then # If C is started already (or it thinks so)
+    cat << EOF
+C processes are either already running, or were not correctly shut down.
+Now clearing any stale PID files and starting C processes.
+EOF
+    rm $PID_OSRF_C;
+    smart_clear;
+    fi;
+
        host=$OSRF_HOSTNAME
        if [ "_$host" = "_" ]; then
                host=$(perl -MNet::Domain=hostfqdn -e 'print hostfqdn()');
        fi;
 
-       do_action "start" $PID_OSRF_C "OpenSRF C (host=$host)";
-       opensrf-c $host $OPT_CONFIG opensrf "$PID_OSRF_C";
+    C_ARGS="-h $host -c $OPT_CONFIG -x opensrf -p $OPT_PID_DIR";
+    if [ -n "$OPT_SERVICE" ]; then
+        C_ARGS="$C_ARGS -s $OPT_SERVICE -a start";
+    else
+        C_ARGS="$C_ARGS -a start_all";
+    fi;
+
+    echo "opensrf-c $C_ARGS";
+       opensrf-c $C_ARGS;
+
+    #Dummy pid file, removed when a proper shutdown happens.
+    [ "$?" = "0" ] && echo "C Started" > $PID_OSRF_C;
        return 0;
 }
 
 stop_c() {
-       do_action "stop" $PID_OSRF_C "OpenSRF C";
-       [ -e $PID_OSRF_C ] && rm $PID_OSRF_C;
+    echo "Stopping OpenSRF C";
+       host=$OSRF_HOSTNAME
+       if [ "_$host" = "_" ]; then
+               host=$(perl -MNet::Domain=hostfqdn -e 'print hostfqdn()');
+       fi;
+
+    C_ARGS="-h $host -c $OPT_CONFIG -x opensrf -p $OPT_PID_DIR";
+    if [ -n "$OPT_SERVICE" ]; then
+        C_ARGS="$C_ARGS -s $OPT_SERVICE -a stop";
+    else
+        C_ARGS="$C_ARGS -a stop_all";
+    fi;
+
+    echo "opensrf-c $C_ARGS";
+       opensrf-c $C_ARGS;
+
+    [ -e $PID_OSRF_C ] && rm $PID_OSRF_C;
        sleep 1;
        return 0;
 }
@@ -293,6 +353,11 @@ smart_clear() {
        echo "Smart clearing PID files...";
        for line in $(find $OPT_PID_DIR -name *.pid -type f)
        do
+        if [ $line = $PID_OSRF_PERL -o $line = $PID_OSRF_C \
+            -o $line = $PID_OSRF_PYTHON ]; then
+            continue;
+        fi;
+
                running="false";
                for p in $(cat $line)
                do
index 328d149..18d8c85 100644 (file)
@@ -27,6 +27,11 @@ int osrfSystemBootstrapClientResc( const char* config_file,
 int osrfSystemBootstrap( const char* hostname, const char* configfile,
                const char* contextNode );
 
+int osrf_system_service_ctrl(
+    const char* host,    const char* config,
+    const char* context, const char* piddir, 
+    const char* action,  const char* service);
+
 transport_client* osrfSystemGetTransportClient( void );
 
 int osrf_system_disconnect_client();
index 5afa27e..27a13ad 100644 (file)
@@ -1,4 +1,8 @@
 #include "opensrf/osrf_system.h"
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
 
 /**
        @brief Run an OSRF server as defined by the command line and a config file.
 */
 int main( int argc, char* argv[] ) {
 
-       if( argc < 4 ) {
-               fprintf(stderr, "Usage: %s <host> <bootstrap_config> <config_context>\n", argv[0]);
+    char* host    = NULL;
+    char* config  = NULL;
+    char* context = NULL;
+    char* piddir  = NULL;
+    char* action  = NULL;
+    char* service = NULL;
+    opterr = 0;
+
+       /* values must be strdup'ed because init_proc_title / 
+     * set_proc_title are evil and overwrite the argv memory */
+
+    int c;
+    while ((c = getopt(argc, argv, "h:c:x:p:a:s:")) != -1) {
+        switch (c) {
+            case 'h':
+                host = strdup(optarg);
+                break;
+            case 'c':
+                config = strdup(optarg);
+                break;
+            case 'x':
+                context = strdup(optarg);
+                break;
+            case 'p':
+                piddir = strdup(optarg);
+                break;
+            case 'a':
+                action = strdup(optarg);
+                break;
+            case 's':
+                service = strdup(optarg);
+                break;
+            default:
+                continue;
+        }
+    }
+
+
+    if (!(host && config && context && piddir && action)) {
+               fprintf(stderr, "Usage: %s -h <host> -c <config> "
+            "-x <config_context> -p <piddir>\n", argv[0]);
                return 1;
        }
 
-       /* these must be strdup'ed because init_proc_title / set_proc_title
-               are evil and overwrite the argv memory */
-       char* host      = strdup( argv[1] );
-       char* config    = strdup( argv[2] );
-       char* context   = strdup( argv[3] );
-
-       if( argv[4] )
-               osrfSystemSetPidFile( argv[4] );
+    // prepare the proc title hack
+    init_proc_title(argc, argv);
 
-       init_proc_title( argc, argv );
-       set_proc_title( "OpenSRF System-C" );
+    // make sure the service name is valid
+    if (service && strlen(service) == 0) {
+        free(service);
+        service = NULL;
+    }
 
-       int ret = osrfSystemBootstrap( host, config, context );
+    int ret = osrf_system_service_ctrl(
+        host, config, context, piddir, action, service);
 
        if (ret != 0) {
                osrfLogError(
@@ -44,6 +85,9 @@ int main( int argc, char* argv[] ) {
        free(host);
        free(config);
        free(context);
+    free(piddir);
+    free(action);
+    if (service) free(service);
 
        return ret;
 }
index d2537e3..cb3860f 100644 (file)
@@ -369,6 +369,7 @@ static int prefork_child_init_hook( prefork_child* child ) {
        free( isclient );
 
        // Remove traces of our parent's socket connection so we can have our own.
+    // TODO: not necessary if parent disconnects first
        osrfSystemIgnoreTransportClient();
 
        // Connect to Jabber
index f6d338c..f201a15 100644 (file)
 #define HOST_NAME_MAX 256
 #endif
 
-static void report_child_status( pid_t pid, int status );
-struct child_node;
-typedef struct child_node ChildNode;
 osrfStringArray* log_protect_arr = NULL;
 
-/**
-       @brief Represents a child process.
-*/
-struct child_node
-{
-       ChildNode* pNext;  /**< Linkage pointer for doubly linked list. */
-       ChildNode* pPrev;  /**< Linkage pointer for doubly linked list. */
-       pid_t pid;         /**< Process ID of the child process. */
-       char* app;
-       char* libfile;
-};
-
-/** List of child processes. */
-static ChildNode* child_list;
-
 /** Pointer to the global transport_client; i.e. our connection to Jabber. */
 static transport_client* osrfGlobalTransportClient = NULL;
 
-/** Switch to be set by signal handler */
-static volatile sig_atomic_t sig_caught;
-
 /** Boolean: set to true when we finish shutting down. */
 static int shutdownComplete = 0;
 
-/** Name of file to which to write the process ID of the child process */
-char* pidfile_name = NULL;
-
-static void add_child( pid_t pid, const char* app, const char* libfile );
-static void delete_child( ChildNode* node );
-static void delete_all_children( void );
-static ChildNode* seek_child( pid_t pid );
-
-/**
-       @brief Wait on all dead child processes so that they won't be zombies.
-*/
-static void reap_children( void ) {
-       if( sig_caught ) {
-               if( SIGTERM == sig_caught || SIGINT == sig_caught ) {
-                       osrfLogInfo( OSRF_LOG_MARK, "Killed by %s; terminating",
-                                       SIGTERM == sig_caught ? "SIGTERM" : "SIGINT" );
-               } else
-                       osrfLogInfo( OSRF_LOG_MARK, "Killed by signal %d; terminating", (int) sig_caught );
-       }
-
-       // If we caught a signal, then the signal handler already did a kill().
-       // If we didn't, then do the kill() now.
-       if( ! sig_caught )
-               kill( 0, SIGTERM );
-
-       sleep(1); /* Give the children a chance to die before we reap them. */
-
-       // Wait for each dead child.  The WNOHANG option means to return immediately if
-       // there are no dead children, instead of waiting for them to die.  It is therefore
-       // possible for a child still to be alive when we exit this function, either because
-       // it intercepted the SIGTERM and ignored it, or because it took longer to die than
-       // the time we gave it.
-       pid_t child_pid;
-       while( (child_pid = waitpid(-1, NULL, WNOHANG)) > 0 )
-               osrfLogInfo(OSRF_LOG_MARK, "Killed child %d", child_pid);
+/** Returns the full path to the pid file for the service */
+static char* get_pid_file(const char* path, const char* service);
 
-       // Remove all nodes from the list of child processes.
-       delete_all_children();
-}
-
-/**
-       @brief Signal handler for SIGTERM and SIGINT.
-
-       Kill all child processes, and set a switch so that we'll know that the signal arrived.
-*/
-static void handleKillSignal( int signo ) {
-       // First ignore SIGTERM.  Otherwise we would send SIGTERM to ourself, intercept it,
-       // and kill() again in an endless loop.
-       signal( SIGTERM, SIG_IGN );
-
-       //Kill all child processes.  This is safe to do in a signal handler, because POSIX
-       // specifies that kill() is reentrant.  It is necessary because, if we did the kill()
-       // only in reap_children() (above), then there would be a narrow window of vulnerability
-       // in the main loop: if the signal arrives between checking sig_caught and calling wait(),
-       // we would wait indefinitely for a child to die on its own.
-       kill( 0, SIGTERM );
-       sig_caught = signo;
-}
+static int stop_service(const char* path, const char* service);
 
 /**
        @brief Return a pointer to the global transport_client.
@@ -126,27 +51,6 @@ transport_client* osrfSystemGetTransportClient( void ) {
 }
 
 /**
-       @brief Save a copy of a file name to be used for writing a process ID.
-       @param name Designated file name, or NULL.
-
-       Save a file name for later use in saving a process ID.  If @a name is NULL, leave
-       the file name NULL.
-
-       When the parent process spawns a child, the child becomes a daemon.  The parent writes the
-       child's process ID to the PID file, if one has been designated, so that some other process
-       can retrieve the PID later and kill the daemon.
-*/
-void osrfSystemSetPidFile( const char* name ) {
-       if( pidfile_name )
-               free( pidfile_name );
-
-       if( name )
-               pidfile_name = strdup( name );
-       else
-               pidfile_name = NULL;
-}
-
-/**
        @brief Discard the global transport_client, but without disconnecting from Jabber.
 
        To be called by a child process in order to disregard the parent's connection without
@@ -209,305 +113,217 @@ int osrfSystemInitCache( void ) {
        return 0;
 }
 
-/**
-       @brief Launch a collection of servers, as defined by the settings server.
-       @param hostname Full network name of the host where the process is running; or
-       'localhost' will do.
-       @param configfile Name of the configuration file; normally '/openils/conf/opensrf_core.xml'.
-       @param contextNode Name of an aggregate within the configuration file, containing the
-       relevant subset of configuration stuff.
-       @return - Zero if successful, or -1 if not.
-*/
-int osrfSystemBootstrap( const char* hostname, const char* configfile,
-               const char* contextNode ) {
-       if( !(hostname && configfile && contextNode) )
-               return -1;
-
-       // Load the conguration, open the log, open a connection to Jabber
-       if(!osrfSystemBootstrapClientResc(configfile, contextNode, "settings_grabber" )) {
-               osrfLogError( OSRF_LOG_MARK,
-                       "Unable to bootstrap for host %s from configuration file %s",
-                       hostname, configfile );
-               return -1;
-       }
-
-       shutdownComplete = 0;
-
-       // Get a list of applications to launch from the settings server
-       int retcode = osrf_settings_retrieve(hostname);
-       osrf_system_disconnect_client();
-
-       if( retcode ) {
-               osrfLogError( OSRF_LOG_MARK,
-                       "Unable to retrieve settings for host %s from configuration file %s",
-                       hostname, configfile );
-               return -1;
-       }
-
-       // Turn into a daemon.  The parent forks and exits.  Only the
-       // child returns, with the standard streams (stdin, stdout, and
-       // stderr) redirected to /dev/null.
-       daemonize();
-
-       jsonObject* apps = osrf_settings_host_value_object("/activeapps/appname");
-       osrfStringArray* arr = osrfNewStringArray(8);
-
-       if(apps) {
-               int i = 0;
-
-               if(apps->type == JSON_STRING) {
-                       osrfStringArrayAdd(arr, jsonObjectGetString(apps));
-
-               } else {
-                       const jsonObject* app;
-                       while( (app = jsonObjectGetIndex(apps, i++)) )
-                               osrfStringArrayAdd(arr, jsonObjectGetString(app));
-               }
-               jsonObjectFree(apps);
-
-               const char* appname = NULL;
-               int first_launch = 1;             // Boolean
-               i = 0;
-               while( (appname = osrfStringArrayGetString(arr, i++)) ) {
-
-                       char* lang = osrf_settings_host_value("/apps/%s/language", appname);
-
-                       if(lang && !strcasecmp(lang,"c"))  {
-
-                               char* libfile = osrf_settings_host_value("/apps/%s/implementation", appname);
-
-                               if(! (appname && libfile) ) {
-                                       osrfLogWarning( OSRF_LOG_MARK, "Missing appname / libfile in settings config");
-                                       continue;
-                               }
-
-                               osrfLogInfo( OSRF_LOG_MARK, "Launching application %s with implementation %s",
-                                               appname, libfile);
-
-                               pid_t pid;
-
-                               if( (pid = fork()) ) {    // if parent
-                                       // store pid in local list for re-launching dead children...
-                                       add_child( pid, appname, libfile );
-                                       osrfLogInfo( OSRF_LOG_MARK, "Running application child %s: process id %ld",
-                                                                appname, (long) pid );
-
-                                       if( first_launch ) {
-                                               if( pidfile_name ) {
-                                                       // Write our own PID to a PID file so that somebody can use it to
-                                                       // send us a signal later.  If we don't find any C apps to launch,
-                                                       // then we will quietly exit without writing a PID file, and without
-                                                       // waiting to be killed by a signal.
-
-                                                       FILE* pidfile = fopen( pidfile_name, "w" );
-                                                       if( !pidfile ) {
-                                                               osrfLogError( OSRF_LOG_MARK, "Unable to open PID file \"%s\": %s",
-                                                                       pidfile_name, strerror( errno ) );
-                                                               free( pidfile_name );
-                                                               pidfile_name = NULL;
-                                                               return -1;
-                                                       } else {
-                                                               fprintf( pidfile, "%ld\n", (long) getpid() );
-                                                               fclose( pidfile );
-                                                       }
-                                               }
-                                               first_launch = 0;
-                                       }
-
-                               } else {         // if child, run the application
-
-                                       osrfLogInfo( OSRF_LOG_MARK, " * Running application %s\n", appname);
-                                       if( pidfile_name ) {
-                                               free( pidfile_name );    // tidy up some debris from the parent
-                                               pidfile_name = NULL;
-                                       }
-                                       if( osrfAppRegisterApplication( appname, libfile ) == 0 )
-                                               osrf_prefork_run(appname);
-
-                                       osrfLogDebug( OSRF_LOG_MARK, "Server exiting for app %s and library %s\n",
-                                                       appname, libfile );
-                                       exit(0);
-                               }
-                       } // language == c
-               } // end while
-       }
-
-       osrfStringArrayFree(arr);
-
-       signal(SIGTERM, handleKillSignal);
-       signal(SIGINT, handleKillSignal);
-
-       // Wait indefinitely for all the child processes to terminate, or for a signal to
-       // tell us to stop.  When there are no more child processes, wait() returns an
-       // ECHILD error and we break out of the loop.
-       int status;
-       pid_t pid;
-       while( ! sig_caught ) {
-               pid = wait( &status );
-               if( -1 == pid ) {
-                       if( errno == ECHILD )
-                               osrfLogError( OSRF_LOG_MARK, "We have no more live services... exiting" );
-                       else if( errno != EINTR )
-                               osrfLogError(OSRF_LOG_MARK, "Exiting top-level system loop with error: %s",
-                                               strerror( errno ) );
-
-                       // Since we're not being killed by a signal as usual, delete the PID file
-                       // so that no one will try to kill us when we're already dead.
-                       if( pidfile_name )
-                               remove( pidfile_name );
-                       break;
-               } else {
-                       report_child_status( pid, status );
-               }
-       }
-
-       reap_children();
-       osrfConfigCleanup();
-       osrf_system_disconnect_client();
-       osrf_settings_free_host_config(NULL);
-       free( pidfile_name );
-       pidfile_name = NULL;
-       return 0;
-}
-
-/**
-       @brief Report the exit status of a dead child process, then remove it from the list.
-       @param pid Process ID of the child.
-       @param status Exit status as captured by wait().
-*/
-static void report_child_status( pid_t pid, int status )
-{
-       const char* app;
-       const char* libfile;
-       ChildNode* node = seek_child( pid );
-
-       if( node ) {
-               app     = node->app     ? node->app     : "[unknown]";
-               libfile = node->libfile ? node->libfile : "[none]";
-       } else
-               app = libfile = "";
-
-       if( WIFEXITED( status ) )
-       {
-               int rc = WEXITSTATUS( status );  // return code of child process
-               if( rc )
-                       osrfLogError( OSRF_LOG_MARK, "Child process %ld (app %s) exited with return code %d",
-                                       (long) pid, app, rc );
-               else
-                       osrfLogInfo( OSRF_LOG_MARK, "Child process %ld (app %s) exited normally",
-                                       (long) pid, app );
-       }
-       else if( WIFSIGNALED( status ) )
-       {
-               osrfLogError( OSRF_LOG_MARK, "Child process %ld (app %s) killed by signal %d",
-                                         (long) pid, app, WTERMSIG( status) );
-       }
-       else if( WIFSTOPPED( status ) )
-       {
-               osrfLogError( OSRF_LOG_MARK, "Child process %ld (app %s) stopped by signal %d",
-                                         (long) pid, app, (int) WSTOPSIG( status ) );
-       }
-
-       delete_child( node );
-}
-
-/*----------- Routines to manage list of children --*/
-
-/**
-       @brief Add a node to the list of child processes.
-       @param pid Process ID of the child process.
-       @param app Name of the child application.
-       @param libfile Name of the shared library where the child process resides.
-*/
-static void add_child( pid_t pid, const char* app, const char* libfile )
-{
-       /* Construct new child node */
-
-       ChildNode* node = safe_malloc( sizeof( ChildNode ) );
-
-       node->pid = pid;
-
-       if( app )
-               node->app = strdup( app );
-       else
-               node->app = NULL;
-
-       if( libfile )
-               node->libfile = strdup( libfile );
-       else
-               node->libfile = NULL;
-
-       /* Add new child node to the head of the list */
-
-       node->pNext = child_list;
-       node->pPrev = NULL;
-
-       if( child_list )
-               child_list->pPrev = node;
-
-       child_list = node;
-}
-
-/**
-       @brief Remove a node from the list of child processes.
-       @param node Pointer to the node to be removed.
-*/
-static void delete_child( ChildNode* node ) {
-
-       /* Sanity check */
-
-       if( ! node )
-               return;
-
-       /* Detach the node from the list */
-
-       if( node->pPrev )
-               node->pPrev->pNext = node->pNext;
-       else
-               child_list = node->pNext;
-
-       if( node->pNext )
-               node->pNext->pPrev = node->pPrev;
-
-       /* Deallocate the node and its payload */
-
-       free( node->app );
-       free( node->libfile );
-       free( node );
+static char* get_pid_file(const char* piddir, const char* service) {
+    int nsize = strlen(piddir) + strlen(service) + 6;
+    char pfname[nsize];
+    snprintf(pfname, nsize, "%s/%s.pid", piddir, service);
+    pfname[nsize-1] = '\0';
+    return strdup(pfname);
 }
 
-/**
-       @brief Remove all nodes from the list of child processes, rendering it empty.
-*/
-static void delete_all_children( void ) {
-
-       while( child_list )
-               delete_child( child_list );
+// TERM the process and delete the PID file
+static int stop_service(const char* piddir, const char* service) {
+    char pidstr[16];
+    char* pidfile_name = get_pid_file(piddir, service);
+    FILE* pidfile = fopen(pidfile_name, "r");
+
+    osrfLogInfo(OSRF_LOG_MARK, "Stopping service %s", service);
+
+    if (pidfile) {
+
+        if (fgets(pidstr, 16, pidfile) != NULL) {
+            long pid = atol(pidstr);
+
+            if (pid) {
+                // we have a PID, now send the TERM signal the process
+                fprintf(stdout, 
+                    "* stopping service pid=%ld %s\n", pid, service);
+                kill(pid, SIGTERM);
+            }
+
+        } else {
+            osrfLogWarning(OSRF_LOG_MARK,
+                "Unable to read pid file %s", pidfile_name);
+        }
+
+        fclose(pidfile);
+
+        if (unlink(pidfile_name) != 0) {
+            osrfLogError(OSRF_LOG_MARK, 
+                "Unable to delete pid file %s", pidfile_name);
+        }
+
+    } else {
+        osrfLogWarning(OSRF_LOG_MARK, 
+            "Unable to open pidfile %s for reading", pidfile_name);
+    }
+    
+    free(pidfile_name);
+    return 0;
 }
 
 /**
-       @brief Find the node for a child process of a given process ID.
-       @param pid The process ID of the child process.
-       @return A pointer to the corresponding node if found; otherwise NULL.
+       @brief Launch one or more opensrf services
+       @param hostname Full network name of the host where the process is 
+        running; or 'localhost' will do.
+       @param config Name of the configuration file; 
+        normally '/openils/conf/opensrf_core.xml'.
+       @param context Name of an aggregate within the configuration file, 
+        containing the relevant subset of configuration stuff.
+    @param piddir Name of the PID path the PID file directory
+    @param action Name of action.  Options include start, start_all, stop,
+        and stop_all
+    @param service Name of the service to start/stop.  If no value is 
+        specified, all C-based services are affected
+       @return - Zero if successful, or -1 if not.
 */
-static ChildNode* seek_child( pid_t pid ) {
-
-       /* Return a pointer to the child node for the */
-       /* specified process ID, or NULL if not found */
-
-       ChildNode* node = child_list;
-       while( node ) {
-               if( node->pid == pid )
-                       break;
-               else
-                       node = node->pNext;
-       }
 
-       return node;
+// if service is null, all services are started
+int osrf_system_service_ctrl(  
+        const char* hostname, const char* config, 
+        const char* context, const char* piddir, 
+        const char* action, const char* service) {
+    
+    // Load the conguration, open the log, open a connection to Jabber
+    if (!osrfSystemBootstrapClientResc(config, context, "c_launcher")) {
+        osrfLogError(OSRF_LOG_MARK,
+            "Unable to bootstrap for host %s from configuration file %s",
+            hostname, config);
+        return -1;
+    }
+    
+    // Get the list of applications from the settings server
+    // sometimes the network / settings server is slow to get going, s
+    // so give it a few tries before giving up.
+    int j;
+    int retcode;
+    for (j = 0; j < 3; j++) {
+        retcode = osrf_settings_retrieve(hostname);
+        if (retcode == 0) break; // success
+        osrfLogInfo(OSRF_LOG_MARK, 
+            "Unable to retrieve settings from settings server, retrying..");
+        sleep(1);
+    }
+
+    // all done talking to the network
+    osrf_system_disconnect_client();
+
+    if (retcode) {
+        osrfLogWarning(OSRF_LOG_MARK, "Unable to retrieve settings for "
+            "host %s from configuration file %s", hostname, config);
+        // this usually means settings server isn't running, which can happen
+        // for a variety of reasons.  Log the problem then exit cleanly.
+        return 0;
+    }
+
+    jsonObject* apps = osrf_settings_host_value_object("/activeapps/appname");
+
+    if (!apps) {
+        osrfLogInfo(OSRF_LOG_MARK, "OpenSRF-C found no apps to run");
+        osrfConfigCleanup();
+        osrf_settings_free_host_config(NULL);
+    }
+
+    osrfStringArray* arr = osrfNewStringArray(8);
+    int i = 0;
+
+    if(apps->type == JSON_STRING) {
+        osrfStringArrayAdd(arr, jsonObjectGetString(apps));
+
+    } else {
+        const jsonObject* app;
+        while( (app = jsonObjectGetIndex(apps, i++)) )
+            osrfStringArrayAdd(arr, jsonObjectGetString(app));
+    }
+    jsonObjectFree(apps);
+
+    i = 0;
+    const char* appname = NULL;
+    while ((appname = osrfStringArrayGetString(arr, i++))) {
+
+        if (!appname) {
+            osrfLogWarning(OSRF_LOG_MARK, 
+                "Invalid service name at index %d", i);
+            continue;
+        }
+
+        char* lang = osrf_settings_host_value("/apps/%s/language", appname);
+
+        // this is not a C service, skip it.
+        if (!lang || strcasecmp(lang, "c")) continue;
+
+        // caller requested a specific service, but not this one
+        if (service && strcmp(service, appname))
+            continue;
+
+        // stop service(s)
+        if (!strncmp(action, "stop", 4)) {
+            stop_service(piddir, appname);
+            continue;
+        }
+
+        pid_t pid;
+        if ((pid = fork())) {
+            // parent process forks the Listener, logs the PID to stdout, 
+            // then goes about its business
+            fprintf(stdout, 
+                "* starting service pid=%ld %s\n", (long) pid, appname);
+            continue;
+        }
+
+        // this is the top-level Listener process.  It's responsible
+        // for managing all of the processes related to a given service.
+        daemonize();
+
+        char* libfile = osrf_settings_host_value(
+            "/apps/%s/implementation", appname);
+
+        if (!libfile) {
+            osrfLogError(OSRF_LOG_MARK, 
+                "Service %s has no implemention", appname);
+            exit(1);
+        }
+
+        osrfLogInfo(OSRF_LOG_MARK, 
+            "Launching application %s with implementation %s",
+            appname, libfile);
+
+        // write the PID of our newly detached process to the PID file
+        // pid file name is /path/to/dir/<service>.pid
+        char* pidfile_name = get_pid_file(piddir, appname);
+        FILE* pidfile = fopen(pidfile_name, "w");
+        if (pidfile) {
+            osrfLogDebug(OSRF_LOG_MARK, 
+                "Writing PID %ld for service %s", (long) getpid(), appname);
+            fprintf(pidfile, "%ld\n", (long) getpid());
+            fclose(pidfile);
+        } else {
+            osrfLogError(OSRF_LOG_MARK, 
+                "Unable to open PID file '%s': %s", 
+                    pidfile_name, strerror(errno));
+            exit(1);
+        }
+        free(pidfile_name);
+
+        if (osrfAppRegisterApplication(appname, libfile) == 0)
+            osrf_prefork_run(appname);
+
+        osrfLogInfo(OSRF_LOG_MARK, 
+            "Prefork Server exiting for service %s and implementation %s\n", 
+            appname, libfile);
+
+        exit(0);
+
+    } // service name loop
+
+    // main process can now go away
+    osrfStringArrayFree(arr);
+    osrfConfigCleanup();
+    osrf_settings_free_host_config(NULL);
+
+    return 0;
 }
 
-/*----------- End of routines to manage list of children --*/
-
 /**
        @brief Bootstrap a generic application from info in the configuration file.
        @param config_file Name of the configuration file.