/* IBM_PROLOG_BEGIN_TAG                                                   */
/* This is an automatically generated prolog.                             */
/*                                                                        */
/*                                                                        */
/*                                                                        */
/* Licensed Materials - Property of IBM                                   */
/*                                                                        */
/* (C) COPYRIGHT International Business Machines Corp. 1996,2019          */
/* All Rights Reserved                                                    */
/*                                                                        */
/* US Government Users Restricted Rights - Use, duplication or            */
/* disclosure restricted by GSA ADP Schedule Contract with IBM Corp.      */
/*                                                                        */
/* IBM_PROLOG_END_TAG                                                     */

/* @(#)43   1.3   src/rsct/pem/emtools/emapi_test/emapi_ex/emapi_v02_ex02.c, emtools, rsct_rady, rady2035a 6/30/98 10:47:26 */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <time.h>

#include <sys/types.h>
#include <sys/select.h>
#include <sys/time.h>

#define  HA_EM_VERSION		2
#include <ha_emapi.h>

/*
 *  emapi_v02_ex02.c
 *
 *  This program presents an example of using the Event Manager Application
 *  Programming Interface (EMAPI).  The program uses the EMAPI to monitor
 *  four programs running on various nodes in two SP partitions.  Since two
 *  SP partitions are involved, the program establishes two sessions with the
 *  EMAPI, one per SP partition.  Since the program is not multi-threaded,
 *  select() is used to multiplex responses from the two EMAPI sessions.
 *
 *  This program provides the same function as the emapi_v02_ex01.c program.
 *  This program differs in that it uses callback routines to receive events.
 *
 *  This program can be compiled with the following command:
 *
 *      cc -O emapi_v02_ex02.c -o emapi_v02_ex02 -lha_em
 */

/*
 *  If a MAX macro isn't defined by one of the included header files, define
 *  it.
 */

#ifndef MAX
#define MAX(x, y)   ((x) > (y) ? (x) : (y))
#endif

/*
 *  The following macros specify the programs this program will monitor through
 *  the EMAPI.
 */

#define PART_1      "k21s"              /* 1st SP partition                  */

#define PROG_1A     "subsys_pgrm1"      /* 1st program in 1st SP partition,  */
#define USER_1A     "root"              /* ... and user running 1st program, */
#define NODE_1A     5                   /* ... and node running 1st program. */

#define PROG_1B     "subsys_pgrm2"      /* 2nd program in 1st SP partition,  */
#define USER_1B     "root"              /* ... and user running 2nd program, */
#define NODE_1B     6                   /* ... and node running 2nd program. */

#define PART_2      "k21sp2"            /* 2nd SP partition                  */

#define PROG_2A     "subsys_pgrm1"      /* 1st program in 2nd SP partition,  */
#define USER_2A     "root"              /* ... and user running 1st program, */
#define NODE_2A     14                  /* ... and node running 1st program. */

#define PROG_2B     "subsys_pgrm2"      /* 2nd program in 2nd SP partition,  */
#define USER_2B     "root"              /* ... and user running 2nd program, */
#define NODE_2B     15                  /* ... and node running 2nd program. */

/*
 *  This program saves information about each EMAPI session it has established
 *  in a session structure.
 */

struct session {
    char        *name;      /* Name of partition used by session             */
    int          fd;        /* Session file descriptor                       */
    int          restart;   /* Boolean - Session's connection has been lost  */
};

/*
 *  This program saves information about each program it is monitoring through
 *  the EMAPI in a program structure.
 */

struct program {
    char        *name;      /* Name of a program to be monitored             */
    char        *user;      /* Name of user running program                  */
    int          node;      /* Node on which program is running              */
    ha_em_eid_t  eid;       /* Event identifier for program                  */
    int          unreged;   /* Event is unregistered                         */
    struct callback_data *cb_data; /* Callback data allocated for program    */
};

/*
 *  Data for a callback routine is stored in a callback_data structure.
 */

struct callback_data {
    struct session  *sess_p;            /* Session structure pointer         */
    struct program  *prog_p;            /* Program structure pointer         */
    int             *reg_cnt_p;         /* Registration counter pointer      */
};

/*
 *  The terminate_requested global variable is set to a non-zero value when
 *  the user of this program requests that the program be terminated.  The
 *  user requests program termination via the interrupt key (typically Ctrl-C).
 *  The interrupt key causes a SIGINT signal to be delivered to this program.
 *  This program installs a signal handler for the SIGINT signal, which sets
 *  the terminate_requested global variable to a non-zero value when SIGINT
 *  is delivered.
 */

static int terminate_requested       = 0;

/*
 *  The unregistrations_requested global variable indicates whether this
 *  program has sent unregistration requests to the Event Manager.
 *  Unregistration requests are sent when the user requests program
 *  termination.
 */

static int unregistrations_requested = 0;

/*
 *  Function prototypes for internal functions.
 */

static void interrupt_catch(int signo);
static void setup_signals(void);
static void start_session(struct session *sess_p);
static void register_for_events(struct session *sess_p,
        struct program *progs_p, int progs_elems, int *reg_cnt_p);
static void unregister_events(struct session *sess_p,
        struct program *progs_p, int progs_elems);
static void select_session(struct session *sess_p,
        fd_set *read_fds_p, int *fd_limit_p, int *timeout_p);
static int session_response_ready(struct session *sess_p, fd_set *read_fds_p);
static void process_response(struct session *sess_p,
         struct program *progs_p, int progs_elems, int *reg_cnt_p);
static void process_response_rerr(struct session *sess_p,
        struct program *progs_p, int progs_elems,
        struct ha_em_rsp_blk *rsp_blk, int *reg_cnt_p);
static void event_callback(int sess_fd, struct ha_em_rpb_event *event_p,
                           void *arg);
static void format_timestamp(struct timeval *timestamp_p,
        char *fmt_timestamp_p);
static void breakdown_rsrc_ID(char *rsrc_ID_p,
        char *prog_name_p, char *user_name_p, int *node_p);
static void find_rsrc_ID_value(char *rsrc_ID_p, char *name_p,
        char **value_pp, size_t *value_len_p);
static void end_session(struct session *sess_p);


/*
 *  The main() function calls functions to initialize the program, establish
 *  EMAPI sessions, register for events, wait for Event Manager responses,
 *  process Event Manager responses, and respond to the user request for
 *  program termination.  When the program termination request is received,
 *  main() calls functions to send unregistration requests through the EMAPI.
 *  Once Event Manager responses have been received indicating that the
 *  events have been unregistered, the program ends its EMAPI sessions,
 *  and terminates.
 */

int main(int argc, char **argv)
{
    struct session sess1 = {PART_1, -1, 0};
    struct session sess2 = {PART_2, -1, 0};

    struct program progs1[] = {{PROG_1A, USER_1A, NODE_1A, 0, 0, NULL},
                               {PROG_1B, USER_1B, NODE_1B, 0, 0, NULL}};
    struct program progs2[] = {{PROG_2A, USER_2A, NODE_2A, 0, 0, NULL},
                               {PROG_2B, USER_2B, NODE_2B, 0, 0, NULL}};

    int     progs1_elems = sizeof progs1 / sizeof progs1[0];
    int     progs2_elems = sizeof progs2 / sizeof progs2[0];

    int     reg_cnt;            /* Count of registered events                */
    fd_set  read_fds;           /* select() file descriptor mask             */
    int     fd_limit;           /* select() file descriptor limit            */
    int     need_timeout;       /* Boolean - select() timeout needed         */
    int     rc;                 /* Return code                               */

    struct timeval  timeout_value = {15, 0};    /* Fifteen second timeout    */


    setup_signals();            /* Initialize signal dispositions            */

    start_session(&sess1);      /* Start EMAPI session for 1st SP partition  */
    start_session(&sess2);      /* Start EMAPI session for 2nd SP partition  */

    /*
     *  Register for events in both EMAPI sessions that will monitor the
     *  programs of interest.  Maintain count of registered events.
     */
    
    reg_cnt = 0;
    register_for_events(&sess1, progs1, progs1_elems, &reg_cnt);
    register_for_events(&sess2, progs2, progs2_elems, &reg_cnt);

    /*
     *  Continue looking for Event Manager responses as long as there are
     *  registered events.
     */
    
    while (reg_cnt > 0) {

        /*
         *  If program termination has been requested, and unregistration
         *  requests have not been sent, send them now.
         */
        
        if (terminate_requested && !unregistrations_requested) {
            printf("Termination requested.\n");
            unregister_events(&sess1, progs1, progs1_elems);
            unregister_events(&sess2, progs2, progs2_elems);
            unregistrations_requested = 1;
        }

        /*
         *  Construct the parameters to be passed to select(), and then
         *  call select() to wait for Event Manager responses.
         */

        FD_ZERO(&read_fds); fd_limit = 0; need_timeout = 0;
        select_session(&sess1, &read_fds, &fd_limit, &need_timeout);
        select_session(&sess2, &read_fds, &fd_limit, &need_timeout);

        rc = select(fd_limit + 1, &read_fds, NULL, NULL,
                                  need_timeout ? &timeout_value : NULL);

        if (rc == -1) {                 /* select() indicates error          */
            if (errno == EINTR) {       /* select() interrupted by signal    */
                continue;               /* Return to top of loop             */
            }
            perror("select()");         /* select() really encountered error */
            exit(1);                    /* End program                       */
        }

        /*
         *  If an Event Manager response is present for the 1st EMAPI session,
         *  process it.  Also, maintain the count of registered events.
         */
        
        if (session_response_ready(&sess1, &read_fds)) {
            process_response(&sess1, progs1, progs1_elems, &reg_cnt);
        }

        /*
         *  If an Event Manager response is present for the 2nd EMAPI session,
         *  process it.  Also, maintain the count of registered events.
         */
        
        if (session_response_ready(&sess2, &read_fds)) {
            process_response(&sess2, progs2, progs2_elems, &reg_cnt);
        }
        
    }

    end_session(&sess1);        /* End EMAPI session for 1st SP partition    */
    end_session(&sess2);        /* End EMAPI session for 2nd SP partition    */

    return 0;                   /* Indicate program was successful           */
}


/*
 *  The interrupt_catch() function is installed as the signal handler for the
 *  SIGINT signal.  The SIGINT signal is delivered to this process when the
 *  user presses the interrupt key (usually Ctrl-C).  When this routine is
 *  invoked, it sets the global variable terminate_requested to a non-zero
 *  value, indicating the program is to be terminated.
 */

static
void interrupt_catch(int signo)
{
    terminate_requested = 1;
    return;
}


/*
 *  The setup_signals() function initializes the disposition of a couple of
 *  signals.
 *
 *  The disposition of the SIGPIPE signal is set such that the signal is
 *  ignored.  When a process writes to a pipe or connection-oriented socket for
 *  which there is no reader, the SIGPIPE signal is delivered to the process.
 *  The default disposition for SIGPIPE is to kill the receiving process.  If
 *  SIGPIPE is ignored, the SIGPIPE signal does not kill the process, and the
 *  system call used to write to the pipe or connection-oriented socket sets
 *  errno to EPIPE and returns an error indication.  It is recommended that
 *  clients of the EMAPI ignore the SIGPIPE signal, since the EMAPI uses
 *  connection-oriented sockets to communicate with the Event Manager.
 *
 *  The interrupt_catch() function is installed as the signal handler for the
 *  SIGINT signal.  See the comments pertaining to the interrupt_catch()
 *  function to find out why this is done.
 */
 
static
void setup_signals(void)
{
    struct sigaction    sa;


    sa.sa_handler = SIG_IGN;                /* SIGPIPE to be ignored         */
    sigemptyset(&sa.sa_mask);               /* No signals to mask off        */
    sa.sa_flags = 0;                        /* No flags needed               */

    if (sigaction(SIGPIPE, &sa, NULL) == -1) {  /* Change SIGPIPE disposition*/
        perror("sigaction(SIGPIPE)");          
        exit(1);
    }

    sa.sa_handler = interrupt_catch;        /* SIGINT signal handler         */
    sigemptyset(&sa.sa_mask);               /* No signals to mask off        */
    sa.sa_flags = SA_RESTART;               /* Restart interrupted syscalls  */

    if (sigaction(SIGINT, &sa, NULL) == -1) {   /* Change SIGINT disposition */
        perror("sigaction(SIGINT)");
        exit(1);
    }

    return;
}


/*
 *  The start_session() function establishes a session with the EMAPI by
 *  calling the EMAPI routine ha_em_start_session().  If a session cannot be
 *  established, the program is terminated.
 */

static
void start_session(struct session *sess_p)
{
    struct ha_em_err_blk errb;                  /* EMAPI error block         */


    /*
     *  The ha_em_start_session() routine receives the name of the partition
     *  with which a session is to be established, and returns a file
     *  descriptor with which the session can be used.
     */
    
    sess_p->fd = ha_em_start_session(HA_EM_DOMAIN_SP, sess_p->name, &errb);

    /*
     *  If ha_em_start_session() indicates a session could not be established,
     *  print the error information returned by the routine, and exit the
     *  program.
     */
    
    if (sess_p->fd == -1) {
        fprintf(stderr, "%s:\tha_em_start_session() returned EM errno %d:\n%s",
                sess_p->name, errb.em_errno, errb.em_errmsg);
        exit(1);
    }

    /*
     *  A session has been established with the EMAPI, and the session's file
     *  descriptor has been saved in the session structure passed to this
     *  routine.  Set the flag in the session structure that indicates the
     *  session does not need to be restarted.
     */
    
    sess_p->restart = 0;

    return;
}


/*
 *  The register_for_events() function uses the EMAPI routine
 *  ha_em_send_command() to request the registration of events pertaining to
 *  programs of interest that are in the same SP partition.
 *
 *  The resource variable name specified is "IBM.PSSP.Prog.xpcount".  This is a
 *  state variable whose value indicates whether or not processes are executing
 *  a specific program.  The resource ID for this resource variable
 *  specifies the program name, the name of the user running the program, and
 *  the node on which the program is running.  The resource variable's value
 *  is a structured byte string of three fields.  Field 0 is of type long; its
 *  value is the number of processes currently running the specified program
 *  for the specified user on the specified node.  Field 1 is also of type
 *  long; its value is the previous number of processes running the program.
 *  Field 2 is of type character string; its value is a comma separated list
 *  of the PIDs of the processes running the program.
 *
 *  When the events are registered, two expressions are specified.  The primary
 *  expression, "X@0 == 0", specifies that an event is to be generated when
 *  field 0 of the resource variable's structured byte string has a value of 0.
 *  Taking into account the semantics of the resource variable, the expression
 *  causes an event to be generated when no processes are running the specified
 *  program for the specified user on the specified node.  The re-arm
 *  expression, "X@0 > 0", specifies that after the primary expression has
 *  generated an event, the primary expression is to be re-armed once a process
 *  starts running the specified program for the specified user on the
 *  specified node.  Since the HA_EM_CMD_REG2 command is specified, the re-arm
 *  expression will generate events.
 *
 *  Notice that when events are registered, the HA_EM_SCMD_REVAL subcommand is
 *  specified.  As a result, the initial value of the programs being monitored
 *  will be returned.
 */

static
void register_for_events(struct session *sess_p,
        struct program *progs_p, int progs_elems, int *reg_cnt_p)
{
    int                     num_events;      /* Number of events to register */
    size_t                  alloc_size;      /* Size of memory to allocate   */
    struct ha_em_cmd_blk    *cmd_blk;        /* Pointer to command block     */
    struct ha_em_rb_reg     *re;             /* Pointer to registered event  */
    int                     i;               /* Index                        */
    struct ha_em_err_blk    errb;            /* EMAPI error block            */
    int                     rc;              /* Return code                  */
    char                    rsrc_ID_buf[80]; /* Resource ID buffer           */
    struct callback_data    *cd_p;           /* Pointer to callback data     */

    
    num_events = progs_elems;           /* Register one event per program    */

    /*
     *  Allocate enough memory to hold the command block to be given to the
     *  EMAPI.  The ha_em_cmd_blk structure contains one ha_em_rb_reg
     *  structure.  Additional space must be allocated when more than one
     *  event is to be registered.
     */

    alloc_size = sizeof(struct ha_em_cmd_blk) +
                     ((num_events - 1) * sizeof(struct ha_em_rb_reg));

    if ((cmd_blk = malloc(alloc_size)) == NULL) {
        perror("malloc()");
        exit(1);
    }

    /*
     *  Fill in the em_rb_reg array entries.  There is one entry per event to
     *  be registered.
     */
    
    for (i = 0; i < num_events; i++) {

        re = &cmd_blk->em_res_blk.em_rb_reg[i]; 

        re->em_name = "IBM.PSSP.Prog.xpcount";   /* Resource variable name   */

        sprintf(rsrc_ID_buf, "ProgName=%s;UserName=%s;NodeNum=%d",
                progs_p[i].name, progs_p[i].user, progs_p[i].node);
        re->em_rsrc_ID = strdup(rsrc_ID_buf);    /* Resource ID              */

        if (re->em_rsrc_ID == NULL) {
            perror("strdup()");
            exit(1);
        }

        re->em_expr   = "X@0 == 0";              /* Expression               */
        re->em_raexpr = "X@0 > 0";               /* Re-arm expression        */

        if ((cd_p = malloc(sizeof(struct callback_data))) == NULL) {
            perror("malloc()");
            exit(1);
        }

        cd_p->sess_p    = sess_p;        /* Callback gets session pointer    */
        cd_p->prog_p    = &progs_p[i];   /* Callback gets program pointer    */
        cd_p->reg_cnt_p = reg_cnt_p;     /* Callback gets reg. count pointer */
        
        re->em_cb     = event_callback;  /* Callback routine                 */
        re->em_cb_arg = cd_p;            /* Callback routine args.           */

        progs_p[i].cb_data = cd_p;       /* Save pointer to callback data    */
    }

    /*
     *  Fill in command block header, specifying number of elements in
     *  em_rb_reg array, command, and subcommand.
     */
    
    cmd_blk->em_cmd_num_elem = num_events;
    cmd_blk->em_cmd          = HA_EM_CMD_REG2;
    cmd_blk->em_subcmd       = HA_EM_SCMD_REVAL;

    /*
     *  Send the command.  A more elaborate program might check for the
     *  HA_EM_ECONNLOST Event Manager error number when an error is
     *  returned, and attempt to restart the session.  But, this program
     *  just terminates if ha_em_send_command() detects a lost connection.
     */
    
    rc = ha_em_send_command(sess_p->fd, cmd_blk, &errb);

    if (rc == -1) {
        fprintf(stderr, "%s:\tha_em_send_command() returned EM errno %d:\n%s",
                sess_p->name, errb.em_errno, errb.em_errmsg);
        exit(1);
    }

    /*
     *  Save the event identifiers assigned to the events just registered.
     */
    
    for (i = 0; i < num_events; i++) {
        re = &cmd_blk->em_res_blk.em_rb_reg[i]; 
        progs_p[i].eid     = re->em_event_id; 
        progs_p[i].unreged = 0; 
        free(re->em_rsrc_ID);
    }

    free(cmd_blk);

    *reg_cnt_p += num_events;           /* Update count of registered events */

    return;
}


/*
 *  The unregister_events() function uses the EMAPI routine
 *  ha_em_send_command() to request the unregistration of events pertaining to
 *  programs of interest that are in the same SP partition.
 */

static
void unregister_events(struct session *sess_p,
        struct program *progs_p, int progs_elems)
{
    int                     num_events;      /* Number of events to unreg.   */
    size_t                  alloc_size;      /* Size of memory to allocate   */
    struct ha_em_cmd_blk    *cmd_blk;        /* Pointer to command block     */
    ha_em_eid_t             *re;             /* Pointer to unreg. event      */
    int                     i;               /* Index                        */
    struct ha_em_err_blk    errb;            /* EMAPI error block            */
    int                     rc;              /* Return code                  */


    /*
     *  Allocate enough memory to hold the command block to be given to the
     *  EMAPI.  The ha_em_cmd_blk structure contains one ha_em_eid_t
     *  element.  Additional space must be allocated when more than one
     *  event is to be unregistered.
     */

    alloc_size = sizeof(struct ha_em_cmd_blk) +
                     ((progs_elems - 1) * sizeof(ha_em_eid_t));

    if ((cmd_blk = malloc(alloc_size)) == NULL) {
        perror("malloc()");
        exit(1);
    }

    /*
     *  Fill in the event identifier array entries.  There is one entry per
     *  event to be unregistered.  Only unregister events that are not already
     *  unregistered.
     */
    
    num_events = 0;

    for (i = 0; i < progs_elems; i++) {
        if (!progs_p[i].unreged) {
            re  = &cmd_blk->em_res_blk.em_rb_unreg[num_events]; 
            *re = progs_p[i].eid; 
            num_events++;
        }
    }

    /*
     *  Fill in command block header, specifying the number of elements in
     *  the event identifier array, and the unregister command.
     */

    cmd_blk->em_cmd_num_elem = num_events;
    cmd_blk->em_cmd          = HA_EM_CMD_UNREG;
    
    /*
     *  Send the unregister command.  A more elaborate program might check for
     *  the HA_EM_ECONNLOST Event Manager error number when an error is
     *  returned, and attempt to restart the session.  But, this program
     *  just terminates if ha_em_send_command() detects a lost connection.
     */

    rc = ha_em_send_command(sess_p->fd, cmd_blk, &errb);

    if (rc == -1) {
        fprintf(stderr, "%s:\tha_em_send_command() returned EM errno %d:\n%s",
                sess_p->name, errb.em_errno, errb.em_errmsg);
        exit(1);
    }

    free(cmd_blk);

    return;
}


/*
 *  The select_session() routine determines how the specified session affects
 *  the next call to select().  If the session is marked as needing to be
 *  restarted, ha_em_restart_session() is called to try to restart the session.
 *  If the restart attempt fails, the next call to select() should specify
 *  a timeout period - so the restart can be attempted again.  If the restart
 *  attempt succeeds, the new session file descriptor is saved, and the session
 *  is marked as not needing to be restarted.  If the session did not need
 *  to be restarted, or had been successfully restarted, the next call to
 *  select() should examine the session's file descriptor.
 */

static
void select_session(struct session *sess_p,
        fd_set *read_fds_p, int *fd_limit_p, int *timeout_p)
{
    struct ha_em_err_blk    errb;           /* EMAPI error block             */
    int                     new_fd;         /* New session file descriptor   */


    /*
     *  If the specified session needs to be restarted, try to do so.
     */
    
    if (sess_p->restart) {

        new_fd = ha_em_restart_session(sess_p->fd, &errb);

        if (new_fd == -1) {

            /*
             *  The session could not be restarted.  If the Event Manager
             *  error number indicates connection refused, indicate the
             *  next select() should specify a timeout value (so the restart
             *  can be tried again later), and return without putting the
             *  session's file descriptor in the select() file descriptor
             *  mask.
             */
            
            if (errb.em_errno == HA_EM_ECONNREFUSED) {
                *timeout_p = 1;
                return;
            }

            /*
             *  If execution reaches here, the session restart attempt failed
             *  for an unexpected reason; terminate the program.
             */
            
            fprintf(stderr, "%s:\tha_em_restart_session() returned EM errno "
                    "%d:\n%s", sess_p->name, errb.em_errno, errb.em_errmsg);
            exit(1);
        }

        /*
         *  If execution reaches here, a session restart was attempted and
         *  was successful.  Save the new session file descriptor, and indicate
         *  that the session no longer needs to be restarted.
         */
        
        sess_p->fd = new_fd;
        sess_p->restart = 0;

    }

    /*
     *  If execution reaches here, the specified session is not known to be
     *  in need of a restart.  Put the session's file descriptor in the
     *  select() file descriptor mask.  Also, adjust the maximum file
     *  descriptor to select, if appropriate.
     */
    
    FD_SET(sess_p->fd, read_fds_p);
    *fd_limit_p = MAX(sess_p->fd, *fd_limit_p);

    return;
}


/*
 *  The session_response_ready() function determines if a select() file
 *  descriptor mask indicates that a session's file descriptor is ready for
 *  reading.
 */

static
int session_response_ready(struct session *sess_p, fd_set *read_fds_p)
{
        return FD_ISSET(sess_p->fd, read_fds_p);
}


/*
 *  The process_response() function processes an Event Manager response
 *  in the specified EMAPI session.  First, ha_em_receive_response() is called
 *  to receive the response.  If ha_em_receive_response() indicates that the
 *  session's connection with the Event Manager has been lost, the session
 *  is marked as being in need of a restart.  Any other error will terminate
 *  the program.  The ha_em_receive_response() function may indicate that
 *  there really isn't any response for the EMAPI client to process at this
 *  time.  In that case, this function just returns.  If a response has
 *  been received, it is processed depending on what type of response it is.
 */

static
void process_response(struct session *sess_p,
         struct program *progs_p, int progs_elems, int *reg_cnt_p)
{
    struct ha_em_rsp_blk    *rsp_blk;   /* Pointer to the response block     */
    int                     rc;         /* Return code                       */
    struct ha_em_err_blk    errb;       /* Event Manager error block         */
    int                     i;          /* Index                             */


    /*
     *  Receive response from session, if there is one to receive.
     */
    
    rc = ha_em_receive_response(sess_p->fd, &rsp_blk, &errb);

    if (rc == -1) {

        /*
         *  If the connection with the Event Manager has been lost,
         *  mark the session as needing to be restarted, and return.
         */
        
        if (errb.em_errno == HA_EM_ECONNLOST) {
            printf("%s:\tconnection to session lost.\n", sess_p->name);
            sess_p->restart = 1;
            return;
        }

        /*
         *  Some error has occurred other than the loss of the connection.
         *  Terminate the program.
         */
        
        fprintf(stderr, "%s:\tha_em_receive_response() returned EM errno %d:\n"
                "%s", sess_p->name, errb.em_errno, errb.em_errmsg);
        exit(1);

    }

    /*
     *  If the ha_em_receive_response() routine returned zero,
     *  there is no response for the EMAPI client (this program) to process
     *  at this time.  The response may have been for the EMAPI itself,
     *  a full response may not be available yet, or the response may have
     *  been handled with the callback routine.  Just return.
     */
    
    if (rc == 0) {
        return;
    }

    /*
     *  A response for the EMAPI client (this program) has been returned.
     *  The response buffer is pointed to by the rsp_blk variable.  Appropriate
     *  processing for the response depends on the command type.  Note that
     *  since all event registrations in this program specify the use of a
     *  callback routine, event responses and event unregistration responses
     *  are not expected by this function.
     */
    
    switch (rsp_blk->em_cmd) {

        case HA_EM_CMD_RERR:            /* REG  event registration error     */
        case HA_EM_CMD_R2ERR:           /* REG2 event registration error     */
            process_response_rerr(sess_p, progs_p, progs_elems, rsp_blk,
                                  reg_cnt_p);
            break;

        default:                        /* Unexpected response               */
            fprintf(stderr, "%s:\tProgram received unexpected command "
                    "response: %d.\n", sess_p->name, rsp_blk->em_cmd);
            exit(1);
            break;

    }

    /*
     *  The EMAPI client (this program) must free the memory associated with
     *  the returned response block when it is no longer needed.
     */
    
    free(rsp_blk);

    return;
}


/*
 *  The process_response_rerr() function processes registration error
 *  responses.  When an event cannot be registered due to some detected error,
 *  a registration error response is sent to the EMAPI client.  This function
 *  marks the event, as tracked in a program structure, as being unregistered.
 *  This will prevent an attempt to unregister a non-registered event later
 *  in the program.
 */

static
void process_response_rerr(struct session *sess_p,
        struct program *progs_p, int progs_elems,
        struct ha_em_rsp_blk *rsp_blk, int *reg_cnt_p)
{
    struct ha_em_rpb_rerr   *error_p;           /* Pointer to error response */
    struct ha_em_rpb_rerr   *last_error_p;      /* Beyond last error response*/
    struct program          *prog_p;            /* Pointer to program struct */

    char prog_name[80];         /* Name of program to which error pertains   */
    char user_name[80];         /* Name of user to which error pertains      */
    int  node;                  /* Node to which error pertains              */

    
    error_p = rsp_blk->em_resp_blk.em_rpb_rerr;
    last_error_p = error_p + rsp_blk->em_rsp_num_resp;

    for ( ; error_p < last_error_p; error_p++) {

        /*
         *  Find the program structure which describes the program associated
         *  with the event whose registration failed.  The match is made
         *  with event identifiers.
         */
        
        for (prog_p = progs_p; prog_p < progs_p + progs_elems; prog_p++) {
            if (prog_p->eid == error_p->em_event_id) {
                break;
            }
        }

        if (prog_p == progs_p + progs_elems) {
            fprintf(stderr, "%s:\tUnknown event identifier encountered (0x%x)."
                    "\n", sess_p->name, error_p->em_event_id);
            exit(1);
        }

        /*
         *  Extract the program name, user name, and node number from the
         *  resource ID.
         */
        
        breakdown_rsrc_ID(error_p->em_rsrc_ID, prog_name, user_name, &node);

        /*
         *  Print a message about the registration error.
         */
        
        printf("%s:\tRegistration error for %s run by %s on node %d (%d, %d)."
               "\n", sess_p->name, prog_name, user_name, node,
               error_p->em_generr, error_p->em_specerr);

        /*
         *  Mark the program as not having an associated event registered.
         */
        
        prog_p->unreged = 1;

        /*
         *  Free the callback data allocated for this program.
         */

        free(prog_p->cb_data);
        prog_p->cb_data = NULL;

    }

    /*
     *  Update the number of events that are registered.
     */
    
    *reg_cnt_p -= rsp_blk->em_rsp_num_resp;

    return;
}


/*
 *  The event_callback() function is called whenever an event response has been
 *  received from the Event Manager.  Several points should be kept in mind:
 *
 *      - The event response may indicate that an unregistration request has
 *        been completed.  The HA_EM_EVENT_UNREG flag indicates the event
 *        response is actually an unregistration response.
 *
 *      - An event response may be an error response.  An event error response
 *        does not indicate that a registered event has occurred.  Instead, it
 *        indicates the Event Manager no longer knows the current value of
 *        the associated resource variable, and cannot generate events for
 *        the resource variable.  This may be a temporary condition.  If the
 *        Event Manager later obtains the current value of the associated
 *        resource variable, an event will be generated (possibly indicating
 *        that the expression is false).
 *
 *      - The event for which an event error response is generated is still
 *        registered.  There is no need to re-register the event.
 *
 *      - Since events are registered by this program using the HA_EM_CMD_REG2
 *        command, the re-arm expressions will generate events.  Therefore, the
 *        HA_EM_EVENT_RE_ARM flag must be tested to see if the expression or
 *        the re-arm expression generated the event.
 *
 *      - It is possible for an event to be delivered indicating the expression
 *        is false.  This can happen for two reasons.  First, when events are
 *        registered by this program, the HA_EM_SCMD_REVAL subcommand is
 *        specified.  That subcommand requests the initial value of the
 *        associated resource variable.  Second, the current value of the
 *        associated resource variable is returned through an event once the
 *        Event Manager obtains the current value of a resource variable after
 *        an error has occurred.
 *
 *      - If an event response does not have the HA_EM_EVENT_RE_ARM nor
 *        HA_EM_EVENT_EXPR_FALSE flags set, the event response indicates that
 *        the event's expression is true.
 */

static
void event_callback(int sess_fd, struct ha_em_rpb_event *event_p, void *arg)
{
    struct callback_data *cd_p; /* Callback routine data pointer             */
    struct session  *sess_p;    /* Session structure pointer                 */
    struct program  *prog_p;    /* Program structure pointer                 */
    int             *reg_cnt_p; /* Registration counter pointer              */
    
    char time_stamp[80];        /* Formatted event time stamp                */


    /*
     *  Get the callback routine's arguments.
     */
    
    cd_p = (struct callback_data *)arg;
    
    sess_p    = cd_p->sess_p;
    prog_p    = cd_p->prog_p;
    reg_cnt_p = cd_p->reg_cnt_p;

    /*
     *  Format the time stamp.
     */
        
    format_timestamp(&event_p->em_timestamp, time_stamp);
        
    if (event_p->em_event_flags & HA_EM_EVENT_UNREG) {
        
        /*
         *  If execution reaches this point, the event indicates an event
         *  unregistration has completed.
         *  Print a message indicating the program associated with the
         *  unregistered event is no longer being monitored.
         */
        
        printf("%s:\t%s no longer monitoring %s run by %s on node %d.\n",
               sess_p->name, time_stamp, prog_p->name, prog_p->user,
               prog_p->node);

        /*
         *  Note: em_errnum could indicate an error, but there is no point
         *        looking at it here, since the program would not do anything
         *        differently.
         */

        /*
         *  Mark the program as not having an associated event registered.
         */

        prog_p->unreged = 1;

        /*
         *  Indicate this program has one fewer registered event.
         */

        (*reg_cnt_p)--;

        /*
         *  The callback routine will no longer be called about this event,
         *  so free the data that was allocated specifically for this event.
         */
        
        free(prog_p->cb_data);
        prog_p->cb_data = NULL;

        return;
    }

    /*
     *  If execution reaches this point, the event response is not an
     *  unregistration response.
     */
    
     /*
      *  If this is an event error response, print a message which includes
      *  the general and specific error codes.
      */

     if (event_p->em_errnum != 0) {
         printf("%s:\t%s State of %s run by %s on node %d unknown (%d, %d).\n",
                sess_p->name, time_stamp, prog_p->name, prog_p->user,
                prog_p->node, event_p->em_generr, event_p->em_specerr);
         return;
     }

     /*
      *  If the event response was generated for the re-arm expression, or
      *  the event response was generated for the primary expression but
      *  it is false, print a message indicating the program associated
      *  with the event is being run.
      */

     if ((event_p->em_event_flags & HA_EM_EVENT_RE_ARM)     ||
         (event_p->em_event_flags & HA_EM_EVENT_EXPR_FALSE)) {
         printf("%s:\t%s %s being run by %s on node %d.\n",
                sess_p->name, time_stamp, prog_p->name, prog_p->user,
                prog_p->node);
         return;
     }

     /*
      *  If execution reaches this point, the event response was generated
      *  for the primary expression, and it is true.  Print a message
      *  indicating the program associated with the event is not being run.
      */

     printf("%s:\t%s %s NOT being run by %s on node %d.\n",
            sess_p->name, time_stamp, prog_p->name, prog_p->user,
            prog_p->node);

    return;
}


/*
 *  The format_timestamp() function takes a time stamp returned by the
 *  Event Manager and converts it into 24-hour time.
 */

static
void format_timestamp(struct timeval *timestamp_p, char *fmt_timestamp_p)
{
    struct tm   *broken_down_time;


    broken_down_time = localtime((time_t *) &timestamp_p->tv_sec);
    (void) strftime(fmt_timestamp_p, 20, "(%X)", broken_down_time);

    return;
}


/*
 *  The breakdown_rsrc_ID() function takes a resource ID
 *  associated with the IBM.PSSP.Prog.xpcount resource variable and extracts
 *  from it the program name, user name, and node number.
 */

static
void breakdown_rsrc_ID(char *rsrc_ID_p,
        char *prog_name_p, char *user_name_p, int *node_p)
{
    char    *value_p;
    size_t  value_len;


    find_rsrc_ID_value(rsrc_ID_p, "ProgName=", &value_p, &value_len);
    strncpy(prog_name_p, value_p, value_len);
    *(prog_name_p + value_len) = '\0';

    find_rsrc_ID_value(rsrc_ID_p, "UserName=", &value_p, &value_len);
    strncpy(user_name_p, value_p, value_len);
    *(user_name_p + value_len) = '\0';

    find_rsrc_ID_value(rsrc_ID_p, "NodeNum=", &value_p, &value_len);
    *node_p = atoi(value_p);

    return;
}


/*
 *  The find_rsrc_ID_value() takes a resource ID and extracts
 *  a value from it.
 */

static
void find_rsrc_ID_value(char *rsrc_ID_p, char *name_p,
        char **value_pp, size_t *value_len_p)
{
    char    *bp;
    char    *ep;
    size_t  len;


    if ((bp = strstr(rsrc_ID_p, name_p)) == NULL) {
        fprintf(stderr, "Unexpected resource ID encountered.\n");
        exit(1);
    }

    bp += strlen(name_p);

    if ((ep = strchr(bp, ';')) != NULL) {
        len = ep - bp;
    } else {
        len = strlen(bp);
    }

    *value_pp    = bp;
    *value_len_p = len;

    return;
}


/*
 *  The end_session() function terminates a session with the EMAPI by
 *  calling the EMAPI routine ha_em_end_session().
 */

static
void end_session(struct session *sess_p)
{
    struct ha_em_err_blk errb;


    if (ha_em_end_session(sess_p->fd, &errb) == -1) {
        fprintf(stderr, "%s:\tha_em_end_session() returned EM errno %d:\n%s",
                sess_p->name, errb.em_errno, errb.em_errmsg);
        exit(1);
    }

    return;
}