/* IBM_PROLOG_BEGIN_TAG                                                   */
/* This is an automatically generated prolog.                             */
/*                                                                        */
/* bos720 src/bos/usr/samples/ahafs/bin/aha.c 1.1.1.1                     */
/*                                                                        */
/* Licensed Materials - Property of IBM                                   */
/*                                                                        */
/* Restricted Materials of IBM                                            */
/*                                                                        */
/* COPYRIGHT International Business Machines Corp. 2009,2010              */
/* 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                                                     */
/* FUNCTIONS in this FILE aha.c:
 *     main or aha
 *     syntax
 *     monitor_a_event
 *     ahaMonFile
 *     mk_parent_dirs
 *     read_data
 *     monitor_a_set_of_events
 *     read_inputfile
 *     print_monList
 *     fdToMonListIndex
 * PURPOSE:
 *  1) Using the -i input flag, to easily monitor a set of AHA events listed in
 *     an input file which has the format as described in the sample aha.inp file.
 *
 *     This option invokes the pollset APIs in AIX to wait for occurrences
 *     of the events listed in the <aha-input-file>.
 *     The advantage of using the pollset APIs instead of select() is that
 *     one does not have to re-register interest in an event each time
 *     select() comes out of even ONE occurrence of an event.
 *
 *  2) Using the -m input flag, too monitor one AHA event represented by an
 *     AHA-monitor-file (filetype .mon).
 *     This option uses select() to wait for the occurrence of the AHA event.
 * SYNTAX:
 *     aha -i <aha-input-file>
 *     aha -m <aha-monitor-file> [<key1>=<value1>[;<key2>=<value2>;..]
 *   e.g.
 *     aha -i aha.inp
 *     aha -m /aha/fs/utilFs.monFactory/tmp.mon THRESH_HI=90
 * CHANGELOG:
 *     2008/04/01 Created by R.Burugula
 *     2008/04/09 Updated by J.Jann
 *     2008/10/30 Updated by R. Burugula, N.Dubey
 *     2008/11/15 Updated by J.Jann
 *     2009/01/21 Updated by N.Dubey
 *     2009/06/9  Updated by N.Dubey
 *     2009/07/30 Updated by N. Dubey
 */
#include <stdio.h>
#include <sys/poll.h>
#include <sys/pollset.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <libgen.h>
#include <usersec.h>

#define MAX_FD                    128
#define MAX_LINE_LENGTH           144
#define MAX_MONITOR_FILEPATH       64
#define MAX_EVENT_NOTIFICATIONS    10
#define STKTRACE_LEN             2048
#define MAX_WRITE_STR_LEN         256

#define AHAFS_THRESH_INVALID  (-1)

char *progname, *cfgFile, *monFile;
char monFileWrStr[MAX_WRITE_STR_LEN];

/* This structure is used to store the info read from the input file. */
typedef struct
{
    char monFileName[MAX_MONITOR_FILEPATH+1];
    int  eventType;
    int  changed;
    int  threshHi;
    int  fd;
    int  stopped;
} monEntry_t;
monEntry_t monList[MAX_FD];
int monListSz; /* Number of valid entries in the above array */

/* Function Prototypes */
void       syntax();
static int monitor_a_event();
int        ahaMonFile(char *str);
static int mk_parent_dirs (char *path);
void       read_data (int fd);
static int monitor_a_set_of_events();
void       read_inputfile (char *filename);
void       print_monList ();
int        fdToMonListIndex (int fd);

/*-----------------------------------------------------------------
 * NAME:    main or aha
 * PURPOSE: To digest the command line arguments and call the function
 *          to monitor a file or a set of files.
 */
int main(int argc, char *argv[])
{
    int    c;
    int    rc;

    cfgFile = NULL;
    monFile = NULL;

    progname = argv[0];

    if (argc < 2)
    {
        syntax();
        return (-1);
    }
    while ((c=getopt(argc,argv,"i:m:h")) != EOF)
    {
        switch(c)
        {
            case 'i':
                cfgFile = optarg;
                break;
            case 'm':
                monFile = optarg;
                if (argv[optind])
                    sprintf (monFileWrStr, "%s", argv[optind]);
                else
                    sprintf (monFileWrStr, "CHANGED=YES");
                break;
            case 'h':
                syntax();
                return (0);
            case '?':
                syntax();
                return (-1);
        }
    }

    if ( (monFile) && (cfgFile))
    {
        syntax();
        return (-1);
    }

    if (monFile)
        rc = monitor_a_event();           /* Monitor a file using select() */
    else
        rc = monitor_a_set_of_events();   /* Monitor a set of files using pollset() */
    return (rc);
}

/*-----------------------------------------------------------------
 * NAME: syntax()
 * PURPOSE: To display the syntax.
 */
void syntax (void)
{
    printf ("\nSYNTAX1: %s -i <aha-input-file> \n", progname);
    printf ("SYNTAX2: %s -m <aha-monitor-file> [<key1>=<value1>[;<key2>=<value2>;...]] \n",
            progname);
    printf(" where: \n");
    printf("  <aha-input-file>   : A file with list of AHA events and their thresholds\n");
    printf("                       in the format of the file \"aha.inp\".\n\n");
    printf("  <aha-monitor-file> : Full pathname of an AHA file with suffix \".mon\".\n");
    printf("  The possible keys and their values are:\n");
    printf("   --------------------------------------------------------------- \n");
    printf("        Keys  |       values             |      comments           \n");
    printf("   =============================================================== \n");
    printf("    WAIT_TYPE | WAIT_IN_SELECT (default) | Uses select() to wait.  \n");
    printf("              | WAIT_IN_READ             | Uses read() to wait.    \n");
    printf("   -----------|--------------------------|------------------------ \n");
    printf("    CHANGED   | YES (default)            | Monitors state-change.  \n");
    printf("              | Any string other than YES| CHANGED cannot be used  \n");
    printf("              |                          | together with THRESH_HI.\n");
    printf("   -----------|--------------------------|------------------------ \n");
    printf("    THRESH_HI | Positive integer         | Monitors high threshold.\n");
    printf("   ----------------------------------------------------------------\n\n");
    printf("Examples: \n");
    printf("   1: %s -i aha.inp\n", progname);
    printf("   2: %s -m /aha/fs/utilFs.monFactory/tmp.mon \"THRESH_HI=90\"\n", progname);
    printf("   3: %s -m /aha/fs/modFile.monFactory/etc/passwd.mon \"CHANGED=YES\" \n",
                                                                  progname);
    printf("   4: %s -m /aha/mem/vmo.monFactory/npskill.mon  \n", progname);
    printf("   5: %s -m /aha/cpu/waitTmCPU.monFactory/waitTmCPU.mon \n", progname);
    printf("                     \"WAIT_TYPE=WAIT_IN_READ;THRESH_HI=50\" \n");

}

/*---------------------------------------------------------------------------
 * NAME:  monitor_a_event()
 * PURPOSE: To monitor an AHA event using select().
 */
static int
monitor_a_event()
{
    int    fd, rc;
    fd_set readfds;
    char   waitInRead[] = "WAIT_TYPE=WAIT_IN_READ";

    if ( ! ahaMonFile(monFile) )  /* Not a .mon file under /aha */
            return (-1);

    /* Create intermediate directories of the .mon file */
    rc = mk_parent_dirs(monFile);
    if (rc)
    {
        fprintf (stderr,
             "Could not create intermediate directories of the file %s !\n", monFile);
        perror ("mkdir: ");
        return(-1);
    }

    printf ("wrstr = %s\n", monFileWrStr);
    fd = open (monFile, O_CREAT|O_RDWR);
    if (fd < 0)
    {
        fprintf (stderr, "Could not open the file %s; errno = %d !\n", monFile, errno);
        return(-1);
    }

    rc = write(fd, monFileWrStr, strlen(monFileWrStr)+1);
    if (rc < 0)
    {
        perror ("write: ");
        fprintf (stderr, "Failed writing to monFile %s !\n", monFile);
        return(-1);
    }
    if (strstr(monFileWrStr, waitInRead) == NULL)
    {
        FD_ZERO(&readfds);
        FD_SET(fd, &readfds);

        printf("Entering select() to wait till the event corresponding to the AHA node %s occurs.\n",
               monFile);
        printf("Please issue a command from another window to trigger this event.\n");

        rc = select (fd+1, &readfds, NULL, NULL, NULL);
        printf("\nThe select() completed. \n");
        if (rc <= 0) /* No event occurred or an error was found. */
        {
            fprintf (stderr, "The select() returned %d.\n", rc);
            perror ("select: ");
            return (-1);
        }
        if(! FD_ISSET(fd, &readfds))
                goto end;

        printf("The event corresponding to the AHA node %s has occurred.\n\n", monFile);
    }
    else
    {
        printf("Entering read() to wait till the event corresponding to the AHA node %s occurs.\n",monFile);
        printf("Please issue a command from another window to trigger this event.\n\n");
    }
    read_data(fd);
 end:
    close(fd);
    return (0);
}

/* --------------------------------------------------------------------------
 * NAME:  ahaMonFile()
 * PURPOSE: To check whether the file provided is an AHA monitor file.
 */
int
ahaMonFile(char *str)
{
    char cwd[PATH_MAX];
    int len1=strlen(str), len2=strlen(".mon");
    int rc = 0;
    struct stat     sbuf;

    /* Make sure that /aha is mounted. */
    if ((stat("/aha", &sbuf) < 0) ||
        (sbuf.st_flag != FS_MOUNT))
    {
        printf("ERROR: The filesystem /aha is not mounted!\n");
        return (rc);
    }

    /* Make sure that the file-pathname has .mon as its suffix. */
    if ( (len1 <= len2) ||
         (strcmp ( (str + len1 - len2), ".mon"))
        )
        goto end;

    if (! strncmp (str, "/aha",4))  /* The given pathname starts with /aha */
       rc = 1;
    else    /* It could be a relative path */
    {
        getcwd (cwd, PATH_MAX);
        if( (str[0] != '/' )  &&         /* Relative path and */
            (! strncmp (cwd, "/aha",4))  /* cwd starts with /aha . */
           )
           rc = 1;
    }
  end:
    if (!rc)
       printf("ERROR:  %s is not an AHA monitor file !\n", str);
    return (rc);
}

/*-----------------------------------------------------------------
 * NAME:  mk_parent_dirs()
 * PURPOSE: To create intermediate directories of a .mon file if
 *          they were not created.
 */
static int
mk_parent_dirs (char *path)
{
    char   s[PATH_MAX];
    char   *dirp;
    struct stat buf;
    int    rc=0;

    dirp = dirname(path);
    if (stat(dirp, &buf) != 0)
    {
        sprintf(s, "/usr/bin/mkdir -p %s", dirp);
        rc = system(s);
    }
    return (rc);
}

/*-----------------------------------------------------------------
 * NAME: read_data
 * PURPOSE: To parse and print the data received at the occurrence
 *          of the event.
 */
void
read_data (int fd)
{
  #define READ_BUF_SIZE  4096
    char   data[READ_BUF_SIZE];
    char   *p, *line;
    char   cmd[64];
    time_t sec, nsec;
    pid_t  pid;
    uid_t  uid, gid;
    gid_t  luid;
    char   curTm[64];
    int    n, seqnum;
    int    stackInfo = 0;
    char   uname[64], lname[64], gname[64];

    bzero((char *)data, READ_BUF_SIZE);
    
    /* Read the info from the beginning of the file. */
    n=pread(fd, data,READ_BUF_SIZE, 0);

    p = data;
    line=strsep(&p, "\n");
    while (line)
    {
        if( (!stackInfo) &&
            (sscanf(line,"TIME_tvsec=%ld",&sec) == 1))
        {
            ctime_r(&sec, curTm);
            if (sscanf(p,
           "TIME_tvnsec=%ld\nSEQUENCE_NUM=%d\nPID=%ld\nUID=%ld\nUID_LOGIN=%ld\nGID=%ld\nPROG_NAME=%s\n",
                        &nsec, &seqnum, &pid, &uid, &luid, &gid, cmd) == 7)
            {
                strcpy(uname, IDtouser(uid));
                strcpy(lname, IDtouser(luid));
                strcpy(gname, IDtogroup(gid));

                printf("Time          : %s",curTm);
                printf("Sequence Num  : %d\n",++seqnum);
                printf("Process ID    : %d\n", pid);
                printf("User Info     : userName=%s, loginName=%s, groupName=%s\n",
                       uname, lname, gname);
                printf("Program Name  : %s\n", cmd);
            }
            else if (sscanf(p,
           "TIME_tvnsec=%ld\nSEQUENCE_NUM=%d\n",
                        &nsec, &seqnum) == 2)
            {
                printf("Time          : %s",curTm);
                printf("Sequence Num  : %d\n",++seqnum);
	    }
            stackInfo=1;
        }
        if (!stackInfo)
	    printf ("%s\n", line);
	else if ((!strncmp(line, "RC_FROM_EVPROD",14)) || 
		 (!strncmp(line, "CURRENT_VALUE",13)))
	{
	    printf("%s\n%s\n", line, p);
	    goto out;
	}

        line=strsep(&p, "\n");
    };
 out:
    return;
}

/*-----------------------------------------------------------------
 * NAME:  monitor_a_set_of_events()
 * PURPOSE: To monitor a set of events using pollset().
 * DESCRIPTION:
 *    This function opens the .mon files in the /aha filesystem, and
 *    calls the pollset API to register for event notification
 *    using those .mon files' descriptors.
 *    This program waits for events to occur in an infinite loop.
 */
static int
monitor_a_set_of_events()
{
    pollset_t ps;
    int i, index, nmsgs, fd, rc;
    char   wrStr[MAX_WRITE_STR_LEN+1];
    int threshold;
    struct poll_ctl pollctl;
    struct pollfd pfds[MAX_EVENT_NOTIFICATIONS];

    ps = pollset_create (MAX_FD);
    read_inputfile (cfgFile);

    /* First open all the aha nodes given in the input file,
     * and add them to the pollset.
     */
    for (i=0; i<monListSz; i++)
    {
        /* Create intermediate directories of the .mon file */
        rc = mk_parent_dirs(monList[i].monFileName);
        if (rc)
        {
            fprintf(stderr,"Error creating parent directories of the monitor file %s.\n",
                     monList[i].monFileName);
            perror ("mkdir: ");
            rc = -1;
            goto out;
        }

        /* Open the file descriptor and write threshold value */
        fd = open(monList[i].monFileName, O_CREAT | O_RDWR);
        if (fd < 0)
        {
            fprintf(stderr, "Error opening the monitor file %s.\n",
                     monList[i].monFileName);
            perror ("open: ");
            rc = -1;
            goto out;
        }
        monList[i].fd = fd;
        threshold = monList[i].threshHi;
        if (monList[i].changed != AHAFS_THRESH_INVALID)
            sprintf (wrStr, "WAIT_TYPE=WAIT_IN_SELECT;CHANGED=YES");
        else
            sprintf (wrStr, "WAIT_TYPE=WAIT_IN_SELECT;THRESH_HI=%d",
                     monList[i].threshHi);

        /* printf ("wrStr = %s\n", wrStr); */

        rc = write (fd, wrStr, strlen(wrStr)+1);
        if (rc  < 0)
        {
            fprintf (stderr, "Error in write system call. errno = %d\n", errno);
            perror ("write: ");
            goto out;
        }

        /* Add the file descriptor to the pollset */
        pollctl.cmd = PS_ADD;
        pollctl.events = POLLIN | POLLOUT | POLLPRI;
        pollctl.fd = fd;
        pollset_ctl(ps, &pollctl, 1);
    }

 poll_again:
    print_monList();

    nmsgs = pollset_poll (ps, pfds, MAX_EVENT_NOTIFICATIONS, -1);
    if (nmsgs <= 0)
    {
        fprintf(stderr, "The pollset_poll() returned %d .\n", nmsgs);
        perror ("pollset_poll: ");
        goto out;
    }
    for (i=0; i<nmsgs; i++)
    {
        index = fdToMonListIndex(pfds[i].fd);
        if (index < 0)
        {
            fprintf (stderr, "Error! Mismatch between monList and pfds arrays!\n");
            continue;
        }

        printf("\n==================================================\n");
        printf("The AHAFS event occurred for the monitor node %s .\n\n",
               monList[index].monFileName);

        /* Before reading, reset the file offset to 0. */
        rc = lseek(pfds[i].fd, 0, SEEK_SET);
        if (rc < 0)
        {
            perror ("lseek error: ");
            continue;
        }

        read_data(pfds[i].fd);

   
        
        /* Add back the fd to the pollset */
        /* Only events of the CHANGED-type are re-registered; threshold-type of
         * events are not re-registered because doing so would result in too many
         * event notifications.
         */
        if (monList[index].changed != AHAFS_THRESH_INVALID)
        {
            /* Reset the event occurrence before restarting the monitoring.*/
            sprintf (wrStr, "WAIT_TYPE=WAIT_IN_SELECT;CHANGED=YES");
            write (pfds[i].fd, wrStr, strlen(wrStr)+1);

            /* Now, add the event to be monitored.*/
            pollctl.cmd = PS_ADD;
            pollctl.events = POLLIN | POLLOUT | POLLPRI;
            pollctl.fd = pfds[i].fd;
            pollset_ctl(ps, &pollctl, 1);
        }
        else
        {
            monList[index].stopped = 1;
            pollctl.cmd = PS_DELETE;
            pollctl.events = POLLIN | POLLOUT | POLLPRI;
            pollctl.fd = pfds[i].fd;
            pollset_ctl(ps, &pollctl, 1);
        }
    }
    goto poll_again;

 out:
    pollset_destroy (ps);
    return (rc);
}

/*-----------------------------------------------------------------
 * NAME:  read_inputfile (*filename)
 * PURPOSE:  To determine the list of events the user wants to
 *    register interest in. This function reads the input file to
 *    determine the list of .mon files and the associated threshold
 *    values.
 */
void read_inputfile (char *filename)
{
    FILE *fp;
    char *str;
    char sep[] = " \t\n";
    char *tok, *str2;
    int linenum, index, toknum;

    str = (char *) malloc (MAX_LINE_LENGTH+1);
    if (str == NULL)
    {
        perror ("malloc: ");
        exit (-1);
    }

    printf("\nAttempting to open the AHAFS configuration file %s .\n\n",
           filename);
    fp = fopen (filename, "r");
    if (fp == NULL)
    {
        perror ("fopen failed! ");
        exit (-1);
    }

    index = 0;
    linenum = 0;
    while (fgets(str, MAX_LINE_LENGTH, fp) != NULL)
    {
        linenum++;

        monList[index].threshHi = AHAFS_THRESH_INVALID;
        monList[index].changed  = AHAFS_THRESH_INVALID;
        monList[index].fd = -1;
        monList[index].stopped = 0;

        /* Remove the trailing newline character */
        if( *(str + (strlen(str)-1)) == '\n')
            *(str + (strlen(str)-1)) = '\0';

        /* Remove the leading and trailing white spaces */
        while( (*str == ' ') || (*str == '\t') )
            str++;

        /* Skip blank lines */
        if (strlen(str) == 0)
            continue;

        /* Skip comments; comment lines start with '#' */
        if (*str == '#')
            continue;

        /* First read the monitor filename */
        tok = strtok_r(str, sep, &str2);
        if (tok != NULL)
        {
            if ( ! ahaMonFile(tok) )  /* Not a .mon file under /aha */
                exit(-1);

            strncpy(monList[index].monFileName, tok, MAX_MONITOR_FILEPATH);
        }
        else
        {
            fprintf (stderr, "Invalid input file format at line %d!\n", linenum);
            exit (-1);
        }

        /* Next, read the thresholdHi value */
        tok = strtok_r(NULL, sep, &str2);
        if (tok != NULL)
        {
            if (isdigit(*tok))
                monList[index].threshHi = atoi(tok);
        }
        else
        {
            fprintf (stderr, "Invalid input file format at line %d!\n", linenum);
            fprintf (stderr, "Invalid 2nd column for the monitor file %s\n",
                     monList[index].monFileName);
            exit (-1);
        }

        /* Next, read the CHANGED value */
        tok = strtok_r(NULL, sep, &str2);
        if (tok != NULL)
        {
            if (isalpha(*tok))
            {
                *tok = toupper(*tok);
                *(tok+1) = toupper (*(tok+1));
                *(tok+2) = toupper (*(tok+2));

                if (!strncmp(tok, "YES", 3))
                    monList[index].changed = 1;
            }
        }
        else
        {
            fprintf (stderr, "Invalid input file format at line %d!\n", linenum);
            fprintf (stderr, "Invalid 3rd column for the monitor file %s\n",
                     monList[index].monFileName);
            exit (-1);
        }

        /* If thresholdHi is specified, then "CHANGED" should not be specified. */
        if ( (monList[index].threshHi != AHAFS_THRESH_INVALID)  &&
             (monList[index].changed != AHAFS_THRESH_INVALID))
        {
            fprintf (stderr, "Invalid input file format at line %d!\n", linenum);
            fprintf (stderr, "The CHANGED and THRESH_HI columns are"
                     " both specified for the monitor file %s .\n",
                     monList[index].monFileName);
            exit (-1);
        }
        index++;
    }
    monListSz = index;

    if (errno)
        perror("Error in fgets: ");
    fclose (fp);
}

/*-----------------------------------------------------------------
 * NAME: print_monList
 * PURPOSE: To print the list of .mon files and the threshold- or
 *          CHANGED-value read from the input file.
 */
void print_monList ()
{
    int i;
    printf("\n------------------------------------------------------\n");
    printf("Monitoring the following events.....\n");
    for (i=0; i<monListSz; i++)
    {
        if ( !monList[i].stopped )
           printf ("%s %d %d\n", monList[i].monFileName,
                    monList[i].threshHi, monList[i].changed);
    }
}

/*-----------------------------------------------------------------
 * NAME:  fdToMonListIndex (fd)
 * PURPOSE:  Given a file descriptor, this function returns
 *           its pathname under the /aha tree.
 */
int
fdToMonListIndex (int fd)
{
    int i;
    for (i=0; i<monListSz; i++)
    {
        if ( monList[i].fd == fd )
           return (i);
    }
    return (-1);
}
