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