1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2001 Dima Dorfman. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 /* 30 * mdmfs (md/MFS) is a wrapper around mdconfig(8), 31 * newfs(8), and mount(8) that mimics the command line option set of 32 * the deprecated mount_mfs(8). 33 */ 34 35 #include <sys/cdefs.h> 36 __FBSDID("$FreeBSD$"); 37 38 #include <sys/param.h> 39 #include <sys/linker.h> 40 #include <sys/mdioctl.h> 41 #include <sys/module.h> 42 #include <sys/mount.h> 43 #include <sys/stat.h> 44 #include <sys/wait.h> 45 46 #include <assert.h> 47 #include <err.h> 48 #include <errno.h> 49 #include <fcntl.h> 50 #include <grp.h> 51 #include <inttypes.h> 52 #include <paths.h> 53 #include <pwd.h> 54 #include <stdarg.h> 55 #include <stdio.h> 56 #include <stdlib.h> 57 #include <string.h> 58 #include <ctype.h> 59 #include <unistd.h> 60 61 typedef enum { false, true } bool; 62 63 struct mtpt_info { 64 uid_t mi_uid; 65 bool mi_have_uid; 66 gid_t mi_gid; 67 bool mi_have_gid; 68 mode_t mi_mode; 69 bool mi_have_mode; 70 bool mi_forced_pw; 71 }; 72 73 static bool debug; /* Emit debugging information? */ 74 static bool loudsubs; /* Suppress output from helper programs? */ 75 static bool norun; /* Actually run the helper programs? */ 76 static int unit; /* The unit we're working with. */ 77 static const char *mdname; /* Name of memory disk device (e.g., "md"). */ 78 static const char *mdsuffix; /* Suffix of memory disk device (e.g., ".uzip"). */ 79 static size_t mdnamelen; /* Length of mdname. */ 80 static const char *path_mdconfig =_PATH_MDCONFIG; 81 82 static void argappend(char **, const char *, ...) __printflike(2, 3); 83 static void debugprintf(const char *, ...) __printflike(1, 2); 84 static void do_mdconfig_attach(const char *, const enum md_types); 85 static void do_mdconfig_attach_au(const char *, const enum md_types); 86 static void do_mdconfig_detach(void); 87 static void do_mount_md(const char *, const char *); 88 static void do_mount_tmpfs(const char *, const char *); 89 static void do_mtptsetup(const char *, struct mtpt_info *); 90 static void do_newfs(const char *); 91 static void do_copy(const char *, const char *); 92 static void extract_ugid(const char *, struct mtpt_info *); 93 static int run(int *, const char *, ...) __printflike(2, 3); 94 static const char *run_exitstr(int); 95 static int run_exitnumber(int); 96 static void usage(void); 97 98 int 99 main(int argc, char **argv) 100 { 101 struct mtpt_info mi; /* Mountpoint info. */ 102 intmax_t mdsize; 103 char *mdconfig_arg, *newfs_arg, /* Args to helper programs. */ 104 *mount_arg; 105 enum md_types mdtype; /* The type of our memory disk. */ 106 bool have_mdtype, mlmac; 107 bool detach, softdep, autounit, newfs; 108 const char *mtpoint, *size_arg, *skel, *unitstr; 109 char *p; 110 int ch, idx; 111 void *set; 112 unsigned long ul; 113 114 /* Misc. initialization. */ 115 (void)memset(&mi, '\0', sizeof(mi)); 116 detach = true; 117 softdep = true; 118 autounit = false; 119 mlmac = false; 120 newfs = true; 121 have_mdtype = false; 122 skel = NULL; 123 mdtype = MD_SWAP; 124 mdname = MD_NAME; 125 mdnamelen = strlen(mdname); 126 mdsize = 0; 127 /* 128 * Can't set these to NULL. They may be passed to the 129 * respective programs without modification. I.e., we may not 130 * receive any command-line options which will caused them to 131 * be modified. 132 */ 133 mdconfig_arg = strdup(""); 134 newfs_arg = strdup(""); 135 mount_arg = strdup(""); 136 size_arg = NULL; 137 138 /* If we were started as mount_mfs or mfs, imply -C. */ 139 if (strcmp(getprogname(), "mount_mfs") == 0 || 140 strcmp(getprogname(), "mfs") == 0) { 141 /* Make compatibility assumptions. */ 142 mi.mi_mode = 01777; 143 mi.mi_have_mode = true; 144 } 145 146 while ((ch = getopt(argc, argv, 147 "a:b:Cc:Dd:E:e:F:f:hi:k:LlMm:NnO:o:Pp:Ss:tT:Uv:w:X")) != -1) 148 switch (ch) { 149 case 'a': 150 argappend(&newfs_arg, "-a %s", optarg); 151 break; 152 case 'b': 153 argappend(&newfs_arg, "-b %s", optarg); 154 break; 155 case 'C': 156 /* Ignored for compatibility. */ 157 break; 158 case 'c': 159 argappend(&newfs_arg, "-c %s", optarg); 160 break; 161 case 'D': 162 detach = false; 163 break; 164 case 'd': 165 argappend(&newfs_arg, "-d %s", optarg); 166 break; 167 case 'E': 168 path_mdconfig = optarg; 169 break; 170 case 'e': 171 argappend(&newfs_arg, "-e %s", optarg); 172 break; 173 case 'F': 174 if (have_mdtype) 175 usage(); 176 mdtype = MD_VNODE; 177 have_mdtype = true; 178 argappend(&mdconfig_arg, "-f %s", optarg); 179 break; 180 case 'f': 181 argappend(&newfs_arg, "-f %s", optarg); 182 break; 183 case 'h': 184 usage(); 185 break; 186 case 'i': 187 argappend(&newfs_arg, "-i %s", optarg); 188 break; 189 case 'k': 190 skel = optarg; 191 break; 192 case 'L': 193 loudsubs = true; 194 break; 195 case 'l': 196 mlmac = true; 197 argappend(&newfs_arg, "-l"); 198 break; 199 case 'M': 200 if (have_mdtype) 201 usage(); 202 mdtype = MD_MALLOC; 203 have_mdtype = true; 204 argappend(&mdconfig_arg, "-o reserve"); 205 break; 206 case 'm': 207 argappend(&newfs_arg, "-m %s", optarg); 208 break; 209 case 'N': 210 norun = true; 211 break; 212 case 'n': 213 argappend(&newfs_arg, "-n"); 214 break; 215 case 'O': 216 argappend(&newfs_arg, "-o %s", optarg); 217 break; 218 case 'o': 219 argappend(&mount_arg, "-o %s", optarg); 220 break; 221 case 'P': 222 newfs = false; 223 break; 224 case 'p': 225 if ((set = setmode(optarg)) == NULL) 226 usage(); 227 mi.mi_mode = getmode(set, S_IRWXU | S_IRWXG | S_IRWXO); 228 mi.mi_have_mode = true; 229 mi.mi_forced_pw = true; 230 free(set); 231 break; 232 case 'S': 233 softdep = false; 234 break; 235 case 's': 236 size_arg = optarg; 237 break; 238 case 't': 239 argappend(&newfs_arg, "-t"); 240 break; 241 case 'T': 242 argappend(&mount_arg, "-t %s", optarg); 243 break; 244 case 'U': 245 softdep = true; 246 break; 247 case 'v': 248 argappend(&newfs_arg, "-O %s", optarg); 249 break; 250 case 'w': 251 extract_ugid(optarg, &mi); 252 mi.mi_forced_pw = true; 253 break; 254 case 'X': 255 debug = true; 256 break; 257 default: 258 usage(); 259 } 260 argc -= optind; 261 argv += optind; 262 if (argc < 2) 263 usage(); 264 265 /* 266 * Historically our size arg was passed directly to mdconfig, which 267 * treats a number without a suffix as a count of 512-byte sectors; 268 * tmpfs would treat it as a count of bytes. To get predictable 269 * behavior for 'auto' we document that the size always uses mdconfig 270 * rules. To make that work, decode the size here so it can be passed 271 * to either tmpfs or mdconfig as a count of bytes. 272 */ 273 if (size_arg != NULL) { 274 mdsize = (intmax_t)strtoumax(size_arg, &p, 0); 275 if (p == size_arg || (p[0] != 0 && p[1] != 0) || mdsize < 0) 276 errx(1, "invalid size '%s'", size_arg); 277 switch (*p) { 278 case 'p': 279 case 'P': 280 mdsize *= 1024; 281 case 't': 282 case 'T': 283 mdsize *= 1024; 284 case 'g': 285 case 'G': 286 mdsize *= 1024; 287 case 'm': 288 case 'M': 289 mdsize *= 1024; 290 case 'k': 291 case 'K': 292 mdsize *= 1024; 293 case 'b': 294 case 'B': 295 break; 296 case '\0': 297 mdsize *= 512; 298 break; 299 default: 300 errx(1, "invalid size suffix on '%s'", size_arg); 301 } 302 } 303 304 /* 305 * Based on the command line 'md-device' either mount a tmpfs filesystem 306 * or configure the md device then format and mount a filesystem on it. 307 * If the device is 'auto' use tmpfs if it is available and there is no 308 * request for multilabel MAC (which tmpfs does not support). 309 */ 310 unitstr = argv[0]; 311 mtpoint = argv[1]; 312 313 if (strcmp(unitstr, "auto") == 0) { 314 if (mlmac) 315 idx = -1; /* Must use md for mlmac. */ 316 else if ((idx = modfind("tmpfs")) == -1) 317 idx = kldload("tmpfs"); 318 if (idx == -1) 319 unitstr = "md"; 320 else 321 unitstr = "tmpfs"; 322 } 323 324 if (strcmp(unitstr, "tmpfs") == 0) { 325 if (size_arg != NULL && mdsize != 0) 326 argappend(&mount_arg, "-o size=%jd", mdsize); 327 do_mount_tmpfs(mount_arg, mtpoint); 328 } else { 329 if (size_arg != NULL) 330 argappend(&mdconfig_arg, "-s %jdB", mdsize); 331 if (strncmp(unitstr, "/dev/", 5) == 0) 332 unitstr += 5; 333 if (strncmp(unitstr, mdname, mdnamelen) == 0) 334 unitstr += mdnamelen; 335 if (!isdigit(*unitstr)) { 336 autounit = true; 337 unit = -1; 338 mdsuffix = unitstr; 339 } else { 340 ul = strtoul(unitstr, &p, 10); 341 if (ul == ULONG_MAX) 342 errx(1, "bad device unit: %s", unitstr); 343 unit = ul; 344 mdsuffix = p; /* can be empty */ 345 } 346 347 if (!have_mdtype) 348 mdtype = MD_SWAP; 349 if (softdep) 350 argappend(&newfs_arg, "-U"); 351 if (mdtype != MD_VNODE && !newfs) 352 errx(1, "-P requires a vnode-backed disk"); 353 354 /* Do the work. */ 355 if (detach && !autounit) 356 do_mdconfig_detach(); 357 if (autounit) 358 do_mdconfig_attach_au(mdconfig_arg, mdtype); 359 else 360 do_mdconfig_attach(mdconfig_arg, mdtype); 361 if (newfs) 362 do_newfs(newfs_arg); 363 do_mount_md(mount_arg, mtpoint); 364 } 365 366 do_mtptsetup(mtpoint, &mi); 367 if (skel != NULL) 368 do_copy(mtpoint, skel); 369 370 return (0); 371 } 372 373 /* 374 * Append the expansion of 'fmt' to the buffer pointed to by '*dstp'; 375 * reallocate as required. 376 */ 377 static void 378 argappend(char **dstp, const char *fmt, ...) 379 { 380 char *old, *new; 381 va_list ap; 382 383 old = *dstp; 384 assert(old != NULL); 385 386 va_start(ap, fmt); 387 if (vasprintf(&new, fmt,ap) == -1) 388 errx(1, "vasprintf"); 389 va_end(ap); 390 391 *dstp = new; 392 if (asprintf(&new, "%s %s", old, new) == -1) 393 errx(1, "asprintf"); 394 free(*dstp); 395 free(old); 396 397 *dstp = new; 398 } 399 400 /* 401 * If run-time debugging is enabled, print the expansion of 'fmt'. 402 * Otherwise, do nothing. 403 */ 404 static void 405 debugprintf(const char *fmt, ...) 406 { 407 va_list ap; 408 409 if (!debug) 410 return; 411 fprintf(stderr, "DEBUG: "); 412 va_start(ap, fmt); 413 vfprintf(stderr, fmt, ap); 414 va_end(ap); 415 fprintf(stderr, "\n"); 416 fflush(stderr); 417 } 418 419 /* 420 * Attach a memory disk with a known unit. 421 */ 422 static void 423 do_mdconfig_attach(const char *args, const enum md_types mdtype) 424 { 425 int rv; 426 const char *ta; /* Type arg. */ 427 428 switch (mdtype) { 429 case MD_SWAP: 430 ta = "-t swap"; 431 break; 432 case MD_VNODE: 433 ta = "-t vnode"; 434 break; 435 case MD_MALLOC: 436 ta = "-t malloc"; 437 break; 438 default: 439 abort(); 440 } 441 rv = run(NULL, "%s -a %s%s -u %s%d", path_mdconfig, ta, args, 442 mdname, unit); 443 if (rv) 444 errx(1, "mdconfig (attach) exited %s %d", run_exitstr(rv), 445 run_exitnumber(rv)); 446 } 447 448 /* 449 * Attach a memory disk with an unknown unit; use autounit. 450 */ 451 static void 452 do_mdconfig_attach_au(const char *args, const enum md_types mdtype) 453 { 454 const char *ta; /* Type arg. */ 455 char *linep; 456 char linebuf[12]; /* 32-bit unit (10) + '\n' (1) + '\0' (1) */ 457 int fd; /* Standard output of mdconfig invocation. */ 458 FILE *sfd; 459 int rv; 460 char *p; 461 size_t linelen; 462 unsigned long ul; 463 464 switch (mdtype) { 465 case MD_SWAP: 466 ta = "-t swap"; 467 break; 468 case MD_VNODE: 469 ta = "-t vnode"; 470 break; 471 case MD_MALLOC: 472 ta = "-t malloc"; 473 break; 474 default: 475 abort(); 476 } 477 rv = run(&fd, "%s -a %s%s", path_mdconfig, ta, args); 478 if (rv) 479 errx(1, "mdconfig (attach) exited %s %d", run_exitstr(rv), 480 run_exitnumber(rv)); 481 482 /* Receive the unit number. */ 483 if (norun) { /* Since we didn't run, we can't read. Fake it. */ 484 unit = 0; 485 return; 486 } 487 sfd = fdopen(fd, "r"); 488 if (sfd == NULL) 489 err(1, "fdopen"); 490 linep = fgetln(sfd, &linelen); 491 /* If the output format changes, we want to know about it. */ 492 if (linep == NULL || linelen <= mdnamelen + 1 || 493 linelen - mdnamelen >= sizeof(linebuf) || 494 strncmp(linep, mdname, mdnamelen) != 0) 495 errx(1, "unexpected output from mdconfig (attach)"); 496 linep += mdnamelen; 497 linelen -= mdnamelen; 498 /* Can't use strlcpy because linep is not NULL-terminated. */ 499 strncpy(linebuf, linep, linelen); 500 linebuf[linelen] = '\0'; 501 ul = strtoul(linebuf, &p, 10); 502 if (ul == ULONG_MAX || *p != '\n') 503 errx(1, "unexpected output from mdconfig (attach)"); 504 unit = ul; 505 506 fclose(sfd); 507 } 508 509 /* 510 * Detach a memory disk. 511 */ 512 static void 513 do_mdconfig_detach(void) 514 { 515 int rv; 516 517 rv = run(NULL, "%s -d -u %s%d", path_mdconfig, mdname, unit); 518 if (rv && debug) /* This is allowed to fail. */ 519 warnx("mdconfig (detach) exited %s %d (ignored)", 520 run_exitstr(rv), run_exitnumber(rv)); 521 } 522 523 /* 524 * Mount the configured memory disk. 525 */ 526 static void 527 do_mount_md(const char *args, const char *mtpoint) 528 { 529 int rv; 530 531 rv = run(NULL, "%s%s /dev/%s%d%s %s", _PATH_MOUNT, args, 532 mdname, unit, mdsuffix, mtpoint); 533 if (rv) 534 errx(1, "mount exited %s %d", run_exitstr(rv), 535 run_exitnumber(rv)); 536 } 537 538 /* 539 * Mount the configured tmpfs. 540 */ 541 static void 542 do_mount_tmpfs(const char *args, const char *mtpoint) 543 { 544 int rv; 545 546 rv = run(NULL, "%s -t tmpfs %s tmp %s", _PATH_MOUNT, args, mtpoint); 547 if (rv) 548 errx(1, "tmpfs mount exited %s %d", run_exitstr(rv), 549 run_exitnumber(rv)); 550 } 551 552 /* 553 * Various configuration of the mountpoint. Mostly, enact 'mip'. 554 */ 555 static void 556 do_mtptsetup(const char *mtpoint, struct mtpt_info *mip) 557 { 558 struct statfs sfs; 559 560 if (!mip->mi_have_mode && !mip->mi_have_uid && !mip->mi_have_gid) 561 return; 562 563 if (!norun) { 564 if (statfs(mtpoint, &sfs) == -1) { 565 warn("statfs: %s", mtpoint); 566 return; 567 } 568 if ((sfs.f_flags & MNT_RDONLY) != 0) { 569 if (mip->mi_forced_pw) { 570 warnx( 571 "Not changing mode/owner of %s since it is read-only", 572 mtpoint); 573 } else { 574 debugprintf( 575 "Not changing mode/owner of %s since it is read-only", 576 mtpoint); 577 } 578 return; 579 } 580 } 581 582 if (mip->mi_have_mode) { 583 debugprintf("changing mode of %s to %o.", mtpoint, 584 mip->mi_mode); 585 if (!norun) 586 if (chmod(mtpoint, mip->mi_mode) == -1) 587 err(1, "chmod: %s", mtpoint); 588 } 589 /* 590 * We have to do these separately because the user may have 591 * only specified one of them. 592 */ 593 if (mip->mi_have_uid) { 594 debugprintf("changing owner (user) or %s to %u.", mtpoint, 595 mip->mi_uid); 596 if (!norun) 597 if (chown(mtpoint, mip->mi_uid, -1) == -1) 598 err(1, "chown %s to %u (user)", mtpoint, 599 mip->mi_uid); 600 } 601 if (mip->mi_have_gid) { 602 debugprintf("changing owner (group) or %s to %u.", mtpoint, 603 mip->mi_gid); 604 if (!norun) 605 if (chown(mtpoint, -1, mip->mi_gid) == -1) 606 err(1, "chown %s to %u (group)", mtpoint, 607 mip->mi_gid); 608 } 609 } 610 611 /* 612 * Put a file system on the memory disk. 613 */ 614 static void 615 do_newfs(const char *args) 616 { 617 int rv; 618 619 rv = run(NULL, "%s%s /dev/%s%d", _PATH_NEWFS, args, mdname, unit); 620 if (rv) 621 errx(1, "newfs exited %s %d", run_exitstr(rv), 622 run_exitnumber(rv)); 623 } 624 625 626 /* 627 * Copy skel into the mountpoint. 628 */ 629 static void 630 do_copy(const char *mtpoint, const char *skel) 631 { 632 int rv; 633 634 rv = chdir(skel); 635 if (rv != 0) 636 err(1, "chdir to %s", skel); 637 rv = run(NULL, "/bin/pax -rw -pe . %s", mtpoint); 638 if (rv != 0) 639 errx(1, "skel copy failed"); 640 } 641 642 /* 643 * 'str' should be a user and group name similar to the last argument 644 * to chown(1); i.e., a user, followed by a colon, followed by a 645 * group. The user and group in 'str' may be either a [ug]id or a 646 * name. Upon return, the uid and gid fields in 'mip' will contain 647 * the uid and gid of the user and group name in 'str', respectively. 648 * 649 * In other words, this derives a user and group id from a string 650 * formatted like the last argument to chown(1). 651 * 652 * Notice: At this point we don't support only a username or only a 653 * group name. do_mtptsetup already does, so when this feature is 654 * desired, this is the only routine that needs to be changed. 655 */ 656 static void 657 extract_ugid(const char *str, struct mtpt_info *mip) 658 { 659 char *ug; /* Writable 'str'. */ 660 char *user, *group; /* Result of extracton. */ 661 struct passwd *pw; 662 struct group *gr; 663 char *p; 664 uid_t *uid; 665 gid_t *gid; 666 667 uid = &mip->mi_uid; 668 gid = &mip->mi_gid; 669 mip->mi_have_uid = mip->mi_have_gid = false; 670 671 /* Extract the user and group from 'str'. Format above. */ 672 ug = strdup(str); 673 assert(ug != NULL); 674 group = ug; 675 user = strsep(&group, ":"); 676 if (user == NULL || group == NULL || *user == '\0' || *group == '\0') 677 usage(); 678 679 /* Derive uid. */ 680 *uid = strtoul(user, &p, 10); 681 if (*uid == (uid_t)ULONG_MAX) 682 usage(); 683 if (*p != '\0') { 684 pw = getpwnam(user); 685 if (pw == NULL) 686 errx(1, "invalid user: %s", user); 687 *uid = pw->pw_uid; 688 } 689 mip->mi_have_uid = true; 690 691 /* Derive gid. */ 692 *gid = strtoul(group, &p, 10); 693 if (*gid == (gid_t)ULONG_MAX) 694 usage(); 695 if (*p != '\0') { 696 gr = getgrnam(group); 697 if (gr == NULL) 698 errx(1, "invalid group: %s", group); 699 *gid = gr->gr_gid; 700 } 701 mip->mi_have_gid = true; 702 703 free(ug); 704 } 705 706 /* 707 * Run a process with command name and arguments pointed to by the 708 * formatted string 'cmdline'. Since system(3) is not used, the first 709 * space-delimited token of 'cmdline' must be the full pathname of the 710 * program to run. 711 * 712 * The return value is the return code of the process spawned, or a negative 713 * signal number if the process exited due to an uncaught signal. 714 * 715 * If 'ofd' is non-NULL, it is set to the standard output of 716 * the program spawned (i.e., you can read from ofd and get the output 717 * of the program). 718 */ 719 static int 720 run(int *ofd, const char *cmdline, ...) 721 { 722 char **argv, **argvp; /* Result of splitting 'cmd'. */ 723 int argc; 724 char *cmd; /* Expansion of 'cmdline'. */ 725 int pid, status; /* Child info. */ 726 int pfd[2]; /* Pipe to the child. */ 727 int nfd; /* Null (/dev/null) file descriptor. */ 728 bool dup2dn; /* Dup /dev/null to stdout? */ 729 va_list ap; 730 char *p; 731 int rv, i; 732 733 dup2dn = true; 734 va_start(ap, cmdline); 735 rv = vasprintf(&cmd, cmdline, ap); 736 if (rv == -1) 737 err(1, "vasprintf"); 738 va_end(ap); 739 740 /* Split up 'cmd' into 'argv' for use with execve. */ 741 for (argc = 1, p = cmd; (p = strchr(p, ' ')) != NULL; p++) 742 argc++; /* 'argc' generation loop. */ 743 argv = (char **)malloc(sizeof(*argv) * (argc + 1)); 744 assert(argv != NULL); 745 for (p = cmd, argvp = argv; (*argvp = strsep(&p, " ")) != NULL;) 746 if (**argvp != '\0') 747 if (++argvp >= &argv[argc]) { 748 *argvp = NULL; 749 break; 750 } 751 assert(*argv); 752 /* The argv array ends up NULL-terminated here. */ 753 754 /* Make sure the above loop works as expected. */ 755 if (debug) { 756 /* 757 * We can't, but should, use debugprintf here. First, 758 * it appends a trailing newline to the output, and 759 * second it prepends "DEBUG: " to the output. The 760 * former is a problem for this would-be first call, 761 * and the latter for the would-be call inside the 762 * loop. 763 */ 764 (void)fprintf(stderr, "DEBUG: running:"); 765 /* Should be equivalent to 'cmd' (before strsep, of course). */ 766 for (i = 0; argv[i] != NULL; i++) 767 (void)fprintf(stderr, " %s", argv[i]); 768 (void)fprintf(stderr, "\n"); 769 } 770 771 /* Create a pipe if necessary and fork the helper program. */ 772 if (ofd != NULL) { 773 if (pipe(&pfd[0]) == -1) 774 err(1, "pipe"); 775 *ofd = pfd[0]; 776 dup2dn = false; 777 } 778 pid = fork(); 779 switch (pid) { 780 case 0: 781 /* XXX can we call err() in here? */ 782 if (norun) 783 _exit(0); 784 if (ofd != NULL) 785 if (dup2(pfd[1], STDOUT_FILENO) < 0) 786 err(1, "dup2"); 787 if (!loudsubs) { 788 nfd = open(_PATH_DEVNULL, O_RDWR); 789 if (nfd == -1) 790 err(1, "open: %s", _PATH_DEVNULL); 791 if (dup2(nfd, STDIN_FILENO) < 0) 792 err(1, "dup2"); 793 if (dup2dn) 794 if (dup2(nfd, STDOUT_FILENO) < 0) 795 err(1, "dup2"); 796 if (dup2(nfd, STDERR_FILENO) < 0) 797 err(1, "dup2"); 798 } 799 800 (void)execv(argv[0], argv); 801 warn("exec: %s", argv[0]); 802 _exit(-1); 803 case -1: 804 err(1, "fork"); 805 } 806 807 free(cmd); 808 free(argv); 809 while (waitpid(pid, &status, 0) != pid) 810 ; 811 if (WIFEXITED(status)) 812 return (WEXITSTATUS(status)); 813 if (WIFSIGNALED(status)) 814 return (-WTERMSIG(status)); 815 err(1, "unexpected waitpid status: 0x%x", status); 816 } 817 818 /* 819 * If run() returns non-zero, provide a string explaining why. 820 */ 821 static const char * 822 run_exitstr(int rv) 823 { 824 if (rv > 0) 825 return ("with error code"); 826 if (rv < 0) 827 return ("with signal"); 828 return (NULL); 829 } 830 831 /* 832 * If run returns non-zero, provide a relevant number. 833 */ 834 static int 835 run_exitnumber(int rv) 836 { 837 if (rv < 0) 838 return (-rv); 839 return (rv); 840 } 841 842 static void 843 usage(void) 844 { 845 846 fprintf(stderr, 847 "usage: %s [-DLlMNnPStUX] [-a maxcontig] [-b block-size]\n" 848 "\t[-c blocks-per-cylinder-group][-d max-extent-size] [-E path-mdconfig]\n" 849 "\t[-e maxbpg] [-F file] [-f frag-size] [-i bytes] [-k skel]\n" 850 "\t[-m percent-free] [-O optimization] [-o mount-options]\n" 851 "\t[-p permissions] [-s size] [-v version] [-w user:group]\n" 852 "\tmd-device mount-point\n", getprogname()); 853 exit(1); 854 } 855