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