root/usr/src/cmd/fs.d/ufs/mount/mount.c
/*
 * 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 2009 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.
 */

/*
 * 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(8)"
                " or metaclear(8).\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(8).\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(8).\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(8).\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(8).\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(8).\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] != '\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] != '\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);
        }
}