xref: /freebsd/sbin/mdmfs/mdmfs.c (revision a220d00e74dd245b4fca59c5eca0c53963686325)
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), disklabel(8),
29  * newfs(8), and mount(8) that mimics the command line option set of
30  * the deprecated mount_mfs(8).
31  */
32 
33 #ifndef lint
34 static const char rcsid[] =
35   "$FreeBSD$";
36 #endif /* not lint */
37 
38 #include <sys/param.h>
39 #include <sys/mdioctl.h>
40 #include <sys/stat.h>
41 #include <sys/wait.h>
42 
43 #include <assert.h>
44 #include <err.h>
45 #include <fcntl.h>
46 #include <grp.h>
47 #include <paths.h>
48 #include <pwd.h>
49 #include <stdarg.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <unistd.h>
54 
55 #include "pathnames.h"
56 
57 typedef enum { false, true } bool;
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 };
67 
68 static	bool compat;		/* Full compatibility with mount_mfs? */
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	size_t mdnamelen;	/* Length of mdname. */
75 
76 static void	 argappend(char **, const char *, ...) __printflike(2, 3);
77 static void	 debugprintf(const char *, ...) __printflike(1, 2);
78 static void	 do_disklabel(void);
79 static void	 do_mdconfig_attach(const char *, const enum md_types);
80 static void	 do_mdconfig_attach_au(const char *, const enum md_types);
81 static void	 do_mdconfig_detach(void);
82 static void	 do_mount(const char *, const char *);
83 static void	 do_mtptsetup(const char *, struct mtpt_info *);
84 static void	 do_newfs(const char *);
85 static void	 extract_ugid(const char *, struct mtpt_info *);
86 static int	 run(int *, const char *, ...) __printflike(2, 3);
87 static void	 usage(void);
88 
89 int
90 main(int argc, char **argv)
91 {
92 	struct mtpt_info mi;		/* Mountpoint info. */
93 	char *mdconfig_arg, *newfs_arg,	/* Args to helper programs. */
94 	    *mount_arg;
95 	enum md_types mdtype;		/* The type of our memory disk. */
96 	bool have_mdtype;
97 	bool detach, softdep, autounit;
98 	char *mtpoint, *unitstr;
99 	char ch, *p;
100 
101 	/* Misc. initialization. */
102 	(void)memset(&mi, '\0', sizeof(mi));
103 	detach = true;
104 	softdep = true;
105 	autounit = false;
106 	have_mdtype = false;
107 	mdname = MD_NAME;
108 	mdnamelen = strlen(mdname);
109 	/*
110 	 * Can't set these to NULL.  They may be passed to the
111 	 * respective programs without modification.  I.e., we may not
112 	 * receive any command-line options which will caused them to
113 	 * be modified.
114 	 */
115 	mdconfig_arg = strdup("");
116 	newfs_arg = strdup("");
117 	mount_arg = strdup("");
118 
119 	/* If we were started as mount_mfs or mfs, imply -C. */
120 	if (strcmp(getprogname(), "mount_mfs") == 0 ||
121 	    strcmp(getprogname(), "mfs") == 0)
122 		compat = true;
123 
124 	while ((ch = getopt(argc, argv,
125 	    "a:b:Cc:Dd:e:F:f:hi:LMm:Nn:O:o:p:Ss:t:Uw:X")) != -1)
126 		switch (ch) {
127 		case 'a':
128 			argappend(&newfs_arg, "-a %s", optarg);
129 			break;
130 		case 'b':
131 			argappend(&newfs_arg, "-b %s", optarg);
132 			break;
133 		case 'C':
134 			if (compat)
135 				usage();
136 			compat = true;
137 			break;
138 		case 'c':
139 			argappend(&newfs_arg, "-c %s", optarg);
140 			break;
141 		case 'D':
142 			if (compat)
143 				usage();
144 			detach = false;
145 			break;
146 		case 'd':
147 			argappend(&newfs_arg, "-d %s", optarg);
148 			break;
149 		case 'e':
150 			argappend(&newfs_arg, "-e %s", optarg);
151 			break;
152 		case 'F':
153 			if (have_mdtype)
154 				usage();
155 			mdtype = MD_VNODE;
156 			have_mdtype = true;
157 			argappend(&mdconfig_arg, "-f %s", optarg);
158 			break;
159 		case 'f':
160 			argappend(&newfs_arg, "-f %s", optarg);
161 			break;
162 		case 'h':
163 			usage();
164 			break;
165 		case 'i':
166 			argappend(&newfs_arg, "-i %s", optarg);
167 			break;
168 		case 'L':
169 			if (compat)
170 				usage();
171 			loudsubs = true;
172 			break;
173 		case 'M':
174 			if (have_mdtype)
175 				usage();
176 			mdtype = MD_MALLOC;
177 			have_mdtype = true;
178 			break;
179 		case 'm':
180 			argappend(&newfs_arg, "-m %s", optarg);
181 			break;
182 		case 'N':
183 			if (compat)
184 				usage();
185 			norun = true;
186 			break;
187 		case 'n':
188 			argappend(&newfs_arg, "-n %s", optarg);
189 			break;
190 		case 'O':
191 			argappend(&newfs_arg, "-o %s", optarg);
192 			break;
193 		case 'o':
194 			argappend(&mount_arg, "-o %s", optarg);
195 			break;
196 		case 'p':
197 			if (compat)
198 				usage();
199 			if (*optarg >= '0' && *optarg <= '7')
200 				mi.mi_mode = strtol(optarg, NULL, 8);
201 			if ((mi.mi_mode & ~07777) != 0)
202 				usage();
203 			mi.mi_have_mode = true;
204 			break;
205 		case 'S':
206 			if (compat)
207 				usage();
208 			softdep = false;
209 			break;
210 		case 's':
211 			argappend(&mdconfig_arg, "-s %s", optarg);
212 			break;
213 		case 'U':
214 			softdep = true;
215 			break;
216 		case 'w':
217 			if (compat)
218 				usage();
219 			extract_ugid(optarg, &mi);
220 			break;
221 		case 'X':
222 			if (compat)
223 				usage();
224 			debug = true;
225 			break;
226 		default:
227 			usage();
228 		}
229 	argc -= optind;
230 	argv += optind;
231 	if (argc < 2)
232 		usage();
233 
234 	/* Make compatibility assumptions. */
235 	if (compat) {
236 		mi.mi_mode = 01777;
237 		mi.mi_have_mode = true;
238 	}
239 
240 	/* Derive 'unit' (global). */
241 	unitstr = argv[0];
242 	if (strncmp(unitstr, "/dev/", 5) == 0)
243 		unitstr += 5;
244 	if (strncmp(unitstr, mdname, mdnamelen) == 0)
245 		unitstr += mdnamelen;
246 	if (*unitstr == '\0') {
247 		autounit = true;
248 		unit = -1;
249 	} else {
250 		unit = strtoul(unitstr, &p, 10);
251 		if ((unsigned)unit == ULONG_MAX || *p != '\0')
252 			errx(1, "bad device unit: %s", unitstr);
253 	}
254 
255 	mtpoint = argv[1];
256 	if (!have_mdtype)
257 		mdtype = MD_SWAP;
258 	if (softdep)
259 		argappend(&newfs_arg, "-U");
260 
261 	/* Do the work. */
262 	if (detach && !autounit)
263 		do_mdconfig_detach();
264 	if (autounit)
265 		do_mdconfig_attach_au(mdconfig_arg, mdtype);
266 	else
267 		do_mdconfig_attach(mdconfig_arg, mdtype);
268 	do_disklabel();
269 	do_newfs(newfs_arg);
270 	do_mount(mount_arg, mtpoint);
271 	do_mtptsetup(mtpoint, &mi);
272 
273 	return (0);
274 }
275 
276 /*
277  * Append the expansion of 'fmt' to the buffer pointed to by '*dstp';
278  * reallocate as required.
279  */
280 static void
281 argappend(char **dstp, const char *fmt, ...)
282 {
283 	char *old, *new;
284 	va_list ap;
285 
286 	old = *dstp;
287 	assert(old != NULL);
288 
289 	va_start(ap, fmt);
290 	if (vasprintf(&new, fmt,ap) == -1)
291 		errx(1, "vasprintf");
292 	va_end(ap);
293 
294 	*dstp = new;
295 	if (asprintf(&new, "%s %s", old, new) == -1)
296 		errx(1, "asprintf");
297 	free(*dstp);
298 	free(old);
299 
300 	*dstp = new;
301 }
302 
303 /*
304  * If run-time debugging is enabled, print the expansion of 'fmt'.
305  * Otherwise, do nothing.
306  */
307 static void
308 debugprintf(const char *fmt, ...)
309 {
310 	va_list ap;
311 
312 	if (!debug)
313 		return;
314 	fprintf(stderr, "DEBUG: ");
315 	va_start(ap, fmt);
316 	vfprintf(stderr, fmt, ap);
317 	va_end(ap);
318 	fprintf(stderr, "\n");
319 	fflush(stderr);
320 }
321 
322 /*
323  * Label the memory disk.
324  */
325 static void
326 do_disklabel(void)
327 {
328 	int rv;
329 
330 	rv = run(NULL, "%s -r -w %s%d auto", PATH_DISKLABEL, mdname, unit);
331 	if (rv)
332 		errx(1, "disklabel exited with error code %d", rv);
333 }
334 
335 /*
336  * Attach a memory disk with a known unit.
337  */
338 static void
339 do_mdconfig_attach(const char *args, const enum md_types mdtype)
340 {
341 	int rv;
342 	const char *ta;		/* Type arg. */
343 
344 	switch (mdtype) {
345 	case MD_SWAP:
346 		ta = "-t swap";
347 		break;
348 	case MD_VNODE:
349 		ta = "-t vnode";
350 		break;
351 	case MD_MALLOC:
352 		ta = "-t malloc";
353 		break;
354 	default:
355 		abort();
356 	}
357 	rv = run(NULL, "%s -a %s%s -u %s%d", PATH_MDCONFIG, ta, args,
358 	    mdname, unit);
359 	if (rv)
360 		errx(1, "mdconfig (attach) exited with error code %d", rv);
361 }
362 
363 /*
364  * Attach a memory disk with an unknown unit; use autounit.
365  */
366 static void
367 do_mdconfig_attach_au(const char *args, const enum md_types mdtype)
368 {
369 	const char *ta;		/* Type arg. */
370 	char *linep, *linebuf; 	/* Line pointer, line buffer. */
371 	int fd;			/* Standard output of mdconfig invocation. */
372 	FILE *sfd;
373 	int rv;
374 	char *p;
375 	size_t linelen;
376 
377 	switch (mdtype) {
378 	case MD_SWAP:
379 		ta = "-t swap";
380 		break;
381 	case MD_VNODE:
382 		ta = "-t vnode";
383 		break;
384 	case MD_MALLOC:
385 		ta = "-t malloc";
386 		break;
387 	default:
388 		abort();
389 	}
390 	rv = run(&fd, "%s -a %s%s", PATH_MDCONFIG, ta, args);
391 	if (rv)
392 		errx(1, "mdconfig (attach) exited with error code %d", rv);
393 
394 	/* Receive the unit number. */
395 	if (norun) {	/* Since we didn't run, we can't read.  Fake it. */
396 		unit = -1;
397 		return;
398 	}
399 	sfd = fdopen(fd, "r");
400 	if (sfd == NULL)
401 		err(1, "fdopen");
402 	linep = fgetln(sfd, &linelen);
403 	if (linep == NULL && linelen < mdnamelen + 1)
404 		errx(1, "unexpected output from mdconfig (attach)");
405 	/* If the output format changes, we want to know about it. */
406 	assert(strncmp(linep, mdname, mdnamelen) == 0);
407 	linebuf = malloc(linelen - mdnamelen + 1);
408 	assert(linebuf != NULL);
409 	/* Can't use strlcpy because linep is not NULL-terminated. */
410 	strncpy(linebuf, linep + mdnamelen, linelen);
411 	linebuf[linelen] = '\0';
412 	unit = strtoul(linebuf, &p, 10);
413 	if ((unsigned)unit == ULONG_MAX || *p != '\n')
414 		errx(1, "unexpected output from mdconfig (attach)");
415 
416 	fclose(sfd);
417 	close(fd);
418 }
419 
420 /*
421  * Detach a memory disk.
422  */
423 static void
424 do_mdconfig_detach(void)
425 {
426 	int rv;
427 
428 	rv = run(NULL, "%s -d -u %s%d", PATH_MDCONFIG, mdname, unit);
429 	if (rv && debug)	/* This is allowed to fail. */
430 		warnx("mdconfig (detach) exited with error code %d (ignored)",
431 		      rv);
432 }
433 
434 /*
435  * Mount the configured memory disk.
436  */
437 static void
438 do_mount(const char *args, const char *mtpoint)
439 {
440 	int rv;
441 
442 	rv = run(NULL, "%s%s /dev/%s%dc %s", PATH_MOUNT, args,
443 	    mdname, unit, mtpoint);
444 	if (rv)
445 		errx(1, "mount exited with error code %d", rv);
446 }
447 
448 /*
449  * Various configuration of the mountpoint.  Mostly, enact 'mip'.
450  */
451 static void
452 do_mtptsetup(const char *mtpoint, struct mtpt_info *mip)
453 {
454 
455 	if (mip->mi_have_mode) {
456 		debugprintf("changing mode of %s to %o.", mtpoint,
457 		    mip->mi_mode);
458 		if (!norun)
459 			if (chmod(mtpoint, mip->mi_mode) == -1)
460 				err(1, "chmod: %s", mtpoint);
461 	}
462 	/*
463 	 * We have to do these separately because the user may have
464 	 * only specified one of them.
465 	 */
466 	if (mip->mi_have_uid) {
467 		debugprintf("changing owner (user) or %s to %u.", mtpoint,
468 		    mip->mi_uid);
469 		if (!norun)
470 			if (chown(mtpoint, mip->mi_uid, -1) == -1)
471 				err(1, "chown %s to %u (user)", mtpoint,
472 				    mip->mi_uid);
473 	}
474 	if (mip->mi_have_gid) {
475 		debugprintf("changing owner (group) or %s to %u.", mtpoint,
476 		    mip->mi_gid);
477 		if (!norun)
478 			if (chown(mtpoint, -1, mip->mi_gid) == -1)
479 				err(1, "chown %s to %u (group)", mtpoint,
480 				    mip->mi_gid);
481 	}
482 }
483 
484 /*
485  * Put a filesystem on the memory disk.
486  */
487 static void
488 do_newfs(const char *args)
489 {
490 	int rv;
491 
492 	rv = run(NULL, "%s%s /dev/%s%dc", PATH_NEWFS, args, mdname, unit);
493 	if (rv)
494 		errx(1, "newfs exited with error code %d", rv);
495 }
496 
497 /*
498  * 'str' should be a user and group name similar to the last argument
499  * to chown(1); i.e., a user, followed by a colon, followed by a
500  * group.  The user and group in 'str' may be either a [ug]id or a
501  * name.  Upon return, the uid and gid fields in 'mip' will contain
502  * the uid and gid of the user and group name in 'str', respectively.
503  *
504  * In other words, this derives a user and group id from a string
505  * formatted like the last argument to chown(1).
506  */
507 static void
508 extract_ugid(const char *str, struct mtpt_info *mip)
509 {
510 	char *ug;			/* Writable 'str'. */
511 	char *user, *group;		/* Result of extracton. */
512 	struct passwd *pw;
513 	struct group *gr;
514 	char *p;
515 	uid_t *uid;
516 	gid_t *gid;
517 
518 	uid = &mip->mi_uid;
519 	gid = &mip->mi_gid;
520 	mip->mi_have_uid = mip->mi_have_gid = false;
521 
522 	/* Extract the user and group from 'str'.  Format above. */
523 	ug = strdup(str);
524 	assert(ug != NULL);
525 	group = ug;
526 	user = strsep(&group, ":");
527 	if (user == NULL || group == NULL || *user == '\0' || *group == '\0')
528 		usage();
529 
530 	/* Derive uid. */
531 	*uid = strtoul(user, &p, 10);
532 	if ((unsigned)*uid == ULONG_MAX)
533 		usage();
534 	if (*p != '\0') {
535 		pw = getpwnam(user);
536 		if (pw == NULL)
537 			errx(1, "invalid user: %s", user);
538 		*uid = pw->pw_uid;
539 		mip->mi_have_uid = true;
540 	}
541 
542 	/* Derive gid. */
543 	*gid = strtoul(group, &p, 10);
544 	if ((unsigned)*gid == ULONG_MAX)
545 		usage();
546 	if (*p != '\0') {
547 		gr = getgrnam(group);
548 		if (gr == NULL)
549 			errx(1, "invalid group: %s", group);
550 		*gid = gr->gr_gid;
551 		mip->mi_have_gid = true;
552 	}
553 
554 	free(ug);
555 	/*
556 	 * At this point we don't support only a username or only a
557 	 * group name.  do_mtptsetup already does, so when this
558 	 * feature is desired, this is the only routine that needs to
559 	 * be changed.
560 	 */
561 	assert(mip->mi_have_uid);
562 	assert(mip->mi_have_gid);
563 }
564 
565 /*
566  * Run a process with command name and arguments pointed to by the
567  * formatted string 'cmdline'.  Since system(3) is not used, the first
568  * space-delimited token of 'cmdline' must be the full pathname of the
569  * program to run.  The return value is the return code of the process
570  * spawned.  If 'ofd' is non-NULL, it is set to the standard output of
571  * the program spawned (i.e., you can read from ofd and get the output
572  * of the program).
573  */
574 static int
575 run(int *ofd, const char *cmdline, ...)
576 {
577 	char **argv, **argvp;		/* Result of splitting 'cmd'. */
578 	int argc;
579 	char *cmd;			/* Expansion of 'cmdline'. */
580 	int pid, status;		/* Child info. */
581 	int pfd[2];			/* Pipe to the child. */
582 	int nfd;			/* Null (/dev/null) file descriptor. */
583 	bool dup2dn;			/* Dup /dev/null to stdout? */
584 	va_list ap;
585 	char *p;
586 	int rv, i;
587 
588 	dup2dn = true;
589 	va_start(ap, cmdline);
590 	rv = vasprintf(&cmd, cmdline, ap);
591 	if (rv == -1)
592 		err(1, "vasprintf");
593 	va_end(ap);
594 
595 	/* Split up 'cmd' into 'argv' for use with execve. */
596 	for (argc = 1, p = cmd; (p = strchr(p, ' ')) != NULL; p++)
597 		argc++;		/* 'argc' generation loop. */
598 	argv = (char **)malloc(sizeof(*argv) * (argc + 1));
599 	assert(argv != NULL);
600 	for (p = cmd, argvp = argv; (*argvp = strsep(&p, " ")) != NULL;)
601 		if (**argv != '\0')
602 			if (++argvp >= &argv[argc]) {
603 				*argvp = NULL;
604 				break;
605 			}
606 	assert(*argv);
607 
608 	/* Make sure the above loop works as expected. */
609 	if (debug) {
610 		/*
611 		 * We can't, but should, use debugprintf here.  First,
612 		 * it appends a trailing newline to the output, and
613 		 * second it prepends "DEBUG: " to the output.  The
614 		 * former is a problem for this would-be first call,
615 		 * and the latter for the would-be call inside the
616 		 * loop.
617 		 */
618 		(void)fprintf(stderr, "DEBUG: running:");
619 		/* Should be equivilent to 'cmd' (before strsep, of course). */
620 		for (i = 0; argv[i] != NULL; i++)
621 			(void)fprintf(stderr, " %s", argv[i]);
622 		(void)fprintf(stderr, "\n");
623 	}
624 
625 	/* Create a pipe if necessary and fork the helper program. */
626 	if (ofd != NULL) {
627 		if (pipe(&pfd[0]) == -1)
628 			err(1, "pipe");
629 		*ofd = pfd[0];
630 		dup2dn = false;
631 	}
632 	pid = fork();
633 	switch (pid) {
634 	case 0:
635 		/* XXX can we call err() in here? */
636 		if (norun)
637 			_exit(0);
638 		if (ofd != NULL)
639 			if (dup2(pfd[1], STDOUT_FILENO) < 0)
640 				err(1, "dup2");
641 		if (!loudsubs) {
642 			nfd = open(_PATH_DEVNULL, O_RDWR);
643 			if (nfd == -1)
644 				err(1, "open: %s", _PATH_DEVNULL);
645 			if (dup2(nfd, STDIN_FILENO) < 0)
646 				err(1, "dup2");
647 			if (dup2dn)
648 				if (dup2(nfd, STDOUT_FILENO) < 0)
649 				   err(1, "dup2");
650 			if (dup2(nfd, STDERR_FILENO) < 0)
651 				err(1, "dup2");
652 		}
653 
654 		(void)execv(argv[0], argv);
655 		warn("exec: %s", argv[0]);
656 		_exit(-1);
657 	case -1:
658 		err(1, "fork");
659 	}
660 
661 	free(cmd);
662 	free(argv);
663 	while (waitpid(pid, &status, 0) != pid)
664 		;
665 	return (WEXITSTATUS(status));
666 }
667 
668 static void
669 usage(void)
670 {
671 	const char *name;
672 
673 	if (compat)
674 		name = getprogname();
675 	else
676 		name = "mdmfs";
677 	if (!compat)
678 		fprintf(stderr,
679 "Usage: %s [-DLMNSUX] [-a maxcontig [-b block-size] [-c cylinders]\n"
680 "\t[-d rotdelay] [-e maxbpg] [-F file] [-f frag-size] [-i bytes]\n"
681 "\t[-m percent-free] [-n rotational-positions] [-O optimization]\n"
682 "\t[-o mount-options] [-p permissions] [-s size] [-w user:group]\n"
683 "\tmd-device mount-point\n", name);
684 	fprintf(stderr,
685 "Usage: %s -C [-NU] [-a maxcontig] [-b block-size] [-c cylinders]\n"
686 "\t[-d rotdelay] [-e maxbpg] [-F file] [-f frag-size] [-i bytes]\n"
687 "\t[-m percent-free] [-n rotational-positions] [-O optimization]\n"
688 "\t[-o mount-options] [-s size] md-device mount-point\n", name);
689 	exit(1);
690 }
691