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