Raise some WARNS in usr.bin.
[dragonfly.git] / sbin / hammer / cmd_cleanup.c
blob95b7d496275515a0b28e301a5fe28bdcc2d15be3
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>
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:
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.
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
46 * defaults:
48 * snapshots 1d 60d (0d 0d for /tmp, /var/tmp, /usr/obj)
49 * prune 1d 5m
50 * reblock 1d 5m
51 * recopy 30d 5m
53 * All hammer commands create and maintain cycle files in the snapshots
54 * directory.
57 #include "hammer.h"
59 struct didpfs {
60 struct didpfs *next;
61 uuid_t uuid;
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,
68 time_t *savep);
69 static void save_period(const char *snapshots_path, const char *cmd,
70 time_t savet);
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 cleanup_snapshots(const char *path, const char *snapshots_path,
77 int arg1, int arg2);
78 static int cleanup_prune(const char *path, const char *snapshots_path,
79 int arg1, int arg2, int snapshots_disabled);
80 static int cleanup_reblock(const char *path, const char *snapshots_path,
81 int arg1, int arg2);
82 static int cleanup_recopy(const char *path, const char *snapshots_path,
83 int arg1, int arg2);
85 static void runcmd(int *resp, const char *ctl, ...);
87 #define WS " \t\r\n"
89 struct didpfs *FirstPFS;
91 void
92 hammer_cmd_cleanup(char **av, int ac)
94 FILE *fp;
95 char *fs, *ptr, *path;
96 char buf[256];
98 tzset();
99 if (ac == 0) {
100 fp = popen("/sbin/mount -t hammer,null", "r");
101 if (fp == NULL)
102 errx(1, "hammer cleanup: 'mount' failed");
103 while (fgets(buf, sizeof(buf), fp) != NULL) {
104 fs = strtok(buf, WS);
105 if (fs == NULL)
106 continue;
107 ptr = strtok(NULL, WS);
108 if (ptr == NULL)
109 continue;
110 path = strtok(NULL, WS);
111 if (path == NULL)
112 continue;
113 ptr = strtok(NULL, WS);
114 if (ptr == NULL)
115 continue;
116 if ((strncmp(ptr, "(hammer,", 8) == 0) ||
117 ((strncmp(ptr, "(null,", 6) == 0) &&
118 (strstr(fs, "/@@0x") != NULL ||
119 strstr(fs, "/@@-1") != NULL))) {
120 do_cleanup(path);
123 fclose(fp);
124 } else {
125 while (ac) {
126 do_cleanup(*av);
127 --ac;
128 ++av;
133 static
134 void
135 do_cleanup(const char *path)
137 struct hammer_ioc_pseudofs_rw pfs;
138 union hammer_ioc_mrecord_any mrec_tmp;
139 char *snapshots_path;
140 char *config_path;
141 struct stat st;
142 char *cmd;
143 char *ptr;
144 int arg1;
145 int arg2;
146 char *arg3;
147 time_t savet;
148 char buf[256];
149 FILE *fp;
150 struct didpfs *didpfs;
151 int snapshots_disabled = 0;
152 int prune_warning = 0;
153 int fd;
154 int r;
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);
160 pfs.pfs_id = -1;
162 printf("cleanup %-20s -", path);
163 fd = open(path, O_RDONLY);
164 if (fd < 0) {
165 printf(" unable to access directory: %s\n", strerror(errno));
166 return;
168 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) != 0) {
169 printf(" not a HAMMER filesystem: %s\n", strerror(errno));
170 return;
172 close(fd);
173 if (pfs.version != HAMMER_IOC_PSEUDOFS_VERSION) {
174 printf(" unrecognized HAMMER version\n");
175 return;
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);
185 return;
188 didpfs = malloc(sizeof(*didpfs));
189 didpfs->next = FirstPFS;
190 FirstPFS = didpfs;
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");
200 return;
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");
206 return;
207 } else {
208 asprintf(&snapshots_path,
209 "%s%ssnapshots", path, dividing_slash(path));
213 * Create a snapshot directory if necessary, and a config file if
214 * necessary.
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));
221 return;
224 asprintf(&config_path, "%s/config", snapshots_path);
225 if ((fp = fopen(config_path, "r")) == NULL) {
226 fp = fopen(config_path, "w");
227 if (fp == NULL) {
228 printf(" cannot create %s: %s\n",
229 config_path, strerror(errno));
230 return;
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");
236 } else {
237 fprintf(fp, "snapshots 1d 60d\n");
239 fprintf(fp,
240 "prune 1d 5m\n"
241 "reblock 1d 5m\n"
242 "recopy 30d 10m\n");
243 fclose(fp);
244 fp = fopen(config_path, "r");
246 if (fp == NULL) {
247 printf(" cannot access %s: %s\n",
248 config_path, strerror(errno));
249 return;
252 printf(" handle PFS #%d using %s\n", pfs.pfs_id, snapshots_path);
255 * Process the config file
257 while (fgets(buf, sizeof(buf), fp) != NULL) {
258 cmd = strtok(buf, WS);
259 arg1 = 0;
260 arg2 = 0;
261 arg3 = NULL;
262 if ((ptr = strtok(NULL, WS)) != NULL) {
263 arg1 = strtosecs(ptr);
264 if ((ptr = strtok(NULL, WS)) != NULL) {
265 arg2 = strtosecs(ptr);
266 arg3 = strtok(NULL, WS);
270 printf("%20s - ", cmd);
271 fflush(stdout);
273 r = 1;
274 if (strcmp(cmd, "snapshots") == 0) {
275 if (arg1 == 0) {
276 if (arg2 && check_softlinks(snapshots_path)) {
277 printf("only removing old snapshots\n");
278 prune_warning = 1;
279 cleanup_softlinks(path, snapshots_path,
280 arg2, arg3);
281 } else {
282 printf("disabled\n");
283 snapshots_disabled = 1;
285 } else
286 if (check_period(snapshots_path, cmd, arg1, &savet)) {
287 printf("run\n");
288 cleanup_softlinks(path, snapshots_path,
289 arg2, arg3);
290 r = cleanup_snapshots(path, snapshots_path,
291 arg1, arg2);
292 } else {
293 printf("skip\n");
295 } else if (arg1 == 0) {
297 * The commands following this check can't handle
298 * a period of 0, so call the feature disabled and
299 * ignore the directive.
301 printf("disabled\n");
302 } else if (strcmp(cmd, "prune") == 0) {
303 if (check_period(snapshots_path, cmd, arg1, &savet)) {
304 if (prune_warning) {
305 printf("run - WARNING snapshot "
306 "softlinks present "
307 "but snapshots disabled\n");
308 } else {
309 printf("run\n");
311 r = cleanup_prune(path, snapshots_path,
312 arg1, arg2, snapshots_disabled);
313 } else {
314 printf("skip\n");
316 } else if (strcmp(cmd, "reblock") == 0) {
317 if (check_period(snapshots_path, cmd, arg1, &savet)) {
318 printf("run");
319 fflush(stdout);
320 if (VerboseOpt)
321 printf("\n");
322 r = cleanup_reblock(path, snapshots_path,
323 arg1, arg2);
324 } else {
325 printf("skip\n");
327 } else if (strcmp(cmd, "recopy") == 0) {
328 if (check_period(snapshots_path, cmd, arg1, &savet)) {
329 printf("run");
330 fflush(stdout);
331 if (VerboseOpt)
332 printf("\n");
333 r = cleanup_recopy(path, snapshots_path,
334 arg1, arg2);
335 } else {
336 printf("skip\n");
338 } else {
339 printf("unknown directive\n");
340 r = 1;
342 if (r == 0)
343 save_period(snapshots_path, cmd, savet);
345 fclose(fp);
346 usleep(1000);
349 static
351 strtosecs(char *ptr)
353 int val;
355 val = strtol(ptr, &ptr, 0);
356 switch(*ptr) {
357 case 'd':
358 val *= 24;
359 /* fall through */
360 case 'h':
361 val *= 60;
362 /* fall through */
363 case 'm':
364 val *= 60;
365 /* fall through */
366 case 's':
367 break;
368 default:
369 errx(1, "illegal suffix converting %s\n", ptr);
370 break;
372 return(val);
375 static const char *
376 dividing_slash(const char *path)
378 int len = strlen(path);
379 if (len && path[len-1] == '/')
380 return("");
381 else
382 return("/");
386 * Check whether the desired period has elapsed since the last successful
387 * run. The run may take a while and cross a boundary so we remember the
388 * current time_t so we can save it later on.
390 * Periods in minutes, hours, or days are assumed to have been crossed
391 * if the local time crosses a minute, hour, or day boundary regardless
392 * of how close the last operation actually was.
394 static int
395 check_period(const char *snapshots_path, const char *cmd, int arg1,
396 time_t *savep)
398 char *check_path;
399 struct tm tp1;
400 struct tm tp2;
401 FILE *fp;
402 time_t baset, lastt;
403 char buf[256];
405 time(savep);
406 localtime_r(savep, &tp1);
409 * Retrieve the start time of the last successful operation.
411 asprintf(&check_path, "%s/.%s.period", snapshots_path, cmd);
412 fp = fopen(check_path, "r");
413 free(check_path);
414 if (fp == NULL)
415 return(1);
416 if (fgets(buf, sizeof(buf), fp) == NULL) {
417 fclose(fp);
418 return(1);
420 fclose(fp);
422 lastt = strtol(buf, NULL, 0);
423 localtime_r(&lastt, &tp2);
426 * Normalize the times. e.g. if asked to do something on a 1-day
427 * interval the operation will be performed as soon as the day
428 * turns over relative to the previous operation, even if the previous
429 * operation ran a few seconds ago just before midnight.
431 if (arg1 % 60 == 0) {
432 tp1.tm_sec = 0;
433 tp2.tm_sec = 0;
435 if (arg1 % (60 * 60) == 0) {
436 tp1.tm_min = 0;
437 tp2.tm_min = 0;
439 if (arg1 % (24 * 60 * 60) == 0) {
440 tp1.tm_hour = 0;
441 tp2.tm_hour = 0;
444 baset = mktime(&tp1);
445 lastt = mktime(&tp2);
447 #if 0
448 printf("%lld vs %lld\n", (long long)(baset - lastt), (long long)arg1);
449 #endif
451 if ((int)(baset - lastt) >= arg1)
452 return(1);
453 return(0);
457 * Store the start time of the last successful operation.
459 static void
460 save_period(const char *snapshots_path, const char *cmd,
461 time_t savet)
463 char *ocheck_path;
464 char *ncheck_path;
465 FILE *fp;
467 asprintf(&ocheck_path, "%s/.%s.period", snapshots_path, cmd);
468 asprintf(&ncheck_path, "%s/.%s.period.new", snapshots_path, cmd);
469 fp = fopen(ncheck_path, "w");
470 fprintf(fp, "0x%08llx\n", (long long)savet);
471 if (fclose(fp) == 0)
472 rename(ncheck_path, ocheck_path);
473 remove(ncheck_path);
477 * Simply count the number of softlinks in the snapshots dir
479 static int
480 check_softlinks(const char *snapshots_path)
482 struct dirent *den;
483 struct stat st;
484 DIR *dir;
485 char *fpath;
486 int res = 0;
488 if ((dir = opendir(snapshots_path)) != NULL) {
489 while ((den = readdir(dir)) != NULL) {
490 if (den->d_name[0] == '.')
491 continue;
492 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
493 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode))
494 ++res;
495 free(fpath);
497 closedir(dir);
499 return(res);
503 * Clean up expired softlinks in the snapshots dir
505 static void
506 cleanup_softlinks(const char *path __unused, const char *snapshots_path,
507 int arg2, char *arg3)
509 struct dirent *den;
510 struct stat st;
511 DIR *dir;
512 char *fpath;
513 int anylink = 0;
515 if (arg3 != NULL && strstr(arg3, "any") != NULL)
516 anylink = 1;
518 if ((dir = opendir(snapshots_path)) != NULL) {
519 while ((den = readdir(dir)) != NULL) {
520 if (den->d_name[0] == '.')
521 continue;
522 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
523 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode) &&
524 (anylink || strncmp(den->d_name, "snap-", 5) == 0)
526 if (check_expired(den->d_name, arg2)) {
527 if (VerboseOpt) {
528 printf(" expire %s\n",
529 fpath);
531 remove(fpath);
534 free(fpath);
536 closedir(dir);
541 * Take a softlink path in the form snap-yyyymmdd-hhmm and the
542 * expiration in seconds (arg2) and return non-zero if the softlink
543 * has expired.
545 static int
546 check_expired(const char *fpath, int arg2)
548 struct tm tm;
549 time_t t;
550 int year;
551 int month;
552 int day = 0;
553 int hour = 0;
554 int minute = 0;
555 int r;
557 while (*fpath && *fpath != '-' && *fpath != '.')
558 ++fpath;
559 if (*fpath)
560 ++fpath;
562 r = sscanf(fpath, "%4d%2d%2d-%2d%2d",
563 &year, &month, &day, &hour, &minute);
565 if (r >= 3) {
566 bzero(&tm, sizeof(tm));
567 tm.tm_isdst = -1;
568 tm.tm_min = minute;
569 tm.tm_hour = hour;
570 tm.tm_mday = day;
571 tm.tm_mon = month - 1;
572 tm.tm_year = year - 1900;
573 t = mktime(&tm);
574 if (t == (time_t)-1)
575 return(0);
576 t = time(NULL) - t;
577 if ((int)t > arg2)
578 return(1);
580 return(0);
584 * Issue a snapshot.
586 static int
587 cleanup_snapshots(const char *path __unused, const char *snapshots_path,
588 int arg1 __unused, int arg2 __unused)
590 int r;
592 runcmd(&r, "hammer snapshot %s %s", path, snapshots_path);
593 return(r);
596 static int
597 cleanup_prune(const char *path __unused, const char *snapshots_path,
598 int arg1 __unused, int arg2, int snapshots_disabled)
601 * If snapshots have been disabled run prune-everything instead
602 * of prune.
604 if (snapshots_disabled && arg2) {
605 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune-everything %s",
606 snapshots_path, arg2, path);
607 } else if (snapshots_disabled) {
608 runcmd(NULL, "hammer prune-everything %s", path);
609 } else if (arg2) {
610 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune %s",
611 snapshots_path, arg2, snapshots_path);
612 } else {
613 runcmd(NULL, "hammer prune %s", snapshots_path);
615 return(0);
618 static int
619 cleanup_reblock(const char *path, const char *snapshots_path,
620 int arg1 __unused, int arg2)
622 if (VerboseOpt == 0) {
623 printf(".");
624 fflush(stdout);
628 * When reblocking the B-Tree always reblock everything in normal
629 * mode.
631 runcmd(NULL,
632 "hammer -c %s/.reblock-1.cycle -t %d reblock-btree %s",
633 snapshots_path, arg2, path);
634 if (VerboseOpt == 0) {
635 printf(".");
636 fflush(stdout);
640 * When reblocking the inodes always reblock everything in normal
641 * mode.
643 runcmd(NULL,
644 "hammer -c %s/.reblock-2.cycle -t %d reblock-inodes %s",
645 snapshots_path, arg2, path);
646 if (VerboseOpt == 0) {
647 printf(".");
648 fflush(stdout);
652 * When reblocking the directories always reblock everything in normal
653 * mode.
655 runcmd(NULL,
656 "hammer -c %s/.reblock-4.cycle -t %d reblock-dirs %s",
657 snapshots_path, arg2, path);
658 if (VerboseOpt == 0) {
659 printf(".");
660 fflush(stdout);
664 * Do not reblock all the data in normal mode.
666 runcmd(NULL,
667 "hammer -c %s/.reblock-3.cycle -t %d reblock-data %s 95",
668 snapshots_path, arg2, path);
669 if (VerboseOpt == 0)
670 printf("\n");
671 return(0);
674 static int
675 cleanup_recopy(const char *path, const char *snapshots_path,
676 int arg1 __unused, int arg2)
678 if (VerboseOpt == 0) {
679 printf(".");
680 fflush(stdout);
682 runcmd(NULL,
683 "hammer -c %s/.recopy-1.cycle -t %d reblock-btree %s",
684 snapshots_path, arg2, path);
685 if (VerboseOpt == 0) {
686 printf(".");
687 fflush(stdout);
689 runcmd(NULL,
690 "hammer -c %s/.recopy-2.cycle -t %d reblock-inodes %s",
691 snapshots_path, arg2, path);
692 if (VerboseOpt == 0) {
693 printf(".");
694 fflush(stdout);
696 runcmd(NULL,
697 "hammer -c %s/.recopy-4.cycle -t %d reblock-dirs %s",
698 snapshots_path, arg2, path);
699 if (VerboseOpt == 0) {
700 printf(".");
701 fflush(stdout);
703 runcmd(NULL,
704 "hammer -c %s/.recopy-3.cycle -t %d reblock-data %s",
705 snapshots_path, arg2, path);
706 if (VerboseOpt == 0)
707 printf("\n");
708 return(0);
711 static
712 void
713 runcmd(int *resp, const char *ctl, ...)
715 va_list va;
716 char *cmd;
717 char *arg;
718 char **av;
719 int n;
720 int nmax;
721 int res;
722 pid_t pid;
725 * Generate the command
727 va_start(va, ctl);
728 vasprintf(&cmd, ctl, va);
729 va_end(va);
730 if (VerboseOpt)
731 printf(" %s\n", cmd);
734 * Break us down into arguments. We do not just use system() here
735 * because it blocks SIGINT and friends.
737 n = 0;
738 nmax = 16;
739 av = malloc(sizeof(char *) * nmax);
741 for (arg = strtok(cmd, WS); arg; arg = strtok(NULL, WS)) {
742 if (n == nmax - 1) {
743 nmax += 16;
744 av = realloc(av, sizeof(char *) * nmax);
746 av[n++] = arg;
748 av[n++] = NULL;
751 * Run the command.
753 RunningIoctl = 1;
754 if ((pid = fork()) == 0) {
755 if (VerboseOpt < 2) {
756 int fd = open("/dev/null", O_RDWR);
757 dup2(fd, 1);
758 close(fd);
760 execvp(av[0], av);
761 _exit(127);
762 } else if (pid < 0) {
763 res = 127;
764 } else {
765 int status;
767 while (waitpid(pid, &status, 0) != pid)
769 res = WEXITSTATUS(status);
771 RunningIoctl = 0;
772 if (DidInterrupt)
773 _exit(1);
775 free(cmd);
776 free(av);
777 if (resp)
778 *resp = res;