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