#include <net-snmp/net-snmp-config.h>

#ifdef solaris2
#define _KMEMUSER               /* Needed by <sys/user.h> */
#include <sys/types.h>          /* helps define struct rlimit */
#endif

#if HAVE_IO_H                   /* win32 */
#include <io.h>
#endif
#if HAVE_STDLIB_H
#include <stdlib.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#if HAVE_MALLOC_H
#include <malloc.h>
#endif
#include <math.h>
#include <ctype.h>
#include <sys/types.h>
#if HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#if TIME_WITH_SYS_TIME
# ifdef WIN32
#  include <sys/timeb.h>
# else
#  include <sys/time.h>
# endif
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif
#if HAVE_KVM_H
#include <kvm.h>
#endif
#if HAVE_WINSOCK_H
#include <winsock.h>
#endif

#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>

#include "struct.h"
#include "proc.h"
#ifdef USING_UCD_SNMP_ERRORMIB_MODULE
#include "errormib.h"
#else
#define setPerrorstatus(x) snmp_log_perror(x)
#endif
#include "util_funcs.h"
#include "kernel.h"

static struct myproc *get_proc_instance(struct myproc *, oid);
struct myproc  *procwatch = NULL;
static struct extensible fixproc;
int             numprocs = 0;

void
init_proc(void)
{

    /*
     * define the structure we're going to ask the agent to register our
     * information at 
     */
    struct variable2 extensible_proc_variables[] = {
        {MIBINDEX, ASN_INTEGER, RONLY, var_extensible_proc, 1, {MIBINDEX}},
        {ERRORNAME, ASN_OCTET_STR, RONLY, var_extensible_proc, 1,
         {ERRORNAME}},
        {PROCMIN, ASN_INTEGER, RONLY, var_extensible_proc, 1, {PROCMIN}},
        {PROCMAX, ASN_INTEGER, RONLY, var_extensible_proc, 1, {PROCMAX}},
        {PROCCOUNT, ASN_INTEGER, RONLY, var_extensible_proc, 1,
         {PROCCOUNT}},
        {ERRORFLAG, ASN_INTEGER, RONLY, var_extensible_proc, 1,
         {ERRORFLAG}},
        {ERRORMSG, ASN_OCTET_STR, RONLY, var_extensible_proc, 1,
         {ERRORMSG}},
        {ERRORFIX, ASN_INTEGER, RWRITE, var_extensible_proc, 1,
         {ERRORFIX}},
        {ERRORFIXCMD, ASN_OCTET_STR, RONLY, var_extensible_proc, 1,
         {ERRORFIXCMD}}
    };

    /*
     * Define the OID pointer to the top of the mib tree that we're
     * registering underneath 
     */
    oid             proc_variables_oid[] = { NETSNMP_UCDAVIS_MIB, NETSNMP_PROCMIBNUM, 1 };

    /*
     * register ourselves with the agent to handle our mib tree 
     */
    REGISTER_MIB("ucd-snmp/proc", extensible_proc_variables, variable2,
                 proc_variables_oid);

    snmpd_register_config_handler("proc", proc_parse_config,
                                  proc_free_config,
                                  "process-name [max-num] [min-num]");
    snmpd_register_config_handler("procfix", procfix_parse_config, NULL,
                                  "process-name program [arguments...]");
}


/*
 * Define snmpd.conf reading routines first.  They get called
 * automatically by the invocation of a macro in the proc.h file. 
 */

void
proc_free_config(void)
{
    struct myproc  *ptmp, *ptmp2;

    for (ptmp = procwatch; ptmp != NULL;) {
        ptmp2 = ptmp;
        ptmp = ptmp->next;
        free(ptmp2);
    }
    procwatch = NULL;
    numprocs = 0;
}

/*
 * find a give entry in the linked list associated with a proc name 
 */
static struct myproc *
get_proc_by_name(char *name)
{
    struct myproc  *ptmp;

    if (name == NULL)
        return NULL;

    for (ptmp = procwatch; ptmp != NULL && strcmp(ptmp->name, name) != 0;
         ptmp = ptmp->next);
    return ptmp;
}

void
procfix_parse_config(const char *token, char *cptr)
{
    char            tmpname[STRMAX];
    struct myproc  *procp;

    /*
     * don't allow two entries with the same name 
     */
    cptr = copy_nword(cptr, tmpname, sizeof(tmpname));
    if ((procp = get_proc_by_name(tmpname)) == NULL) {
        config_perror("No proc entry registered for this proc name yet.");
        return;
    }

    if (strlen(cptr) > sizeof(procp->fixcmd)) {
        config_perror("fix command too long.");
        return;
    }

    strcpy(procp->fixcmd, cptr);
}


void
proc_parse_config(const char *token, char *cptr)
{
    char            tmpname[STRMAX];
    struct myproc **procp = &procwatch;

    /*
     * don't allow two entries with the same name 
     */
    copy_nword(cptr, tmpname, sizeof(tmpname));
    if (get_proc_by_name(tmpname) != NULL) {
        config_perror("Already have an entry for this process.");
        return;
    }

    /*
     * skip past used ones 
     */
    while (*procp != NULL)
        procp = &((*procp)->next);

    (*procp) = (struct myproc *) calloc(1, sizeof(struct myproc));
    if (*procp == NULL)
        return;                 /* memory alloc error */
    numprocs++;
    /*
     * not blank and not a comment 
     */
    copy_nword(cptr, (*procp)->name, sizeof((*procp)->name));
    cptr = skip_not_white(cptr);
    if ((cptr = skip_white(cptr))) {
        (*procp)->max = atoi(cptr);
        cptr = skip_not_white(cptr);
        if ((cptr = skip_white(cptr)))
            (*procp)->min = atoi(cptr);
        else
            (*procp)->min = 0;
    } else {
        (*procp)->max = 0;
        (*procp)->min = 0;
    }
#ifdef NETSNMP_PROCFIXCMD
    sprintf((*procp)->fixcmd, NETSNMP_PROCFIXCMD, (*procp)->name);
#endif
    DEBUGMSGTL(("ucd-snmp/proc", "Read:  %s (%d) (%d)\n",
                (*procp)->name, (*procp)->max, (*procp)->min));
}

/*
 * The routine that handles everything 
 */

u_char         *
var_extensible_proc(struct variable *vp,
                    oid * name,
                    size_t * length,
                    int exact,
                    size_t * var_len, WriteMethod ** write_method)
{

    struct myproc  *proc;
    static long     long_ret;
    static char     errmsg[300];


    if (header_simple_table
        (vp, name, length, exact, var_len, write_method, numprocs))
        return (NULL);

    if ((proc = get_proc_instance(procwatch, name[*length - 1]))) {
        switch (vp->magic) {
        case MIBINDEX:
            long_ret = name[*length - 1];
            return ((u_char *) (&long_ret));
        case ERRORNAME:        /* process name to check for */
            *var_len = strlen(proc->name);
            return ((u_char *) (proc->name));
        case PROCMIN:
            long_ret = proc->min;
            return ((u_char *) (&long_ret));
        case PROCMAX:
            long_ret = proc->max;
            return ((u_char *) (&long_ret));
        case PROCCOUNT:
            long_ret = sh_count_procs(proc->name);
            return ((u_char *) (&long_ret));
        case ERRORFLAG:
            long_ret = sh_count_procs(proc->name);
            if (long_ret >= 0 &&
                ((proc->min && long_ret < proc->min) ||
                 (proc->max && long_ret > proc->max) ||
                 (proc->min == 0 && proc->max == 0 && long_ret < 1))) {
                long_ret = 1;
            } else {
                long_ret = 0;
            }
            return ((u_char *) (&long_ret));
        case ERRORMSG:
            long_ret = sh_count_procs(proc->name);
            if (long_ret < 0) {
                errmsg[0] = 0;  /* catch out of mem errors return 0 count */
            } else if (proc->min && long_ret < proc->min) {
                snprintf(errmsg, sizeof(errmsg),
                        "Too few %s running (# = %d)",
                        proc->name, (int) long_ret);
            } else if (proc->max && long_ret > proc->max) {
                snprintf(errmsg, sizeof(errmsg),
                        "Too many %s running (# = %d)",
                        proc->name, (int) long_ret);
            } else if (proc->min == 0 && proc->max == 0 && long_ret < 1) {
                snprintf(errmsg, sizeof(errmsg),
                        "No %s process running.", proc->name);
            } else {
                errmsg[0] = 0;
            }
            errmsg[ sizeof(errmsg)-1 ] = 0;
            *var_len = strlen(errmsg);
            return ((u_char *) errmsg);
        case ERRORFIX:
            *write_method = fixProcError;
            long_return = fixproc.result;
            return ((u_char *) & long_return);
        case ERRORFIXCMD:
            if (proc->fixcmd) {
                *var_len = strlen(proc->fixcmd);
                return (u_char *) proc->fixcmd;
            }
            errmsg[0] = 0;
            *var_len = 0;
            return ((u_char *) errmsg);
        }
        return NULL;
    }
    return NULL;
}

int
fixProcError(int action,
             u_char * var_val,
             u_char var_val_type,
             size_t var_val_len,
             u_char * statP, oid * name, size_t name_len)
{

    struct myproc  *proc;
    long            tmp = 0;

    if ((proc = get_proc_instance(procwatch, name[10]))) {
        if (var_val_type != ASN_INTEGER) {
            snmp_log(LOG_ERR, "Wrong type != int\n");
            return SNMP_ERR_WRONGTYPE;
        }
        tmp = *((long *) var_val);
        if (tmp == 1 && action == COMMIT) {
            if (proc->fixcmd[0]) {
                strcpy(fixproc.command, proc->fixcmd);
                exec_command(&fixproc);
            }
        }
        return SNMP_ERR_NOERROR;
    }
    return SNMP_ERR_WRONGTYPE;
}

static struct myproc *
get_proc_instance(struct myproc *proc, oid inst)
{
    int             i;

    if (proc == NULL)
        return (NULL);
    for (i = 1; (i != (int) inst) && (proc != NULL); i++)
        proc = proc->next;
    return (proc);
}

#ifdef bsdi2
#include <sys/param.h>
#include <sys/sysctl.h>

#define PP(pp, field) ((pp)->kp_proc . field)
#define EP(pp, field) ((pp)->kp_eproc . field)
#define VP(pp, field) ((pp)->kp_eproc.e_vm . field)

/*
 * these are for keeping track of the proc array 
 */

static size_t   nproc = 0;
static size_t   onproc = -1;
static struct kinfo_proc *pbase = 0;

int
sh_count_procs(char *procname)
{
    register int    i, ret = 0;
    register struct kinfo_proc *pp;
    static int      mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };

    if (sysctl(mib, 3, NULL, &nproc, NULL, 0) < 0)
        return 0;

    if (nproc > onproc || !pbase) {
        if ((pbase = (struct kinfo_proc *) realloc(pbase,
                                                   nproc +
                                                   sizeof(struct
                                                          kinfo_proc))) ==
            0)
            return -1;
        onproc = nproc;
        memset(pbase, 0, nproc + sizeof(struct kinfo_proc));
    }

    if (sysctl(mib, 3, pbase, &nproc, NULL, 0) < 0)
        return -1;

    for (pp = pbase, i = 0; i < nproc / sizeof(struct kinfo_proc);
         pp++, i++) {
        if (PP(pp, p_stat) != 0 && (((PP(pp, p_flag) & P_SYSTEM) == 0))) {
            if (PP(pp, p_stat) != SZOMB
                && !strcmp(PP(pp, p_comm), procname))
                ret++;
        }
    }
    return ret;
}

#elif defined(aix4) || defined(aix5) || defined(aix6)
#include <procinfo.h>
#include <sys/types.h>

struct procsinfo pinfo;
char pinfo_name[256];

int
sh_count_procs(char *procname)
{
    pid_t index;
    int count;
    char *sep;
    
    index = 0;
    count = 0;

    while(getprocs(&pinfo, sizeof(pinfo), NULL, 0, &index, 1) == 1) {
        strncpy(pinfo_name, pinfo.pi_comm, 256);
        pinfo_name[255] = 0;
        sep = strchr(pinfo_name, ' ');
        if(sep != NULL) *sep = 0;
        if(strcmp(procname, pinfo_name) == 0) count++;
    }

    return count;
}

#elif NETSNMP_OSTYPE == NETSNMP_LINUXID

#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>

int
sh_count_procs(char *procname)
{
    DIR *dir;
    char cmdline[512], *tmpc;
    char state[64];
    struct dirent *ent;
#ifdef USE_PROC_CMDLINE
    int fd;
#endif
    int len,plen=strlen(procname),total = 0;
    FILE *status;

    if ((dir = opendir("/proc")) == NULL) return -1;
    while (NULL != (ent = readdir(dir))) {
      if(!(ent->d_name[0] >= '0' && ent->d_name[0] <= '9')) continue;
#ifdef USE_PROC_CMDLINE  /* old method */
      /* read /proc/XX/cmdline */
      sprintf(cmdline,"/proc/%s/cmdline",ent->d_name);
      if((fd = open(cmdline, O_RDONLY)) < 0) continue;
      len = read(fd,cmdline,sizeof(cmdline) - 1);
      close(fd);
      if(len <= 0) continue;
      cmdline[len] = 0;
      while(--len && !cmdline[len]);
      if(len <= 0) continue;
      while(--len) if(!cmdline[len]) cmdline[len] = ' ';
      if(!strncmp(cmdline,procname,plen)) total++;
#else
      /* read /proc/XX/status */
      sprintf(cmdline,"/proc/%s/status",ent->d_name);
      if ((status = fopen(cmdline, "r")) == NULL)
          continue;
      if (fgets(cmdline, sizeof(cmdline), status) == NULL) {
          fclose(status);
          continue;
      }
      /* Grab the state of the process as well
       * (so we can ignore zombie processes)
       * XXX: Assumes the second line is the status
       */
      if (fgets(state, sizeof(state), status) == NULL) {
          state[0]='\0';
      }
      fclose(status);
      cmdline[sizeof(cmdline)-1] = '\0';
      state[sizeof(state)-1] = '\0';
      /* XXX: assumes Name: is first */
      if (strncmp("Name:",cmdline, 5) != 0)
          break;
      tmpc = skip_token(cmdline);
      if (!tmpc)
          break;
      for (len=0;; len++) {
	if (tmpc[len] && isgraph(tmpc[len])) continue;
	tmpc[len]='\0';
	break;
      }
      DEBUGMSGTL(("proc","Comparing wanted %s against %s\n",
                  procname, tmpc));
      if(len==plen && !strncmp(tmpc,procname,plen)) {
          /* Do not count zombie process as they are not running processes */
          if ( strstr(state, "zombie") == NULL ) {
              total++;
              DEBUGMSGTL(("proc", " Matched.  total count now=%d\n", total));
          } else {
              DEBUGMSGTL(("proc", " Skipping zombie process.\n"));
          }
      }
#endif      
    }
    closedir(dir);
    return total;
}

#elif NETSNMP_OSTYPE == NETSNMP_ULTRIXID

#define	NPROCS		32      /* number of proces to read at once */

extern int      kmem, mem, swap;

#include <sys/user.h>
#include <sys/proc.h>
#include <sys/file.h>
#include <sys/vm.h>
#include <machine/pte.h>
#ifdef HAVE_NLIST_H
#include <nlist.h>
#endif

static struct user *getuser(struct proc *);
static int      getword(off_t);
static int      getstruct(off_t, char *, off_t, int);

static struct nlist proc_nl[] = {
    {"_nproc"},
#define X_NPROC		0
    {"_proc"},
#define X_PROC		1
    {"_proc_bitmap"},
#define X_PROC_BITMAP	2
    {NULL}
};

int
sh_count_procs(char *procname)
{
    int             total, proc_active, nproc;
    int             thisproc = 0;
    int             absolute_proc_number = -1;
    struct user    *auser;
    struct proc    *aproc, *procp;
    unsigned        bitmap;
    struct proc     procs[NPROCS], *procsp;
    static int      inited = 0;

    procp = (struct proc *) getword(proc_nl[X_PROC].n_value);
    nproc = getword(proc_nl[X_NPROC].n_value);

    total = 0;
    for (;;) {
        do {
            while (thisproc == 0) {
                int             nread;
                int             psize;

                if (nproc == 0)
                    return (total);

                thisproc = MIN(NPROCS, nproc);
                psize = thisproc * sizeof(struct proc);
                nproc -= thisproc;
                if (lseek(kmem, (off_t) procp, L_SET) == -1 ||
                    (nread = read(kmem, (char *) procs, psize)) < 0) {
                    /*
                     * warn("read proc"); 
                     */
                    return (total);
                } else if (nread != psize) {
                    thisproc = nread / sizeof(struct proc);
                    nproc = 0;
                    /*
                     * warn("read proc: short read"); 
                     */
                }
                procsp = procs;
                procp += thisproc;
            }

            aproc = procsp++;
            thisproc--;

            absolute_proc_number++;
            if ((absolute_proc_number % 32) == 0)
                bitmap =
                    getword((unsigned int) proc_nl[X_PROC_BITMAP].n_value +
                            ((absolute_proc_number / 32) * 4));
            proc_active =
                (bitmap & (1 << (absolute_proc_number % 32))) != 0;
            if (proc_active && aproc->p_stat != SZOMB
                && !(aproc->p_type & SWEXIT))
                auser = getuser(aproc);
        } while (!proc_active || auser == NULL);

        if (strcmp(auser->u_comm, procname) == 0)
            total++;
    }
}

#define	SW_UADDR	dtob(getword((off_t)dmap.dm_ptdaddr))
#define	SW_UBYTES	sizeof(struct user)

#define	SKRD(file, src, dst, size)			\
	(lseek(file, (off_t)(src), L_SET) == -1) ||	\
	(read(file, (char *)(dst), (size)) != (size))

static struct user *
getuser(struct proc *aproc)
{
    static union {
        struct user     user;
        char            upgs[UPAGES][NBPG];
    } u;
    static struct pte uptes[UPAGES];
    static struct dmap dmap;
    int             i, nbytes;

    /*
     * If process is not in core, we simply snarf it's user struct
     * from the swap device.
     */
    if ((aproc->p_sched & SLOAD) == 0) {
        if (!getstruct
            ((off_t) aproc->p_smap, "aproc->p_smap", (off_t) & dmap,
             sizeof(dmap))) {
            /*
             * warnx("can't read dmap for pid %d from %s", aproc->p_pid,
             * _PATH_DRUM); 
             */
            return (NULL);
        }
        if (SKRD(swap, SW_UADDR, &u.user, SW_UBYTES)) {
            /*
             * warnx("can't read u for pid %d from %s", aproc->p_pid, _PATH_DRUM); 
             */
            return (NULL);
        }
        return (&u.user);
    }

    /*
     * Process is in core.  Follow p_addr to read in the page
     * table entries that map the u-area and then read in the
     * physical pages that comprise the u-area.
     *
     * If at any time, an lseek() or read() fails, print a warning
     * message and return NULL.
     */
    if (SKRD(kmem, aproc->p_addr, uptes, sizeof(uptes))) {
        /*
         * warnx("can't read user pt for pid %d from %s", aproc->p_pid, _PATH_DRUM); 
         */
        return (NULL);
    }

    nbytes = sizeof(struct user);
    for (i = 0; i < UPAGES && nbytes > 0; i++) {
        if (SKRD(mem, ptob(uptes[i].pg_pfnum), u.upgs[i], NBPG)) {
            /*
             * warnx("can't read user page %u for pid %d from %s",
             * uptes[i].pg_pfnum, aproc->p_pid, _PATH_MEM); 
             */
            return (NULL);
        }
        nbytes -= NBPG;
    }
    return (&u.user);
}

static int
getword(off_t loc)
{
    int             val;

    if (SKRD(kmem, loc, &val, sizeof(val)))
        exit(1);
    return (val);
}

static int
getstruct(off_t loc, char *name, off_t dest, int size)
{
    if (SKRD(kmem, loc, dest, size))
        return (0);
    return (1);
}
#elif NETSNMP_OSTYPE == NETSNMP_SOLARISID

#ifdef _SLASH_PROC_METHOD_

#include <fcntl.h>
#include <dirent.h>

#include <procfs.h>

/*
 * Gets process information from /proc/.../psinfo
 */

int
sh_count_procs(char *procname)
{
    int             fd, total = 0;
    struct psinfo   info;
    char            fbuf[32];
    struct dirent  *ent;
    DIR            *dir;

    if (!(dir = opendir("/proc"))) {
        snmp_perror("/proc");
        return -1;
    }

    while ((ent = readdir(dir))) {
        if (!strcmp(ent->d_name, "..") || !strcmp(ent->d_name, "."))
            continue;

        snprintf(fbuf, sizeof fbuf, "/proc/%s/psinfo", ent->d_name);
        if ((fd = open(fbuf, O_RDONLY)) < 0) {  /* Continue or return error? */
            snmp_perror(fbuf);
	    continue;
        }

        if (read(fd, (char *) &info, sizeof(struct psinfo)) !=
            sizeof(struct psinfo)) {
            snmp_perror(fbuf);
            close(fd);
            closedir(dir);
            return -1;
        }

        if (!info.pr_nlwp && !info.pr_lwp.pr_lwpid) {
            /*
             * Zombie process 
             */
        } else {
            DEBUGMSGTL(("proc","Comparing wanted %s against %s\n",
                        procname, info.pr_fname));
            if (!strcmp(procname, info.pr_fname)) {
                total++;
                DEBUGMSGTL(("proc", " Matched.  total count now=%d\n", total));
            }
        }

        close(fd);
    }
    closedir(dir);
    return total;
}

#else                           /* _SLASH_PROC_METHOD_ */

#define _KMEMUSER               /* Needed by <sys/user.h> */

#include <kvm.h>
#include <fcntl.h>
#include <sys/user.h>
#include <sys/proc.h>

int
sh_count_procs(char *procname)
{
    struct proc    *p;
    struct user    *u;
    int             total;

    if (kd == NULL) {
        return -1;
    }
    if (kvm_setproc(kd) < 0) {
        return (-1);
    }
    kvm_setproc(kd);
    total = 0;
    while ((p = kvm_nextproc(kd)) != NULL) {
        if (!p) {
            return (-1);
        }
        u = kvm_getu(kd, p);
        /*
         * Skip this entry if u or u->u_comm is a NULL pointer 
         */
        if (!u) {
            continue;
        }
        if (strcmp(procname, u->u_comm) == 0)
            total++;
    }
    return (total);
}
#endif                          /* _SLASH_PROC_METHOD_ */
#else
int
sh_count_procs(char *procname)
{
    char            line[STRMAX], *cptr, *cp;
    int             ret = 0, fd;
    FILE           *file;
#ifndef NETSNMP_EXCACHETIME
#endif
    struct extensible ex;
    int             slow = strstr(PSCMD, "ax") != NULL;

    strcpy(ex.command, PSCMD);
    if ((fd = get_exec_output(&ex)) >= 0) {
        if ((file = fdopen(fd, "r")) == NULL) {
            setPerrorstatus("fdopen");
            close(fd);
            return (-1);
        }
        while (fgets(line, sizeof(line), file) != NULL) {
            if (slow) {
                cptr = find_field(line, 5);
                cp = strrchr(cptr, '/');
                if (cp)
                    cptr = cp + 1;
                else if (*cptr == '-')
                    cptr++;
                else if (*cptr == '[') {
                    cptr++;
                    cp = strchr(cptr, ']');
                    if (cp)
                        *cp = 0;
                }
                copy_nword(cptr, line, sizeof(line));
                cp = line + strlen(line) - 1;
                if (*cp == ':')
                    *cp = 0;
            } else {
                if ((cptr = find_field(line, NETSNMP_LASTFIELD)) == NULL)
                    continue;
                copy_nword(cptr, line, sizeof(line));
            }
            if (!strcmp(line, procname))
                ret++;
        }
        if (ftell(file) < 2) {
#ifdef USING_UCD_SNMP_ERRORMIB_MODULE
            seterrorstatus("process list unreasonable short (mem?)", 2);
#endif
            ret = -1;
        }
        fclose(file);
        wait_on_exec(&ex);
    } else {
        ret = -1;
    }
    return (ret);
}
#endif
