1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1980, 1989, 1993, 1994
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 *    may be used to endorse or promote products derived from this software
17 *    without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32#include <sys/param.h>
33#define _WANT_MNTOPTNAMES
34#include <sys/mount.h>
35#include <sys/stat.h>
36#include <sys/wait.h>
37
38#include <ctype.h>
39#include <err.h>
40#include <errno.h>
41#include <fstab.h>
42#include <paths.h>
43#include <pwd.h>
44#include <signal.h>
45#include <stdint.h>
46#include <stdio.h>
47#include <stdlib.h>
48#include <string.h>
49#include <unistd.h>
50#include <libutil.h>
51#include <libxo/xo.h>
52
53#include "extern.h"
54#include "mntopts.h"
55#include "pathnames.h"
56
57#define EXIT(a) {			\
58	xo_close_container("mount");	\
59	xo_finish();			\
60	exit(a);			\
61	}
62
63/* `meta' options */
64#define MOUNT_META_OPTION_FSTAB		"fstab"
65#define MOUNT_META_OPTION_CURRENT	"current"
66
67static int debug, fstab_style, verbose;
68
69struct cpa {
70	char	**a;
71	ssize_t	sz;
72	int	c;
73};
74
75char   *catopt(char *, const char *);
76int	hasopt(const char *, const char *);
77int	ismounted(struct fstab *, struct statfs *, int);
78int	isremountable(const char *);
79int	allow_file_mount(const char *);
80void	mangle(char *, struct cpa *);
81char   *update_options(char *, char *, int);
82int	mountfs(const char *, const char *, const char *,
83			int, const char *, const char *);
84void	remopt(char *, const char *);
85void	prmount(struct statfs *);
86void	putfsent(struct statfs *);
87void	usage(void);
88char   *flags2opts(int);
89
90/* Map from mount options to printable formats. */
91static struct mntoptnames optnames[] = {
92	MNTOPT_NAMES
93};
94
95/*
96 * List of VFS types that can be remounted without becoming mounted on top
97 * of each other.
98 * XXX Is this list correct?
99 */
100static const char *
101remountable_fs_names[] = {
102	"ufs", "ffs", "ext2fs",
103	0
104};
105
106static const char userquotaeq[] = "userquota=";
107static const char groupquotaeq[] = "groupquota=";
108
109static char *mountprog = NULL;
110
111static int
112use_mountprog(const char *vfstype)
113{
114	/* XXX: We need to get away from implementing external mount
115	 *      programs for every filesystem, and move towards having
116	 *	each filesystem properly implement the nmount() system call.
117	 */
118	unsigned int i;
119	const char *fs[] = {
120	"cd9660", "mfs", "msdosfs", "nfs",
121	"nullfs", "smbfs", "udf", "unionfs",
122	NULL
123	};
124
125	if (mountprog != NULL)
126		return (1);
127
128	for (i = 0; fs[i] != NULL; ++i) {
129		if (strcmp(vfstype, fs[i]) == 0)
130			return (1);
131	}
132
133	return (0);
134}
135
136static int
137exec_mountprog(const char *name, const char *execname, char *const argv[])
138{
139	pid_t pid;
140	int status;
141
142	switch (pid = fork()) {
143	case -1:				/* Error. */
144		xo_warn("fork");
145		EXIT(1);
146	case 0:					/* Child. */
147		/* Go find an executable. */
148		execvP(execname, _PATH_SYSPATH, argv);
149		if (errno == ENOENT) {
150			xo_warn("exec %s not found", execname);
151			if (execname[0] != '/') {
152				xo_warnx("in path: %s", _PATH_SYSPATH);
153			}
154		}
155		EXIT(1);
156	default:				/* Parent. */
157		if (waitpid(pid, &status, 0) < 0) {
158			xo_warn("waitpid");
159			return (1);
160		}
161
162		if (WIFEXITED(status)) {
163			if (WEXITSTATUS(status) != 0)
164				return (WEXITSTATUS(status));
165		} else if (WIFSIGNALED(status)) {
166			xo_warnx("%s: %s", name, sys_siglist[WTERMSIG(status)]);
167			return (1);
168		}
169		break;
170	}
171
172	return (0);
173}
174
175static int
176specified_ro(const char *arg)
177{
178	char *optbuf, *opt;
179	int ret = 0;
180
181	optbuf = strdup(arg);
182	if (optbuf == NULL)
183		 xo_err(1, "strdup failed");
184
185	for (opt = optbuf; (opt = strtok(opt, ",")) != NULL; opt = NULL) {
186		if (strcmp(opt, "ro") == 0) {
187			ret = 1;
188			break;
189		}
190	}
191	free(optbuf);
192	return (ret);
193}
194
195static void
196restart_mountd(void)
197{
198
199	pidfile_signal(_PATH_MOUNTDPID, SIGHUP, NULL);
200}
201
202int
203main(int argc, char *argv[])
204{
205	const char *mntfromname, **vfslist, *vfstype;
206	struct fstab *fs;
207	struct statfs *mntbuf;
208	int all, ch, i, init_flags, late, failok, mntsize, rval, have_fstab, ro;
209	int onlylate;
210	char *cp, *ep, *options;
211
212	all = init_flags = late = onlylate = 0;
213	ro = 0;
214	options = NULL;
215	vfslist = NULL;
216	vfstype = "ufs";
217
218	argc = xo_parse_args(argc, argv);
219	if (argc < 0)
220		exit(1);
221	xo_open_container("mount");
222
223	while ((ch = getopt(argc, argv, "adF:fLlno:prt:uvw")) != -1)
224		switch (ch) {
225		case 'a':
226			all = 1;
227			break;
228		case 'd':
229			debug = 1;
230			break;
231		case 'F':
232			setfstab(optarg);
233			break;
234		case 'f':
235			init_flags |= MNT_FORCE;
236			break;
237		case 'L':
238			onlylate = 1;
239			late = 1;
240			break;
241		case 'l':
242			late = 1;
243			break;
244		case 'n':
245			/* For compatibility with the Linux version of mount. */
246			break;
247		case 'o':
248			if (*optarg) {
249				options = catopt(options, optarg);
250				if (specified_ro(optarg))
251					ro = 1;
252			}
253			break;
254		case 'p':
255			fstab_style = 1;
256			verbose = 1;
257			break;
258		case 'r':
259			options = catopt(options, "ro");
260			ro = 1;
261			break;
262		case 't':
263			if (vfslist != NULL)
264				xo_errx(1, "only one -t option may be specified");
265			vfslist = makevfslist(optarg);
266			vfstype = optarg;
267			break;
268		case 'u':
269			init_flags |= MNT_UPDATE;
270			break;
271		case 'v':
272			verbose = 1;
273			break;
274		case 'w':
275			options = catopt(options, "noro");
276			break;
277		case '?':
278		default:
279			usage();
280			/* NOTREACHED */
281		}
282	argc -= optind;
283	argv += optind;
284
285#define	BADTYPE(type)							\
286	(strcmp(type, FSTAB_RO) &&					\
287	    strcmp(type, FSTAB_RW) && strcmp(type, FSTAB_RQ))
288
289	if ((init_flags & MNT_UPDATE) && (ro == 0))
290		options = catopt(options, "noro");
291
292	rval = 0;
293	switch (argc) {
294	case 0:
295		if ((mntsize = getmntinfo(&mntbuf,
296		     verbose ? MNT_WAIT : MNT_NOWAIT)) == 0)
297			xo_err(1, "getmntinfo");
298		if (all) {
299			while ((fs = getfsent()) != NULL) {
300				if (BADTYPE(fs->fs_type))
301					continue;
302				if (checkvfsname(fs->fs_vfstype, vfslist))
303					continue;
304				if (hasopt(fs->fs_mntops, "noauto"))
305					continue;
306				if (!hasopt(fs->fs_mntops, "late") && onlylate)
307					continue;
308				if (hasopt(fs->fs_mntops, "late") && !late)
309					continue;
310				if (hasopt(fs->fs_mntops, "failok"))
311					failok = 1;
312				else
313					failok = 0;
314				if (!(init_flags & MNT_UPDATE) &&
315				    !hasopt(fs->fs_mntops, "update") &&
316				    ismounted(fs, mntbuf, mntsize))
317					continue;
318				options = update_options(options, fs->fs_mntops,
319				    mntbuf->f_flags);
320				if (mountfs(fs->fs_vfstype, fs->fs_spec,
321				    fs->fs_file, init_flags, options,
322				    fs->fs_mntops) && !failok)
323					rval = 1;
324			}
325		} else if (fstab_style) {
326			xo_open_list("fstab");
327			for (i = 0; i < mntsize; i++) {
328				if (checkvfsname(mntbuf[i].f_fstypename, vfslist))
329					continue;
330				xo_open_instance("fstab");
331				putfsent(&mntbuf[i]);
332				xo_close_instance("fstab");
333			}
334			xo_close_list("fstab");
335		} else {
336			xo_open_list("mounted");
337			for (i = 0; i < mntsize; i++) {
338				if (checkvfsname(mntbuf[i].f_fstypename,
339				    vfslist))
340					continue;
341				if (!verbose &&
342				    (mntbuf[i].f_flags & MNT_IGNORE) != 0)
343					continue;
344				xo_open_instance("mounted");
345				prmount(&mntbuf[i]);
346				xo_close_instance("mounted");
347			}
348			xo_close_list("mounted");
349		}
350		EXIT(rval);
351	case 1:
352		if (vfslist != NULL)
353			usage();
354
355		rmslashes(*argv, *argv);
356		if (init_flags & MNT_UPDATE) {
357			mntfromname = NULL;
358			have_fstab = 0;
359			if ((mntbuf = getmntpoint(*argv)) == NULL)
360				xo_errx(1, "not currently mounted %s", *argv);
361			/*
362			 * Only get the mntflags from fstab if both mntpoint
363			 * and mntspec are identical. Also handle the special
364			 * case where just '/' is mounted and 'spec' is not
365			 * identical with the one from fstab ('/dev' is missing
366			 * in the spec-string at boot-time).
367			 */
368			if ((fs = getfsfile(mntbuf->f_mntonname)) != NULL) {
369				if (strcmp(fs->fs_spec,
370				    mntbuf->f_mntfromname) == 0 &&
371				    strcmp(fs->fs_file,
372				    mntbuf->f_mntonname) == 0) {
373					have_fstab = 1;
374					mntfromname = mntbuf->f_mntfromname;
375				} else if (argv[0][0] == '/' &&
376				    argv[0][1] == '\0' &&
377				    strcmp(fs->fs_vfstype,
378				    mntbuf->f_fstypename) == 0) {
379					fs = getfsfile("/");
380					have_fstab = 1;
381					mntfromname = fs->fs_spec;
382				}
383			}
384			if (have_fstab) {
385				options = update_options(options, fs->fs_mntops,
386				    mntbuf->f_flags);
387			} else {
388				mntfromname = mntbuf->f_mntfromname;
389				options = update_options(options, NULL,
390				    mntbuf->f_flags);
391			}
392			rval = mountfs(mntbuf->f_fstypename, mntfromname,
393			    mntbuf->f_mntonname, init_flags, options, 0);
394			break;
395		}
396		if ((fs = getfsfile(*argv)) == NULL &&
397		    (fs = getfsspec(*argv)) == NULL)
398			xo_errx(1, "%s: unknown special file or file system",
399			    *argv);
400		if (BADTYPE(fs->fs_type))
401			xo_errx(1, "%s has unknown file system type",
402			    *argv);
403		rval = mountfs(fs->fs_vfstype, fs->fs_spec, fs->fs_file,
404		    init_flags, options, fs->fs_mntops);
405		break;
406	case 2:
407		/*
408		 * If -t flag has not been specified, the path cannot be
409		 * found, spec contains either a ':' or a '@', then assume
410		 * that an NFS file system is being specified ala Sun.
411		 * Check if the hostname contains only allowed characters
412		 * to reduce false positives.  IPv6 addresses containing
413		 * ':' will be correctly parsed only if the separator is '@'.
414		 * The definition of a valid hostname is taken from RFC 1034.
415		 */
416		if (vfslist == NULL && ((ep = strchr(argv[0], '@')) != NULL ||
417		    (ep = strchr(argv[0], ':')) != NULL)) {
418			if (*ep == '@') {
419				cp = ep + 1;
420				ep = cp + strlen(cp);
421			} else
422				cp = argv[0];
423			while (cp != ep) {
424				if (!isdigit(*cp) && !isalpha(*cp) &&
425				    *cp != '.' && *cp != '-' && *cp != ':')
426					break;
427				cp++;
428			}
429			if (cp == ep)
430				vfstype = "nfs";
431		}
432		rval = mountfs(vfstype,
433		    argv[0], argv[1], init_flags, options, NULL);
434		break;
435	default:
436		usage();
437		/* NOTREACHED */
438	}
439
440	/*
441	 * If the mount was successfully, and done by root, tell mountd the
442	 * good news.
443	 */
444	if (rval == 0 && getuid() == 0)
445		restart_mountd();
446
447	EXIT(rval);
448}
449
450int
451ismounted(struct fstab *fs, struct statfs *mntbuf, int mntsize)
452{
453	char realfsfile[PATH_MAX];
454	int i;
455
456	if (fs->fs_file[0] == '/' && fs->fs_file[1] == '\0')
457		/* the root file system can always be remounted */
458		return (0);
459
460	/* The user may have specified a symlink in fstab, resolve the path */
461	if (realpath(fs->fs_file, realfsfile) == NULL) {
462		/* Cannot resolve the path, use original one */
463		strlcpy(realfsfile, fs->fs_file, sizeof(realfsfile));
464	}
465
466	/*
467	 * Consider the filesystem to be mounted if:
468	 * It has the same mountpoint as a mounted filesystem, and
469	 * It has the same type as that same mounted filesystem, and
470	 * It has the same device name as that same mounted filesystem, OR
471	 *     It is a nonremountable filesystem
472	 */
473	for (i = mntsize - 1; i >= 0; --i)
474		if (strcmp(realfsfile, mntbuf[i].f_mntonname) == 0 &&
475		    strcmp(fs->fs_vfstype, mntbuf[i].f_fstypename) == 0 &&
476		    (!isremountable(fs->fs_vfstype) ||
477		     (strcmp(fs->fs_spec, mntbuf[i].f_mntfromname) == 0)))
478			return (1);
479	return (0);
480}
481
482int
483isremountable(const char *vfsname)
484{
485	const char **cp;
486
487	for (cp = remountable_fs_names; *cp; cp++)
488		if (strcmp(*cp, vfsname) == 0)
489			return (1);
490	return (0);
491}
492
493int
494allow_file_mount(const char *vfsname)
495{
496
497	if (strcmp(vfsname, "nullfs") == 0)
498		return (1);
499	return (0);
500}
501
502int
503hasopt(const char *mntopts, const char *option)
504{
505	int negative, found;
506	char *opt, *optbuf;
507
508	if (option[0] == 'n' && option[1] == 'o') {
509		negative = 1;
510		option += 2;
511	} else
512		negative = 0;
513	optbuf = strdup(mntopts);
514	found = 0;
515	for (opt = optbuf; (opt = strtok(opt, ",")) != NULL; opt = NULL) {
516		if (opt[0] == 'n' && opt[1] == 'o') {
517			if (!strcasecmp(opt + 2, option))
518				found = negative;
519		} else if (!strcasecmp(opt, option))
520			found = !negative;
521	}
522	free(optbuf);
523	return (found);
524}
525
526static void
527append_arg(struct cpa *sa, char *arg)
528{
529	if (sa->c + 1 == sa->sz) {
530		sa->sz = sa->sz == 0 ? 8 : sa->sz * 2;
531		sa->a = realloc(sa->a, sizeof(*sa->a) * sa->sz);
532		if (sa->a == NULL)
533			xo_errx(1, "realloc failed");
534	}
535	sa->a[++sa->c] = arg;
536}
537
538int
539mountfs(const char *vfstype, const char *spec, const char *name, int flags,
540	const char *options, const char *mntopts)
541{
542	struct statfs sf;
543	int i, ret;
544	char *optbuf, execname[PATH_MAX], mntpath[PATH_MAX];
545	static struct cpa mnt_argv;
546
547	/* resolve the mountpoint with realpath(3) */
548	if (allow_file_mount(vfstype)) {
549		if (checkpath_allow_file(name, mntpath) != 0) {
550			xo_warn("%s", mntpath);
551			return (1);
552		}
553	} else {
554		if (checkpath(name, mntpath) != 0) {
555			xo_warn("%s", mntpath);
556			return (1);
557		}
558	}
559	name = mntpath;
560
561	if (mntopts == NULL)
562		mntopts = "";
563	optbuf = catopt(strdup(mntopts), options);
564
565	if (strcmp(name, "/") == 0)
566		flags |= MNT_UPDATE;
567	if (flags & MNT_FORCE)
568		optbuf = catopt(optbuf, "force");
569	if (flags & MNT_RDONLY)
570		optbuf = catopt(optbuf, "ro");
571	/*
572	 * XXX
573	 * The mount_mfs (newfs) command uses -o to select the
574	 * optimization mode.  We don't pass the default "-o rw"
575	 * for that reason.
576	 */
577	if (flags & MNT_UPDATE)
578		optbuf = catopt(optbuf, "update");
579
580	/* Compatibility glue. */
581	if (strcmp(vfstype, "msdos") == 0)
582		vfstype = "msdosfs";
583
584	/* Construct the name of the appropriate mount command */
585	(void)snprintf(execname, sizeof(execname), "mount_%s", vfstype);
586
587	mnt_argv.c = -1;
588	append_arg(&mnt_argv, execname);
589	mangle(optbuf, &mnt_argv);
590	if (mountprog != NULL)
591		strlcpy(execname, mountprog, sizeof(execname));
592
593	append_arg(&mnt_argv, strdup(spec));
594	append_arg(&mnt_argv, strdup(name));
595	append_arg(&mnt_argv, NULL);
596
597	if (debug) {
598		if (use_mountprog(vfstype))
599			xo_emit("{Lwc:exec}{:execname/%s}", execname);
600		else
601			xo_emit("{:execname/mount}{P: }{l:opts/-t}{P: }{l:opts/%s}", vfstype);
602		for (i = 1; i < mnt_argv.c; i++)
603			xo_emit("{P: }{l:opts}", mnt_argv.a[i]);
604		xo_emit("\n");
605		free(optbuf);
606		free(mountprog);
607		mountprog = NULL;
608		return (0);
609	}
610
611	if (use_mountprog(vfstype)) {
612		ret = exec_mountprog(name, execname, mnt_argv.a);
613	} else {
614		ret = mount_fs(vfstype, mnt_argv.c, mnt_argv.a);
615	}
616
617	free(optbuf);
618	free(mountprog);
619	mountprog = NULL;
620
621	if (verbose) {
622		if (statfs(name, &sf) < 0) {
623			xo_warn("statfs %s", name);
624			return (1);
625		}
626		if (fstab_style) {
627			xo_open_list("fstab");
628			xo_open_instance("fstab");
629			putfsent(&sf);
630			xo_close_instance("fstab");
631			xo_close_list("fstab");
632		} else {
633			xo_open_list("mounted");
634			xo_open_instance("mounted");
635			prmount(&sf);
636			xo_close_instance("mounted");
637			xo_close_list("mounted");
638		}
639	}
640
641	return (ret);
642}
643
644void
645prmount(struct statfs *sfp)
646{
647	uint64_t flags;
648	unsigned int i;
649	struct mntoptnames *o;
650	struct passwd *pw;
651	char *fsidbuf;
652
653	xo_emit("{:special/%hs}{L: on }{:node/%hs}{L: (}{:fstype}", sfp->f_mntfromname,
654	    sfp->f_mntonname, sfp->f_fstypename);
655
656	flags = sfp->f_flags & MNT_VISFLAGMASK;
657	for (o = optnames; flags != 0 && o->o_opt != 0; o++)
658		if (flags & o->o_opt) {
659			xo_emit("{D:, }{l:opts}", o->o_name);
660			flags &= ~o->o_opt;
661		}
662	/*
663	 * Inform when file system is mounted by an unprivileged user
664	 * or privileged non-root user.
665	 */
666	if ((flags & MNT_USER) != 0 || sfp->f_owner != 0) {
667		xo_emit("{D:, }{L:mounted by }");
668		if ((pw = getpwuid(sfp->f_owner)) != NULL)
669			xo_emit("{:mounter/%hs}", pw->pw_name);
670		else
671			xo_emit("{:mounter/%hs}", sfp->f_owner);
672	}
673	if (verbose) {
674		if (sfp->f_syncwrites != 0 || sfp->f_asyncwrites != 0) {
675			xo_open_container("writes");
676			xo_emit("{D:, }{Lwc:writes}{Lw:sync}{w:sync/%ju}{Lw:async}{:async/%ju}",
677			    (uintmax_t)sfp->f_syncwrites,
678			    (uintmax_t)sfp->f_asyncwrites);
679			xo_close_container("writes");
680		}
681		if (sfp->f_syncreads != 0 || sfp->f_asyncreads != 0) {
682			xo_open_container("reads");
683			xo_emit("{D:, }{Lwc:reads}{Lw:sync}{w:sync/%ju}{Lw:async}{:async/%ju}",
684			    (uintmax_t)sfp->f_syncreads,
685			    (uintmax_t)sfp->f_asyncreads);
686			xo_close_container("reads");
687		}
688		if (sfp->f_fsid.val[0] != 0 || sfp->f_fsid.val[1] != 0) {
689			fsidbuf = malloc(sizeof(sfp->f_fsid) * 2 + 1);
690			if (fsidbuf == NULL)
691				xo_errx(1, "malloc failed");
692			for (i = 0; i < sizeof(sfp->f_fsid); i++)
693				sprintf(&fsidbuf[i * 2], "%02x",
694				    ((u_char *)&sfp->f_fsid)[i]);
695			fsidbuf[i * 2] = '\0';
696			xo_emit("{D:, }{Lw:fsid}{:fsid}", fsidbuf);
697			free(fsidbuf);
698		}
699		if (sfp->f_nvnodelistsize != 0) {
700			xo_open_container("vnodes");
701			xo_emit("{D:, }{Lwc:vnodes}{Lw:count}{w:count/%ju}",
702			    (uintmax_t)sfp->f_nvnodelistsize);
703			xo_close_container("vnodes");
704		}
705	}
706	xo_emit("{D:)}\n");
707}
708
709char *
710catopt(char *s0, const char *s1)
711{
712	char *cp;
713
714	if (s1 == NULL || *s1 == '\0')
715		return (s0);
716
717	if (s0 && *s0) {
718		if (asprintf(&cp, "%s,%s", s0, s1) == -1)
719			xo_errx(1, "asprintf failed");
720	} else
721		cp = strdup(s1);
722
723	if (s0)
724		free(s0);
725	return (cp);
726}
727
728void
729mangle(char *options, struct cpa *a)
730{
731	char *p, *s, *val;
732
733	for (s = options; (p = strsep(&s, ",")) != NULL;)
734		if (*p != '\0') {
735			if (strcmp(p, "noauto") == 0) {
736				/*
737				 * Do not pass noauto option to nmount().
738				 * or external mount program.  noauto is
739				 * only used to prevent mounting a filesystem
740				 * when 'mount -a' is specified, and is
741				 * not a real mount option.
742				 */
743				continue;
744			} else if (strcmp(p, "late") == 0) {
745				/*
746				 * "late" is used to prevent certain file
747				 * systems from being mounted before late
748				 * in the boot cycle; for instance,
749				 * loopback NFS mounts can't be mounted
750				 * before mountd starts.
751				 */
752				continue;
753			} else if (strcmp(p, "failok") == 0) {
754				/*
755				 * "failok" is used to prevent certain file
756				 * systems from being causing the system to
757				 * drop into single user mode in the boot
758				 * cycle, and is not a real mount option.
759				 */
760				continue;
761			} else if (strncmp(p, "mountprog", 9) == 0) {
762				/*
763				 * "mountprog" is used to force the use of
764				 * userland mount programs.
765				 */
766				val = strchr(p, '=');
767                        	if (val != NULL) {
768                                	++val;
769					if (*val != '\0')
770						mountprog = strdup(val);
771				}
772
773				if (mountprog == NULL) {
774					xo_errx(1, "Need value for -o mountprog");
775				}
776				continue;
777			} else if (strcmp(p, "userquota") == 0) {
778				continue;
779			} else if (strncmp(p, userquotaeq,
780			    sizeof(userquotaeq) - 1) == 0) {
781				continue;
782			} else if (strcmp(p, "groupquota") == 0) {
783				continue;
784			} else if (strncmp(p, groupquotaeq,
785			    sizeof(groupquotaeq) - 1) == 0) {
786				continue;
787			} else if (*p == '-') {
788				append_arg(a, p);
789				p = strchr(p, '=');
790				if (p != NULL) {
791					*p = '\0';
792					append_arg(a, p + 1);
793				}
794			} else {
795				append_arg(a, strdup("-o"));
796				append_arg(a, p);
797			}
798		}
799}
800
801
802char *
803update_options(char *opts, char *fstab, int curflags)
804{
805	char *o, *p;
806	char *cur;
807	char *expopt, *newopt, *tmpopt;
808
809	if (opts == NULL)
810		return (strdup(""));
811
812	/* remove meta options from list */
813	remopt(fstab, MOUNT_META_OPTION_FSTAB);
814	remopt(fstab, MOUNT_META_OPTION_CURRENT);
815	cur = flags2opts(curflags);
816
817	/*
818	 * Expand all meta-options passed to us first.
819	 */
820	expopt = NULL;
821	for (p = opts; (o = strsep(&p, ",")) != NULL;) {
822		if (strcmp(MOUNT_META_OPTION_FSTAB, o) == 0)
823			expopt = catopt(expopt, fstab);
824		else if (strcmp(MOUNT_META_OPTION_CURRENT, o) == 0)
825			expopt = catopt(expopt, cur);
826		else
827			expopt = catopt(expopt, o);
828	}
829	free(cur);
830	free(opts);
831
832	/*
833	 * Remove previous contradictory arguments. Given option "foo" we
834	 * remove all the "nofoo" options. Given "nofoo" we remove "nonofoo"
835	 * and "foo" - so we can deal with possible options like "notice".
836	 */
837	newopt = NULL;
838	for (p = expopt; (o = strsep(&p, ",")) != NULL;) {
839		if ((tmpopt = malloc( strlen(o) + 2 + 1 )) == NULL)
840			xo_errx(1, "malloc failed");
841
842		strcpy(tmpopt, "no");
843		strcat(tmpopt, o);
844		remopt(newopt, tmpopt);
845		free(tmpopt);
846
847		if (strncmp("no", o, 2) == 0)
848			remopt(newopt, o+2);
849
850		newopt = catopt(newopt, o);
851	}
852	free(expopt);
853
854	return (newopt);
855}
856
857void
858remopt(char *string, const char *opt)
859{
860	char *o, *p, *r;
861
862	if (string == NULL || *string == '\0' || opt == NULL || *opt == '\0')
863		return;
864
865	r = string;
866
867	for (p = string; (o = strsep(&p, ",")) != NULL;) {
868		if (strcmp(opt, o) != 0) {
869			if (*r == ',' && *o != '\0')
870				r++;
871			while ((*r++ = *o++) != '\0')
872			    ;
873			*--r = ',';
874		}
875	}
876	*r = '\0';
877}
878
879void
880usage(void)
881{
882
883	xo_error("%s\n%s\n%s\n",
884"usage: mount [-adflpruvw] [-F fstab] [-o options] [-t ufs | external_type]",
885"       mount [-dfpruvw] special | node",
886"       mount [-dfpruvw] [-o options] [-t ufs | external_type] special node");
887	EXIT(1);
888}
889
890void
891putfsent(struct statfs *ent)
892{
893	struct fstab *fst;
894	char *opts, *rw;
895	int l;
896
897	opts = NULL;
898	/* flags2opts() doesn't return the "rw" option. */
899	if ((ent->f_flags & MNT_RDONLY) != 0)
900		rw = NULL;
901	else
902		rw = catopt(NULL, "rw");
903
904	opts = flags2opts(ent->f_flags);
905	opts = catopt(rw, opts);
906
907	if (strncmp(ent->f_mntfromname, "<below>", 7) == 0 ||
908	    strncmp(ent->f_mntfromname, "<above>", 7) == 0) {
909		strlcpy(ent->f_mntfromname,
910		    (strnstr(ent->f_mntfromname, ":", 8) +1),
911		    sizeof(ent->f_mntfromname));
912	}
913
914	l = strlen(ent->f_mntfromname);
915	xo_emit("{:device}{P:/%s}{P:/%s}{P:/%s}",
916	    ent->f_mntfromname,
917	    l < 8 ? "\t" : "",
918	    l < 16 ? "\t" : "",
919	    l < 24 ? "\t" : " ");
920	l = strlen(ent->f_mntonname);
921	xo_emit("{:mntpoint}{P:/%s}{P:/%s}{P:/%s}",
922	    ent->f_mntonname,
923	    l < 8 ? "\t" : "",
924	    l < 16 ? "\t" : "",
925	    l < 24 ? "\t" : " ");
926	xo_emit("{:fstype}{P:\t}", ent->f_fstypename);
927	l = strlen(opts);
928	xo_emit("{:opts}{P:/%s}", opts,
929	    l < 8 ? "\t" : " ");
930	free(opts);
931
932	if ((fst = getfsspec(ent->f_mntfromname)))
933		xo_emit("{P:\t}{n:dump/%u}{P: }{n:pass/%u}\n",
934		    fst->fs_freq, fst->fs_passno);
935	else if ((fst = getfsfile(ent->f_mntonname)))
936		xo_emit("{P:\t}{n:dump/%u}{P: }{n:pass/%u}\n",
937		    fst->fs_freq, fst->fs_passno);
938	else if (strcmp(ent->f_fstypename, "ufs") == 0) {
939		if (strcmp(ent->f_mntonname, "/") == 0)
940			xo_emit("{P:\t}{n:dump/1}{P: }{n:pass/1}\n");
941		else
942			xo_emit("{P:\t}{n:dump/2}{P: }{n:pass/2}\n");
943	} else
944		xo_emit("{P:\t}{n:dump/0}{P: }{n:pass/0}\n");
945}
946
947
948char *
949flags2opts(int flags)
950{
951	char *res;
952
953	res = NULL;
954
955	if (flags & MNT_RDONLY)		res = catopt(res, "ro");
956	if (flags & MNT_SYNCHRONOUS)	res = catopt(res, "sync");
957	if (flags & MNT_NOEXEC)		res = catopt(res, "noexec");
958	if (flags & MNT_NOSUID)		res = catopt(res, "nosuid");
959	if (flags & MNT_UNION)		res = catopt(res, "union");
960	if (flags & MNT_ASYNC)		res = catopt(res, "async");
961	if (flags & MNT_NOATIME)	res = catopt(res, "noatime");
962	if (flags & MNT_NOCLUSTERR)	res = catopt(res, "noclusterr");
963	if (flags & MNT_NOCLUSTERW)	res = catopt(res, "noclusterw");
964	if (flags & MNT_NOSYMFOLLOW)	res = catopt(res, "nosymfollow");
965	if (flags & MNT_SUIDDIR)	res = catopt(res, "suiddir");
966	if (flags & MNT_MULTILABEL)	res = catopt(res, "multilabel");
967	if (flags & MNT_ACLS)		res = catopt(res, "acls");
968	if (flags & MNT_NFS4ACLS)	res = catopt(res, "nfsv4acls");
969	if (flags & MNT_UNTRUSTED)	res = catopt(res, "untrusted");
970	if (flags & MNT_NOCOVER)	res = catopt(res, "nocover");
971	if (flags & MNT_EMPTYDIR)	res = catopt(res, "emptydir");
972
973	return (res);
974}
975