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