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