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