/* 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                                                     */

/* @(#)45   1.3   src/rsct/pem/emtools/emapi_test/emapi_ex_r/emapi_v02_ex03.c, emtools, rsct_rady, rady2035a 6/30/98 10:48:47 */

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

#define  HA_EM_VERSION		2
#include <ha_emapi.h>

/*
 *  emapi_v02_ex03.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.  Each session is managed by a separate thread.
 *
 *  This program can be compiled with the following command:
 *
 *      cc_r -O emapi_v02_ex03.c -o emapi_v02_ex03 -lha_em_r -lpthreads -lc_r
 */

/*
 *  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. */

/*
 *  The STRERROR_BUF and STRERROR macros make it a little more convenient to
 *  use the thread safe version of strerror(), strerror_r().
 */

#define STRERROR_BUF    char strerror_buf[256]

#define STRERROR(en) (                                                      \
    strerror_r(en, strerror_buf, sizeof strerror_buf) == 0                  \
        ? strerror_buf : "Unknown error"                                    \
)   

/*
 *  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                         */
};

/*
 *  The thread_data structure is used by the initial thread to pass data to
 *  a secondary thread when it is created.
 */

struct thread_data {
    struct session  *sess;      /* EMAPI session the thread is to establish  */
    struct program  *progs;     /* Programs the thread is to monitor         */
    int              progs_cnt; /* Number of programs thread is to monitor   */
};

/*
 *  Following are handles for threads created by the initial thread.
 */

pthread_t  thread1;             /* Handle to thread 1 - a program monitor    */
pthread_t  thread2;             /* Handle to thread 2 - a program monitor    */
pthread_t  thread3;             /* Handle to thread 3 - cancellation monitor */

/*
 *  Function prototypes for internal functions.
 */

static void setup_signals(void);
static void create_thread(pthread_t *thread_p, void *(* thread_rtn)(void *),
                          void *data_p);
static void cancel_thread(pthread_t thread);
static void wait_for_thread(pthread_t thread);
static void *program_thread_main(void *parm_p);
static void program_thread_main_cleanup(void *parm_p);
static void *cancel_thread_main(void *parm_p);
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 ensure_session_connected(struct session *sess_p);
static void process_response(struct session *sess_p,
         struct program *progs_p, int progs_elems, int *reg_cnt_p);
static void process_response_reg(struct session *sess_p,
        struct program *progs_p, int progs_elems,
        struct ha_em_rsp_blk *rsp_blk);
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 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 is executed by this program's initial thread.
 *  The function creates three other threads.  The first two threads
 *  use the EMAPI to monitor other programs.  One thread is used per
 *  EMAPI session, and one EMAPI session is used per SP partition.
 *  The third thread waits for the user to request the termination of this
 *  program.  When the third thread detects the user would like this program
 *  to terminate, it cancels the two threads using the EMAPI.  When those
 *  two threads terminate, the initial thread cancels the third thread, waits
 *  for the third thread to terminate, and terminates the program.
 */

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},
                               {PROG_1B, USER_1B, NODE_1B, 0, 0}};
    struct program progs2[] = {{PROG_2A, USER_2A, NODE_2A, 0, 0},
                               {PROG_2B, USER_2B, NODE_2B, 0, 0}};

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

    struct thread_data td1 = {&sess1, progs1, progs1_elems};
    struct thread_data td2 = {&sess2, progs2, progs2_elems};

    
    setup_signals();                    /* Initialize signal dispositions    */

    /*
     *  Create 2 threads that will monitor programs with the EMAPI.
     */
    
    create_thread(&thread1, program_thread_main, &td1);  /* Create thread 1  */
    create_thread(&thread2, program_thread_main, &td2);  /* Create thread 2  */

    /*
     *  Create a thread that will wait for the user to request termination.
     */
    
    create_thread(&thread3, cancel_thread_main,  NULL);  /* Create thread 3  */

    /*
     *  Wait for the 2 threads that are monitoring programs to end.  They
     *  will end due to errors or they will be canceled by thread 3 when
     *  the user requests termination.
     */
    
    wait_for_thread(thread1);           /* Wait for thread 1 to terminate    */
    wait_for_thread(thread2);           /* Wait for thread 2 to terminate    */

    /*
     *  Cancel the thread waiting for the user to request termination, and
     *  then wait for that thread to terminate.
     */
    
    cancel_thread(thread3);             /* Cancel thread 3                   */
    wait_for_thread(thread3);           /* Wait for thread 3 to terminate    */

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


/*
 *  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 SIGINT signal is blocked in the initial thread's signal mask.
 *  Threads created by the initial thread will inherit the signal mask from
 *  the initial thread.  One thread will wait for the SIGINT signal
 *  to be delivered by using sigwait().
 */
 
static
void setup_signals(void)
{
    struct sigaction    sa;             /* Signal action description         */
    int                 rc;             /* Return code (error number)        */
    STRERROR_BUF;                       /* Buffer for strerror_r()           */
    sigset_t            sigset;         /* Signal mask to block SIGINT       */


    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);
    }

    sigemptyset(&sigset);               /* Initialize signal set             */
    sigaddset(&sigset, SIGINT);         /* Add SIGINT to signal set          */

    rc = sigthreadmask(SIG_BLOCK, &sigset, NULL);  /* Block SIGINT signal    */
    if (rc != 0) {
        fprintf(stderr, "sigthreadmask(): %s\n", STRERROR(rc));
        exit(1);
    }

    return;
}


/*
 *  The create_thread() function creates a thread, passing along the specified
 *  main function address and parameter address.  The function returns the
 *  thread's handle.  The thread is created such that it can be joined later
 *  on.
 */

static
void create_thread(pthread_t *thread_p, void *(* thread_rtn)(void *),
                   void *data_p)
{
    pthread_attr_t  thread_attr;        /* Thread attributes                 */
    int             rc;                 /* Return code (error number)        */
    STRERROR_BUF;                       /* Buffer for strerror_r()           */


    rc = pthread_attr_init(&thread_attr);
    if (rc != 0) {
        fprintf(stderr, "pthread_attr_init(): %s\n", STRERROR(rc));
        exit(1);
    }
    
    rc = pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_UNDETACHED);
    if (rc != 0) {
        fprintf(stderr, "pthread_attr_setdetachstate(): %s\n",
                STRERROR(rc));
        exit(1);
    }
    
    rc = pthread_create(thread_p, &thread_attr, thread_rtn, data_p);
    if (rc != 0) {
        fprintf(stderr, "pthread_create(): %s\n", STRERROR(rc));
        exit(1);
    }
        
    rc = pthread_attr_destroy(&thread_attr);
    if (rc != 0) {
        fprintf(stderr, "pthread_attr_destroy(): %s\n", STRERROR(rc));
        exit(1);
    }

    return;
}


/*
 *  The cancel_thread() function requests the cancellation of the specified
 *  thread.
 */

static
void cancel_thread(pthread_t thread)
{
    int             rc;                 /* Return code (error number)        */
    STRERROR_BUF;                       /* Buffer for strerror_r()           */


    rc = pthread_cancel(thread);
    if (rc != 0) {
        fprintf(stderr, "pthread_cancel(): %s\n", STRERROR(rc));
        exit(1);
    }

    return;
}


/*
 *  The wait_for_thread() function waits for the specified thread to
 *  terminate.
 */

static
void wait_for_thread(pthread_t thread)
{
    int             rc;                 /* Return code (error number)        */
    STRERROR_BUF;                       /* Buffer for strerror_r()           */


    rc = pthread_join(thread, NULL);
    if (rc != 0) {
        fprintf(stderr, "pthread_join(): %s\n", STRERROR(rc));
        exit(1);
    }

    return;
}


/*
 *  The program_thread_main() function is the main function for threads that
 *  are created to monitor programs in a SP partition.  The function is told
 *  what programs are to be monitored in what partition through the function
 *  parameter, which is a pointer to a thread_data structure.  The function
 *  calls functions to establish an EMAPI session, register for events, wait
 *  for process Event Manager responses, and cleanup when the thread is
 *  canceled.  The thread cleanup function that is installed ends the EMAPI
 *  session that was established for the thread.
 */

static
void *program_thread_main(void *parm_p)
{
    struct thread_data *td;     /* Thread data (parameters)                  */
    int     reg_cnt;            /* Count of registered events                */
    int     rc;                 /* Return code                               */


    td = (struct thread_data *) parm_p; /* Get parameters for the thread     */

    start_session(td->sess);    /* Start EMAPI session for SP partition      */

    /*
     *  Install a clean up handler that will end the session when the thread
     *  is canceled.
     */
    
    pthread_cleanup_push(program_thread_main_cleanup, (void *)td->sess);

    /*
     *  Register for events that will monitor the programs of interest.
     *  Maintain count of registered events.
     */
    
    reg_cnt = 0;
    register_for_events(td->sess, td->progs, td->progs_cnt, &reg_cnt);

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

        /*
         *  Ensure the session is still connected to the Event Manager.
         */
        
        ensure_session_connected(td->sess);
        
        /*
         *  Process the next response from the Event Manager.
         */
        
        process_response(td->sess, td->progs, td->progs_cnt, &reg_cnt);
        
    }

    /*
     *  Pop and execute the thread cleanup handler installed by this routine.
     *  Note that the cleanup handler will end the EMAPI session.
     */
    
    pthread_cleanup_pop(1);

    return NULL;               /* The thread is finished.                   */
}


/*
 *  The program_thread_main_cleanup() function is the thread cancellation
 *  cleanup handler for the program_thread_main() function.  When this
 *  function is installed as a thread cancellation cleanup handler, the
 *  thread has an EMAPI session.  This function ends that session.  The
 *  function receives as input a pointer to the session structure describing
 *  the session.
 */

static
void program_thread_main_cleanup(void *parm_p)
{
    struct session  *sess_p;


    sess_p = (struct session *) parm_p;
    
    end_session(sess_p);
        
    printf("%s:\tno longer monitoring programs in this partition.\n",
           sess_p->name);
    
    return;
}


/*
 *  The cancel_thread_main() function waits for the user to request
 *  the termination of this program.  The user requests program termination
 *  by pressing the interrupt key (usually Ctrl-C).  Pressing the interrupt
 *  key will cause this program to receive the SIGINT signal.  This function
 *  waits for the SIGINT signal to be delivered.  Once the SIGINT signal has
 *  been delivered, this function cancels the two threads monitoring programs
 *  through the EMAPI.
 */

static
void *cancel_thread_main(void *parm_p)
{
    int             rc;                 /* Return code (error number)        */
    sigset_t        sigset;             /* Signal mask to unblock SIGINT     */
    int             sig;                /* Received signal                   */
    STRERROR_BUF;                       /* Buffer for strerror_r()           */


    sigemptyset(&sigset);               /* Initialize signal set             */
    sigaddset(&sigset, SIGINT);         /* Add SIGINT to signal set          */

    for ( ; ; ) {

        rc = sigwait(&sigset, &sig);
        if (rc != 0) {
            fprintf(stderr, "sigwait(): %s\n", STRERROR(rc));
            exit(1);
        }

        if (sig == SIGINT) {
            printf("Termination requested.\n");
            cancel_thread(thread1);     /* Request cancellation of thread 1  */
            cancel_thread(thread2);     /* Request cancellation of thread 2  */
            break;                      /* No longer wait for SIGINT         */
        }

        fprintf(stderr, "sigwait() reported unexpected signal: %d.\n", sig);
        exit(1);
        
    }

    return NULL;                        /* The thread is finished            */
}


/*
 *  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           */

    
    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        */

        re->em_cb     = NULL;                    /* Callback routine not used*/
        re->em_cb_arg = NULL;                    /* Callback routine not used*/
    }

    /*
     *  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 ensure_session_connected() function ensures the specified session
 *  is not known to be disconnected.  If the session is marked as needing
 *  to be restarted, the function does not return until the session has been
 *  successfully restarted.  Restart attempts are tried every 15 seconds.
 */

static
void ensure_session_connected(struct session *sess_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.
     */
    
    while (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, wait 15 seconds
             *  before trying the restart again.
             */
            
            if (errb.em_errno == HA_EM_ECONNREFUSED) {
                sleep(15);
                continue;
            }

            /*
             *  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.  Just return.
     */

    return;
}


/*
 *  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, or
     *  a full response may not be available yet.  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.
     */
    
    switch (rsp_blk->em_cmd) {

        case HA_EM_CMD_REG:             /* Event response                    */
        case HA_EM_CMD_REG2:            /* Event response (possible re-arm)  */
            process_response_reg(sess_p, progs_p, progs_elems, rsp_blk);
            break;

        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_reg() function processes event responses from the
 *  Event Manager.  Several points should be kept in mind:
 *
 *      - The response block may contain multiple event responses.  The
 *        number of responses included in the response block is given in
 *        the response block header.
 *
 *      - 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 process_response_reg(struct session *sess_p,
        struct program *progs_p, int progs_elems,
        struct ha_em_rsp_blk *rsp_blk)
{
    struct ha_em_rpb_event  *event_p;           /* Pointer to event response */
    struct ha_em_rpb_event  *last_event_p;      /* Beyond last event response*/

    char time_stamp[80];        /* Formatted event time stamp                */
    char prog_name[80];         /* Name of program to which event pertains   */
    char user_name[80];         /* Name of user to which event pertains      */
    int  node;                  /* Node to which event pertains              */

    
    event_p = rsp_blk->em_resp_blk.em_rpb_event;
    last_event_p = event_p + rsp_blk->em_rsp_num_resp;

    for ( ; event_p < last_event_p; event_p++) {

        /*
         *  Format the event's time stamp; extract the program name, user
         *  name, and node number from the resource ID.
         */
        
        format_timestamp(&event_p->em_timestamp, time_stamp);
        breakdown_rsrc_ID(event_p->em_rsrc_ID, prog_name, user_name, &node);

        /*
         *  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_name, user_name, node,
                   event_p->em_generr, event_p->em_specerr);
            continue;
        }

        /*
         *  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_name, user_name, node);
            continue;
        }

        /*
         *  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_name, user_name, node);

    }

    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;

    }

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

    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;


    localtime_r((time_t *) &timestamp_p->tv_sec, &broken_down_time);
    (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;
}
