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