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
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
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
34 * $DragonFly: src/sbin/hammer/cmd_cleanup.c,v 1.6 2008/10/07 22:28:41 thomas Exp $
37 * Clean up a specific HAMMER filesystem or all HAMMER filesystems.
39 * Each filesystem is expected to have a <mount>/snapshots directory.
40 * No cleanup will be performed on any filesystem that does not. If
41 * no filesystems are specified the 'df' program is run and any HAMMER
42 * or null-mounted hammer PFS's are extracted.
44 * The snapshots directory may contain a config file called 'config'. If
45 * no config file is present one will be created with the following
48 * snapshots 1d 60d (0d 0d for /tmp, /var/tmp, /usr/obj)
53 * All hammer commands create and maintain cycle files in the snapshots
64 static void do_cleanup(const char *path);
65 static int strtosecs(char *ptr);
66 static const char *dividing_slash(const char *path);
67 static int check_period(const char *snapshots_path, const char *cmd, int arg1,
69 static void save_period(const char *snapshots_path, const char *cmd,
71 static int check_softlinks(const char *snapshots_path);
72 static void cleanup_softlinks(const char *path, const char *snapshots_path,
73 int arg2, char *arg3);
74 static int check_expired(const char *fpath, int arg2);
76 static int create_snapshot(const char *path, const char *snapshots_path,
78 static int cleanup_rebalance(const char *path, const char *snapshots_path,
80 static int cleanup_prune(const char *path, const char *snapshots_path,
81 int arg1, int arg2, int snapshots_disabled);
82 static int cleanup_reblock(const char *path, const char *snapshots_path,
84 static int cleanup_recopy(const char *path, const char *snapshots_path,
87 static void runcmd(int *resp, const char *ctl, ...);
91 struct didpfs *FirstPFS;
94 hammer_cmd_cleanup(char **av, int ac)
96 char *fstype, *fs, *path;
97 struct statfs *stfsbuf;
102 mntsize = getmntinfo(&stfsbuf, MNT_NOWAIT);
104 for (i=0; i < mntsize; i++) {
106 * We will cleanup in the case fstype is hammer.
107 * If we have null-mounted PFS, we check the
108 * mount source. If it looks like a PFS, we
109 * proceed to cleanup also.
111 fstype = stfsbuf[i].f_fstypename;
112 fs = stfsbuf[i].f_mntfromname;
113 if ((strcmp(fstype, "hammer") == 0) ||
114 ((strcmp(fstype, "null") == 0) &&
115 (strstr(fs, "/@@0x") != NULL ||
116 strstr(fs, "/@@-1") != NULL))) {
117 path = stfsbuf[i].f_mntonname;
134 do_cleanup(const char *path)
136 struct hammer_ioc_pseudofs_rw pfs;
137 union hammer_ioc_mrecord_any mrec_tmp;
138 char *snapshots_path;
149 struct didpfs *didpfs;
150 int snapshots_disabled = 0;
151 int prune_warning = 0;
156 bzero(&pfs, sizeof(pfs));
157 bzero(&mrec_tmp, sizeof(mrec_tmp));
158 pfs.ondisk = &mrec_tmp.pfs.pfsd;
159 pfs.bytes = sizeof(mrec_tmp.pfs.pfsd);
162 printf("cleanup %-20s -", path);
163 fd = open(path, O_RDONLY);
165 printf(" unable to access directory: %s\n", strerror(errno));
168 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) != 0) {
169 printf(" not a HAMMER filesystem: %s\n", strerror(errno));
173 if (pfs.version != HAMMER_IOC_PSEUDOFS_VERSION) {
174 printf(" unrecognized HAMMER version\n");
179 * Make sure we have not already handled this PFS. Several nullfs
180 * mounts might alias the same PFS.
182 for (didpfs = FirstPFS; didpfs; didpfs = didpfs->next) {
183 if (bcmp(&didpfs->uuid, &mrec_tmp.pfs.pfsd.unique_uuid, sizeof(uuid_t)) == 0) {
184 printf(" PFS #%d already handled\n", pfs.pfs_id);
188 didpfs = malloc(sizeof(*didpfs));
189 didpfs->next = FirstPFS;
191 didpfs->uuid = mrec_tmp.pfs.pfsd.unique_uuid;
194 * Figure out where the snapshot directory is.
196 if (mrec_tmp.pfs.pfsd.snapshots[0] == '/') {
197 asprintf(&snapshots_path, "%s", mrec_tmp.pfs.pfsd.snapshots);
198 } else if (mrec_tmp.pfs.pfsd.snapshots[0]) {
199 printf(" WARNING: pfs-slave's snapshots dir is not absolute\n");
201 } else if (mrec_tmp.pfs.pfsd.mirror_flags & HAMMER_PFSD_SLAVE) {
202 printf(" WARNING: must configure snapshot dir for PFS slave\n");
203 printf("\tWe suggest <fs>/var/slaves/<name> where "
204 "<fs> is the base HAMMER fs\n");
205 printf("\tcontaining the slave\n");
208 asprintf(&snapshots_path,
209 "%s%ssnapshots", path, dividing_slash(path));
213 * Create a snapshot directory if necessary, and a config file if
216 if (stat(snapshots_path, &st) < 0) {
217 if (mkdir(snapshots_path, 0755) != 0) {
218 free(snapshots_path);
219 printf(" unable to create snapshot dir \"%s\": %s\n",
220 snapshots_path, strerror(errno));
224 asprintf(&config_path, "%s/config", snapshots_path);
225 if ((fp = fopen(config_path, "r")) == NULL) {
226 fp = fopen(config_path, "w");
228 printf(" cannot create %s: %s\n",
229 config_path, strerror(errno));
232 if (strcmp(path, "/tmp") == 0 ||
233 strcmp(path, "/var/tmp") == 0 ||
234 strcmp(path, "/usr/obj") == 0) {
235 fprintf(fp, "snapshots 0d 0d\n");
237 fprintf(fp, "snapshots 1d 60d\n");
245 fp = fopen(config_path, "r");
248 printf(" cannot access %s: %s\n",
249 config_path, strerror(errno));
253 if (flock(fileno(fp), LOCK_EX|LOCK_NB) == -1) {
254 if (errno == EWOULDBLOCK)
255 printf(" PFS #%d locked by other process\n", pfs.pfs_id);
257 printf(" can not lock %s: %s\n", config_path, strerror(errno));
262 printf(" handle PFS #%d using %s\n", pfs.pfs_id, snapshots_path);
265 * Process the config file
267 while (fgets(buf, sizeof(buf), fp) != NULL) {
268 cmd = strtok(buf, WS);
272 if ((ptr = strtok(NULL, WS)) != NULL) {
273 arg1 = strtosecs(ptr);
274 if ((ptr = strtok(NULL, WS)) != NULL) {
275 arg2 = strtosecs(ptr);
276 arg3 = strtok(NULL, WS);
280 printf("%20s - ", cmd);
284 if (strcmp(cmd, "snapshots") == 0) {
286 if (arg2 && check_softlinks(snapshots_path)) {
287 printf("only removing old snapshots\n");
289 cleanup_softlinks(path, snapshots_path,
292 printf("disabled\n");
293 snapshots_disabled = 1;
296 if (check_period(snapshots_path, cmd, arg1, &savet)) {
298 cleanup_softlinks(path, snapshots_path,
300 r = create_snapshot(path, snapshots_path,
305 } else if (arg1 == 0) {
307 * The commands following this check can't handle
308 * a period of 0, so call the feature disabled and
309 * ignore the directive.
311 printf("disabled\n");
312 } else if (strcmp(cmd, "prune") == 0) {
313 if (check_period(snapshots_path, cmd, arg1, &savet)) {
315 printf("run - WARNING snapshot "
317 "but snapshots disabled\n");
321 r = cleanup_prune(path, snapshots_path,
322 arg1, arg2, snapshots_disabled);
326 } else if (strcmp(cmd, "rebalance") == 0) {
328 if (check_period(snapshots_path, cmd, arg1, &savet)) {
333 r = cleanup_rebalance(path, snapshots_path,
338 } else if (strcmp(cmd, "reblock") == 0) {
339 if (check_period(snapshots_path, cmd, arg1, &savet)) {
344 r = cleanup_reblock(path, snapshots_path,
349 } else if (strcmp(cmd, "recopy") == 0) {
350 if (check_period(snapshots_path, cmd, arg1, &savet)) {
355 r = cleanup_recopy(path, snapshots_path,
361 printf("unknown directive\n");
365 save_period(snapshots_path, cmd, savet);
370 * Add new rebalance feature if the config doesn't have it
372 if (found_rebal == 0) {
373 if ((fp = fopen(config_path, "r+")) != NULL) {
375 fprintf(fp, "rebalance 1d 5m\n");
388 val = strtol(ptr, &ptr, 0);
402 errx(1, "illegal suffix converting %s\n", ptr);
409 dividing_slash(const char *path)
411 int len = strlen(path);
412 if (len && path[len-1] == '/')
419 * Check whether the desired period has elapsed since the last successful
420 * run. The run may take a while and cross a boundary so we remember the
421 * current time_t so we can save it later on.
423 * Periods in minutes, hours, or days are assumed to have been crossed
424 * if the local time crosses a minute, hour, or day boundary regardless
425 * of how close the last operation actually was.
428 check_period(const char *snapshots_path, const char *cmd, int arg1,
439 localtime_r(savep, &tp1);
442 * Retrieve the start time of the last successful operation.
444 asprintf(&check_path, "%s/.%s.period", snapshots_path, cmd);
445 fp = fopen(check_path, "r");
449 if (fgets(buf, sizeof(buf), fp) == NULL) {
455 lastt = strtol(buf, NULL, 0);
456 localtime_r(&lastt, &tp2);
459 * Normalize the times. e.g. if asked to do something on a 1-day
460 * interval the operation will be performed as soon as the day
461 * turns over relative to the previous operation, even if the previous
462 * operation ran a few seconds ago just before midnight.
464 if (arg1 % 60 == 0) {
468 if (arg1 % (60 * 60) == 0) {
472 if (arg1 % (24 * 60 * 60) == 0) {
477 baset = mktime(&tp1);
478 lastt = mktime(&tp2);
481 printf("%lld vs %lld\n", (long long)(baset - lastt), (long long)arg1);
484 if ((int)(baset - lastt) >= arg1)
490 * Store the start time of the last successful operation.
493 save_period(const char *snapshots_path, const char *cmd,
500 asprintf(&ocheck_path, "%s/.%s.period", snapshots_path, cmd);
501 asprintf(&ncheck_path, "%s/.%s.period.new", snapshots_path, cmd);
502 fp = fopen(ncheck_path, "w");
504 fprintf(fp, "0x%08llx\n", (long long)savet);
506 rename(ncheck_path, ocheck_path);
509 fprintf(stderr, "hammer: Unable to create period-file %s: %s\n",
510 ncheck_path, strerror(errno));
515 * Simply count the number of softlinks in the snapshots dir
518 check_softlinks(const char *snapshots_path)
526 if ((dir = opendir(snapshots_path)) != NULL) {
527 while ((den = readdir(dir)) != NULL) {
528 if (den->d_name[0] == '.')
530 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
531 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode))
541 * Clean up expired softlinks in the snapshots dir
544 cleanup_softlinks(const char *path __unused, const char *snapshots_path,
545 int arg2, char *arg3)
553 if (arg3 != NULL && strstr(arg3, "any") != NULL)
556 if ((dir = opendir(snapshots_path)) != NULL) {
557 while ((den = readdir(dir)) != NULL) {
558 if (den->d_name[0] == '.')
560 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
561 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode) &&
562 (anylink || strncmp(den->d_name, "snap-", 5) == 0)
564 if (check_expired(den->d_name, arg2)) {
566 printf(" expire %s\n",
579 * Take a softlink path in the form snap-yyyymmdd-hhmm and the
580 * expiration in seconds (arg2) and return non-zero if the softlink
584 check_expired(const char *fpath, int arg2)
595 while (*fpath && *fpath != '-' && *fpath != '.')
600 r = sscanf(fpath, "%4d%2d%2d-%2d%2d",
601 &year, &month, &day, &hour, &minute);
604 bzero(&tm, sizeof(tm));
609 tm.tm_mon = month - 1;
610 tm.tm_year = year - 1900;
625 create_snapshot(const char *path __unused, const char *snapshots_path,
626 int arg1 __unused, int arg2 __unused)
630 runcmd(&r, "hammer snapshot %s %s", path, snapshots_path);
635 cleanup_prune(const char *path __unused, const char *snapshots_path,
636 int arg1 __unused, int arg2, int snapshots_disabled)
639 * If snapshots have been disabled run prune-everything instead
642 if (snapshots_disabled && arg2) {
643 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune-everything %s",
644 snapshots_path, arg2, path);
645 } else if (snapshots_disabled) {
646 runcmd(NULL, "hammer prune-everything %s", path);
648 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune %s",
649 snapshots_path, arg2, snapshots_path);
651 runcmd(NULL, "hammer prune %s", snapshots_path);
657 cleanup_rebalance(const char *path, const char *snapshots_path,
658 int arg1 __unused, int arg2)
660 if (VerboseOpt == 0) {
666 "hammer -c %s/.rebalance.cycle -t %d rebalance %s",
667 snapshots_path, arg2, path);
668 if (VerboseOpt == 0) {
678 cleanup_reblock(const char *path, const char *snapshots_path,
679 int arg1 __unused, int arg2)
681 if (VerboseOpt == 0) {
687 * When reblocking the B-Tree always reblock everything in normal
691 "hammer -c %s/.reblock-1.cycle -t %d reblock-btree %s",
692 snapshots_path, arg2, path);
693 if (VerboseOpt == 0) {
699 * When reblocking the inodes always reblock everything in normal
703 "hammer -c %s/.reblock-2.cycle -t %d reblock-inodes %s",
704 snapshots_path, arg2, path);
705 if (VerboseOpt == 0) {
711 * When reblocking the directories always reblock everything in normal
715 "hammer -c %s/.reblock-4.cycle -t %d reblock-dirs %s",
716 snapshots_path, arg2, path);
717 if (VerboseOpt == 0) {
723 * Do not reblock all the data in normal mode.
726 "hammer -c %s/.reblock-3.cycle -t %d reblock-data %s 95",
727 snapshots_path, arg2, path);
734 cleanup_recopy(const char *path, const char *snapshots_path,
735 int arg1 __unused, int arg2)
737 if (VerboseOpt == 0) {
742 "hammer -c %s/.recopy-1.cycle -t %d reblock-btree %s",
743 snapshots_path, arg2, path);
744 if (VerboseOpt == 0) {
749 "hammer -c %s/.recopy-2.cycle -t %d reblock-inodes %s",
750 snapshots_path, arg2, path);
751 if (VerboseOpt == 0) {
756 "hammer -c %s/.recopy-4.cycle -t %d reblock-dirs %s",
757 snapshots_path, arg2, path);
758 if (VerboseOpt == 0) {
763 "hammer -c %s/.recopy-3.cycle -t %d reblock-data %s",
764 snapshots_path, arg2, path);
772 runcmd(int *resp, const char *ctl, ...)
784 * Generate the command
787 vasprintf(&cmd, ctl, va);
790 printf(" %s\n", cmd);
793 * Break us down into arguments. We do not just use system() here
794 * because it blocks SIGINT and friends.
798 av = malloc(sizeof(char *) * nmax);
800 for (arg = strtok(cmd, WS); arg; arg = strtok(NULL, WS)) {
803 av = realloc(av, sizeof(char *) * nmax);
813 if ((pid = fork()) == 0) {
814 if (VerboseOpt < 2) {
815 int fd = open("/dev/null", O_RDWR);
821 } else if (pid < 0) {
826 while (waitpid(pid, &status, 0) != pid)
828 res = WEXITSTATUS(status);