/* 
 * $Id: s_cmdcls.c,v 1.2 91/05/03 03:04:33 qjb Exp Locker: qjb $
 * $Source: /afs/athena.mit.edu/astaff/project/qcs/src/RCS/s_cmdcls.c,v $
 * $Author: qjb $
 *
 * This is the main source file for the server-side of the 
 * qcs library.  It takes care of server initialization and
 * handling the server's main loop.
 */

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


#include <stdio.h>
#include <syslog.h>

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


/* dynamically allocated array of command classes */
static qcs_cmd_class *cmd_classes; 
/* dymnamically allocated command mapping table */

typedef struct {
    qcs_cmd_desc desc;
    qcs_cmd_class_id id;
} cmd_map_entry;

static cmd_map_entry *cmd_map;	/* Mapping of commands */
static int ncmds;		/* Number of commands */

static char tmpbuf[BUFSIZ];


static int cmd_map_entry_cmp(cmd_map_entry *e1, cmd_map_entry *e2)
{
    return (strcmp(e1->desc.cmd_name, e2->desc.cmd_name));
}


static qcs_error_t qcsi_construct_cc_map(char *errmsg)
  /*
   * This routine constructs a mapping between command names and
   * command class indices.  The table also contains the dispatching
   * functions for efficiency of lookup.
   *
   * This routine returns an error if there are any duplicated
   * commands.
   */
{
    qcs_cmd_class *cur_class;
    qcs_cmd_desc *cur_desc;
    cmd_map_entry *cur_entry;
    int i;

    for (cur_class = &(cmd_classes[0]); cur_class->descs; cur_class++) 
	for (cur_desc = &(cur_class->descs[0]); cur_desc->cmd_name; cur_desc++)
	    ncmds++;
    
    if ((cmd_map = (cmd_map_entry *)calloc(ncmds, sizeof(cmd_map_entry))) 
	== NULL) {
	sprintf(errmsg, "Failure allocating %d bytes for command map",
		ncmds * sizeof(cmd_map_entry));
	return (QCS_ERR_MEMORY);
    }

    /*
     * Initialize the table without sorting 
     */
    cur_entry = cmd_map;
    for (i = 0, cur_class = &(cmd_classes[0]); cur_class->descs; 
	 i++, cur_class++) {
	for (cur_desc = &(cur_class->descs[0]); cur_desc->cmd_name; 
	     cur_desc++) {
	    cur_entry->desc = *cur_desc;
	    cur_entry->id = i;
	    cur_entry++;
	}
    }	

    qsort(cmd_map, ncmds, sizeof(cmd_map_entry), cmd_map_entry_cmp);

    for (i = 1; i < ncmds; i++)
	if (strcmp(cmd_map[i].desc.cmd_name, 
		   cmd_map[i-1].desc.cmd_name) == 0) {
	    sprintf(errmsg, "Duplicate command %s detected");
	    return (QCS_ERR_DUP_CMD);
	}
    
    return (QCS_SUCCESS);
}


qcs_error_t qcsi_cmdcls_init(char *errmsg, qcs_cmd_desc **descslist)
{
    qcs_cmd_desc **cur_descs;
    int i;
    int ndescs;			/* Number of command class descriptions */

    /*
     * Try to set up command classes.  First count, then allocate memory
     * for an array of the appropriate size.  Start with 1 so that
     * we'll end up with an extra at end to terminate with.  
     */

    for (ndescs = 1, cur_descs = descslist; *cur_descs; cur_descs++) 
	ndescs++;

    if ((cmd_classes = (qcs_cmd_class *)calloc(ndescs, sizeof(qcs_cmd_class)))
	== NULL) {
	sprintf(errmsg, 
		"Unable to allocate %d bytes of memory for command classes",
		ndescs * sizeof(qcs_cmd_class));
	return (QCS_ERR_MEMORY);
    }

    for (i = 0, cur_descs = descslist; *cur_descs; i++, cur_descs++) {
	cmd_classes[i].descs = *cur_descs;
	cmd_classes[i].tail = MAXREQUESTS - 1;
    }
	
    cmd_classes[0].pid = QCS_NOFORK; /* Command class 0 doesn't fork */

    return (qcsi_construct_cc_map(errmsg));
}     


qcs_error_t qcsi_cmd_class_pid(int pid)
{
    int cc_index;

    for (cc_index = 0; cmd_classes[cc_index].descs; cc_index++)
	if (cmd_classes[cc_index].pid == pid)
	    break;

    if (cmd_classes[cc_index].descs == NULL) {
	sprintf(tmpbuf, "Internal consistency error: pid %d not found",	pid);
	qcsi_error(LOG_CRIT, tmpbuf, NULL);
	return (QCS_NO_CMD_CLASS);
    }
    else
	return (cc_index);
}


qcs_cmd_class *qcsi_get_cmd_class(qcs_cmd_class_id cc_index)
{
    qcs_cmd_class *cur_class;
    
    if (cc_index >= MAXREQUESTS) {
	sprintf(tmpbuf, "Attempt to access out of range cmd_class %d", 
		cc_index);
	qcsi_error(LOG_CRIT, tmpbuf, NULL);
	return (NULL);
    }

    cur_class = &(cmd_classes[cc_index]);
    if (cur_class->descs == NULL) {
	sprintf(tmpbuf, "Attempt to access invalid cmd_class %d", cc_index);
	qcsi_error(LOG_CRIT, tmpbuf, NULL);
	return (NULL);
    }

    return (cur_class);
}


qcs_error_t qcsi_queue_request(qcs_cmd_class_id cc_index, 
			       qcs_client client, qcs_cmd cmd,
			       qcs_jobno jobno)
{
    qcs_cmd_class *cur_class;
    qcs_request *cur_req;

    if ((cur_class = qcsi_get_cmd_class(cc_index)) == NULL)
	return (QCS_FAILURE);

    sprintf(tmpbuf, "enqueuing request in class %d", cc_index);
    qcsi_debug(tmpbuf);

    /*
     * Our queue is really a circular data structure.  
     */

    if (cur_class->npending == MAXREQUESTS) {
	sprintf(tmpbuf, 
		"Unable to process command: queue for command class %d %s",
		cc_index, "is full");
	qcsi_error(LOG_ERR, tmpbuf, client);
	return (QCS_FAILURE);
    }

    cur_class->tail++;
    cur_class->tail %= MAXREQUESTS;
    cur_class->npending++;

    cur_req = &(cur_class->pending[cur_class->tail]);

    bcopy(&(client->id), &(cur_req->client_id), sizeof(qcs_client_id));
    cur_req->cmd = cmd;
    cur_req->jobno = jobno;

    if (cur_class->npending > 1) {
	sprintf(tmpbuf, 
		"Your command (%s) has been queued behind %d other job%s.",
		qcsi_unparse_cmd(cmd), cur_class->npending - 1, 
		(cur_class->npending - 1 == 1) ? "" : "s");
	if (qcsi_client_notify(client, tmpbuf))
	    qcsi_flush_client(&client);
    }

    return (QCS_SUCCESS);
}


qcs_error_t qcsi_dequeue_request(qcs_cmd_class_id cc_index,
				 qcs_request *request)
{
    qcs_cmd_class *cur_class;
    qcs_request *cur_req;
    
    if ((cur_class = qcsi_get_cmd_class(cc_index)) == NULL)
	return (QCS_FAILURE);

    sprintf(tmpbuf, "dequeuing from class %d", cc_index); 
    qcsi_debug(tmpbuf);

    if (cur_class->npending == 0) {
	sprintf(tmpbuf, "Attempt to dequeue from an empty command class");
	qcsi_error(LOG_CRIT, tmpbuf, NULL);
	return (QCS_FAILURE);
    }

    cur_req = &(cur_class->pending[cur_class->head]);

    bcopy((char *)cur_req, (char *)request, sizeof(qcs_request));
    bzero((char *)cur_req, sizeof(qcs_request));

    cur_class->head++;
    cur_class->head %= MAXREQUESTS;
    cur_class->npending--;
    
    return (QCS_SUCCESS);
}


char *qcsi_unparse_client(qcs_client client)
{
    static tmp[BUFSIZ];
    
    if (client) {
	sprintf(tmp, "%s (%d): %s%s%s@%s", 
		qcsi_resolve_host(client->qrpc->caddr.sin_addr),
		ntohs(client->qrpc->caddr.sin_port),
		client->auth_dat.pname,
		(client->auth_dat.pinst[0]) ? "." : "",
		client->auth_dat.pinst,
		client->auth_dat.prealm);
    }
    else
	strcpy(tmp, "NULL or flushed client");

    return ((char *)tmp);
}

void qcsi_dump_cmd_classes(void)
{
    int i;
    int j;
    qcs_cmd_class *c;
    qcs_cmd_desc *cur_cmd;

    for (i = 0; cmd_classes[i].descs; i++) {
	c = &(cmd_classes[i]);
	printf("Command class %d: \n", i);
	printf("  Commands: \n");
	for (cur_cmd = &(c->descs[0]); cur_cmd->cmd_name; cur_cmd++) 
	    printf("\t%s\n", cur_cmd->cmd_name);
	printf("  Pending requests");
	if (c->npending == 0)
	    printf(": none\n");
	else {
	    int i;
	    qcs_request *req;
	    qcs_client client;
	    
	    printf(" from first to last:\n");
	    for (i = c->head, j = 0; j < c->npending; j++,
		 i = (i + 1) % MAXREQUESTS) {
		req = &(c->pending[i]);
		client = qcsi_get_client_from_req(req);
		printf("  %s\n\t", qcsi_unparse_client(client));
		printf("%s\n", qcsi_unparse_cmd(req->cmd));
	    }
	}
    }		
}


static cmd_map_entry *qcsi_get_cmd_map_entry(qcs_cmd cmd)
{
    int cmp;    
    int interval;
    int index;
    int ckindex;
    
    /*
     * Find the largest power of two less than or equal to the 
     * number of commands.
     */
    for (index = 1; index <= ncmds ; index <<= 1);

    /*
     * We start checking at index with an interval of half of index
     */
    interval = index >> 1;

    /*
     * Do a binary search in the mapping table for the command
     */

    for (;;) {
	/* 
	 * index goes from 1 to ncmds; we want to go from 
	 * 0 to ncmds - 1.  Furthermore, index could be larger
	 * than ncmds if ncmds is not a power of two. 
	 * ckindex is the index into the table we actually want
	 * to check.  If index > ncmds, then we want the last
	 * command; otherwise, we want index - 1.  This could mean
	 * that we check the last command several times, but 
	 * since we only check lg(n) entries, it is not worth
	 * the overhead to code around this case.
	 */
	ckindex = (index > ncmds) ? ncmds - 1 : index - 1;
	cmp = strcmp(cmd_map[ckindex].desc.cmd_name, cmd[0]);
	if (cmp == 0) 
	    /* This is the command */
	    return (&(cmd_map[ckindex]));
	else {
	    /* 
	     * This is not the command.  First we find out whether
	     * we can keep searching 
	     */
	    if (interval == 0) {
		/* Command not found */
		return (NULL);
	    }
	    if (cmp > 0)
		index -= interval;
	    else
		index += interval;
	    
	    interval >>= 1;
	}
    }
}


qcs_dispatcher qcsi_get_dispatcher(qcs_cmd cmd)
{
    cmd_map_entry *entry;

    if ((entry = qcsi_get_cmd_map_entry(cmd)) == NULL)
	return (NULL);
    else 
	return (entry->desc.dispatcher);
}


qcs_cmd_class_id qcsi_get_cmd_class_id(qcs_cmd cmd)
{
    cmd_map_entry *entry;

    if ((entry = qcsi_get_cmd_map_entry(cmd)) == NULL)
	return (QCS_NO_CMD_CLASS);
    else 
	return (entry->id);
}


void qcsi_notify_jobs(void)
{
    int i, j, k;
    qcs_cmd_class *c;
    qcs_request *req;
    unsigned long now;
    qcs_client client;

    now = time(0);

    for (i = 0; cmd_classes[i].descs; i++) {
	c = (&cmd_classes[i]);
	for (j = c->head, k = 0; k < c->npending; k++, 
	     j = (j + 1) % MAXREQUESTS) {
	    req = &(c->pending[j]);
	    if (client = qcsi_get_client_from_req(req)) {
		/* 
		 * Notify the client if the notify time is greater
		 * than QCS_NOTIFYTIME or if the client was last 
		 * notified during this call to this function.  The
		 * latter check enables clients who have multiple jobs
		 * to hear about each job.
		 */
		if (((now - client->last_notified) >= QCS_NOTIFYTIME) ||
		    (client->last_notified == now)) {
		    sprintf(tmpbuf, "Your command (%s) is ", 
			    qcsi_unparse_cmd(req->cmd));
		    if (k == 0)
			strcat(tmpbuf, "currently being processed.");
		    else {
			sprintf(tmpbuf + strlen(tmpbuf), 
				"waiting behind %d other job%s.",
				k, (k == 1) ? "" : "s");
		    }
		    if (qcsi_client_notify(client, tmpbuf)) 
			qcsi_flush_client(&client);
		}
	    }
	}
    }
}


qcs_error_t qcsi_show_active_jobs(qcs_client client, char *name, char *inst, 
				  char *realm)
{
    qcs_bool all_jobs = FALSE;
    qcs_bool any_jobs = FALSE;
    qcs_client cur_client;
    qcs_cmd_class *c;
    qcs_request *req;
    int i, j, k;
    char lrealm[REALM_SZ + 1];

    if (name == NULL)
	all_jobs = TRUE;
    else {
	if (realm && (realm[0] == '\0')) {
	    if (krb_get_lrealm(lrealm, 1))
		strcpy(lrealm, KRB_REALM);
	    realm = lrealm;
	}
    }

    for (i = 0; cmd_classes[i].descs; i++) {
	c = (&cmd_classes[i]);
	for (j = c->head, k = 0; k < c->npending; k++, 
	     j = (j + 1) % MAXREQUESTS) {
	    req = &(c->pending[j]);
	    if (cur_client = qcsi_get_client_from_req(req)) {
		if (all_jobs ||
		    ((strcmp(name, cur_client->auth_dat.pname) == 0) &&
		     (strcmp(inst, cur_client->auth_dat.pinst) == 0) &&
		     (strcmp(realm, cur_client->auth_dat.prealm) == 0))) {
		    any_jobs = TRUE;
		    sprintf(tmpbuf, "%c%s (class %d, job %d)\n   %s",
			    (client == cur_client) ? '*' : ' ',
			    qcsi_unparse_client(cur_client), i, 
			    (j - c->head) % MAXREQUESTS,
			    qcsi_unparse_cmd(req->cmd));
		    if (qcs_client_message(client, tmpbuf)) {
			qcsi_flush_client(&client);
			return (QCS_FAILURE);
		    }
		}
	    }
	}
    }	
    if (! any_jobs) {
	/* 
	 * This cannot be global status or else the current job would be
	 * active...
	 */
	sprintf(tmpbuf, "No active jobs for %s%s%s@%s",
		name, (inst[0] ? "." : ""), inst, realm);
	qcs_client_message(client, tmpbuf);
    }

    return (QCS_SUCCESS);
}
