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 static const char *path_mdconfig =_PATH_MDCONFIG; 72 73 static void argappend(char **, const char *, ...) __printflike(2, 3); 74 static void debugprintf(const char *, ...) __printflike(1, 2); 75 static void do_mdconfig_attach(const char *, const enum md_types); 76 static void do_mdconfig_attach_au(const char *, const enum md_types); 77 static void do_mdconfig_detach(void); 78 static void do_mount(const char *, const char *); 79 static void do_mtptsetup(const char *, struct mtpt_info *); 80 static void do_newfs(const char *); 81 static void extract_ugid(const char *, struct mtpt_info *); 82 static int run(int *, const char *, ...) __printflike(2, 3); 83 static void usage(void); 84 85 int 86 main(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 */ 298 static void 299 argappend(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 */ 325 static void 326 debugprintf(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 */ 343 static void 344 do_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 */ 371 static void 372 do_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 */ 430 static void 431 do_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 */ 444 static void 445 do_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 */ 458 static void 459 do_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 */ 494 static void 495 do_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 */ 518 static void 519 extract_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 */ 577 static int 578 run(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 671 static void 672 usage(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