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