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