mdmfs.c revision 78447
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 34static const char rcsid[] = 35 "$FreeBSD: head/sbin/mdmfs/mdmfs.c 78447 2001-06-18 23:46:58Z dd $"; 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 57typedef enum { false, true } bool; 58 59struct 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 68static bool debug; /* Emit debugging information? */ 69static bool loudsubs; /* Suppress output from helper programs? */ 70static bool norun; /* Actually run the helper programs? */ 71static int unit; /* The unit we're working with. */ 72static const char *mdname; /* Name of memory disk device (e.g., "md"). */ 73static size_t mdnamelen; /* Length of mdname. */ 74 75static void argappend(char **, const char *, ...); 76static void debugprintf(const char *, ...); 77static void do_disklabel(void); 78static void do_mdconfig_attach(const char *, const enum md_types); 79static void do_mdconfig_attach_au(const char *, const enum md_types); 80static void do_mdconfig_detach(void); 81static void do_mount(const char *, const char *); 82static void do_mtptsetup(const char *, struct mtpt_info *); 83static void do_newfs(const char *); 84static void extract_ugid(const char *, struct mtpt_info *); 85static int run(int *, const char *, ...); 86static void usage(void); 87 88int 89main(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 */ 246static void 247argappend(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 */ 273static void 274debugprintf(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 */ 291static void 292do_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 */ 304static void 305do_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 */ 332static void 333do_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 */ 389static void 390do_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 */ 403static void 404do_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 */ 417static void 418do_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 */ 453static void 454do_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 */ 473static void 474extract_ugid(const char *str, struct mtpt_info *mip) 475{ 476 char *ug; /* Writable 'str'. */ 477 char *user, *group; /* Result of extracton. */ 478 size_t strl; /* Length of 'str' incl. NULL. */ 479 struct passwd *pw; 480 struct group *gr; 481 char *p; 482 uid_t *uid; 483 gid_t *gid; 484 size_t rv; 485 486 uid = &mip->mi_uid; 487 gid = &mip->mi_gid; 488 mip->mi_have_uid = mip->mi_have_gid = false; 489 490 /* Extract the user and group from 'str'. Format above. */ 491 strl = strlen(str) + 1; 492 ug = malloc(strl); 493 assert(ug != NULL); 494 rv = strlcpy(ug, str, strl); 495 if (rv >= strl) 496 errx(1, "-w word too long (%d >= %d)", rv, strl); 497 group = ug; 498 user = strsep(&group, ":"); 499 if (user == NULL || group == NULL || *user == '\0' || *group == '\0') 500 usage(); 501 502 /* Derive uid. */ 503 *uid = strtoul(user, &p, 10); 504 if ((unsigned)*uid == ULONG_MAX) 505 usage(); 506 if (*p != '\0') { 507 pw = getpwnam(user); 508 if (pw == NULL) 509 errx(1, "invalid user: %s", user); 510 *uid = pw->pw_uid; 511 mip->mi_have_uid = true; 512 } 513 514 /* Derive gid. */ 515 *gid = strtoul(group, &p, 10); 516 if ((unsigned)*gid == ULONG_MAX) 517 usage(); 518 if (*p != '\0') { 519 gr = getgrnam(group); 520 if (gr == NULL) 521 errx(1, "invalid group: %s", group); 522 *gid = gr->gr_gid; 523 mip->mi_have_gid = true; 524 } 525 526 free(ug); 527 /* 528 * At this point we don't support only a username or only a 529 * group name. do_mtptsetup already does, so when this 530 * feature is desired, this is the only routine that needs to 531 * be changed. 532 */ 533 assert(mip->mi_have_uid); 534 assert(mip->mi_have_gid); 535} 536 537/* 538 * Run a process with command name and arguments pointed to by the 539 * formatted string 'cmdline'. Since system(3) is not used, the first 540 * space-delimited token of 'cmdline' must be the full pathname of the 541 * program to run. The return value is the return code of the process 542 * spawned. If 'ofd' is non-NULL, it is set to the standard output of 543 * the program spawned (i.e., you can read from ofd and get the output 544 * of the program). 545 */ 546static int 547run(int *ofd, const char *cmdline, ...) 548{ 549 char **av, **avp; /* Result of splitting 'cmd'. */ 550 int ac; 551 char *cmd; /* Expansion of 'cmdline'. */ 552 int pid, status; /* Child info. */ 553 int pfd[2]; /* Pipe to the child. */ 554 int nfd; /* Null (/dev/null) file descriptor. */ 555 bool dup2dn; /* Dup /dev/null to stdout? */ 556 va_list ap; 557 char *p; 558 int rv, i; 559 560 dup2dn = true; 561 va_start(ap, cmdline); 562 rv = vasprintf(&cmd, cmdline, ap); 563 if (rv == -1) 564 err(1, "vasprintf"); 565 va_end(ap); 566 567 /* Split up 'cmd' into 'av' for use with execve. */ 568 for (ac = 1, p = cmd; (p = strchr(p, ' ')) != NULL; p++) 569 ac++; /* 'ac' generation loop. */ 570 av = (char **)malloc(sizeof(*av) * (ac + 1)); 571 assert(av != NULL); 572 for (p = cmd, avp = av; (*avp = strsep(&p, " ")) != NULL;) 573 if (**av != '\0') 574 if (++avp >= &av[ac]) { 575 *avp = NULL; 576 break; 577 } 578 assert(*av); 579 580 /* Make sure the above loop works as expected. */ 581 if (debug) { 582 /* 583 * We can't, but should, use debugprintf here. First, 584 * it appends a trailing newline to the output, and 585 * second it prepends "DEBUG: " to the output. The 586 * former is a problem for this would-be first call, 587 * and the latter for the would-be call inside the 588 * loop. 589 */ 590 (void)fprintf(stderr, "DEBUG: running:"); 591 /* Should be equivilent to 'cmd' (before strsep, of course). */ 592 for (i = 0; av[i] != NULL; i++) 593 (void)fprintf(stderr, " %s", av[i]); 594 (void)fprintf(stderr, "\n"); 595 } 596 597 /* Create a pipe if necessary and fork the helper program. */ 598 if (ofd != NULL) { 599 if (pipe(&pfd[0]) == -1) 600 err(1, "pipe"); 601 *ofd = pfd[0]; 602 dup2dn = false; 603 } 604 pid = fork(); 605 switch (pid) { 606 case 0: 607 /* XXX can we call err() in here? */ 608 if (norun) 609 _exit(0); 610 if (ofd != NULL) 611 if (dup2(pfd[1], STDOUT_FILENO) < 0) 612 err(1, "dup2"); 613 if (!loudsubs) { 614 nfd = open(_PATH_DEVNULL, O_RDWR); 615 if (nfd == -1) 616 err(1, "open: %s", _PATH_DEVNULL); 617 if (dup2(nfd, STDIN_FILENO) < 0) 618 err(1, "dup2"); 619 if (dup2dn) 620 if (dup2(nfd, STDOUT_FILENO) < 0) 621 err(1, "dup2"); 622 if (dup2(nfd, STDERR_FILENO) < 0) 623 err(1, "dup2"); 624 } 625 626 (void)execv(av[0], av); 627 warn("exec: %s", av[0]); 628 _exit(-1); 629 case -1: 630 err(1, "fork"); 631 } 632 633 free(cmd); 634 free(av); 635 while (waitpid(pid, &status, 0) != pid) 636 ; 637 return (WEXITSTATUS(status)); 638} 639 640static void 641usage(void) 642{ 643 644 fprintf(stderr, 645"usage: %s [-DLMNSX] [-a maxcontig] [-b block-size] [-c cylinders]\n" 646"\t[-d rotdelay] [-e maxbpg] [-F file] [-f frag-size] [-i bytes]\n" 647"\t[-m percent-free] [-n rotational-positions] [-O optimization]\n" 648"\t[-o mount-options] [-p permissions] [-s size] [-w user:group]\n" 649"\tmd-device mount-point\n", getprogname()); 650 exit(1); 651} 652