/* 
 * $Id: s_fdfunc.c,v 1.2 91/05/03 03:05:52 qjb Exp Locker: qjb $
 * $Source: /afs/athena.mit.edu/astaff/project/qcs/src/RCS/s_fdfunc.c,v $
 * $Author: qjb $
 *
 * All file descriptor mappings are handled in this file.
 */

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


#include <stdio.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/param.h>
#include <qrpc.h>

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

/*
 * Table of file descriptor functions.  fd_functions[i] contains
 * NULL if we don't care about file descriptor, or, otherwise,
 * information about what the function of file descriptor i
 * has in the program.
 */
static qcs_fd_function *fd_functions;
static fd_set complete_read_set; /* All fd's we care about */
static int max_fd = 0;		/* Maximum fd we are selecting */

static char tmpbuf[BUFSIZ];

void qcsi_unregister_fd(int fd)
{
    qcs_fd_function *ff;

    /*
     * Unregister the descriptor we read for completion in our table and
     * remove it from the set being selected.
     */
    
    ff = &(fd_functions[fd]);

    switch (ff->fd_type) {
      case qf_unused:
      case qf_completion:

      case qf_listen:
	qrpc_destroy(&((qrpc_t)ff->data));
	break;

      case qf_client:
	qrpc_destroy(&((qrpc_t) ((qcs_client)ff->data)->qrpc));
	free((char *) ff->data);
	break;

      default:
	sprintf(tmpbuf, 
		"Internal consistency error in unregister: unknown type %d",
		ff->fd_type);
	qcsi_error(LOG_CRIT, tmpbuf, NULL);
	break;
    }
    
    ff->fd_type = qf_unused;
    ff->data = NULL;
    
    FD_CLR(fd, &complete_read_set);

    if (fd == max_fd)
	while (max_fd && (! FD_ISSET(max_fd, &complete_read_set)))
	    max_fd--;
}        


void qcsi_register_fd(int fd, qcs_fd_type fd_type, qcs_pointer data)
{
    /*
     * Register the descriptor we read for completion in our table 
     * and set it up for being selected.
     */
    
    
    if (fd_functions[fd].fd_type) {
	sprintf(tmpbuf, 
	       "Internal consistency error: register: fd %d already type %d",
	       fd, fd_functions[fd].fd_type);
	qcsi_error(LOG_CRIT, tmpbuf, NULL);
	qcsi_unregister_fd(fd);
    }
    
    fd_functions[fd].fd_type = fd_type;
    fd_functions[fd].data = data;
    
    if (fd > max_fd)
	max_fd = fd;

    FD_SET(fd, &complete_read_set);
}        


qcs_error_t qcsi_fd_init(char *errmsg)
{
    FD_ZERO(&complete_read_set);
    fd_functions = (qcs_fd_function *)
	calloc(getdtablesize(), sizeof(qcs_fd_function));
    if (fd_functions == NULL) {
	sprintf(errmsg, "Unable to allocate %d bytes for fd function table",
		sizeof(qcs_fd_function) * getdtablesize());
	return (QCS_ERR_MEMORY);
    }

    return (QCS_SUCCESS);
}


void qcsi_init_read_fd_set(fd_set *select_read_set)
{
    int i;
    
    FD_ZERO(select_read_set);

    /*
     * I realize that this is not the most efficient way of doing this, 
     * but I am trying not to break the abstraction barrier.
     */
    for (i = 0; i <= max_fd; i++)
	if (FD_ISSET(i, &complete_read_set))
	    FD_SET(i, select_read_set);
}


int qcsi_max_fd(void)
{
    return (max_fd);
}


qcs_fd_type qcsi_fd_type(int fd)
{
    return (fd_functions[fd].fd_type);
}


qcs_error_t qcsi_assert_fd_type(int fd, char *name, qcs_fd_type fd_type)
{
    if (fd_functions[fd].fd_type == fd_type)
	return (QCS_SUCCESS);
    else {
	sprintf(tmpbuf, "Internal consistency error: %s %s %d; got %d",
		name, "expected fd type", fd_type, fd_functions[fd].fd_type);
	qcsi_error(LOG_CRIT, tmpbuf, NULL);
	return (QCS_FAILURE);
    }
}


qcs_pointer qcsi_fd_data(int fd)
{
    return (fd_functions[fd].data);
}


void qcsi_shutdown_listener(void)
{
    int i;

    for (i = 0; i <= max_fd; i++) {
	if (fd_functions[i].fd_type == qf_listen)
	    qcsi_unregister_fd(i);
    }
}


void qcsi_flush_idle_jobs(void)
{
    int i;
    qcs_client client;

    for (i = 0; i <= max_fd; i++) {
	if (fd_functions[i].fd_type == qf_client) {
	    client = (qcs_client)fd_functions[i].data;
	    if ((client->nreq == 0) && (! client->shutdown))
		qcsi_flush_client(&client);
	}
    }	
}


qcs_bool qcsi_no_vulnerable_clients(void)
{
    int i;
    qcs_bool no_clients = TRUE;
    qcs_client client;
    
    for (i = 0; i <= max_fd; i++) {
	if (fd_functions[i].fd_type == qf_client) {
	    client = (qcs_client)fd_functions[i].data;
	    if ((! client->shutdown) || (client->nreq > 0))
		no_clients = FALSE;
	}	 
    }

    return (no_clients);
}


qcs_client qcsi_get_client_from_req(qcs_request *req)
{
    qcs_client client;

    if (fd_functions[req->client_id.fd].fd_type != qf_client)
	return (NULL);
    
    client = (qcs_client) fd_functions[req->client_id.fd].data;
    if (client->id.unique != req->client_id.unique)
	return (NULL);
    
    return (client);
}


void qcsi_dump_fd_functions(void)
{
    int i;
    qcs_fd_function *f;
    qcs_client client;

    for (i = 0; i <= max_fd; i++) {
	f = &(fd_functions[i]);
	printf("File descrptor %d:", i);
	switch (f->fd_type) {
	  case qf_unused:
	    printf(" unused.\n");
	    break;
	  case qf_completion:
	    printf(" completion fd.\n");
	    break;
	  case qf_listen:
	    printf(" listener.\n");
	    break;
	  case qf_client:
	    client = (qcs_client) f->data;
	    printf(" client:\n");
	    printf("\tHost: %s\n", 
		   qcsi_resolve_host(client->qrpc->caddr.sin_addr));
	    printf("\tPort: %d\n", ntohs(client->qrpc->caddr.sin_port));
	    printf("\tUser: %s%s%s@%s\n", 
		   client->auth_dat.pname,
		   (client->auth_dat.pinst[0]) ? "." : "",
		   client->auth_dat.pinst,
		   client->auth_dat.prealm);
	    printf("\tNumber of requests: %d\n", client->nreq);
	    printf("\tLast request: %s", ctime(&(client->last_req)));
	    printf("\tLast notification: %s", ctime(&(client->last_notified)));
	    break;
	  default:
	    printf(" unknown type %d.\n", f->fd_type);
	    break;
	}
    }
}


qcs_error_t qcsi_show_idle_clients(qcs_client client, char *name, char *inst, 
				   char *realm)
{
    qcs_bool all_jobs = FALSE;
    qcs_bool any_jobs = FALSE;
    qcs_client cur_client;
    int i;
    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; i <= max_fd; i++) {
	if (fd_functions[i].fd_type == qf_client) {
	    cur_client = (qcs_client)fd_functions[i].data;
	    if ((cur_client->nreq == 0) && 
		(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 (idle)", 
			(client == cur_client) ? '*' : ' ',
			qcsi_unparse_client(cur_client));
		if (qcs_client_message(client, tmpbuf)) {
		    qcsi_flush_client(&client);
		    return (QCS_FAILURE);
		}
	    }
	}
    }	
    if (! any_jobs) {
	sprintf(tmpbuf, "No idle clients");
	if (!all_jobs) {
	    sprintf(tmpbuf + strlen(tmpbuf), " for %s%s%s@%s",
		    name, (inst[0] ? "." : ""), inst, realm);
	}
	qcs_client_message(client, tmpbuf);
    }

    return (QCS_SUCCESS);
}
