static char rcsid[] = "$Header: dfs_crash.c,v 820.1 86/12/04 19:44:03 root Exp $";
static char sccsid[]="%W% %Y% %Q% %G%";

/************************************************************************
*									*
*				Copyright 1984				*
*			VALID LOGIC SYSTEMS INCORPORATED		*
*									*
*	This listing contains confidential proprietary information	*
*	which is not to be disclosed to unauthorized persons without	*
*	written consent of an officer of Valid Logic Systems 		*
*	Incorporated.							*
*									*
*	The copyright notice appearing above is included to provide	*
*	statutory protection in the event of unauthorized or 		*
*	unintentional public disclosure.				*
*									*
************************************************************************/

/*
 *
 *	Bakul shah
 *
 * bvs 840329 -- original version
 * jht 841115,16,26-29 -- complete conversion to use prpc.
 * jht 850215 -- invalidate local cached texts when host goes down.
 * jht 850220 -- handle case where u_cdir is on a crashed server.
 * jht 850301 -- conversion to use new errno.h error-code mnemonics.
 * jht 850401 -- fill out the gathering of statistics.
 * jht 850404 -- cleanup connection-layer references.
 * jht 850412 -- Inappropriate panic("dfsServer_exception"):
 *		 rp not in i_rmt_node chain -- flaming death is too severe.
 * jht 850412 -- Correct and cleanup some diagnostic printf-s.
 * jht 850414 -- Clear i_rmt_lock along with DFS_RMT_LOCKED flag.
 */

#include "../h/param.h"
#include "../h/ioctl.h"
#include "../h/dir.h"
#include "../h/user.h"

#include "../vnet/vnet.h"
#include "../conn/conn.h"
#include "../rpc/rpc.h"
#include "../dfs/dfs.h"

#include "../h/inode.h"
#include "../h/file.h"
#include "../h/socket.h"
#include "../h/kernel.h"
#include "../net/if.h"
#include "../h/mbuf.h"

extern	char	     *	myName;			/* Set by conn_enable() */
extern	connection_t *	conn_findConnection();

	/*
	 * Inode graveyard for aggregating
	 * references to dead servers.
	 * There is no analog for dead clients.
	 */
	struct inode	genericDeadServerInode;	/* Agent inode graveyard */

/*
 * Exception handled on client-side:
 *
 * Deal with the consequences
 * of a connection-level state-change
 * detected at the client-side.
 * I.e., the (remote) serving-side
 * changed state (died or was reborn.)
 */
dfsClient_exception(serverConn, connException)	/* A server-host up or down! */
	connection_t *	serverConn;
	int		connException;
{
	register struct	inode	*	ip;

    if (!serverConn)
		return;
    if (serverConn == conn_findConnection(&myNode)) /* It's us: nothing to do */
		return;
    dfs_incClient(exception);
    DFS_DEBUG(12,("dfsClient_exception/I-0: (%s) @ '%s' for serverConn=0x%X (%s)\n",
	( (connException == CONN_UP)	? "CONN_UP"
	 :(connException == CONN_DOWN)	? "CONN_DOWN"
					: "UnKnownException"
	),
	myName,
	serverConn, serverConn->name));

   switch (connException) {
   case CONN_UP:
	/*
	 * Locate any agent inodes
	 * associated with the serverConn.
	 * Ignore inodes on freelist.
	 */
	for (ip = inode; ip < inodeNINODE; ip++) if (ip->i_count) {
		/*
		 * Scan through the system file-table to locate
		 * any/all remaining references
		 * to IDEADSERVER agent inodes.
		 *
		 * We replace the agent inode reference in the f_data
		 * bucket of the file table with a reference
		 * to a generic dead-agent inode for any dead server.
		 *
		 * The dead generic-agent is never on any linked list
		 * and inheirits all of the several inode count-fields.
		 * (We set IWANT to provoke a subsequent wakeup()
		 * to insure all referencing process don't remain asleep.)
		 *
		 * We irele() the now unreferenced agent inode
		 * and let nature take its course:
		 * Eventually the user process(es) referring
		 * to dead agents will have
		 * to divest themselves of the generic corpse,
		 * on pain of inode and filetable consumption.
		 *
		 * cdevsw[dfs_dev].d_close() is nulldev() ==> benigness
		 * C.f., sys/sys_inode.c -- initialization of 'inodeops'.
		 *
		 * Any NEW references to the (origional) remote object
		 * will get a NEW (agent) inode.
		 */
		if (ip->i_host && (ip->i_host == serverConn)) {
		   register struct file	 * fp;
		   register struct inode * gds_ip = &genericDeadServerInode;

		   for (fp = file; fp < fileNFILE; fp++) if (fp->f_data==(caddr_t)ip) {
			/*
			 * Generic dead-server inode inhierits
			 * the various inode counter values.
			 * Note:
			 *	i_number	==0, ==> an ILLEGAL value.
			 *	i_fs		==0, ==> no assoc'd filesystem.
			 *	ic_mode		==0, ==> no mode-bits.
			 *	ic_uid		==0, ==> superUser ownership.
			 *	ic_gid		==0, ==> superUser group.
			 *	ic_size		==0, ==> null file.
			 *	ic_flags	==0, ==> currently unused.
			 *	ic_"time"	==0, ==> no access/update times.
			 *	i_rmt_ip	==0, ==> no remote server-inode.
			 */
			fp->f_data		= (caddr_t) gds_ip;
			/*
			 * Though unnecessarily repetitive,
			 * there is little traffic through
			 * the following inode-initialization code.
			 * Simplicity and minimum lexical distance
			 * are profferred to be sufficient justification.
			 */
				/*
				 * Force invocation of
				 * dfsClient_*() routines.
				 */
			gds_ip->i_host		 = (struct connection *) -1;
				/*
				 * 1) Force wakeup();
				 * 2) Force dfsClient_*() calls to fail.
				 */
			gds_ip->i_flag		 = IDEADSERVER|IWANT;
				/*
				 * Force cdevsw[].d_close()
				 * routing through DFS
				 */
			gds_ip->i_dev		 = makedev(dfs_dev,0);
				/*
				 * Aggregate the tallies,
				 * perhaps to no purpose.
				 */
			gds_ip->i_count		+= ip->i_count;
			gds_ip->i_wcount	+= ip->i_wcount;
			gds_ip->i_shlockc	+= ip->i_shlockc;
			gds_ip->i_exlockc	+= ip->i_exlockc;
		   }
		}
	}
	break;

   case CONN_DOWN:
	dfs_incTotal(serverCrashes);	/* Indicate a server machine died */
	/*
	 * Locate the agent inodes
	 * associated with the serverConn.
	 * Ignore inodes on freelist.
	 */
	for (ip = inode; ip < inodeNINODE; ip++) if (ip->i_count) {
		/*
		 * In GENERAL:
		 * Prevent use of the agent inode
		 * without incurring network communication
		 * with the recently deceased server;
		 * and then let nature take its course.
		 *
		 * We may NOT summarily free
		 * the still referenced agent!
		 *
		 * In PARTICULAR:
		 * If the user was chdir()'d to an agent,
		 * and then attempts to ilock() u_cdir,
		 * the access will be prevented by IDEADSERVER.
		 *
		 * We will then uprintf() the user
		 * to chdir() to another filetree leaf.
		 */
		if (ip->i_host && (ip->i_host == serverConn)) {
			/*
			 * Mark the (agent) inode so that
			 * the next access fails.
			 */
			ip->i_flag |= IDEADSERVER;

			DFS_DEBUG(6,("dfsClient_exception/I-3: (%s) @ '%s' for serverConn=0x%X (%s) IDEADSERVER for ip=0x%X i_dev=0x%x i_number=%d i_rmt_ip=0x%X\n",
			  ( (connException == CONN_UP)	? "CONN_UP"
			   :(connException == CONN_DOWN)? "CONN_DOWN"
							: "UnKnownException"
			  ),
			  myName,
			  serverConn, serverConn->name,
			  ip, ip->i_dev, ip->i_number, ip->i_rmt_ip));
		}
	}
#ifdef	DFS_LOCAL_CACHED_TEXT
	/*
	 * Turn off the sticky bit so
	 * that our cached text will
	 * get flushed eventually.
	 */
	if (ip->i_flag&ITEXT && ip->i_mode&ISVTX) {
		DFS_DEBUG(2,("dfsClient_exception/I-4: xrele(ip=0x%X)\n", ip));
		xrele(ip);
	}
#endif	DFS_LOCAL_CACHED_TEXT
	/*
	 * BUG: a prpc_serverMain() process will deadlock
	 * -- (in xrele() while
	 * invoking xlock() and sleeping on XLOCK
	 * in x_flag for the text) --
	 * if the client process was
	 * interrupted after locking the agent.
	 */
	break;

    default:
	;
    }
}


/*
 * Exception handled on server-side:
 *
 * Deal with the consequences
 * of a connection-level state-change
 * detected at the server-side.
 * I.e., the (remote) client-side
 * changed state (died or was reborn.)
 *
 * Algorithm:
 *	Recover from a remote (client) machine crash.
 *	If an inode is opened by the crashed machine, close it. 
 *	If we have opened an inode on
 *	the crashed (client) machine, mark it closed.
 */
dfsServer_exception(clientConn, connException)	/* A client-host died! */
	connection_t *	clientConn;
	int		connException;
{
	register dfs_remote_t	*	rp;
	register dfs_remote_t	* *	rpp;
	dfs_remote_t		*	first;
	struct inode		*	ip;
	extern dfs_remote_t	*	dfs_rmt_free;
	node_t			*	node = &clientConn->node;
	extern dfs_remote_t	* dfs_rmt_findByNode();	/* Disconnects chain */

    if (!clientConn)
	return;

    if (clientConn == conn_findConnection(&myNode)) /* It's us: nothing to do */
	return;

    dfs_incServer(exception);
    DFS_DEBUG(12,("dfsServer_exception/I-0: (%s) @ '%s' for clientConn=0x%X (%s)\n",
	( (connException == CONN_UP)	? "CONN_UP"
	 :(connException == CONN_DOWN)	? "CONN_DOWN"
					: "UnKnownException"
	),
	myName,
	clientConn, clientConn->name));

   switch (connException) {
   case CONN_UP:
	/*
	 * Nothing to do -- just log the fact for debugging.
	 */
	first = rp = dfs_rmt_findByNode(node);
	DFS_DEBUG(8,("dfsServer_exception/I-1: rp=0x%x ip=0x%x\n",
		rp, rp ? rp->ip : 0));
	break;

   case CONN_DOWN:
	dfs_incTotal(clientCrashes);	/* Indicate the client-machine died */
	/*
	 * Cleanup (local) inodes.
	 * Unlock if locked by the remote client,
	 * Adjust the OPENWRITE, SHLOCK, EXLOCK flags,
	 * and i_count, i_exlockc and i_shlockc counters.
	 */
	first = rp = dfs_rmt_findByNode(node);
	while (rp) {
		register dfs_remote_t * * rpp;
		register dfs_remote_t *	  nextrp = rp->nexthash;

		if ((ip = rp->ip)
		 &&  ip != DFS_ILLEGAL_INODE) {	/* Paranoia */
			/*
			 * For each of
			 *
			 *	DFS_RMT_OPENWRITE
			 *	DFS_RMT_SHLOCK
			 *	DFS_RMT_EXLOCK
			 *
			 * in the client's dfs_remote_t structure,
			 * turn that flag off and reduce the associated
			 * counts by the amounts in which
			 * the client was involved.
			 */
			if (rp->flags & DFS_RMT_OPENWRITE) {
				DFS_DEBUG(8,("dfsServer_exception/I-2: rp=0x%x wcount=0x%x i_wcount=0x%x\n",
					rp, rp->wcount, ip->i_wcount));
				if (!(ip->i_wcount -= rp->wcount)) {
					ip->i_flag &= ~IOPENWRITE;
					/*
					 * (No one sleeps upon i_wcount.)
					 */
				}
				if (ip->i_wcount < 0){
					DFS_DEBUG(0,("dfsServer_exception/E-3: rp=0x%x i_wcount=0x%x < 0\n",
						rp, ip->i_wcount));
					panic("dfsServer_exception: ip_wcount<0");
				}
				rp->wcount	 = 0;
				rp->flags	&= ~DFS_RMT_OPENWRITE;
			}
			if (rp->flags & DFS_RMT_SHLOCK) {
				DFS_DEBUG(4,("dfsServer_exception/I-4: rp=0x%x shlockc=0x%x i_shlockc=0x%x\n",
					rp, rp->shlockc, ip->i_shlockc));
				if (!(ip->i_shlockc -= rp->shlockc)) {
					ip->i_flag &= ~ISHLOCK;
					if (ip->i_flag & ILWAIT)
						wakeup((caddr_t)&ip->i_shlockc);
				}
				if (ip->i_shlockc < 0){
					DFS_DEBUG(0,("dfsServer_exception/E-5: rp=0x%x i_shlockc=0x%x < 0\n",
						rp, ip->i_shlockc));
					panic("dfsServer_exception: i_shlockc<0");
				}
				rp->shlockc	 = 0;
				rp->flags	&= ~DFS_RMT_SHLOCK;
			}
			if (rp->flags & DFS_RMT_EXLOCK) {
				DFS_DEBUG(2,("dfsServer_exception/I-6: rp=0x%x exlockc=0x%x i_exlockc=0x%x\n",
					rp, rp->shlockc, ip->i_shlockc));
				if (!(ip->i_exlockc -= rp->exlockc)) {
					ip->i_flag &= ~IEXLOCK;
					if (ip->i_flag & ILWAIT)
						wakeup((caddr_t)&ip->i_exlockc);
				}
				if (ip->i_exlockc < 0){
					DFS_DEBUG(0,("dfsServer_exception/E-7: rp=0x%x i_exlockc=0x%x < 0\n",
						rp, ip->i_exlockc));
					panic("dfsServer_exception: i_exlockc<0");
				}
				rp->exlockc	 = 0;
				rp->flags	&= ~DFS_RMT_EXLOCK;
			}
			if (rp->flags & DFS_RMT_LOCKED) {
				DFS_DEBUG(4,("dfsServer_exception/I-8:  clientConn=0x%X (%s) IUNLOCK(ip=0x%X i_dev=0x%x i_number=%d)\n",
					clientConn, clientConn->name,
					ip, ip->i_dev, ip->i_number));
				rp->flags &= ~DFS_RMT_LOCKED;
#ifdef	DFS_LOCK_FIX
				ip->i_rmt_lock = NULL;
#endif	DFS_LOCK_FIX
				IUNLOCK(ip);
			}
			/*
			 * Throw away as many as
			 * the client actually had.
			 */
			DFS_DEBUG(4,("dfsServer_exception/I-9:  clientConn=0x%X (%s)   irele(ip=0x%X i_dev=0x%x i_number=%d i_count=%d rp->count=%d)\n",
				clientConn, clientConn->name,
				ip, ip->i_dev, ip->i_number,
				ip->i_count, rp->count));
			ip->i_count -= rp->count - 1;	/* irele() needs one */
			irele(ip);
		} else {
			DFS_DEBUG(2,("dfsServer_exception/E-10:  clientConn=0x%X (%s) UNEXPECTED ip=0x%X for rp=0x%X\n",
				clientConn, clientConn->name, ip, rp));
		}
#ifdef	notyet
		dfs_rmt_remove(node,ip);
#else	notyet
		/*
		 * Unlink from the ip->i_rmt_node chain
		 */
		for (rpp = &ip->i_rmt_node;	/* Start at head of chain */
		     *rpp && rp != *rpp;
		     rpp = &(*rpp)->next)
			continue;
		if (*rpp) {
			*rpp = rp->next;
		} else {
			DFS_DEBUG(4,("dfsServer_exception/E-11: rp=0x%X not found from ip=0x%X\n", rp, ip));
#if	1
			/*
			 * This is too severe, especially since
			 * we are abandoning the data structures.
			 * BUG: Why is it missing from the i_rmt_node chain?!
			 */
			panic("dfsServer_exception: rp not in i_rmt_node chain");
#endif
		}
		/*
		 * Put the rmt-struct onto freelist.
		 */
		if (!nextrp) {
			/*
			 * Chained in freelist as a ring:
			 */
			rp->next = dfs_rmt_free;
			dfs_rmt_free = first;
			break;
		}
#endif	notyet
		dfs_incTotal(remoteDropped);

		rp = nextrp;		/* Next in hash-chain */
	}

	/*
	 * Go through the inodes to determine
	 * opens via our agent to the remote machine.
	 */
	for (ip = inode; ip < inodeNINODE; ip++)	if (ip->i_count) {
		if (ip->i_host != clientConn)
			continue;
		/*
		 * If the agent is locked,
		 * it may never be unlocked
		 * as the remote machine is down.
		 * (Our agent will never have i_rmt_lock set.)
		 */
		if (ip->i_flag & ILOCKED) {
			DFS_DEBUG(4,("dfsServer_exception/I-12:  clientConn=0x%X (%s) IUNLOCK(ip=0x%X i_dev=0x%x i_number=%d)\n",
				clientConn, clientConn->name,
				ip, ip->i_dev, ip->i_number));
			IUNLOCK(ip);
		}
		dfs_incTotal(localDropped);
	}
	break;

   default:
	;
   }
}

/*
 * Shutdown our DFS connection to the world.
 *
 * FORCIBLY remove all INCOMING (remote) connections.
 *
 * Do not attempt to close any files we have opened,
 * as the remote hosts will take care of that
 * when they detect we have shutdown.
 * 
 * We do not do anything about AGENT inodes
 * that may exist on our machine.
 * (Our) AGENTs will be handled via
 *	u.u_error = DFS_ENOTENABLED
 * upon the next (outgoing) access.
 *
 * BUG: we really should first send an exception
 * to the remote node, but we do not have that
 * mechanism as yet.
 */
dfs_shutdown()	/* FORCEABLY shut down the DFS facilities */
{
	register struct inode * ip;

	/*
	 * Detach DFS from both client and server
	 * RPC interfaces.
	 *
	 * BUG: We don't check for any work-in-progress.
	 * The presumption is that an advisory
	 * has already been given to all concerned;
	 * or that we don't care.
	 */
	if (!dfs_disable())
		return;		/* Already disabled -- don't bother */

	dfs_incTotal(shutdowns);
	DFS_DEBUG(12,("dfs_shutdown/I-1: Entry\n"));
	/*
	 * Unlock all of our local inodes
	 * which are remotely locked.
	 */
	for (ip = inode; ip < inodeNINODE; ip++)	if (ip->i_count) {
		register dfs_remote_t * rp;

		/*
		 * Free up all remote connections as well.
		 */
		for (rp = ip->i_rmt_node; rp; rp = rp->next) {
			/*
			 * Do what amounts to a 'local' iput():
			 *	IUNLOCK(ip);
			 *	irele(ip);
			 * (Don't invoke any remote DFS services.)
			 */
			/*
			 * BUG: if a call is in progress,
			 * we can not clean up until after
			 * it is complete.
			 * No ABORT-flag has been set for the server process.
			 */
			if (rp->flags & DFS_RMT_LOCKED) {
				DFS_DEBUG(4,("dfs_shutdown/I-2: IUNLOCK(ip=0x%X i_dev=0x%x i_number=%d i_count=%d rp->count=%d)\n",
					ip, ip->i_dev, ip->i_number,
					ip->i_count, rp->count));
				IUNLOCK(ip);
			}
			ip->i_count -= rp->count - 1;
			if (ip->i_host) {
				printf("dfs_shutdown/E-3: ip=0x%x i_dev=0x%x i_number=%d\n",
					ip, ip->i_dev, ip->i_number);
				panic( "dfs_shutdown/E-3: local inode is an AGENT");
			}
			DFS_DEBUG(4,("dfs_shutdown/I-4:   irele(ip=0x%X i_dev=0x%x i_number=%d)\n",
				ip, ip->i_dev, ip->i_number));
			irele(ip);
		}
		ip->i_rmt_node = NULL;

		if (!ip->i_host)
			continue;

		/*
		 * BUG: Can't mark the inode so that
		 * BUG: the next access from the PARTICULAR
		 * BUG: (crashed) client fails.
		 * BUG: A single
		 * BUG:		ip->i_flag |= IDEADCLIENT;
		 * BUG: is insufficient resolution:
		 * BUG: All accesses from ANY host would fail!
		 */
	}
	/*
	 * Now free up all DFS remote structures.
	 */
	dfs_rmt_freeAll();
	DFS_DEBUG(12,("dfs_shutdown/I-5: Exit\n"));
}
