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