/* 
 * $Id: util.c,v 1.2 91/05/03 03:07:43 qjb Exp Locker: qjb $
 * $Source: /afs/athena.mit.edu/astaff/project/qcs/src/RCS/util.c,v $
 * $Author: qjb $
 *
 * This file contains miscellaneous utilities for use by the library,
 * clients, and servers.
 */

#if !defined(lint) && !defined(SABER) || defined(RCS_HDRS)
static char *rcsid = "$Id: util.c,v 1.2 91/05/03 03:07:43 qjb Exp Locker: qjb $";
#endif /* !lint && !SABER || RCS_HDRS */


#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

#include "qcs.h"
#include "qcs_private.h"

/* Maximum number of columns for a line in the log file */
#define LOG_COLS 80

static char tmpbuf[BUFSIZ];

extern int qcs_debug;

void qcsi_log(char *origmsg) 
{
    int breakpoint;
    char *p, *q;
    char t;
    char *msg;

    breakpoint = LOG_COLS - 3;

    /* Sizeof(string constant) adds in the NULL terminator */
    if ((msg = (char *)calloc(strlen(origmsg) + 
			      sizeof("Jun 26  9:35:00 - "), 1)) == NULL)
	msg = origmsg;
    else {
	time_t clock;
	char datestr[27];
	clock = time(0);
	strcpy(datestr, ctime(&clock));
	strncpy(msg, datestr + sizeof("Thu ") - 1, 
		sizeof("Jun 26  9:35:00") - 1);
	strcat(msg, " - ");
	strcat(msg, origmsg);
    }

    p = msg;

    /* 
     * Print message breaking lines so that no line is longer than
     * LOG_COLS characters.  Print first line over all LOG_COLS columns.
     * Print subsequent lines with three-character margin.
     */

    if (strlen(msg) > LOG_COLS - breakpoint) {
	write(1, p, LOG_COLS - breakpoint);
	p += LOG_COLS - breakpoint;
    }

    while (strlen(p) > breakpoint) {
	t = p[breakpoint];
	p[breakpoint] = '\0';
	if ((q = strrchr(p, ' ')) == NULL)
	    q = p + breakpoint;
	p[breakpoint] = t;
	
	write(1, p, q-p);
	write(1, "\n   ", 4);
	p = q + 1;
    }
    if (p) {
	write(1, p, strlen(p));
	write(1, "\n", 1);
    }

    if (msg != origmsg)
	free(msg);
}


void qcsi_error(int priority, char *msg, qcs_client client)
{
    /* syslog the error */
    strcpy(tmpbuf, "QCS error: ");
    strcat(tmpbuf, msg);
    (void) syslog(priority | LOG_DAEMON, tmpbuf);

    /* put in the logfile */
    strcpy(tmpbuf, "Error: ");
    switch (priority) {
      case LOG_EMERG:
	strcat(tmpbuf, "(emergency): ");
	break;
      case LOG_ALERT:
	strcat(tmpbuf, "(alert): ");
	break;
      case LOG_CRIT:
	strcat(tmpbuf, "(critical): ");
	break;
      case LOG_ERR:
	strcat(tmpbuf, "(error): ");
	break;
      case LOG_WARNING:
	strcat(tmpbuf, "(warning): ");
	break;
      case LOG_NOTICE:
	strcat(tmpbuf, "(notice): ");
	break;
      case LOG_INFO:
	strcat(tmpbuf, "(info): ");
	break;
      case LOG_DEBUG:
	strcat(tmpbuf, "(debug): ");
	break;
      default:
	strcat(tmpbuf, "(unkown level): ");
	break;
    }
    strcat(tmpbuf, msg);
    qcsi_log(tmpbuf);

    /* tell the client if necessary */
    if (client)
	qrpc_drop(client->qrpc, msg);
}


void qcsi_debug(char *msg)
{
    if (qcs_debug)
	(void) printf("==> %s\n", msg);
}


qcs_client qcsi_get_client_object(void)
{
    static id = 0;		/* Unique client id counter */
    qcs_client client;

    if ((client = (qcs_client) malloc(sizeof(*client))) == NULL) {
	sprintf(tmpbuf, 
		"Failure allocating %d bytes of memory for client object",
		sizeof (*client));
	qcsi_error(LOG_ERR, (char *)tmpbuf, NULL);
    }
    else
	bzero((char *)client, sizeof(*client));

    /*
     * Assign client a unique identifier.  Each client must have
     * one so that pointers to clients whose memory has been freed
     * can be safely detected.  This situation can occur when a
     * client with pending jobs is flushed.
     */

    client->id.unique = ++id;

    return (client);
}


void qcsi_dump(void)
{
    printf("\nDumping file descriptor mapping table:\n");
    qcsi_dump_fd_functions();
    printf("\nDumping command class table:\n");
    qcsi_dump_cmd_classes();
    printf("\n");
}


char *qcsi_resolve_host(struct in_addr addr)
{
    struct hostent *hp;
    extern char *inet_ntoa(struct in_addr);
    
    if ((hp = gethostbyaddr(&addr, sizeof(struct in_addr), AF_INET)) != NULL)
	return (hp->h_name);
    else
	return (inet_ntoa(addr));
}


/*
 * Warning: this routine returns a pointer to a static variable
 */
char *qcsi_unparse_cmd(qcs_cmd cmd)
{
    static char cmdstr[BUFSIZ];
    char **cur_arg;
    
    strcpy(cmdstr, cmd[0]);
    for (cur_arg = cmd + 1; *cur_arg; cur_arg++) {
	strcat(cmdstr, " ");
	strcat(cmdstr, *cur_arg);
    }

    return ((char *)cmdstr);
}


/*
 * This routine takes a raw command as typed in (raw) and coverts
 * it to a series of concatinated null-terminated strings.  After
 * removing all spaces between arguments.  For example, (ignoring < and >)
 * 
 * <   one   two  three four   five   >
 *
 * would become
 *
 * <one\0two\0three\0four\0five\0>
 *
 * XXX A better implementation would count all strings in double
 * quotes as single arguments and would accept \" and \\ as a 
 * double quote character or a backslash.
 */
qcs_error_t qcsi_command_to_argv(char *raw, long *argc, char **cooked, 
				 int *datalen, char *errmsg)
{
    char *p1, *p2;
    enum { q_rs, q_rns } cmd_state;
    
    *datalen = 0;

    /* 
     * First, count up the arguments (including the the command name
     * itself) and figure out how many characters we will for our cooked 
     * command.
     */

    /* 
     * This is a two-state FSM.  The states are "reading spaces" and
     * "reading non-spaces".  We start in "reading spaces".  While in
     * this state, if a space is read, just stay.  If a non-space is
     * read, increment argc and datalen and then change states.  While in 
     * "reading non-spaces", stay and increment datalen if a non-space
     * is read, and change to "reading spaces" after incrementing 
     * datalen if a space is read.  This way we simultaneously determine 
     * values for argc and datalen.
     */
    cmd_state = q_rs;
    for (p1 = raw; *p1; p1++) {
	switch (cmd_state) {
	  case q_rs:
	    if (!isspace(*p1)) {
		cmd_state = q_rns;
		(*datalen)++;
		(*argc)++;
	    }
	    break;

	  case q_rns:
	    (*datalen)++;
	    if (isspace(*p1))
		cmd_state = q_rs;
	    break;

	  default:
	    sprintf(errmsg, 
		    "internal error: bad state in qcsi_command_to_argv");
	    return(QCS_FAILURE);
	}
    }
    /* Add an extra byte for a null-terminator at the end */
    (*datalen)++;
       
    /*
     * Now we can allocate memory for our cooked command.
     */

    if ((*cooked = (char *)malloc(*datalen)) == NULL) {
	sprintf(errmsg, "not enough memory to allocate %d %s",
		(*datalen), "bytes for cooked command");
	return(QCS_FAILURE);
    }
    
    /* Assemble cooked command. */

    p1 = *cooked;

    /*
     * Here is another two-state FSM.  This time, we copy the non-space
     * characters of command into packet substituting the first space
     * after a non-space with a null.
     */
    cmd_state = q_rs;
    for (p2 = raw; *p2; p2++) {
	switch (cmd_state) {
	  case q_rs:
	    if (! isspace(*p2)) {
		*p1++ = *p2;
		cmd_state = q_rns;
	    }
	    break;
	
	  case q_rns:
	    if (isspace(*p2)) {
		*p1++ = '\0';
		cmd_state = q_rs;
	    }
	    else
		*p1++ = *p2;
	    break;

	  default:
	    sprintf(errmsg, "internal error: bad state while %s",
		    "assembling cooked command in qcsi_command_to_argv");
	    return (QCS_FAILURE);
	}
    }

    /* Put a null-terminator at the end of the command */
    *p1++ = '\0';
    
    /* Check to make sure we didn't screw up */
    if (p1 - *cooked != (*datalen)) {
	sprintf(errmsg, "internal error: %s %d (datalen) vs. %d (copied)",
		"qcsi_command_to_argv cooked command length inconsistency:",
		*datalen, p1 - *cooked);
	return (QCS_FAILURE);
    }

    return (QCS_SUCCESS);
}
