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