/*
 * ipmish.c
 *
 * MontaVista IPMI basic UI to use the main UI code.
 *
 * Author: MontaVista Software, Inc.
 *         Corey Minyard <minyard@mvista.com>
 *         source@mvista.com
 *
 * Copyright 2002,2003 MontaVista Software Inc.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2 of
 *  the License, or (at your option) any later version.
 *
 *
 *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 *  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 *  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 *  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this program; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <config.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <termios.h>
#include <unistd.h>
#include <signal.h>
#include <OpenIPMI/selector.h>
#include <OpenIPMI/ipmiif.h>
#include <OpenIPMI/ipmi_conn.h>
#include <OpenIPMI/ipmi_err.h>
#include <OpenIPMI/ipmi_posix.h>
#include <OpenIPMI/ipmi_glib.h>
#include <OpenIPMI/ipmi_tcl.h>
#include <OpenIPMI/ipmi_cmdlang.h>
#include <OpenIPMI/ipmi_debug.h>
#include <editline/readline.h>

#ifdef HAVE_GLIB
#include <glib.h>
#endif

/* Internal includes, do not use in your programs */
#include <OpenIPMI/internal/ipmi_malloc.h>

#ifdef HAVE_UCDSNMP
# ifdef HAVE_NETSNMP
#  include <net-snmp/net-snmp-config.h>
#  include <net-snmp/net-snmp-includes.h>
# elif defined(HAVE_ALT_UCDSNMP_DIR)
#  include <ucd-snmp/asn1.h>
#  include <ucd-snmp/snmp_api.h>
#  include <ucd-snmp/snmp.h>
# else
#  include <asn1.h>
#  include <snmp_api.h>
#  include <snmp.h>
# endif
#endif

extern os_handler_t ipmi_debug_os_handlers;
struct selector_s *debug_sel;

os_hnd_fd_id_t *term_fd_id;

static int done = 0;
static int evcount = 0;
static int handling_input = 0;
static int cmd_redisp = 1;
#ifdef HAVE_UCDSNMP
static int do_snmp = 0;
#endif

static void user_input_ready(int fd, void *data, os_hnd_fd_id_t *id);

static void
redraw_cmdline(int force)
{
    int redisp = cmd_redisp;

    if (force)
	redisp = 1;
    if (!done && handling_input && redisp) {
	rl_redisplay();
	fflush(stdout);
    }
}

static void
my_vlog(os_handler_t         *handler,
	const char           *format,
	enum ipmi_log_type_e log_type,
	va_list              ap)
{
    int do_nl = 1;
    static int last_was_cont = 0;

    if (handling_input && !last_was_cont && !done && cmd_redisp) 
	fputc('\n', stdout);

    last_was_cont = 0;
    switch(log_type) {
    case IPMI_LOG_INFO:
	printf("INFO: ");
	break;

    case IPMI_LOG_WARNING:
	printf("WARN: ");
	break;

    case IPMI_LOG_SEVERE:
	printf("SEVR: ");
	break;

    case IPMI_LOG_FATAL:
	printf("FATL: ");
	break;

    case IPMI_LOG_ERR_INFO:
	printf("EINF: ");
	break;

    case IPMI_LOG_DEBUG_START:
	do_nl = 0;
	last_was_cont = 1;
	/* FALLTHROUGH */
    case IPMI_LOG_DEBUG:
	printf("DEBG: ");
	break;

    case IPMI_LOG_DEBUG_CONT:
	last_was_cont = 1;
	do_nl = 0;
	/* FALLTHROUGH */
    case IPMI_LOG_DEBUG_END:
	break;
    }

    vprintf(format, ap);
    if (do_nl) {
	printf("\n");
	redraw_cmdline(0);
    }
}

#ifdef HAVE_GLIB
void glib_handle_log(const gchar *log_domain,
		     GLogLevelFlags log_level,
		     const gchar *message,
		     gpointer user_data)
{
    char *pfx = "";

    if (log_level & G_LOG_LEVEL_ERROR)
	pfx = "FATL: ";
    else if (log_level & G_LOG_LEVEL_CRITICAL)
	pfx = "SEVR: ";
    else if (log_level & G_LOG_LEVEL_WARNING)
	pfx = "WARN: ";
    else if (log_level & G_LOG_LEVEL_MESSAGE)
	pfx = "EINF: ";
    else if (log_level & G_LOG_LEVEL_INFO)
	pfx = "INFO: ";
    else if (log_level & G_LOG_LEVEL_DEBUG)
	pfx = "DEBG: ";
    printf("%s%s\n", pfx, message);
    redraw_cmdline(0);
}
#endif

static void
enable_term_fd(ipmi_cmdlang_t *cmdlang)
{
    int rv;

    if (term_fd_id)
	return;

    rv = cmdlang->os_hnd->add_fd_to_wait_for(cmdlang->os_hnd, 0, 
					     user_input_ready,
					     cmdlang,
					     NULL, &term_fd_id);
    if (rv) {
	fprintf(stderr, "error enabling terminal handler, giving up\n");
	exit(1);
    }
}

static void
disable_term_fd(ipmi_cmdlang_t *cmdlang)
{
    int rv;

    if (!term_fd_id)
	return;

    rv = cmdlang->os_hnd->remove_fd_to_wait_for(cmdlang->os_hnd, term_fd_id);
    if (rv) {
	fprintf(stderr, "error removing terminal handler, giving up\n");
	exit(1);
    }
    term_fd_id = NULL;
}

#ifdef HAVE_UCDSNMP
#define IPMI_OID_SIZE 9
static oid ipmi_oid[IPMI_OID_SIZE] = {1,3,6,1,4,1,3183,1,1};
int snmp_input(int op,
	       struct snmp_session *session,
	       int reqid,
	       struct snmp_pdu *pdu,
	       void *magic)
{
    struct sockaddr_in   *src_ip;
    uint32_t             specific;
    struct variable_list *var;

#ifdef HAVE_NETSNMP
    if (op != NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE)
	goto out;
#else
    if (op != RECEIVED_MESSAGE)
	goto out;
#endif
    if (pdu->command != SNMP_MSG_TRAP)
	goto out;
    if (snmp_oid_compare(ipmi_oid, IPMI_OID_SIZE,
			 pdu->enterprise, pdu->enterprise_length)
	!= 0)
    {
	goto out;
    }
    if (pdu->trap_type != SNMP_TRAP_ENTERPRISESPECIFIC)
	goto out;

    src_ip = (struct sockaddr_in *) &pdu->agent_addr;
    specific = pdu->specific_type;

    var = pdu->variables;
    if (var == NULL)
	goto out;
    if (var->type != ASN_OCTET_STR)
	goto out;
    if (snmp_oid_compare(ipmi_oid, IPMI_OID_SIZE, var->name, var->name_length)
	!= 0)
    {
	goto out;
    }
    if (var->val_len < 46)
	goto out;
    
    ipmi_handle_snmp_trap_data(src_ip,
		    	       sizeof(*src_ip),
			       IPMI_EXTERN_ADDR_IP,
			       specific,
			       var->val.string,
			       var->val_len);

 out:
    return 1;
}

#ifdef HAVE_NETSNMP
static int
snmp_pre_parse(netsnmp_session * session, netsnmp_transport *transport,
	       void *transport_data, int transport_data_length)
{
    return 1;
}
#else
static int
snmp_pre_parse(struct snmp_session *session, snmp_ipaddr from)
{
    return 1;
}
#endif

static struct snmp_session *snmp_session;

struct snmp_fd_data {
    int fd;
    os_hnd_fd_id_t *id;
    struct snmp_fd_data *next;
};

static struct snmp_fd_data *snmpfd = NULL;
os_hnd_timer_id_t *snmp_timer = NULL;

static void
snmp_check_read_fds(int fd, void *cb_data, os_hnd_fd_id_t *id)
{
    fd_set fdset;

    FD_ZERO(&fdset);
    FD_SET(fd, &fdset);
    snmp_read(&fdset);
}

static void
snmp_check_timeout(void *cb_data, os_hnd_timer_id_t *id)
{
    snmp_timeout();
}

static void
snmp_setup_fds(os_handler_t *os_hnd)
{
    int nfds = 0, block = 0, i, rv;
    fd_set fdset;
    struct timeval tv;
    struct snmp_fd_data *fdd, *nfdd, *prev = NULL;

    if (!do_snmp)
	return;

    FD_ZERO(&fdset);
    tv.tv_sec = 0;
    tv.tv_usec = 0;
    snmp_select_info(&nfds, &fdset, &tv, &block);

    /* Run through the list.  Since the list is kept sorted, we only
       need one pass. */
    fdd = snmpfd;
    for (i = 0; i < nfds; i++) {
	if (!FD_ISSET(i, &fdset))
	    continue;

	if (fdd) {
	    if (fdd->fd == i) {
		/* Didn't change. */
		prev = fdd;
		fdd = fdd->next;
		continue;
	    }
	    if (fdd->fd < i) {
		/* Current one was deleted. */
		os_hnd->remove_fd_to_wait_for(os_hnd, fdd->id);
		if (prev)
		    prev->next = fdd->next;
		else
		    snmpfd = fdd->next;
		os_hnd->mem_free(fdd);
		continue;
	    }
	}

	/* New one to add. */
	nfdd = os_hnd->mem_alloc(sizeof(*fdd));
	if (!nfdd) {
	    rv = ENOMEM;
	    goto err;
	}
	nfdd->fd = i;
	rv = os_hnd->add_fd_to_wait_for(os_hnd, i, snmp_check_read_fds,
					NULL, NULL, &nfdd->id);
	if (rv)
	    goto err;

	/* Insert after */
	if (fdd) {
	    nfdd->next = fdd->next;
	    fdd->next = nfdd;
	} else {
	    nfdd->next = NULL;
	    snmpfd = fdd;
	}
    }

    if (!block) {
	os_hnd->stop_timer(os_hnd, snmp_timer);
    } else {
	os_hnd->stop_timer(os_hnd, snmp_timer);
	os_hnd->start_timer(os_hnd, snmp_timer, &tv, snmp_check_timeout, NULL);
    }
    return;

 err:
    fprintf(stderr, "Error handling SNMP fd data: %s\n", strerror(rv));
    exit(1);
}

static int
snmp_init(os_handler_t *os_hnd)
{
    struct snmp_session session;
#ifdef HAVE_NETSNMP
    netsnmp_transport *transport = NULL;
    static char *snmp_default_port = "udp:162";
    int rv;

    rv = os_hnd->alloc_timer(os_hnd, &snmp_timer);
    if (rv) {
	fprintf(stderr, "Could not allocate SNMP timer\n");
	return -1;
    }

    netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID,
			   NETSNMP_DS_LIB_MIB_ERRORS,
			   0);

    init_snmp("ipmish");

    transport = netsnmp_tdomain_transport(snmp_default_port, 1, "udp");
    if (!transport) {
        snmp_sess_perror("ipmish", &session);
	return -1;
    }
#else
    void *transport = NULL;
#endif
    snmp_sess_init(&session);
    session.peername = SNMP_DEFAULT_PEERNAME;
    session.version = SNMP_DEFAULT_VERSION;
    session.community_len = SNMP_DEFAULT_COMMUNITY_LEN;
    session.retries = SNMP_DEFAULT_RETRIES;
    session.timeout = SNMP_DEFAULT_TIMEOUT;
    session.local_port = SNMP_TRAP_PORT;
    session.callback = snmp_input;
    session.callback_magic = transport;
    session.authenticator = NULL;
    session.isAuthoritative = SNMP_SESS_UNKNOWNAUTH;

#ifdef HAVE_NETSNMP
    snmp_session = snmp_add(&session, transport, snmp_pre_parse, NULL);
#else
    snmp_session = snmp_open_ex(&session, snmp_pre_parse,
				NULL, NULL, NULL, NULL);
#endif
    if (snmp_session == NULL) {
        snmp_sess_perror("ipmish", &session);
	return -1;
    }

    return 0;
}
#else
static void snmp_setup_fds(os_handler_t *os_hnd) { }
#endif /* HAVE_UCDSNMP */

typedef struct out_data_s
{
    FILE *stream;
    int  indent;
} out_data_t;

static int columns = 80;

static void
out_help(FILE *s, int indent, const char *name, const char *v)
{
    int pos, endpos;
    const char *endword;
    const char *endspace;

    pos = fprintf(s, "%*s%s ", indent, "", name);
    while (*v) {
	endword = v;
	while (isspace(*endword)) {
	    if (*endword == '\n') {
		v = endword + 1;
		fprintf(s, "\n%*s", indent+2, "");
		pos = indent + 2;
	    }
	    endword++;
	}
	endspace = endword;
	while (*endword && !isspace(*endword))
	    endword++;
	endpos = pos + endword - v;
	if (endpos > columns) {
	    v = endspace;
	    fprintf(s, "\n%*s", indent+2, "");
	    pos = indent + 2;
	}
	fwrite(v, 1, endword-v, s);
	pos += endword - v;
	v = endword;
    }
    fputc('\n', s);
}

static void
out_value(ipmi_cmdlang_t *info, const char *name, const char *value)
{
    out_data_t *out_data = info->user_data;

    if (value) {
	if (info->help) {
	    out_help(out_data->stream, out_data->indent*2, name, value);
	} else {
	    fprintf(out_data->stream, "%*s%s: %s\n", out_data->indent*2, "",
		    name, value);
	}
    } else
	fprintf(out_data->stream, "%*s%s\n", out_data->indent*2, "", name);
    fflush(out_data->stream);
}

static void
out_binary(ipmi_cmdlang_t *info, const char *name, const char *value,
	   unsigned int len)
{
    out_data_t *out_data = info->user_data;
    unsigned char *data = (unsigned char *) value;
    int indent2 = (out_data->indent * 2) + strlen(name) + 1;
    unsigned int i;
    char *sep = ":";

    if (info->help)
      sep = "";

    fprintf(out_data->stream, "%*s%s%s", out_data->indent*2, "", name, sep);
    for (i=0; i<len; i++) {
	if ((i != 0) && ((i % 8) == 0))
	    fprintf(out_data->stream, "\n%*s", indent2, "");
	fprintf(out_data->stream, " 0x%2.2x", (data[i] & 0xff));
    }
    fprintf(out_data->stream, "\n");
    
    fflush(out_data->stream);
}

static void
out_unicode(ipmi_cmdlang_t *info, const char *name, const char *value,
	    unsigned int len)
{
    out_binary(info, name, value, len);
}

static void
down_level(ipmi_cmdlang_t *info)
{
    out_data_t *out_data = info->user_data;

    out_data->indent++;
}

static void
up_level(ipmi_cmdlang_t *info)
{
    out_data_t *out_data = info->user_data;

    out_data->indent--;
}

static void cmd_done(ipmi_cmdlang_t *info);

static out_data_t lout_data =
{
    .stream = NULL,
    .indent = 0,
};
static char cmdlang_objstr[IPMI_MAX_NAME_LEN];
static ipmi_cmdlang_t cmdlang =
{
    .out = out_value,
    .out_binary = out_binary,
    .out_unicode = out_unicode,
    .down = down_level,
    .up = up_level,
    .done = cmd_done,

    .os_hnd = NULL,

    .user_data = &lout_data,

    .objstr = cmdlang_objstr,
    .objstr_len = sizeof(cmdlang_objstr),
};

int *done_ptr = NULL;

static void
cmd_done(ipmi_cmdlang_t *info)
{
    out_data_t *out_data = info->user_data;

    if (info->err) {
	char errval[128];
	if (!info->location)
	    info->location = "";
	if (strlen(info->objstr) == 0) {
	    fprintf(out_data->stream, "error: %s: %s (0x%x, %s)\n",
		    info->location, info->errstr,
		    info->err,
		    ipmi_get_error_string(info->err, errval, sizeof(errval)));
	} else {
	    fprintf(out_data->stream, "error: %s %s: %s (0x%x, %s)\n",
		    info->location, info->objstr, info->errstr,
		    info->err,
		    ipmi_get_error_string(info->err, errval, sizeof(errval)));
	}
	if (info->errstr_dynalloc)
	    ipmi_mem_free(info->errstr);
	info->errstr_dynalloc = 0;
	info->errstr = NULL;
	info->location = NULL;
	info->objstr[0] = '\0';
	info->err = 0;
    }

    if (done_ptr) {
	*done_ptr = 1;
    } else {
	handling_input = 1;
	redraw_cmdline(1);
	enable_term_fd(info);
	fflush(out_data->stream);
    }
}

void
ipmi_cmdlang_global_err(char *objstr,
			char *location,
			char *errstr,
			int  errval)
{
    if (handling_input && !done && cmd_redisp)
	fputc('\n', stdout);
    if (objstr)
	fprintf(stderr, "global error: %s %s: %s (0x%x)", location, objstr,
		errstr, errval);
    else
	fprintf(stderr, "global error: %s: %s (0x%x)", location,
		errstr, errval);
    evcount = 0;
    redraw_cmdline(0);
}

void
ipmi_cmdlang_report_event(ipmi_cmdlang_event_t *event)
{
    unsigned int                level, len;
    enum ipmi_cmdlang_out_types type;
    char                        *name, *value;
    int                         indent2;
    unsigned int                i;

    if (handling_input && !done && cmd_redisp)
	fputc('\n', stdout);
    ipmi_cmdlang_event_restart(event);
    printf("Event\n");
    while (ipmi_cmdlang_event_next_field(event, &level, &type, &name, &len,
					 &value))
    {
	switch (type) {
	case IPMI_CMDLANG_STRING:
	    if (value)
		printf("  %*s%s: %s\n", level*2, "", name, value);
	    else
		printf("  %*s%s\n", level*2, "", name);
	    break;

	case IPMI_CMDLANG_BINARY:
	case IPMI_CMDLANG_UNICODE:
	    indent2 = (level * 2) + strlen(name) + 1;
	    printf("  %*s%s:", level*2, "", name);
	    for (i=0; i<len; i++) {
		if ((i != 0) && ((i % 8) == 0))
		    printf("\n  %*s", indent2, "");
		printf(" 0x%2.2x", value[i] & 0xff);
	    }
	    printf("\n");
    
	    fflush(stdout);
	    break;
	}
    }
    evcount = 0;
    redraw_cmdline(0);
}

static void
user_input_ready(int fd, void *data, os_hnd_fd_id_t *id)
{
    rl_callback_read_char();
}

static void
rl_ipmish_cb_handler(char *cmdline)
{
    char *expansion = NULL;
    int result;

    if (cmdline == NULL) {
	done = 1;
	evcount = 1; /* Force a newline */
	return;
    }
    result = history_expand(cmdline, &expansion);
    if (result < 0 || result == 2) {
	fprintf(stderr, "%s\n", expansion);
    } else if (expansion && strlen(expansion)){
	cmdlang.err = 0;
	cmdlang.errstr = NULL;
	cmdlang.errstr_dynalloc = 0;
	cmdlang.location = NULL;
	handling_input = 0;
	add_history(expansion);
	ipmi_cmdlang_handle(&cmdlang, expansion);
    }
    if (expansion)
	free(expansion);
}

static void
cleanup_term(void)
{
    rl_callback_handler_remove();
    disable_term_fd(&cmdlang);
}

static void cleanup_sig(int sig);

static void
setup_term(os_handler_t *os_hnd)
{
    signal(SIGINT, cleanup_sig);
    signal(SIGPIPE, cleanup_sig);
    signal(SIGUSR1, cleanup_sig);
    signal(SIGUSR2, cleanup_sig);
#ifdef SIGPWR
    signal(SIGPWR, cleanup_sig);
#endif

    stifle_history(500);
    rl_callback_handler_install("> ", rl_ipmish_cb_handler);
    lout_data.stream = stdout;

    cmdlang.os_hnd = os_hnd;
}

static void
redisp_cmd(ipmi_cmd_info_t *cmd_info)
{
    ipmi_cmdlang_t  *cmdlang = ipmi_cmdinfo_get_cmdlang(cmd_info);
    int             curr_arg = ipmi_cmdlang_get_curr_arg(cmd_info);
    int             argc = ipmi_cmdlang_get_argc(cmd_info);
    char            **argv = ipmi_cmdlang_get_argv(cmd_info);
    int             redisp;

    if ((argc - curr_arg) < 1) {
	/* Not enough parameters */
	cmdlang->errstr = "Not enough parameters";
	cmdlang->err = EINVAL;
	goto out_err;
    }

    ipmi_cmdlang_get_bool(argv[curr_arg], &redisp, cmd_info);
    if (cmdlang->err) {
	cmdlang->errstr = "redisp setting invalid";
	goto out_err;
    }
    curr_arg++;

    cmd_redisp = redisp;

    ipmi_cmdlang_out(cmd_info, "redisp set", NULL);

 out_err:
    cmdlang->location = "ipmish.c(redisp_cmd)";
}

static void
exit_cmd(ipmi_cmd_info_t *cmd_info)
{
    done = 1;
    evcount = 0;
    ipmi_cmdlang_out(cmd_info, "Exiting ipmish", NULL);
}

static int read_nest = 0;
static void
read_cmd(ipmi_cmd_info_t *cmd_info)
{
    ipmi_cmdlang_t *cmdlang = ipmi_cmdinfo_get_cmdlang(cmd_info);
    int            cdone;
    char           cmdline[256];
    FILE           *s;
    out_data_t     my_out_data;
    ipmi_cmdlang_t my_cmdlang = *cmdlang;
    int            curr_arg = ipmi_cmdlang_get_curr_arg(cmd_info);
    int            argc = ipmi_cmdlang_get_argc(cmd_info);
    char           **argv = ipmi_cmdlang_get_argv(cmd_info);
    int            *saved_done_ptr;
    char           *fname;

    if ((argc - curr_arg) < 1) {
	cmdlang->errstr = "No filename entered";
	cmdlang->err = EINVAL;
	goto out_err;
    }

    fname = argv[curr_arg];
    curr_arg++;
    s = fopen(fname, "r");
    if (!s) {
	cmdlang->errstr = "Unable to openfile";
	cmdlang->err = errno;
	goto out_err;
    }

    if (!read_nest) {
	handling_input = 0;
	disable_term_fd(cmdlang);
    }
    read_nest++;
    saved_done_ptr = done_ptr;

    /* not record the file's commands into history */
    while (fgets(cmdline, sizeof(cmdline), s)) {
	my_out_data.stream = stdout;
	my_out_data.indent = 0;
	my_cmdlang.user_data = &my_out_data;
	cdone = 0;
	done_ptr = &cdone;
	printf("> %s", cmdline);
	fflush(stdout);
	ipmi_cmdlang_handle(&my_cmdlang, cmdline);
	while (!cdone) {
	    snmp_setup_fds(cmdlang->os_hnd);
	    cmdlang->os_hnd->perform_one_op(cmdlang->os_hnd, NULL);
	}
	done_ptr = NULL;
    }
    fclose(s);

    done_ptr = saved_done_ptr;
    read_nest--;
    if (!read_nest) {
	handling_input = 1;
	enable_term_fd(cmdlang);
    }

    ipmi_cmdlang_out(cmd_info, "File read", fname);

    return;

 out_err:
    cmdlang->location = "ipmish.c(read_cmd)";
}

static void
setup_cmds(void)
{
    int rv;

    rv = ipmi_cmdlang_reg_cmd(NULL,
			      "redisp_cmd",
			      "on|off - If an asynchronous event comes in,"
			      " redisplay the current working command.  This"
			      " is on by default.",
			      redisp_cmd, NULL, NULL, NULL);
    if (rv) {
	fprintf(stderr, "Error adding exit command: 0x%x\n", rv);
	exit(1);
    }

    rv = ipmi_cmdlang_reg_cmd(NULL,
			      "exit",
			      "- leave the program",
			      exit_cmd, NULL, NULL, NULL);
    if (rv) {
	fprintf(stderr, "Error adding exit command: 0x%x\n", rv);
	exit(1);
    }

    rv = ipmi_cmdlang_reg_cmd(NULL,
			      "read",
			      "<file> - Read commands from the file and"
			      " execute them",
			      read_cmd, NULL, NULL, NULL);
    if (rv) {
	fprintf(stderr, "Error adding read command: 0x%x\n", rv);
	exit(1);
    }
}

static void
domain_down(void *cb_data)
{
    int *count = cb_data;
    (*count)--;
}

static void
shutdown_domain_handler(ipmi_domain_t *domain, void *cb_data)
{
    int *count = cb_data;
    int rv;

    rv = ipmi_domain_close(domain, domain_down, cb_data);
    if (!rv)
	(*count)++;
}

static void
cleanup_sig(int sig)
{
    fprintf(stderr, "Exiting due to signal %d\n", sig);
    done = 0;
    ipmi_domain_iterate_domains(shutdown_domain_handler, &done);
    while (done) {
	snmp_setup_fds(cmdlang.os_hnd);
	cmdlang.os_hnd->perform_one_op(cmdlang.os_hnd, NULL);
    }
    cleanup_term();
    exit(1);
}

typedef struct exec_list_s
{
    char *str;
    struct exec_list_s *next;
} exec_list_t;
static exec_list_t *execs, *execs_tail;

static void
add_exec_str(char *str)
{
    exec_list_t *e;

    e = malloc(sizeof(*e));
    if (!e) {
	fprintf(stderr, "Out of memory");
	exit(1);
    }
    e->str = str;
    e->next = NULL;
    if (execs)
	execs_tail->next = e;
    else
	execs = e;
    execs_tail = e;
}

static char *usage_str =
"%s is a program that gives access to the OpenIPMI library from a command\n"
"line.  It is designed to be script driven.  Format is:\n"
"  %s [options]\n"
"Options are:\n"
"  --execute <string> - execute the given string at startup.  This may be\n"
"    entered multiple times for multiple commands.\n"
"  -x <string> - same as --execute\n"
"  --dlock - turn on lock debugging.\n"
"  --dmem - turn on memory debugging.\n"
"  --drawmsg - turn on raw message tracing.\n"
"  --dmsg - turn on message tracing debugging.\n"
"  --dmsgerr - turn on printing out low-level message errors.\n"
#ifdef HAVE_GLIB
"  --glib - use glib for the OS handler.\n"
#endif
#ifdef HAVE_TCL
"  --tcl - use tcl for the OS handler.\n"
#endif
#ifdef HAVE_UCDSNMP
"  --snmp - turn on SNMP trap handling.\n"
#endif
"  --help - This output.\n"
;
static void usage(char *name)
{
    fprintf(stderr, usage_str, name, name);
}

int
main(int argc, char *argv[])
{
    int              rv;
    int              curr_arg = 1;
    const char       *arg;
    os_handler_t     *os_hnd;
    int              use_debug_os = 0;
    char             *colstr;
#ifdef HAVE_GLIB
    int              use_glib = 0;
#endif
#ifdef HAVE_TCL
    int              use_tcl = 0;
#endif

    colstr = getenv("COLUMNS");
    if (colstr) {
	int tmp = strtoul(colstr, NULL, 0);
	if (tmp) 
	    columns = tmp;
    }

    while ((curr_arg < argc) && (argv[curr_arg][0] == '-')) {
	arg = argv[curr_arg];
	curr_arg++;
	if (strcmp(arg, "--") == 0) {
	    break;
	} else if ((strcmp(arg, "-x") == 0) || (strcmp(arg, "--execute") == 0))
	{
	    if (curr_arg >= argc) {
		fprintf(stderr, "No option given for %s", arg);
		usage(argv[0]);
		return 1;
	    }
	    add_exec_str(argv[curr_arg]);
	    curr_arg++;
	} else if (strcmp(arg, "--dlock") == 0) {
	    DEBUG_LOCKS_ENABLE();
	    use_debug_os = 1;
	} else if (strcmp(arg, "--dmem") == 0) {
	    DEBUG_MALLOC_ENABLE();
	} else if (strcmp(arg, "--drawmsg") == 0) {
	    DEBUG_RAWMSG_ENABLE();
	} else if (strcmp(arg, "--dmsg") == 0) {
	    DEBUG_MSG_ENABLE();
	} else if (strcmp(arg, "--dmsgerr") == 0) {
	    DEBUG_MSG_ERR_ENABLE();
#ifdef HAVE_UCDSNMP
	} else if (strcmp(arg, "--snmp") == 0) {
	    do_snmp = 1;
#endif
#ifdef HAVE_GLIB
	} else if (strcmp(arg, "--glib") == 0) {
	    use_glib = 1;
#endif
#ifdef HAVE_TCL
	} else if (strcmp(arg, "--tcl") == 0) {
	    use_tcl = 1;
#endif
	} else if (strcmp(arg, "--help") == 0) {
	    usage(argv[0]);
	    return 0;
	} else {
	    fprintf(stderr, "Unknown option: %s\n", arg);
	    usage(argv[0]);
	    return 1;
	}
    }

    if (use_debug_os) {
	os_hnd = &ipmi_debug_os_handlers;
	rv = sel_alloc_selector_nothread(&debug_sel);
	if (rv) {
	    fprintf(stderr, "Could not allocate selector\n");
	    return 1;
	}
#ifdef HAVE_GLIB
    } else if (use_glib) {
	g_thread_init(NULL);
	os_hnd = ipmi_glib_get_os_handler(0);
	if (!os_hnd) {
	    fprintf(stderr,
		    "ipmi_smi_setup_con: Unable to allocate os handler\n");
	    return 1;
	}
	g_log_set_handler("OpenIPMI",
			  G_LOG_LEVEL_ERROR
			  | G_LOG_LEVEL_CRITICAL
			  | G_LOG_LEVEL_WARNING
			  | G_LOG_LEVEL_MESSAGE
			  | G_LOG_LEVEL_INFO
			  | G_LOG_LEVEL_DEBUG
			  | G_LOG_FLAG_FATAL,
			  glib_handle_log,
                          NULL);
#endif
#ifdef HAVE_TCL
    } else if (use_tcl) {
	os_hnd = ipmi_tcl_get_os_handler(0);
	if (!os_hnd) {
	    fprintf(stderr,
		    "ipmi_smi_setup_con: Unable to allocate os handler\n");
	    return 1;
	}
#endif
    } else {
	os_hnd = ipmi_posix_setup_os_handler();
	if (!os_hnd) {
	    fprintf(stderr,
		    "ipmi_smi_setup_con: Unable to allocate os handler\n");
	    return 1;
	}
    }

    os_hnd->set_log_handler(os_hnd, my_vlog);

    /* Initialize the OpenIPMI library. */
    ipmi_init(os_hnd);

#ifdef HAVE_UCDSNMP
    if (do_snmp) {
	if (snmp_init(os_hnd) < 0)
	    return 1;
    }
#endif

    rv = ipmi_cmdlang_init(os_hnd);
    if (rv) {
	fprintf(stderr, "Unable to initialize command processor: 0x%x\n", rv);
	return 1;
    }

    setup_cmds();

    setup_term(os_hnd);

    while (execs) {
	exec_list_t *e = execs;
	int         cdone = 0;
	read_nest = 1;
	execs = e->next;
	printf("> %s\n", e->str);
	fflush(stdout);
	done_ptr = &cdone;
	rl_ipmish_cb_handler(e->str);
	while (!cdone) {
	    snmp_setup_fds(os_hnd);
	    os_hnd->perform_one_op(os_hnd, NULL);
	}
	done_ptr = NULL;
	free(e);
	read_nest = 0;
    }

    fflush(stdout);

    handling_input = 1;
    enable_term_fd(&cmdlang);

    while (!done) {
	snmp_setup_fds(os_hnd);
	os_hnd->perform_one_op(os_hnd, NULL);
    }

    cleanup_term();

    /* Shut down all existing domains. */
    
    done = 0;
    ipmi_domain_iterate_domains(shutdown_domain_handler, &done);
    while (done) {
	snmp_setup_fds(os_hnd);
	os_hnd->perform_one_op(os_hnd, NULL);
    }

    ipmi_cmdlang_cleanup();
    ipmi_shutdown();

    ipmi_debug_malloc_cleanup();

    os_hnd->free_os_handler(os_hnd);

    /* remove the prompt which editline printed */
    printf("\b\b  \b\b");
    if (evcount)
	printf("\n");
    fflush(stdout);

    if (rv)
	return 1;
    return 0;
}
