503032072033d0000f738b2590fb39feabb724f2
[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 10m
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 }
535
536 /*
537  * Migrate configuration data from the old snapshots/config
538  * file to the new meta-data format.
539  */
540 static void
541 migrate_config(FILE *fp, struct hammer_ioc_config *config)
542 {
543         int n;
544
545         n = fread(config->config.text, 1, sizeof(config->config.text) - 1, fp);
546         if (n >= 0)
547                 bzero(config->config.text + n, sizeof(config->config.text) - n);
548 }
549
550 /*
551  * Migrate snapshot softlinks in the snapshots directory to the
552  * new meta-data format.  The softlinks are left intact, but
553  * this way the pruning code won't lose track of them if you
554  * happen to blow away the snapshots directory.
555  */
556 static void
557 migrate_snapshots(int fd, const char *snapshots_path)
558 {
559         struct hammer_ioc_snapshot snapshot;
560         struct dirent *den;
561         struct stat st;
562         DIR *dir;
563         char *fpath;
564
565         bzero(&snapshot, sizeof(snapshot));
566
567         if ((dir = opendir(snapshots_path)) != NULL) {
568                 while ((den = readdir(dir)) != NULL) {
569                         if (den->d_name[0] == '.')
570                                 continue;
571                         asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
572                         if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode)) {
573                                 migrate_one_snapshot(fd, fpath, &snapshot);
574                         }
575                         free(fpath);
576                 }
577                 closedir(dir);
578         }
579         migrate_one_snapshot(fd, NULL, &snapshot);
580
581 }
582
583 /*
584  * Migrate a single snapshot.  If fpath is NULL the ioctl is flushed,
585  * otherwise it is flushed when it fills up.
586  */
587 static void
588 migrate_one_snapshot(int fd, const char *fpath,
589                      struct hammer_ioc_snapshot *snapshot)
590 {
591         if (fpath) {
592                 struct hammer_snapshot_data *snap;
593                 struct tm tm;
594                 time_t t;
595                 int year;
596                 int month;
597                 int day = 0;
598                 int hour = 0;
599                 int minute = 0;
600                 int r;
601                 char linkbuf[1024];
602                 const char *ptr;
603                 hammer_tid_t tid;
604
605                 t = (time_t)-1;
606                 tid = (hammer_tid_t)(int64_t)-1;
607
608                 ptr = fpath;
609                 while (*ptr && *ptr != '-' && *ptr != '.')
610                         ++ptr;
611                 if (*ptr)
612                         ++ptr;
613                 r = sscanf(ptr, "%4d%2d%2d-%2d%2d",
614                            &year, &month, &day, &hour, &minute);
615
616                 if (r >= 3) {
617                         bzero(&tm, sizeof(tm));
618                         tm.tm_isdst = -1;
619                         tm.tm_min = minute;
620                         tm.tm_hour = hour;
621                         tm.tm_mday = day;
622                         tm.tm_mon = month - 1;
623                         tm.tm_year = year - 1900;
624                         t = mktime(&tm);
625                 }
626                 bzero(linkbuf, sizeof(linkbuf));
627                 if (readlink(fpath, linkbuf, sizeof(linkbuf) - 1) > 0 &&
628                     (ptr = strrchr(linkbuf, '@')) != NULL &&
629                     ptr > linkbuf && ptr[-1] == '@') {
630                         tid = strtoull(ptr + 1, NULL, 16);
631                 }
632                 if (t != (time_t)-1 && tid != (hammer_tid_t)(int64_t)-1) {
633                         snap = &snapshot->snaps[snapshot->count];
634                         bzero(snap, sizeof(*snap));
635                         snap->tid = tid;
636                         snap->ts = (u_int64_t)t * 1000000ULL;
637                         snprintf(snap->label, sizeof(snap->label),
638                                  "migrated");
639                         ++snapshot->count;
640                 }
641         }
642
643         if ((fpath == NULL && snapshot->count) ||
644             snapshot->count == HAMMER_SNAPS_PER_IOCTL) {
645                 printf(" (%d snapshots)", snapshot->count);
646 again:
647                 if (ioctl(fd, HAMMERIOC_ADD_SNAPSHOT, snapshot) < 0) {
648                         printf("    Ioctl to migrate snapshots failed: %s\n",
649                                strerror(errno));
650                 } else if (snapshot->head.error == EALREADY) {
651                         ++snapshot->index;
652                         goto again;
653                 } else if (snapshot->head.error) {
654                         printf("    Ioctl to migrate snapshots failed: %s\n",
655                                strerror(snapshot->head.error));
656                 }
657                 printf("index %d\n", snapshot->index);
658                 snapshot->index = 0;
659                 snapshot->count = 0;
660                 snapshot->head.error = 0;
661         }
662 }
663
664 static
665 int
666 strtosecs(char *ptr)
667 {
668         int val;
669
670         val = strtol(ptr, &ptr, 0);
671         switch(*ptr) {
672         case 'd':
673                 val *= 24;
674                 /* fall through */
675         case 'h':
676                 val *= 60;
677                 /* fall through */
678         case 'm':
679                 val *= 60;
680                 /* fall through */
681         case 's':
682                 break;
683         default:
684                 errx(1, "illegal suffix converting %s\n", ptr);
685                 break;
686         }
687         return(val);
688 }
689
690 static const char *
691 dividing_slash(const char *path)
692 {
693         int len = strlen(path);
694         if (len && path[len-1] == '/')
695                 return("");
696         else
697                 return("/");
698 }
699
700 /*
701  * Check whether the desired period has elapsed since the last successful
702  * run.  The run may take a while and cross a boundary so we remember the
703  * current time_t so we can save it later on.
704  *
705  * Periods in minutes, hours, or days are assumed to have been crossed
706  * if the local time crosses a minute, hour, or day boundary regardless
707  * of how close the last operation actually was.
708  */
709 static int
710 check_period(const char *snapshots_path, const char *cmd, int arg1,
711         time_t *savep)
712 {
713         char *check_path;
714         struct tm tp1;
715         struct tm tp2;
716         FILE *fp;
717         time_t baset, lastt;
718         char buf[256];
719
720         time(savep);
721         localtime_r(savep, &tp1);
722
723         /*
724          * Retrieve the start time of the last successful operation.
725          */
726         asprintf(&check_path, "%s/.%s.period", snapshots_path, cmd);
727         fp = fopen(check_path, "r");
728         free(check_path);
729         if (fp == NULL)
730                 return(1);
731         if (fgets(buf, sizeof(buf), fp) == NULL) {
732                 fclose(fp);
733                 return(1);
734         }
735         fclose(fp);
736
737         lastt = strtol(buf, NULL, 0);
738         localtime_r(&lastt, &tp2);
739
740         /*
741          * Normalize the times.  e.g. if asked to do something on a 1-day
742          * interval the operation will be performed as soon as the day
743          * turns over relative to the previous operation, even if the previous
744          * operation ran a few seconds ago just before midnight.
745          */
746         if (arg1 % 60 == 0) {
747                 tp1.tm_sec = 0;
748                 tp2.tm_sec = 0;
749         }
750         if (arg1 % (60 * 60) == 0) {
751                 tp1.tm_min = 0;
752                 tp2.tm_min = 0;
753         }
754         if (arg1 % (24 * 60 * 60) == 0) {
755                 tp1.tm_hour = 0;
756                 tp2.tm_hour = 0;
757         }
758
759         baset = mktime(&tp1);
760         lastt = mktime(&tp2);
761
762 #if 0
763         printf("%lld vs %lld\n", (long long)(baset - lastt), (long long)arg1);
764 #endif
765
766         if ((int)(baset - lastt) >= arg1)
767                 return(1);
768         return(0);
769 }
770
771 /*
772  * Store the start time of the last successful operation.
773  */
774 static void
775 save_period(const char *snapshots_path, const char *cmd,
776                         time_t savet)
777 {
778         char *ocheck_path;
779         char *ncheck_path;
780         FILE *fp;
781
782         asprintf(&ocheck_path, "%s/.%s.period", snapshots_path, cmd);
783         asprintf(&ncheck_path, "%s/.%s.period.new", snapshots_path, cmd);
784         fp = fopen(ncheck_path, "w");
785         if (fp) {
786                 fprintf(fp, "0x%08llx\n", (long long)savet);
787                 if (fclose(fp) == 0)
788                         rename(ncheck_path, ocheck_path);
789                 remove(ncheck_path);
790         } else {
791                 fprintf(stderr, "hammer: Unable to create period-file %s: %s\n",
792                         ncheck_path, strerror(errno));
793         }
794 }
795
796 /*
797  * Simply count the number of softlinks in the snapshots dir
798  */
799 static int
800 check_softlinks(int fd, int new_config, const char *snapshots_path)
801 {
802         struct dirent *den;
803         struct stat st;
804         DIR *dir;
805         char *fpath;
806         int res = 0;
807
808         /*
809          * Old-style softlink-based snapshots
810          */
811         if ((dir = opendir(snapshots_path)) != NULL) {
812                 while ((den = readdir(dir)) != NULL) {
813                         if (den->d_name[0] == '.')
814                                 continue;
815                         asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
816                         if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode))
817                                 ++res;
818                         free(fpath);
819                 }
820                 closedir(dir);
821         }
822
823         /*
824          * New-style snapshots are stored as filesystem meta-data,
825          * count those too.
826          */
827         if (new_config) {
828                 struct hammer_ioc_snapshot snapshot;
829
830                 bzero(&snapshot, sizeof(snapshot));
831                 do {
832                         if (ioctl(fd, HAMMERIOC_GET_SNAPSHOT, &snapshot) < 0) {
833                                 err(2, "hammer cleanup: check_softlink "
834                                         "snapshot error");
835                                 /* not reached */
836                         }
837                         res += snapshot.count;
838                 } while (snapshot.head.error == 0 && snapshot.count);
839         }
840         return (res);
841 }
842
843 /*
844  * Clean up expired softlinks in the snapshots dir
845  */
846 static void
847 cleanup_softlinks(int fd, int new_config,
848                   const char *snapshots_path, int arg2, char *arg3)
849 {
850         struct dirent *den;
851         struct stat st;
852         DIR *dir;
853         char *fpath;
854         int anylink = 0;
855
856         if (arg3 != NULL && strstr(arg3, "any") != NULL)
857                 anylink = 1;
858
859         if ((dir = opendir(snapshots_path)) != NULL) {
860                 while ((den = readdir(dir)) != NULL) {
861                         if (den->d_name[0] == '.')
862                                 continue;
863                         asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
864                         if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode) &&
865                             (anylink || strncmp(den->d_name, "snap-", 5) == 0)
866                         ) {
867                                 if (check_expired(den->d_name, arg2)) {
868                                         if (VerboseOpt) {
869                                                 printf("    expire %s\n",
870                                                         fpath);
871                                         }
872                                         remove(fpath);
873                                 }
874                         }
875                         free(fpath);
876                 }
877                 closedir(dir);
878         }
879
880         /*
881          * New-style snapshots are stored as filesystem meta-data,
882          * count those too.
883          */
884         if (new_config) {
885                 struct hammer_ioc_snapshot snapshot;
886                 struct hammer_ioc_snapshot dsnapshot;
887                 struct hammer_snapshot_data *snap;
888                 struct tm *tp;
889                 time_t t;
890                 char snapts[32];
891                 u_int32_t i;
892
893                 bzero(&snapshot, sizeof(snapshot));
894                 bzero(&dsnapshot, sizeof(dsnapshot));
895                 do {
896                         if (ioctl(fd, HAMMERIOC_GET_SNAPSHOT, &snapshot) < 0) {
897                                 err(2, "hammer cleanup: check_softlink "
898                                         "snapshot error");
899                                 /* not reached */
900                         }
901                         for (i = 0; i < snapshot.count; ++i) {
902                                 snap = &snapshot.snaps[i];
903                                 t = time(NULL) - snap->ts / 1000000ULL;
904                                 if ((int)t > arg2 && snap->tid != 0) {
905                                         dsnapshot.snaps[dsnapshot.count++] =
906                                                 *snap;
907                                 }
908                                 if ((int)t > arg2 && VerboseOpt) {
909                                         tp = localtime(&t);
910                                         strftime(snapts, sizeof(snapts),
911                                                  "%Y-%m-%d %H:%M:%S %Z", tp);
912                                         printf("    expire 0x%016jx %s %s\n",
913                                                (uintmax_t)snap->tid,
914                                                snapts,
915                                                snap->label);
916                                 }
917                                 if (dsnapshot.count == HAMMER_SNAPS_PER_IOCTL) {
918                                         if (ioctl(fd, HAMMERIOC_DEL_SNAPSHOT, &dsnapshot) < 0) {
919                                                 printf("    Ioctl to delete snapshots failed: %s index %d\n", strerror(errno), dsnapshot.index);
920                                         } else if (dsnapshot.head.error) {
921                                                 printf("    Ioctl to delete snapshots failed: %s\n", strerror(dsnapshot.head.error));
922                                                 exit(1);
923                                         }
924                                         dsnapshot.index = 0;
925                                         dsnapshot.count = 0;
926                                         dsnapshot.head.error = 0;
927                                 }
928                         }
929                 } while (snapshot.head.error == 0 && snapshot.count);
930
931                 if (dsnapshot.count) {
932                         if (ioctl(fd, HAMMERIOC_DEL_SNAPSHOT, &dsnapshot) < 0) {
933                                 printf("    Ioctl to delete snapshots failed: %s\n", strerror(errno));
934                         } else if (dsnapshot.head.error) {
935                                 printf("    Ioctl to delete snapshots failed: %s\n", strerror(dsnapshot.head.error));
936                         }
937                         dsnapshot.count = 0;
938                         dsnapshot.index = 0;
939                         dsnapshot.head.error = 0;
940                 }
941         }
942 }
943
944 /*
945  * Take a softlink path in the form snap-yyyymmdd-hhmm and the
946  * expiration in seconds (arg2) and return non-zero if the softlink
947  * has expired.
948  */
949 static int
950 check_expired(const char *fpath, int arg2)
951 {
952         struct tm tm;
953         time_t t;
954         int year;
955         int month;
956         int day = 0;
957         int hour = 0;
958         int minute = 0;
959         int r;
960
961         while (*fpath && *fpath != '-' && *fpath != '.')
962                 ++fpath;
963         if (*fpath)
964                 ++fpath;
965
966         r = sscanf(fpath, "%4d%2d%2d-%2d%2d",
967                    &year, &month, &day, &hour, &minute);
968
969         if (r >= 3) {
970                 bzero(&tm, sizeof(tm));
971                 tm.tm_isdst = -1;
972                 tm.tm_min = minute;
973                 tm.tm_hour = hour;
974                 tm.tm_mday = day;
975                 tm.tm_mon = month - 1;
976                 tm.tm_year = year - 1900;
977                 t = mktime(&tm);
978                 if (t == (time_t)-1)
979                         return(0);
980                 t = time(NULL) - t;
981                 if ((int)t > arg2)
982                         return(1);
983         }
984         return(0);
985 }
986
987 /*
988  * Issue a snapshot.
989  */
990 static int
991 create_snapshot(const char *path, const char *snapshots_path)
992 {
993         int r;
994
995         runcmd(&r, "hammer snapshot %s %s", path, snapshots_path);
996         return(r);
997 }
998
999 static int
1000 cleanup_prune(const char *path __unused, const char *snapshots_path,
1001                   int arg1 __unused, int arg2, int snapshots_disabled)
1002 {
1003         /*
1004          * If snapshots have been disabled run prune-everything instead
1005          * of prune.
1006          */
1007         if (snapshots_disabled && arg2) {
1008                 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune-everything %s",
1009                         snapshots_path, arg2, path);
1010         } else if (snapshots_disabled) {
1011                 runcmd(NULL, "hammer prune-everything %s", path);
1012         } else if (arg2) {
1013                 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune %s",
1014                         snapshots_path, arg2, snapshots_path);
1015         } else {
1016                 runcmd(NULL, "hammer prune %s", snapshots_path);
1017         }
1018         return(0);
1019 }
1020
1021 static int
1022 cleanup_rebalance(const char *path, const char *snapshots_path,
1023                   int arg1 __unused, int arg2)
1024 {
1025         if (VerboseOpt == 0) {
1026                 printf(".");
1027                 fflush(stdout);
1028         }
1029
1030         runcmd(NULL,
1031                "hammer -c %s/.rebalance.cycle -t %d rebalance %s",
1032                snapshots_path, arg2, path);
1033         if (VerboseOpt == 0) {
1034                 printf(".");
1035                 fflush(stdout);
1036         }
1037         if (VerboseOpt == 0)
1038                 printf("\n");
1039         return(0);
1040 }
1041
1042 static int
1043 cleanup_reblock(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
1051         /*
1052          * When reblocking the B-Tree always reblock everything in normal
1053          * mode.
1054          */
1055         runcmd(NULL,
1056                "hammer -c %s/.reblock-1.cycle -t %d reblock-btree %s",
1057                snapshots_path, arg2, path);
1058         if (VerboseOpt == 0) {
1059                 printf(".");
1060                 fflush(stdout);
1061         }
1062
1063         /*
1064          * When reblocking the inodes always reblock everything in normal
1065          * mode.
1066          */
1067         runcmd(NULL,
1068                "hammer -c %s/.reblock-2.cycle -t %d reblock-inodes %s",
1069                snapshots_path, arg2, path);
1070         if (VerboseOpt == 0) {
1071                 printf(".");
1072                 fflush(stdout);
1073         }
1074
1075         /*
1076          * When reblocking the directories always reblock everything in normal
1077          * mode.
1078          */
1079         runcmd(NULL,
1080                "hammer -c %s/.reblock-4.cycle -t %d reblock-dirs %s",
1081                snapshots_path, arg2, path);
1082         if (VerboseOpt == 0) {
1083                 printf(".");
1084                 fflush(stdout);
1085         }
1086
1087         /*
1088          * Do not reblock all the data in normal mode.
1089          */
1090         runcmd(NULL,
1091                "hammer -c %s/.reblock-3.cycle -t %d reblock-data %s 95",
1092                snapshots_path, arg2, path);
1093         if (VerboseOpt == 0)
1094                 printf("\n");
1095         return(0);
1096 }
1097
1098 static int
1099 cleanup_recopy(const char *path, const char *snapshots_path,
1100                   int arg1 __unused, int arg2)
1101 {
1102         if (VerboseOpt == 0) {
1103                 printf(".");
1104                 fflush(stdout);
1105         }
1106         runcmd(NULL,
1107                "hammer -c %s/.recopy-1.cycle -t %d reblock-btree %s",
1108                snapshots_path, arg2, path);
1109         if (VerboseOpt == 0) {
1110                 printf(".");
1111                 fflush(stdout);
1112         }
1113         runcmd(NULL,
1114                "hammer -c %s/.recopy-2.cycle -t %d reblock-inodes %s",
1115                snapshots_path, arg2, path);
1116         if (VerboseOpt == 0) {
1117                 printf(".");
1118                 fflush(stdout);
1119         }
1120         runcmd(NULL,
1121                "hammer -c %s/.recopy-4.cycle -t %d reblock-dirs %s",
1122                snapshots_path, arg2, path);
1123         if (VerboseOpt == 0) {
1124                 printf(".");
1125                 fflush(stdout);
1126         }
1127         runcmd(NULL,
1128                "hammer -c %s/.recopy-3.cycle -t %d reblock-data %s",
1129                snapshots_path, arg2, path);
1130         if (VerboseOpt == 0)
1131                 printf("\n");
1132         return(0);
1133 }
1134
1135 static
1136 void
1137 runcmd(int *resp, const char *ctl, ...)
1138 {
1139         va_list va;
1140         char *cmd;
1141         char *arg;
1142         char **av;
1143         int n;
1144         int nmax;
1145         int res;
1146         pid_t pid;
1147
1148         /*
1149          * Generate the command
1150          */
1151         va_start(va, ctl);
1152         vasprintf(&cmd, ctl, va);
1153         va_end(va);
1154         if (VerboseOpt)
1155                 printf("    %s\n", cmd);
1156
1157         /*
1158          * Break us down into arguments.  We do not just use system() here
1159          * because it blocks SIGINT and friends.
1160          */
1161         n = 0;
1162         nmax = 16;
1163         av = malloc(sizeof(char *) * nmax);
1164
1165         for (arg = strtok(cmd, WS); arg; arg = strtok(NULL, WS)) {
1166                 if (n == nmax - 1) {
1167                         nmax += 16;
1168                         av = realloc(av, sizeof(char *) * nmax);
1169                 }
1170                 av[n++] = arg;
1171         }
1172         av[n++] = NULL;
1173
1174         /*
1175          * Run the command.
1176          */
1177         RunningIoctl = 1;
1178         if ((pid = fork()) == 0) {
1179                 if (VerboseOpt < 2) {
1180                         int fd = open("/dev/null", O_RDWR);
1181                         dup2(fd, 1);
1182                         close(fd);
1183                 }
1184                 execvp(av[0], av);
1185                 _exit(127);
1186         } else if (pid < 0) {
1187                 res = 127;
1188         } else {
1189                 int status;
1190
1191                 while (waitpid(pid, &status, 0) != pid)
1192                         ;
1193                 res = WEXITSTATUS(status);
1194         }
1195         RunningIoctl = 0;
1196         if (DidInterrupt)
1197                 _exit(1);
1198
1199         free(cmd);
1200         free(av);
1201         if (resp)
1202                 *resp = res;
1203 }