xref: /dragonfly/sbin/hammer/cmd_cleanup.c (revision a2003076)
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("/sbin/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 	RunningIoctl = 1;
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 
732 		while (waitpid(pid, &status, 0) != pid)
733 			;
734 		res = WEXITSTATUS(status);
735 	}
736 	RunningIoctl = 0;
737 	if (DidInterrupt)
738 		_exit(1);
739 
740 	free(cmd);
741 	free(av);
742 	if (resp)
743 		*resp = res;
744 }
745 
746 
747