/* 
 * $Id: s_main.c,v 1.2 91/05/03 03:06:37 qjb Exp $
 * $Source: /afs/athena.mit.edu/astaff/project/qcs/src/RCS/s_main.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_main.c,v 1.2 91/05/03 03:06:37 qjb Exp $";
#endif /* !lint && !SABER || RCS_HDRS */


#include <stdio.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
#include <krb.h>
#include <des.h>
#include <qrpc.h>

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

int qcs_debug;

static int completion_fds[2];

static qcs_bool set_up = FALSE;	/* Are we set up already? */

/* 
 * Being shut down means we don't take any new connections but 
 * still allow old ones to finish.
 */
static qcs_bool shutdown = FALSE; /* Have we been shut down? */
static qcs_client shutdown_client = NULL; /* client that requested shutdown */

static char tmpbuf[BUFSIZ];


/* 
 * Put activity on the completion pipe so that select in qcs_server_mainloop
 * will return.
 */
void qcsi_complete(char *job_type)
{
    (void) write (completion_fds[1], job_type, 1);
}

static qcs_sigtype qcsi_chld(void) 
{
    qcsi_complete(QCS_JT_FORK);
    return 0;
}


void qcsi_mark_server_shutdown(qcs_client client)
{
    shutdown = TRUE;
    shutdown_client = client;
    if (client)
	client->shutdown = TRUE;
}

qcs_bool qcsi_is_server_shutdown(void)
{
    return (shutdown);
}

void qcs_setup_server_params(qcs_server_params *params, char *aname,
			     char *keyfile, int timeout, char *service,
			     int port)
{
    params->aname = aname;
    params->keyfile = keyfile;
    params->timeout = timeout;
    params->service = service;
    /* qrpc library puts port in host byte order so we don't have to */
    params->port = port;	

    qcsi_init_aname_keyfile(params->aname, params->keyfile);
}


qcs_error_t qcs_server_setup(qcs_cmd_desc **descslist,
			     qrpc_t *qrpcp, qcs_server_params *params,
			     char *errmsg)
  /*
   * Requires:
   *   All descriptions in class_descs must contain objects that
   *   will exist throughout the life of the server.  (This routine
   *   does not copy the descriptions passed in in class_descs but
   *   rather sets up pointers to them.)  The same restriction applies
   *   to params.
   */
{
    qrpc_error_t status = QCS_SUCCESS;
    qrpc_server_info server_info;
    qrpc_t listen_qrpc;		/* qrpc object for listening for connections */
    

    if (set_up)
	return (QCS_SUCCESS);


    if (status = qcsi_fd_init(errmsg))
	return (status);

    /* Set up command classes */
    if (status = qcsi_cmdcls_init(errmsg, descslist))
	return (status);


    /*
     * Create completion pipe.  When we receive a SIGCHLD, the child
     * handler sends a byte over this pipe.  That way, we can take
     * care of handling children from the main select loop.
     */
    if (pipe(completion_fds) == -1) {
	sprintf(errmsg, "Unable to create completion pipe: %s",
		sys_errlist[errno]);
	return(QCS_ERR_PIPE);
    }

    /*
     * Register the descriptor we read for completion in our table 
     * and set it up for being selected.
     */
    qcsi_register_fd(completion_fds[0], qf_completion, NULL);
        
    /*
     * Set up the child signal handler now that we have the
     * pipes...
     */
    signal(SIGCHLD, qcsi_chld);

    /*
     * Dump state to stdout on SIGQUIT.  Useful for debugging.
     */
    signal(SIGQUIT, qcsi_dump);


    /*
     * Set up the qrpc object with which we will listen for 
     * new connections.
     */
    (void) qrpc_init_listen_server_info(&server_info, params->service, 
					params->port, QCS_BACKLOG);

    *qrpcp = NULL;

    if (status = qrpc_create_server(qrpcp, &server_info, qcsi_opcodes, 
				    QCS_MINV, QCS_MAXV)) {
	strcpy(errmsg, qrpc_error_string(*qrpcp, status));
	return(status);
    }

    listen_qrpc = *qrpcp;

    qcsi_register_fd(listen_qrpc->listen_info->sock, qf_listen, 
		     (qcs_pointer) listen_qrpc);

    set_up = TRUE;
    return (QCS_SUCCESS);
}


qcs_error_t qcs_server_mainloop(void)
{
    fd_set select_read_set;	/* To actually pass to select(2) */
    int cur_fd;
    qcs_fd_type fd_type;
    int max_fd;
    int sel_ret;		/* return value of select */
    struct timeval periodic;	/* timer for select */
    time_t last_periodic = 0;
    
    static qcs_bool in_mainloop = FALSE; 

    if (! set_up)
	return (QCS_ERR_NOTSETUP);

    /*
     * This function is NOT re-entrant.  It counts on being the only
     * accessor of certain static tables.  Therefore, it is very important
     * the in_mainloop variable be set to false before this procedure
     * returns.
     */

    if (in_mainloop)
	return (QCS_ERR_REENTER);

    in_mainloop = TRUE;

    /*
     * Loop forever accepting new connections and processing
     * old ones
     */

    for (;;) {
	qcsi_init_read_fd_set(&select_read_set);
	max_fd = qcsi_max_fd();
	
	/*
	 * Prepare for timeout.  We want select to return every so often
	 * so that we can do our periodic checks even when there is not 
	 * a lot of activity going on.  We do this each time because
	 * select specifies that the timer value may eventually be
	 * modified.
	 */
	
	periodic.tv_sec = QCS_PERIODICTIME;
	periodic.tv_usec = 0;

	sel_ret = select(max_fd + 1, &select_read_set, NULL, NULL, &periodic);
	switch (sel_ret) {
	  case -1:
	    if (errno != EINTR) {
		sprintf(tmpbuf, "select failed: %s", sys_errlist[errno]);
		qcsi_error(LOG_CRIT, tmpbuf, NULL);
		qcsi_dump();
	    }
	    break;
	    
	  default:
	    if (sel_ret > 0) {
		sprintf(tmpbuf, "some activity detected...");
		qcsi_debug(tmpbuf);
		for (cur_fd = 0; cur_fd <= max_fd; cur_fd ++) 
		    if (FD_ISSET(cur_fd, &select_read_set)) {
			fd_type = qcsi_fd_type(cur_fd);
			switch (fd_type) {
			    
			  case qf_listen:
			    sprintf(tmpbuf, "listen");
			    qcsi_debug(tmpbuf);
			    qcsi_handle_connection(cur_fd);
			    break;
			    
			  case qf_client:
			    sprintf(tmpbuf, "client");
			    qcsi_debug(tmpbuf);
			    qcsi_handle_request(cur_fd);
			    break;
			    
			  case qf_completion:
			    sprintf(tmpbuf, "completion");
			    qcsi_debug(tmpbuf);
			    qcsi_handle_completion(cur_fd);
			    break;
			    
			  default:
			    sprintf(tmpbuf, "Internal consistency error %s",
				    "(qcsi_handle_fd): unknown type");
			    qcsi_error(LOG_CRIT, tmpbuf, NULL);
			}
		    }
	    }
	    
	    /* 
	     * Do periodic activities if it has been long enough
	     * since last time we did them.
	     */
	    if (time(0) - last_periodic > QCS_PERIODICTIME) {
		sprintf(tmpbuf, "doing periodic activities");
		qcsi_debug(tmpbuf);
		qcsi_handle_periodic();
		last_periodic = time(0);
	    }
	    
	    break;
	}
	
	if (shutdown) {
	    if (qcsi_no_vulnerable_clients()) {
		if (shutdown_client) {
		    sprintf(tmpbuf, "All jobs have exited; server exiting.");
		    (void) qcs_client_message(shutdown_client, tmpbuf);
		    shutdown_client->shutdown = FALSE;
		    (void) qcsi_flush_client(&shutdown_client);
		}
		break;
	    }
	    else if (shutdown_client) {
		unsigned long now;
		now = time(0);
		if (shutdown_client->last_notified - now > QCS_NOTIFYTIME) {
		    qcs_sd_status(shutdown_client, NULL);
		    shutdown_client->last_notified = now;
		}
	    }
	}
    }

    in_mainloop = FALSE;
    return (QCS_SUCCESS);
}
