/* Copyright 2003-2023 James F. Duff */
/* License and disclaimer: http://www.eight-cubed.com/disclaimer.html */

#define __NEW_STARLET 1

/*
** This example if fairly complex, but to demo the ICC services, I don't
** see how I can do it in any less code.
**
** This program contains both client and server code.  When the code runs,
** it attempts to take out a system lock on a resource, and if it succeeds,
** runs the server section of the code.  Else it runs the client section.
**
** The server discovers all the other nodes in the cluster that have the same
** architecture as the node running the server, and creates a process on each
** of them, which runs this code (as the client).
**
** The server section opens an association using ICC, and the clients connect
** to the association, send some data and an EOD, and exit.  Once the server
** sees that all clients have sent data, it shuts down.
**
** You need the SYSLCK and SYSNAM privileges to run this program.  The
** program will create output files in the directory the program resides in,
** so you need write access to that dir.  The two output files are called
** ICC_DEMO_CLNT.OUT_nodename and ICC_DEMO_CLNT.ERR_nodename, where nodename
** is a name of a node in the cluster.  If the program runs successfully,
** there should be one .OUT file for each node in the cluster, and no .ERRs ;-)
*/

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <ctype.h>
#include <string.h>
#include <descrip.h>
#include <efndef.h>
#include <fscndef.h>
#include <iccdef.h>
#include <jpidef.h>
#include <lckdef.h>
#include <lnmdef.h>
#include <prvdef.h>
#include <ssdef.h>
#include <stsdef.h>
#include <syidef.h>
#include <iosbdef.h>
#include <iledef.h>
#include <gen64def.h>
#include <lib$routines.h>
#include <starlet.h>

#include "errchk.h"

struct node_info {
    struct node_info *next_node;
    unsigned int client_pid;
    unsigned int conn_handle;
    char node_name[15+1];
    struct dsc$descriptor_s node_name_d;
    char seen_eod;
    char seen_disconnect;
};

#define END_BLOCK_TYPE 1
#define DATA_BLOCK_TYPE 2
#define MAXBUFSIZE 1023
#define ASSOC_NAME "DEMO_ASSOC_NAME"
#define TABLE_NAME "ICC$REGISTRY_TABLE"

/*
** Global listhead for nodes.
*/
static struct node_info *node_list = NULL;


/******************************************************************************/
static int determine_master (char *master) {
/*
** Attempt to get an exclusive mode lock on a resource.  If we get it,
** we are the master (ie, the server).
*/

static struct {
    unsigned short int status;
    unsigned short int reserved;
    unsigned int lock_id;
} lksb;

static int r0_status;

static $DESCRIPTOR (resource_d, "ICC_DEMO_MASTER");

    r0_status = sys$enqw (EFN$C_ENF,
                          LCK$K_EXMODE,
                          &lksb,
                          LCK$M_NOQUEUE|LCK$M_SYSTEM,
                          &resource_d,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0);
    if (r0_status == SS$_NOTQUEUED) {
        *master = FALSE;
    } else {
        errchk_sig (r0_status);
        errchk_sig (lksb.status);
        *master = TRUE;
    }
    return SS$_NORMAL;
}


/******************************************************************************/
static struct node_info *find_node (struct dsc$descriptor_s *node_d) {
/*
** Find a node on the list of nodes.
*/

static struct node_info *ni_p;

    ni_p = node_list;
    while (ni_p != NULL) {
        if ((node_d->dsc$w_length == ni_p->node_name_d.dsc$w_length) &&
            (memcmp (node_d->dsc$a_pointer,
                     ni_p->node_name_d.dsc$a_pointer,
                     node_d->dsc$w_length) == 0)) {
            break;
        }
        ni_p = ni_p->next_node;
    }

    return ni_p;
}


/******************************************************************************/
static struct node_info *add_node (struct dsc$descriptor_s *node_d) {
/*
** Add a node to the end of the linked list.
*/

static struct node_info *ni_p;
static struct node_info *last_node_p = NULL;

    ni_p = calloc (1, sizeof (struct node_info));
    assert (ni_p != NULL);

    memcpy (ni_p->node_name,
            node_d->dsc$a_pointer,
            node_d->dsc$w_length);
    ni_p->node_name_d.dsc$w_length = node_d->dsc$w_length;
    ni_p->node_name_d.dsc$a_pointer = ni_p->node_name;
    ni_p->next_node = NULL;
    ni_p->seen_eod = FALSE;
    ni_p->seen_disconnect = FALSE;

    /*
    ** Add the new node to the end of the linked list.
    */
    if (last_node_p == NULL) {
        node_list = ni_p;
        last_node_p = ni_p;
    } else {
        last_node_p->next_node = ni_p;
        last_node_p = ni_p;
    }
    return ni_p;
}


/******************************************************************************/
static struct node_info *get_node_list (void) {
/*
** Find all the nodes in the cluster.
*/

static IOSB iosb;

static struct node_info *ni_p;
static struct node_info *our_ni_p;

static int r0_status;
static unsigned int csid = -1; /* Nasty.  Poor def in new starlet causes this */
static int arch_type;
static int save_arch_type;

static char nodename[15+1];
static struct dsc$descriptor_s nodename_d = { 0,
                                              DSC$K_DTYPE_T,
                                              DSC$K_CLASS_S,
                                              nodename };


static ILE3 syiitms[] = 
    {  15, SYI$_NODENAME, nodename, &nodename_d.dsc$w_length,
        4, SYI$_ARCH_TYPE, &arch_type, NULL,
        0, 0, NULL, NULL };

    /*
    ** Get our node name and architecture type.
    */
    r0_status = sys$getsyiw (EFN$C_ENF,
                             0,
                             0,
                             syiitms,
                             &iosb,
                             0,
                             0);
    errchk_sig (r0_status);
    errchk_sig (iosb.iosb$l_getxxi_status);

    /*
    ** Add this node to the linked list and save our architecture type.
    */
    our_ni_p = add_node (&nodename_d);
    save_arch_type = arch_type;

    /*
    ** Get info on all nodes in the cluster.
    */
    while (r0_status != SS$_NOMORENODE) {
        r0_status = sys$getsyiw (EFN$C_ENF,
                                 &csid,
                                 0,
                                 syiitms,
                                 &iosb,
                                 0,
                                 0);
        if (r0_status == SS$_NOMORENODE) {
            continue;
        } else {
            errchk_sig (r0_status);
        }
        errchk_sig (iosb.iosb$l_getxxi_status);

        /*
        ** For each node of the same architecture type, create a node info
        ** structure for it and add it to the linked list of nodes.
        */
        if (arch_type == save_arch_type) {
            if (find_node (&nodename_d) == NULL) {
                add_node (&nodename_d);
            }
        }
    }
    return our_ni_p;
}


/******************************************************************************/
static void client_send_hello (struct node_info *ni_p) {
/*
** Send a hello message to the server.
*/

static IOS_ICC ios_icc;

static IOSB iosb;

static int r0_status;

static char buffer[MAXBUFSIZE];

static $DESCRIPTOR (association_d, ASSOC_NAME);
static $DESCRIPTOR (table_d, TABLE_NAME);

    /*
    ** Format some stuff to put in the buffer.  This gets sent to the server
    ** unchanged, so you could use it to pass any info you want.
    */
    (void)sprintf (buffer,
                   "%-.*s (connecting on association %-.*s)",
                   ni_p->node_name_d.dsc$w_length,
                   ni_p->node_name,
                   association_d.dsc$w_length,
                   association_d.dsc$a_pointer);

    (void)printf ("Client %s sending connect request... ",
                  buffer);
    (void)fflush (stdout);

    r0_status = sys$icc_connectw (&ios_icc,
                                  0,
                                  0,
                                  ICC$C_DFLT_ASSOC_HANDLE,
                                  &ni_p->conn_handle,
                                  &association_d,
                                  0,
                                  0,
                                  buffer,
                                  strlen (buffer),
                                  0,
                                  0,
                                  0,
                                  0);
    errchk_sig (r0_status);
    errchk_sig (ios_icc.ios_icc$w_status);
    (void)printf ("success\n");

    /*
    ** Format a data message to send.
    */
    (void)sprintf (buffer, "%cHELLO!", DATA_BLOCK_TYPE);
    (void)printf ("Sending hello... ");
    (void)fflush (stdout);

    /*
    ** Send it.
    */
    r0_status = sys$icc_transmitw (ni_p->conn_handle,
                                   &ios_icc,
                                   0,
                                   0,
                                   buffer,
                                   strlen (buffer));
    errchk_sig (r0_status);
    errchk_sig (ios_icc.ios_icc$w_status);
    (void)printf ("success\n");
}


/******************************************************************************/
static void client_send_eod (struct node_info *ni_p) {
/*
** Send an end of data indicator.
*/

static int r0_status;
static unsigned int buffer = END_BLOCK_TYPE;
static IOS_ICC ios_icc;
static IOSB iosb;

    (void)printf ("Sending EOD... ");
    (void)fflush (stdout);
    r0_status = sys$icc_transmitw (ni_p->conn_handle,
                                   &ios_icc,
                                   0,
                                   0,
                                   &buffer,
                                   sizeof (buffer));
    errchk_sig (r0_status);
    errchk_sig (ios_icc.ios_icc$w_status);
    (void)printf ("success\n");
}


/******************************************************************************/
static void client_disconnect (struct node_info *ni_p) {
/*
** Disconnect from the association.
*/

static IOSB iosb;

static int r0_status;

    r0_status = sys$icc_disconnectw (ni_p->conn_handle,
                                     &iosb,
                                     0,
                                     0,
                                     0,
                                     0);
    errchk_sig (r0_status);
    errchk_sig (iosb.iosb$w_status);
}


/******************************************************************************/
static void server_conn_disc_ast (unsigned int event,
                                  unsigned int conn_handle,
                                  unsigned int data_len,
                                  char *data_buffer,
                                  unsigned int p5,
                                  unsigned int p6,
                                  char *p7) {
/*
** Server routine.  Called as an AST on events received from clients.
*/

static IOSB iosb;

static struct node_info *ni_p;

static int r0_status;
static int i;

static struct dsc$descriptor_s node_d = { 0,
                                          DSC$K_DTYPE_T,
                                          DSC$K_CLASS_S,
                                          NULL };

    switch (event) {
        case ICC$C_EV_CONNECT :
            /*
            ** We have a connect request from a client.  Find its node name,
            ** which happens to be passed to us the first bit of the buffer.
            */
            for (i = 0; i < data_len; i++) {
                if (isspace (data_buffer[i])) {
                    break;
                }
            }

            node_d.dsc$w_length = i;
            node_d.dsc$a_pointer = data_buffer;
            ni_p = find_node (&node_d);
            assert (ni_p != NULL);
            (void)printf ("Received connect event from %-.*s... ",
                          data_len,
                          data_buffer);
            (void)fflush (stdout);

            /*
            ** Accept the request and pass the node specific info as the user
            ** context so that the node info is delivered on subsequent data
            ** and disconnect events.
            */
            r0_status = sys$icc_accept (conn_handle,
                                        0,
                                        0,
                                        (unsigned int)ni_p,
                                        0);
            errchk_sig (r0_status);

            ni_p->conn_handle = conn_handle;
            (void)printf ("accepted\n");
            break;
        case ICC$C_EV_DISCONNECT :
            /*
            ** Received a disconnect event.  Get our user context from p6.
            */
            ni_p = (struct node_info *)p6;
            (void)printf ("Received disconnect event from node %-.*s\n",
                          ni_p->node_name_d.dsc$w_length,
                          ni_p->node_name_d.dsc$a_pointer);
            ni_p->seen_disconnect = TRUE;
            break;
        default :
            (void)printf ("Ignored unrecognised event received from ICC: %i\n",
                          event);
            break;
    }
}


/******************************************************************************/
static void server_receive_data_ast (unsigned int message_size,
                                     unsigned int conn_handle,
                                     unsigned int user_context) {
/*
** Server routine called an an AST to get data from clients.
*/

static IOS_ICC ios_icc;

static struct node_info *ni_p;

static int r0_status;

static char buffer[MAXBUFSIZE];

    ni_p = (struct node_info *)user_context;
    (void)printf ("Received data event from %-.*s\n",
                  ni_p->node_name_d.dsc$w_length,
                  ni_p->node_name_d.dsc$a_pointer);

    memset (buffer, 0, sizeof (buffer));

    r0_status = sys$icc_receivew (conn_handle,
                                  &ios_icc,
                                  0,
                                  0,
                                  buffer,
                                  MAXBUFSIZE);
    errchk_sig (r0_status);
    errchk_sig (ios_icc.ios_icc$w_status);

    switch (buffer[0]) {
        case DATA_BLOCK_TYPE :
            (void)printf ("Received data message from %-.*s\n",
                          ni_p->node_name_d.dsc$w_length,
                          ni_p->node_name_d.dsc$a_pointer);
            (void)printf ("  Data: %-.*s\n",
                          ios_icc.ios_icc$l_rcv_len - 1,
                          buffer+1);
            break;
        case END_BLOCK_TYPE :
            (void)printf ("EOD indicator received from %-.*s\n",
                          ni_p->node_name_d.dsc$w_length,
                          ni_p->node_name_d.dsc$a_pointer);
            ni_p->seen_eod = TRUE;
            break;
        default :
            (void)printf ("Bad block type = %d!\n", buffer[0]);
            exit (EXIT_FAILURE);
            break;
    }
}


/******************************************************************************/
static void start_server (unsigned int *server_handle) {
/*
** Open communications.
*/

static int r0_status;

static $DESCRIPTOR (association_d, ASSOC_NAME);
static $DESCRIPTOR (table_d, TABLE_NAME);

    /*
    ** Open an association.  Specify our event and data handlers.
    */
    r0_status = sys$icc_open_assoc (server_handle,
                                    0,
                                    &association_d,
                                    &table_d,
                                    &server_conn_disc_ast,
                                    &server_conn_disc_ast,
                                    &server_receive_data_ast,
                                    0,
                                    0);
    errchk_sig (r0_status);
}


/******************************************************************************/
static void start_client (struct node_info *ni_p) {
/*
** Create a client process on a specific node.
*/

static int r0_status;

static $DESCRIPTOR (prcnam_d, "ICC_DEMO_CLNT");

static char output[255+1];
static char error[255+1];
static char program[255+1];

static struct dsc$descriptor_s output_d = { 0,
                                            DSC$K_DTYPE_T,
                                            DSC$K_CLASS_S,
                                            output };
static struct dsc$descriptor_s error_d = { 0,
                                           DSC$K_DTYPE_T,
                                           DSC$K_CLASS_S,
                                           error };
static struct dsc$descriptor_s program_d = { sizeof (program) - 1,
                                             DSC$K_DTYPE_T,
                                             DSC$K_CLASS_S,
                                             program };

static struct {
    unsigned short int retlen;
    unsigned short int function;
    char *address;
} fcitms[] = { 0, FSCN$_DEVICE, NULL,
               0, FSCN$_ROOT, NULL,
               0, FSCN$_DIRECTORY, NULL,
               0, 0, NULL };

static PRVDEF privs;
static int item_code = JPI$_IMAGNAME;

    /*
    ** Get the name of this executable as it is running in this process.
    */
    r0_status = lib$getjpi (&item_code,
                            0,
                            0,
                            0,
                            &program_d,
                            &program_d.dsc$w_length);
    errchk_sig (r0_status);

    /*
    ** Get the addresses and lengths of the various bits of the filename.
    */
    r0_status = sys$filescan (&program_d,
                              fcitms,
                              0,
                              0,
                              0);
    errchk_sig (r0_status);

    /*
    ** Format some output file names.
    */
    output_d.dsc$w_length = sprintf (output,
                                     "%-.*s%-.*s%-.*sICC_DEMO_CLNT.OUT_%-.*s",
                                     fcitms[0].retlen,
                                     fcitms[0].address,
                                     fcitms[1].retlen,
                                     fcitms[1].address,
                                     fcitms[2].retlen,
                                     fcitms[2].address,
                                     ni_p->node_name_d.dsc$w_length,
                                     ni_p->node_name_d.dsc$a_pointer);

    error_d.dsc$w_length = sprintf (error,
                                    "%-.*s%-.*s%-.*sICC_DEMO_CLNT.ERR_%-.*s",
                                     fcitms[0].retlen,
                                     fcitms[0].address,
                                     fcitms[1].retlen,
                                     fcitms[1].address,
                                     fcitms[2].retlen,
                                     fcitms[2].address,
                                     ni_p->node_name_d.dsc$w_length,
                                     ni_p->node_name_d.dsc$a_pointer);

    /*
    ** Required privileges for the client.
    */
    privs.prv$v_netmbx = TRUE;
    privs.prv$v_tmpmbx = TRUE;
    privs.prv$v_syslck = TRUE;
    privs.prv$v_sysnam = TRUE;

    /*
    ** Create the client process.
    */
    r0_status = sys$creprc (&ni_p->client_pid,
                            &program_d,
                            0,
                            &output_d,
                            &error_d,
                            (GENERIC_64 *)&privs,
                            0,
                            &prcnam_d,
                            0,
                            0,
                            0,
                            0,
                            0,
                            &ni_p->node_name_d);
    errchk_sig (r0_status);

    (void)printf ("Client process started on node %-.*s\n",
                  ni_p->node_name_d.dsc$w_length,
                  ni_p->node_name_d.dsc$a_pointer);
}


/******************************************************************************/
static void wait_awhile (void) {
/*
** Wait five seconds.
*/

static GENERIC_64 delay_bin;

static int r0_status;
static unsigned int efn;

static $DESCRIPTOR (delay_d, "0 00:00:05.00");

static char first_time = TRUE;

    if (first_time) {
        /*
        ** First time through, convert ascii time to binary.
        */
        first_time = FALSE;
        r0_status = sys$bintim (&delay_d,
                                &delay_bin);
        errchk_sig (r0_status);
    }

    r0_status = lib$get_ef (&efn);
    errchk_sig (r0_status);

    r0_status = sys$setimr (efn,
                            &delay_bin,
                            0,
                            0,
                            0);
    errchk_sig (r0_status);

    r0_status = sys$waitfr (efn);
    errchk_sig (r0_status);

    r0_status = lib$free_ef (&efn);
    errchk_sig (r0_status);
}


/******************************************************************************/
static void server_wait () {
/*
** Wait for the clients to finish.
*/ 

static struct node_info *ni_p;

static int r0_status;

static char finished = FALSE;

    while (!finished) {
        ni_p = node_list;
        while (ni_p != NULL) {
            if (!ni_p->seen_disconnect) {
                break;
            }
            ni_p = ni_p->next_node;
        }
        if (ni_p == NULL) {
            /*
            ** If all the disconnect indicators are set, we have received
            ** info from all the clients.  Exit the loop.
            */
            finished = TRUE;
        } else {
            /*
            ** We haven't seen a disconnect from at least one client. Go
            ** to sleep for a while.
            */
            wait_awhile ();
        }
    }
}


/******************************************************************************/
static int main (void) {

static struct node_info *ni_p;

static int r0_status;
static unsigned int server_handle;

static char master;
    
    /*
    ** Are we a client or are we the server?
    */
    determine_master (&master);

    /*
    ** Get list of nodes in the cluster.  Of course, this will work on a
    ** standalone node as well.  The return value is the address of this
    ** node's info.
    */
    ni_p = get_node_list ();

    if (master) {
        /*
        ** Here we are the server.
        */
        (void)printf ("ICC_DEMO - master\n");

        /*
        ** Open communication channel so the clients can talk to us.
        */
        start_server (&server_handle);

        ni_p = node_list;
        while (ni_p != NULL) {
            /*
            ** Start a client on each node in the cluster.
            */
            start_client (ni_p);
            ni_p = ni_p->next_node;
        }

        /*
        ** Wait for all clients to finish.
        */
        server_wait ();

        /*
        ** Close the ICC association.
        */
        r0_status = sys$icc_close_assoc (server_handle);
        errchk_sig (r0_status);

        (void)printf ("Master exit\n");
    } else {
        /*
        ** Here the code is acting as a client.
        */
        (void)printf ("ICC_DEMO - client\n");

        /*
        ** Send a message to the server.
        */
        client_send_hello (ni_p);

        /*
        ** Tell the server we have finished.
        */
        client_send_eod (ni_p);

        /*
        ** Disconnect.
        */
        client_disconnect (ni_p);

        (void)printf ("Client exit\n");
    }
}

Back to the master examples list.