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