/*
 * 5799-WZQ (C) COPYRIGHT = NONE
 * LICENSED MATERIALS - PROPERTY OF IBM
 */
/* $Header:main.c 12.0$ */
/* $ACIS:main.c 12.0$ */
/* $Source: /ibm/acis/usr/src/ibm/rvd/server/RCS/main.c,v $ */

#ifndef lint
static char *rcsid = "$Header:main.c 12.0$";
#endif


#ifndef lint
static char rcsid_main_c[] = "$Header:main.c 12.0$";
#endif lint

/* Copyright 1984 by the Massachusetts Institute of Technology */
/* See permission and disclaimer notice in the file "notice.h" */
#include "notice.h"

/* Main entry point and top-level routines for the rvd server. */

#include	<stdio.h>
#include	<errno.h>
#include	<signal.h>
#include	<sys/types.h>
#include	<sys/time.h>
#include	<sys/ioctl.h>
#include	<sys/file.h>
#include	<netinet/in.h>
#include	<netinet/in_systm.h>
#include	<netinet/ip.h>
#include	<netinet/rvd.h>
#include	<machineio/vdconst.h>
#ifdef	KERBEROS
#include	<krb.h>
#endif	KERBEROS

#include	"rvd_types.h"
#include	"rvdadd.h"
#include	"custom.h"
#include	"logging.h"
#include	"ctl_pkt.h"
#include	"obj.h"
#include	"queue.h"
#include	"packet.h"
#include	"physd.h"
#include	"virtd.h"
#include	"conn.h"
#include	"extern.h"

extern	long	timeatstart;

main(argc, argv)

int	argc;				/* arg count */
char	**argv;				/* arg vector */
{
	register unsigned rvdmask;	/* select bitmask for rvd conn */
	register unsigned ctlmask;	/* select bitmask for ctl conn */
	int	waitflag;		/* select flag (it changes its args) */
	struct	timeval	sel_timeout;	/* select timeout */
	int	pid;
	int	fd;

	/*
	 *  Disassociate the server from /dev/tty...
	 */
	fd = (int) open("/dev/tty",O_RDWR);
	if (fd >= 0) {
		(void) ioctl(fd, TIOCNOTTY, 0);
		(void) close(fd);
		(void) close(0);
	}

	timeatstart = time(0);		/*Remember time the server started*/

	do_argv(argc, argv);		/* handle arguments */

	/*
	 * fork off a child to run in the background, then exit.
	 */

	if((pid = fork()) == -1) {
		fputs("rvdsrv: ", stderr);
		perror("fork");
		exit(-1);
	}
	else if(pid != 0)
		exit(0);

	/*
	 * we don't want parents waiting on us to close stdout/err,
	 * which might be piped, so we should close those now, as
	 * we don't use them.
	 */

	(void)close(0);
	(void)close(1);
	(void)close(2);

	/*
	 * now need to send error diagnostics somewhere, so
	 * we connect to the logger.
	 */

	openlog("rvdsrv", LOG_PID, LOG_RFS);
	syslog(LOG_INFO, "RVD Server Started");
	syslog(LOG_INFO, "%s", version_s1);
	syslog(LOG_INFO, "%s", version_s2);

	localhost.s_addr = gethostid();	/* where am i? */

	init_all();			/* call all initialization routines */
	pd_input();			/* set up the global virtual disk
					 * data base, including opening
					 * files containing vd's etc.
					 */
	rvdmask = net_init();		/* open RVD and UDP (control) */
	ctlmask = ctl_init();		/* connections (in nonblocking mode) */
	sel_timeout.tv_usec = 0;
	sel_timeout.tv_sec = 180;	/* 3 minute timeout */

	for (;;) {			/* wait for work, then do it */
		(void)time((time_t *)&now);	/* this is cheap, so there is */
						/* no problem doing everytime */
		waitflag = rvdmask | ctlmask;	/* select() is value/result */
		if (select(32, (fd_set *)&waitflag, (fd_set *) 0, (fd_set *) 0,
		    &sel_timeout) < 0 && errno != EINTR)
			bughalt(errno < sys_nerr ? sys_errlist[errno] :
			    "main: unknown select error");

		if (waitflag & rvdmask) {	/* pending RVD request */
			do_all_rvd_requests();	/* do all pending requests */
		}

		if (waitflag & ctlmask) {	/* pending control request */
			do_control_request();	/* read ctl pkt. and do it */
		}
	}
}

/* Handle the command line switches.  Present switches are:
 *	-l <level>		set the initial logging level to <level>
 *	-m <file>		use <file> as the master database file
 *	-r <file>		use <file> to restart the server on bughalt.
 */

do_argv(argc, argv)

int	argc;				/* arg count */
char	**argv;				/* arg vector */
{
	register int	i;		/* index */

	for (i = 1; i < argc; i++) {
		if (argv[i][0] == '-') {
			switch(argv[i][1]) {
			case 'l':
				if (++i >= argc || sscanf(argv[i], "%d",
				    &log_flag) != 1)
					goto usage;
				break;
			case 'm':
				if (++i >= argc)
					goto usage;
				rvdmaster = argv[i];
				break;
			case 'r':
				if (++i < argc) {
					if (argv[i][0] == '-')
						i--;
					else
						rvdrestart = argv[i];
				}
				restart_flag++;
				break;
			default:
				goto usage;
			}
		} else {
usage:
			fprintf(stderr, "usage: %s [-l loglevel] ", argv[0]);
			fprintf(stderr, "[-m masterfile] [-r [restartfile]]\n");
			exit(1);
		}
	}
}

/* Call all the necessary initialization routines.
 */

init_all()

{
	cn_init();
	pkt_init();
	pd_init();
/*	vd_init();	*/
}


do_all_rvd_requests()

/* First, read in all pending rvd packets, to try to avoid overflowing the
 * kernel queue.  Next, sort each packet into the appropriate read or write
 * queue for that packet's connection.  Finally, perform one read or write
 * request, then loop to the beginning.
 */

{
	register struct rvd_pkt	*pkt;		/* input packet to process */
	register u_char	etype;			/* error code if any */
	struct	pkt_q	pending;		/* pending input packets */
	struct	conn_q	workq;			/* list of conns with work */

	q_init(&pending);
	q_init(&workq);

	for (;;) {
		recv_all(&pending);		/* appending to packet q */

		if (q_empty(&pending) && q_empty(&workq)) /* nothing to do */
			break;

		for (pkt = rem_q_head(&pending, struct rvd_pkt *); pkt != NULL;
		     pkt = rem_q_head(&pending, struct rvd_pkt *) ) {

			if ((etype = pkt_check(pkt)) != RVDENOER) {
			    if (loglevel(LOG_CLIENT_ERROR))
					syslog(LOG_INFO,
					"do_request: bad packet from %s, err: 0x%X",
					inet_ntoa(pkt->rp_fhost), etype);
			    rvd_error(pkt, etype);
			    continue;
			}

			if ((time(0) - timeatstart) > RVDSRVDELAY) {
			  if(! (requests_enabled != 0
			      || pkt->rp_fhost.s_addr == localhost.s_addr)) {
				if(loglevel(LOG_ERROR))
					syslog(LOG_INFO,
					"do_request: premature packet (%s)",
					inet_ntoa(pkt->rp_fhost));
				rvd_error(pkt, RVDESNA);
				continue;
			  }
			} else if (requests_enabled == 0) continue;

			switch (pkt->rp_rvd.hdrun.allpkt.rvd_type) {
			case RVDRESPIN:
				respinup(pkt);
				break;
#ifdef	KERBEROS
			case RVDAUTHSPIN:
#endif	KERBEROS
			case RVDSPIN:
				spinup(pkt);
				break;
			case RVDSDOWN:
				spindown(pkt);
				break;
			case RVDREAD:
				sort_read(pkt, &workq);
				rw_qstats();
				break;
			case RVDWRITE:
				sort_write(pkt, &workq);
				rw_qstats();
				break;
			default:
				if (loglevel(LOG_CLIENT_ERROR)) {
				    syslog(LOG_INFO,
					   "do_request: bad packet type from %s, type = %D",
					    inet_ntoa(pkt->rp_fhost),
					    pkt->rp_rvd.hdrun.allpkt.rvd_type);
				}
				rvd_error(pkt, RVDEPKT);
				break;
			}
		}

		if (!q_empty(&workq))
			do_request(&workq);

	}
}


recv_all(queue)

/* Receive all pending rvd packets off the network, appending them to the
 * specified queue.
 */

register struct	pkt_q	*queue;		/* pending packet queue */
{
	register struct rvd_pkt	*pkt;	/* received packet */

	while ((pkt = net_recv()) != NULL)
		ins_q_tail(pkt, queue);
}


sort_read(pkt, workq)

/* Sort the specified read request into the queue of read requests its
 * connection.  If that connection is not yet enqueued on the work queue,
 * add it.  Request packets are enqueued in block number order.
 * The packet is assumed to have already been checked and byteswapped.
 */

register struct	rvd_pkt	*pkt;		/* read request packet */
register struct	conn_q	*workq;		/* queue of connections with work */
{
	register struct	conn	*cn;	/* connection to read on */
	register struct	rvdr	*rdp;	/* pointer to read packet part */
#ifdef	TEST_SERVER
	int	error;

	if((error = test_get_read_err(pkt->rp_fhost))) {
		rvd_error(pkt, error);
		return;
	}
#endif	TEST_SERVER

	rdp = (struct rvdr *)&pkt->rp_rvd;
	if ((cn = cn_lookup(pkt->rp_fhost, rdp->drive, rdp->index)) == NULL) {
		if (loglevel(LOG_ERROR)) {
			syslog(LOG_INFO, 
			       "sort_read: drive %D not active (%s)",
			       rdp->drive, inet_ntoa(pkt->rp_fhost));
		}
		rvd_error(pkt, RVDEND);	/* no such connection */
		return;
	}

	sorted_insque(pkt, &cn->cn_reads); /* sort into list of reads */
	if (!enqueued(cn))		/* not on work queue yet */
		ins_q_tail(cn, workq);	/* so put it on */
}


sort_write(pkt, workq)

/* Sort the specified write request into the queue of write requests its
 * connection.  If that connection is not yet enqueued on the work queue,
 * add it.  Request packets are enqueued in block number order.
 * The packet is assumed to have already been checked and byteswapped.
 */

register struct	rvd_pkt	*pkt;		/* read request packet */
register struct	conn_q	*workq;		/* queue of connections with work */
{
	register struct	conn	*cn;	/* connection to write on */
	register struct	rvdw	*wdp;	/* pointer to write packet part */
#ifdef	TEST_SERVER
	int	error;

	if((error = test_get_write_err(pkt->rp_fhost))) {
		rvd_error(pkt, error);
		return;
	}
#endif	TEST_SERVER

	wdp = (struct rvdw *)&pkt->rp_rvd;
	if ((cn = cn_lookup(pkt->rp_fhost, wdp->drive, wdp->index)) == NULL) {
		if (loglevel(LOG_ERROR)) {
			syslog(LOG_INFO, 
			       "sort_write: drive %D not active (%s)",
			       wdp->drive, inet_ntoa(pkt->rp_fhost));
		}
		rvd_error(pkt, RVDEND);	/* no such connection */
		return;
	}

	sorted_insque(pkt, &cn->cn_writes); /* sort into list of writes */
	if (!enqueued(cn))		/* not on work queue yet */
		ins_q_tail(cn, workq);	/* so put it on */
}


do_request(workq)

/* Do the work of the first request pending on the work queue.  Writes have
 * precedence over reads.  To try to achieve fairness, the connection block
 * is removed from the work queue, and reinserted at the end of the queue if
 * it still has work pending.
 */

register struct	conn_q	*workq;			/* queue of conns with work */
{
	register struct	conn	*cn;		/* connection to work on */

	if ((cn = rem_q_head(workq, struct conn *)) == NULL)
		return;

	if (!q_empty(&cn->cn_writes)) {		/* pending write requests */

		do_write(cn);			/* do a write request */

	}

	if (!q_empty(&cn->cn_reads)) {	/* pending read requests */

		do_read(cn);			/* do a read request */

	}

	if (!q_empty(&cn->cn_reads) || !q_empty(&cn->cn_writes))
		ins_q_tail(cn, workq);	/* put at end of work q */
}


do_control_request()

/* Read a single control packet from the control connection and perform
 * the request.
 */
{
	static	char	buf[CTLSIZE];	/* buffer for received packet */
	struct	sockaddr_in	fhost;	/* foreign host doing request */
	register int	size;		/* size of received packet */

	if ((size = ctl_recv(&fhost, buf, CTLSIZE)) <= 0)
		return;
	ctl_domsg(&fhost, buf, size);
}


rw_qstats()
{
	register int	qlen;
	register int	index;
	register int	remainder;
	register int	power_of_ten;
	register int	ten_to_power_of_ten;


	/*
	 * Do the corrections for rw_qlength.  Rw_qlength was incremented
	 * for the current packet.  The zero test prevents negative numbers.
	 */
	if (rw_qlength > MAX_RW_QLENGTH)		/* Almost imposible */
		rw_qlength = MAX_RW_QLENGTH;
	if (rw_qlength > 0)
		qlen = rw_qlength-1;
	else
		qlen = 0;
	if (qlen > max_qlength)
		max_qlength = qlen;


	/*
	 * Convert qlen to index and increment the sample count.
	 */
	if (qlen < 10) {
		index = qlen;
	} else {
		remainder = rw_qlength; 
		ten_to_power_of_ten = 1;
		for (power_of_ten = 0; remainder/10 != 0; power_of_ten++) {
			remainder /= 10;
			ten_to_power_of_ten *= 10;
		}

		index = 10*power_of_ten + qlen/ten_to_power_of_ten;
	}
	rw_qsize[index]++;
}
