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