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

#ifdef NETSNMP_CAN_USE_NLIST
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif

#if HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#if HAVE_KVM_H
#include <kvm.h>
#elif defined(HAVE_NLIST_H)
#include <nlist.h>
#endif

#include <net-snmp/agent/auto_nlist.h>
#include "autonlist.h"
#include "kernel.h"

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

struct autonlist *nlists = 0;
static void     init_nlist(struct nlist *);

long
auto_nlist_value(const char *string)
{
    struct autonlist **ptr, *it = 0;
    int             cmp;

    if (string == 0)
        return 0;

    ptr = &nlists;
    while (*ptr != 0 && it == 0) {
        cmp = strcmp((*ptr)->symbol, string);
        if (cmp == 0)
            it = *ptr;
        else if (cmp < 0) {
            ptr = &((*ptr)->left);
        } else {
            ptr = &((*ptr)->right);
        }
    }
    if (*ptr == 0) {
        *ptr = (struct autonlist *) malloc(sizeof(struct autonlist));
        memset(*ptr, 0, sizeof(struct autonlist));
        it = *ptr;
        it->left = 0;
        it->right = 0;
        it->symbol = (char *) malloc(strlen(string) + 1);
        strcpy(it->symbol, string);
        /*
         * allocate an extra byte for inclusion of a preceding '_' later 
         */
        it->nl[0].n_name = (char *) malloc(strlen(string) + 2);
#if defined(aix4) || defined(aix5) || defined(aix6) || defined(aix7)
        strcpy(it->nl[0].n_name, string);
        it->nl[0].n_name[strlen(string)+1] = '\0';
#elif defined(freebsd9)
        sprintf(__DECONST(char*, it->nl[0].n_name), "_%s", string);
#else
        {
            static char *name;

            free(name);
	    name = NULL;
	    if (asprintf(&name, "_%s", string) < 0) {
                snmp_log(LOG_ERR, "nlist err: failed to allocate memory");
                return -1;
            }
            it->nl[0].n_name = name;
        }
#endif
        it->nl[1].n_name = 0;
        init_nlist(it->nl);
#if !(defined(aix4) || defined(aix5) || defined(aix6) || defined(aix7) || \
                    defined(netbsd1) || defined(dragonfly))
        if (it->nl[0].n_type == 0) {
#if defined(freebsd9)
            strcpy(__DECONST(char*, it->nl[0].n_name), string);
            __DECONST(char*, it->nl[0].n_name)[strlen(string)+1] = '\0';
#else
            static char *n_name2 = NULL;

            if (n_name2 != NULL)
                free(n_name2);

            n_name2 = malloc(strlen(string) + 1);
            if (n_name2 == NULL) {
                snmp_log(LOG_ERR, "nlist err: failed to allocate memory");
                return (-1);
            }
            strcpy(n_name2, string);
            it->nl[0].n_name = n_name2;
#endif
            init_nlist(it->nl);
        }
#endif
        if (it->nl[0].n_type == 0) {
            if (!netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, 
					NETSNMP_DS_AGENT_NO_ROOT_ACCESS)) {
                snmp_log(LOG_ERR, "nlist err: neither %s nor _%s found.\n",
                         string, string);
	    }
            return (-1);
        } else {
            DEBUGMSGTL(("auto_nlist:auto_nlist_value",
			"found symbol %s at %lx.\n",
                        it->symbol, it->nl[0].n_value));
            return (it->nl[0].n_value);
        }
    } else
        return (it->nl[0].n_value);
}

int
auto_nlist(const char *string, char *var, size_t size)
{
    long            result;
    int             ret;
    result = auto_nlist_value(string);
    if (result != -1) {
        if (var != NULL) {
            ret = klookup(result, var, size);
            if (!ret)
                snmp_log(LOG_ERR,
                         "auto_nlist failed on %s at location %lx\n",
                         string, result);
            return ret;
        } else
            return 1;
    }
    return 0;
}

static void
init_nlist(struct nlist nl[])
{
    int             ret;
#if HAVE_KVM_OPENFILES
    kvm_t          *kernel;
    char            kvm_errbuf[4096];

    if ((kernel = kvm_openfiles(KERNEL_LOC, NULL, NULL, O_RDONLY, kvm_errbuf))
	== NULL) {
        if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, 
				   NETSNMP_DS_AGENT_NO_ROOT_ACCESS)) {
            return;
	} else {
            snmp_log_perror("kvm_openfiles");
            snmp_log(LOG_ERR, "kvm_openfiles: %s\n", kvm_errbuf);
            exit(1);
        }
    }
    if ((ret = kvm_nlist(kernel, nl)) == -1) {
        if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, 
				   NETSNMP_DS_AGENT_NO_ROOT_ACCESS)) {
            return;
	} else {
            snmp_log_perror("kvm_nlist");
            exit(1);
        }
    }
    kvm_close(kernel);
#else                           /* ! HAVE_KVM_OPENFILES */
#if (defined(aix4) || defined(aix5) || defined(aix6) || defined(aix7)) && defined(HAVE_KNLIST)
    if (knlist(nl, 1, sizeof(struct nlist)) == -1) {
        DEBUGMSGTL(("auto_nlist:init_nlist", "knlist failed on symbol:  %s\n",
                    nl[0].n_name));
        if (errno == EFAULT) {
            nl[0].n_type = 0;
            nl[0].n_value = 0;
        } else {
            snmp_log_perror("knlist");
            if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, 
				       NETSNMP_DS_AGENT_NO_ROOT_ACCESS)) {
                return;
	    } else {
                exit(1);
	    }
        }
    }
#else
    if ((ret = nlist(KERNEL_LOC, nl)) == -1) {
        if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, 
				   NETSNMP_DS_AGENT_NO_ROOT_ACCESS)) {
            return;
	} else {
            snmp_log_perror("nlist");
            exit(1);
        }
    }
#endif                          /*aix4 */
#endif                          /* ! HAVE_KVM_OPENFILES */
    for (ret = 0; nl[ret].n_name != NULL; ret++) {
#if defined(aix4) || defined(aix5) || defined(aix6) || defined(aix7)
        if (nl[ret].n_type == 0 && nl[ret].n_value != 0)
            nl[ret].n_type = 1;
#endif
        if (nl[ret].n_type == 0) {
            if (!netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, 
					NETSNMP_DS_AGENT_NO_ROOT_ACCESS)) {
                DEBUGMSGTL(("auto_nlist:init_nlist", "nlist err:  %s not found\n",
                            nl[ret].n_name));
	    }
        } else {
            DEBUGMSGTL(("auto_nlist:init_nlist", "nlist: %s 0x%X\n", nl[ret].n_name,
                        (unsigned int) nl[ret].n_value));
        }
    }
}

int
KNLookup(struct nlist nl[], int nl_which, char *buf, size_t s)
{
    struct nlist   *nlp = &nl[nl_which];

    if (nlp->n_value == 0) {
        snmp_log(LOG_ERR, "Accessing non-nlisted variable: %s\n",
                 nlp->n_name);
        nlp->n_value = -1;      /* only one error message ... */
        return 0;
    }
    if (nlp->n_value == -1)
        return 0;

    return klookup(nlp->n_value, buf, s);
}

#ifdef TESTING
void
auto_nlist_print_tree(int indent, struct autonlist *ptr)
{
    char            buf[1024];
    if (indent == -2) {
        snmp_log(LOG_ERR, "nlist tree:\n");
        auto_nlist_print_tree(12, nlists);
    } else {
        if (ptr == 0)
            return;
        sprintf(buf, "%%%ds\n", indent);
        /*
         * DEBUGMSGTL(("auto_nlist", "buf: %s\n",buf)); 
         */
        DEBUGMSGTL(("auto_nlist", buf, ptr->symbol));
        auto_nlist_print_tree(indent + 2, ptr->left);
        auto_nlist_print_tree(indent + 2, ptr->right);
    }
}
#endif
#else                           /* !NETSNMP_CAN_USE_NLIST */
#include <net-snmp/agent/auto_nlist.h>
int
auto_nlist_noop(void)
{
    return 0;
}
#endif                          /* NETSNMP_CAN_USE_NLIST */
