/* IBM_PROLOG_BEGIN_TAG                                                   */
/* This is an automatically generated prolog.                             */
/*                                                                        */
/*                                                                        */
/*                                                                        */
/* Licensed Materials - Property of IBM                                   */
/*                                                                        */
/* (C) COPYRIGHT International Business Machines Corp. 1998,2021          */
/* 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                                                     */

static const char *sccsid = "@(#)52   1.12   src/rsct/pgs/samples/Sample_Subscribe.C, gssamples, rsct_rady, radys003a 6/6/21 12:23:55";

#if !defined(_HAGSD_COPYRIGHT_H)
#define _HAGSD_COPYRIGHT_H
static const char copyright[] = "Licensed Materials - Property of IBM\n\
(C) COPYRIGHT International Business Machines Corp. 1998,2001.\n\
All Rights Reserved.\n\
US Government Users Restricted Rights - Use, duplication or \n\
disclosure restricted by GSA ADP Schedule Contract with IBM Corp.\n";
#endif

/*********************************************************************/
/*
 * Name:  Sample_Subscribe.C
 *
 * This program's purpose in life is quite simple:  it wants to take
 * as input a group name and some optional flags.  It will connect to
 * Group Services, subscribe to the given group, get the desired data,
 * then sit and wait for further notifications, or unsubscribe and exit.
 *
 * For usage syntax and help info, see the WriteHelpInfo() function.
 *
 * While this may not seem too exciting, it does do certain things that
 * can make your life a bit easier.  Group Services maintains the
 * provider membership list in "age" order - the first provider to join
 * the group is at the front, the last is at the end.  This is very
 * helpful from a programmatic point, because a group can use this
 * feature to easily "elect" a group leader, if they so choose.  It is
 * the provider who has been around the longest.
 *
 * However, in certain cases for human eyes this is troublesome.  An
 * easy example is that you have 100 nodes (or, 500) and expect to have
 * one provider on each node.  But, using lssrc Group Services states
 * that there are 99 providers in the group!  Who is missing?  If you
 * use "sample_test" to subscribe, it will always print the membership
 * list in 'raw' format - the same order in which Group Services
 * maintains it.  Which makes it very difficult to figure out from
 * which node is a provider missing.
 *
 * Therefore, this program offers three options in how to display the
 * membership list:
 *  'raw' -- straight printout in same order as the list is presented
 *      to us by Group Services.
 *  'ordered' - sorted by node number, lowest first.
 *  'tabular' - table printout, displaying each "frame" and the providers
 *      on each node in that "frame".
 *
 * One more note, regarding the group's state value.  The program will
 * atempt to print it, but many group state values may be unprintable
 * characters (e.g., array of ints).  Thus, the program will display it
 * three ways:
 *  - as characters, replacing non-printable characters with '.'.  (All
 *      characters are assumed to be single-byte.)
 *  - if the total length of the group state value is an integral value,
 *      print as an array of ints.
 *  - display the hex equivalents of the entire group state value.
 */
 /*********************************************************************/

#if defined(__linux__) || defined(__INTERIX) || defined(__sun)
#define NFDS(a) a
#endif

/*********************************************************************/
/*
 * Include the classes used by this program.
 */
/*********************************************************************/

#include "Sample_Subscribe.h"           // Common declarations.
#include "Sample_Subscription.h"        // Handle the subscriptions
extern "C" unsigned int sleep (unsigned int Seconds);

Subscription   *subscription;           // The subscription to our group.

char           *outputFileName;         // Name of the output file.
ofstream       outputFile;             // Output stream, if not cout.

// Callback functions.
GSSAMPLE_EXTERN_C void DelayedErrorCB(const ha_gs_delayed_error_notification_t *_note);
GSSAMPLE_EXTERN_C void SubscriptionCB(const ha_gs_subscription_notification_t *_note);

/*********************************************************************/
/*
 * Write out help info.
 */
/*********************************************************************/

void    WriteHelpInfo(void)
{
    cerr << "Sample_Subscribe group_name < [-a] [-m] [-j] [-l] [-s] > [-r|-o] [-p]" << endl;
    cerr << "      [-c flag] [-d domain] [-u subsystem] [-f output_file] [-t] [-h]" << endl;
    cerr << "group_name -- required, is the group name for subscription." << endl;
    cerr << endl;
    cerr << "  Subscription controls:" << endl;
    cerr << "    At least one of the following must be given." << endl;
    cerr << "-a         -- if given, subscribe for membership changes, with deltas" << endl;
    cerr << "-m         -- if given, subscribe for membership changes, no deltas" << endl;
    cerr << "-j         -- if given, subscribe for membership join change deltas" << endl;
    cerr << "-l         -- if given, subscribe for membership leave change deltas" << endl;
    cerr << "-s         -- if given, subscribe for group state value changes" << endl;
    cerr << "    Specifying -a is equivalent (and overrides) specifying '-m -j -l'." << endl;
    cerr << endl;
    cerr << "-p         -- if given, be persistent, remain connected and print" << endl;
    cerr << "              out notifications (use Ctrl-C, or kill signal to stop" << endl;
    cerr << "              in this case.)  If not given, disconnect and exit" << endl;
    cerr << "              after first notification." << endl;
    cerr << endl;
    cerr << "-c flag    -- if given, when we receive a subscription notification" << endl;
    cerr << "              that includes any optional \"special\" data, print out" << endl;
    cerr << "              the contents of the special data.  The flag controls " << endl;
    cerr << "              types of special data to print.  Flag values:" << endl;
    cerr << "                1 -- print out the \"adapter death array\" if given." << endl;
    cerr << "                2 -- print out the \"current alias array\" if given." << endl;
    cerr << "                4 -- print out the \"changing alias array\" if given." << endl;
    cerr << "              To display multiple of these, specify a flag value with" << endl;
    cerr << "              one or more of the above values added together." << endl;
    cerr << "    If -c is not given, then print only the flags that specify what" << endl;
    cerr << "    special data was received, but not the full contents of the data." << endl;
    cerr << endl;
    cerr << "  Display controls:" << endl;
    cerr << "    If neither -r nor -o are given, display a tabular format" << endl;
    cerr << "    translating node numbers to frame/node number pairs." << endl;
    cerr << "-r         -- if given, do \"raw\" display, use node numbers when" << endl;
    cerr << "              displaying membership, display in order given by" << endl;
    cerr << "              Group Services in the notification." << endl;
    cerr << "-o         -- if given, sort the output in node number order, lowest" << endl;
    cerr << "               first." << endl;
    cerr << endl;
    cerr << "  Domain control:" << endl;
    cerr << "    You can specify the Group Service domain to which the program " << endl;
    cerr << "    connects by setting HA_SYSPAR_NAME in your environment to the " << endl;
    cerr << "    name of the domain (SP partition name), or by using the -d flag." << endl;
    cerr << "    If neither is set, the program exits with an error." << endl;
    cerr << "-d domain  -- specifies the Group Services domain.  If this is given," << endl;
    cerr << "              any setting in HA_SYSPAR_NAME is overridden." << endl;
    cerr << endl;
    cerr << "  Group Services subsystem control:" << endl;
    cerr << "    By default, connect to the SP Realm (PSSP) Group Services daemon." << endl;
    cerr << "    You can override this by specifying the subsystem name, either" << endl;
    cerr << "    setting PGSD_SUBSYS in your environment or using this flag." << endl;
    cerr << "-u subsys  -- specify the Group Services subsystem.  \"hags\" connects" << endl;
    cerr << "              to the PSSP daemon (default).  \"grpsvcs\" connects to" << endl;
    cerr << "              the HA daemon (if HACMP/ES is installed and running.)" << endl;
    cerr << "              If given, this overrides PGSD_SUBSYS, if it is set." << endl;
    cerr << endl;
    cerr << "  Connect to test daemon:" << endl;
    cerr << "-t         -- ignore any settings of HA_SYSPAR_NAME and PGSD_SUBSYS." << endl;
    cerr << endl;
    cerr << "-f output_file -- specifies a file for output.  If not given, use" << endl;
    cerr << "              stdout." << endl;
    cerr << endl;
    cerr << "-h         -- display this info, and exit." << endl;

    cerr.flush();

    return;
}

/*********************************************************************/
/*
 * Redirect cout into the specified file.  This allows us to simply use
 * "cout" everywhere.
 */
/*********************************************************************/

int     RedirectCout(char *_outputFileName)
{
    int _rc = 0;

    outputFile.open( _outputFileName, ios::out );
    if (outputFile.good()) {
        //cout = *outputFile;
        cerr << "log file is opened." << endl;
    } else {
        cerr << "ERROR: could not open log file: " << _outputFileName << "." << endl;
        _rc = -1;
    }
    return(_rc);
}

/*********************************************************************/
/*
 * Basically just parse input args, connect to Group Services, and rip
 * into it!
 */
/*********************************************************************/

int main(int argc,
          char ** argv)
{
    /*********************************************************************/
    /*
     * This is the file descriptor of the socket that will connect this
     * program to Group Services.  It will be initialized when we call
     * ha_gs_init().
     */
    ha_gs_descriptor_t socketFD;

    /*********************************************************************/
    /*
     * This defines the "default" responsiveness settings used for the
     * ha_gs_init() call.
     */
    ha_gs_responsiveness_t auto_responsiveness = {HA_GS_NO_RESPONSIVENESS,
                                                  0,
                                                  0,
                                                  (char *)0,
                                                  0};

    /*********************************************************************/
    /*
     * These variables represent various data types used to send and/or
     * receive data to/from Group Services.  Refer to the IBM High
     * Availability Group Services manual for full descriptions.
     */
    ha_gs_socket_ctrl_t   socket_ctrl = HA_GS_SOCKET_NO_SIGNAL;
    ha_gs_rc_t            rc;

    /*********************************************************************/
    /*
     * These variables are used to control the subscription setup.
     */
    int     subscriptionControl;            // For what to control?
    char   *subscriptionGroupName;          // Group name to subscribe to.
    Subscription::providerDisplayType  displayType; // Type of display for right now.
    Subscription::specialDisplayType   displaySpecial; // Display special data.
    int     persistentConnexion;            // Stay connected?
    const char   *hagsDomain;                     // Name of the GS domain.
    const char   *hagsSubsys;                     // Hags subsystem.
    int     hagsTestConn;                   // Ignore domain and subsystem?

    /*********************************************************************/
    /*
     * These variables are used to control the select() system call.  Please
     * refer to the AIX documentation for a full descriptions of select().
     */
    int         select_rc;
    int         highestDescriptor;
    int         howMany;
    int         commandFD;
    fd_set      socketsForSelect;       /* Maintain all registered sockets in mask. */
    fd_set      socketSelectMask;       /* Used for the actual select. */
    struct      timeval nextJob;        /* Wait time for select. */

    /*********************************************************************/
    /*
     * Set defaults.
     */
    displayType = Subscription::kTable; // Assume we want pretty display.
    displaySpecial = Subscription::kShort; // Just print out flag values.
    persistentConnexion = 0;            // Do not stay connected.
    subscriptionControl = Subscription::kNothing; // Nothing yet specified.
    subscriptionGroupName = NULL;       // Group name.
    outputFileName = NULL;              // Using cout for now.
    hagsDomain = NULL;                  // No domain yet specified.
    hagsSubsys = NULL;                  // No subsystem yet specified.
    hagsTestConn = 0;                   // Do not connect to a test domain.

    /*********************************************************************/
    /*
     * Verify command-line arguments, if any.
     */
    if (2 > argc) {
        cerr << "ERROR: Must specify a group name for subscription!" << endl;
        WriteHelpInfo();
        exit(-10);
    } else {
        if (!strcmp( argv[1], "-h")) {
            WriteHelpInfo();
            exit(0);
        }
        subscriptionGroupName = argv[1];
    }

    for(int argCtr = 2;
        argCtr < argc;
        argCtr++) {
        if(!strcmp( argv[argCtr], "-r")) {
            displayType = Subscription::kRaw;
        } else if(!strcmp( argv[argCtr], "-o")) {
            displayType = Subscription::kOrdered;
        } else if(!strcmp( argv[argCtr], "-p")) {
            persistentConnexion = 1;
        } else if(!strcmp( argv[argCtr], "-h")) {
            WriteHelpInfo();
            exit(0);
        } else if(!strcmp( argv[argCtr], "-a")) {
            subscriptionControl |= Subscription::kAllMembership;
        } else if(!strcmp( argv[argCtr], "-m")) {
            subscriptionControl |= Subscription::kMembership;
        } else if(!strcmp( argv[argCtr], "-j")) {
            subscriptionControl |= Subscription::kJoins;
        } else if(!strcmp( argv[argCtr], "-l")) {
            subscriptionControl |= Subscription::kLeaves;
        } else if(!strcmp( argv[argCtr], "-s")) {
            subscriptionControl |= Subscription::kState;
        } else if(!strcmp( argv[argCtr], "-c")) {
            displaySpecial = (Subscription::specialDisplayType)atoi(argv[++argCtr]);
        } else if(!strcmp( argv[argCtr], "-f")) {
            outputFileName = argv[++argCtr];
        } else if(!strcmp( argv[argCtr], "-d")) {
            hagsDomain = argv[++argCtr];
        } else if(!strcmp( argv[argCtr], "-u")) {
            hagsSubsys = argv[++argCtr];
        } else if(!strcmp( argv[argCtr], "-t")) {
            hagsTestConn = 1;
        } else {
            cerr << "Invalid flag [" << argv[argCtr] << "] given." << endl;
            WriteHelpInfo();
            exit(-20);
        }
    }

    /*********************************************************************/
    /*
     * Ensure user has specified a domain for us to connect to, unless -t
     * was given, in which case simply let the environment do whatever it
     * wants to do!  Normal assumption in such a case is that PGSD_SUPP_SOCK
     * is set with the actual socket path.
     */
    if (!hagsTestConn) {

        if (NULL == hagsDomain) {
            // Nothing given via -d.
            if (NULL == (hagsDomain = getenv("HA_DOMAIN_NAME"))) {
                if (NULL == (hagsDomain = getenv("HA_SYSPAR_NAME"))) {
			outputFile << "**PeerDomain is assumed. "
			     << "If it is HACMP or PSSP, "
			     << "HA_DOMAIN_NAME or HA_SYSPAR_NAME must be specified."
			     << endl;
			hagsDomain = "PeerDomain";
                }
            }
        } else {
            char *hagsDomain2 = new char[strlen("HA_SYSPAR_NAME=") +
                                         strlen(hagsDomain) + 2];
            sprintf(hagsDomain2, "HA_SYSPAR_NAME=%s", hagsDomain);
            putenv(hagsDomain2);
        }

        if (NULL != hagsSubsys) {
            char *hagsSubsys2 = new char[strlen("PGSD_SUBSYS=") +
                                         strlen(hagsSubsys) + 2];
            sprintf(hagsSubsys2, "PGSD_SUBSYS=%s", hagsSubsys);
            putenv(hagsSubsys2);

	} else {
	    hagsSubsys = "cthags";	/* peer domain */
        }

    }

    /*********************************************************************/
    /*
     * Set up structures based on parameters.
     */
    if (!Subscription::Valid(subscriptionControl)) {
        cerr << "ERROR: Must specify valid subscription type!" << endl;
        WriteHelpInfo();
        exit(-2);
    }        

    if (NULL != outputFileName) {
        if (-1 == RedirectCout(outputFileName)) {
            exit(-3);
        }
    }

    /*********************************************************************/
    /*
     * Establish defaults for select().
     */
    nextJob.tv_sec = 3600;              /* Wait one hour for select. */
    nextJob.tv_usec = 0;
    highestDescriptor = 0;

    FD_ZERO(&socketsForSelect);         /* No sockets yet. */

    socketFD = -1;                      /* No socket connection to Group Services yet. */

    /*********************************************************************/
    /*
     * Processing as follows:
     *  - init with Group Services, try 10 times, if unsuccessful, display
     *     error and exit.
     *  - once initted, subscribe, and wait in select.
     *    - if get error (e.g., NO GROUP) display error and exit.
     *    - get subscription data, display it.
     *  - if not persistent, exit.
     *  - if persistent, wait in select, display each notification as it
     *     arrives, until we get killed.
     */

    for (int _tries = 9;
         0 <= _tries;
         _tries--) {

        // Only need delayed error callback, since we use NO_RESPONSIVENESS.
        rc = ha_gs_init(&socketFD,
                        socket_ctrl,
                        &auto_responsiveness,
                        NULL,
                        NULL,
                        DelayedErrorCB,
                        NULL);

        if (HA_GS_OK == rc) {
            outputFile << "Connected to Group Services [subsys: " << hagsSubsys <<
                " domain: " << hagsDomain << "]." << endl;
            FD_SET(socketFD, &socketsForSelect);
            if (socketFD > highestDescriptor) {
                highestDescriptor = socketFD;
            }
            break;
        } else if (HA_GS_CONNECT_FAILED == rc) {
            if (0 < _tries) {
                cerr << "Could not connect to Group Services [subsys: " <<
                    hagsSubsys << " domain: " << hagsDomain << "]." << endl;
                cerr << " Attempt " << _tries << " more time";
                if (1 == _tries) {
                    cerr << "." << endl;
                } else {
                    cerr << "s." << endl;
                }
                sleep(10);
            } else {
                cerr << "Could not connect to Group Services [subsys: " <<
                    hagsSubsys << "domain: " << hagsDomain << "]." << endl;
                cerr << " No more attempts!  Giving up." << endl;
                exit(-4);
            }
        } else {
            cerr << "Fatal error connecting to Group Services [subsys: " <<
                hagsSubsys << "domain: " << hagsDomain << "]." << endl;
            cerr << " Error code [" << WriteAnRC(rc) << "]" << endl;
            exit(rc);
        }
    }                               // for(init tries)

    /*********************************************************************/
    /*
     * Set up the subscription.  Must be connected to get here.
     */

    subscription = new Subscription(subscriptionGroupName,
                                    subscriptionControl,
                                    persistentConnexion,
                                    displayType,
                                    displaySpecial,
                                    SubscriptionCB);

    if (subscription->Bad()) {
        cerr << "ERROR: Could not establish subscription to [" <<
            subscriptionGroupName << "].  Give up and exit." << endl;
        exit(subscription->ErrorCode());
    }

    /*********************************************************************/
    /*
     * Sit in select processing.  
     */

    while(1) {

        /*********************************************************************/
        /*
         * Set up and call select().
         */
        /*
         * Load all desired file descriptors into the select mask, then
         * call select.  We use "nextJob" as a timer, to pop out of select()
         * if no input arrives (default time 3600 seconds).
         */
	nextJob.tv_sec = 3600;              /* Wait one hour for select. */
	nextJob.tv_usec = 0;

        memcpy(&socketSelectMask, &socketsForSelect, sizeof(socketsForSelect));
        select_rc = select(highestDescriptor + 1,
                           &socketSelectMask,
                           0,
                           0,
                           &nextJob);

        if (select_rc < 0) {
            /*
             * If rc < 0, then an error occured.  If select was interrupted,
             * then no worries, just start select() over.  Any other error,
             * give up.
             */
            if (errno == EINTR) {
                cerr << "Got EINTR during the select.  Continue." << endl;
                continue;
            } else {
                cerr << "ERROR: Error [" << strerror(errno) <<
                    "] on select.  Give up and exit." << endl;
                exit(errno);
            }
        } else if (0 < (howMany = NFDS(select_rc))) {
            /*
             * Input has arrived on one or more of our file descriptors.
             * We normally expect to have 1 -- the socket to Group Services.
             */
            if (1 < howMany) {
                cerr << "ERROR: Input on more than one sockets??  Have [" <<
                    howMany << "].  Give up!" << endl;
                exit(howMany);
            }
	    rc = ha_gs_dispatch(HA_GS_NON_BLOCKING);
            if (HA_GS_OK != rc) {
                cerr << "ERROR: Bad news, bad return code from dispatch[" <<
                    WriteAnRC(rc) << "].  Give up and exit." << endl;
                exit(rc);
            }

            // If not persistent, we are done.  The dispatch call was either
            // the initial subscription notification, or an error.  In either
            // case, time to leave unless we really have to stick around.

            if (!subscription->Persistent()) {
                delete subscription;    // Kill the subscription.
                break;
            }
        } else {
            /*
             * Select() simply timed out.  Display something to show
             * that the program is alive, then just restart select().
             */
            outputFile << ".";            
        }

        outputFile.flush();                   // Ensure output goes out.
    }                                   // while(1)

    outputFile << "Thank you for using our services.  Please call again." << endl;
    exit(0);
}                                       // main()

/*********************************************************************/
/*
 * Handle a delayed error notification.  Essentially just pass through
 * to the established subscription object.
 */
/*********************************************************************/
GSSAMPLE_EXTERN_C
void DelayedErrorCB(const ha_gs_delayed_error_notification_t *_note)
{
    subscription->HandleDelayedError(_note);

    return;
}

/*********************************************************************/
/*
 * Handle a subscription notification.  Essentially just pass through
 * to the established subscription object.
 */
/*********************************************************************/

GSSAMPLE_EXTERN_C
void SubscriptionCB(const ha_gs_subscription_notification_t *_note)
{
    subscription->HandleNotification(_note);

    return;
}

/*********************************************************************/
/*
 * Utility to return the "name" of a Group Services error code.
 */
/*********************************************************************/
GSSAMPLE_EXTERN_C const char  *WriteAnRC(ha_gs_rc_t _pRC)
{
    switch(_pRC) {
      case HA_GS_OK:
        return("HA_GS_OK");
      case HA_GS_NOT_OK:
        return("HA_GS_NOT_OK");
      case HA_GS_EXISTS:
        return("HA_GS_EXISTS");
      case HA_GS_NO_INIT:
        return("HA_GS_NO_INIT");
      case HA_GS_NAME_TOO_LONG:
        return("HA_GS_NAME_TOO_LONG");
      case HA_GS_NO_MEMORY:
        return("HA_GS_NO_MEMORY");
      case HA_GS_NOT_A_MEMBER:
        return("HA_GS_NOT_A_MEMBER");
      case HA_GS_BAD_CLIENT_TOKEN:
        return("HA_GS_BAD_CLIENT_TOKEN");
      case HA_GS_BAD_MEMBER_TOKEN:
        return("HA_GS_BAD_MEMBER_TOKEN");
      case HA_GS_BAD_PARAMETER:
        return("HA_GS_BAD_PARAMETER");
      case HA_GS_UNKNOWN_GROUP:
        return("HA_GS_UNKNOWN_GROUP");
      case HA_GS_INVALID_GROUP:
        return("HA_GS_INVALID_GROUP");
      case HA_GS_NO_SOURCE_GROUP_PROVIDER:
        return("HA_GS_NO_SOURCE_GROUP_PROVIDER");
      case HA_GS_BAD_GROUP_ATTRIBUTES:
        return("HA_GS_BAD_GROUP_ATTRIBUTES");
      case HA_GS_WRONG_OLD_STATE:
        return("HA_GS_WRONG_OLD_STATE");
      case HA_GS_DUPLICATE_INSTANCE_NUMBER:
        return("HA_GS_DUPLICATE_INSTANCE_NUMBER");
      case HA_GS_COLLIDE:
        return("HA_GS_COLLIDE");
      case HA_GS_SOCK_CREATE_FAILED:
        return("HA_GS_SOCK_CREATE_FAILED");
      case HA_GS_SOCK_INIT_FAILED:
        return("HA_GS_SOCK_INIT_FAILED");
      case HA_GS_CONNECT_FAILED:
        return("HA_GS_CONNECT_FAILED");
      case HA_GS_VOTE_NOT_EXPECTED:
        return("HA_GS_VOTE_NOT_EXPECTED");
      case HA_GS_NOT_SUPPORTED:
        return("HA_GS_NOT_SUPPORTED");
      case HA_GS_INVALID_SOURCE_GROUP:
        return("HA_GS_INVALID_SOURCE_GROUP");
#ifndef SAMPLE_22
      case HA_GS_UNKNOWN_PROVIDER:
        return("HA_GS_UNKNOWN_PROVIDER");
      case HA_GS_INVALID_DEACTIVATE_PHASE:
        return("HA_GS_INVALID_DEACTIVATE_PHASE");
      case HA_GS_PROVIDER_APPEARS_TWICE:
        return("HA_GS_PROVIDER_APPEARS_TWICE");
#endif  /* !SAMPLE_22 */
      default:
        return("Unknown error code");
    }
}

/*********************************************************************/
/*
 * Write a time stamp.
 */
/*********************************************************************/

GSSAMPLE_EXTERN_C const char *WriteTheTime(void)
{
    struct timeval  current_time;
    struct timezone tz;
    static char     tod[32];            /* extract part of string. */
    char           *cTod;

    gettimeofday(&current_time, &tz);

                                        /* Grab month/day/time only. */
    cTod = ctime(&current_time.tv_sec);
    memcpy(tod, cTod+4, 16);
    tod[15] = '\0';

    return(tod);
}

/*********************************************************************/
/*
 * Determine how many characters are needed to display the given
 * provider ID.  Calculation is:
 *  (length of instance) + (length of node) + 2 [for " " and "/"].
 */
/*********************************************************************/

int     DisplayLen(short _val)
{
    int   _len = 0;
    short _abs = abs(_val);

    if (10 > _abs) _len = 1;
    else if (100 > _abs) _len = 2;
    else if (1000 > _abs) _len = 3;
    else if (10000 > _abs) _len = 4;
    else _len = 5;

    if (0 > _val) _len++;

    return(_len);
}

int     PrintLength(short _inst,
                    short _node)
{
    int _len = DisplayLen(_inst) + DisplayLen(_node);

    return(_len + 2);
}
