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