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