HAMMER - Enhance cleanup utility for HAMMER v3
[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 void config_init(const char *path, struct hammer_ioc_config *config);
67 static void migrate_config(FILE *fp, struct hammer_ioc_config *config);
68 static void migrate_snapshots(int fd, const char *snapshots_path);
69 static void migrate_one_snapshot(int fd, const char *fpath,
70                         struct hammer_ioc_snapshot *snapshot);
71 static int strtosecs(char *ptr);
72 static const char *dividing_slash(const char *path);
73 static int check_period(const char *snapshots_path, const char *cmd, int arg1,
74                         time_t *savep);
75 static void save_period(const char *snapshots_path, const char *cmd,
76                         time_t savet);
77 static int check_softlinks(int fd, int new_config, const char *snapshots_path);
78 static void cleanup_softlinks(int fd, int new_config,
79                         const char *snapshots_path, int arg2, char *arg3);
80 static int check_expired(const char *fpath, int arg2);
81
82 static int create_snapshot(const char *path, const char *snapshots_path);
83 static int cleanup_rebalance(const char *path, const char *snapshots_path,
84                         int arg1, int arg2);
85 static int cleanup_prune(const char *path, const char *snapshots_path,
86                         int arg1, int arg2, int snapshots_disabled);
87 static int cleanup_reblock(const char *path, const char *snapshots_path,
88                         int arg1, int arg2);
89 static int cleanup_recopy(const char *path, const char *snapshots_path,
90                         int arg1, int arg2);
91
92 static void runcmd(int *resp, const char *ctl, ...);
93
94 /*
95  * WARNING: Do not make the SNAPSHOTS_BASE "/var/snapshots" because
96  * it will interfere with the older HAMMER VERS < 3 snapshots directory
97  * for the /var PFS.
98  */
99 #define SNAPSHOTS_BASE  "/var/hammer"   /* HAMMER VERS >= 3 */
100 #define WS      " \t\r\n"
101
102 struct didpfs *FirstPFS;
103
104 void
105 hammer_cmd_cleanup(char **av, int ac)
106 {
107         char *fstype, *fs, *path;
108         struct statfs *stfsbuf;
109         int mntsize, i;
110
111         tzset();
112         if (ac == 0) {
113                 mntsize = getmntinfo(&stfsbuf, MNT_NOWAIT);
114                 if (mntsize > 0) {
115                         for (i=0; i < mntsize; i++) {
116                                 /*
117                                  * We will cleanup in the case fstype is hammer.
118                                  * If we have null-mounted PFS, we check the
119                                  * mount source. If it looks like a PFS, we
120                                  * proceed to cleanup also.
121                                  */
122                                 fstype = stfsbuf[i].f_fstypename;
123                                 fs = stfsbuf[i].f_mntfromname;
124                                 if ((strcmp(fstype, "hammer") == 0) ||
125                                     ((strcmp(fstype, "null") == 0) &&
126                                      (strstr(fs, "/@@0x") != NULL ||
127                                       strstr(fs, "/@@-1") != NULL))) {
128                                         path = stfsbuf[i].f_mntonname;
129                                         do_cleanup(path);
130                                 }
131                         }
132                 }
133
134         } else {
135                 while (ac) {
136                         do_cleanup(*av);
137                         --ac;
138                         ++av;
139                 }
140         }
141 }
142
143 static
144 void
145 do_cleanup(const char *path)
146 {
147         struct hammer_ioc_pseudofs_rw pfs;
148         struct hammer_ioc_config config;
149         struct hammer_ioc_version version;
150         union hammer_ioc_mrecord_any mrec_tmp;
151         char *snapshots_path;
152         char *config_path;
153         struct stat st;
154         char *cmd;
155         char *ptr;
156         int arg1;
157         int arg2;
158         char *arg3;
159         time_t savet;
160         char buf[256];
161         char *cbase;
162         char *cptr;
163         FILE *fp;
164         struct didpfs *didpfs;
165         int snapshots_disabled = 0;
166         int prune_warning = 0;
167         int new_config = 0;
168         int snapshots_from_pfs = 0;
169         int fd;
170         int r;
171         int found_rebal = 0;
172
173         bzero(&pfs, sizeof(pfs));
174         bzero(&mrec_tmp, sizeof(mrec_tmp));
175         pfs.ondisk = &mrec_tmp.pfs.pfsd;
176         pfs.bytes = sizeof(mrec_tmp.pfs.pfsd);
177         pfs.pfs_id = -1;
178
179         printf("cleanup %-20s -", path);
180         fd = open(path, O_RDONLY);
181         if (fd < 0) {
182                 printf(" unable to access directory: %s\n", strerror(errno));
183                 return;
184         }
185         if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) < 0) {
186                 printf(" not a HAMMER filesystem: %s\n", strerror(errno));
187                 close(fd);
188                 return;
189         }
190         if (pfs.version != HAMMER_IOC_PSEUDOFS_VERSION) {
191                 printf(" unrecognized HAMMER version\n");
192                 close(fd);
193                 return;
194         }
195         bzero(&version, sizeof(version));
196         if (ioctl(fd, HAMMERIOC_GET_VERSION, &version) < 0) {
197                 printf(" HAMMER filesystem but couldn't retrieve version!\n");
198                 close(fd);
199                 return;
200         }
201
202         bzero(&config, sizeof(config));
203         if (version.cur_version >= 3) {
204                 if (ioctl(fd, HAMMERIOC_GET_CONFIG, &config) == 0 &&
205                     config.head.error == 0) {
206                         new_config = 1;
207                 }
208         }
209
210         /*
211          * Make sure we have not already handled this PFS.  Several nullfs
212          * mounts might alias the same PFS.
213          */
214         for (didpfs = FirstPFS; didpfs; didpfs = didpfs->next) {
215                 if (bcmp(&didpfs->uuid, &mrec_tmp.pfs.pfsd.unique_uuid, sizeof(uuid_t)) == 0) {
216                         printf(" PFS #%d already handled\n", pfs.pfs_id);
217                         close(fd);
218                         return;
219                 }
220         }
221         didpfs = malloc(sizeof(*didpfs));
222         didpfs->next = FirstPFS;
223         FirstPFS = didpfs;
224         didpfs->uuid = mrec_tmp.pfs.pfsd.unique_uuid;
225
226         /*
227          * Calculate the old snapshots directory for HAMMER VERSION < 3
228          *
229          * If the directory is explicitly specified in the PFS config
230          * we flag it and will not migrate it later.
231          */
232         if (mrec_tmp.pfs.pfsd.snapshots[0] == '/') {
233                 asprintf(&snapshots_path, "%s", mrec_tmp.pfs.pfsd.snapshots);
234                 snapshots_from_pfs = 1;
235         } else if (mrec_tmp.pfs.pfsd.snapshots[0]) {
236                 printf(" WARNING: pfs-slave's snapshots dir is not absolute\n");
237                 close(fd);
238                 return;
239         } else if (mrec_tmp.pfs.pfsd.mirror_flags & HAMMER_PFSD_SLAVE) {
240                 printf(" WARNING: must configure snapshot dir for PFS slave\n");
241                 printf("\tWe suggest <fs>/var/slaves/<name> where "
242                        "<fs> is the base HAMMER fs\n");
243                 printf("\tcontaining the slave\n");
244                 close(fd);
245                 return;
246         } else {
247                 asprintf(&snapshots_path,
248                          "%s%ssnapshots", path, dividing_slash(path));
249         }
250
251         /*
252          * Check for old-style config file
253          */
254         asprintf(&config_path, "%s/config", snapshots_path);
255         fp = fopen(config_path, "r");
256
257         /*
258          * Handle upgrades to hammer version 3, move the config
259          * file into meta-data.
260          *
261          * For the old config read the file into the config structure,
262          * we will parse it out of the config structure regardless.
263          */
264         if (version.cur_version >= 3) {
265                 if (fp) {
266                         printf("(migrating) ");
267                         fflush(stdout);
268                         migrate_config(fp, &config);
269                         migrate_snapshots(fd, snapshots_path);
270                         fclose(fp);
271                         if (ioctl(fd, HAMMERIOC_SET_CONFIG, &config) < 0) {
272                                 printf(" cannot init meta-data config!\n");
273                                 close(fd);
274                                 return;
275                         }
276                         remove(config_path);
277                 } else if (new_config == 0) {
278                         config_init(path, &config);
279                         if (ioctl(fd, HAMMERIOC_SET_CONFIG, &config) < 0) {
280                                 printf(" cannot init meta-data config!\n");
281                                 close(fd);
282                                 return;
283                         }
284                 }
285                 new_config = 1;
286         } else {
287                 /*
288                  * Create missing snapshots directory for HAMMER VERSION < 3
289                  */
290                 if (stat(snapshots_path, &st) < 0) {
291                         if (mkdir(snapshots_path, 0755) != 0) {
292                                 free(snapshots_path);
293                                 printf(" unable to create snapshot dir \"%s\": %s\n",
294                                         snapshots_path, strerror(errno));
295                                 close(fd);
296                                 return;
297                         }
298                 }
299
300                 /*
301                  *  Create missing config file for HAMMER VERSION < 3
302                  */
303                 if (fp == NULL) {
304                         config_init(path, &config);
305                         fp = fopen(config_path, "w");
306                         if (fp) {
307                                 fwrite(config.config.text, 1,
308                                         strlen(config.config.text), fp);
309                                 fclose(fp);
310                         }
311                 } else {
312                         migrate_config(fp, &config);
313                         fclose(fp);
314                 }
315         }
316
317         /*
318          * If snapshots_from_pfs is not set we calculate the new snapshots
319          * directory default (in /var) for HAMMER VERSION >= 3 and migrate
320          * the old snapshots directory over.
321          *
322          * People who have set an explicit snapshots directory will have
323          * to migrate the data manually into /var/hammer, or not bother at
324          * all.  People running slaves may wish to migrate it and then
325          * clear the snapshots specification in the PFS config for the
326          * slave.
327          */
328         if (new_config && snapshots_from_pfs == 0) {
329                 char *npath;
330
331                 assert(path[0] == '/');
332                 if (strcmp(path, "/") == 0)
333                         asprintf(&npath, "%s/root", SNAPSHOTS_BASE);
334                 else
335                         asprintf(&npath, "%s/%s", SNAPSHOTS_BASE, path + 1);
336                 if (stat(npath, &st) < 0 && errno == ENOENT) {
337                         if (stat(snapshots_path, &st) < 0 && errno == ENOENT) {
338                                 printf(" HAMMER UPGRADE: Creating snapshots\n"
339                                        "\tCreating snapshots in %s\n",
340                                        npath);
341                                 runcmd(&r, "mkdir -p %s", npath);
342                         } else {
343                                 printf(" HAMMER UPGRADE: Moving snapshots\n"
344                                        "\tMoving snapshots from %s to %s\n",
345                                        snapshots_path, npath);
346                                 runcmd(&r, "mkdir -p %s", npath);
347                                 runcmd(&r, "cpdup %s %s", snapshots_path, npath);
348                                 if (r != 0) {
349                             printf("Unable to move snapshots directory!\n");
350                             printf("Please fix this critical error.\n");
351                             printf("Aborting cleanup of %s\n", path);
352                                         close(fd);
353                                         return;
354                                 }
355                                 runcmd(&r, "rm -rf %s", snapshots_path);
356                         }
357                 }
358                 free(snapshots_path);
359                 snapshots_path = npath;
360         }
361
362         /*
363          * Lock the PFS.  fd is the base directory of the mounted PFS.
364          */
365         if (flock(fd, LOCK_EX|LOCK_NB) == -1) {
366                 if (errno == EWOULDBLOCK)
367                         printf(" PFS #%d locked by other process\n", pfs.pfs_id);
368                 else
369                         printf(" can not lock %s: %s\n", config_path, strerror(errno));
370                 close(fd);
371                 return;
372         }
373
374         printf(" handle PFS #%d using %s\n", pfs.pfs_id, snapshots_path);
375
376         /*
377          * Process the config file
378          */
379         cbase = config.config.text;
380
381         while ((cptr = strchr(cbase, '\n')) != NULL) {
382                 bcopy(cbase, buf, cptr - cbase);
383                 buf[cptr - cbase] = 0;
384                 cbase = cptr + 1;
385
386                 cmd = strtok(buf, WS);
387                 arg1 = 0;
388                 arg2 = 0;
389                 arg3 = NULL;
390                 if ((ptr = strtok(NULL, WS)) != NULL) {
391                         arg1 = strtosecs(ptr);
392                         if ((ptr = strtok(NULL, WS)) != NULL) {
393                                 arg2 = strtosecs(ptr);
394                                 arg3 = strtok(NULL, WS);
395                         }
396                 }
397
398                 printf("%20s - ", cmd);
399                 fflush(stdout);
400
401                 r = 1;
402                 if (strcmp(cmd, "snapshots") == 0) {
403                         if (arg1 == 0) {
404                                 if (arg2 &&
405                                     check_softlinks(fd, new_config,
406                                                     snapshots_path)) {
407                                         printf("only removing old snapshots\n");
408                                         prune_warning = 1;
409                                         cleanup_softlinks(fd, new_config,
410                                                           snapshots_path,
411                                                           arg2, arg3);
412                                 } else {
413                                         printf("disabled\n");
414                                         snapshots_disabled = 1;
415                                 }
416                         } else
417                         if (check_period(snapshots_path, cmd, arg1, &savet)) {
418                                 printf("run\n");
419                                 cleanup_softlinks(fd, new_config,
420                                                   snapshots_path,
421                                                   arg2, arg3);
422                                 r = create_snapshot(path, snapshots_path);
423                         } else {
424                                 printf("skip\n");
425                         }
426                 } else if (arg1 == 0) {
427                         /*
428                          * The commands following this check can't handle
429                          * a period of 0, so call the feature disabled and
430                          * ignore the directive.
431                          */
432                         printf("disabled\n");
433                 } else if (strcmp(cmd, "prune") == 0) {
434                         if (check_period(snapshots_path, cmd, arg1, &savet)) {
435                                 if (prune_warning) {
436                                         printf("run - WARNING snapshot "
437                                                "softlinks present "
438                                                "but snapshots disabled\n");
439                                 } else {
440                                         printf("run\n");
441                                 }
442                                 r = cleanup_prune(path, snapshots_path,
443                                               arg1, arg2, snapshots_disabled);
444                         } else {
445                                 printf("skip\n");
446                         }
447                 } else if (strcmp(cmd, "rebalance") == 0) {
448                         found_rebal = 1;
449                         if (check_period(snapshots_path, cmd, arg1, &savet)) {
450                                 printf("run");
451                                 fflush(stdout);
452                                 if (VerboseOpt)
453                                         printf("\n");
454                                 r = cleanup_rebalance(path, snapshots_path,
455                                                 arg1, arg2);
456                         } else {
457                                 printf("skip\n");
458                         }
459                 } else if (strcmp(cmd, "reblock") == 0) {
460                         if (check_period(snapshots_path, cmd, arg1, &savet)) {
461                                 printf("run");
462                                 fflush(stdout);
463                                 if (VerboseOpt)
464                                         printf("\n");
465                                 r = cleanup_reblock(path, snapshots_path,
466                                                 arg1, arg2);
467                         } else {
468                                 printf("skip\n");
469                         }
470                 } else if (strcmp(cmd, "recopy") == 0) {
471                         if (check_period(snapshots_path, cmd, arg1, &savet)) {
472                                 printf("run");
473                                 fflush(stdout);
474                                 if (VerboseOpt)
475                                         printf("\n");
476                                 r = cleanup_recopy(path, snapshots_path,
477                                                arg1, arg2);
478                         } else {
479                                 printf("skip\n");
480                         }
481                 } else {
482                         printf("unknown directive\n");
483                         r = 1;
484                 }
485                 if (r == 0)
486                         save_period(snapshots_path, cmd, savet);
487         }
488
489         /*
490          * Add new rebalance feature if the config doesn't have it.
491          * (old style config only).
492          */
493         if (new_config == 0 && found_rebal == 0) {
494                 if ((fp = fopen(config_path, "r+")) != NULL) {
495                         fseek(fp, 0L, 2);
496                         fprintf(fp, "rebalance 1d 5m\n");
497                         fclose(fp);
498                 }
499         }
500
501         /*
502          * Cleanup, and delay a little
503          */
504         close(fd);
505         usleep(1000);
506 }
507
508 /*
509  * Initialize new config data (new or old style)
510  */
511 static void
512 config_init(const char *path, struct hammer_ioc_config *config)
513 {
514         const char *snapshots;
515
516         if (strcmp(path, "/tmp") == 0 ||
517             strcmp(path, "/var/tmp") == 0 ||
518             strcmp(path, "/usr/obj") == 0) {
519                 snapshots = "snapshots 0d 0d\n";
520         } else {
521                 snapshots = "snapshots 1d 60d\n";
522         }
523         bzero(config->config.text, sizeof(config->config.text));
524         snprintf(config->config.text, sizeof(config->config.text) - 1, "%s%s",
525                 snapshots,
526                 "prune     1d 5m\n"
527                 "rebalance 1d 5m\n"
528                 "reblock   1d 5m\n"
529                 "recopy    30d 10m\n"
530                 "rebalance 1d 5m\n");
531 }
532
533 /*
534  * Migrate configuration data from the old snapshots/config
535  * file to the new mata-data format.
536  */
537 static void
538 migrate_config(FILE *fp, struct hammer_ioc_config *config)
539 {
540         int n;
541
542         n = fread(config->config.text, 1, sizeof(config->config.text) - 1, fp);
543         if (n >= 0)
544                 bzero(config->config.text + n, sizeof(config->config.text) - n);
545 }
546
547 /*
548  * Migrate snapshot softlinks in the snapshots directory to the
549  * new meta-data format.  The softlinks are left intact, but
550  * this way the pruning code won't lose track of them if you
551  * happen to blow away the snapshots directory.
552  */
553 static void
554 migrate_snapshots(int fd, const char *snapshots_path)
555 {
556         struct hammer_ioc_snapshot snapshot;
557         struct dirent *den;
558         struct stat st;
559         DIR *dir;
560         char *fpath;
561
562         bzero(&snapshot, sizeof(snapshot));
563
564         if ((dir = opendir(snapshots_path)) != NULL) {
565                 while ((den = readdir(dir)) != NULL) {
566                         if (den->d_name[0] == '.')
567                                 continue;
568                         asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
569                         if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode)) {
570                                 migrate_one_snapshot(fd, fpath, &snapshot);
571                         }
572                         free(fpath);
573                 }
574                 closedir(dir);
575         }
576         migrate_one_snapshot(fd, NULL, &snapshot);
577
578 }
579
580 /*
581  * Migrate a single snapshot.  If fpath is NULL the ioctl is flushed,
582  * otherwise it is flushed when it fills up.
583  */
584 static void
585 migrate_one_snapshot(int fd, const char *fpath,
586                      struct hammer_ioc_snapshot *snapshot)
587 {
588         if (fpath) {
589                 struct hammer_snapshot_data *snap;
590                 struct tm tm;
591                 time_t t;
592                 int year;
593                 int month;
594                 int day = 0;
595                 int hour = 0;
596                 int minute = 0;
597                 int r;
598                 char linkbuf[1024];
599                 const char *ptr;
600                 hammer_tid_t tid;
601
602                 t = (time_t)-1;
603                 tid = (hammer_tid_t)(int64_t)-1;
604
605                 ptr = fpath;
606                 while (*ptr && *ptr != '-' && *ptr != '.')
607                         ++ptr;
608                 if (*ptr)
609                         ++ptr;
610                 r = sscanf(ptr, "%4d%2d%2d-%2d%2d",
611                            &year, &month, &day, &hour, &minute);
612
613                 if (r >= 3) {
614                         bzero(&tm, sizeof(tm));
615                         tm.tm_isdst = -1;
616                         tm.tm_min = minute;
617                         tm.tm_hour = hour;
618                         tm.tm_mday = day;
619                         tm.tm_mon = month - 1;
620                         tm.tm_year = year - 1900;
621                         t = mktime(&tm);
622                 }
623                 bzero(linkbuf, sizeof(linkbuf));
624                 if (readlink(fpath, linkbuf, sizeof(linkbuf) - 1) > 0 &&
625                     (ptr = strrchr(linkbuf, '@')) != NULL &&
626                     ptr > linkbuf && ptr[-1] == '@') {
627                         tid = strtoull(ptr + 1, NULL, 16);
628                 }
629                 if (t != (time_t)-1 && tid != (hammer_tid_t)(int64_t)-1) {
630                         snap = &snapshot->snaps[snapshot->count];
631                         bzero(snap, sizeof(*snap));
632                         snap->tid = tid;
633                         snap->ts = (u_int64_t)t * 1000000ULL;
634                         snprintf(snap->label, sizeof(snap->label),
635                                  "migrated");
636                         ++snapshot->count;
637                 }
638         }
639
640         if ((fpath == NULL && snapshot->count) ||
641             snapshot->count == HAMMER_SNAPS_PER_IOCTL) {
642                 printf(" (%d snapshots)", snapshot->count);
643 again:
644                 if (ioctl(fd, HAMMERIOC_ADD_SNAPSHOT, snapshot) < 0) {
645                         printf("    Ioctl to migrate snapshots failed: %s\n",
646                                strerror(errno));
647                 } else if (snapshot->head.error == EALREADY) {
648                         ++snapshot->index;
649                         goto again;
650                 } else if (snapshot->head.error) {
651                         printf("    Ioctl to delete snapshots failed: %s\n",
652                                strerror(snapshot->head.error));
653                 }
654                 printf("index %d\n", snapshot->index);
655                 snapshot->index = 0;
656                 snapshot->count = 0;
657                 snapshot->head.error = 0;
658         }
659 }
660
661 static
662 int
663 strtosecs(char *ptr)
664 {
665         int val;
666
667         val = strtol(ptr, &ptr, 0);
668         switch(*ptr) {
669         case 'd':
670                 val *= 24;
671                 /* fall through */
672         case 'h':
673                 val *= 60;
674                 /* fall through */
675         case 'm':
676                 val *= 60;
677                 /* fall through */
678         case 's':
679                 break;
680         default:
681                 errx(1, "illegal suffix converting %s\n", ptr);
682                 break;
683         }
684         return(val);
685 }
686
687 static const char *
688 dividing_slash(const char *path)
689 {
690         int len = strlen(path);
691         if (len && path[len-1] == '/')
692                 return("");
693         else
694                 return("/");
695 }
696
697 /*
698  * Check whether the desired period has elapsed since the last successful
699  * run.  The run may take a while and cross a boundary so we remember the
700  * current time_t so we can save it later on.
701  *
702  * Periods in minutes, hours, or days are assumed to have been crossed
703  * if the local time crosses a minute, hour, or day boundary regardless
704  * of how close the last operation actually was.
705  */
706 static int
707 check_period(const char *snapshots_path, const char *cmd, int arg1,
708         time_t *savep)
709 {
710         char *check_path;
711         struct tm tp1;
712         struct tm tp2;
713         FILE *fp;
714         time_t baset, lastt;
715         char buf[256];
716
717         time(savep);
718         localtime_r(savep, &tp1);
719
720         /*
721          * Retrieve the start time of the last successful operation.
722          */
723         asprintf(&check_path, "%s/.%s.period", snapshots_path, cmd);
724         fp = fopen(check_path, "r");
725         free(check_path);
726         if (fp == NULL)
727                 return(1);
728         if (fgets(buf, sizeof(buf), fp) == NULL) {
729                 fclose(fp);
730                 return(1);
731         }
732         fclose(fp);
733
734         lastt = strtol(buf, NULL, 0);
735         localtime_r(&lastt, &tp2);
736
737         /*
738          * Normalize the times.  e.g. if asked to do something on a 1-day
739          * interval the operation will be performed as soon as the day
740          * turns over relative to the previous operation, even if the previous
741          * operation ran a few seconds ago just before midnight.
742          */
743         if (arg1 % 60 == 0) {
744                 tp1.tm_sec = 0;
745                 tp2.tm_sec = 0;
746         }
747         if (arg1 % (60 * 60) == 0) {
748                 tp1.tm_min = 0;
749                 tp2.tm_min = 0;
750         }
751         if (arg1 % (24 * 60 * 60) == 0) {
752                 tp1.tm_hour = 0;
753                 tp2.tm_hour = 0;
754         }
755
756         baset = mktime(&tp1);
757         lastt = mktime(&tp2);
758
759 #if 0
760         printf("%lld vs %lld\n", (long long)(baset - lastt), (long long)arg1);
761 #endif
762
763         if ((int)(baset - lastt) >= arg1)
764                 return(1);
765         return(0);
766 }
767
768 /*
769  * Store the start time of the last successful operation.
770  */
771 static void
772 save_period(const char *snapshots_path, const char *cmd,
773                         time_t savet)
774 {
775         char *ocheck_path;
776         char *ncheck_path;
777         FILE *fp;
778
779         asprintf(&ocheck_path, "%s/.%s.period", snapshots_path, cmd);
780         asprintf(&ncheck_path, "%s/.%s.period.new", snapshots_path, cmd);
781         fp = fopen(ncheck_path, "w");
782         if (fp) {
783                 fprintf(fp, "0x%08llx\n", (long long)savet);
784                 if (fclose(fp) == 0)
785                         rename(ncheck_path, ocheck_path);
786                 remove(ncheck_path);
787         } else {
788                 fprintf(stderr, "hammer: Unable to create period-file %s: %s\n",
789                         ncheck_path, strerror(errno));
790         }
791 }
792
793 /*
794  * Simply count the number of softlinks in the snapshots dir
795  */
796 static int
797 check_softlinks(int fd, int new_config, const char *snapshots_path)
798 {
799         struct dirent *den;
800         struct stat st;
801         DIR *dir;
802         char *fpath;
803         int res = 0;
804
805         /*
806          * Old-style softlink-based snapshots
807          */
808         if ((dir = opendir(snapshots_path)) != NULL) {
809                 while ((den = readdir(dir)) != NULL) {
810                         if (den->d_name[0] == '.')
811                                 continue;
812                         asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
813                         if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode))
814                                 ++res;
815                         free(fpath);
816                 }
817                 closedir(dir);
818         }
819
820         /*
821          * New-style snapshots are stored as filesystem meta-data,
822          * count those too.
823          */
824         if (new_config) {
825                 struct hammer_ioc_snapshot snapshot;
826
827                 bzero(&snapshot, sizeof(snapshot));
828                 do {
829                         if (ioctl(fd, HAMMERIOC_GET_SNAPSHOT, &snapshot) < 0) {
830                                 err(2, "hammer cleanup: check_softlink "
831                                         "snapshot error");
832                                 /* not reached */
833                         }
834                         res += snapshot.count;
835                 } while (snapshot.head.error == 0 && snapshot.count);
836         }
837         return (res);
838 }
839
840 /*
841  * Clean up expired softlinks in the snapshots dir
842  */
843 static void
844 cleanup_softlinks(int fd, int new_config,
845                   const char *snapshots_path, int arg2, char *arg3)
846 {
847         struct dirent *den;
848         struct stat st;
849         DIR *dir;
850         char *fpath;
851         int anylink = 0;
852
853         if (arg3 != NULL && strstr(arg3, "any") != NULL)
854                 anylink = 1;
855
856         if ((dir = opendir(snapshots_path)) != NULL) {
857                 while ((den = readdir(dir)) != NULL) {
858                         if (den->d_name[0] == '.')
859                                 continue;
860                         asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
861                         if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode) &&
862                             (anylink || strncmp(den->d_name, "snap-", 5) == 0)
863                         ) {
864                                 if (check_expired(den->d_name, arg2)) {
865                                         if (VerboseOpt) {
866                                                 printf("    expire %s\n",
867                                                         fpath);
868                                         }
869                                         remove(fpath);
870                                 }
871                         }
872                         free(fpath);
873                 }
874                 closedir(dir);
875         }
876
877         /*
878          * New-style snapshots are stored as filesystem meta-data,
879          * count those too.
880          */
881         if (new_config) {
882                 struct hammer_ioc_snapshot snapshot;
883                 struct hammer_ioc_snapshot dsnapshot;
884                 struct hammer_snapshot_data *snap;
885                 struct tm *tp;
886                 time_t t;
887                 char snapts[32];
888                 u_int32_t i;
889
890                 bzero(&snapshot, sizeof(snapshot));
891                 bzero(&dsnapshot, sizeof(dsnapshot));
892                 do {
893                         if (ioctl(fd, HAMMERIOC_GET_SNAPSHOT, &snapshot) < 0) {
894                                 err(2, "hammer cleanup: check_softlink "
895                                         "snapshot error");
896                                 /* not reached */
897                         }
898                         for (i = 0; i < snapshot.count; ++i) {
899                                 snap = &snapshot.snaps[i];
900                                 t = time(NULL) - snap->ts / 1000000ULL;
901                                 if ((int)t > arg2) {
902                                         dsnapshot.snaps[dsnapshot.count++] =
903                                                 *snap;
904                                 }
905                                 if ((int)t > arg2 && VerboseOpt) {
906                                         tp = localtime(&t);
907                                         strftime(snapts, sizeof(snapts),
908                                                  "%Y-%m-%d %H:%M:%S %Z", tp);
909                                         printf("    expire 0x%016jx %s %s\n",
910                                                (uintmax_t)snap->tid,
911                                                snapts,
912                                                snap->label);
913                                 }
914                                 if (dsnapshot.count == HAMMER_SNAPS_PER_IOCTL) {
915                                         if (ioctl(fd, HAMMERIOC_DEL_SNAPSHOT, &dsnapshot) < 0) {
916                                                 printf("    Ioctl to delete snapshots failed: %s\n", strerror(errno));
917                                         } else if (dsnapshot.head.error) {
918                                                 printf("    Ioctl to delete snapshots failed: %s\n", strerror(dsnapshot.head.error));
919                                         }
920                                         dsnapshot.count = 0;
921                                         dsnapshot.head.error = 0;
922                                 }
923                         }
924                 } while (snapshot.head.error == 0 && snapshot.count);
925
926                 if (dsnapshot.count) {
927                         if (ioctl(fd, HAMMERIOC_DEL_SNAPSHOT, &dsnapshot) < 0) {
928                                 printf("    Ioctl to delete snapshots failed: %s\n", strerror(errno));
929                         } else if (dsnapshot.head.error) {
930                                 printf("    Ioctl to delete snapshots failed: %s\n", strerror(dsnapshot.head.error));
931                         }
932                         dsnapshot.count = 0;
933                         dsnapshot.head.error = 0;
934                 }
935         }
936 }
937
938 /*
939  * Take a softlink path in the form snap-yyyymmdd-hhmm and the
940  * expiration in seconds (arg2) and return non-zero if the softlink
941  * has expired.
942  */
943 static int
944 check_expired(const char *fpath, int arg2)
945 {
946         struct tm tm;
947         time_t t;
948         int year;
949         int month;
950         int day = 0;
951         int hour = 0;
952         int minute = 0;
953         int r;
954
955         while (*fpath && *fpath != '-' && *fpath != '.')
956                 ++fpath;
957         if (*fpath)
958                 ++fpath;
959
960         r = sscanf(fpath, "%4d%2d%2d-%2d%2d",
961                    &year, &month, &day, &hour, &minute);
962
963         if (r >= 3) {
964                 bzero(&tm, sizeof(tm));
965                 tm.tm_isdst = -1;
966                 tm.tm_min = minute;
967                 tm.tm_hour = hour;
968                 tm.tm_mday = day;
969                 tm.tm_mon = month - 1;
970                 tm.tm_year = year - 1900;
971                 t = mktime(&tm);
972                 if (t == (time_t)-1)
973                         return(0);
974                 t = time(NULL) - t;
975                 if ((int)t > arg2)
976                         return(1);
977         }
978         return(0);
979 }
980
981 /*
982  * Issue a snapshot.
983  */
984 static int
985 create_snapshot(const char *path, const char *snapshots_path)
986 {
987         int r;
988
989         runcmd(&r, "hammer snapshot %s %s", path, snapshots_path);
990         return(r);
991 }
992
993 static int
994 cleanup_prune(const char *path __unused, const char *snapshots_path,
995                   int arg1 __unused, int arg2, int snapshots_disabled)
996 {
997         /*
998          * If snapshots have been disabled run prune-everything instead
999          * of prune.
1000          */
1001         if (snapshots_disabled && arg2) {
1002                 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune-everything %s",
1003                         snapshots_path, arg2, path);
1004         } else if (snapshots_disabled) {
1005                 runcmd(NULL, "hammer prune-everything %s", path);
1006         } else if (arg2) {
1007                 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune %s",
1008                         snapshots_path, arg2, snapshots_path);
1009         } else {
1010                 runcmd(NULL, "hammer prune %s", snapshots_path);
1011         }
1012         return(0);
1013 }
1014
1015 static int
1016 cleanup_rebalance(const char *path, const char *snapshots_path,
1017                   int arg1 __unused, int arg2)
1018 {
1019         if (VerboseOpt == 0) {
1020                 printf(".");
1021                 fflush(stdout);
1022         }
1023
1024         runcmd(NULL,
1025                "hammer -c %s/.rebalance.cycle -t %d rebalance %s",
1026                snapshots_path, arg2, path);
1027         if (VerboseOpt == 0) {
1028                 printf(".");
1029                 fflush(stdout);
1030         }
1031         if (VerboseOpt == 0)
1032                 printf("\n");
1033         return(0);
1034 }
1035
1036 static int
1037 cleanup_reblock(const char *path, const char *snapshots_path,
1038                   int arg1 __unused, int arg2)
1039 {
1040         if (VerboseOpt == 0) {
1041                 printf(".");
1042                 fflush(stdout);
1043         }
1044
1045         /*
1046          * When reblocking the B-Tree always reblock everything in normal
1047          * mode.
1048          */
1049         runcmd(NULL,
1050                "hammer -c %s/.reblock-1.cycle -t %d reblock-btree %s",
1051                snapshots_path, arg2, path);
1052         if (VerboseOpt == 0) {
1053                 printf(".");
1054                 fflush(stdout);
1055         }
1056
1057         /*
1058          * When reblocking the inodes always reblock everything in normal
1059          * mode.
1060          */
1061         runcmd(NULL,
1062                "hammer -c %s/.reblock-2.cycle -t %d reblock-inodes %s",
1063                snapshots_path, arg2, path);
1064         if (VerboseOpt == 0) {
1065                 printf(".");
1066                 fflush(stdout);
1067         }
1068
1069         /*
1070          * When reblocking the directories always reblock everything in normal
1071          * mode.
1072          */
1073         runcmd(NULL,
1074                "hammer -c %s/.reblock-4.cycle -t %d reblock-dirs %s",
1075                snapshots_path, arg2, path);
1076         if (VerboseOpt == 0) {
1077                 printf(".");
1078                 fflush(stdout);
1079         }
1080
1081         /*
1082          * Do not reblock all the data in normal mode.
1083          */
1084         runcmd(NULL,
1085                "hammer -c %s/.reblock-3.cycle -t %d reblock-data %s 95",
1086                snapshots_path, arg2, path);
1087         if (VerboseOpt == 0)
1088                 printf("\n");
1089         return(0);
1090 }
1091
1092 static int
1093 cleanup_recopy(const char *path, const char *snapshots_path,
1094                   int arg1 __unused, int arg2)
1095 {
1096         if (VerboseOpt == 0) {
1097                 printf(".");
1098                 fflush(stdout);
1099         }
1100         runcmd(NULL,
1101                "hammer -c %s/.recopy-1.cycle -t %d reblock-btree %s",
1102                snapshots_path, arg2, path);
1103         if (VerboseOpt == 0) {
1104                 printf(".");
1105                 fflush(stdout);
1106         }
1107         runcmd(NULL,
1108                "hammer -c %s/.recopy-2.cycle -t %d reblock-inodes %s",
1109                snapshots_path, arg2, path);
1110         if (VerboseOpt == 0) {
1111                 printf(".");
1112                 fflush(stdout);
1113         }
1114         runcmd(NULL,
1115                "hammer -c %s/.recopy-4.cycle -t %d reblock-dirs %s",
1116                snapshots_path, arg2, path);
1117         if (VerboseOpt == 0) {
1118                 printf(".");
1119                 fflush(stdout);
1120         }
1121         runcmd(NULL,
1122                "hammer -c %s/.recopy-3.cycle -t %d reblock-data %s",
1123                snapshots_path, arg2, path);
1124         if (VerboseOpt == 0)
1125                 printf("\n");
1126         return(0);
1127 }
1128
1129 static
1130 void
1131 runcmd(int *resp, const char *ctl, ...)
1132 {
1133         va_list va;
1134         char *cmd;
1135         char *arg;
1136         char **av;
1137         int n;
1138         int nmax;
1139         int res;
1140         pid_t pid;
1141
1142         /*
1143          * Generate the command
1144          */
1145         va_start(va, ctl);
1146         vasprintf(&cmd, ctl, va);
1147         va_end(va);
1148         if (VerboseOpt)
1149                 printf("    %s\n", cmd);
1150
1151         /*
1152          * Break us down into arguments.  We do not just use system() here
1153          * because it blocks SIGINT and friends.
1154          */
1155         n = 0;
1156         nmax = 16;
1157         av = malloc(sizeof(char *) * nmax);
1158
1159         for (arg = strtok(cmd, WS); arg; arg = strtok(NULL, WS)) {
1160                 if (n == nmax - 1) {
1161                         nmax += 16;
1162                         av = realloc(av, sizeof(char *) * nmax);
1163                 }
1164                 av[n++] = arg;
1165         }
1166         av[n++] = NULL;
1167
1168         /*
1169          * Run the command.
1170          */
1171         RunningIoctl = 1;
1172         if ((pid = fork()) == 0) {
1173                 if (VerboseOpt < 2) {
1174                         int fd = open("/dev/null", O_RDWR);
1175                         dup2(fd, 1);
1176                         close(fd);
1177                 }
1178                 execvp(av[0], av);
1179                 _exit(127);
1180         } else if (pid < 0) {
1181                 res = 127;
1182         } else {
1183                 int status;
1184
1185                 while (waitpid(pid, &status, 0) != pid)
1186                         ;
1187                 res = WEXITSTATUS(status);
1188         }
1189         RunningIoctl = 0;
1190         if (DidInterrupt)
1191                 _exit(1);
1192
1193         free(cmd);
1194         free(av);
1195         if (resp)
1196                 *resp = res;
1197 }