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