Merge branch 'master' of ssh://crater.dragonflybsd.org/repository/git/dragonfly into...
[dragonfly.git] / sbin / hammer / cmd_cleanup.c
1 /*
2  * Copyright (c) 2008 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthew Dillon <dillon@backplane.com>
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  * 
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  * 
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  *
34  * $DragonFly: src/sbin/hammer/cmd_cleanup.c,v 1.6 2008/10/07 22:28:41 thomas Exp $
35  */
36 /*
37  * Clean up a specific HAMMER filesystem or all HAMMER filesystems.
38  *
39  * Each filesystem is expected to have a <mount>/snapshots directory.
40  * No cleanup will be performed on any filesystem that does not.  If
41  * no filesystems are specified the 'df' program is run and any HAMMER
42  * or null-mounted hammer PFS's are extracted.
43  *
44  * The snapshots directory may contain a config file called 'config'.  If
45  * no config file is present one will be created with the following
46  * defaults:
47  *
48  *      snapshots 1d 60d        (0d 0d for /tmp, /var/tmp, /usr/obj)
49  *      prune     1d 5m
50  *      reblock   1d 5m
51  *      recopy    30d 5m
52  *
53  * All hammer commands create and maintain cycle files in the snapshots
54  * directory.
55  */
56
57 #include "hammer.h"
58
59 struct didpfs {
60         struct didpfs *next;
61         uuid_t          uuid;
62 };
63
64 static void do_cleanup(const char *path);
65 static int strtosecs(char *ptr);
66 static const char *dividing_slash(const char *path);
67 static int check_period(const char *snapshots_path, const char *cmd, int arg1,
68                         time_t *savep);
69 static void save_period(const char *snapshots_path, const char *cmd,
70                         time_t savet);
71 static int check_softlinks(const char *snapshots_path);
72 static void cleanup_softlinks(const char *path, const char *snapshots_path,
73                         int arg2, char *arg3);
74 static int check_expired(const char *fpath, int arg2);
75
76 static int cleanup_snapshots(const char *path, const char *snapshots_path,
77                               int arg1, int arg2);
78 static int cleanup_prune(const char *path, const char *snapshots_path,
79                               int arg1, int arg2, int snapshots_disabled);
80 static int cleanup_reblock(const char *path, const char *snapshots_path,
81                               int arg1, int arg2);
82 static int cleanup_recopy(const char *path, const char *snapshots_path,
83                               int arg1, int arg2);
84
85 static void runcmd(int *resp, const char *ctl, ...);
86
87 #define WS      " \t\r\n"
88
89 struct didpfs *FirstPFS;
90
91 void
92 hammer_cmd_cleanup(char **av, int ac)
93 {
94         FILE *fp;
95         char *fs, *ptr, *path;
96         char buf[256];
97
98         tzset();
99         if (ac == 0) {
100                 fp = popen("mount -t hammer,null", "r");
101                 if (fp == NULL)
102                         errx(1, "hammer cleanup: 'mount' failed");
103                 while (fgets(buf, sizeof(buf), fp) != NULL) {
104                         fs = strtok(buf, WS);
105                         if (fs == NULL)
106                                 continue;
107                         ptr = strtok(NULL, WS);
108                         if (ptr == NULL)
109                                 continue;
110                         path = strtok(NULL, WS);
111                         if (path == NULL)
112                                 continue;
113                         ptr = strtok(NULL, WS);
114                         if (ptr == NULL)
115                                 continue;
116                         if ((strncmp(ptr, "(hammer,", 8) == 0) ||
117                             ((strncmp(ptr, "(null,", 6) == 0) &&
118                              (strncmp(fs, "/pfs", 4) == 0)))
119                                 do_cleanup(path);
120                 }
121                 fclose(fp);
122         } else {
123                 while (ac) {
124                         do_cleanup(*av);
125                         --ac;
126                         ++av;
127                 }
128         }
129 }
130
131 static
132 void
133 do_cleanup(const char *path)
134 {
135         struct hammer_ioc_pseudofs_rw pfs;
136         union hammer_ioc_mrecord_any mrec_tmp;
137         char *snapshots_path;
138         char *config_path;
139         struct stat st;
140         char *cmd;
141         char *ptr;
142         int arg1;
143         int arg2;
144         char *arg3;
145         time_t savet;
146         char buf[256];
147         FILE *fp;
148         struct didpfs *didpfs;
149         int snapshots_disabled = 0;
150         int prune_warning = 0;
151         int fd;
152         int r;
153
154         bzero(&pfs, sizeof(pfs));
155         bzero(&mrec_tmp, sizeof(mrec_tmp));
156         pfs.ondisk = &mrec_tmp.pfs.pfsd;
157         pfs.bytes = sizeof(mrec_tmp.pfs.pfsd);
158         pfs.pfs_id = -1;
159
160         printf("cleanup %-20s -", path);
161         fd = open(path, O_RDONLY);
162         if (fd < 0) {
163                 printf(" unable to access directory: %s\n", strerror(errno));
164                 return;
165         }
166         if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) != 0) {
167                 printf(" not a HAMMER filesystem: %s\n", strerror(errno));
168                 return;
169         }
170         close(fd);
171         if (pfs.version != HAMMER_IOC_PSEUDOFS_VERSION) {
172                 printf(" unrecognized HAMMER version\n");
173                 return;
174         }
175
176         /*
177          * Make sure we have not already handled this PFS.  Several nullfs
178          * mounts might alias the same PFS.
179          */
180         for (didpfs = FirstPFS; didpfs; didpfs = didpfs->next) {
181                 if (bcmp(&didpfs->uuid, &mrec_tmp.pfs.pfsd.unique_uuid, sizeof(uuid_t)) == 0) {
182                         printf(" PFS #%d already handled\n", pfs.pfs_id);
183                         return;
184                 }
185         }
186         didpfs = malloc(sizeof(*didpfs));
187         didpfs->next = FirstPFS;
188         FirstPFS = didpfs;
189         didpfs->uuid = mrec_tmp.pfs.pfsd.unique_uuid;
190
191         /*
192          * Figure out where the snapshot directory is.
193          */
194         if (mrec_tmp.pfs.pfsd.snapshots[0] == '/') {
195                 asprintf(&snapshots_path, "%s", mrec_tmp.pfs.pfsd.snapshots);
196         } else if (mrec_tmp.pfs.pfsd.snapshots[0]) {
197                 printf(" WARNING: pfs-slave's snapshots dir is not absolute\n");
198                 return;
199         } else if (mrec_tmp.pfs.pfsd.mirror_flags & HAMMER_PFSD_SLAVE) {
200                 printf(" WARNING: must configure snapshot dir for PFS slave\n");
201                 printf("\tWe suggest <fs>/var/slaves/<name> where "
202                        "<fs> is the base HAMMER fs\n");
203                 printf("\tcontaining the slave\n");
204                 return;
205         } else {
206                 asprintf(&snapshots_path,
207                          "%s%ssnapshots", path, dividing_slash(path));
208         }
209
210         /*
211          * Create a snapshot directory if necessary, and a config file if
212          * necessary.
213          */
214         if (stat(snapshots_path, &st) < 0) {
215                 if (mkdir(snapshots_path, 0755) != 0) {
216                         free(snapshots_path);
217                         printf(" unable to create snapshot dir \"%s\": %s\n",
218                                 snapshots_path, strerror(errno));
219                         return;
220                 }
221         }
222         asprintf(&config_path, "%s/config", snapshots_path);
223         if ((fp = fopen(config_path, "r")) == NULL) {
224                 fp = fopen(config_path, "w");
225                 if (fp == NULL) {
226                         printf(" cannot create %s: %s\n",
227                                 config_path, strerror(errno));
228                         return;
229                 }
230                 if (strcmp(path, "/tmp") == 0 ||
231                     strcmp(path, "/var/tmp") == 0 ||
232                     strcmp(path, "/usr/obj") == 0) {
233                         fprintf(fp, "snapshots 0d 0d\n");
234                 } else {
235                         fprintf(fp, "snapshots 1d 60d\n");
236                 }
237                 fprintf(fp, 
238                         "prune     1d 5m\n"
239                         "reblock   1d 5m\n"
240                         "recopy    30d 10m\n");
241                 fclose(fp);
242                 fp = fopen(config_path, "r");
243         }
244         if (fp == NULL) {
245                 printf(" cannot access %s: %s\n",
246                        config_path, strerror(errno));
247                 return;
248         }
249
250         printf(" handle PFS #%d using %s\n", pfs.pfs_id, snapshots_path);
251
252         /*
253          * Process the config file
254          */
255         while (fgets(buf, sizeof(buf), fp) != NULL) {
256                 cmd = strtok(buf, WS);
257                 arg1 = 0;
258                 arg2 = 0;
259                 arg3 = NULL;
260                 if ((ptr = strtok(NULL, WS)) != NULL) {
261                         arg1 = strtosecs(ptr);
262                         if ((ptr = strtok(NULL, WS)) != NULL) {
263                                 arg2 = strtosecs(ptr);
264                                 arg3 = strtok(NULL, WS);
265                         }
266                 }
267
268                 printf("%20s - ", cmd);
269                 fflush(stdout);
270
271                 r = 1;
272                 if (strcmp(cmd, "snapshots") == 0) {
273                         if (arg1 == 0) {
274                                 if (arg2 && check_softlinks(snapshots_path)) {
275                                         printf("only removing old snapshots\n");
276                                         prune_warning = 1;
277                                         cleanup_softlinks(path, snapshots_path,
278                                                           arg2, arg3);
279                                 } else {
280                                         printf("disabled\n");
281                                         snapshots_disabled = 1;
282                                 }
283                         } else
284                         if (check_period(snapshots_path, cmd, arg1, &savet)) {
285                                 printf("run\n");
286                                 cleanup_softlinks(path, snapshots_path,
287                                                   arg2, arg3);
288                                 r = cleanup_snapshots(path, snapshots_path,
289                                                   arg1, arg2);
290                         } else {
291                                 printf("skip\n");
292                         }
293                 } else if (arg1 == 0) {
294                         /*
295                          * The commands following this check can't handle
296                          * a period of 0, so call the feature disabled and
297                          * ignore the directive.
298                          */
299                         printf("disabled\n");
300                 } else if (strcmp(cmd, "prune") == 0) {
301                         if (check_period(snapshots_path, cmd, arg1, &savet)) {
302                                 if (prune_warning) {
303                                         printf("run - WARNING snapshot "
304                                                "softlinks present "
305                                                "but snapshots disabled\n");
306                                 } else {
307                                         printf("run\n");
308                                 }
309                                 r = cleanup_prune(path, snapshots_path,
310                                               arg1, arg2, snapshots_disabled);
311                         } else {
312                                 printf("skip\n");
313                         }
314                 } else if (strcmp(cmd, "reblock") == 0) {
315                         if (check_period(snapshots_path, cmd, arg1, &savet)) {
316                                 printf("run");
317                                 fflush(stdout);
318                                 if (VerboseOpt)
319                                         printf("\n");
320                                 r = cleanup_reblock(path, snapshots_path,
321                                                 arg1, arg2);
322                         } else {
323                                 printf("skip\n");
324                         }
325                 } else if (strcmp(cmd, "recopy") == 0) {
326                         if (check_period(snapshots_path, cmd, arg1, &savet)) {
327                                 printf("run");
328                                 fflush(stdout);
329                                 if (VerboseOpt)
330                                         printf("\n");
331                                 r = cleanup_recopy(path, snapshots_path,
332                                                arg1, arg2);
333                         } else {
334                                 printf("skip\n");
335                         }
336                 } else {
337                         printf("unknown directive\n");
338                         r = 1;
339                 }
340                 if (r == 0)
341                         save_period(snapshots_path, cmd, savet);
342         }
343         fclose(fp);
344         usleep(1000);
345 }
346
347 static
348 int
349 strtosecs(char *ptr)
350 {
351         int val;
352
353         val = strtol(ptr, &ptr, 0);
354         switch(*ptr) {
355         case 'd':
356                 val *= 24;
357                 /* fall through */
358         case 'h':
359                 val *= 60;
360                 /* fall through */
361         case 'm':
362                 val *= 60;
363                 /* fall through */
364         case 's':
365                 break;
366         default:
367                 errx(1, "illegal suffix converting %s\n", ptr);
368                 break;
369         }
370         return(val);
371 }
372
373 static const char *
374 dividing_slash(const char *path)
375 {
376         int len = strlen(path);
377         if (len && path[len-1] == '/')
378                 return("");
379         else
380                 return("/");
381 }
382
383 /*
384  * Check whether the desired period has elapsed since the last successful
385  * run.  The run may take a while and cross a boundary so we remember the
386  * current time_t so we can save it later on.
387  *
388  * Periods in minutes, hours, or days are assumed to have been crossed
389  * if the local time crosses a minute, hour, or day boundary regardless
390  * of how close the last operation actually was.
391  */
392 static int
393 check_period(const char *snapshots_path, const char *cmd, int arg1,
394         time_t *savep)
395 {
396         char *check_path;
397         struct tm tp1;
398         struct tm tp2;
399         FILE *fp;
400         time_t baset, lastt;
401         char buf[256];
402
403         time(savep);
404         localtime_r(savep, &tp1);
405
406         /*
407          * Retrieve the start time of the last successful operation.
408          */
409         asprintf(&check_path, "%s/.%s.period", snapshots_path, cmd);
410         fp = fopen(check_path, "r");
411         free(check_path);
412         if (fp == NULL)
413                 return(1);
414         if (fgets(buf, sizeof(buf), fp) == NULL) {
415                 fclose(fp);
416                 return(1);
417         }
418         fclose(fp);
419
420         lastt = strtol(buf, NULL, 0);
421         localtime_r(&lastt, &tp2);
422
423         /*
424          * Normalize the times.  e.g. if asked to do something on a 1-day
425          * interval the operation will be performed as soon as the day
426          * turns over relative to the previous operation, even if the previous
427          * operation ran a few seconds ago just before midnight.
428          */
429         if (arg1 % 60 == 0) {
430                 tp1.tm_sec = 0;
431                 tp2.tm_sec = 0;
432         }
433         if (arg1 % (60 * 60) == 0) {
434                 tp1.tm_min = 0;
435                 tp2.tm_min = 0;
436         }
437         if (arg1 % (24 * 60 * 60) == 0) {
438                 tp1.tm_hour = 0;
439                 tp2.tm_hour = 0;
440         }
441
442         baset = mktime(&tp1);
443         lastt = mktime(&tp2);
444
445 #if 0
446         printf("%lld vs %lld\n", (long long)(baset - lastt), (long long)arg1);
447 #endif
448
449         if ((int)(baset - lastt) >= arg1)
450                 return(1);
451         return(0);
452 }
453
454 /*
455  * Store the start time of the last successful operation.
456  */
457 static void
458 save_period(const char *snapshots_path, const char *cmd,
459                         time_t savet)
460 {
461         char *ocheck_path;
462         char *ncheck_path;
463         FILE *fp;
464
465         asprintf(&ocheck_path, "%s/.%s.period", snapshots_path, cmd);
466         asprintf(&ncheck_path, "%s/.%s.period.new", snapshots_path, cmd);
467         fp = fopen(ncheck_path, "w");
468         fprintf(fp, "0x%08llx\n", (long long)savet);
469         if (fclose(fp) == 0)
470                 rename(ncheck_path, ocheck_path);
471         remove(ncheck_path);
472 }
473
474 /*
475  * Simply count the number of softlinks in the snapshots dir
476  */
477 static int
478 check_softlinks(const char *snapshots_path)
479 {
480         struct dirent *den;
481         struct stat st;
482         DIR *dir;
483         char *fpath;
484         int res = 0;
485
486         if ((dir = opendir(snapshots_path)) != NULL) {
487                 while ((den = readdir(dir)) != NULL) {
488                         if (den->d_name[0] == '.')
489                                 continue;
490                         asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
491                         if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode))
492                                 ++res;
493                         free(fpath);
494                 }
495                 closedir(dir);
496         }
497         return(res);
498 }
499
500 /*
501  * Clean up expired softlinks in the snapshots dir
502  */
503 static void
504 cleanup_softlinks(const char *path __unused, const char *snapshots_path,
505                   int arg2, char *arg3)
506 {
507         struct dirent *den;
508         struct stat st;
509         DIR *dir;
510         char *fpath;
511         int anylink = 0;
512
513         if (arg3 != NULL && strstr(arg3, "any") != NULL)
514                 anylink = 1;
515
516         if ((dir = opendir(snapshots_path)) != NULL) {
517                 while ((den = readdir(dir)) != NULL) {
518                         if (den->d_name[0] == '.')
519                                 continue;
520                         asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
521                         if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode) &&
522                             (anylink || strncmp(den->d_name, "snap-", 5) == 0)
523                         ) {
524                                 if (check_expired(den->d_name, arg2)) {
525                                         if (VerboseOpt) {
526                                                 printf("    expire %s\n",
527                                                         fpath);
528                                         }
529                                         remove(fpath);
530                                 }
531                         }
532                         free(fpath);
533                 }
534                 closedir(dir);
535         }
536 }
537
538 /*
539  * Take a softlink path in the form snap-yyyymmdd-hhmm and the
540  * expiration in seconds (arg2) and return non-zero if the softlink
541  * has expired.
542  */
543 static int
544 check_expired(const char *fpath, int arg2)
545 {
546         struct tm tm;
547         time_t t;
548         int year;
549         int month;
550         int day = 0;
551         int hour = 0;
552         int minute = 0;
553         int r;
554
555         while (*fpath && *fpath != '-' && *fpath != '.')
556                 ++fpath;
557         if (*fpath)
558                 ++fpath;
559
560         r = sscanf(fpath, "%4d%2d%2d-%2d%2d",
561                    &year, &month, &day, &hour, &minute);
562
563         if (r >= 3) {
564                 bzero(&tm, sizeof(tm));
565                 tm.tm_isdst = -1;
566                 tm.tm_min = minute;
567                 tm.tm_hour = hour;
568                 tm.tm_mday = day;
569                 tm.tm_mon = month - 1;
570                 tm.tm_year = year - 1900;
571                 t = mktime(&tm);
572                 if (t == (time_t)-1)
573                         return(0);
574                 t = time(NULL) - t;
575                 if ((int)t > arg2)
576                         return(1);
577         }
578         return(0);
579 }
580
581 /*
582  * Issue a snapshot.
583  */
584 static int
585 cleanup_snapshots(const char *path __unused, const char *snapshots_path,
586                   int arg1 __unused, int arg2 __unused)
587 {
588         int r;
589
590         runcmd(&r, "hammer snapshot %s %s", path, snapshots_path);
591         return(r);
592 }
593
594 static int
595 cleanup_prune(const char *path __unused, const char *snapshots_path,
596                   int arg1 __unused, int arg2, int snapshots_disabled)
597 {
598         /*
599          * If snapshots have been disabled run prune-everything instead
600          * of prune.
601          */
602         if (snapshots_disabled && arg2) {
603                 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune-everything %s",
604                         snapshots_path, arg2, path);
605         } else if (snapshots_disabled) {
606                 runcmd(NULL, "hammer prune-everything %s", path);
607         } else if (arg2) {
608                 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune %s",
609                         snapshots_path, arg2, snapshots_path);
610         } else {
611                 runcmd(NULL, "hammer prune %s", snapshots_path);
612         }
613         return(0);
614 }
615
616 static int
617 cleanup_reblock(const char *path, const char *snapshots_path,
618                   int arg1 __unused, int arg2)
619 {
620         if (VerboseOpt == 0) {
621                 printf(".");
622                 fflush(stdout);
623         }
624         runcmd(NULL,
625                "hammer -c %s/.reblock-1.cycle -t %d reblock-btree %s 95",
626                snapshots_path, arg2, path);
627         if (VerboseOpt == 0) {
628                 printf(".");
629                 fflush(stdout);
630         }
631         runcmd(NULL,
632                "hammer -c %s/.reblock-2.cycle -t %d reblock-inodes %s 95",
633                snapshots_path, arg2, path);
634         if (VerboseOpt == 0) {
635                 printf(".");
636                 fflush(stdout);
637         }
638         runcmd(NULL,
639                "hammer -c %s/.reblock-3.cycle -t %d reblock-data %s 95",
640                snapshots_path, arg2, path);
641         if (VerboseOpt == 0)
642                 printf("\n");
643         return(0);
644 }
645
646 static int
647 cleanup_recopy(const char *path, const char *snapshots_path,
648                   int arg1 __unused, int arg2)
649 {
650         if (VerboseOpt == 0) {
651                 printf(".");
652                 fflush(stdout);
653         }
654         runcmd(NULL,
655                "hammer -c %s/.recopy-1.cycle -t %d reblock-btree %s",
656                snapshots_path, arg2, path);
657         if (VerboseOpt == 0) {
658                 printf(".");
659                 fflush(stdout);
660         }
661         runcmd(NULL,
662                "hammer -c %s/.recopy-2.cycle -t %d reblock-inodes %s",
663                snapshots_path, arg2, path);
664         if (VerboseOpt == 0) {
665                 printf(".");
666                 fflush(stdout);
667         }
668         runcmd(NULL,
669                "hammer -c %s/.recopy-3.cycle -t %d reblock-data %s",
670                snapshots_path, arg2, path);
671         if (VerboseOpt == 0)
672                 printf("\n");
673         return(0);
674 }
675
676 static
677 void
678 runcmd(int *resp, const char *ctl, ...)
679 {
680         va_list va;
681         char *cmd;
682         char *arg;
683         char **av;
684         int n;
685         int nmax;
686         int res;
687         pid_t pid;
688
689         /*
690          * Generate the command
691          */
692         va_start(va, ctl);
693         vasprintf(&cmd, ctl, va);
694         va_end(va);
695         if (VerboseOpt)
696                 printf("    %s\n", cmd);
697
698         /*
699          * Break us down into arguments.  We do not just use system() here
700          * because it blocks SIGINT and friends.
701          */
702         n = 0;
703         nmax = 16;
704         av = malloc(sizeof(char *) * nmax);
705
706         for (arg = strtok(cmd, WS); arg; arg = strtok(NULL, WS)) {
707                 if (n == nmax - 1) {
708                         nmax += 16;
709                         av = realloc(av, sizeof(char *) * nmax);
710                 }
711                 av[n++] = arg;
712         }
713         av[n++] = NULL;
714
715         /*
716          * Run the command.
717          */
718         if ((pid = fork()) == 0) {
719                 if (VerboseOpt < 2) {
720                         int fd = open("/dev/null", O_RDWR);
721                         dup2(fd, 1);
722                         close(fd);
723                 }
724                 execvp(av[0], av);
725                 _exit(127);
726         } else if (pid < 0) {
727                 res = 127;
728         } else {
729                 int status;
730                 while (waitpid(pid, &status, 0) != pid)
731                         ;
732                 res = WEXITSTATUS(status);
733         }
734
735         free(cmd);
736         free(av);
737         if (resp)
738                 *resp = res;
739 }
740
741