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