text: /*
   * Copyright (c) 1990 Jan-Simon Pendry
   * Copyright (c) 1990 Imperial College of Science, Technology & Medicine
   * Copyright (c) 1990, 1993
   *	The Regents of the University of California.  All rights reserved.
   *
   * This code is derived from software contributed to Berkeley by
   * Jan-Simon Pendry at Imperial College, London.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in the
   *    documentation and/or other materials provided with the distribution.
   * 3. Neither the name of the University nor the names of its contributors
   *    may be used to endorse or promote products derived from this software
   *    without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
   * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   *
   *	from: @(#)srvr_nfs.c	8.1 (Berkeley) 6/6/93
   *	$Id: srvr_nfs.c,v 1.10 2014/10/26 03:28:41 guenther Exp $
   */
  
  /*
   * NFS server modeling
   */
  
  #include "am.h"
  #include <netdb.h>
  #include <rpc/pmap_prot.h>
  #include "mount.h"
  
  extern qelem nfs_srvr_list;
  qelem nfs_srvr_list = { &nfs_srvr_list, &nfs_srvr_list };
  
  typedef struct nfs_private {
  	u_short np_mountd;	/* Mount daemon port number */
  	char np_mountd_inval;	/* Port *may* be invalid */
  	int np_ping;		/* Number of failed ping attempts */
  	time_t np_ttl;		/* Time when server is thought dead */
  	int np_xid;		/* RPC transaction id for pings */
  	int np_error;		/* Error during portmap request */
  } nfs_private;
  
  static int np_xid;	/* For NFS pings */
  #define	NPXID_ALLOC()	(++np_xid)
  /*#define	NPXID_ALLOC()	((++np_xid&0x0fffffff) == 0 ? npxid_gc() : np_xid)*/
  
  /*
   * Number of pings allowed to fail before host is declared down
   * - three-fifths of the allowed mount time...
  #define	MAX_ALLOWED_PINGS	((((ALLOWED_MOUNT_TIME + 5 * AM_PINGER - 1) * 3) / 5) / AM_PINGER)
   */
  #define	MAX_ALLOWED_PINGS	(3 + /* for luck ... */ 1)
  
  /*
   * How often to ping when starting a new server
   */
  #define	FAST_NFS_PING		3
  
  #if (FAST_NFS_PING * MAX_ALLOWED_PINGS) >= ALLOWED_MOUNT_TIME
   #error: sanity check failed
  /*
   you cannot do things this way...
   sufficient fast pings must be given the chance to fail
   within the allowed mount time
   */
  #endif /* (FAST_NFS_PING * MAX_ALLOWED_PINGS) >= ALLOWED_MOUNT_TIME */
  
  static int ping_len;
  static char ping_buf[sizeof(struct rpc_msg) + 32];
  
  /*
   * Flush any cached data
   */
  void
  flush_srvr_nfs_cache(void)
  {
  	fserver *fs = 0;
  
  	ITER(fs, fserver, &nfs_srvr_list) {
  		nfs_private *np = (nfs_private *) fs->fs_private;
  		if (np) {
  			np->np_mountd_inval = TRUE;
  			np->np_error = -1;
  		}
  	}
  }
  
  /*
   * Startup the NFS ping
   */
  static void
  start_ping(void)
  {
  	XDR ping_xdr;
  	struct rpc_msg ping_msg;
  
  	rpc_msg_init(&ping_msg, NFS_PROGRAM, NFS_VERSION, NFSPROC_NULL);
  
  	/*
  	 * Create an XDR endpoint
  	 */
  	xdrmem_create(&ping_xdr, ping_buf, sizeof(ping_buf), XDR_ENCODE);
  
  	/*
  	 * Create the NFS ping message
  	 */
  	if (!xdr_callmsg(&ping_xdr, &ping_msg)) {
  		plog(XLOG_ERROR, "Couldn't create ping RPC message");
  		going_down(3);
  	}
  
  	/*
  	 * Find out how long it is
  	 */
  	ping_len = xdr_getpos(&ping_xdr);
  
  	/*
  	 * Destroy the XDR endpoint - we don't need it anymore
  	 */
  	xdr_destroy(&ping_xdr);
  }
  
  
  /*
   * Called when a portmap reply arrives
   */
  static void
  got_portmap(void *pkt, int len, struct sockaddr_in *sa,
      struct sockaddr_in *ia, void *idv, int done)
  {
  	fserver *fs2 = (fserver *) idv;
  	fserver *fs = 0;
  
  	/*
  	 * Find which fileserver we are talking about
  	 */
  	ITER(fs, fserver, &nfs_srvr_list)
  		if (fs == fs2)
  			break;
  
  	if (fs == fs2) {
  		u_long port = 0;	/* XXX - should be short but protocol is naff */
  		int error = done ? pickup_rpc_reply(pkt, len, &port, xdr_u_long) : -1;
  		nfs_private *np = (nfs_private *) fs->fs_private;
  		if (!error && port) {
  #ifdef DEBUG
  			dlog("got port (%d) for mountd on %s", port, fs->fs_host);
  #endif /* DEBUG */
  			/*
  			 * Grab the port number.  Portmap sends back
  			 * an unsigned long in native ordering, so it
  			 * needs converting to a unsigned short in
  			 * network ordering.
  			 */
  			np->np_mountd = htons((u_short) port);
  			np->np_mountd_inval = FALSE;
  			np->np_error = 0;
  		} else {
  #ifdef DEBUG
  			dlog("Error fetching port for mountd on %s", fs->fs_host);
  #endif /* DEBUG */
  			/*
  			 * Almost certainly no mountd running on remote host
  			 */
  			np->np_error = error ? error : ETIMEDOUT;
  		}
  		if (fs->fs_flags & FSF_WANT)
  			wakeup_srvr(fs);
  	} else if (done) {
  #ifdef DEBUG
  		dlog("Got portmap for old port request");
  #endif /* DEBUG */
  	} else {
  #ifdef DEBUG
  		dlog("portmap request timed out");
  #endif /* DEBUG */
  	}
  }
  
  /*
   * Obtain portmap information
   */
  static int
  call_portmap(fserver *fs, AUTH *auth, unsigned long prog,
      unsigned long vers, unsigned long prot)
  {
  	struct rpc_msg pmap_msg;
  	int len;
  	char iobuf[UDPMSGSIZE];
  	int error;
  	struct pmap pmap;
  
  	rpc_msg_init(&pmap_msg, PMAPPROG, PMAPVERS, (unsigned long) 0);
  	pmap.pm_prog = prog;
  	pmap.pm_vers = vers;
  	pmap.pm_prot = prot;
  	pmap.pm_port = 0;
  	len = make_rpc_packet(iobuf, sizeof(iobuf), PMAPPROC_GETPORT,
  			&pmap_msg, &pmap, xdr_pmap, auth);
  	if (len > 0) {
  		struct sockaddr_in sin;
  		bzero(&sin, sizeof(sin));
  		sin = *fs->fs_ip;
  		sin.sin_port = htons(PMAPPORT);
  		error = fwd_packet(RPC_XID_PORTMAP, iobuf, len,
  				&sin, &sin, fs, got_portmap);
  	} else {
  		error = -len;
  	}
  	return error;
  }
  
  static void nfs_keepalive(void *);
  
  static void
  recompute_portmap(fserver *fs)
  {
  	int error;
  
  	if (nfs_auth)
  		error = 0;
  	else
  		error = make_nfs_auth();
  
  	if (error) {
  		nfs_private *np = (nfs_private *) fs->fs_private;
  		np->np_error = error;
  	} else {
  		call_portmap(fs, nfs_auth, MOUNTPROG,
  			MOUNTVERS, (unsigned long) IPPROTO_UDP);
  	}
  }
  
  /*
   * This is called when we get a reply to an RPC ping.
   * The value of id was taken from the nfs_private
   * structure when the ping was transmitted.
   */
  static void
  nfs_pinged(void *pkt, int len, struct sockaddr_in *sp,
      struct sockaddr_in *tsp, void *idv, int done)
  {
  	/* XXX EVIL! XXX */
  	int xid = (int) ((long)idv);
  	fserver *fs;
  #ifdef DEBUG
  	int found_map = 0;
  #endif /* DEBUG */
  
  	if (!done)
  		return;
  
  	/*
  	 * For each node...
  	 */
  	ITER(fs, fserver, &nfs_srvr_list) {
  		nfs_private *np = (nfs_private *) fs->fs_private;
  		if (np->np_xid == xid) {
  			/*
  			 * Reset the ping counter.
  			 * Update the keepalive timer.
  			 * Log what happened.
  			 */
  			if (fs->fs_flags & FSF_DOWN) {
  				fs->fs_flags &= ~FSF_DOWN;
  				if (fs->fs_flags & FSF_VALID) {
  					srvrlog(fs, "is up");
  				} else {
  					if (np->np_ping > 1)
  						srvrlog(fs, "ok");
  #ifdef DEBUG
  					else
  						srvrlog(fs, "starts up");
  #endif
  					fs->fs_flags |= FSF_VALID;
  				}
  
  #ifdef notdef
  				/* why ??? */
  				if (fs->fs_flags & FSF_WANT)
  					wakeup_srvr(fs);
  #endif /* notdef */
  				map_flush_srvr(fs);
  			} else {
  				if (fs->fs_flags & FSF_VALID) {
  #ifdef DEBUG
  					dlog("file server %s type nfs is still up", fs->fs_host);
  #endif /* DEBUG */
  				} else {
  					if (np->np_ping > 1)
  						srvrlog(fs, "ok");
  					fs->fs_flags |= FSF_VALID;
  				}
  			}
  
  			/*
  			 * Adjust ping interval
  			 */
  			untimeout(fs->fs_cid);
  			fs->fs_cid = timeout(fs->fs_pinger, nfs_keepalive, fs);
  
  			/*
  			 * Update ttl for this server
  			 */
  			np->np_ttl = clocktime() +
  				(MAX_ALLOWED_PINGS - 1) * FAST_NFS_PING + fs->fs_pinger - 1;
  
  			/*
  			 * New RPC xid...
  			 */
  			np->np_xid = NPXID_ALLOC();
  
  			/*
  			 * Failed pings is zero...
  			 */
  			np->np_ping = 0;
  
  			/*
  			 * Recompute portmap information if not known
  			 */
  			if (np->np_mountd_inval)
  				recompute_portmap(fs);
  
  #ifdef DEBUG
  			found_map++;
  #endif /* DEBUG */
  			break;
  		}
  	}
  
  #ifdef DEBUG
  	if (found_map == 0)
  		dlog("Spurious ping packet");
  #endif /* DEBUG */
  }
  
  /*
   * Called when no ping-reply received
   */
  static void
  nfs_timed_out(void *arg)
  {
  	fserver *fs = arg;
  
  	nfs_private *np = (nfs_private *) fs->fs_private;
  
  	/*
  	 * Another ping has failed
  	 */
  	np->np_ping++;
  
  	/*
  	 * Not known to be up any longer
  	 */
  	if (FSRV_ISUP(fs)) {
  		fs->fs_flags &= ~FSF_VALID;
  		if (np->np_ping > 1)
  			srvrlog(fs, "not responding");
  	}
  
  	/*
  	 * If ttl has expired then guess that it is dead
  	 */
  	if (np->np_ttl < clocktime()) {
  		int oflags = fs->fs_flags;
  		if ((fs->fs_flags & FSF_DOWN) == 0) {
  			/*
  			 * Server was up, but is now down.
  			 */
  			srvrlog(fs, "is down");
  			fs->fs_flags |= FSF_DOWN|FSF_VALID;
  			/*
  			 * Since the server is down, the portmap
  			 * information may now be wrong, so it
  			 * must be flushed from the local cache
  			 */
  			flush_nfs_fhandle_cache(fs);
  			np->np_error = -1;
  #ifdef notdef
  			/*
  			 * Pretend just one ping has failed now
  			 */
  			np->np_ping = 1;
  #endif
  		} else {
  			/*
  			 * Known to be down
  			 */
  #ifdef DEBUG
  			if ((fs->fs_flags & FSF_VALID) == 0)
  				srvrlog(fs, "starts down");
  #endif
  			fs->fs_flags |= FSF_VALID;
  		}
  		if (oflags != fs->fs_flags && (fs->fs_flags & FSF_WANT))
  			wakeup_srvr(fs);
  	} else {
  #ifdef DEBUG
  		if (np->np_ping > 1)
  			dlog("%d pings to %s failed - at most %d allowed", np->np_ping, fs->fs_host, MAX_ALLOWED_PINGS);
  #endif /* DEBUG */
  	}
  
  	/*
  	 * Run keepalive again
  	 */
  	nfs_keepalive(fs);
  }
  
  /*
   * Keep track of whether a server is alive
   */
  static void
  nfs_keepalive(void *arg)
  {
  	fserver *fs = arg;
  
  	int error;
  	nfs_private *np = (nfs_private *) fs->fs_private;
  	int fstimeo = -1;
  
  	/*
  	 * Send an NFS ping to this node
  	 */
  
  	if (ping_len == 0)
  		start_ping();
  
  	/*
  	 * Queue the packet...
  	 */
  	/*
  	 * XXX EVIL!  We cast xid to a pointer, then back to an int when
  	 * XXX we get the reply.
  	 */
  	error = fwd_packet(MK_RPC_XID(RPC_XID_NFSPING, np->np_xid), ping_buf,
  		ping_len, fs->fs_ip, NULL, (void *)((long)np->np_xid),
  		nfs_pinged);
  
  	/*
  	 * See if a hard error occured
  	 */
  	switch (error) {
  	case ENETDOWN:
  	case ENETUNREACH:
  	case EHOSTDOWN:
  	case EHOSTUNREACH:
  		np->np_ping = MAX_ALLOWED_PINGS;	/* immediately down */
  		np->np_ttl = (time_t) 0;
  		/*
  		 * This causes an immediate call to nfs_timed_out
  		 * whenever the server was thought to be up.
  		 * See +++ below.
  		 */
  		fstimeo = 0;
  		break;
  
  	case 0:
  #ifdef DEBUG
  		dlog("Sent NFS ping to %s", fs->fs_host);
  #endif /* DEBUG */
  		break;
  	}
  
  #ifdef DEBUG
  	/*dlog("keepalive, ping = %d", np->np_ping);*/
  #endif /* DEBUG */
  
  	/*
  	 * Back off the ping interval if we are not getting replies and
  	 * the remote system is know to be down.
  	 */
  	switch (fs->fs_flags & (FSF_DOWN|FSF_VALID)) {
  	case FSF_VALID:			/* Up */
  		if (fstimeo < 0)	/* +++ see above */
  			fstimeo = FAST_NFS_PING;
  		break;
  
  	case FSF_VALID|FSF_DOWN:	/* Down */
  		fstimeo = fs->fs_pinger;
  		break;
  
  	default:			/* Unknown */
  		fstimeo = FAST_NFS_PING;
  		break;
  	}
  
  #ifdef DEBUG
  	dlog("NFS timeout in %d seconds", fstimeo);
  #endif /* DEBUG */
  
  	fs->fs_cid = timeout(fstimeo, nfs_timed_out, fs);
  }
  
  int
  nfs_srvr_port(fserver *fs, u_short *port, void *wchan)
  {
  	int error = -1;
  	if ((fs->fs_flags & FSF_VALID) == FSF_VALID) {
  		if ((fs->fs_flags & FSF_DOWN) == 0) {
  			nfs_private *np = (nfs_private *) fs->fs_private;
  			if (np->np_error == 0) {
  				*port = np->np_mountd;
  				error = 0;
  			} else {
  				error = np->np_error;
  			}
  			/*
  			 * Now go get the port mapping again in case it changed.
  			 * Note that it is used even if (np_mountd_inval)
  			 * is True.  The flag is used simply as an
  			 * indication that the mountd may be invalid, not
  			 * that it is known to be invalid.
  			 */
  			if (np->np_mountd_inval)
  				recompute_portmap(fs);
  			else
  				np->np_mountd_inval = TRUE;
  		} else {
  			error = EWOULDBLOCK;
  		}
  	}
  	if (error < 0 && wchan && !(fs->fs_flags & FSF_WANT)) {
  		/*
  		 * If a wait channel is supplied, and no
  		 * error has yet occured, then arrange
  		 * that a wakeup is done on the wait channel,
  		 * whenever a wakeup is done on this fs node.
  		 * Wakeup's are done on the fs node whenever
  		 * it changes state - thus causing control to
  		 * come back here and new, better things to happen.
  		 */
  		fs->fs_flags |= FSF_WANT;
  		sched_task(wakeup_task, wchan, fs);
  	}
  	return error;
  }
  
  static void
  start_nfs_pings(fserver *fs, int pingval)
  {
  	if (!(fs->fs_flags & FSF_PINGING)) {
  		fs->fs_flags |= FSF_PINGING;
  		if (fs->fs_cid)
  			untimeout(fs->fs_cid);
  		if (pingval < 0) {
  			srvrlog(fs, "wired up");
  			fs->fs_flags |= FSF_VALID;
  			fs->fs_flags &= ~FSF_DOWN;
  		} else {
  			nfs_keepalive(fs);
  		}
  	} else {
  #ifdef DEBUG
  		dlog("Already running pings to %s", fs->fs_host);
  #endif /* DEBUG */
  	}
  }
  
  /*
   * Find an nfs server for a host.
   */
  fserver *
  find_nfs_srvr(mntfs *mf)
  {
  	fserver *fs;
  	struct hostent *hp = 0;
  	char *host = mf->mf_fo->opt_rhost;
  	struct sockaddr_in *ip;
  	nfs_private *np;
  	int pingval;
  
  	/*
  	 * Get ping interval from mount options.
  	 * Current only used to decide whether pings
  	 * are required or not.  < 0 = no pings.
  	 */
  	{ struct mntent mnt;
  	  mnt.mnt_opts = mf->mf_mopts;
  	  pingval = hasmntval(&mnt, "ping");
  	  /*
  	   * Over TCP mount, don't bother to do pings.
  	   * This is experimental - maybe you want to
  	   * do pings anyway...
  	   */
  	  if (pingval == 0 && hasmntopt(&mnt, "tcp"))
  		pingval = -1;
  	}
  
  
  	/*
  	 * lookup host address and canonical name
  	 */
  	hp = gethostbyname(host);
  
  	/*
  	 * New code from Bob Harris <harris@basil-rathbone.mit.edu>
  	 * Use canonical name to keep track of file server
  	 * information.  This way aliases do not generate
  	 * multiple NFS pingers.  (Except when we're normalizing
  	 * hosts.)
  	 */
  	if (hp && !normalize_hosts) host = hp->h_name;
  
  	ITER(fs, fserver, &nfs_srvr_list) {
  		if (STREQ(host, fs->fs_host)) {
  			start_nfs_pings(fs, pingval);
  			fs->fs_refc++;
  			return fs;
  		}
  	}
  
  
  
  	/*
  	 * Get here if we can't find an entry
  	 */
  	if (hp) {
  		switch (hp->h_addrtype) {
  		case AF_INET:
  			ip = ALLOC(sockaddr_in);
  			bzero(ip, sizeof(*ip));
  			ip->sin_family = AF_INET;
  			bcopy(hp->h_addr, &ip->sin_addr, sizeof(ip->sin_addr));
  
  			ip->sin_port = htons(NFS_PORT);
  			break;
  
  		default:
  			ip = 0;
  			break;
  		}
  	} else {
  		plog(XLOG_USER, "Unknown host: %s", host);
  		ip = 0;
  	}
  
  	/*
  	 * Allocate a new server
  	 */
  	fs = ALLOC(fserver);
  	fs->fs_refc = 1;
  	fs->fs_host = strdup(hp ? hp->h_name : "unknown_hostname");
  	if (normalize_hosts) host_normalize(&fs->fs_host);
  	fs->fs_ip = ip;
  	fs->fs_cid = 0;
  	if (ip) {
  		fs->fs_flags = FSF_DOWN;	/* Starts off down */
  	} else {
  		fs->fs_flags = FSF_ERROR|FSF_VALID;
  		mf->mf_flags |= MFF_ERROR;
  		mf->mf_error = ENOENT;
  	}
  	fs->fs_type = "nfs";
  	fs->fs_pinger = AM_PINGER;
  	np = ALLOC(nfs_private);
  	bzero(np, sizeof(*np));
  	np->np_mountd_inval = TRUE;
  	np->np_xid = NPXID_ALLOC();
  	np->np_error = -1;
  	/*
  	 * Initially the server will be deemed dead after
  	 * MAX_ALLOWED_PINGS of the fast variety have failed.
  	 */
  	np->np_ttl = clocktime() + MAX_ALLOWED_PINGS * FAST_NFS_PING - 1;
  	fs->fs_private = np;
  	fs->fs_prfree = free;
  
  	if (!(fs->fs_flags & FSF_ERROR)) {
  		/*
  		 * Start of keepalive timer
  		 */
  		start_nfs_pings(fs, pingval);
  	}
  
  	/*
  	 * Add to list of servers
  	 */
  	ins_que(&fs->fs_q, &nfs_srvr_list);
  
  	return fs;
  }
  