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