xref: /dragonfly/sbin/hammer/cmd_cleanup.c (revision a615f06f)
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 
743