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