/*
 * Copyright (c) 1989 The Regents of the University of California.
 * All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Rick Macklem at The University of Guelph.
 *
 * 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. 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.
 */

#ifndef lint
char copyright[] =
"@(#) Copyright (c) 1989 Regents of the University of California.\n\
 All rights reserved.\n";
#endif not lint

#if !defined(lint) && !defined(SABER)
static char *rcsid = "$Id: fixmount.c,v 1.19 1993/06/10 07:15:32 stolcke Exp $ ICSI (Berkeley)";
#endif

#include <sys/types.h>
#include <sys/time.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <netdb.h>
#include <rpc/rpc.h>
#include <errno.h>
#ifdef sgi
#include <sys/fs/nfs.h>
#else
#include <nfs/nfs.h>
#endif
#include <rpcsvc/mount.h>
#include <stdio.h>
#include <string.h>
#include <sys/param.h>
#include <signal.h>
#include <setjmp.h>

#define CREATE_TIMEOUT	2	/* seconds */
#define CALL_TIMEOUT	5	/* seconds */

#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 64
#endif

/* Constant defs */
#define	ALL	1
#define	DIRS	2

#define	DODUMP		0x1
#define	DOEXPORTS	0x2
#define	DOREMOVE	0x4
#define	DOVERIFY	0x8

static char thishost[MAXHOSTNAMELEN] = "";
static struct mountlist *mntdump;
static struct exports *exports;
static int type = 0;

/* static CLIENT *clnt_create_timeout(); */

static int quiet = 0;

/*
 * This command queries the NFS mount daemon for it's mount list and/or
 * it's exports list and prints them out.
 * See "NFS: Network File System Protocol Specification, RFC1094, Appendix A"
 * for detailed information on the protocol.
 */
main(argc, argv)
	int argc;
	char **argv;
{
	register struct exports *exp;
	register struct groups *grp;
	extern char *optarg;
	extern int optind;
	register int rpcs = 0;
	int ch;
	char *host;
	CLIENT *client;
	AUTH *auth;
	enum clnt_stat estat;
	struct timeval tv;
	int morethanone;

	while ((ch = getopt(argc, argv, "adervqh:")) != EOF)
		switch((char)ch) {
		case 'a':
			if (type == 0) {
				type = ALL;
				rpcs |= DODUMP;
			} else
				usage();
			break;
		case 'd':
			if (type == 0) {
				type = DIRS;
				rpcs |= DODUMP;
			} else
				usage();
			break;
		case 'e':
			rpcs |= DOEXPORTS;
			break;
		case 'r':
			rpcs |= DOREMOVE;
			break;
		case 'v':
			rpcs |= DOVERIFY;
			break;
		case 'q':
			quiet = 1;
			break;
		case 'h':
			strncpy(thishost, optarg, sizeof(thishost));
			thishost[sizeof(thishost) - 1] = '\0';
			break;
		case '?':
		default:
			usage();
		}

	if (optind == argc)
		usage();

	if (rpcs == 0)
		rpcs = DODUMP;

	if (!*thishost && gethostname(thishost, sizeof(thishost)) < 0) {
		perror("gethostname");
		exit(1);
	}

#ifdef __svr4__
	if (!(auth = authsys_create_default())) {
#else
	if (!(auth = authunix_create_default())) {
#endif /* __svr4__ */
		fprintf(stderr, "couldn't create authentication handle\n");
		exit(1);
	}

	morethanone = (optind + 1 < argc);

	for (; optind < argc; optind++) {

		host = argv[optind];
		tv.tv_sec = CREATE_TIMEOUT; tv.tv_usec = 0;

		if (!(client = clnt_create_timeout(host, &tv)))
			continue;
		
		client->cl_auth = auth;
		tv.tv_sec = CALL_TIMEOUT; tv.tv_usec = 0;

		if (rpcs & (DODUMP|DOREMOVE|DOVERIFY))
			if ((estat = clnt_call(client,
				MOUNTPROC_DUMP, xdr_void, (char *)0,
				xdr_mountlist, (char *)&mntdump, tv)) != RPC_SUCCESS) {
				fprintf(stderr, "%s: MOUNTPROC_DUMP: ", host);
				clnt_perrno(estat);
				fflush(stderr);
				mntdump = NULL;
				goto next;
			}
		if (rpcs & DOEXPORTS)
			if ((estat = clnt_call(client,
				MOUNTPROC_EXPORT, xdr_void, (char *)0,
				xdr_exports, (char *)&exports, tv)) != RPC_SUCCESS) {
				fprintf(stderr, "%s: MOUNTPROC_EXPORT: ", host);
				clnt_perrno(estat);
				fflush(stderr);
				exports = NULL;
				goto next;
			}

		/* Now just print out the results */
		if ((rpcs & (DODUMP|DOEXPORTS)) &&
		    morethanone) {
			printf(">>> %s <<<\n", host);
			fflush(stdout);
		}

		if (rpcs & DODUMP) {
			print_dump(mntdump);
		}
		if (rpcs & DOEXPORTS) {
			exp = exports;
			while (exp) {
				printf("%-35s", exp->ex_name);
				grp = exp->ex_groups;
				if (grp == NULL) {
					printf("Everyone\n");
				} else {
					while (grp) {
						printf("%s ", grp->g_name);
						grp = grp->g_next;
					}
					printf("\n");
				}
				exp = exp->ex_next;
			}
		}
		if (rpcs & DOVERIFY)
			fix_rmtab(client, host, mntdump, 0);

		if (rpcs & DOREMOVE)
			fix_rmtab(client, host, mntdump, 1);

	next:
		if (mntdump)
			(void)clnt_freeres(client, xdr_mountlist, (char *)&mntdump);
		if (exports)
			(void)clnt_freeres(client, xdr_exports, (char *)&exports);

 		clnt_destroy(client);
	}
}

usage()
{
	fprintf(stderr, "usage: fixmount [-adervq] [-h hostname] host ...\n");
	exit(1);
}

static jmp_buf before_rpc;

void
static create_timeout()
{
	signal(SIGALRM, SIG_DFL);
	longjmp(before_rpc, 1);
}

static CLIENT *
clnt_create_timeout(host, to)
	char *host;
	struct timeval *to;
{
	CLIENT *client;

	if (setjmp(before_rpc)) {
		if (!quiet) {
			fprintf(stderr, "%s: ", host);
			clnt_perrno(RPC_TIMEDOUT);
		}
		return NULL;
	}

	signal(SIGALRM, create_timeout);
	ualarm(to->tv_sec * 1000000 + to->tv_usec, 0);

	if (!(client = clnt_create(host, MOUNTPROG, MOUNTVERS, "tcp"))) {
		ualarm(0,0);
		if (!quiet) {
			 clnt_pcreateerror(host);
		}
		return NULL;
	}

	ualarm(0,0);
	return client;
}
		
/*
 * Print the binary tree in inorder so that output is sorted.
 */
print_dump(mp)
	struct mountlist *mp;
{

	if (mp == NULL)
		return;
	if (strcmp(mp->ml_name, thishost) == 0) {
		switch (type) {
		case ALL:
			printf("%s:%s\n", mp->ml_name, mp->ml_path);
			break;
		case DIRS:
			printf("%s\n", mp->ml_path);
			break;
		default:
				printf("%s\n", mp->ml_name);
			break;
		};
	}
	if (mp->ml_nxt)
		print_dump(mp->ml_nxt);
}

#if defined(sun) || defined(NeXT) || defined(sgi)
#include <mntent.h>

#ifndef _PATH_MTAB
#define _PATH_MTAB "/etc/mtab"
#endif

int
check_mount(host, path)
	char *host;		/* remote host name */
	char *path;		/* path to check */
{
	FILE *mtab;
	struct mntent *ment;
	int found = 0;
	
	/* scan mtab for path */
	if (!(mtab = setmntent(_PATH_MTAB, "r"))) {
		perror(_PATH_MTAB);
		exit(1);
	}

	/* setmntent() doesn't do locking in read-only mode.
	   Too bad -- it seems to rely on mount() and friends to do
	   atomic updates by renaming the file.  Well, our patched amd
	   rewrites mtab in place to avoid NFS lossage, so better do the
	   locking ourselves.
	*/
	if (flock(fileno(mtab), LOCK_SH) < 0) {
		perror(_PATH_MTAB);
		exit(1);
	}

	while (!found && (ment = getmntent(mtab))) {
		char *colon;

		if (colon = strchr(ment->mnt_fsname, ':')) {
			*colon = '\0';
			if (strcmp(ment->mnt_fsname, host) == 0 &&
			    (strcmp(colon+1, path) == 0 ||
			     strcmp(ment->mnt_dir, path) == 0))
				found = 1;
		}
	}

	(void) endmntent(mtab);

	if (!found) {
		char *swap;

		/*** HACK ***/
		/* swap files never show up in mtab, only root fs */
		if (swap = strstr(path, "swap")) {
			strncpy(swap, "root", 4);
			found = check_mount(host, path);
			strncpy(swap, "swap", 4);
		}
	}
	
	return found;
}
#endif

#ifdef ultrix
#include <sys/mount.h>

int
check_mount(host, path)
	char *host;		/* remote host name */
	char *path;		/* path to check */
{
	int start = 0;
	int err = 0;
	struct fs_data ment;
	int found = 0;
	
	/* scan mtab for path */
	while (!found && (err = getmnt(&start, &ment, sizeof(ment),
		                       NOSTAT_MANY, NULL)) > 0) {
		char *colon;

		if (colon = strchr(ment.fd_devname, ':')) {
			*colon = '\0';
			if (strcmp(ment.fd_devname, host) == 0 &&
			    (strcmp(colon+1, path) == 0 ||
			     strcmp(ment.fd_path, path) == 0))
				found = 1;
		}
	}

	if (!found && err < 0) {
		perror("getmnt");
		exit(1);
	}
	
	return found;
}
#endif

static char dirpath[NFS_MAXPATHLEN];

/*
 * remove entry from remove rmtab
 */
remove_mount(client, host, path, fixit)
	CLIENT *client;		/* RCP handle to remote mountd */
	char *path;		/* remote directory */
	int fixit;		/* go ahead with fix */
{
	enum clnt_stat estat;
	struct timeval tv;
	char *pathp = dirpath;

	strncpy(dirpath, path, sizeof(dirpath));

	if (!fixit) {
		printf("%s: bogus mount %s:%s\n", host, thishost, path);
		fflush(stdout);
	}
	else {
		printf("%s: removing %s:%s\n", host, thishost, path);
		fflush(stdout);

		tv.tv_sec = CALL_TIMEOUT; tv.tv_usec = 0;

		if ((estat = clnt_call(client,
			MOUNTPROC_UMNT, xdr_path, &pathp,
			xdr_void, (char *)0, &tv)) != RPC_SUCCESS) {
			fprintf(stderr, "%s:%s MOUNTPROC_UMNT: ",
				host, path);
			clnt_perrno(estat);
			fflush(stderr);
			return -1;
		}
	}
	return 0;
}

/*
 * fix mount list on remote host
 */
fix_rmtab(client, host, mp, fixit)
	CLIENT *client;		/* RCP handle to remote mountd */
	char *host;		/* remote host name */
	struct mountlist *mp;	/* mountlist from mountd */
	int fixit;		/* go ahead with fix */
{
	struct mountlist *p;

	for (p = mp; p; p = p->ml_nxt) {
		if (strcmp(p->ml_name, thishost) == 0) {
			if (!check_mount(host, p->ml_path))
				remove_mount(client, host, p->ml_path, fixit);
		}
	}
}
