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/mount.h> 39 #include <sys/stat.h> 40 #include <sys/wait.h> 41 42 #include <assert.h> 43 #include <err.h> 44 #include <fcntl.h> 45 #include <grp.h> 46 #include <paths.h> 47 #include <pwd.h> 48 #include <stdarg.h> 49 #include <stdio.h> 50 #include <stdlib.h> 51 #include <string.h> 52 #include <ctype.h> 53 #include <unistd.h> 54 55 typedef enum { false, true } bool; 56 57 struct mtpt_info { 58 uid_t mi_uid; 59 bool mi_have_uid; 60 gid_t mi_gid; 61 bool mi_have_gid; 62 mode_t mi_mode; 63 bool mi_have_mode; 64 bool mi_forced_pw; 65 }; 66 67 static bool debug; /* Emit debugging information? */ 68 static bool loudsubs; /* Suppress output from helper programs? */ 69 static bool norun; /* Actually run the helper programs? */ 70 static int unit; /* The unit we're working with. */ 71 static const char *mdname; /* Name of memory disk device (e.g., "md"). */ 72 static const char *mdsuffix; /* Suffix of memory disk device (e.g., ".uzip"). */ 73 static size_t mdnamelen; /* Length of mdname. */ 74 static const char *path_mdconfig =_PATH_MDCONFIG; 75 76 static void argappend(char **, const char *, ...) __printflike(2, 3); 77 static void debugprintf(const char *, ...) __printflike(1, 2); 78 static void do_mdconfig_attach(const char *, const enum md_types); 79 static void do_mdconfig_attach_au(const char *, const enum md_types); 80 static void do_mdconfig_detach(void); 81 static void do_mount(const char *, const char *); 82 static void do_mtptsetup(const char *, struct mtpt_info *); 83 static void do_newfs(const char *); 84 static void extract_ugid(const char *, struct mtpt_info *); 85 static int run(int *, const char *, ...) __printflike(2, 3); 86 static void usage(void); 87 88 int 89 main(int argc, char **argv) 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, newfs; 97 char *mtpoint, *unitstr; 98 char *p; 99 int ch; 100 void *set; 101 unsigned long ul; 102 103 /* Misc. initialization. */ 104 (void)memset(&mi, '\0', sizeof(mi)); 105 detach = true; 106 softdep = true; 107 autounit = false; 108 newfs = true; 109 have_mdtype = false; 110 mdtype = MD_SWAP; 111 mdname = MD_NAME; 112 mdnamelen = strlen(mdname); 113 /* 114 * Can't set these to NULL. They may be passed to the 115 * respective programs without modification. I.e., we may not 116 * receive any command-line options which will caused them to 117 * be modified. 118 */ 119 mdconfig_arg = strdup(""); 120 newfs_arg = strdup(""); 121 mount_arg = strdup(""); 122 123 /* If we were started as mount_mfs or mfs, imply -C. */ 124 if (strcmp(getprogname(), "mount_mfs") == 0 || 125 strcmp(getprogname(), "mfs") == 0) { 126 /* Make compatibility assumptions. */ 127 mi.mi_mode = 01777; 128 mi.mi_have_mode = true; 129 } 130 131 while ((ch = getopt(argc, argv, 132 "a:b:Cc:Dd:E:e:F:f:hi:LlMm:NnO:o:Pp:Ss:tT:Uv:w:X")) != -1) 133 switch (ch) { 134 case 'a': 135 argappend(&newfs_arg, "-a %s", optarg); 136 break; 137 case 'b': 138 argappend(&newfs_arg, "-b %s", optarg); 139 break; 140 case 'C': 141 /* Ignored for compatibility. */ 142 break; 143 case 'c': 144 argappend(&newfs_arg, "-c %s", optarg); 145 break; 146 case 'D': 147 detach = false; 148 break; 149 case 'd': 150 argappend(&newfs_arg, "-d %s", optarg); 151 break; 152 case 'E': 153 path_mdconfig = optarg; 154 break; 155 case 'e': 156 argappend(&newfs_arg, "-e %s", optarg); 157 break; 158 case 'F': 159 if (have_mdtype) 160 usage(); 161 mdtype = MD_VNODE; 162 have_mdtype = true; 163 argappend(&mdconfig_arg, "-f %s", optarg); 164 break; 165 case 'f': 166 argappend(&newfs_arg, "-f %s", optarg); 167 break; 168 case 'h': 169 usage(); 170 break; 171 case 'i': 172 argappend(&newfs_arg, "-i %s", optarg); 173 break; 174 case 'L': 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 norun = true; 191 break; 192 case 'n': 193 argappend(&newfs_arg, "-n"); 194 break; 195 case 'O': 196 argappend(&newfs_arg, "-o %s", optarg); 197 break; 198 case 'o': 199 argappend(&mount_arg, "-o %s", optarg); 200 break; 201 case 'P': 202 newfs = false; 203 break; 204 case 'p': 205 if ((set = setmode(optarg)) == NULL) 206 usage(); 207 mi.mi_mode = getmode(set, S_IRWXU | S_IRWXG | S_IRWXO); 208 mi.mi_have_mode = true; 209 mi.mi_forced_pw = true; 210 free(set); 211 break; 212 case 'S': 213 softdep = false; 214 break; 215 case 's': 216 argappend(&mdconfig_arg, "-s %s", optarg); 217 break; 218 case 't': 219 argappend(&newfs_arg, "-t"); 220 break; 221 case 'T': 222 argappend(&mount_arg, "-t %s", optarg); 223 break; 224 case 'U': 225 softdep = true; 226 break; 227 case 'v': 228 argappend(&newfs_arg, "-O %s", optarg); 229 break; 230 case 'w': 231 extract_ugid(optarg, &mi); 232 mi.mi_forced_pw = true; 233 break; 234 case 'X': 235 debug = true; 236 break; 237 default: 238 usage(); 239 } 240 argc -= optind; 241 argv += optind; 242 if (argc < 2) 243 usage(); 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 (!isdigit(*unitstr)) { 252 autounit = true; 253 unit = -1; 254 mdsuffix = unitstr; 255 } else { 256 ul = strtoul(unitstr, &p, 10); 257 if (ul == ULONG_MAX) 258 errx(1, "bad device unit: %s", unitstr); 259 unit = ul; 260 mdsuffix = p; /* can be empty */ 261 } 262 263 mtpoint = argv[1]; 264 if (!have_mdtype) 265 mdtype = MD_SWAP; 266 if (softdep) 267 argappend(&newfs_arg, "-U"); 268 if (mdtype != MD_VNODE && !newfs) 269 errx(1, "-P requires a vnode-backed disk"); 270 271 /* Do the work. */ 272 if (detach && !autounit) 273 do_mdconfig_detach(); 274 if (autounit) 275 do_mdconfig_attach_au(mdconfig_arg, mdtype); 276 else 277 do_mdconfig_attach(mdconfig_arg, mdtype); 278 if (newfs) 279 do_newfs(newfs_arg); 280 do_mount(mount_arg, mtpoint); 281 do_mtptsetup(mtpoint, &mi); 282 283 return (0); 284 } 285 286 /* 287 * Append the expansion of 'fmt' to the buffer pointed to by '*dstp'; 288 * reallocate as required. 289 */ 290 static void 291 argappend(char **dstp, const char *fmt, ...) 292 { 293 char *old, *new; 294 va_list ap; 295 296 old = *dstp; 297 assert(old != NULL); 298 299 va_start(ap, fmt); 300 if (vasprintf(&new, fmt,ap) == -1) 301 errx(1, "vasprintf"); 302 va_end(ap); 303 304 *dstp = new; 305 if (asprintf(&new, "%s %s", old, new) == -1) 306 errx(1, "asprintf"); 307 free(*dstp); 308 free(old); 309 310 *dstp = new; 311 } 312 313 /* 314 * If run-time debugging is enabled, print the expansion of 'fmt'. 315 * Otherwise, do nothing. 316 */ 317 static void 318 debugprintf(const char *fmt, ...) 319 { 320 va_list ap; 321 322 if (!debug) 323 return; 324 fprintf(stderr, "DEBUG: "); 325 va_start(ap, fmt); 326 vfprintf(stderr, fmt, ap); 327 va_end(ap); 328 fprintf(stderr, "\n"); 329 fflush(stderr); 330 } 331 332 /* 333 * Attach a memory disk with a known unit. 334 */ 335 static void 336 do_mdconfig_attach(const char *args, const enum md_types mdtype) 337 { 338 int rv; 339 const char *ta; /* Type arg. */ 340 341 switch (mdtype) { 342 case MD_SWAP: 343 ta = "-t swap"; 344 break; 345 case MD_VNODE: 346 ta = "-t vnode"; 347 break; 348 case MD_MALLOC: 349 ta = "-t malloc"; 350 break; 351 default: 352 abort(); 353 } 354 rv = run(NULL, "%s -a %s%s -u %s%d", path_mdconfig, ta, args, 355 mdname, unit); 356 if (rv) 357 errx(1, "mdconfig (attach) exited with error code %d", rv); 358 } 359 360 /* 361 * Attach a memory disk with an unknown unit; use autounit. 362 */ 363 static void 364 do_mdconfig_attach_au(const char *args, const enum md_types mdtype) 365 { 366 const char *ta; /* Type arg. */ 367 char *linep, *linebuf; /* Line pointer, line buffer. */ 368 int fd; /* Standard output of mdconfig invocation. */ 369 FILE *sfd; 370 int rv; 371 char *p; 372 size_t linelen; 373 unsigned long ul; 374 375 switch (mdtype) { 376 case MD_SWAP: 377 ta = "-t swap"; 378 break; 379 case MD_VNODE: 380 ta = "-t vnode"; 381 break; 382 case MD_MALLOC: 383 ta = "-t malloc"; 384 break; 385 default: 386 abort(); 387 } 388 rv = run(&fd, "%s -a %s%s", path_mdconfig, ta, args); 389 if (rv) 390 errx(1, "mdconfig (attach) exited with error code %d", rv); 391 392 /* Receive the unit number. */ 393 if (norun) { /* Since we didn't run, we can't read. Fake it. */ 394 unit = 0; 395 return; 396 } 397 sfd = fdopen(fd, "r"); 398 if (sfd == NULL) 399 err(1, "fdopen"); 400 linep = fgetln(sfd, &linelen); 401 if (linep == NULL && linelen < mdnamelen + 1) 402 errx(1, "unexpected output from mdconfig (attach)"); 403 /* If the output format changes, we want to know about it. */ 404 assert(strncmp(linep, mdname, mdnamelen) == 0); 405 linebuf = malloc(linelen - mdnamelen + 1); 406 assert(linebuf != NULL); 407 /* Can't use strlcpy because linep is not NULL-terminated. */ 408 strncpy(linebuf, linep + mdnamelen, linelen); 409 linebuf[linelen] = '\0'; 410 ul = strtoul(linebuf, &p, 10); 411 if (ul == ULONG_MAX || *p != '\n') 412 errx(1, "unexpected output from mdconfig (attach)"); 413 unit = ul; 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%d%s %s", _PATH_MOUNT, args, 442 mdname, unit, mdsuffix, 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 struct statfs sfs; 454 455 if (!mip->mi_have_mode && !mip->mi_have_uid && !mip->mi_have_gid) 456 return; 457 458 if (!norun) { 459 if (statfs(mtpoint, &sfs) == -1) { 460 warn("statfs: %s", mtpoint); 461 return; 462 } 463 if ((sfs.f_flags & MNT_RDONLY) != 0) { 464 if (mip->mi_forced_pw) { 465 warnx( 466 "Not changing mode/owner of %s since it is read-only", 467 mtpoint); 468 } else { 469 debugprintf( 470 "Not changing mode/owner of %s since it is read-only", 471 mtpoint); 472 } 473 return; 474 } 475 } 476 477 if (mip->mi_have_mode) { 478 debugprintf("changing mode of %s to %o.", mtpoint, 479 mip->mi_mode); 480 if (!norun) 481 if (chmod(mtpoint, mip->mi_mode) == -1) 482 err(1, "chmod: %s", mtpoint); 483 } 484 /* 485 * We have to do these separately because the user may have 486 * only specified one of them. 487 */ 488 if (mip->mi_have_uid) { 489 debugprintf("changing owner (user) or %s to %u.", mtpoint, 490 mip->mi_uid); 491 if (!norun) 492 if (chown(mtpoint, mip->mi_uid, -1) == -1) 493 err(1, "chown %s to %u (user)", mtpoint, 494 mip->mi_uid); 495 } 496 if (mip->mi_have_gid) { 497 debugprintf("changing owner (group) or %s to %u.", mtpoint, 498 mip->mi_gid); 499 if (!norun) 500 if (chown(mtpoint, -1, mip->mi_gid) == -1) 501 err(1, "chown %s to %u (group)", mtpoint, 502 mip->mi_gid); 503 } 504 } 505 506 /* 507 * Put a file system on the memory disk. 508 */ 509 static void 510 do_newfs(const char *args) 511 { 512 int rv; 513 514 rv = run(NULL, "%s%s /dev/%s%d", _PATH_NEWFS, args, mdname, unit); 515 if (rv) 516 errx(1, "newfs exited with error code %d", rv); 517 } 518 519 /* 520 * 'str' should be a user and group name similar to the last argument 521 * to chown(1); i.e., a user, followed by a colon, followed by a 522 * group. The user and group in 'str' may be either a [ug]id or a 523 * name. Upon return, the uid and gid fields in 'mip' will contain 524 * the uid and gid of the user and group name in 'str', respectively. 525 * 526 * In other words, this derives a user and group id from a string 527 * formatted like the last argument to chown(1). 528 * 529 * Notice: At this point we don't support only a username or only a 530 * group name. do_mtptsetup already does, so when this feature is 531 * desired, this is the only routine that needs to be changed. 532 */ 533 static void 534 extract_ugid(const char *str, struct mtpt_info *mip) 535 { 536 char *ug; /* Writable 'str'. */ 537 char *user, *group; /* Result of extracton. */ 538 struct passwd *pw; 539 struct group *gr; 540 char *p; 541 uid_t *uid; 542 gid_t *gid; 543 544 uid = &mip->mi_uid; 545 gid = &mip->mi_gid; 546 mip->mi_have_uid = mip->mi_have_gid = false; 547 548 /* Extract the user and group from 'str'. Format above. */ 549 ug = strdup(str); 550 assert(ug != NULL); 551 group = ug; 552 user = strsep(&group, ":"); 553 if (user == NULL || group == NULL || *user == '\0' || *group == '\0') 554 usage(); 555 556 /* Derive uid. */ 557 *uid = strtoul(user, &p, 10); 558 if (*uid == (uid_t)ULONG_MAX) 559 usage(); 560 if (*p != '\0') { 561 pw = getpwnam(user); 562 if (pw == NULL) 563 errx(1, "invalid user: %s", user); 564 *uid = pw->pw_uid; 565 } 566 mip->mi_have_uid = true; 567 568 /* Derive gid. */ 569 *gid = strtoul(group, &p, 10); 570 if (*gid == (gid_t)ULONG_MAX) 571 usage(); 572 if (*p != '\0') { 573 gr = getgrnam(group); 574 if (gr == NULL) 575 errx(1, "invalid group: %s", group); 576 *gid = gr->gr_gid; 577 } 578 mip->mi_have_gid = true; 579 580 free(ug); 581 } 582 583 /* 584 * Run a process with command name and arguments pointed to by the 585 * formatted string 'cmdline'. Since system(3) is not used, the first 586 * space-delimited token of 'cmdline' must be the full pathname of the 587 * program to run. The return value is the return code of the process 588 * spawned. If 'ofd' is non-NULL, it is set to the standard output of 589 * the program spawned (i.e., you can read from ofd and get the output 590 * of the program). 591 */ 592 static int 593 run(int *ofd, const char *cmdline, ...) 594 { 595 char **argv, **argvp; /* Result of splitting 'cmd'. */ 596 int argc; 597 char *cmd; /* Expansion of 'cmdline'. */ 598 int pid, status; /* Child info. */ 599 int pfd[2]; /* Pipe to the child. */ 600 int nfd; /* Null (/dev/null) file descriptor. */ 601 bool dup2dn; /* Dup /dev/null to stdout? */ 602 va_list ap; 603 char *p; 604 int rv, i; 605 606 dup2dn = true; 607 va_start(ap, cmdline); 608 rv = vasprintf(&cmd, cmdline, ap); 609 if (rv == -1) 610 err(1, "vasprintf"); 611 va_end(ap); 612 613 /* Split up 'cmd' into 'argv' for use with execve. */ 614 for (argc = 1, p = cmd; (p = strchr(p, ' ')) != NULL; p++) 615 argc++; /* 'argc' generation loop. */ 616 argv = (char **)malloc(sizeof(*argv) * (argc + 1)); 617 assert(argv != NULL); 618 for (p = cmd, argvp = argv; (*argvp = strsep(&p, " ")) != NULL;) 619 if (**argvp != '\0') 620 if (++argvp >= &argv[argc]) { 621 *argvp = NULL; 622 break; 623 } 624 assert(*argv); 625 /* The argv array ends up NULL-terminated here. */ 626 627 /* Make sure the above loop works as expected. */ 628 if (debug) { 629 /* 630 * We can't, but should, use debugprintf here. First, 631 * it appends a trailing newline to the output, and 632 * second it prepends "DEBUG: " to the output. The 633 * former is a problem for this would-be first call, 634 * and the latter for the would-be call inside the 635 * loop. 636 */ 637 (void)fprintf(stderr, "DEBUG: running:"); 638 /* Should be equivalent to 'cmd' (before strsep, of course). */ 639 for (i = 0; argv[i] != NULL; i++) 640 (void)fprintf(stderr, " %s", argv[i]); 641 (void)fprintf(stderr, "\n"); 642 } 643 644 /* Create a pipe if necessary and fork the helper program. */ 645 if (ofd != NULL) { 646 if (pipe(&pfd[0]) == -1) 647 err(1, "pipe"); 648 *ofd = pfd[0]; 649 dup2dn = false; 650 } 651 pid = fork(); 652 switch (pid) { 653 case 0: 654 /* XXX can we call err() in here? */ 655 if (norun) 656 _exit(0); 657 if (ofd != NULL) 658 if (dup2(pfd[1], STDOUT_FILENO) < 0) 659 err(1, "dup2"); 660 if (!loudsubs) { 661 nfd = open(_PATH_DEVNULL, O_RDWR); 662 if (nfd == -1) 663 err(1, "open: %s", _PATH_DEVNULL); 664 if (dup2(nfd, STDIN_FILENO) < 0) 665 err(1, "dup2"); 666 if (dup2dn) 667 if (dup2(nfd, STDOUT_FILENO) < 0) 668 err(1, "dup2"); 669 if (dup2(nfd, STDERR_FILENO) < 0) 670 err(1, "dup2"); 671 } 672 673 (void)execv(argv[0], argv); 674 warn("exec: %s", argv[0]); 675 _exit(-1); 676 case -1: 677 err(1, "fork"); 678 } 679 680 free(cmd); 681 free(argv); 682 while (waitpid(pid, &status, 0) != pid) 683 ; 684 return (WEXITSTATUS(status)); 685 } 686 687 static void 688 usage(void) 689 { 690 691 fprintf(stderr, 692 "usage: %s [-DLlMNnPStUX] [-a maxcontig] [-b block-size]\n" 693 "\t[-c blocks-per-cylinder-group][-d max-extent-size] [-E path-mdconfig]\n" 694 "\t[-e maxbpg] [-F file] [-f frag-size] [-i bytes] [-m percent-free]\n" 695 "\t[-O optimization] [-o mount-options]\n" 696 "\t[-p permissions] [-s size] [-v version] [-w user:group]\n" 697 "\tmd-device mount-point\n", getprogname()); 698 exit(1); 699 } 700