/*
 * |-----------------------------------------------------------|
 * | Copyright (c) 1991, 1990 MIPS Computer Systems, Inc.      |
 * | All Rights Reserved                                       |
 * |-----------------------------------------------------------|
 * |          Restricted Rights Legend                         |
 * | Use, duplication, or disclosure by the Government is      |
 * | subject to restrictions as set forth in                   |
 * | subparagraph (c)(1)(ii) of the Rights in Technical        |
 * | Data and Computer Software Clause of DFARS 252.227-7013.  |
 * |         MIPS Computer Systems, Inc.                       |
 * |         950 DeGuigne Avenue                               |
 * |         Sunnyvale, California 94088-3650, USA             |
 * |-----------------------------------------------------------|
 */
#ident	"$Header: auto_mount.c,v 1.13.1.1 92/06/24 13:49:28 marker Exp $"
/*
 * @(#)auto_mount.c	1.4 89/04/28 4.0NFSSRC Copyr 1988 Sun Micro
 *
 * Copyright (c) 1987 by Sun Microsystems, Inc.
 */

#include <stdio.h>
#include <ctype.h>
#include <syslog.h>
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/signal.h>
#include <rpc/rpc.h>
#include <rpc/pmap_clnt.h>
#include <mntent.h>
#include <netdb.h>
#include <errno.h>
#include "nfs_prot.h"
typedef nfs_fh fhandle_t;
#include <rpcsvc/mount.h>
#define NFSCLIENT
#include <sys/mount.h>
#include "automount.h"
extern int errno;

#if defined(_RISCOS)
#include <syslog.h>
#define fprintf syslog
#ifdef stderr
#undef stderr
#endif
#define stderr LOG_DEBUG
#endif /* _RISCOS */

#define MAXHOSTS  20

struct mapfs *find_server();
struct filsys *already_mounted();
void addtomtab();
char *inet_ntoa();
extern int trace;
#ifdef _SVR4_MODS
extern int verbose;
extern dev_t tmpdev;
#endif

static long last_mtab_time = 0;

do_mount(dir, me, rootfs, linkpath)
	struct autodir *dir;
	struct mapent *me;
	struct filsys **rootfs;
	char **linkpath;
{
	char mntpnt[MAXPATHLEN];
	static char linkbuf[MAXPATHLEN];
	enum clnt_stat pingmount();
	struct filsys *tfs;
	struct mapfs *mfs;
	struct mapent *m;
#ifdef _SVR4_MODS
	struct filsys *fs;
	struct mapent *mapnext;
#endif
	nfsstat status = NFSERR_NOENT;
#ifdef _SVR4_MODS
	char *prevhost;
#else
	struct in_addr prevhost;
#endif
	int imadeit;
	struct stat stbuf;

	*rootfs = NULL;
	*linkpath = "";

#ifdef _SVR4_MODS
	prevhost = "";
	for (m = me ; m ; m = mapnext) {
		mapnext = m->map_next;

		(void) sprintf(mntpnt, "%s%s%s%s%s", tmpdir,
			       (suppress_access_name) ? "" : dir->dir_name,
			       me->map_root, rootdir, m->map_mntpnt);

		/* check whether the mntpnt is already mounted on */
		if (m == me) {
			for (fs = HEAD(struct filsys, fs_q); fs;
			     fs = NEXT(struct filsys, fs)) {
				if (strcmp(mntpnt, fs->fs_mntpnt) == 0) {
					strcpy(linkbuf, mntpnt);
#ifdef _RISCOS /* Sun bug fix from Brent */
					/* When a remote machine doesn't
					 * export a root filesystem,
					 * we need to truncate off the
					 * portion of the pathname which is
					 * one of the directory.  fs_dir
					 * has at least a '/'.
					 */
					if (strlen(fs->fs_dir) > 1)
						linkbuf[strlen(linkbuf)-strlen(fs->fs_dir)] = '\0';
#endif
					if (trace > 1) 
						(void) fprintf(stderr,
						  "renew link for %s\n",
						  linkbuf);
					*linkpath = linkbuf;
					return (NFS_OK);
				}
			}
		}
#else
	prevhost.s_addr = (u_long)0;

	for (m = me ; m ; m = m->map_next) {
#endif /* _SVR4_MODS */
		tfs = NULL;
#ifdef _RISCOS
		mfs = find_server(m, &tfs, m == me, prevhost, dir->dir_name);
#else
		mfs = find_server(m, &tfs, m == me, prevhost);
#endif
		if (mfs == NULL)
			continue;

#ifdef DEBUG
		syslog(LOG_DEBUG,
		       "map->root: %s  map->next: %x  map->next->root: %s   mfs.addr: %x",
		       me->map_root, me->map_next,
		       me->map_next ? me->map_next->map_root: 0,
		       mfs->mfs_addr.s_addr);
#endif /* DEBUG */
		/*
		 * It may be possible to return a symlink
		 * to an existing mount point without
		 * actually having to do a mount.
		 */

#ifdef _SVR4_MODS
		/* Is it my host ? */
		if (strcmp(mfs->mfs_host, self) == 0 ||
		    strcmp(mfs->mfs_host, "localhost") == 0)
#else
		if (me->map_next == NULL &&
		    mfs->mfs_addr.s_addr == my_addr.s_addr)
#endif /* _SVR4_MODS */
		{
			(void) sprintf(linkbuf, "%s%s",
				mfs->mfs_dir, mfs->mfs_subdir);
			*linkpath = linkbuf;
#ifdef _SVR4_MODS
			if (trace > 1)
				(void) fprintf(stderr, "It's on my host\n");
#endif
			return (NFS_OK);
		}

		/*
		 * already mounted ?
		 * Can just point the symbolic link at the current
		 * mount point.
		 * If a mount root is specified, then it must match.
		 *
		 * SVR4 NOTE: this is a bit risky - the
		 * mount may belong to another automount
		 * daemon - it could unmount it anytime and
		 * this symlink would then point to an empty
		 * or non-existent mount point.
		 */
#ifdef _SVR4_MODS
		if (tfs != NULL)
#else
		if (m == me && tfs != NULL)
#endif
		{
			if (trace > 1)
				(void) fprintf(stderr,
				"already mounted %s:%s on %s (%s)\n",
				tfs->fs_host, tfs->fs_dir,
				tfs->fs_mntpnt, tfs->fs_opts);

			if (*me->map_root == '\0' ||
			    strcmp(mntpnt, tfs->fs_mntpnt) == 0) {
				*rootfs = tfs;

				(void) sprintf(linkbuf, "%s%s%s%s%s",
					tmpdir,
					suppress_access_name?"" : dir->dir_name,
					me->map_root,
					rootdir,
					mfs->mfs_subdir);

				*linkpath = linkbuf;
				return (NFS_OK);
			}
		}

		if (nomounts)
			return (NFSERR_PERM);

		 /* Create the mount point if necessary */
		(void) sprintf(mntpnt, "%s%s%s%s%s", tmpdir,
			(suppress_access_name) ? "" : dir->dir_name,
			me->map_root, rootdir, m->map_mntpnt);
		imadeit = 0;
		if (stat(mntpnt, &stbuf) != 0) {
			if (mkdir_r(mntpnt) == 0) {
				imadeit = 1;
#ifdef _SVR4_MODS
				if (stat(mntpnt, &stbuf) < 0) {
					syslog(LOG_ERR,
					"Couldn't stat created mountpoint %s: %m",
					mntpnt);
					continue;
				}
#endif
			} else {
#ifdef _SVR4_MODS
				if (verbose)
					syslog(LOG_ERR,
					"Couldn't create mountpoint %s: %m",
					mntpnt);
#endif
				if (trace > 1)
					(void) fprintf(stderr,
					"couldn't create mntpnt %s\n",
					mntpnt);
				continue;
			}
			errno = 0;
		}

#ifdef _SVR4_MODS
		if (pingmount(mfs->mfs_host, mfs->mfs_addr) != RPC_SUCCESS) {
#else
		if (pingmount(mfs->mfs_addr) != RPC_SUCCESS) {
#endif
			syslog(LOG_ERR, "host %s not responding", mfs->mfs_host);
			continue;
		}
#ifndef _SVR4_MODS
		prevhost.s_addr = mfs->mfs_addr.s_addr;
#else
		if (verbose && *rootfs == NULL && tmpdev != stbuf.st_dev)
			syslog(LOG_ERR, "WARNING: %s already mounted on",
				mntpnt);
#endif /* _SVR4_MODS */
		/*  Now do the mount */

		tfs = NULL;
		status = nfsmount(mfs->mfs_host, mfs->mfs_addr, mfs->mfs_dir,
				  mntpnt, m->map_mntopts, &tfs);
		if (status == NFS_OK) {
			if (*rootfs == NULL) {
				*rootfs = tfs;
#if defined(_RISCOS)
				(void) sprintf(linkbuf, "%s%s%s%s%s", tmpdir,
					suppress_access_name?"" : dir->dir_name,
					       me->map_root,
					       rootdir,
					       mfs->mfs_subdir);
#else
				(void) sprintf(linkbuf, "%s%s%s%s",
					tmpdir, dir->dir_name,
					me->map_root,
					mfs->mfs_subdir);
#endif /* _RISCOS */
				*linkpath = linkbuf;
			}
			tfs->fs_rootfs = *rootfs;
#ifdef _SVR4_MODS
			tfs->fs_mntpntdev = stbuf.st_dev;
			if (stat(mntpnt, &stbuf) < 0) {
				syslog(LOG_ERR, "Couldn't stat: %s: %m",
					mntpnt);
			} else {
				tfs->fs_mountdev = stbuf.st_dev;
			}
			prevhost = mfs->mfs_host;
#endif
		} else {
			if (imadeit)	/* delete dir if created it */
				safe_rmdir(mntpnt);
#ifdef _SVR4_MODS
			mfs->mfs_ignore = 1;
			prevhost = "";
			mapnext = m;
			continue;	/* try another server */
#endif
		}
	}
	if (*rootfs != NULL) {
		addtomtab(*rootfs);
		return (NFS_OK);
	}
	return (status);
}	/* end of do_mount */


struct mapfs *
#ifdef _RISCOS
find_server(me, fsp, rootmount, preferred, access_name)
#else
find_server(me, fsp, rootmount, preferred)
#endif
	struct mapent *me;
	struct filsys **fsp;
	int rootmount;
#ifdef _SVR4_MODS
	char *preferred;
#else
	struct in_addr preferred;
#endif
#ifdef _RISCOS
	char *access_name;
#endif
{
	int entrycount, localcount;
	struct mapfs *mfs;
#ifdef _SVR4_MODS
	struct mapfs *mfs_one;
#else
	struct hostent *hp;
#endif
	struct in_addr addrs[MAXHOSTS], addr, *addrp, trymany();

	/*
	 * get addresses & see if any are myself
	 * or were mounted from previously in a
	 * hierarchical mount.
	 */
	entrycount = localcount = 0;
#ifndef _SVR4_MODS
	for (mfs = me->map_fs; mfs; mfs = mfs->mfs_next) {
		if (mfs->mfs_addr.s_addr == 0) {
			hp = gethostbyname(mfs->mfs_host);
			if (hp)
				bcopy(hp->h_addr, (char *)&mfs->mfs_addr,
					hp->h_length);
		}
		if (mfs->mfs_addr.s_addr) {
			entrycount++;
			if (mfs->mfs_addr.s_addr == my_addr.s_addr)
				return mfs;
			if (mfs->mfs_addr.s_addr == preferred.s_addr)
				return mfs;
		}
	}
#else
	mfs_one = NULL;
	for (mfs = me->map_fs; mfs; mfs = mfs->mfs_next) {
		if (mfs->mfs_ignore)
			continue;
		mfs_one = mfs;
		if (strcmp(mfs->mfs_host, "localhost") == 0 ||
		    strcmp(mfs->mfs_host, self) == 0 ||
		    strcmp(mfs->mfs_host, preferred) == 0)
			return (mfs);
		entrycount++;
	}
#endif /* _SVR4_MODS */
	if (entrycount == 0)
		return NULL;

	/* see if any already mounted */
	if (rootmount)
	    for (mfs = me->map_fs; mfs; mfs = mfs->mfs_next) {
#ifdef _RISCOS
	    	char mntpnt[MAXPATHLEN];
#ifdef _SVR4_MODS
		if (mfs->mfs_ignore)
			continue;
#endif
		(void) sprintf(mntpnt, "%s%s%s%s%s", tmpdir,
			(suppress_access_name) ? "" : access_name,
			me->map_root, rootdir, me->map_mntpnt);

		    if (*fsp = already_mounted(mfs->mfs_host, mfs->mfs_dir,
					       mntpnt))
#else /* !_RISCOS */
		    if (*fsp = already_mounted(mfs->mfs_host, mfs->mfs_dir,
					       me->map_mntopts,mfs->mfs_addr))
#endif /* _RISCOS */
			return mfs;
	    }

	if (entrycount == 1) 	/* no replication involved */
#ifdef _SVR4_MODS
		return mfs_one;
#else
		return me->map_fs;
#endif

	/* try local net */
	addrp = addrs;
	for (mfs = me->map_fs; mfs; mfs = mfs->mfs_next) {
		if (inet_netof(mfs->mfs_addr) == inet_netof(my_addr)) {
			localcount++;
			*addrp++ = mfs->mfs_addr;
		}
	}
	if (localcount > 0) {	/* got some */
		(*addrp).s_addr = 0;	/* terminate list */
		addr = trymany(addrs, mount_timeout / 2);
		if (addr.s_addr) {	/* got one */
			for (mfs = me->map_fs; mfs; mfs = mfs->mfs_next)
			if (addr.s_addr == mfs->mfs_addr.s_addr)
				return mfs;
		}
	}
	if (entrycount == localcount)
		return NULL;

	/* now try them all */
	addrp = addrs;
	for (mfs = me->map_fs; mfs; mfs = mfs->mfs_next)
		*addrp++ = mfs->mfs_addr;
	(*addrp).s_addr = 0;	/* terminate list */
	addr = trymany(addrs, mount_timeout / 2);
	if (addr.s_addr) {	/* got one */
		for (mfs = me->map_fs; mfs; mfs = mfs->mfs_next)
			/* Don't need 1 by N comparison */
			if (addr.s_addr == mfs->mfs_addr.s_addr)
				return mfs;
	}
	return NULL;
}

/*
 *  If mtab has changed update internal fs info
 */
void
check_mtab()
{
	struct stat stbuf;
	struct filsys *fs, *fsnext;
	FILE *table;
	register struct mntent *mt;
	char *index();
	int found;
	struct hostent *hp;

	/* 
	 * read in /etc/mtab if it's changed
	 */
	if (stat(MOUNTED, &stbuf) != 0) {
		syslog(LOG_ERR, "Cannot stat %s: %m", MOUNTED);
		return;
	}
	if (stbuf.st_mtime == last_mtab_time)
		return;				/* no change */

	last_mtab_time = stbuf.st_mtime;

	/* reset the present flags */
	for (fs = HEAD(struct filsys, fs_q); fs;
	    fs = NEXT(struct filsys, fs))
		    fs->fs_present = 0;

	/* now see what's been mounted */
	table = setmntent(MOUNTED, "r");
	while ((mt = getmntent(table)) != NULL) {
		char *tmphost, *tmppath, *p, tmpc;

		if (strcmp(mt->mnt_type, MNTTYPE_NFS) != 0)
			continue;
		p = index(mt->mnt_fsname, ':');
		if (p == NULL)
			continue;
		tmpc = *p;
		*p = '\0';
		tmphost = mt->mnt_fsname;
		tmppath = p+1;
		if (tmppath[0] != '/')
			continue;
		found = 0;
		for (fs = HEAD(struct filsys, fs_q); fs;
			fs = NEXT(struct filsys, fs)) {
			if (strcmp(mt->mnt_dir, fs->fs_mntpnt) == 0 &&
			    strcmp(tmphost, fs->fs_host) == 0 &&
			    strcmp(tmppath, fs->fs_dir) == 0 &&
			    strcmp(mt->mnt_opts, fs->fs_opts) == 0) {
				fs->fs_present = 1;
				found++;
				break;
			}
		}
		if (!found) {
			fs = alloc_fs(tmphost, tmppath,
				mt->mnt_dir, mt->mnt_opts);
			if (fs == NULL) {
				(void) endmntent(table);
				syslog(LOG_ERR, "alloc_fs failed: %m");
				return;
			}
			fs->fs_present = 1;
			hp = gethostbyname(tmphost);
			if (hp != NULL) {
				bcopy(hp->h_addr, &fs->fs_addr.sin_addr,
				      hp->h_length);
			}
		}
		*p = tmpc;
	}
	(void) endmntent(table);

	/* free fs's that are no longer present */
	for (fs = HEAD(struct filsys, fs_q); fs; fs = fsnext) {
		fsnext = NEXT(struct filsys, fs);
		if (!fs->fs_present) {
#ifdef _SVR4_MODS
			if (fs->fs_mine)
				syslog(LOG_ERR,
					"%s:%s no longer mounted\n",
					fs->fs_host, fs->fs_dir);
			if (trace > 1)
				(void) fprintf(stderr,
					"%s:%s no longer mounted\n",
					fs->fs_host, fs->fs_dir);
#endif
			flush_links(fs);
			free_filsys(fs);
		}
	}
}

/*
 * Search the mount table to see if the given file system is already
 * mounted.  NOTE: This routine is not called when accessing the local
 * filesystem through the automounter.  This case is handled higher up
 * in find_server().
 */
#ifdef _SVR4_MODS
struct filsys *
already_mounted(host, fsname, mntpnt)
#else
struct filsys *
already_mounted(host, fsname, opts, hostaddr)
#endif
	char *host;
	char *fsname;
#ifdef _SVR4_MODS
	char *mntpnt;
#else
	char *opts;
	struct in_addr hostaddr;
#endif
{
	struct filsys *fs;
	struct stat stbuf;
	struct mntent m1, m2;
	int has1, has2;
	int fd;
	struct autodir *dir;
	int mydir;
	extern int verbose;

	check_mtab();
	m1.mnt_opts = mntpnt;
	for (fs = HEAD(struct filsys, fs_q); fs; fs = NEXT(struct filsys, fs)){
		if (strcmp(fsname, fs->fs_dir) != 0)
			continue;
#ifdef _SVR4_MODS
		if (strcmp(host, fs->fs_host) != 0)
			continue;
#else
		if (hostaddr.s_addr != fs->fs_addr.sin_addr.s_addr)
			continue;
#endif
		/*
		 * Check it's not on one of my mount points.
		 * I might be mounted on top of a previous 
		 * mount of the same file system.
		 */

		for (mydir = 0, dir = HEAD(struct autodir, dir_q); dir;
			dir = NEXT(struct autodir, dir)) {
			if (strcmp(dir->dir_name, fs->fs_mntpnt) == 0) {
				mydir = 1;
				if (verbose)
					syslog(LOG_ERR,
					"%s:%s already mounted on %s",
					host, fsname, fs->fs_mntpnt);
				break;
			}
		}
		if (mydir)
			continue;

#ifdef _RISCOS
		/* If it's mounted, then why worry about mount options.
		 * Check to see if mount points match.  'opts' formals
		 * parameter is used differently in this case to represent
		 * the mountpoint of the current access attempt.  This mod
		 * also fixes a problem when all of a remote machine's
		 * filesystems are mounted manually and the directories are
		 * accessed.  If the mount options were different for the
		 * root filesystem, the automounter would attempt to mount
		 * all of the remote filesystems even though they were mounted.
		 * Because all would fail (already mounted), the do_mount
		 * routine would return a code as if the machine was down.
		 * With the current mod, the above behavior is no longer
		 * present; however, if all but one of a machine's filesystems
		 * are mounted manually, the current code will not mount
		 * the remaining filesystem.  The old code would have mounted
		 * it because [in my opinion] of a side-affect of a bug.
		 */
		if (strcmp(mntpnt, fs->fs_mntpnt))	/* if different */
			continue;
#else
		m2.mnt_opts = fs->fs_opts;
		has1 = hasmntopt(&m1, MNTOPT_RO) != NULL;
		has2 = hasmntopt(&m2, MNTOPT_RO) != NULL;
		if (has1 != has2)
			continue;
		has1 = hasmntopt(&m1, MNTOPT_NOSUID) != NULL;
		has2 = hasmntopt(&m2, MNTOPT_NOSUID) != NULL;
		if (has1 != has2)
			continue;
		has1 = hasmntopt(&m1, MNTOPT_SOFT) != NULL;
		has2 = hasmntopt(&m2, MNTOPT_SOFT) != NULL;
		if (has1 != has2)
			continue;
		has1 = hasmntopt(&m1, MNTOPT_INTR) != NULL;
		has2 = hasmntopt(&m2, MNTOPT_INTR) != NULL;
		if (has1 != has2)
			continue;
#ifdef MNTOPT_SECURE
		has1 = hasmntopt(&m1, MNTOPT_SECURE) != NULL;
		has2 = hasmntopt(&m2, MNTOPT_SECURE) != NULL;
		if (has1 != has2)
			continue;
#endif MNTOPT_SECURE
#endif /* !_RISCOS */
		/*
		 * dir under mountpoint may have been removed by other means
		 * If so, we get a stale file handle error here if we fsync
		 * the dir to remove the attr cache info
		 */
		fd = open(fs->fs_mntpnt, 0);
		if (fd < 0)
			continue;
		if (fsync(fd) != 0 || fstat(fd, &stbuf) != 0) {
			(void) close(fd);
			continue;
		}
		(void) close(fd);
		return (fs);
	}
	return(0);
}

nfsunmount(fs)
	struct filsys *fs;
{
	struct sockaddr_in sin;
	struct timeval timeout;
	CLIENT *client;
	enum clnt_stat rpc_stat;
	int s;

	sin = fs->fs_addr;
	/*
	 * Port number of "fs->fs_addr" is NFS port number; make port
	 * number 0, so "clntudp_create" finds port number of mount
	 * service.
	 */
	sin.sin_port = 0;
	s = RPC_ANYSOCK;
	timeout.tv_usec = 0;
	timeout.tv_sec = 10;
	if ((client = clntudp_create(&sin, MOUNTPROG, MOUNTVERS,
	    timeout, &s)) == NULL) {
		syslog(LOG_WARNING, "%s:%s %s",
		    fs->fs_host, fs->fs_dir,
		    clnt_spcreateerror("server not responding"));
		return;
	}
#ifdef OLDMOUNT
	if (bindresvport(s, NULL))
		syslog(LOG_ERR, "Warning: cannot do local bind");
#endif
	client->cl_auth = authunix_create_default();
	timeout.tv_usec = 0;
	timeout.tv_sec = 25;
	rpc_stat = clnt_call(client, MOUNTPROC_UMNT, xdr_path, &fs->fs_dir,
	    xdr_void, (char *)NULL, timeout);
	(void) close(s);
	AUTH_DESTROY(client->cl_auth);
	clnt_destroy(client);
	if (rpc_stat != RPC_SUCCESS)
		syslog(LOG_WARNING, "%s", clnt_sperror(client, "unmount"));
}

enum clnt_stat
#ifdef _SVR4_MODS
pingmount(hostname, hostaddr)
	char *hostname;
	struct in_addr hostaddr;
#else
pingmount(hostaddr)
	struct in_addr hostaddr;
#endif
{
	struct timeval tottime;
	struct sockaddr_in server_addr;
	enum clnt_stat clnt_stat;
	u_long port;
#ifdef _SVR4_MODS
	static char goodhost[MAXHOSTNAMELEN+1];
	static char deadhost[MAXHOSTNAMELEN+1];
#else
	static struct in_addr goodhost, deadhost;
#endif
	static time_t goodtime, deadtime;
	int cache_time = 60;  /* sec */

#ifdef _SVR4_MODS
	if (goodtime > time_now && strcmp(hostname, goodhost) == 0)
			return (RPC_SUCCESS);
	if (deadtime > time_now && strcmp(hostname, deadhost) == 0)
			return (RPC_TIMEDOUT);
	if (trace > 1)
		(void) fprintf(stderr, "ping %s ", hostname);

#else
	if (goodtime > time_now && hostaddr.s_addr == goodhost.s_addr)
			return RPC_SUCCESS;
	if (deadtime > time_now && hostaddr.s_addr == deadhost.s_addr)
			return RPC_TIMEDOUT;

	if (trace > 1)
		fprintf(stderr, "ping %s ", inet_ntoa(hostaddr));
#endif /* _SVR4_MODS */

	/* Setting this too low will cause a mount point to appear to
	 * disappear for periods of time up to 60 seconds.  3 seconds
	 * was too low, so I'll put it back to 10 seconds.
	 */
	tottime.tv_sec = 10;
	tottime.tv_usec = 0;

	/* 
	 * We use the pmap_rmtcall interface because the normal
	 * portmapper has a wired-in 60 second timeout
	 */
	server_addr.sin_family = AF_INET;
	server_addr.sin_addr = hostaddr;
	server_addr.sin_port = 0;
	clnt_stat = pmap_rmtcall(&server_addr, MOUNTPROG, MOUNTVERS, NULLPROC,
			xdr_void, 0, xdr_void, 0, tottime, &port);

	if (clnt_stat == RPC_SUCCESS) {
#ifdef _SVR4_MODS
		(void) strcpy(goodhost, hostname);
#else
		goodhost = hostaddr;
#endif
		goodtime = time_now + cache_time;
	} else {
#ifdef _SVR4_MODS
		(void) strcpy(deadhost, hostname);
#else
		deadhost = hostaddr;
#endif
		deadtime = time_now + cache_time;
	}

	if (trace > 1)
		fprintf(stderr, "%s\n", clnt_stat == RPC_SUCCESS ?
			"OK" : "NO RESPONSE");

	return (clnt_stat);
}

struct in_addr gotaddr;

/* ARGSUSED */
catchfirst(ignore, raddr)
	struct sockaddr_in *raddr;
{
	gotaddr = raddr->sin_addr;
	return (1);	/* first one ends search */
}

/*
 * ping a bunch of hosts at once and find out who
 * responds first
 */
struct in_addr
trymany(addrs, timeout)
	struct in_addr *addrs;
	int timeout;
{
	enum clnt_stat many_cast();
	enum clnt_stat clnt_stat;

	if (trace > 1) {
		register struct in_addr *a;

		fprintf(stderr, "many_cast: ");
		for (a = addrs ; a->s_addr ; a++)
			fprintf(stderr, "%s ", inet_ntoa(*a));
		fprintf(stderr, "\n");
	}
		
	gotaddr.s_addr = 0;
	clnt_stat = many_cast(addrs, MOUNTPROG, MOUNTVERS, NULLPROC,
			xdr_void, 0, xdr_void, 0, catchfirst, timeout);
	if (trace > 1) {
		fprintf(stderr, "many_cast: got %s\n",
			(int) clnt_stat ? "no response" : inet_ntoa(gotaddr));
	}
	if (clnt_stat)
		syslog(LOG_ERR, "trymany: servers not responding: %s",
			clnt_sperrno(clnt_stat));
	return (gotaddr);
}

nfsstat
nfsmount(host, hostaddr, dir, mntpnt, opts, fsp)
	char *host, *dir, *mntpnt, *opts;
	struct in_addr hostaddr;
	struct filsys **fsp;
{
	struct filsys *fs;
	char netname[MAXNETNAMELEN+1];
	char remname[MAXPATHLEN];
	struct mntent m;
	struct nfs_args args;
	int flags;
	struct sockaddr_in sin;
	static struct fhstatus fhs;
	struct timeval timeout;
	CLIENT *client;
	enum clnt_stat rpc_stat;
	int s;
	u_short port;
	nfsstat status;
	struct stat stbuf;

	if (lstat(mntpnt, &stbuf) < 0) {
		syslog(LOG_ERR, "Couldn't stat %s: %m", mntpnt);
		return (NFSERR_NOENT);
	}
	if ((stbuf.st_mode & S_IFMT) == S_IFLNK) {
		if (readlink(mntpnt, remname, sizeof remname) < 0)
			return (NFSERR_NOENT);
		if (remname[0] == '/') {
			syslog(LOG_ERR,
				"%s -> %s from %s: absolute symbolic link",
				mntpnt, remname, host);
			return (NFSERR_NOENT);
		}
	}

	/*
	 * get fhandle of remote path from server's mountd
	 */
	(void) sprintf(remname, "%s:%s", host, dir);
	bzero((char *)&sin, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = hostaddr.s_addr;
	timeout.tv_usec = 0;
	timeout.tv_sec = mount_timeout;
	s = RPC_ANYSOCK;
	if ((client = clntudp_create(&sin, MOUNTPROG, MOUNTVERS,
	    timeout, &s)) == NULL) {
		syslog(LOG_ERR, "%s %s", remname,
		    clnt_spcreateerror("server not responding"));
		return (NFSERR_NOENT);
	}
#ifdef OLDMOUNT
	if (bindresvport(s, NULL))
		syslog(LOG_ERR, "Warning: cannot do local bind");
#endif
	client->cl_auth = authunix_create_default();
	timeout.tv_usec = 0;
	timeout.tv_sec = mount_timeout;
	rpc_stat = clnt_call(client, MOUNTPROC_MNT, xdr_path, &dir,
	    xdr_fhstatus, &fhs, timeout);
	if (rpc_stat != RPC_SUCCESS) {
		/*
		 * Given the way "clnt_sperror" works, the "%s" immediately
		 * following the "not responding" is correct.
		 */
		syslog(LOG_ERR, "%s server not responding%s", remname,
		    clnt_sperror(client, ""));
		(void) close(s);
		clnt_destroy(client);
		return (NFSERR_NOENT);
	}
	(void) close(s);
	AUTH_DESTROY(client->cl_auth);
	clnt_destroy(client);

	if (errno = fhs.fhs_status)  {
                if (errno == EACCES) {
			status = NFSERR_ACCES;
                } else {
			syslog(LOG_ERR, "%s: %m", remname);
			status = NFSERR_IO;
                }
                return (status);
        }        

	/*
	 * set mount args
	 */
	args.fh = (caddr_t)&fhs.fhs_fh;
	args.hostname = host;
	args.flags = 0;
	args.flags |= NFSMNT_HOSTNAME;
	args.addr = &sin;
	m.mnt_opts = opts;

	if (hasmntopt(&m, MNTOPT_SOFT) != NULL) {
		args.flags |= NFSMNT_SOFT;
	}
	if (hasmntopt(&m, MNTOPT_INTR) != NULL) {
		args.flags |= NFSMNT_INT;
	}
#ifdef MNTOPT_SECURE
	if (hasmntopt(&m, MNTOPT_SECURE) != NULL) {
		args.flags |= NFSMNT_SECURE;
		/*
                 * XXX: need to support other netnames outside domain
                 * and not always just use the default conversion
                 */
                if (!host2netname(netname, host, NULL)) {
                        return (NFSERR_NOENT); /* really unknown host */
                }
                args.netname = netname;
	}
#endif MNTOPT_SECURE
	if (port = nopt(&m, "port")) {
		sin.sin_port = htons(port);
	} else {
		sin.sin_port = htons(NFS_PORT);	/* XXX should use portmapper */
	}

	if (args.rsize = nopt(&m, "rsize")) {
		args.flags |= NFSMNT_RSIZE;
	}
	if (args.wsize = nopt(&m, "wsize")) {
		args.flags |= NFSMNT_WSIZE;
	}
	if (args.timeo = nopt(&m, "timeo")) {
		args.flags |= NFSMNT_TIMEO;
	}
	if (args.retrans = nopt(&m, "retrans")) {
		args.flags |= NFSMNT_RETRANS;
	}

	flags = 0;
	flags |= (hasmntopt(&m, MNTOPT_RO) == NULL) ? 0 : M_RDONLY;
	flags |= (hasmntopt(&m, MNTOPT_NOSUID) == NULL) ? 0 : M_NOSUID;

	if (trace > 1) {
		fprintf(stderr, "mount %s %s (%s)\n",
			remname, mntpnt, opts);
	}
#ifdef OLDMOUNT
#define MOUNT_NFS 1
	if (mount(MOUNT_NFS, mntpnt, flags, &args)) {
#else
	if (mount("nfs", mntpnt, flags | M_NEWTYPE, &args)) {
#endif
		syslog(LOG_ERR, "Mount of %s on %s: %m", remname, mntpnt);
		return (NFSERR_IO);
	}
	if (trace > 1) {
		fprintf(stderr, "mount %s OK\n", remname);
	}
	if (*fsp)
		fs = *fsp;
	else {
		fs = alloc_fs(host, dir, mntpnt, opts);
		if (fs == NULL)
			return (NFSERR_NOSPC);
	}
	fs->fs_type = MNTTYPE_NFS;
	fs->fs_mine = 1;
	fs->fs_nfsargs = args;
	fs->fs_mflags = flags;
	fs->fs_nfsargs.hostname = fs->fs_host;
	fs->fs_nfsargs.addr = &fs->fs_addr;
	fs->fs_nfsargs.fh = (caddr_t)&fs->fs_rootfh;
	fs->fs_addr = sin;
	bcopy(&fhs.fhs_fh, &fs->fs_rootfh, sizeof fs->fs_rootfh);
	*fsp = fs;
	return (NFS_OK);
}

nfsstat
remount(fs)
	struct filsys *fs;
{
	char remname[1024];

	if (fs->fs_nfsargs.fh == 0) 
		return nfsmount(fs->fs_host, fs->fs_addr.sin_addr, fs->fs_dir,
				fs->fs_mntpnt, fs->fs_opts, &fs);
	(void) sprintf(remname, "%s:%s", fs->fs_host, fs->fs_dir);
	if (trace > 1) {
		fprintf(stderr, "remount %s %s (%s)\n",
			remname, fs->fs_mntpnt, fs->fs_opts);
	}
#ifdef OLDMOUNT
	if (mount(MOUNT_NFS, fs->fs_mntpnt, fs->fs_mflags, &fs->fs_nfsargs)) {
#else
	if (mount("nfs", fs->fs_mntpnt, fs->fs_mflags | M_NEWTYPE, &fs->fs_nfsargs)) {
#endif
		syslog(LOG_ERR, "Remount of %s on %s: %m", remname,
		    fs->fs_mntpnt);
		return (NFSERR_IO);
	}
	if (trace > 1) {
		fprintf(stderr, "remount %s OK\n", remname);
	}
	return (NFS_OK);
}


/*
 * Add one or more entries to /etc/mtab
 */
void
addtomtab(rootfs)
	struct filsys *rootfs;
{
	FILE *fp;
	struct filsys *fs;
	struct stat stbuf;
	struct mntent mnt;
	char remname[MAXPATHLEN];
	char buf[16], opts[1024];
	int forked;
	
	/*
	 * In SunOs 4.0 only mount and automount flock the mtab.
	 * In releases prior to 4.0 any programs that browse mtab
	 * (df, find etc) also lock the mtab and deadlock with
	 * the automounter is a distinct possibility. Avoid
	 * deadlock here by backgrounding the update.
	 */
	forked = 0;
	if (islocked(MOUNTED)) {
		if (fork())
			return;
		forked++;
	}

	fp = setmntent(MOUNTED, "r+");
	if (fp == NULL) {
		syslog(LOG_ERR, "%s: %m", MOUNTED);
		return;
	}

	for (fs = TAIL(struct filsys, fs_q); fs; fs = PREV(struct filsys, fs)) {
		if (fs->fs_rootfs != rootfs)
			continue;

		(void) sprintf(remname, "%s:%s", fs->fs_host, fs->fs_dir);
		mnt.mnt_fsname = remname;
		mnt.mnt_dir = fs->fs_mntpnt;
		mnt.mnt_type = MNTTYPE_NFS;
		mnt.mnt_freq = mnt.mnt_passno = 0;

		(void) strcpy(opts, fs->fs_opts);
		mnt.mnt_opts = opts;

		if (addmntent(fp, &mnt)) {
			(void) endmntent(fp);
			syslog(LOG_ERR, "%s: %m", MOUNTED);
			return;
		}
	}
	(void) endmntent(fp);
	if (stat(MOUNTED, &stbuf) < 0)
		syslog(LOG_ERR, "%s: %m", MOUNTED);
	else
		last_mtab_time = stbuf.st_mtime;

	if (forked)
		_exit(0);
}

#include <sys/file.h>
#include <fcntl.h>
/*
 * Check whether the file is flock'ed.
 */
int
islocked(file)
	char *file;
{
	int f;

	f = open(file, O_RDONLY);
	if (f < 0) {
		syslog(LOG_ERR, "%s: %m", MOUNTED);
		return 1;
	}
	if (flock(f, LOCK_EX | LOCK_NB) < 0) {
		sleep(1);
		if (flock(f, LOCK_EX | LOCK_NB) < 0) {
			(void) close(f);
			return 1;
		}
	}
	(void) close(f);	/* close and release the lock */
	return 0;
}

/*
 * Remove one or more entries from the mount table.
 * If mntpnt is non-null then remove the entry
 * for that mount point.
 * Otherwise use rootfs - it is the root fs of
 * a mounted hierarchy.  Remove all entries for
 * the hierarchy.
 */
void
clean_mtab(mntpnt, rootfs)
	char *mntpnt;
	struct filsys *rootfs;
{
	FILE *fold, *fnew;
	struct mntent *mnt;
	struct stat stbuf;
	struct filsys *fs;
	char tmpnam[64];
	int delete;
#ifdef _RISCOS
	int omask;
#endif

	fold = setmntent(MOUNTED, "r+");
	if (fold == NULL) {
		syslog(LOG_ERR, "%s: %m", MOUNTED);
		return;
	}
	(void) sprintf(tmpnam, "%s.XXXXXX", MOUNTED);
#ifdef _RISCOS
	if (stat(MOUNTED, &stbuf) < 0) {
		syslog(LOG_ERR, "%s: %m", MOUNTED);
		endmntent(fold);
		return;
	}
	omask = umask(0660);
#endif
	(void) mktemp(tmpnam);
#ifdef _RISCOS
	umask(omask);
#endif
	fnew = setmntent(tmpnam, "w");
	if (fnew == NULL) {
		syslog(LOG_ERR, "%s: %m", tmpnam);
		(void) endmntent(fold);
		return;
	}

	while (mnt = getmntent(fold)) {

		if (mntpnt) {
			if (strcmp(mnt->mnt_dir, mntpnt) == 0)
				continue;
		} else {
			delete = 0;
			for (fs = rootfs ; fs ; fs = NEXT(struct filsys, fs)) {
				if (strcmp(mnt->mnt_dir, fs->fs_mntpnt) == 0) {
					delete = 1;
					break;
				}
			}
			if (delete)
				continue;
		}
		if (addmntent(fnew, mnt)) {
			syslog(LOG_ERR, "addmntent %s: %m", tmpnam);
			(void) endmntent(fold);
			(void) endmntent(fnew);
			(void) unlink(tmpnam);
			return;
		}
	}

	(void) endmntent(fold);
	(void) endmntent(fnew);
#ifdef _RISCOS
	/* restore the original attributes to /etc/mtab */
	if (chown(tmpnam, stbuf.st_uid, stbuf.st_gid) < 0) {
		syslog(LOG_ERR, "chown %s to %d/%d: %m",
		       tmpnam, stbuf.st_uid, stbuf.st_gid);
		unlink(tmpnam);
		return;
	}
	if (chmod(tmpnam, stbuf.st_mode) < 0) {
		syslog(LOG_ERR, "chmod %s to %d: %m", tmpnam, stbuf.st_mode);
		unlink(tmpnam);
		return;
	}
#endif /* _RISCOS */
	if (rename(tmpnam, MOUNTED) < 0) {
		syslog(LOG_ERR, "rename %s %s: %m", tmpnam, MOUNTED);
		(void) unlink(tmpnam);
		return;
	}
	if (stat(MOUNTED, &stbuf) < 0)
		syslog(LOG_ERR, "%s: %m", MOUNTED);
	else
		last_mtab_time = stbuf.st_mtime;
}

/*
 * Return the value of a numeric option of the form foo=x, if
 * option is not found or is malformed, return 0.
 */
nopt(mnt, opt)
	struct mntent *mnt;
	char *opt;
{
	int val = 0;
	char *equal;
	char *str;

	if (str = hasmntopt(mnt, opt)) {
		if (equal = index(str, '=')) {
			val = atoi(&equal[1]);
		} else {
			syslog(LOG_ERR, "Bad numeric option '%s'", str);
		}
	}
	return (val);
}
