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