hammer util: avoid running concurrent cleanups
[dragonfly.git] / sbin / hammer / cmd_cleanup.c
1 /*
2  * Copyright (c) 2008 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthew Dillon <dillon@backplane.com>
6  * 
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  * 
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  *
34  * $DragonFly: src/sbin/hammer/cmd_cleanup.c,v 1.6 2008/10/07 22:28:41 thomas Exp $
35  */
36 /*
37  * Clean up a specific HAMMER filesystem or all HAMMER filesystems.
38  *
39  * Each filesystem is expected to have a <mount>/snapshots directory.
40  * No cleanup will be performed on any filesystem that does not.  If
41  * no filesystems are specified the 'df' program is run and any HAMMER
42  * or null-mounted hammer PFS's are extracted.
43  *
44  * The snapshots directory may contain a config file called 'config'.  If
45  * no config file is present one will be created with the following
46  * defaults:
47  *
48  *      snapshots 1d 60d        (0d 0d for /tmp, /var/tmp, /usr/obj)
49  *      prune     1d 5m
50  *      reblock   1d 5m
51  *      recopy    30d 5m
52  *
53  * All hammer commands create and maintain cycle files in the snapshots
54  * directory.
55  */
56
57 #include "hammer.h"
58
59 struct didpfs {
60         struct didpfs *next;
61         uuid_t          uuid;
62 };
63
64 static void do_cleanup(const char *path);
65 static int strtosecs(char *ptr);
66 static const char *dividing_slash(const char *path);
67 static int check_period(const char *snapshots_path, const char *cmd, int arg1,
68                         time_t *savep);
69 static void save_period(const char *snapshots_path, const char *cmd,
70                         time_t savet);
71 static int check_softlinks(const char *snapshots_path);
72 static void cleanup_softlinks(const char *path, const char *snapshots_path,
73                         int arg2, char *arg3);
74 static int check_expired(const char *fpath, int arg2);
75
76 static int cleanup_snapshots(const char *path, const char *snapshots_path,
77                               int arg1, int arg2);
78 static int cleanup_prune(const char *path, const char *snapshots_path,
79                               int arg1, int arg2, int snapshots_disabled);
80 static int cleanup_reblock(const char *path, const char *snapshots_path,
81                               int arg1, int arg2);
82 static int cleanup_recopy(const char *path, const char *snapshots_path,
83                               int arg1, int arg2);
84
85 static void runcmd(int *resp, const char *ctl, ...);
86
87 #define WS      " \t\r\n"
88
89 struct didpfs *FirstPFS;
90
91 void
92 hammer_cmd_cleanup(char **av, int ac)
93 {
94         FILE *fp;
95         char *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         if (flock(fileno(fp), LOCK_EX|LOCK_NB) == -1) {
253                 if (errno == EWOULDBLOCK)
254                         printf(" PFS #%d locked by other process\n", pfs.pfs_id);
255                 else
256                         printf(" can not lock %s: %s\n", config_path, strerror(errno));
257                 fclose(fp);
258                 return;
259         }
260
261         printf(" handle PFS #%d using %s\n", pfs.pfs_id, snapshots_path);
262
263         /*
264          * Process the config file
265          */
266         while (fgets(buf, sizeof(buf), fp) != NULL) {
267                 cmd = strtok(buf, WS);
268                 arg1 = 0;
269                 arg2 = 0;
270                 arg3 = NULL;
271                 if ((ptr = strtok(NULL, WS)) != NULL) {
272                         arg1 = strtosecs(ptr);
273                         if ((ptr = strtok(NULL, WS)) != NULL) {
274                                 arg2 = strtosecs(ptr);
275                                 arg3 = strtok(NULL, WS);
276                         }
277                 }
278
279                 printf("%20s - ", cmd);
280                 fflush(stdout);
281
282                 r = 1;
283                 if (strcmp(cmd, "snapshots") == 0) {
284                         if (arg1 == 0) {
285                                 if (arg2 && check_softlinks(snapshots_path)) {
286                                         printf("only removing old snapshots\n");
287                                         prune_warning = 1;
288                                         cleanup_softlinks(path, snapshots_path,
289                                                           arg2, arg3);
290                                 } else {
291                                         printf("disabled\n");
292                                         snapshots_disabled = 1;
293                                 }
294                         } else
295                         if (check_period(snapshots_path, cmd, arg1, &savet)) {
296                                 printf("run\n");
297                                 cleanup_softlinks(path, snapshots_path,
298                                                   arg2, arg3);
299                                 r = cleanup_snapshots(path, snapshots_path,
300                                                   arg1, arg2);
301                         } else {
302                                 printf("skip\n");
303                         }
304                 } else if (arg1 == 0) {
305                         /*
306                          * The commands following this check can't handle
307                          * a period of 0, so call the feature disabled and
308                          * ignore the directive.
309                          */
310                         printf("disabled\n");
311                 } else if (strcmp(cmd, "prune") == 0) {
312                         if (check_period(snapshots_path, cmd, arg1, &savet)) {
313                                 if (prune_warning) {
314                                         printf("run - WARNING snapshot "
315                                                "softlinks present "
316                                                "but snapshots disabled\n");
317                                 } else {
318                                         printf("run\n");
319                                 }
320                                 r = cleanup_prune(path, snapshots_path,
321                                               arg1, arg2, snapshots_disabled);
322                         } else {
323                                 printf("skip\n");
324                         }
325                 } else if (strcmp(cmd, "reblock") == 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_reblock(path, snapshots_path,
332                                                 arg1, arg2);
333                         } else {
334                                 printf("skip\n");
335                         }
336                 } else if (strcmp(cmd, "recopy") == 0) {
337                         if (check_period(snapshots_path, cmd, arg1, &savet)) {
338                                 printf("run");
339                                 fflush(stdout);
340                                 if (VerboseOpt)
341                                         printf("\n");
342                                 r = cleanup_recopy(path, snapshots_path,
343                                                arg1, arg2);
344                         } else {
345                                 printf("skip\n");
346                         }
347                 } else {
348                         printf("unknown directive\n");
349                         r = 1;
350                 }
351                 if (r == 0)
352                         save_period(snapshots_path, cmd, savet);
353         }
354         fclose(fp);
355         usleep(1000);
356 }
357
358 static
359 int
360 strtosecs(char *ptr)
361 {
362         int val;
363
364         val = strtol(ptr, &ptr, 0);
365         switch(*ptr) {
366         case 'd':
367                 val *= 24;
368                 /* fall through */
369         case 'h':
370                 val *= 60;
371                 /* fall through */
372         case 'm':
373                 val *= 60;
374                 /* fall through */
375         case 's':
376                 break;
377         default:
378                 errx(1, "illegal suffix converting %s\n", ptr);
379                 break;
380         }
381         return(val);
382 }
383
384 static const char *
385 dividing_slash(const char *path)
386 {
387         int len = strlen(path);
388         if (len && path[len-1] == '/')
389                 return("");
390         else
391                 return("/");
392 }
393
394 /*
395  * Check whether the desired period has elapsed since the last successful
396  * run.  The run may take a while and cross a boundary so we remember the
397  * current time_t so we can save it later on.
398  *
399  * Periods in minutes, hours, or days are assumed to have been crossed
400  * if the local time crosses a minute, hour, or day boundary regardless
401  * of how close the last operation actually was.
402  */
403 static int
404 check_period(const char *snapshots_path, const char *cmd, int arg1,
405         time_t *savep)
406 {
407         char *check_path;
408         struct tm tp1;
409         struct tm tp2;
410         FILE *fp;
411         time_t baset, lastt;
412         char buf[256];
413
414         time(savep);
415         localtime_r(savep, &tp1);
416
417         /*
418          * Retrieve the start time of the last successful operation.
419          */
420         asprintf(&check_path, "%s/.%s.period", snapshots_path, cmd);
421         fp = fopen(check_path, "r");
422         free(check_path);
423         if (fp == NULL)
424                 return(1);
425         if (fgets(buf, sizeof(buf), fp) == NULL) {
426                 fclose(fp);
427                 return(1);
428         }
429         fclose(fp);
430
431         lastt = strtol(buf, NULL, 0);
432         localtime_r(&lastt, &tp2);
433
434         /*
435          * Normalize the times.  e.g. if asked to do something on a 1-day
436          * interval the operation will be performed as soon as the day
437          * turns over relative to the previous operation, even if the previous
438          * operation ran a few seconds ago just before midnight.
439          */
440         if (arg1 % 60 == 0) {
441                 tp1.tm_sec = 0;
442                 tp2.tm_sec = 0;
443         }
444         if (arg1 % (60 * 60) == 0) {
445                 tp1.tm_min = 0;
446                 tp2.tm_min = 0;
447         }
448         if (arg1 % (24 * 60 * 60) == 0) {
449                 tp1.tm_hour = 0;
450                 tp2.tm_hour = 0;
451         }
452
453         baset = mktime(&tp1);
454         lastt = mktime(&tp2);
455
456 #if 0
457         printf("%lld vs %lld\n", (long long)(baset - lastt), (long long)arg1);
458 #endif
459
460         if ((int)(baset - lastt) >= arg1)
461                 return(1);
462         return(0);
463 }
464
465 /*
466  * Store the start time of the last successful operation.
467  */
468 static void
469 save_period(const char *snapshots_path, const char *cmd,
470                         time_t savet)
471 {
472         char *ocheck_path;
473         char *ncheck_path;
474         FILE *fp;
475
476         asprintf(&ocheck_path, "%s/.%s.period", snapshots_path, cmd);
477         asprintf(&ncheck_path, "%s/.%s.period.new", snapshots_path, cmd);
478         fp = fopen(ncheck_path, "w");
479         if (fp) {
480                 fprintf(fp, "0x%08llx\n", (long long)savet);
481                 if (fclose(fp) == 0)
482                         rename(ncheck_path, ocheck_path);
483                 remove(ncheck_path);
484         } else {
485                 fprintf(stderr, "hammer: Unable to create period-file %s: %s\n",
486                         ncheck_path, strerror(errno));
487         }
488 }
489
490 /*
491  * Simply count the number of softlinks in the snapshots dir
492  */
493 static int
494 check_softlinks(const char *snapshots_path)
495 {
496         struct dirent *den;
497         struct stat st;
498         DIR *dir;
499         char *fpath;
500         int res = 0;
501
502         if ((dir = opendir(snapshots_path)) != NULL) {
503                 while ((den = readdir(dir)) != NULL) {
504                         if (den->d_name[0] == '.')
505                                 continue;
506                         asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
507                         if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode))
508                                 ++res;
509                         free(fpath);
510                 }
511                 closedir(dir);
512         }
513         return(res);
514 }
515
516 /*
517  * Clean up expired softlinks in the snapshots dir
518  */
519 static void
520 cleanup_softlinks(const char *path __unused, const char *snapshots_path,
521                   int arg2, char *arg3)
522 {
523         struct dirent *den;
524         struct stat st;
525         DIR *dir;
526         char *fpath;
527         int anylink = 0;
528
529         if (arg3 != NULL && strstr(arg3, "any") != NULL)
530                 anylink = 1;
531
532         if ((dir = opendir(snapshots_path)) != NULL) {
533                 while ((den = readdir(dir)) != NULL) {
534                         if (den->d_name[0] == '.')
535                                 continue;
536                         asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
537                         if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode) &&
538                             (anylink || strncmp(den->d_name, "snap-", 5) == 0)
539                         ) {
540                                 if (check_expired(den->d_name, arg2)) {
541                                         if (VerboseOpt) {
542                                                 printf("    expire %s\n",
543                                                         fpath);
544                                         }
545                                         remove(fpath);
546                                 }
547                         }
548                         free(fpath);
549                 }
550                 closedir(dir);
551         }
552 }
553
554 /*
555  * Take a softlink path in the form snap-yyyymmdd-hhmm and the
556  * expiration in seconds (arg2) and return non-zero if the softlink
557  * has expired.
558  */
559 static int
560 check_expired(const char *fpath, int arg2)
561 {
562         struct tm tm;
563         time_t t;
564         int year;
565         int month;
566         int day = 0;
567         int hour = 0;
568         int minute = 0;
569         int r;
570
571         while (*fpath && *fpath != '-' && *fpath != '.')
572                 ++fpath;
573         if (*fpath)
574                 ++fpath;
575
576         r = sscanf(fpath, "%4d%2d%2d-%2d%2d",
577                    &year, &month, &day, &hour, &minute);
578
579         if (r >= 3) {
580                 bzero(&tm, sizeof(tm));
581                 tm.tm_isdst = -1;
582                 tm.tm_min = minute;
583                 tm.tm_hour = hour;
584                 tm.tm_mday = day;
585                 tm.tm_mon = month - 1;
586                 tm.tm_year = year - 1900;
587                 t = mktime(&tm);
588                 if (t == (time_t)-1)
589                         return(0);
590                 t = time(NULL) - t;
591                 if ((int)t > arg2)
592                         return(1);
593         }
594         return(0);
595 }
596
597 /*
598  * Issue a snapshot.
599  */
600 static int
601 cleanup_snapshots(const char *path __unused, const char *snapshots_path,
602                   int arg1 __unused, int arg2 __unused)
603 {
604         int r;
605
606         runcmd(&r, "hammer snapshot %s %s", path, snapshots_path);
607         return(r);
608 }
609
610 static int
611 cleanup_prune(const char *path __unused, const char *snapshots_path,
612                   int arg1 __unused, int arg2, int snapshots_disabled)
613 {
614         /*
615          * If snapshots have been disabled run prune-everything instead
616          * of prune.
617          */
618         if (snapshots_disabled && arg2) {
619                 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune-everything %s",
620                         snapshots_path, arg2, path);
621         } else if (snapshots_disabled) {
622                 runcmd(NULL, "hammer prune-everything %s", path);
623         } else if (arg2) {
624                 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune %s",
625                         snapshots_path, arg2, snapshots_path);
626         } else {
627                 runcmd(NULL, "hammer prune %s", snapshots_path);
628         }
629         return(0);
630 }
631
632 static int
633 cleanup_reblock(const char *path, const char *snapshots_path,
634                   int arg1 __unused, int arg2)
635 {
636         if (VerboseOpt == 0) {
637                 printf(".");
638                 fflush(stdout);
639         }
640
641         /*
642          * When reblocking the B-Tree always reblock everything in normal
643          * mode.
644          */
645         runcmd(NULL,
646                "hammer -c %s/.reblock-1.cycle -t %d reblock-btree %s",
647                snapshots_path, arg2, path);
648         if (VerboseOpt == 0) {
649                 printf(".");
650                 fflush(stdout);
651         }
652
653         /*
654          * When reblocking the inodes always reblock everything in normal
655          * mode.
656          */
657         runcmd(NULL,
658                "hammer -c %s/.reblock-2.cycle -t %d reblock-inodes %s",
659                snapshots_path, arg2, path);
660         if (VerboseOpt == 0) {
661                 printf(".");
662                 fflush(stdout);
663         }
664
665         /*
666          * When reblocking the directories always reblock everything in normal
667          * mode.
668          */
669         runcmd(NULL,
670                "hammer -c %s/.reblock-4.cycle -t %d reblock-dirs %s",
671                snapshots_path, arg2, path);
672         if (VerboseOpt == 0) {
673                 printf(".");
674                 fflush(stdout);
675         }
676
677         /*
678          * Do not reblock all the data in normal mode.
679          */
680         runcmd(NULL,
681                "hammer -c %s/.reblock-3.cycle -t %d reblock-data %s 95",
682                snapshots_path, arg2, path);
683         if (VerboseOpt == 0)
684                 printf("\n");
685         return(0);
686 }
687
688 static int
689 cleanup_recopy(const char *path, const char *snapshots_path,
690                   int arg1 __unused, int arg2)
691 {
692         if (VerboseOpt == 0) {
693                 printf(".");
694                 fflush(stdout);
695         }
696         runcmd(NULL,
697                "hammer -c %s/.recopy-1.cycle -t %d reblock-btree %s",
698                snapshots_path, arg2, path);
699         if (VerboseOpt == 0) {
700                 printf(".");
701                 fflush(stdout);
702         }
703         runcmd(NULL,
704                "hammer -c %s/.recopy-2.cycle -t %d reblock-inodes %s",
705                snapshots_path, arg2, path);
706         if (VerboseOpt == 0) {
707                 printf(".");
708                 fflush(stdout);
709         }
710         runcmd(NULL,
711                "hammer -c %s/.recopy-4.cycle -t %d reblock-dirs %s",
712                snapshots_path, arg2, path);
713         if (VerboseOpt == 0) {
714                 printf(".");
715                 fflush(stdout);
716         }
717         runcmd(NULL,
718                "hammer -c %s/.recopy-3.cycle -t %d reblock-data %s",
719                snapshots_path, arg2, path);
720         if (VerboseOpt == 0)
721                 printf("\n");
722         return(0);
723 }
724
725 static
726 void
727 runcmd(int *resp, const char *ctl, ...)
728 {
729         va_list va;
730         char *cmd;
731         char *arg;
732         char **av;
733         int n;
734         int nmax;
735         int res;
736         pid_t pid;
737
738         /*
739          * Generate the command
740          */
741         va_start(va, ctl);
742         vasprintf(&cmd, ctl, va);
743         va_end(va);
744         if (VerboseOpt)
745                 printf("    %s\n", cmd);
746
747         /*
748          * Break us down into arguments.  We do not just use system() here
749          * because it blocks SIGINT and friends.
750          */
751         n = 0;
752         nmax = 16;
753         av = malloc(sizeof(char *) * nmax);
754
755         for (arg = strtok(cmd, WS); arg; arg = strtok(NULL, WS)) {
756                 if (n == nmax - 1) {
757                         nmax += 16;
758                         av = realloc(av, sizeof(char *) * nmax);
759                 }
760                 av[n++] = arg;
761         }
762         av[n++] = NULL;
763
764         /*
765          * Run the command.
766          */
767         RunningIoctl = 1;
768         if ((pid = fork()) == 0) {
769                 if (VerboseOpt < 2) {
770                         int fd = open("/dev/null", O_RDWR);
771                         dup2(fd, 1);
772                         close(fd);
773                 }
774                 execvp(av[0], av);
775                 _exit(127);
776         } else if (pid < 0) {
777                 res = 127;
778         } else {
779                 int status;
780
781                 while (waitpid(pid, &status, 0) != pid)
782                         ;
783                 res = WEXITSTATUS(status);
784         }
785         RunningIoctl = 0;
786         if (DidInterrupt)
787                 _exit(1);
788
789         free(cmd);
790         free(av);
791         if (resp)
792                 *resp = res;
793 }
794
795