test some more
[dragonfly.git] / sbin / hammer / cmd_cleanup.c
blob836628639bd030fc40f192ccf3ecb339916ac13b
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 *ptr;
96 char *path;
97 char buf[256];
99 tzset();
100 if (ac == 0) {
101 fp = popen("df -t hammer,null", "r");
102 if (fp == NULL)
103 errx(1, "hammer cleanup: 'df' failed");
104 while (fgets(buf, sizeof(buf), fp) != NULL) {
105 ptr = strtok(buf, WS);
106 if (ptr && strcmp(ptr, "Filesystem") == 0)
107 continue;
108 if (ptr)
109 ptr = strtok(NULL, WS);
110 if (ptr)
111 ptr = strtok(NULL, WS);
112 if (ptr)
113 ptr = strtok(NULL, WS);
114 if (ptr)
115 ptr = strtok(NULL, WS);
116 if (ptr) {
117 path = strtok(NULL, WS);
118 if (path)
119 do_cleanup(path);
122 fclose(fp);
123 } else {
124 while (ac) {
125 do_cleanup(*av);
126 --ac;
127 ++av;
132 static
133 void
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;
139 char *config_path;
140 struct stat st;
141 char *cmd;
142 char *ptr;
143 int arg1;
144 int arg2;
145 char *arg3;
146 time_t savet;
147 char buf[256];
148 FILE *fp;
149 struct didpfs *didpfs;
150 int snapshots_disabled = 0;
151 int prune_warning = 0;
152 int fd;
153 int r;
155 bzero(&pfs, sizeof(pfs));
156 bzero(&mrec_tmp, sizeof(mrec_tmp));
157 pfs.ondisk = &mrec_tmp.pfs.pfsd;
158 pfs.bytes = sizeof(mrec_tmp.pfs.pfsd);
159 pfs.pfs_id = -1;
161 printf("cleanup %-20s -", path);
162 fd = open(path, O_RDONLY);
163 if (fd < 0) {
164 printf(" unable to access directory: %s\n", strerror(errno));
165 return;
167 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) != 0) {
168 printf(" not a HAMMER filesystem: %s\n", strerror(errno));
169 return;
171 close(fd);
172 if (pfs.version != HAMMER_IOC_PSEUDOFS_VERSION) {
173 printf(" unrecognized HAMMER version\n");
174 return;
178 * Make sure we have not already handled this PFS. Several nullfs
179 * mounts might alias the same PFS.
181 for (didpfs = FirstPFS; didpfs; didpfs = didpfs->next) {
182 if (bcmp(&didpfs->uuid, &mrec_tmp.pfs.pfsd.unique_uuid, sizeof(uuid_t)) == 0) {
183 printf(" PFS #%d already handled\n", pfs.pfs_id);
184 return;
187 didpfs = malloc(sizeof(*didpfs));
188 didpfs->next = FirstPFS;
189 FirstPFS = didpfs;
190 didpfs->uuid = mrec_tmp.pfs.pfsd.unique_uuid;
193 * Figure out where the snapshot directory is.
195 if (mrec_tmp.pfs.pfsd.snapshots[0] == '/') {
196 asprintf(&snapshots_path, "%s", mrec_tmp.pfs.pfsd.snapshots);
197 } else if (mrec_tmp.pfs.pfsd.snapshots[0]) {
198 printf(" WARNING: pfs-slave's snapshots dir is not absolute\n");
199 return;
200 } else if (mrec_tmp.pfs.pfsd.mirror_flags & HAMMER_PFSD_SLAVE) {
201 printf(" WARNING: must configure snapshot dir for PFS slave\n");
202 printf("\tWe suggest <fs>/var/slaves/<name> where "
203 "<fs> is the base HAMMER fs\n");
204 printf("\tcontaining the slave\n");
205 return;
206 } else {
207 asprintf(&snapshots_path,
208 "%s%ssnapshots", path, dividing_slash(path));
212 * Create a snapshot directory if necessary, and a config file if
213 * necessary.
215 if (stat(snapshots_path, &st) < 0) {
216 if (mkdir(snapshots_path, 0755) != 0) {
217 free(snapshots_path);
218 printf(" unable to create snapshot dir \"%s\": %s\n",
219 snapshots_path, strerror(errno));
220 return;
223 asprintf(&config_path, "%s/config", snapshots_path);
224 if ((fp = fopen(config_path, "r")) == NULL) {
225 fp = fopen(config_path, "w");
226 if (fp == NULL) {
227 printf(" cannot create %s: %s\n",
228 config_path, strerror(errno));
229 return;
231 if (strcmp(path, "/tmp") == 0 ||
232 strcmp(path, "/var/tmp") == 0 ||
233 strcmp(path, "/usr/obj") == 0) {
234 fprintf(fp, "snapshots 0d 0d\n");
235 } else {
236 fprintf(fp, "snapshots 1d 60d\n");
238 fprintf(fp,
239 "prune 1d 5m\n"
240 "reblock 1d 5m\n"
241 "recopy 30d 10m\n");
242 fclose(fp);
243 fp = fopen(config_path, "r");
245 if (fp == NULL) {
246 printf(" cannot access %s: %s\n",
247 config_path, strerror(errno));
248 return;
251 printf(" handle PFS #%d using %s\n", pfs.pfs_id, snapshots_path);
254 * Process the config file
256 while (fgets(buf, sizeof(buf), fp) != NULL) {
257 cmd = strtok(buf, WS);
258 arg1 = 0;
259 arg2 = 0;
260 arg3 = NULL;
261 if ((ptr = strtok(NULL, WS)) != NULL) {
262 arg1 = strtosecs(ptr);
263 if ((ptr = strtok(NULL, WS)) != NULL) {
264 arg2 = strtosecs(ptr);
265 arg3 = strtok(NULL, WS);
269 printf("%20s - ", cmd);
270 fflush(stdout);
272 r = 1;
273 if (strcmp(cmd, "snapshots") == 0) {
274 if (arg1 == 0) {
275 if (arg2 && check_softlinks(snapshots_path)) {
276 printf("only removing old snapshots\n");
277 prune_warning = 1;
278 cleanup_softlinks(path, snapshots_path,
279 arg2, arg3);
280 } else {
281 printf("disabled\n");
282 snapshots_disabled = 1;
284 } else
285 if (check_period(snapshots_path, cmd, arg1, &savet)) {
286 printf("run\n");
287 cleanup_softlinks(path, snapshots_path,
288 arg2, arg3);
289 r = cleanup_snapshots(path, snapshots_path,
290 arg1, arg2);
291 } else {
292 printf("skip\n");
294 } else if (arg1 == 0) {
296 * The commands following this check can't handle
297 * a period of 0, so call the feature disabled and
298 * ignore the directive.
300 printf("disabled\n");
301 } else if (strcmp(cmd, "prune") == 0) {
302 if (check_period(snapshots_path, cmd, arg1, &savet)) {
303 if (prune_warning) {
304 printf("run - WARNING snapshot "
305 "softlinks present "
306 "but snapshots disabled\n");
307 } else {
308 printf("run\n");
310 r = cleanup_prune(path, snapshots_path,
311 arg1, arg2, snapshots_disabled);
312 } else {
313 printf("skip\n");
315 } else if (strcmp(cmd, "reblock") == 0) {
316 if (check_period(snapshots_path, cmd, arg1, &savet)) {
317 printf("run");
318 fflush(stdout);
319 if (VerboseOpt)
320 printf("\n");
321 r = cleanup_reblock(path, snapshots_path,
322 arg1, arg2);
323 } else {
324 printf("skip\n");
326 } else if (strcmp(cmd, "recopy") == 0) {
327 if (check_period(snapshots_path, cmd, arg1, &savet)) {
328 printf("run");
329 fflush(stdout);
330 if (VerboseOpt)
331 printf("\n");
332 r = cleanup_recopy(path, snapshots_path,
333 arg1, arg2);
334 } else {
335 printf("skip\n");
337 } else {
338 printf("unknown directive\n");
339 r = 1;
341 if (r == 0)
342 save_period(snapshots_path, cmd, savet);
344 fclose(fp);
345 usleep(1000);
348 static
350 strtosecs(char *ptr)
352 int val;
354 val = strtol(ptr, &ptr, 0);
355 switch(*ptr) {
356 case 'd':
357 val *= 24;
358 /* fall through */
359 case 'h':
360 val *= 60;
361 /* fall through */
362 case 'm':
363 val *= 60;
364 /* fall through */
365 case 's':
366 break;
367 default:
368 errx(1, "illegal suffix converting %s\n", ptr);
369 break;
371 return(val);
374 static const char *
375 dividing_slash(const char *path)
377 int len = strlen(path);
378 if (len && path[len-1] == '/')
379 return("");
380 else
381 return("/");
385 * Check whether the desired period has elapsed since the last successful
386 * run. The run may take a while and cross a boundary so we remember the
387 * current time_t so we can save it later on.
389 * Periods in minutes, hours, or days are assumed to have been crossed
390 * if the local time crosses a minute, hour, or day boundary regardless
391 * of how close the last operation actually was.
393 static int
394 check_period(const char *snapshots_path, const char *cmd, int arg1,
395 time_t *savep)
397 char *check_path;
398 struct tm tp1;
399 struct tm tp2;
400 FILE *fp;
401 time_t baset, lastt;
402 char buf[256];
404 time(savep);
405 localtime_r(savep, &tp1);
408 * Retrieve the start time of the last successful operation.
410 asprintf(&check_path, "%s/.%s.period", snapshots_path, cmd);
411 fp = fopen(check_path, "r");
412 free(check_path);
413 if (fp == NULL)
414 return(1);
415 if (fgets(buf, sizeof(buf), fp) == NULL) {
416 fclose(fp);
417 return(1);
419 fclose(fp);
421 lastt = strtol(buf, NULL, 0);
422 localtime_r(&lastt, &tp2);
425 * Normalize the times. e.g. if asked to do something on a 1-day
426 * interval the operation will be performed as soon as the day
427 * turns over relative to the previous operation, even if the previous
428 * operation ran a few seconds ago just before midnight.
430 if (arg1 % 60 == 0) {
431 tp1.tm_sec = 0;
432 tp2.tm_sec = 0;
434 if (arg1 % (60 * 60) == 0) {
435 tp1.tm_min = 0;
436 tp2.tm_min = 0;
438 if (arg1 % (24 * 60 * 60) == 0) {
439 tp1.tm_hour = 0;
440 tp2.tm_hour = 0;
443 baset = mktime(&tp1);
444 lastt = mktime(&tp2);
446 #if 0
447 printf("%lld vs %lld\n", (long long)(baset - lastt), (long long)arg1);
448 #endif
450 if ((int)(baset - lastt) >= arg1)
451 return(1);
452 return(0);
456 * Store the start time of the last successful operation.
458 static void
459 save_period(const char *snapshots_path, const char *cmd,
460 time_t savet)
462 char *ocheck_path;
463 char *ncheck_path;
464 FILE *fp;
466 asprintf(&ocheck_path, "%s/.%s.period", snapshots_path, cmd);
467 asprintf(&ncheck_path, "%s/.%s.period.new", snapshots_path, cmd);
468 fp = fopen(ncheck_path, "w");
469 fprintf(fp, "0x%08llx\n", (long long)savet);
470 if (fclose(fp) == 0)
471 rename(ncheck_path, ocheck_path);
472 remove(ncheck_path);
476 * Simply count the number of softlinks in the snapshots dir
478 static int
479 check_softlinks(const char *snapshots_path)
481 struct dirent *den;
482 struct stat st;
483 DIR *dir;
484 char *fpath;
485 int res = 0;
487 if ((dir = opendir(snapshots_path)) != NULL) {
488 while ((den = readdir(dir)) != NULL) {
489 if (den->d_name[0] == '.')
490 continue;
491 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
492 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode))
493 ++res;
494 free(fpath);
496 closedir(dir);
498 return(res);
502 * Clean up expired softlinks in the snapshots dir
504 static void
505 cleanup_softlinks(const char *path __unused, const char *snapshots_path,
506 int arg2, char *arg3)
508 struct dirent *den;
509 struct stat st;
510 DIR *dir;
511 char *fpath;
512 int anylink = 0;
514 if (strstr(arg3, "any") != NULL)
515 anylink = 1;
517 if ((dir = opendir(snapshots_path)) != NULL) {
518 while ((den = readdir(dir)) != NULL) {
519 if (den->d_name[0] == '.')
520 continue;
521 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
522 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode) &&
523 (anylink || strncmp(den->d_name, "snap-", 5) == 0)
525 if (check_expired(den->d_name, arg2)) {
526 if (VerboseOpt) {
527 printf(" expire %s\n",
528 fpath);
530 remove(fpath);
533 free(fpath);
535 closedir(dir);
540 * Take a softlink path in the form snap-yyyymmdd-hhmm and the
541 * expiration in seconds (arg2) and return non-zero if the softlink
542 * has expired.
544 static int
545 check_expired(const char *fpath, int arg2)
547 struct tm tm;
548 time_t t;
549 int year;
550 int month;
551 int day = 0;
552 int hour = 0;
553 int minute = 0;
554 int r;
556 while (*fpath && *fpath != '-' && *fpath != '.')
557 ++fpath;
558 if (*fpath)
559 ++fpath;
561 r = sscanf(fpath, "%4d%2d%2d-%2d%2d",
562 &year, &month, &day, &hour, &minute);
564 if (r >= 3) {
565 bzero(&tm, sizeof(tm));
566 tm.tm_isdst = -1;
567 tm.tm_min = minute;
568 tm.tm_hour = hour;
569 tm.tm_mday = day;
570 tm.tm_mon = month - 1;
571 tm.tm_year = year - 1900;
572 t = mktime(&tm);
573 if (t == (time_t)-1)
574 return(0);
575 t = time(NULL) - t;
576 if ((int)t > arg2)
577 return(1);
579 return(0);
583 * Issue a snapshot.
585 static int
586 cleanup_snapshots(const char *path __unused, const char *snapshots_path,
587 int arg1 __unused, int arg2 __unused)
589 int r;
591 runcmd(&r, "hammer snapshot %s %s", path, snapshots_path);
592 return(r);
595 static int
596 cleanup_prune(const char *path __unused, const char *snapshots_path,
597 int arg1 __unused, int arg2, int snapshots_disabled)
600 * If snapshots have been disabled run prune-everything instead
601 * of prune.
603 if (snapshots_disabled && arg2) {
604 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune-everything %s",
605 snapshots_path, arg2, path);
606 } else if (snapshots_disabled) {
607 runcmd(NULL, "hammer prune-everything %s", path);
608 } else if (arg2) {
609 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune %s",
610 snapshots_path, arg2, snapshots_path);
611 } else {
612 runcmd(NULL, "hammer prune %s", snapshots_path);
614 return(0);
617 static int
618 cleanup_reblock(const char *path, const char *snapshots_path,
619 int arg1 __unused, int arg2)
621 if (VerboseOpt == 0) {
622 printf(".");
623 fflush(stdout);
625 runcmd(NULL,
626 "hammer -c %s/.reblock-1.cycle -t %d reblock-btree %s 95",
627 snapshots_path, arg2, path);
628 if (VerboseOpt == 0) {
629 printf(".");
630 fflush(stdout);
632 runcmd(NULL,
633 "hammer -c %s/.reblock-2.cycle -t %d reblock-inodes %s 95",
634 snapshots_path, arg2, path);
635 if (VerboseOpt == 0) {
636 printf(".");
637 fflush(stdout);
639 runcmd(NULL,
640 "hammer -c %s/.reblock-3.cycle -t %d reblock-data %s 95",
641 snapshots_path, arg2, path);
642 if (VerboseOpt == 0)
643 printf("\n");
644 return(0);
647 static int
648 cleanup_recopy(const char *path, const char *snapshots_path,
649 int arg1 __unused, int arg2)
651 if (VerboseOpt == 0) {
652 printf(".");
653 fflush(stdout);
655 runcmd(NULL,
656 "hammer -c %s/.recopy-1.cycle -t %d reblock-btree %s",
657 snapshots_path, arg2, path);
658 if (VerboseOpt == 0) {
659 printf(".");
660 fflush(stdout);
662 runcmd(NULL,
663 "hammer -c %s/.recopy-2.cycle -t %d reblock-inodes %s",
664 snapshots_path, arg2, path);
665 if (VerboseOpt == 0) {
666 printf(".");
667 fflush(stdout);
669 runcmd(NULL,
670 "hammer -c %s/.recopy-3.cycle -t %d reblock-data %s",
671 snapshots_path, arg2, path);
672 if (VerboseOpt == 0)
673 printf("\n");
674 return(0);
677 static
678 void
679 runcmd(int *resp, const char *ctl, ...)
681 va_list va;
682 char *cmd;
683 char *arg;
684 char **av;
685 int n;
686 int nmax;
687 int res;
688 pid_t pid;
691 * Generate the command
693 va_start(va, ctl);
694 vasprintf(&cmd, ctl, va);
695 va_end(va);
696 if (VerboseOpt)
697 printf(" %s\n", cmd);
700 * Break us down into arguments. We do not just use system() here
701 * because it blocks SIGINT and friends.
703 n = 0;
704 nmax = 16;
705 av = malloc(sizeof(char *) * nmax);
707 for (arg = strtok(cmd, WS); arg; arg = strtok(NULL, WS)) {
708 if (n == nmax - 1) {
709 nmax += 16;
710 av = realloc(av, sizeof(char *) * nmax);
712 av[n++] = arg;
714 av[n++] = NULL;
717 * Run the command.
719 if ((pid = fork()) == 0) {
720 if (VerboseOpt < 2) {
721 int fd = open("/dev/null", O_RDWR);
722 dup2(fd, 1);
723 close(fd);
725 execvp(av[0], av);
726 _exit(127);
727 } else if (pid < 0) {
728 res = 127;
729 } else {
730 int status;
731 while (waitpid(pid, &status, 0) != pid)
733 res = WEXITSTATUS(status);
736 free(cmd);
737 free(av);
738 if (resp)
739 *resp = res;