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