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