sbin/hammer: Change "PFS #" to "PFS#"
[dragonfly.git] / sbin / hammer / cmd_cleanup.c
blobc20518805572b73be6a38dbe06034b70feeda2bb
1 /*
2 * Copyright (c) 2008 The DragonFly Project. All rights reserved.
4 * This code is derived from software contributed to The DragonFly Project
5 * by Matthew Dillon <dillon@backplane.com>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
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.
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.
35 * Clean up specific HAMMER filesystems or all HAMMER filesystems.
37 * If no filesystems are specified any HAMMER- or null-mounted hammer PFS's
38 * are cleaned.
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:
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
51 * All hammer commands create and maintain cycle files in the snapshots
52 * directory.
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).
61 #include <libutil.h>
63 #include "hammer.h"
65 struct didpfs {
66 struct didpfs *next;
67 uuid_t uuid;
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);
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);
100 static void runcmd(int *resp, const char *ctl, ...) __printflike(2, 3);
102 #define WS " \t\r\n"
104 struct didpfs *FirstPFS;
106 void
107 hammer_cmd_cleanup(char **av, int ac)
109 char *fstype, *fs, *path;
110 struct statfs *stfsbuf;
111 int mntsize, i;
113 tzset();
114 if (ac == 0) {
115 mntsize = getmntinfo(&stfsbuf, MNT_NOWAIT);
116 if (mntsize > 0) {
117 for (i=0; i < mntsize; i++) {
119 * We will cleanup in the case fstype is hammer.
120 * If we have null-mounted PFS, we check the
121 * mount source. If it looks like a PFS, we
122 * proceed to cleanup also.
124 fstype = stfsbuf[i].f_fstypename;
125 fs = stfsbuf[i].f_mntfromname;
126 if ((strcmp(fstype, "hammer") == 0) ||
127 ((strcmp(fstype, "null") == 0) &&
128 (strstr(fs, "/@@0x") != NULL ||
129 strstr(fs, "/@@-1") != NULL))) {
130 path = stfsbuf[i].f_mntonname;
131 do_cleanup(path);
136 } else {
137 while (ac) {
138 do_cleanup(*av);
139 --ac;
140 ++av;
145 static
146 void
147 do_cleanup(const char *path)
149 struct hammer_ioc_pseudofs_rw pfs;
150 struct hammer_ioc_config config;
151 struct hammer_ioc_version version;
152 union hammer_ioc_mrecord_any mrec_tmp;
153 char *snapshots_path = NULL;
154 char *config_path;
155 struct stat st;
156 char *cmd;
157 char *ptr;
158 int arg1;
159 int arg2;
160 char *arg3;
161 time_t savet;
162 char buf[256];
163 char *cbase;
164 char *cptr;
165 FILE *fp = NULL;
166 struct didpfs *didpfs;
167 int snapshots_disabled = 0;
168 int prune_warning = 0;
169 int new_config = 0;
170 int snapshots_from_pfs = 0;
171 int fd;
172 int r;
173 int found_rebal = 0;
175 bzero(&mrec_tmp, sizeof(mrec_tmp));
176 clrpfs(&pfs, &mrec_tmp.pfs.pfsd, -1);
178 printf("cleanup %-20s -", path);
179 fd = open(path, O_RDONLY);
180 if (fd < 0) {
181 printf(" unable to access directory: %s\n", strerror(errno));
182 return;
184 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) < 0) {
185 printf(" not a HAMMER filesystem: %s\n", strerror(errno));
186 close(fd);
187 return;
189 if (pfs.version != HAMMER_IOC_PSEUDOFS_VERSION) {
190 printf(" unrecognized HAMMER version\n");
191 close(fd);
192 return;
194 bzero(&version, sizeof(version));
195 if (ioctl(fd, HAMMERIOC_GET_VERSION, &version) < 0) {
196 printf(" HAMMER filesystem but couldn't retrieve version!\n");
197 close(fd);
198 return;
201 bzero(&config, sizeof(config));
202 if (version.cur_version >= 3) {
203 if (ioctl(fd, HAMMERIOC_GET_CONFIG, &config) == 0 &&
204 config.head.error == 0) {
205 new_config = 1;
210 * Make sure we have not already handled this PFS. Several nullfs
211 * mounts might alias the same PFS.
213 for (didpfs = FirstPFS; didpfs; didpfs = didpfs->next) {
214 if (bcmp(&didpfs->uuid, &mrec_tmp.pfs.pfsd.unique_uuid, sizeof(uuid_t)) == 0) {
215 printf(" PFS#%d already handled\n", pfs.pfs_id);
216 close(fd);
217 return;
220 didpfs = malloc(sizeof(*didpfs));
221 didpfs->next = FirstPFS;
222 FirstPFS = didpfs;
223 didpfs->uuid = mrec_tmp.pfs.pfsd.unique_uuid;
226 * Calculate the old snapshots directory for HAMMER VERSION < 3
228 * If the directory is explicitly specified in the PFS config
229 * we flag it and will not migrate it later.
231 if (mrec_tmp.pfs.pfsd.snapshots[0] == '/') {
232 asprintf(&snapshots_path, "%s", mrec_tmp.pfs.pfsd.snapshots);
233 snapshots_from_pfs = 1;
234 } else if (mrec_tmp.pfs.pfsd.snapshots[0]) {
235 printf(" WARNING: pfs-slave's snapshots dir is not absolute\n");
236 close(fd);
237 return;
238 } else if (hammer_is_pfs_slave(&mrec_tmp.pfs.pfsd)) {
239 if (version.cur_version < 3) {
240 printf(" WARNING: must configure snapshot dir for PFS slave\n");
241 printf("\tWe suggest <fs>/var/slaves/<name> where "
242 "<fs> is the base HAMMER fs\n");
243 printf("\tcontaining the slave\n");
244 close(fd);
245 return;
247 } else {
248 asprintf(&snapshots_path,
249 "%s%ssnapshots", path, dividing_slash(path));
253 * Check for old-style config file
255 if (snapshots_path) {
256 asprintf(&config_path, "%s/config", snapshots_path);
257 fp = fopen(config_path, "r");
261 * Handle upgrades to hammer version 3, move the config
262 * file into meta-data.
264 * For the old config read the file into the config structure,
265 * we will parse it out of the config structure regardless.
267 if (version.cur_version >= 3) {
268 if (fp) {
269 printf("(migrating) ");
270 fflush(stdout);
271 migrate_config(fp, &config);
272 migrate_snapshots(fd, snapshots_path);
273 fclose(fp);
274 if (ioctl(fd, HAMMERIOC_SET_CONFIG, &config) < 0) {
275 printf(" cannot init meta-data config!\n");
276 close(fd);
277 return;
279 remove(config_path);
280 } else if (new_config == 0) {
281 config_init(path, &config);
282 if (ioctl(fd, HAMMERIOC_SET_CONFIG, &config) < 0) {
283 printf(" cannot init meta-data config!\n");
284 close(fd);
285 return;
288 new_config = 1;
289 } else {
291 * Create missing snapshots directory for HAMMER VERSION < 3
293 if (stat(snapshots_path, &st) < 0) {
294 if (mkdir(snapshots_path, 0755) != 0) {
295 free(snapshots_path);
296 printf(" unable to create snapshot dir \"%s\": %s\n",
297 snapshots_path, strerror(errno));
298 close(fd);
299 return;
304 * Create missing config file for HAMMER VERSION < 3
306 if (fp == NULL) {
307 config_init(path, &config);
308 fp = fopen(config_path, "w");
309 if (fp) {
310 fwrite(config.config.text, 1,
311 strlen(config.config.text), fp);
312 fclose(fp);
314 } else {
315 migrate_config(fp, &config);
316 fclose(fp);
321 * If snapshots_from_pfs is not set we calculate the new snapshots
322 * directory default (in /var) for HAMMER VERSION >= 3 and migrate
323 * the old snapshots directory over.
325 * People who have set an explicit snapshots directory will have
326 * to migrate the data manually into /var/hammer, or not bother at
327 * all. People running slaves may wish to migrate it and then
328 * clear the snapshots specification in the PFS config for the
329 * slave.
331 if (new_config && snapshots_from_pfs == 0) {
332 char *npath;
334 if (path[0] != '/') {
335 printf(" path must start with '/'\n");
336 return;
338 if (strcmp(path, "/") == 0)
339 asprintf(&npath, "%s/root", SNAPSHOTS_BASE);
340 else
341 asprintf(&npath, "%s/%s", SNAPSHOTS_BASE, path + 1);
342 if (snapshots_path) {
343 if (stat(npath, &st) < 0 && errno == ENOENT) {
344 if (stat(snapshots_path, &st) < 0 && errno == ENOENT) {
345 printf(" HAMMER UPGRADE: Creating snapshots\n"
346 "\tCreating snapshots in %s\n",
347 npath);
348 runcmd(&r, "mkdir -p %s", npath);
349 } else {
350 printf(" HAMMER UPGRADE: Moving snapshots\n"
351 "\tMoving snapshots from %s to %s\n",
352 snapshots_path, npath);
353 runcmd(&r, "mkdir -p %s", npath);
354 runcmd(&r, "cpdup %s %s", snapshots_path, npath);
355 if (r != 0) {
356 printf("Unable to move snapshots directory!\n");
357 printf("Please fix this critical error.\n");
358 printf("Aborting cleanup of %s\n", path);
359 close(fd);
360 return;
362 runcmd(&r, "rm -rf %s", snapshots_path);
365 free(snapshots_path);
366 } else if (stat(npath, &st) < 0 && errno == ENOENT) {
367 runcmd(&r, "mkdir -p %s", npath);
369 snapshots_path = npath;
373 * Lock the PFS. fd is the base directory of the mounted PFS.
375 if (flock(fd, LOCK_EX|LOCK_NB) == -1) {
376 if (errno == EWOULDBLOCK)
377 printf(" PFS#%d locked by other process\n", pfs.pfs_id);
378 else
379 printf(" can not lock %s: %s\n", config_path, strerror(errno));
380 close(fd);
381 return;
384 printf(" handle PFS#%d using %s\n", pfs.pfs_id, snapshots_path);
386 struct pidfh *pfh = NULL;
387 static char pidfile[PIDFILE_BUFSIZE];
389 snprintf (pidfile, PIDFILE_BUFSIZE, "%s/hammer.cleanup.%d",
390 pidfile_loc, getpid());
391 pfh = pidfile_open(pidfile, 0644, NULL);
392 if (pfh == NULL) {
393 warn ("Unable to open or create %s", pidfile);
395 pidfile_write(pfh);
398 * Process the config file
400 cbase = config.config.text;
402 while ((cptr = strchr(cbase, '\n')) != NULL) {
403 bcopy(cbase, buf, cptr - cbase);
404 buf[cptr - cbase] = 0;
405 cbase = cptr + 1;
407 cmd = strtok(buf, WS);
408 if (cmd == NULL || cmd[0] == '#')
409 continue;
411 arg1 = 0;
412 arg2 = 0;
413 arg3 = NULL;
414 if ((ptr = strtok(NULL, WS)) != NULL) {
415 arg1 = strtosecs(ptr);
416 if ((ptr = strtok(NULL, WS)) != NULL) {
417 arg2 = strtosecs(ptr);
418 arg3 = strtok(NULL, WS);
422 printf("%20s - ", cmd);
423 fflush(stdout);
425 r = 1;
426 if (strcmp(cmd, "snapshots") == 0) {
427 if (arg1 == 0) {
428 if (arg2 &&
429 check_softlinks(fd, new_config,
430 snapshots_path)) {
431 printf("only removing old snapshots\n");
432 prune_warning = 1;
433 cleanup_softlinks(fd, new_config,
434 snapshots_path,
435 arg2, arg3);
436 } else {
437 printf("disabled\n");
438 snapshots_disabled = 1;
440 } else
441 if (check_period(snapshots_path, cmd, arg1, &savet)) {
442 printf("run\n");
443 cleanup_softlinks(fd, new_config,
444 snapshots_path,
445 arg2, arg3);
446 r = create_snapshot(path, snapshots_path);
447 } else {
448 printf("skip\n");
450 } else if (arg1 == 0) {
452 * The commands following this check can't handle
453 * a period of 0, so call the feature disabled and
454 * ignore the directive.
456 printf("disabled\n");
457 } else if (strcmp(cmd, "prune") == 0) {
458 if (check_period(snapshots_path, cmd, arg1, &savet)) {
459 if (prune_warning) {
460 printf("run - WARNING snapshot "
461 "softlinks present "
462 "but snapshots disabled\n");
463 } else {
464 printf("run\n");
466 r = cleanup_prune(path, snapshots_path,
467 arg1, arg2, snapshots_disabled);
468 } else {
469 printf("skip\n");
471 } else if (strcmp(cmd, "rebalance") == 0) {
472 found_rebal = 1;
473 if (check_period(snapshots_path, cmd, arg1, &savet)) {
474 printf("run");
475 fflush(stdout);
476 if (VerboseOpt)
477 printf("\n");
478 r = cleanup_rebalance(path, snapshots_path,
479 arg1, arg2);
480 } else {
481 printf("skip\n");
483 } else if (strcmp(cmd, "reblock") == 0) {
484 if (check_period(snapshots_path, cmd, arg1, &savet)) {
485 printf("run");
486 fflush(stdout);
487 if (VerboseOpt)
488 printf("\n");
489 r = cleanup_reblock(path, snapshots_path,
490 arg1, arg2);
491 } else {
492 printf("skip\n");
494 } else if (strcmp(cmd, "recopy") == 0) {
495 if (check_period(snapshots_path, cmd, arg1, &savet)) {
496 printf("run");
497 fflush(stdout);
498 if (VerboseOpt)
499 printf("\n");
500 r = cleanup_recopy(path, snapshots_path,
501 arg1, arg2);
502 } else {
503 printf("skip\n");
505 } else if (strcmp(cmd, "dedup") == 0) {
506 if (check_period(snapshots_path, cmd, arg1, &savet)) {
507 printf("run");
508 fflush(stdout);
509 if (VerboseOpt)
510 printf("\n");
511 r = cleanup_dedup(path, snapshots_path,
512 arg1, arg2);
513 } else {
514 printf("skip\n");
516 } else {
517 printf("unknown directive\n");
518 r = 1;
520 if (r == 0)
521 save_period(snapshots_path, cmd, savet);
525 * Add new rebalance feature if the config doesn't have it.
526 * (old style config only).
528 if (new_config == 0 && found_rebal == 0) {
529 if ((fp = fopen(config_path, "r+")) != NULL) {
530 fseek(fp, 0L, 2);
531 fprintf(fp, "rebalance 1d 5m\n");
532 fclose(fp);
537 * Cleanup, and delay a little
539 close(fd);
540 usleep(1000);
541 pidfile_close(pfh);
542 pidfile_remove(pfh);
546 * Initialize new config data (new or old style)
548 static void
549 config_init(const char *path, struct hammer_ioc_config *config)
551 const char *snapshots;
553 if (strcmp(path, "/tmp") == 0 ||
554 strcmp(path, "/var/tmp") == 0 ||
555 strcmp(path, "/usr/obj") == 0) {
556 snapshots = "snapshots 0d 0d\n";
557 } else {
558 snapshots = "snapshots 1d 60d\n";
560 bzero(config->config.text, sizeof(config->config.text));
561 snprintf(config->config.text, sizeof(config->config.text) - 1, "%s%s",
562 snapshots,
563 "prune 1d 5m\n"
564 "rebalance 1d 5m\n"
565 "#dedup 1d 5m\n"
566 "reblock 1d 5m\n"
567 "recopy 30d 10m\n");
571 * Migrate configuration data from the old snapshots/config
572 * file to the new meta-data format.
574 static void
575 migrate_config(FILE *fp, struct hammer_ioc_config *config)
577 int n;
579 n = fread(config->config.text, 1, sizeof(config->config.text) - 1, fp);
580 if (n >= 0)
581 bzero(config->config.text + n, sizeof(config->config.text) - n);
585 * Migrate snapshot softlinks in the snapshots directory to the
586 * new meta-data format. The softlinks are left intact, but
587 * this way the pruning code won't lose track of them if you
588 * happen to blow away the snapshots directory.
590 static void
591 migrate_snapshots(int fd, const char *snapshots_path)
593 struct hammer_ioc_snapshot snapshot;
594 struct dirent *den;
595 struct stat st;
596 DIR *dir;
597 char *fpath;
599 bzero(&snapshot, sizeof(snapshot));
601 if ((dir = opendir(snapshots_path)) != NULL) {
602 while ((den = readdir(dir)) != NULL) {
603 if (den->d_name[0] == '.')
604 continue;
605 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
606 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode)) {
607 migrate_one_snapshot(fd, fpath, &snapshot);
609 free(fpath);
611 closedir(dir);
613 migrate_one_snapshot(fd, NULL, &snapshot);
618 * Migrate a single snapshot. If fpath is NULL the ioctl is flushed,
619 * otherwise it is flushed when it fills up.
621 static void
622 migrate_one_snapshot(int fd, const char *fpath,
623 struct hammer_ioc_snapshot *snapshot)
625 if (fpath) {
626 hammer_snapshot_data_t snap;
627 struct tm tm;
628 time_t t;
629 int year;
630 int month;
631 int day = 0;
632 int hour = 0;
633 int minute = 0;
634 int r;
635 char linkbuf[1024];
636 const char *ptr;
637 hammer_tid_t tid;
639 t = (time_t)-1;
640 tid = (hammer_tid_t)(int64_t)-1;
642 /* fpath may contain directory components */
643 if ((ptr = strrchr(fpath, '/')) != NULL)
644 ++ptr;
645 else
646 ptr = fpath;
647 while (*ptr && *ptr != '-' && *ptr != '.')
648 ++ptr;
649 if (*ptr)
650 ++ptr;
651 r = sscanf(ptr, "%4d%2d%2d-%2d%2d",
652 &year, &month, &day, &hour, &minute);
654 if (r >= 3) {
655 bzero(&tm, sizeof(tm));
656 tm.tm_isdst = -1;
657 tm.tm_min = minute;
658 tm.tm_hour = hour;
659 tm.tm_mday = day;
660 tm.tm_mon = month - 1;
661 tm.tm_year = year - 1900;
662 t = mktime(&tm);
664 bzero(linkbuf, sizeof(linkbuf));
665 if (readlink(fpath, linkbuf, sizeof(linkbuf) - 1) > 0 &&
666 (ptr = strrchr(linkbuf, '@')) != NULL &&
667 ptr > linkbuf && ptr[-1] == '@') {
668 tid = strtoull(ptr + 1, NULL, 16);
670 if (t != (time_t)-1 && tid != (hammer_tid_t)(int64_t)-1) {
671 snap = &snapshot->snaps[snapshot->count];
672 bzero(snap, sizeof(*snap));
673 snap->tid = tid;
674 snap->ts = (uint64_t)t * 1000000ULL;
675 snprintf(snap->label, sizeof(snap->label),
676 "migrated");
677 ++snapshot->count;
678 } else {
679 printf(" non-canonical snapshot softlink: %s->%s\n",
680 fpath, linkbuf);
684 if ((fpath == NULL && snapshot->count) ||
685 snapshot->count == HAMMER_SNAPS_PER_IOCTL) {
686 printf(" (%d snapshots)", snapshot->count);
687 again:
688 if (ioctl(fd, HAMMERIOC_ADD_SNAPSHOT, snapshot) < 0) {
689 printf(" Ioctl to migrate snapshots failed: %s\n",
690 strerror(errno));
691 } else if (snapshot->head.error == EALREADY) {
692 ++snapshot->index;
693 goto again;
694 } else if (snapshot->head.error) {
695 printf(" Ioctl to migrate snapshots failed: %s\n",
696 strerror(snapshot->head.error));
698 printf("index %d\n", snapshot->index);
699 snapshot->index = 0;
700 snapshot->count = 0;
701 snapshot->head.error = 0;
705 static
707 strtosecs(char *ptr)
709 int val;
711 val = strtol(ptr, &ptr, 0);
712 switch(*ptr) {
713 case 'd':
714 val *= 24;
715 /* fall through */
716 case 'h':
717 val *= 60;
718 /* fall through */
719 case 'm':
720 val *= 60;
721 /* fall through */
722 case 's':
723 break;
724 default:
725 errx(1, "illegal suffix converting %s\n", ptr);
726 break;
728 return(val);
731 static const char *
732 dividing_slash(const char *path)
734 int len = strlen(path);
735 if (len && path[len-1] == '/')
736 return("");
737 else
738 return("/");
742 * Check whether the desired period has elapsed since the last successful
743 * run. The run may take a while and cross a boundary so we remember the
744 * current time_t so we can save it later on.
746 * Periods in minutes, hours, or days are assumed to have been crossed
747 * if the local time crosses a minute, hour, or day boundary regardless
748 * of how close the last operation actually was.
750 * If ForceOpt is set always return true.
752 static int
753 check_period(const char *snapshots_path, const char *cmd, int arg1,
754 time_t *savep)
756 char *check_path;
757 struct tm tp1;
758 struct tm tp2;
759 FILE *fp;
760 time_t baset, lastt;
761 char buf[256];
763 time(savep);
764 localtime_r(savep, &tp1);
767 * Force run if -F
769 if (ForceOpt)
770 return(1);
773 * Retrieve the start time of the last successful operation.
775 asprintf(&check_path, "%s/.%s.period", snapshots_path, cmd);
776 fp = fopen(check_path, "r");
777 free(check_path);
778 if (fp == NULL)
779 return(1);
780 if (fgets(buf, sizeof(buf), fp) == NULL) {
781 fclose(fp);
782 return(1);
784 fclose(fp);
786 lastt = strtol(buf, NULL, 0);
787 localtime_r(&lastt, &tp2);
790 * Normalize the times. e.g. if asked to do something on a 1-day
791 * interval the operation will be performed as soon as the day
792 * turns over relative to the previous operation, even if the previous
793 * operation ran a few seconds ago just before midnight.
795 if (arg1 % 60 == 0) {
796 tp1.tm_sec = 0;
797 tp2.tm_sec = 0;
799 if (arg1 % (60 * 60) == 0) {
800 tp1.tm_min = 0;
801 tp2.tm_min = 0;
803 if (arg1 % (24 * 60 * 60) == 0) {
804 tp1.tm_hour = 0;
805 tp2.tm_hour = 0;
808 baset = mktime(&tp1);
809 lastt = mktime(&tp2);
811 #if 0
812 printf("%lld vs %lld\n", (long long)(baset - lastt), (long long)arg1);
813 #endif
815 if ((int)(baset - lastt) >= arg1)
816 return(1);
817 return(0);
821 * Store the start time of the last successful operation.
823 static void
824 save_period(const char *snapshots_path, const char *cmd,
825 time_t savet)
827 char *ocheck_path;
828 char *ncheck_path;
829 FILE *fp;
831 asprintf(&ocheck_path, "%s/.%s.period", snapshots_path, cmd);
832 asprintf(&ncheck_path, "%s/.%s.period.new", snapshots_path, cmd);
833 fp = fopen(ncheck_path, "w");
834 if (fp) {
835 fprintf(fp, "0x%08llx\n", (long long)savet);
836 if (fclose(fp) == 0)
837 rename(ncheck_path, ocheck_path);
838 remove(ncheck_path);
839 } else {
840 fprintf(stderr, "hammer: Unable to create period-file %s: %s\n",
841 ncheck_path, strerror(errno));
846 * Simply count the number of softlinks in the snapshots dir
848 static int
849 check_softlinks(int fd, int new_config, const char *snapshots_path)
851 struct dirent *den;
852 struct stat st;
853 DIR *dir;
854 char *fpath;
855 int res = 0;
858 * Old-style softlink-based snapshots
860 if ((dir = opendir(snapshots_path)) != NULL) {
861 while ((den = readdir(dir)) != NULL) {
862 if (den->d_name[0] == '.')
863 continue;
864 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
865 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode))
866 ++res;
867 free(fpath);
869 closedir(dir);
873 * New-style snapshots are stored as filesystem meta-data,
874 * count those too.
876 if (new_config) {
877 struct hammer_ioc_snapshot snapshot;
879 bzero(&snapshot, sizeof(snapshot));
880 do {
881 if (ioctl(fd, HAMMERIOC_GET_SNAPSHOT, &snapshot) < 0) {
882 err(2, "hammer cleanup: check_softlink "
883 "snapshot error");
884 /* not reached */
886 res += snapshot.count;
887 } while (snapshot.head.error == 0 && snapshot.count);
889 return (res);
893 * Clean up expired softlinks in the snapshots dir
895 static void
896 cleanup_softlinks(int fd, int new_config,
897 const char *snapshots_path, int arg2, char *arg3)
899 struct dirent *den;
900 struct stat st;
901 DIR *dir;
902 char *fpath;
903 int anylink = 0;
905 if (arg3 != NULL && strstr(arg3, "any") != NULL)
906 anylink = 1;
908 if ((dir = opendir(snapshots_path)) != NULL) {
909 while ((den = readdir(dir)) != NULL) {
910 if (den->d_name[0] == '.')
911 continue;
912 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
913 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode) &&
914 (anylink || strncmp(den->d_name, "snap-", 5) == 0)) {
915 if (check_expired(den->d_name, arg2)) {
916 if (VerboseOpt) {
917 printf(" expire %s\n",
918 fpath);
920 remove(fpath);
923 free(fpath);
925 closedir(dir);
929 * New-style snapshots are stored as filesystem meta-data,
930 * count those too.
932 if (new_config) {
933 struct hammer_ioc_snapshot snapshot;
934 struct hammer_ioc_snapshot dsnapshot;
935 hammer_snapshot_data_t snap;
936 struct tm *tp;
937 time_t t;
938 time_t dt;
939 char snapts[32];
940 uint32_t i;
942 bzero(&snapshot, sizeof(snapshot));
943 bzero(&dsnapshot, sizeof(dsnapshot));
944 do {
945 if (ioctl(fd, HAMMERIOC_GET_SNAPSHOT, &snapshot) < 0) {
946 err(2, "hammer cleanup: check_softlink "
947 "snapshot error");
948 /* not reached */
950 for (i = 0; i < snapshot.count; ++i) {
951 snap = &snapshot.snaps[i];
952 t = snap->ts / 1000000ULL;
953 dt = time(NULL) - t;
954 if ((int)dt > arg2 || snap->tid == 0) {
955 dsnapshot.snaps[dsnapshot.count++] =
956 *snap;
958 if ((int)dt > arg2 && VerboseOpt) {
959 tp = localtime(&t);
960 strftime(snapts, sizeof(snapts),
961 "%Y-%m-%d %H:%M:%S %Z", tp);
962 printf(" expire 0x%016jx %s %s\n",
963 (uintmax_t)snap->tid,
964 snapts,
965 snap->label);
967 if (dsnapshot.count == HAMMER_SNAPS_PER_IOCTL)
968 delete_snapshots(fd, &dsnapshot);
970 } while (snapshot.head.error == 0 && snapshot.count);
972 if (dsnapshot.count)
973 delete_snapshots(fd, &dsnapshot);
977 static void
978 delete_snapshots(int fd, struct hammer_ioc_snapshot *dsnapshot)
980 for (;;) {
981 if (ioctl(fd, HAMMERIOC_DEL_SNAPSHOT, dsnapshot) < 0) {
982 printf(" Ioctl to delete snapshots failed: %s\n",
983 strerror(errno));
984 break;
986 if (dsnapshot->head.error) {
987 printf(" Ioctl to delete snapshots failed at "
988 "snap=%016jx: %s\n",
989 dsnapshot->snaps[dsnapshot->index].tid,
990 strerror(dsnapshot->head.error));
991 if (++dsnapshot->index < dsnapshot->count)
992 continue;
994 break;
996 dsnapshot->index = 0;
997 dsnapshot->count = 0;
998 dsnapshot->head.error = 0;
1002 * Take a softlink path in the form snap-yyyymmdd-hhmm and the
1003 * expiration in seconds (arg2) and return non-zero if the softlink
1004 * has expired.
1006 static int
1007 check_expired(const char *fpath, int arg2)
1009 struct tm tm;
1010 time_t t;
1011 int year;
1012 int month;
1013 int day = 0;
1014 int hour = 0;
1015 int minute = 0;
1016 int r;
1018 while (*fpath && *fpath != '-' && *fpath != '.')
1019 ++fpath;
1020 if (*fpath)
1021 ++fpath;
1023 r = sscanf(fpath, "%4d%2d%2d-%2d%2d",
1024 &year, &month, &day, &hour, &minute);
1026 if (r >= 3) {
1027 bzero(&tm, sizeof(tm));
1028 tm.tm_isdst = -1;
1029 tm.tm_min = minute;
1030 tm.tm_hour = hour;
1031 tm.tm_mday = day;
1032 tm.tm_mon = month - 1;
1033 tm.tm_year = year - 1900;
1034 t = mktime(&tm);
1035 if (t == (time_t)-1)
1036 return(0);
1037 t = time(NULL) - t;
1038 if ((int)t > arg2)
1039 return(1);
1041 return(0);
1045 * Issue a snapshot.
1047 static int
1048 create_snapshot(const char *path, const char *snapshots_path)
1050 int r;
1052 runcmd(&r, "hammer snapshot %s %s", path, snapshots_path);
1053 return(r);
1056 static int
1057 cleanup_prune(const char *path, const char *snapshots_path,
1058 int arg1 __unused, int arg2, int snapshots_disabled)
1060 const char *path_or_snapshots_path;
1063 * If the snapshots_path (e.g. /var/hammer/...) has no snapshots
1064 * in it then prune will get confused and prune the filesystem
1065 * containing the snapshots_path instead of the requested
1066 * filesystem. De-confuse prune. We need a better way.
1068 if (hammer_softprune_testdir(snapshots_path))
1069 path_or_snapshots_path = snapshots_path;
1070 else
1071 path_or_snapshots_path = path;
1074 * If snapshots have been disabled run prune-everything instead
1075 * of prune.
1077 if (snapshots_disabled && arg2) {
1078 runcmd(NULL,
1079 "hammer -c %s/.prune.cycle -t %d prune-everything %s",
1080 snapshots_path, arg2, path);
1081 } else if (snapshots_disabled) {
1082 runcmd(NULL, "hammer prune-everything %s", path);
1083 } else if (arg2) {
1084 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune %s",
1085 snapshots_path, arg2, path_or_snapshots_path);
1086 } else {
1087 runcmd(NULL, "hammer prune %s", path_or_snapshots_path);
1089 return(0);
1092 static int
1093 cleanup_rebalance(const char *path, const char *snapshots_path,
1094 int arg1 __unused, int arg2)
1096 if (VerboseOpt == 0) {
1097 printf(".");
1098 fflush(stdout);
1101 runcmd(NULL,
1102 "hammer -c %s/.rebalance.cycle -t %d rebalance %s",
1103 snapshots_path, arg2, path);
1104 if (VerboseOpt == 0) {
1105 printf(".");
1106 fflush(stdout);
1108 if (VerboseOpt == 0)
1109 printf("\n");
1110 return(0);
1113 static int
1114 cleanup_reblock(const char *path, const char *snapshots_path,
1115 int arg1 __unused, int arg2)
1117 if (VerboseOpt == 0) {
1118 printf(".");
1119 fflush(stdout);
1123 * When reblocking the B-Tree always reblock everything in normal
1124 * mode.
1126 runcmd(NULL,
1127 "hammer -c %s/.reblock-1.cycle -t %d reblock-btree %s",
1128 snapshots_path, arg2, path);
1129 if (VerboseOpt == 0) {
1130 printf(".");
1131 fflush(stdout);
1135 * When reblocking the inodes always reblock everything in normal
1136 * mode.
1138 runcmd(NULL,
1139 "hammer -c %s/.reblock-2.cycle -t %d reblock-inodes %s",
1140 snapshots_path, arg2, path);
1141 if (VerboseOpt == 0) {
1142 printf(".");
1143 fflush(stdout);
1147 * When reblocking the directories always reblock everything in normal
1148 * mode.
1150 runcmd(NULL,
1151 "hammer -c %s/.reblock-4.cycle -t %d reblock-dirs %s",
1152 snapshots_path, arg2, path);
1153 if (VerboseOpt == 0) {
1154 printf(".");
1155 fflush(stdout);
1159 * Do not reblock all the data in normal mode.
1161 runcmd(NULL,
1162 "hammer -c %s/.reblock-3.cycle -t %d reblock-data %s 95",
1163 snapshots_path, arg2, path);
1164 if (VerboseOpt == 0)
1165 printf("\n");
1166 return(0);
1169 static int
1170 cleanup_recopy(const char *path, const char *snapshots_path,
1171 int arg1 __unused, int arg2)
1173 if (VerboseOpt == 0) {
1174 printf(".");
1175 fflush(stdout);
1177 runcmd(NULL,
1178 "hammer -c %s/.recopy-1.cycle -t %d reblock-btree %s",
1179 snapshots_path, arg2, path);
1180 if (VerboseOpt == 0) {
1181 printf(".");
1182 fflush(stdout);
1184 runcmd(NULL,
1185 "hammer -c %s/.recopy-2.cycle -t %d reblock-inodes %s",
1186 snapshots_path, arg2, path);
1187 if (VerboseOpt == 0) {
1188 printf(".");
1189 fflush(stdout);
1191 runcmd(NULL,
1192 "hammer -c %s/.recopy-4.cycle -t %d reblock-dirs %s",
1193 snapshots_path, arg2, path);
1194 if (VerboseOpt == 0) {
1195 printf(".");
1196 fflush(stdout);
1198 runcmd(NULL,
1199 "hammer -c %s/.recopy-3.cycle -t %d reblock-data %s",
1200 snapshots_path, arg2, path);
1201 if (VerboseOpt == 0)
1202 printf("\n");
1203 return(0);
1206 static int
1207 cleanup_dedup(const char *path, const char *snapshots_path __unused,
1208 int arg1 __unused, int arg2)
1210 if (VerboseOpt == 0) {
1211 printf(".");
1212 fflush(stdout);
1215 runcmd(NULL, "hammer -t %d dedup %s", arg2, path);
1216 if (VerboseOpt == 0) {
1217 printf(".");
1218 fflush(stdout);
1220 if (VerboseOpt == 0)
1221 printf("\n");
1222 return(0);
1225 static
1226 void
1227 runcmd(int *resp, const char *ctl, ...)
1229 va_list va;
1230 char *cmd;
1231 char *arg;
1232 char **av;
1233 int n;
1234 int nmax;
1235 int res;
1236 pid_t pid;
1239 * Generate the command
1241 va_start(va, ctl);
1242 vasprintf(&cmd, ctl, va);
1243 va_end(va);
1244 if (VerboseOpt)
1245 printf(" %s\n", cmd);
1248 * Break us down into arguments. We do not just use system() here
1249 * because it blocks SIGINT and friends.
1251 n = 0;
1252 nmax = 16;
1253 av = malloc(sizeof(char *) * nmax);
1255 for (arg = strtok(cmd, WS); arg; arg = strtok(NULL, WS)) {
1256 if (n == nmax - 1) {
1257 nmax += 16;
1258 av = realloc(av, sizeof(char *) * nmax);
1260 av[n++] = arg;
1262 av[n++] = NULL;
1265 * Run the command.
1267 RunningIoctl = 1;
1268 if ((pid = fork()) == 0) {
1269 if (VerboseOpt < 2) {
1270 int fd = open("/dev/null", O_RDWR);
1271 dup2(fd, 1);
1272 close(fd);
1274 execvp(av[0], av);
1275 _exit(127);
1276 } else if (pid < 0) {
1277 res = 127;
1278 } else {
1279 int status;
1281 while (waitpid(pid, &status, 0) != pid)
1283 res = WEXITSTATUS(status);
1285 RunningIoctl = 0;
1286 if (DidInterrupt)
1287 _exit(1);
1289 free(cmd);
1290 free(av);
1291 if (resp)
1292 *resp = res;