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

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


#ifndef lint
static char rcsid_spins_c[] = "$Header:spins.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"

/* This file contains the spinup and spindown code. */

#include	<sys/types.h>
#include	<stdio.h>
#include	<errno.h>
#include	<netinet/in.h>
#include	<netinet/in_systm.h>
#include	<netinet/ip.h>
#include	<netinet/rvd.h>
#include	<machineio/vdconst.h>

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

static char	modes[] = "-rs?e";

struct	spin_stats {			/* spin up/down statistics */
	int	ss_epack;		/* nonexistent pack */
	int	ss_ebmd;		/* bad spinup mode */
	int	ss_enomd;		/* disallowed mode */
	int	ss_dupl;		/* duplicate spinup */
	int	ss_eomd;		/* already spun up in other mode */
	int	ss_exmd;		/* already spun up exclusive */
	int	ss_ebpwd;		/* bad password supplied */
	int	ss_ups;			/* successful spinups */
	int	ss_downs;		/* successful spindowns */
	int	ss_ddown;		/* duplicate spindowns */
} spin_stats;


respinup(pkt)

/* Perform the spinup requested by the specified packet:
 *	find the specified virtual disk
 *	verify that the specified spinup mode is allowed
 *	verify that the disk is not already spun up in a conflicting mode
 *	verify the password
 *	insure that this host doesn't already have this disk spun up
 *	create a new connection structure and fill it in
 *	return a spinup ack
 */

register struct	rvd_pkt	*pkt;		/* received packet */
{
	register struct	rvdrs	*rsup;	/* ptr to respinup packet proper */
	register struct	virtd	*vd;	/* virtual disk being spun up */
	register struct	conn	*cn;	/* new connection structure */
	register char	*pwd;		/* password for this mode */
	struct	virtd	*vd_look_uid();
#ifdef	TEST_SERVER
	int	error;

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

	rsup = (struct rvdrs *)&pkt->rp_rvd;

	if ((vd = vd_look_uid(rsup->vd_uid)) == NULL) { /* no such vd */
		spin_stats.ss_epack++;
		if (loglevel(LOG_ERROR)) {
			syslog(LOG_INFO,
			  "respinup: drive %D uid %D nonexistent (%s)",
			  rsup->drive, rsup->vd_uid, inet_ntoa(pkt->rp_fhost));
		}
		rvd_error(pkt, RVDEPACK);
		return;
	}

	switch (rsup->mode) {		/* validate mode, prepare for... */
					/* password check */
	case RVDMRO:			/* read-only mode */
		pwd = vd->vd_ropasswd;	/* use read-only password */
		break;
	case RVDMSHR:
		pwd = vd->vd_shpasswd;	/* shared mode password */
		break;
	case RVDMEXC:
		pwd = vd->vd_expasswd;	/* exclusive mode password */
		break;
	default:			/* bad mode */
		spin_stats.ss_ebmd++;
		if (loglevel(LOG_ERROR)) {
			syslog(LOG_INFO,
			  "spinup: drive %D uid %D bad mode %D (%s)",
			  rsup->drive, rsup->vd_uid, rsup->mode,
			  inet_ntoa(pkt->rp_fhost));
		}
		rvd_error(pkt, RVDEBMD);
		return;
	}

	/*
	 * The mode must be compatible with globally allowed modes and
	 * with those of the pack itself, OR the password must be the
	 * authorization passwd.
	 */
	if(!((rsup->mode & gmodes && rsup->mode & vd->vd_modes) ||
			authorized(rsup->cpblty,FALSE,FALSE))) {
		spin_stats.ss_enomd++;
		if (loglevel(LOG_ERROR)) {
			if ( (!(rsup->mode & gmodes) && 
				(gmodes != gmodes_orig)) ||
				(!(rsup->mode & vd->vd_modes) &&
				(vd->vd_modes != vd->vd_modes_orig)) ) {
				syslog(LOG_INFO,
					"respinup: drive %D uid %D, mode %D temporarily unavailable (%s)",
					rsup->drive, rsup->vd_uid, rsup->mode,
					inet_ntoa(pkt->rp_fhost));
			} else {
				syslog(LOG_INFO,
					"respinup: drive %D uid %D disallowed mode %D (%s)",
					rsup->drive, rsup->vd_uid, rsup->mode,
					inet_ntoa(pkt->rp_fhost));
			}
		}
		if ( (!(rsup->mode & gmodes) && (gmodes != gmodes_orig)) ||
		     (!(rsup->mode & vd->vd_modes) &&
				(vd->vd_modes != vd->vd_modes_orig)) ) {
			rvd_error(pkt, RVDETBMD);
		} else
			rvd_error(pkt, RVDEBMD);
		return;
	}

	/*
	 * host must be owning host or password must be appropriate for
	 * pack and mode OR must be authorization password
	 */
	if(vd->vd_host.s_addr != pkt->rp_fhost.s_addr && *pwd != '\0' &&
		strncmp(pwd, rsup->cpblty, sizeof(rsup->cpblty) - 1) != 0 &&
			!authorized(rsup->cpblty,FALSE,FALSE)) {
		spin_stats.ss_ebpwd++;
		if (loglevel(LOG_ERROR)) {
			syslog(LOG_INFO,
			  "respinup: drive %D uid %D bad password (%s)",
			  rsup->drive, rsup->vd_uid, inet_ntoa(pkt->rp_fhost));
		}
		rvd_error(pkt, RVDEBPWD);
		return;
	}

	if ((cn = cn_unique(pkt->rp_fhost, rsup->drive)) != NULL) {

/* This drive already spun up.  If a different disk or other mode, error.
 * If the nonces match, this must be a retransmitted spinup, so just ack.
 * If the nonces don't match, return the "identical pack already spun up"
 * indication.
 */

		spin_stats.ss_dupl++;

		if (cn->cn_virtd != vd) {
		    if (loglevel(LOG_ERROR)) {
			syslog(LOG_INFO, "respinup: drive %D uid %s already spunup on %D (%s)",
			  	rsup->drive, rsup->vd_uid, cn->cn_drive,
			  	inet_ntoa(pkt->rp_fhost));
		    }
		    rvd_error(pkt, RVDESPN);
		    return;
		}

		if (rsup->mode != vd->vd_mode) {
		    spin_stats.ss_eomd++;
		    if (loglevel(LOG_ERROR)) {
				syslog(LOG_INFO,
				"respinup: drive %D uid %D mode conflict (%s)",
				rsup->drive, rsup->vd_uid, inet_ntoa(pkt->rp_fhost));
		    }
		    rvd_error(pkt, RVDEOMD); /* no */
		    return;
		}

		if (loglevel(LOG_SPINS)) {
		    syslog(LOG_INFO,
			   "respinup: drive %D uid %D duplicate spinup (%s)",
			   rsup->drive, rsup->vd_uid, inet_ntoa(pkt->rp_fhost));
		}
		spinack(pkt, (u_long)vd->vd_blocks, cn->cn_index, vd->vd_uid,
		    (u_char)(cn->cn_nonce == rsup->nonce ? RVDENOER : RVDEIDA));
		return;
	}

	if (vd->vd_mode != RVDMNONE)	/* already spun up somewhere */
		if (rsup->mode != vd->vd_mode) { /* mode match? */
			spin_stats.ss_eomd++;
			if (loglevel(LOG_ERROR)) {
				syslog(LOG_INFO,
					"respinup: drive %D uid %D mode conflict (%s)",
					rsup->drive, rsup->vd_uid,
					inet_ntoa(pkt->rp_fhost));
			}
			rvd_error(pkt, RVDEOMD); /* no */
			return;
		} else if (vd->vd_mode == RVDMEXC) { /* already exclusive? */
			spin_stats.ss_exmd++;
			if (loglevel(LOG_ERROR)) {
				syslog(LOG_INFO,
				"respinup: drive %D uid %D already exclusive (%s)",
				rsup->drive, rsup->vd_uid, inet_ntoa(pkt->rp_fhost));
			}
			rvd_error(pkt, RVDEXMD); /* yes */
			return;
		}

/* If the physical drive for this pack has been disabled return an error.
 */
	if ((vd->pd_forw->pd_mode & RVD_USE_PHYS) == NULL) {
		if (loglevel(LOG_CLIENT_ERROR)) {
			syslog(LOG_INFO,
			"respinup: physical device for drive %D was disabled (%s)",
			rsup->drive, inet_ntoa(pkt->rp_fhost));
		}
		rvd_error(pkt, RVDEPDIS);
		return;
	}

/* Almost everything checks out.  Create and fill in a conn structure, then
 * send a spinack.  If cn_new fails it is because there was not enough memory
 * to extend the all_conns array.
 */

	spin_stats.ss_ups++;
	cn = cn_new(pkt->rp_fhost, rsup->drive, rsup->nonce,
		rsup->br_factor, vd, rsup->mode);
	if (cn == (struct conn *)NULL) {
		if (loglevel(LOG_ERROR)) {
			syslog(LOG_INFO,
			"respinup: could not expand the connection array (%s)",
			inet_ntoa(pkt->rp_fhost));
		}
		rvd_error(pkt, RVDETCG); /* too many connections for server */
		return;
	}
	if (loglevel(LOG_SPINS))
		log_up(cn);
	spinack(pkt, (u_long)vd->vd_blocks, cn->cn_index, vd->vd_uid, RVDENOER);
}

spinup(pkt)

/* Perform the spinup requested by the specified packet:
 *	find the specified virtual disk
 *	verify that the specified spinup mode is allowed
 *	verify that the disk is not already spun up in a conflicting mode
 *	verify the password
 *	insure that this host doesn't already have this disk spun up
 *	create a new connection structure and fill it in
 *	return a spinup ack
 */

	register struct	rvd_pkt	*pkt;	/* received packet */
{
	register struct	rvds	*sup;	/* ptr to spinup packet proper */
#ifdef	KERBEROS
	register struct	rvdas	*asup;	/* ptr to authenticated spinup packet*/
#endif	KERBEROS
	register struct	virtd	*vd;	/* virtual disk being spun up */
	register struct	conn	*cn;	/* new connection structure */
	register char	*pwd;		/* password for this mode */
	boolean	auth_flag;		/* Flag for authenticated spinup. */
	boolean	spinup_ok;		/* Used in authorization check. */
	char	*uname = NULL;		/* Authenticated user name. */

#ifdef	TEST_SERVER
	int	error;

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

	auth_flag = FALSE;
	sup = (struct rvds *)&pkt->rp_rvd;
#ifdef	KERBEROS
	if (sup->rvd_type == RVDAUTHSPIN) {
		auth_flag = TRUE;
		asup = (struct rvdas *)&pkt->rp_rvd;
	}
#endif	KERBEROS

	if ((vd = vd_lookup(sup->pname)) == NULL) { /* no such vd */
		spin_stats.ss_epack++;
		if (loglevel(LOG_ERROR)) {
			syslog(LOG_INFO,
			  "spinup: drive %D pack %s nonexistent (%s)",
			  sup->drive, sup->pname, inet_ntoa(pkt->rp_fhost));
		}
		rvd_error(pkt, RVDEPACK);
		return;
	}

	switch (sup->mode) {		/* validate mode, prepare for... */
					/* password check */
	case RVDMRO:			/* read-only mode */
		pwd = vd->vd_ropasswd;	/* use read-only password */
		break;
	case RVDMSHR:
		pwd = vd->vd_shpasswd;	/* shared mode password */
		break;
	case RVDMEXC:
		pwd = vd->vd_expasswd;	/* exclusive mode password */
		break;
	default:			/* bad mode */
		spin_stats.ss_ebmd++;
		if (loglevel(LOG_ERROR)) {
			syslog(LOG_INFO,
			  "spinup: drive %D pack %s bad mode %D (%s)",
			  sup->drive, sup->pname, sup->mode,
			  inet_ntoa(pkt->rp_fhost));
		}
		rvd_error(pkt, RVDEBMD);
		return;
	}

	/*
	 * The mode must be compatible with globally allowed modes and
	 * with those of the pack itself, OR the password must be the
	 * authorization passwd.
	 */
	if (! ((sup->mode & gmodes && sup->mode & vd->vd_modes)
	  || authorized(sup->cpblty,FALSE,FALSE))) {
		spin_stats.ss_enomd++;
		if (loglevel(LOG_ERROR)) {
			if ( (!(sup->mode & gmodes) && 
				(gmodes != gmodes_orig)) ||
				(!(sup->mode & vd->vd_modes) &&
				(vd->vd_modes != vd->vd_modes_orig)) ) {
				syslog(LOG_INFO,
					"spinup: drive %D pack %s, mode %D temporarily unavailable (%s)",
					sup->drive, sup->pname, sup->mode,
					inet_ntoa(pkt->rp_fhost));
			} else {
				syslog(LOG_INFO,
					"spinup: drive %D pack %s disallowed mode %D (%s)",
					sup->drive, sup->pname, sup->mode,
					inet_ntoa(pkt->rp_fhost));
			}
		}
		if ( (!(sup->mode & gmodes) && (gmodes != gmodes_orig)) ||
		     (!(sup->mode & vd->vd_modes) &&
				(vd->vd_modes != vd->vd_modes_orig)) ) {
			rvd_error(pkt, RVDETBMD);
		} else
			rvd_error(pkt, RVDEBMD);
		return;
	}

	/* The spinup is authorized if:
	 * 	1. host is the owning host
	 *	2. no password required for this pack and mode
	 *	3. if this is not an authenticated spinup:
	 *		password must be correct for pack and mode
	 *		OR the capability must be the operations password
	 *	4. if this is an authenticated spinup:
	 *		authenticated user is pack owner or on operations list
	 *		OR capability list includes the authenticated user.
	 */
	spinup_ok = FALSE;
	/* 1. & 2. */
	if (vd->vd_host.s_addr == pkt->rp_fhost.s_addr || *pwd == '\0') {
		spinup_ok = TRUE;
	}

	/* 3. */
	if (!spinup_ok && auth_flag == FALSE) {
		if ( is_aclname(pwd) == FALSE
		&& ((strncmp(pwd, sup->cpblty, sizeof(sup->cpblty) - 1) == 0)
		||   authorized(sup->cpblty, FALSE, FALSE))) {
			spinup_ok = TRUE;
		}
	}
#ifdef	KERBEROS
	/* 4. */
	if (!spinup_ok && auth_flag == TRUE) {
		if (authenticate(&asup->authent, vd->vd_desc, pkt->rp_fhost))
			spinup_ok = TRUE;
		else {
			if (is_aclname(pwd)) {
			    uname = auth_to_user(&asup->authent,pkt->rp_fhost);
			    if (uname != NULL && ismember(uname, pwd, FALSE))
				spinup_ok = TRUE;
			}
		}
	}
#endif	KERBEROS

	if (!spinup_ok) {
		spin_stats.ss_ebpwd++;
		if (loglevel(LOG_CLIENT_ERROR)) {
			if (uname) {
			    syslog(LOG_INFO,
				"spinup: drive %D pack %s, unauthorized spinup by %s (%s)",
				uname,
				sup->drive,sup->pname,
				inet_ntoa(pkt->rp_fhost));
			} else {
			    syslog(LOG_INFO,
				"spinup: drive %D pack %s unauthorized spinup (%s)",
				sup->drive,sup->pname,
				inet_ntoa(pkt->rp_fhost));
			}
		}
		rvd_error(pkt, RVDEBPWD);
		return;
	}

	if ((cn = cn_unique(pkt->rp_fhost, sup->drive)) != NULL) {

/* This drive already spun up.  If a different disk or other mode, error.
 * If the nonces match, this must be a retransmitted spinup, so just ack.
 * If the nonces don't match, return the "identical pack already spun up"
 * indication.
 */

		spin_stats.ss_dupl++;

		if (cn->cn_virtd != vd) {
		    if (loglevel(LOG_ERROR)) {
				syslog(LOG_INFO,
				"spinup: drive %D pack %s already spunup on %D (%s)",
			  	sup->drive, sup->pname, cn->cn_drive,
			  	inet_ntoa(pkt->rp_fhost));
		    }
		    rvd_error(pkt, RVDESPN);
		    return;
		}

		if (sup->mode != vd->vd_mode) {
		    spin_stats.ss_eomd++;
		    if (loglevel(LOG_ERROR)) {
				syslog(LOG_INFO,
				"spinup: drive %D pack %s mode conflict (%s)",
				sup->drive, sup->pname, inet_ntoa(pkt->rp_fhost));
		    }
		    rvd_error(pkt, RVDEOMD); /* no */
		    return;
		}

		if (loglevel(LOG_SPINS)) {
		    syslog(LOG_INFO,
			   "spinup: drive %D pack %s duplicate spinup (%s)",
			   sup->drive, sup->pname, inet_ntoa(pkt->rp_fhost));
		}
		spinack(pkt, (u_long)vd->vd_blocks, cn->cn_index, vd->vd_uid,
		    (u_char)(cn->cn_nonce == sup->nonce ? RVDENOER : RVDEIDA));
		return;
	}

	if (vd->vd_mode != RVDMNONE) {	/* already spun up somewhere */
		if (sup->mode != vd->vd_mode) { /* mode match? */
		    spin_stats.ss_eomd++;
		    if (loglevel(LOG_ERROR)) {
				syslog(LOG_INFO,
				"spinup: drive %D pack %s mode conflict (%s)",
				sup->drive, sup->pname, inet_ntoa(pkt->rp_fhost));
		    }
		    rvd_error(pkt, RVDEOMD); /* no */
		    return;
		} else if (vd->vd_mode == RVDMEXC) { /* already exclusive? */
		    spin_stats.ss_exmd++;
		    if (loglevel(LOG_ERROR)) {
				syslog(LOG_INFO,
				"spinup: drive %D pack %s already exclusive (%s)",
				sup->drive, sup->pname, inet_ntoa(pkt->rp_fhost));
		    }
		    rvd_error(pkt, RVDEXMD); /* yes */
		    return;
		}
	}

/* If the physical drive for this pack has been disabled return an error.
 */
	if ((vd->pd_forw->pd_mode & RVD_USE_PHYS) == NULL) {
		if (loglevel(LOG_CLIENT_ERROR)) {
			syslog(LOG_INFO,
			"spinup: physical device for drive %D was disabled (%s)",
			sup->drive, inet_ntoa(pkt->rp_fhost));
		}
		rvd_error(pkt, RVDEPDIS);
		return;
	}

/* Almost everything checks out.  Create and fill in a conn structure, then
 * send a spinack.  If cn_new fails it is because there was not enough memory
 * to extend the all_conns array.
 */

	spin_stats.ss_ups++;
	cn = cn_new(pkt->rp_fhost, sup->drive, sup->nonce, sup->br_factor, vd,
	    sup->mode);
	if (cn == (struct conn *)NULL) {
		if (loglevel(LOG_ERROR)) {
			syslog(LOG_INFO,
			"spinup: could not expand the connection array (%s)",
			inet_ntoa(pkt->rp_fhost));
		}
		rvd_error(pkt, RVDETCG); /* too many connections for server */
		return;
	}
	if (loglevel(LOG_SPINS))
		log_up(cn);
	spinack(pkt, (u_long)vd->vd_blocks, cn->cn_index, vd->vd_uid, RVDENOER);
}


spindown(pkt)

/* Perform the spindown requested by the specified packet.  Just look up
 * the connection, terminate it, and return an acknowledgement.  If there
 * is no such connection, return an acknowledgement anyway.
 * Eventually this routine should check the spindown password.
 */

register struct	rvd_pkt	*pkt;		/* received packet */
{
	register struct	rvdsd	*sdn;	/* ptr to spindown part of packet */
	register struct	conn	*cn;	/* connection block to spin down */
	register u_char	mode;		/* mode drive was spun up in */
#ifdef	TEST_SERVER
	int	error;

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

	sdn = (struct rvdsd *)&pkt->rp_rvd;

	if ((cn = cn_lookup(pkt->rp_fhost, sdn->drive, sdn->index)) == NULL) {
		spin_stats.ss_ddown++;
		if (loglevel(LOG_CLIENT_ERROR)) {
			syslog(LOG_INFO, 
			  "spindown: drive %D not active (%s)",
			  sdn->drive, inet_ntoa(pkt->rp_fhost));
		}
		sdack(pkt, RVDMNONE);	/* just return spindown ack */
		return;
	}

	spin_stats.ss_downs++;
	mode = cn->cn_virtd->vd_mode;	/* get current spinup mode */
	cn_end(cn);			/* terminate the connection */
	sdack(pkt, mode);		/* return spindown ack */
}


log_up(cn)

/* Log the specified spinup to the standard error.
 */

register struct	conn	*cn;		/* newly created connection */
{
	register struct virtd	*vd;	/* virtual disk spun up */

	vd = cn->cn_virtd;
	syslog(LOG_INFO, "spinup: drive %D pack %s mode %c (%s)",
	  cn->cn_drive, vd->vd_pack, modes[vd->vd_mode],
	  inet_ntoa(cn->cn_fhost));
}


log_down(cn)

/* Log the spindown of the specified connection.
 */

register struct	conn	*cn;		/* connection being spun down */
{
	register struct	virtd	*vd;	/* virtual disk being spun down */

	vd = cn->cn_virtd;
	syslog(LOG_INFO, "spindown: drive %D pack %s mode %c (%s)",
	  cn->cn_drive, vd->vd_pack, modes[vd->vd_mode],
	  inet_ntoa(cn->cn_fhost));
}


spin_show()

/* Show spinup/spindown statistics in log.
 */
{
	syslog(LOG_INFO, "Spinup/spindown statistics:");
	syslog(LOG_INFO, " %8d    nonexistent pack", spin_stats.ss_epack);
	syslog(LOG_INFO, " %8d    bad spinup mode", spin_stats.ss_ebmd);
	syslog(LOG_INFO, " %8d    disallowed mode", spin_stats.ss_enomd);
	syslog(LOG_INFO, " %8d    duplicate spinups", spin_stats.ss_dupl);
	syslog(LOG_INFO, " %8d    already up in other mode",
	    spin_stats.ss_eomd);
	syslog(LOG_INFO, " %8d    already in exclusive mode",
	    spin_stats.ss_exmd);
	syslog(LOG_INFO, " %8d    bad password", spin_stats.ss_ebpwd);
	syslog(LOG_INFO, " %8d    spinups", spin_stats.ss_ups);
	syslog(LOG_INFO, " %8d    spindowns", spin_stats.ss_downs);
	syslog(LOG_INFO, " %8d    duplicate spindowns\n", spin_stats.ss_ddown);
}
