/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

/*
 * University Copyright- Copyright (c) 1982, 1986, 1988
 * The Regents of the University of California
 * All Rights Reserved
 *
 * University Acknowledgment- Portions of this document are derived from
 * software developed by the University of California, Berkeley, and its
 * contributors.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * mount
 */
#include <ctype.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <poll.h>
#include <sys/mkdev.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/mntent.h>
#include <stdlib.h>

#define	bcopy(f, t, n)	memcpy(t, f, n)
#define	bzero(s, n)	memset(s, 0, n)
#define	bcmp(s, d, n)	memcmp(s, d, n)

#define	index(s, r)	strchr(s, r)
#define	rindex(s, r)	strrchr(s, r)

#include <errno.h>
#include <sys/vfs.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mnttab.h>
#include <sys/mount.h>
#include <sys/mntio.h>
#include <sys/wait.h>
#include <sys/fstyp.h>
#include <sys/fsid.h>
#include <sys/vfstab.h>
#include <sys/filio.h>
#include <sys/fs/ufs_fs.h>

#include <sys/fs/ufs_mount.h>
#include <sys/fs/ufs_filio.h>

#include <locale.h>
#include <fslib.h>

static int	ro = 0;
static int	largefiles = 0; /* flag - add default nolargefiles to mnttab */

static int	gflg = 0;
static int	mflg = 0;
static int 	Oflg = 0;
static int	qflg = 0;

#define	NAME_MAX	64		/* sizeof "fstype myname" */

static int	checkislog(char *);
static void	disable_logging(char *, char *);
static int	eatmntopt(struct mnttab *, char *);
static void	enable_logging(char *, char *);
static void	fixopts(struct mnttab *, char *);
static void	mountfs(struct mnttab *);
static void	replace_opts(char *, int, char *, char *);
static int	replace_opts_dflt(char *, int, const char *, const char *);
static void	rmopt(struct mnttab *, char *);
static void	rpterr(char *, char *);
static void	usage(void);

static char	fstype[] = MNTTYPE_UFS;
static char	opts[MAX_MNTOPT_STR];
static char	typename[NAME_MAX], *myname;
static char	*fop_subopts[] = { MNTOPT_ONERROR, NULL };
#define	NOMATCH	(-1)
#define	ONERROR	(0)		/* index within fop_subopts */

static struct fop_subopt {
	char	*str;
	int	 flag;
} fop_subopt_list[] = {
	{ UFSMNT_ONERROR_PANIC_STR,	UFSMNT_ONERROR_PANIC	},
	{ UFSMNT_ONERROR_LOCK_STR,	UFSMNT_ONERROR_LOCK	},
	{ UFSMNT_ONERROR_UMOUNT_STR,	UFSMNT_ONERROR_UMOUNT	},
	{ NULL,				UFSMNT_ONERROR_DEFAULT	}
};


/*
 * Check if the specified filesystem is already mounted.
 */
static boolean_t
in_mnttab(char *mountp)
{
	FILE *file;
	int found = B_FALSE;
	struct mnttab mntent;

	if ((file = fopen(MNTTAB, "r")) == NULL)
		return (B_FALSE);
	while (getmntent(file, &mntent) == 0) {
		if (mntent.mnt_mountp != NULL &&
		    strcmp(mntent.mnt_mountp, mountp) == 0 &&
		    mntent.mnt_fstype != NULL &&
		    strcmp(mntent.mnt_fstype, MNTTYPE_UFS) == 0) {
			found = B_TRUE;
			break;
		}
	}
	(void) fclose(file);
	return (found);
}

/*
 * Find opt in mntopt
 */
static char *
findopt(char *mntopt, char *opt)
{
	int nc, optlen = strlen(opt);

	while (*mntopt) {
		nc = strcspn(mntopt, ", =");
		if (strncmp(mntopt, opt, nc) == 0)
			if (optlen == nc)
				return (mntopt);
		mntopt += nc;
		mntopt += strspn(mntopt, ", =");
	}
	return (NULL);
}

int
main(int argc, char *argv[])
{
	struct mnttab mnt;
	int	c;

	(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)
#define	TEXT_DOMAIN	"SYS_TEST"
#endif
	(void) textdomain(TEXT_DOMAIN);

	myname = strrchr(argv[0], '/');
	if (myname)
		myname++;
	else
		myname = argv[0];
	(void) snprintf(typename, sizeof (typename), "%s %s", fstype, myname);
	argv[0] = typename;

	opts[0] = '\0';

	/*
	 * Set options
	 */
	while ((c = getopt(argc, argv, "gmo:pqrVO")) != EOF) {
		switch (c) {

		case 'g':
			gflg++;
			break;

		case 'o':
			if (strlcpy(opts, optarg, sizeof (opts)) >=
			    sizeof (opts)) {
				(void) fprintf(stderr, gettext("option string "
				    "argument too long\n"));
			}
			break;

		case 'O':
			Oflg++;
			break;

		case 'r':
			ro++;
			break;

		case 'm':
			mflg++;
			break;

		case 'q':
			qflg++;
			break;

		default:
			usage();
		}
	}

	if ((argc - optind) != 2)
		usage();

	mnt.mnt_special = argv[optind];
	mnt.mnt_mountp = argv[optind+1];
	mnt.mnt_fstype = fstype;

	/*
	 * Process options.  The resulting options string overwrites the
	 * original.
	 *
	 * XXX:	This code doesn't do a good job of resolving options that are
	 *	specified multiple times or that are given in conflicting
	 *	forms (e.g., both "largefiles" and "nolargefiles").  It also
	 *	doesn't produce well defined behavior for options that may
	 *	also be specified as flags (e.g, "-r" and "ro"/"rw") when both
	 *	are present.
	 *
	 *	The proper way to deal with such conflicts is to start with
	 *	the default value (i.e., the one if no flag or option is
	 *	specified), override it with the last mentioned option pair
	 *	in the -o option string, and finally, override that with
	 *	the flag value. This allows "mount -r" command to mount a
	 *	file system read only that is listed rw in /etc/vfstab.
	 */
	mnt.mnt_mntopts = opts;
	if (findopt(mnt.mnt_mntopts, "m"))
		mflg++;
	if ((gflg || findopt(mnt.mnt_mntopts, MNTOPT_GLOBAL)) &&
			findopt(mnt.mnt_mntopts, MNTOPT_NBMAND)) {
		(void) fprintf(stderr, gettext("NBMAND option not supported on"
						" global filesystem\n"));
		exit(32);
	}

	replace_opts(opts, ro, MNTOPT_RO, MNTOPT_RW);
	replace_opts(opts, largefiles, MNTOPT_NOLARGEFILES, MNTOPT_LARGEFILES);
	gflg = replace_opts_dflt(opts, gflg, MNTOPT_GLOBAL, MNTOPT_NOGLOBAL);

	if (findopt(mnt.mnt_mntopts, MNTOPT_RQ)) {
		rmopt(&mnt, MNTOPT_RQ);
		replace_opts(opts, 1, MNTOPT_QUOTA, MNTOPT_NOQUOTA);
	}

	mountfs(&mnt);
	return (0);
}

static void
reportlogerror(int ret, char *mp, char *special, char *cmd, fiolog_t *flp)
{
	/* No error */
	if ((ret != -1) && (flp->error == FIOLOG_ENONE))
		return;

	/* logging was not enabled/disabled */
	if (ret == -1 || flp->error != FIOLOG_ENONE)
		(void) fprintf(stderr, gettext("Could not %s logging"
				" for %s on %s.\n"), cmd, mp, special);

	/* ioctl returned error */
	if (ret == -1)
		return;

	/* Some more info */
	switch (flp->error) {
	case FIOLOG_ENONE :
		if (flp->nbytes_requested &&
		    (flp->nbytes_requested != flp->nbytes_actual)) {
		    (void) fprintf(stderr, gettext("The log has been resized"
					" from %d bytes to %d bytes.\n"),
					flp->nbytes_requested,
					flp->nbytes_actual);
		}
		return;
	case FIOLOG_ETRANS :
		(void) fprintf(stderr, gettext("Solaris Volume Manager logging"
				" is already enabled.\n"));
		(void) fprintf(stderr, gettext("Please see the"
				" commands metadetach(1M)"
				" or metaclear(1M).\n"));
		break;
	case FIOLOG_EROFS :
		(void) fprintf(stderr, gettext("File system is mounted read "
						"only.\n"));
		(void) fprintf(stderr, gettext("Please see the remount "
				"option described in mount_ufs(1M).\n"));
		break;
	case FIOLOG_EULOCK :
		(void) fprintf(stderr, gettext("File system is locked.\n"));
		(void) fprintf(stderr, gettext("Please see the -u option "
						"described in lockfs(1M).\n"));
		break;
	case FIOLOG_EWLOCK :
		(void) fprintf(stderr, gettext("The file system could not be"
					" write locked.\n"));
		(void) fprintf(stderr, gettext("Please see the -w option "
						"described in lockfs(1M).\n"));
		break;
	case FIOLOG_ECLEAN :
		(void) fprintf(stderr, gettext("The file system may not be"
					" stable.\n"));
		(void) fprintf(stderr, gettext("Please see the -n option"
				" for fsck(1M).\n"));
		break;
	case FIOLOG_ENOULOCK :
		(void) fprintf(stderr, gettext("The file system could not be"
					" unlocked.\n"));
		(void) fprintf(stderr, gettext("Please see the -u option "
						"described in lockfs(1M).\n"));
		break;
	default :
		(void) fprintf(stderr, gettext("Unknown internal error"
					" %d.\n"), flp->error);
		break;
	}
}

static int
checkislog(char *mp)
{
	int fd;
	uint32_t islog;

	fd = open(mp, O_RDONLY);
	islog = 0;
	(void) ioctl(fd, _FIOISLOG, &islog);
	(void) close(fd);
	return ((int)islog);
}

static void
enable_logging(char *mp, char *special)
{
	int fd, ret, islog;
	fiolog_t fl;

	fd = open(mp, O_RDONLY);
	if (fd == -1) {
		perror(mp);
		return;
	}
	fl.nbytes_requested = 0;
	fl.nbytes_actual = 0;
	fl.error = FIOLOG_ENONE;
	ret = ioctl(fd, _FIOLOGENABLE, &fl);
	if (ret == -1)
		perror(mp);
	(void) close(fd);

	/* is logging enabled? */
	islog = checkislog(mp);

	/* report errors, if any */
	if (ret == -1 || !islog)
		reportlogerror(ret, mp, special, "enable", &fl);
}

static void
disable_logging(char *mp, char *special)
{
	int fd, ret, islog;
	fiolog_t fl;

	fd = open(mp, O_RDONLY);
	if (fd == -1) {
		perror(mp);
		return;
	}
	fl.error = FIOLOG_ENONE;
	ret = ioctl(fd, _FIOLOGDISABLE, &fl);
	if (ret == -1)
		perror(mp);
	(void) close(fd);

	/* is logging enabled? */
	islog = checkislog(mp);

	/* report errors, if any */
	if (ret == -1 || islog)
		reportlogerror(ret, mp, special, "disable", &fl);
}


/*
 * attempt to mount file system, return errno or 0
 */
void
mountfs(struct mnttab *mnt)
{
	char			 opt[MAX_MNTOPT_STR];
	char			 opt2[MAX_MNTOPT_STR];
	char			*opts =	opt;
	int			 flags = MS_OPTIONSTR;
	struct ufs_args		 args;
	int			 need_separator = 0;
	int			mount_attempts = 5;

	(void) bzero((char *)&args, sizeof (args));
	(void) strcpy(opts, mnt->mnt_mntopts);
	opt2[0] = '\0';

	flags |= Oflg ? MS_OVERLAY : 0;
	flags |= eatmntopt(mnt, MNTOPT_RO) ? MS_RDONLY : 0;
	flags |= eatmntopt(mnt, MNTOPT_REMOUNT) ? MS_REMOUNT : 0;
	flags |= eatmntopt(mnt, MNTOPT_GLOBAL) ? MS_GLOBAL : 0;

	if (eatmntopt(mnt, MNTOPT_NOINTR))
		args.flags |= UFSMNT_NOINTR;
	if (eatmntopt(mnt, MNTOPT_INTR))
		args.flags &= ~UFSMNT_NOINTR;
	if (eatmntopt(mnt, MNTOPT_SYNCDIR))
		args.flags |= UFSMNT_SYNCDIR;
	if (eatmntopt(mnt, MNTOPT_FORCEDIRECTIO)) {
		args.flags |= UFSMNT_FORCEDIRECTIO;
		args.flags &= ~UFSMNT_NOFORCEDIRECTIO;
	}
	if (eatmntopt(mnt, MNTOPT_NOFORCEDIRECTIO)) {
		args.flags |= UFSMNT_NOFORCEDIRECTIO;
		args.flags &= ~UFSMNT_FORCEDIRECTIO;
	}
	if (eatmntopt(mnt, MNTOPT_NOSETSEC))
		args.flags |= UFSMNT_NOSETSEC;
	if (eatmntopt(mnt, MNTOPT_LARGEFILES))
		args.flags |= UFSMNT_LARGEFILES;
	if (eatmntopt(mnt, MNTOPT_NOLARGEFILES))
		args.flags &= ~UFSMNT_LARGEFILES;
	args.flags |= UFSMNT_LOGGING;	/* default is logging */
	(void) eatmntopt(mnt, MNTOPT_LOGGING);
	if (eatmntopt(mnt, MNTOPT_NOLOGGING))
		args.flags &= ~UFSMNT_LOGGING;
	if (eatmntopt(mnt, MNTOPT_NOATIME))
		args.flags |= UFSMNT_NOATIME;
	if (eatmntopt(mnt, MNTOPT_DFRATIME))
		args.flags &= ~UFSMNT_NODFRATIME;
	if (eatmntopt(mnt, MNTOPT_NODFRATIME))
		args.flags |= UFSMNT_NODFRATIME;

	while (*opts != '\0') {
		char	*argval;

		switch (getsubopt(&opts, fop_subopts, &argval)) {
		case ONERROR:
			if (argval) {
				struct fop_subopt	*s;
				int			 found = 0;

				for (s = fop_subopt_list;
				    s->str && !found;
				    s++) {
					if (strcmp(argval, s->str) == 0) {
						args.flags |= s->flag;
						found = 1;
					}
				}
				if (!found) {
					usage();
				}

				if (need_separator)
					(void) strcat(opt2, ",");
				(void) strcat(opt2, MNTOPT_ONERROR);
				(void) strcat(opt2, "=");
				(void) strcat(opt2, argval);
				need_separator = 1;

			} else {
				args.flags |= UFSMNT_ONERROR_DEFAULT;
			}
			break;

		case NOMATCH:
		default:
			if (argval) {
				if (need_separator)
					(void) strcat(opt2, ",");
				(void) strcat(opt2, argval);
				need_separator = 1;
			}
			break;

		}
	}

	if (*opt2 != '\0')
		(void) strcpy(opt, opt2);
	opts = opt;
	if ((args.flags & UFSMNT_ONERROR_FLGMASK) == 0)
		args.flags |= UFSMNT_ONERROR_DEFAULT;

	(void) signal(SIGHUP,  SIG_IGN);
	(void) signal(SIGQUIT, SIG_IGN);
	(void) signal(SIGINT,  SIG_IGN);

	errno = 0;
	flags |= MS_DATA | MS_OPTIONSTR;
	if (mflg)
		flags |= MS_NOMNTTAB;
	if (flags & MS_REMOUNT) {
		replace_opts(mnt->mnt_mntopts, 1, MNTOPT_RW, MNTOPT_RO);
	}
	fixopts(mnt, opts);

	/*
	 * For global filesystems we want to pass in logging option
	 * so that it shows up in the mnttab of all nodes. We add
	 * logging option if its not specified.
	 */
	if (gflg || findopt(mnt->mnt_mntopts, MNTOPT_GLOBAL)) {
		if (!flags & MS_RDONLY) {
			if (mnt->mnt_mntopts != '\0')
				(void) strcat(mnt->mnt_mntopts, ",");
			(void) strcat(mnt->mnt_mntopts, MNTOPT_LOGGING);
			args.flags |= UFSMNT_LOGGING;
		} else {
			/*
			 * Turn off logging for read only global mounts.
			 * It was set to logging as default above.
			 */
			if (mnt->mnt_mntopts != '\0')
				(void) strcat(mnt->mnt_mntopts, ",");
			(void) strcat(mnt->mnt_mntopts, MNTOPT_NOLOGGING);
			args.flags &= ~UFSMNT_LOGGING;
		}
	}

again:	if (mount(mnt->mnt_special, mnt->mnt_mountp, flags, fstype,
		&args, sizeof (args), mnt->mnt_mntopts, MAX_MNTOPT_STR) != 0) {
		if (errno == EBUSY && !(flags & MS_OVERLAY)) {
			/*
			 * Because of bug 6176743, any attempt to mount any
			 * filesystem could fail for reasons described in that
			 * bug.  We're trying to detect that situation here by
			 * checking that the filesystem we're mounting is not
			 * in /etc/mnttab yet.  When that bug is fixed, this
			 * code can be removed.
			 */
			if (!in_mnttab(mnt->mnt_mountp) &&
			    mount_attempts-- > 0) {
				(void) poll(NULL, 0, 50);
				goto again;
			}
		}
		rpterr(mnt->mnt_special, mnt->mnt_mountp);
		exit(32);
	}

	if (!(flags & MS_RDONLY)) {
		if (args.flags & UFSMNT_LOGGING)
			enable_logging(mnt->mnt_mountp, mnt->mnt_special);
		else
			disable_logging(mnt->mnt_mountp, mnt->mnt_special);
	}

	if (!qflg) {
		cmp_requested_to_actual_options(opts, mnt->mnt_mntopts,
		    mnt->mnt_special, mnt->mnt_mountp);
	}

	if (checkislog(mnt->mnt_mountp)) {
		/* update mnttab file if necessary */
		if (!mflg) {
			struct stat64 statb;
			struct mnttagdesc mtdesc;
			int fd;

			if (stat64(mnt->mnt_mountp, &statb) != 0)
				exit(32);
			/* do tag ioctl */
			mtdesc.mtd_major = major(statb.st_dev);
			mtdesc.mtd_minor = minor(statb.st_dev);
			mtdesc.mtd_mntpt = mnt->mnt_mountp;
			mtdesc.mtd_tag = MNTOPT_LOGGING;
			if ((fd = open(MNTTAB, O_RDONLY, 0)) < 0)
				exit(32);
			if (ioctl(fd, MNTIOC_SETTAG, &mtdesc) != 0) {
				(void) close(fd);
				exit(32);
			}
			(void) close(fd);
		}
	}
	exit(0);
}

/*
 * same as findopt but remove the option from the option string and return
 * true or false
 */
static int
eatmntopt(struct mnttab *mnt, char *opt)
{
	int has;

	has = (findopt(mnt->mnt_mntopts, opt) != NULL);
	rmopt(mnt, opt);
	return (has);
}

/*
 * remove an option string from the option list
 */
static void
rmopt(struct mnttab *mnt, char *opt)
{
	char *str;
	char *optstart;

	while (optstart = findopt(mnt->mnt_mntopts, opt)) {
		for (str = optstart;
		    *str != ','	&& *str != '\0' && *str != ' ';
		    str++)
			/* NULL */;
		if (*str == ',') {
			str++;
		} else if (optstart != mnt->mnt_mntopts) {
			optstart--;
		}
		while (*optstart++ = *str++)
			;
	}
}

/*
 * mnt->mnt_ops has un-eaten opts, opts is the original opts list.
 * Set mnt->mnt_opts to the original, the kernel will then remove
 * the ones it cannot deal with.
 * Set "opts" to the the original options for later comparison in
 * cmp_....().  But strip the options which aren't returned by
 * the kernel: "noglobal", "global" and "quota".
 * And strip the options which aren't set through mount: "logging",
 * "nologging" from those passed to mount(2).
 */
static void
fixopts(struct mnttab *mnt, char *opts)
{
	struct mnttab omnt;

	omnt.mnt_mntopts = opts;

	/*
	 * Options not passed to the kernel and possibly not returned;
	 * these are dealt with using ioctl; and the ioctl may fail.
	 */
	rmopt(&omnt, MNTOPT_LOGGING);
	rmopt(&omnt, MNTOPT_NOLOGGING);

	/*
	 * Set the options for ``/etc/mnttab'' to be the original
	 * options from main(); except for the option "f" and "remount".
	 */
	(void) strlcpy(mnt->mnt_mntopts, opts, MAX_MNTOPT_STR);
	rmopt(mnt, "f");
	rmopt(mnt, MNTOPT_REMOUNT);

	rmopt(&omnt, MNTOPT_GLOBAL);
	rmopt(&omnt, MNTOPT_NOGLOBAL);
	rmopt(&omnt, MNTOPT_QUOTA);
}

static void
usage(void)
{
	(void) fprintf(stdout, gettext(
"ufs usage:\n"
"mount [-F ufs] [generic options] [-o suboptions] {special | mount_point}\n"));
	(void) fprintf(stdout, gettext(
			"\tsuboptions are: \n"
			"\t	ro,rw,nosuid,remount,f,m,\n"
			"\t	global,noglobal,\n"
			"\t	largefiles,nolargefiles,\n"
			"\t	forcedirectio,noforcedirectio\n"
			"\t	logging,nologging,\n"
			"\t	nbmand,nonbmand,\n"
			"\t	onerror[={panic | lock | umount}]\n"));

	exit(32);
}

/*
 * Returns the next option in the option string.
 */
static char *
getnextopt(char **p)
{
	char *cp = *p;
	char *retstr;

	while (*cp && isspace(*cp))
		cp++;
	retstr = cp;
	while (*cp && *cp != ',')
		cp++;
	/* strip empty options */
	while (*cp == ',') {
		*cp = '\0';
		cp++;
	}
	*p = cp;
	return (retstr);
}

/*
 * "trueopt" and "falseopt" are two settings of a Boolean option.
 * If "flag" is true, forcibly set the option to the "true" setting; otherwise,
 * if the option isn't present, set it to the false setting.
 */
static void
replace_opts(char *options, int flag, char *trueopt, char *falseopt)
{
	char *f;
	char *tmpoptsp;
	int found;
	char tmptopts[MNTMAXSTR];

	(void) strcpy(tmptopts, options);
	tmpoptsp = tmptopts;
	(void) strcpy(options, "");

	found = 0;
	for (f = getnextopt(&tmpoptsp); *f; f = getnextopt(&tmpoptsp)) {
		if (options[0] != '\0')
			(void) strcat(options, ",");
		if (strcmp(f, trueopt) == 0) {
			(void) strcat(options, f);
			found++;
		} else if (strcmp(f, falseopt) == 0) {
			if (flag)
				(void) strcat(options, trueopt);
			else
				(void) strcat(options, f);
			found++;
		} else
			(void) strcat(options, f);
	}
	if (!found) {
		if (options[0] != '\0')
			(void) strcat(options, ",");
		(void) strcat(options, flag ? trueopt : falseopt);
	}
}

/*
 * "trueopt" and "falseopt" are two settings of a Boolean option and "dflt" is
 * a default value for the option.  Rewrite the contents of options to include
 * only the last mentioned occurrence of trueopt and falseopt.  If neither is
 * mentioned, append one or the other to options, according to the value of
 * dflt.  Return the resulting value of the option in boolean form.
 *
 * Note that the routine is implemented to have the resulting occurrence of
 * trueopt or falseopt appear at the end of the resulting option string.
 *
 * N.B.	This routine should take the place of replace_opts, but there are
 *	probably some compatibility issues to resolve before doing so.  It
 *	should certainly be used to handle new options that don't have
 *	compatibility issues.
 */
static int
replace_opts_dflt(
	char *options,
	int dflt,
	const char *trueopt,
	const char *falseopt)
{
	char *f;
	char *tmpoptsp;
	int last;
	char tmptopts[MNTMAXSTR];

	/*
	 * Transfer the contents of options to tmptopts, in anticipation of
	 * copying a subset of the contents back to options.
	 */
	(void) strcpy(tmptopts, options);
	tmpoptsp = tmptopts;
	(void) strcpy(options, "");

	/*
	 * Loop over each option value, copying non-matching values back into
	 * options and updating the last seen occurrence of trueopt or
	 * falseopt.
	 */
	last = dflt;
	for (f = getnextopt(&tmpoptsp); *f; f = getnextopt(&tmpoptsp)) {
		/* Check for both forms of the option of interest. */
		if (strcmp(f, trueopt) == 0) {
			last = 1;
		} else if (strcmp(f, falseopt) == 0) {
			last = 0;
		} else {
			/* Not what we're looking for; transcribe. */
			if (options[0] != '\0')
				(void) strcat(options, ",");
			(void) strcat(options, f);
		}
	}

	/*
	 * Transcribe the correct form of the option of interest, using the
	 * default value if it wasn't overwritten above.
	 */
	if (options[0] != '\0')
		(void) strcat(options, ",");
	(void) strcat(options, last ? trueopt : falseopt);

	return (last);
}

static void
rpterr(char *bs, char *mp)
{
	switch (errno) {
	case EPERM:
		(void) fprintf(stderr, gettext("%s: Insufficient privileges\n"),
		    myname);
		break;
	case ENXIO:
		(void) fprintf(stderr, gettext("%s: %s no such device\n"),
								myname, bs);
		break;
	case ENOTDIR:
		(void) fprintf(stderr,
			gettext(
	"%s: %s not a directory\n\tor a component of %s is not a directory\n"),
			myname, mp, bs);
		break;
	case ENOENT:
		(void) fprintf(stderr, gettext(
			"%s: %s or %s, no such file or directory\n"),
			myname, bs, mp);
		break;
	case EINVAL:
		(void) fprintf(stderr, gettext("%s: %s is not this fstype\n"),
			myname, bs);
		break;
	case EBUSY:
		(void) fprintf(stderr,
		    gettext("%s: %s is already mounted or %s is busy\n"),
		    myname, bs, mp);
		break;
	case ENOTBLK:
		(void) fprintf(stderr, gettext(
			"%s: %s not a block device\n"), myname, bs);
		break;
	case EROFS:
		(void) fprintf(stderr, gettext("%s: %s write-protected\n"),
			myname, bs);
		break;
	case ENOSPC:
		(void) fprintf(stderr, gettext(
			"%s: The state of %s is not okay\n"
			"\tand it was attempted to be mounted read/write\n"),
			myname, bs);
		(void) printf(gettext(
			"mount: Please run fsck and try again\n"));
		break;
	case EFBIG:
		(void) fprintf(stderr, gettext(
			"%s: Large files may be present on %s,\n"
			"\tand it was attempted to be mounted nolargefiles\n"),
		    myname, bs);
		break;
	default:
		perror(myname);
		(void) fprintf(stderr, gettext("%s: Cannot mount %s\n"),
				myname, bs);
	}
}