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