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