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