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