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