/*
 * tnmSmx.c --
 *
 *	This module implements the Script MIB extensibility protocol
 *	(SMX) as defined in RFC 2593.
 *
 * Copyright (c) 1998-1999 Technical University of Braunschweig.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * @(#) $Id: tnmUnixSmx.c 1403 1999-11-05 11:33:39Z schoenw $
 */

#include "tnmInt.h"
#include "tnmUnixSmx.h"

#include <signal.h>

/*
 * Module global variables.
 */

static Tcl_AsyncHandler async = NULL;	/* The token for our async handler. */
static Tcl_Channel smx = NULL;		/* The channel to the SMX master. */
static TnmSmxThread *threads = NULL;	/* The list of known SMX threads. */

static char *smxPort = NULL;
static char *smxCookie = NULL;

/*
 * Forward declarations for procedures defined later in this file:
 */

static int
AsyncProc		_ANSI_ARGS_((ClientData clientData,
				     Tcl_Interp *interp, int code));
static void
SignalProc		_ANSI_ARGS_((int sig));

static void
ReceiveProc		_ANSI_ARGS_((ClientData clientData, int mask));

static void
HelloCmd		_ANSI_ARGS_((char *line));
	
/*
 * SMX command and response tables.
 */

typedef struct SmxCmdTable {
    int key;
    char *name;
    void (*impl)(char *line);
} SmxCmdTable;

static SmxCmdTable smxCmdTable[] = {
    { TNM_SMX_CMD_HELLO,	"hello",	HelloCmd },
    { TNM_SMX_CMD_START,	"start",	NULL },
    { TNM_SMX_CMD_SUSPEND,	"suspend",	NULL },
    { TNM_SMX_CMD_RESUME,	"resume",	NULL },
    { TNM_SMX_CMD_ABORT,	"abort",	NULL },
    { TNM_SMX_CMD_STATUS,	"status",	NULL },
    { 0, NULL }
};

static TnmTable smxReplyTable[] = {
    { TNM_SMX_REPL_IDENT,		"identification" },
    { TNM_SMX_REPL_STATUS,		"script status" },
    { TNM_SMX_REPL_ABORT,		"script abort" },
    { TNM_SMX_REPL_SYNTAX_ERROR,	"syntax error" },
    { TNM_SMX_REPL_UNKNOWN_CMD,		"unknown command" },
    { TNM_SMX_REPL_UNKNOWN_FILE,	"unknown file name" },
    { TNM_SMX_REPL_UNKNOWN_ID,		"unknown script" },
    { TNM_SMX_REPL_UNKNOWN_PROFILE,	"unknown profile" },
    { TNM_SMX_REPL_ILLEGAL_ARG,		"illegal argument" },
    { TNM_SMX_REPL_ILLEGAL_CHANGE,	"illegal status" },
    { TNM_SMX_NTFY_MSG,			"message" },
    { TNM_SMX_NTFY_STATUS_CHANGE,	"status change" },
    { TNM_SMX_NTFY_RESULT,		"result" },
    { TNM_SMX_NTFY_RESULT_TRAP,		"result and trap" },
    { TNM_SMX_NTFY_TERMINATION,		"script termination" },
    { TNM_SMX_NTFY_ABORT,		"script abort" },
    { 0, NULL }
};


/*
 *----------------------------------------------------------------------
 *
 * AsyncProc --
 *
 *	This procedure is invoked by Tcl whenever we receive a
 *      SIGVTALRM. It checks whether SMX requests are ready to be
 *	received and calls the appropriate function to process the
 *	SMX message.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
AsyncProc(clientData, interp, code)
    ClientData clientData;
    Tcl_Interp *interp;
    int code;
{
    fprintf(stderr, "** AsyncProc() **\n");
    if (Tcl_InputBuffered(smx)) {
	fprintf(stderr, "*** oops - got something ***\n");
    }

    return code;
}

/*
 *----------------------------------------------------------------------
 *
 * SignalProc --
 *
 *	This procedure is invoked by the signal handler whenever we
 *	receive a SIGVTALRM. It simply marks the async handler and
 *	returns so that Tcl can continue until it is in a safe state.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Activates the async handler.
 *
 *----------------------------------------------------------------------
 */

static void
SignalProc(sig)
    int sig;
{
    Tcl_AsyncMark(async);
}

/*
 *----------------------------------------------------------------------
 *
 * ReceiveProc --
 *
 *	This procedure is called to receive a message from the SMX
 *	master.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Processes the SMX command as described in the SMX documentation.
 *
 *----------------------------------------------------------------------
 */

static void
ReceiveProc(clientData, mask)
    ClientData clientData;
    int mask;
{
    static Tcl_DString *in = NULL;
    char *line;
    int len;
    SmxCmdTable *cmd;

    if (! smx) return;

    if (! in) {
	in = (Tcl_DString *) ckalloc(sizeof(Tcl_DString));
	Tcl_DStringInit(in);
    } else {
	Tcl_DStringFree(in);
    }

    len = Tcl_Gets(smx, in);
    if (len < 0) {
	Tcl_UnregisterChannel((Tcl_Interp *) NULL, smx);
	smx = NULL;
    }
    line = Tcl_DStringValue(in);

    /*
     * Check which command we got and call the relevant function.
     */

    for (cmd = smxCmdTable; cmd->name; cmd++) {
	if (cmd->impl && strncmp(line, cmd->name, strlen(cmd->name)) == 0) {
	    fprintf(stderr, "smx: processing %s command...\n", cmd->name);
	    (cmd->impl)(line);
	    break;
	}
    }
}

static void
HelloCmd(char *line)
{
    char buf[80];
    int id;
    Tcl_DString out;
    
    if (1 != sscanf(line, "%*s %d", &id)) {
	fprintf(stderr, "invalid hello cmd\n");
	return;
    }
    sprintf(buf, "%d", id);
   
    Tcl_DStringInit(&out);
    Tcl_DStringAppend(&out, "211 ", -1);
    Tcl_DStringAppend(&out, buf, -1);
    Tcl_DStringAppend(&out, " SMX/1.0 ", -1);
    Tcl_DStringAppend(&out, smxCookie, -1);
    Tcl_DStringAppend(&out, "\n", -1);
    fprintf(stderr, "*** %s", Tcl_DStringValue(&out));
    Tcl_Write(smx, Tcl_DStringValue(&out), Tcl_DStringLength(&out));
    Tcl_DStringFree(&out);
}

/*
 *----------------------------------------------------------------------
 *
 * TnmSmxInit --
 *
 *	This procedure performs application-specific initialization.
 *	Most applications, especially those that incorporate additional
 *	packages, will have their own version of this procedure.
 *
 * Results:
 *	Returns a standard Tcl completion code, and leaves an error
 *	message in interp->result if an error occurs.
 *
 * Side effects:
 *	Depends on the startup script.
 *
 *----------------------------------------------------------------------
 */

int
TnmSmxInit(interp)
    Tcl_Interp *interp;
{
    struct itimerval interval;
    struct sigaction action;

    smxPort = getenv("SMX_PORT");
    smxCookie = getenv("SMX_COOKIE");

    /*
     * We only initialize the SMX protocol if we are running in
     * SMX mode. We are in SMX mode whenever we can read the SMX
     * environment variables.
     */

    if (! smxPort || ! smxCookie) {
	return TCL_OK;
    }

    if (Tcl_PkgProvide(interp, "SMX", TNM_SMX_VERSION) != TCL_OK) {
	return TCL_ERROR;
    }

    if (! smx) {
	smx = Tcl_OpenTcpClient(interp, atoi(smxPort), "localhost",
				 NULL, 0, 0);
	if (! smx) {
	    return TCL_ERROR;
	}
	Tcl_RegisterChannel((Tcl_Interp *) NULL, smx);
	Tcl_SetChannelOption((Tcl_Interp *) NULL, smx, "-buffering", "line");
	Tcl_SetChannelOption((Tcl_Interp *) NULL, smx, "-translation", "crlf");
	Tcl_CreateChannelHandler(smx, TCL_READABLE, ReceiveProc,
				 (ClientData) NULL);
    }

    /*
     * Install a signal handler which will interupt the interpreter
     * at regular intervals.
     */

    if (! async) {
	async = Tcl_AsyncCreate(AsyncProc, 0);
	action.sa_handler = SignalProc;
	sigemptyset(&action.sa_mask);
	action.sa_flags = 0;
	if (sigaction(SIGVTALRM, &action, NULL) < 0) {
	    Tcl_SetResult(interp, Tcl_PosixError(interp), TCL_VOLATILE);
	    return TCL_ERROR;
	}
	interval.it_interval.tv_sec = 0;
	interval.it_interval.tv_usec = 500000;
	interval.it_value.tv_sec = 0;
	interval.it_value.tv_usec = 500000;
	if (setitimer(ITIMER_VIRTUAL, &interval, NULL) < 0) {
	    Tcl_SetResult(interp, Tcl_PosixError(interp), TCL_VOLATILE);
	    return TCL_ERROR;
	}
    }

    return TCL_OK;
}
