1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2001 Dima Dorfman.
5 * 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 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29/*
30 * mdmfs (md/MFS) is a wrapper around mdconfig(8),
31 * newfs(8), and mount(8) that mimics the command line option set of
32 * the deprecated mount_mfs(8).
33 */
34
35#include <sys/cdefs.h>
36__FBSDID("$FreeBSD$");
37
38#include <sys/param.h>
39#include <sys/linker.h>
40#include <sys/mdioctl.h>
41#include <sys/module.h>
42#include <sys/mount.h>
43#include <sys/stat.h>
44#include <sys/wait.h>
45
46#include <assert.h>
47#include <err.h>
48#include <errno.h>
49#include <fcntl.h>
50#include <grp.h>
51#include <inttypes.h>
52#include <paths.h>
53#include <pwd.h>
54#include <stdarg.h>
55#include <stdio.h>
56#include <stdlib.h>
57#include <string.h>
58#include <ctype.h>
59#include <unistd.h>
60
61typedef enum { false, true } bool;
62
63struct mtpt_info {
64	uid_t		 mi_uid;
65	bool		 mi_have_uid;
66	gid_t		 mi_gid;
67	bool		 mi_have_gid;
68	mode_t		 mi_mode;
69	bool		 mi_have_mode;
70	bool		 mi_forced_pw;
71};
72
73static	bool debug;		/* Emit debugging information? */
74static	bool loudsubs;		/* Suppress output from helper programs? */
75static	bool norun;		/* Actually run the helper programs? */
76static	int unit;      		/* The unit we're working with. */
77static	const char *mdname;	/* Name of memory disk device (e.g., "md"). */
78static	const char *mdsuffix;	/* Suffix of memory disk device (e.g., ".uzip"). */
79static	size_t mdnamelen;	/* Length of mdname. */
80static	const char *path_mdconfig =_PATH_MDCONFIG;
81
82static void	 argappend(char **, const char *, ...) __printflike(2, 3);
83static void	 debugprintf(const char *, ...) __printflike(1, 2);
84static void	 do_mdconfig_attach(const char *, const enum md_types);
85static void	 do_mdconfig_attach_au(const char *, const enum md_types);
86static void	 do_mdconfig_detach(void);
87static void	 do_mount_md(const char *, const char *);
88static void	 do_mount_tmpfs(const char *, const char *);
89static void	 do_mtptsetup(const char *, struct mtpt_info *);
90static void	 do_newfs(const char *);
91static void	 do_copy(const char *, const char *);
92static void	 extract_ugid(const char *, struct mtpt_info *);
93static int	 run(int *, const char *, ...) __printflike(2, 3);
94static void	 usage(void);
95
96int
97main(int argc, char **argv)
98{
99	struct mtpt_info mi;		/* Mountpoint info. */
100	intmax_t mdsize;
101	char *mdconfig_arg, *newfs_arg,	/* Args to helper programs. */
102	    *mount_arg;
103	enum md_types mdtype;		/* The type of our memory disk. */
104	bool have_mdtype, mlmac;
105	bool detach, softdep, autounit, newfs;
106	const char *mtpoint, *size_arg, *skel, *unitstr;
107	char *p;
108	int ch, idx;
109	void *set;
110	unsigned long ul;
111
112	/* Misc. initialization. */
113	(void)memset(&mi, '\0', sizeof(mi));
114	detach = true;
115	softdep = true;
116	autounit = false;
117	mlmac = false;
118	newfs = true;
119	have_mdtype = false;
120	skel = NULL;
121	mdtype = MD_SWAP;
122	mdname = MD_NAME;
123	mdnamelen = strlen(mdname);
124	mdsize = 0;
125	/*
126	 * Can't set these to NULL.  They may be passed to the
127	 * respective programs without modification.  I.e., we may not
128	 * receive any command-line options which will caused them to
129	 * be modified.
130	 */
131	mdconfig_arg = strdup("");
132	newfs_arg = strdup("");
133	mount_arg = strdup("");
134	size_arg = NULL;
135
136	/* If we were started as mount_mfs or mfs, imply -C. */
137	if (strcmp(getprogname(), "mount_mfs") == 0 ||
138	    strcmp(getprogname(), "mfs") == 0) {
139		/* Make compatibility assumptions. */
140		mi.mi_mode = 01777;
141		mi.mi_have_mode = true;
142	}
143
144	while ((ch = getopt(argc, argv,
145	    "a:b:Cc:Dd:E:e:F:f:hi:k:LlMm:NnO:o:Pp:Ss:tT:Uv:w:X")) != -1)
146		switch (ch) {
147		case 'a':
148			argappend(&newfs_arg, "-a %s", optarg);
149			break;
150		case 'b':
151			argappend(&newfs_arg, "-b %s", optarg);
152			break;
153		case 'C':
154			/* Ignored for compatibility. */
155			break;
156		case 'c':
157			argappend(&newfs_arg, "-c %s", optarg);
158			break;
159		case 'D':
160			detach = false;
161			break;
162		case 'd':
163			argappend(&newfs_arg, "-d %s", optarg);
164			break;
165		case 'E':
166			path_mdconfig = optarg;
167			break;
168		case 'e':
169			argappend(&newfs_arg, "-e %s", optarg);
170			break;
171		case 'F':
172			if (have_mdtype)
173				usage();
174			mdtype = MD_VNODE;
175			have_mdtype = true;
176			argappend(&mdconfig_arg, "-f %s", optarg);
177			break;
178		case 'f':
179			argappend(&newfs_arg, "-f %s", optarg);
180			break;
181		case 'h':
182			usage();
183			break;
184		case 'i':
185			argappend(&newfs_arg, "-i %s", optarg);
186			break;
187		case 'k':
188			skel = optarg;
189			break;
190		case 'L':
191			loudsubs = true;
192			break;
193		case 'l':
194			mlmac = true;
195			argappend(&newfs_arg, "-l");
196			break;
197		case 'M':
198			if (have_mdtype)
199				usage();
200			mdtype = MD_MALLOC;
201			have_mdtype = true;
202			argappend(&mdconfig_arg, "-o reserve");
203			break;
204		case 'm':
205			argappend(&newfs_arg, "-m %s", optarg);
206			break;
207		case 'N':
208			norun = true;
209			break;
210		case 'n':
211			argappend(&newfs_arg, "-n");
212			break;
213		case 'O':
214			argappend(&newfs_arg, "-o %s", optarg);
215			break;
216		case 'o':
217			argappend(&mount_arg, "-o %s", optarg);
218			break;
219		case 'P':
220			newfs = false;
221			break;
222		case 'p':
223			if ((set = setmode(optarg)) == NULL)
224				usage();
225			mi.mi_mode = getmode(set, S_IRWXU | S_IRWXG | S_IRWXO);
226			mi.mi_have_mode = true;
227			mi.mi_forced_pw = true;
228			free(set);
229			break;
230		case 'S':
231			softdep = false;
232			break;
233		case 's':
234			size_arg = optarg;
235			break;
236		case 't':
237			argappend(&newfs_arg, "-t");
238			break;
239		case 'T':
240			argappend(&mount_arg, "-t %s", optarg);
241			break;
242		case 'U':
243			softdep = true;
244			break;
245		case 'v':
246			argappend(&newfs_arg, "-O %s", optarg);
247			break;
248		case 'w':
249			extract_ugid(optarg, &mi);
250			mi.mi_forced_pw = true;
251			break;
252		case 'X':
253			debug = true;
254			break;
255		default:
256			usage();
257		}
258	argc -= optind;
259	argv += optind;
260	if (argc < 2)
261		usage();
262
263	/*
264	 * Historically our size arg was passed directly to mdconfig, which
265	 * treats a number without a suffix as a count of 512-byte sectors;
266	 * tmpfs would treat it as a count of bytes.  To get predictable
267	 * behavior for 'auto' we document that the size always uses mdconfig
268	 * rules.  To make that work, decode the size here so it can be passed
269	 * to either tmpfs or mdconfig as a count of bytes.
270	 */
271	if (size_arg != NULL) {
272		mdsize = (intmax_t)strtoumax(size_arg, &p, 0);
273		if (p == size_arg || (p[0] != 0 && p[1] != 0) || mdsize < 0)
274			errx(1, "invalid size '%s'", size_arg);
275		switch (*p) {
276		case 'p':
277		case 'P':
278			mdsize *= 1024;
279		case 't':
280		case 'T':
281			mdsize *= 1024;
282		case 'g':
283		case 'G':
284			mdsize *= 1024;
285		case 'm':
286		case 'M':
287			mdsize *= 1024;
288		case 'k':
289		case 'K':
290			mdsize *= 1024;
291		case 'b':
292		case 'B':
293			break;
294		case '\0':
295			mdsize *= 512;
296			break;
297		default:
298			errx(1, "invalid size suffix on '%s'", size_arg);
299		}
300	}
301
302	/*
303	 * Based on the command line 'md-device' either mount a tmpfs filesystem
304	 * or configure the md device then format and mount a filesystem on it.
305	 * If the device is 'auto' use tmpfs if it is available and there is no
306	 * request for multilabel MAC (which tmpfs does not support).
307	 */
308	unitstr = argv[0];
309	mtpoint = argv[1];
310
311	if (strcmp(unitstr, "auto") == 0) {
312		if (mlmac)
313			idx = -1; /* Must use md for mlmac. */
314		else if ((idx = modfind("tmpfs")) == -1)
315			idx = kldload("tmpfs");
316		if (idx == -1)
317			unitstr = "md";
318		else
319			unitstr = "tmpfs";
320	}
321
322	if (strcmp(unitstr, "tmpfs") == 0) {
323		if (size_arg != NULL && mdsize != 0)
324			argappend(&mount_arg, "-o size=%jd", mdsize);
325		do_mount_tmpfs(mount_arg, mtpoint);
326	} else {
327		if (size_arg != NULL)
328			argappend(&mdconfig_arg, "-s %jdB", mdsize);
329		if (strncmp(unitstr, "/dev/", 5) == 0)
330			unitstr += 5;
331		if (strncmp(unitstr, mdname, mdnamelen) == 0)
332			unitstr += mdnamelen;
333		if (!isdigit(*unitstr)) {
334			autounit = true;
335			unit = -1;
336			mdsuffix = unitstr;
337		} else {
338			ul = strtoul(unitstr, &p, 10);
339			if (ul == ULONG_MAX)
340				errx(1, "bad device unit: %s", unitstr);
341			unit = ul;
342			mdsuffix = p;	/* can be empty */
343		}
344
345		if (!have_mdtype)
346			mdtype = MD_SWAP;
347		if (softdep)
348			argappend(&newfs_arg, "-U");
349		if (mdtype != MD_VNODE && !newfs)
350			errx(1, "-P requires a vnode-backed disk");
351
352		/* Do the work. */
353		if (detach && !autounit)
354			do_mdconfig_detach();
355		if (autounit)
356			do_mdconfig_attach_au(mdconfig_arg, mdtype);
357		else
358			do_mdconfig_attach(mdconfig_arg, mdtype);
359		if (newfs)
360			do_newfs(newfs_arg);
361		do_mount_md(mount_arg, mtpoint);
362	}
363
364	do_mtptsetup(mtpoint, &mi);
365	if (skel != NULL)
366		do_copy(mtpoint, skel);
367
368	return (0);
369}
370
371/*
372 * Append the expansion of 'fmt' to the buffer pointed to by '*dstp';
373 * reallocate as required.
374 */
375static void
376argappend(char **dstp, const char *fmt, ...)
377{
378	char *old, *new;
379	va_list ap;
380
381	old = *dstp;
382	assert(old != NULL);
383
384	va_start(ap, fmt);
385	if (vasprintf(&new, fmt,ap) == -1)
386		errx(1, "vasprintf");
387	va_end(ap);
388
389	*dstp = new;
390	if (asprintf(&new, "%s %s", old, new) == -1)
391		errx(1, "asprintf");
392	free(*dstp);
393	free(old);
394
395	*dstp = new;
396}
397
398/*
399 * If run-time debugging is enabled, print the expansion of 'fmt'.
400 * Otherwise, do nothing.
401 */
402static void
403debugprintf(const char *fmt, ...)
404{
405	va_list ap;
406
407	if (!debug)
408		return;
409	fprintf(stderr, "DEBUG: ");
410	va_start(ap, fmt);
411	vfprintf(stderr, fmt, ap);
412	va_end(ap);
413	fprintf(stderr, "\n");
414	fflush(stderr);
415}
416
417/*
418 * Attach a memory disk with a known unit.
419 */
420static void
421do_mdconfig_attach(const char *args, const enum md_types mdtype)
422{
423	int rv;
424	const char *ta;		/* Type arg. */
425
426	switch (mdtype) {
427	case MD_SWAP:
428		ta = "-t swap";
429		break;
430	case MD_VNODE:
431		ta = "-t vnode";
432		break;
433	case MD_MALLOC:
434		ta = "-t malloc";
435		break;
436	default:
437		abort();
438	}
439	rv = run(NULL, "%s -a %s%s -u %s%d", path_mdconfig, ta, args,
440	    mdname, unit);
441	if (rv)
442		errx(1, "mdconfig (attach) exited with error code %d", rv);
443}
444
445/*
446 * Attach a memory disk with an unknown unit; use autounit.
447 */
448static void
449do_mdconfig_attach_au(const char *args, const enum md_types mdtype)
450{
451	const char *ta;		/* Type arg. */
452	char *linep;
453	char linebuf[12];	/* 32-bit unit (10) + '\n' (1) + '\0' (1) */
454	int fd;			/* Standard output of mdconfig invocation. */
455	FILE *sfd;
456	int rv;
457	char *p;
458	size_t linelen;
459	unsigned long ul;
460
461	switch (mdtype) {
462	case MD_SWAP:
463		ta = "-t swap";
464		break;
465	case MD_VNODE:
466		ta = "-t vnode";
467		break;
468	case MD_MALLOC:
469		ta = "-t malloc";
470		break;
471	default:
472		abort();
473	}
474	rv = run(&fd, "%s -a %s%s", path_mdconfig, ta, args);
475	if (rv)
476		errx(1, "mdconfig (attach) exited with error code %d", rv);
477
478	/* Receive the unit number. */
479	if (norun) {	/* Since we didn't run, we can't read.  Fake it. */
480		unit = 0;
481		return;
482	}
483	sfd = fdopen(fd, "r");
484	if (sfd == NULL)
485		err(1, "fdopen");
486	linep = fgetln(sfd, &linelen);
487	/* If the output format changes, we want to know about it. */
488	if (linep == NULL || linelen <= mdnamelen + 1 ||
489	    linelen - mdnamelen >= sizeof(linebuf) ||
490	    strncmp(linep, mdname, mdnamelen) != 0)
491		errx(1, "unexpected output from mdconfig (attach)");
492	linep += mdnamelen;
493	linelen -= mdnamelen;
494	/* Can't use strlcpy because linep is not NULL-terminated. */
495	strncpy(linebuf, linep, linelen);
496	linebuf[linelen] = '\0';
497	ul = strtoul(linebuf, &p, 10);
498	if (ul == ULONG_MAX || *p != '\n')
499		errx(1, "unexpected output from mdconfig (attach)");
500	unit = ul;
501
502	fclose(sfd);
503}
504
505/*
506 * Detach a memory disk.
507 */
508static void
509do_mdconfig_detach(void)
510{
511	int rv;
512
513	rv = run(NULL, "%s -d -u %s%d", path_mdconfig, mdname, unit);
514	if (rv && debug)	/* This is allowed to fail. */
515		warnx("mdconfig (detach) exited with error code %d (ignored)",
516		    rv);
517}
518
519/*
520 * Mount the configured memory disk.
521 */
522static void
523do_mount_md(const char *args, const char *mtpoint)
524{
525	int rv;
526
527	rv = run(NULL, "%s%s /dev/%s%d%s %s", _PATH_MOUNT, args,
528	    mdname, unit, mdsuffix, mtpoint);
529	if (rv)
530		errx(1, "mount exited with error code %d", rv);
531}
532
533/*
534 * Mount the configured tmpfs.
535 */
536static void
537do_mount_tmpfs(const char *args, const char *mtpoint)
538{
539	int rv;
540
541	rv = run(NULL, "%s -t tmpfs %s tmp %s", _PATH_MOUNT, args, mtpoint);
542	if (rv)
543		errx(1, "tmpfs mount exited with error code %d", rv);
544}
545
546/*
547 * Various configuration of the mountpoint.  Mostly, enact 'mip'.
548 */
549static void
550do_mtptsetup(const char *mtpoint, struct mtpt_info *mip)
551{
552	struct statfs sfs;
553
554	if (!mip->mi_have_mode && !mip->mi_have_uid && !mip->mi_have_gid)
555		return;
556
557	if (!norun) {
558		if (statfs(mtpoint, &sfs) == -1) {
559			warn("statfs: %s", mtpoint);
560			return;
561		}
562		if ((sfs.f_flags & MNT_RDONLY) != 0) {
563			if (mip->mi_forced_pw) {
564				warnx(
565	"Not changing mode/owner of %s since it is read-only",
566				    mtpoint);
567			} else {
568				debugprintf(
569	"Not changing mode/owner of %s since it is read-only",
570				    mtpoint);
571			}
572			return;
573		}
574	}
575
576	if (mip->mi_have_mode) {
577		debugprintf("changing mode of %s to %o.", mtpoint,
578		    mip->mi_mode);
579		if (!norun)
580			if (chmod(mtpoint, mip->mi_mode) == -1)
581				err(1, "chmod: %s", mtpoint);
582	}
583	/*
584	 * We have to do these separately because the user may have
585	 * only specified one of them.
586	 */
587	if (mip->mi_have_uid) {
588		debugprintf("changing owner (user) or %s to %u.", mtpoint,
589		    mip->mi_uid);
590		if (!norun)
591			if (chown(mtpoint, mip->mi_uid, -1) == -1)
592				err(1, "chown %s to %u (user)", mtpoint,
593				    mip->mi_uid);
594	}
595	if (mip->mi_have_gid) {
596		debugprintf("changing owner (group) or %s to %u.", mtpoint,
597		    mip->mi_gid);
598		if (!norun)
599			if (chown(mtpoint, -1, mip->mi_gid) == -1)
600				err(1, "chown %s to %u (group)", mtpoint,
601				    mip->mi_gid);
602	}
603}
604
605/*
606 * Put a file system on the memory disk.
607 */
608static void
609do_newfs(const char *args)
610{
611	int rv;
612
613	rv = run(NULL, "%s%s /dev/%s%d", _PATH_NEWFS, args, mdname, unit);
614	if (rv)
615		errx(1, "newfs exited with error code %d", rv);
616}
617
618
619/*
620 * Copy skel into the mountpoint.
621 */
622static void
623do_copy(const char *mtpoint, const char *skel)
624{
625	int rv;
626
627	rv = chdir(skel);
628	if (rv != 0)
629		err(1, "chdir to %s", skel);
630	rv = run(NULL, "/bin/pax -rw -pe . %s", mtpoint);
631	if (rv != 0)
632		errx(1, "skel copy failed");
633}
634
635/*
636 * 'str' should be a user and group name similar to the last argument
637 * to chown(1); i.e., a user, followed by a colon, followed by a
638 * group.  The user and group in 'str' may be either a [ug]id or a
639 * name.  Upon return, the uid and gid fields in 'mip' will contain
640 * the uid and gid of the user and group name in 'str', respectively.
641 *
642 * In other words, this derives a user and group id from a string
643 * formatted like the last argument to chown(1).
644 *
645 * Notice: At this point we don't support only a username or only a
646 * group name. do_mtptsetup already does, so when this feature is
647 * desired, this is the only routine that needs to be changed.
648 */
649static void
650extract_ugid(const char *str, struct mtpt_info *mip)
651{
652	char *ug;			/* Writable 'str'. */
653	char *user, *group;		/* Result of extracton. */
654	struct passwd *pw;
655	struct group *gr;
656	char *p;
657	uid_t *uid;
658	gid_t *gid;
659
660	uid = &mip->mi_uid;
661	gid = &mip->mi_gid;
662	mip->mi_have_uid = mip->mi_have_gid = false;
663
664	/* Extract the user and group from 'str'.  Format above. */
665	ug = strdup(str);
666	assert(ug != NULL);
667	group = ug;
668	user = strsep(&group, ":");
669	if (user == NULL || group == NULL || *user == '\0' || *group == '\0')
670		usage();
671
672	/* Derive uid. */
673	*uid = strtoul(user, &p, 10);
674	if (*uid == (uid_t)ULONG_MAX)
675		usage();
676	if (*p != '\0') {
677		pw = getpwnam(user);
678		if (pw == NULL)
679			errx(1, "invalid user: %s", user);
680		*uid = pw->pw_uid;
681	}
682	mip->mi_have_uid = true;
683
684	/* Derive gid. */
685	*gid = strtoul(group, &p, 10);
686	if (*gid == (gid_t)ULONG_MAX)
687		usage();
688	if (*p != '\0') {
689		gr = getgrnam(group);
690		if (gr == NULL)
691			errx(1, "invalid group: %s", group);
692		*gid = gr->gr_gid;
693	}
694	mip->mi_have_gid = true;
695
696	free(ug);
697}
698
699/*
700 * Run a process with command name and arguments pointed to by the
701 * formatted string 'cmdline'.  Since system(3) is not used, the first
702 * space-delimited token of 'cmdline' must be the full pathname of the
703 * program to run.  The return value is the return code of the process
704 * spawned.  If 'ofd' is non-NULL, it is set to the standard output of
705 * the program spawned (i.e., you can read from ofd and get the output
706 * of the program).
707 */
708static int
709run(int *ofd, const char *cmdline, ...)
710{
711	char **argv, **argvp;		/* Result of splitting 'cmd'. */
712	int argc;
713	char *cmd;			/* Expansion of 'cmdline'. */
714	int pid, status;		/* Child info. */
715	int pfd[2];			/* Pipe to the child. */
716	int nfd;			/* Null (/dev/null) file descriptor. */
717	bool dup2dn;			/* Dup /dev/null to stdout? */
718	va_list ap;
719	char *p;
720	int rv, i;
721
722	dup2dn = true;
723	va_start(ap, cmdline);
724	rv = vasprintf(&cmd, cmdline, ap);
725	if (rv == -1)
726		err(1, "vasprintf");
727	va_end(ap);
728
729	/* Split up 'cmd' into 'argv' for use with execve. */
730	for (argc = 1, p = cmd; (p = strchr(p, ' ')) != NULL; p++)
731		argc++;		/* 'argc' generation loop. */
732	argv = (char **)malloc(sizeof(*argv) * (argc + 1));
733	assert(argv != NULL);
734	for (p = cmd, argvp = argv; (*argvp = strsep(&p, " ")) != NULL;)
735		if (**argvp != '\0')
736			if (++argvp >= &argv[argc]) {
737				*argvp = NULL;
738				break;
739			}
740	assert(*argv);
741	/* The argv array ends up NULL-terminated here. */
742
743	/* Make sure the above loop works as expected. */
744	if (debug) {
745		/*
746		 * We can't, but should, use debugprintf here.  First,
747		 * it appends a trailing newline to the output, and
748		 * second it prepends "DEBUG: " to the output.  The
749		 * former is a problem for this would-be first call,
750		 * and the latter for the would-be call inside the
751		 * loop.
752		 */
753		(void)fprintf(stderr, "DEBUG: running:");
754		/* Should be equivalent to 'cmd' (before strsep, of course). */
755		for (i = 0; argv[i] != NULL; i++)
756			(void)fprintf(stderr, " %s", argv[i]);
757		(void)fprintf(stderr, "\n");
758	}
759
760	/* Create a pipe if necessary and fork the helper program. */
761	if (ofd != NULL) {
762		if (pipe(&pfd[0]) == -1)
763			err(1, "pipe");
764		*ofd = pfd[0];
765		dup2dn = false;
766	}
767	pid = fork();
768	switch (pid) {
769	case 0:
770		/* XXX can we call err() in here? */
771		if (norun)
772			_exit(0);
773		if (ofd != NULL)
774			if (dup2(pfd[1], STDOUT_FILENO) < 0)
775				err(1, "dup2");
776		if (!loudsubs) {
777			nfd = open(_PATH_DEVNULL, O_RDWR);
778			if (nfd == -1)
779				err(1, "open: %s", _PATH_DEVNULL);
780			if (dup2(nfd, STDIN_FILENO) < 0)
781				err(1, "dup2");
782			if (dup2dn)
783				if (dup2(nfd, STDOUT_FILENO) < 0)
784				   err(1, "dup2");
785			if (dup2(nfd, STDERR_FILENO) < 0)
786				err(1, "dup2");
787		}
788
789		(void)execv(argv[0], argv);
790		warn("exec: %s", argv[0]);
791		_exit(-1);
792	case -1:
793		err(1, "fork");
794	}
795
796	free(cmd);
797	free(argv);
798	while (waitpid(pid, &status, 0) != pid)
799		;
800	return (WEXITSTATUS(status));
801}
802
803static void
804usage(void)
805{
806
807	fprintf(stderr,
808"usage: %s [-DLlMNnPStUX] [-a maxcontig] [-b block-size]\n"
809"\t[-c blocks-per-cylinder-group][-d max-extent-size] [-E path-mdconfig]\n"
810"\t[-e maxbpg] [-F file] [-f frag-size] [-i bytes] [-k skel]\n"
811"\t[-m percent-free] [-O optimization] [-o mount-options]\n"
812"\t[-p permissions] [-s size] [-v version] [-w user:group]\n"
813"\tmd-device mount-point\n", getprogname());
814	exit(1);
815}
816