8fc1e572e0bbecc5b1f2453b694aefdcfb187cd4
[dragonfly.git] / sbin / hammer / cmd_cleanup.c
blob8fc1e572e0bbecc5b1f2453b694aefdcfb187cd4
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:
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.4.2.2 2008/09/25 01:39:33 dillon 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 60d 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);
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 time_t savet;
146 char buf[256];
147 FILE *fp;
148 struct didpfs *didpfs;
149 int snapshots_disabled = 0;
150 int prune_warning = 0;
151 int fd;
152 int r;
154 bzero(&pfs, sizeof(pfs));
155 bzero(&mrec_tmp, sizeof(mrec_tmp));
156 pfs.ondisk = &mrec_tmp.pfs.pfsd;
157 pfs.bytes = sizeof(mrec_tmp.pfs.pfsd);
158 pfs.pfs_id = -1;
160 printf("cleanup %-20s -", path);
161 fd = open(path, O_RDONLY);
162 if (fd < 0) {
163 printf(" unable to access directory: %s\n", strerror(errno));
164 return;
166 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) != 0) {
167 printf(" not a HAMMER filesystem: %s\n", strerror(errno));
168 return;
170 close(fd);
171 if (pfs.version != HAMMER_IOC_PSEUDOFS_VERSION) {
172 printf(" unrecognized HAMMER version\n");
173 return;
177 * Make sure we have not already handled this PFS. Several nullfs
178 * mounts might alias the same PFS.
180 for (didpfs = FirstPFS; didpfs; didpfs = didpfs->next) {
181 if (bcmp(&didpfs->uuid, &mrec_tmp.pfs.pfsd.unique_uuid, sizeof(uuid_t)) == 0) {
182 printf(" pfs_id %d already handled\n", pfs.pfs_id);
183 return;
186 didpfs = malloc(sizeof(*didpfs));
187 didpfs->next = FirstPFS;
188 FirstPFS = didpfs;
189 didpfs->uuid = mrec_tmp.pfs.pfsd.unique_uuid;
192 * Figure out where the snapshot directory is.
194 if (mrec_tmp.pfs.pfsd.snapshots[0] == '/') {
195 asprintf(&snapshots_path, "%s", mrec_tmp.pfs.pfsd.snapshots);
196 } else if (mrec_tmp.pfs.pfsd.snapshots[0]) {
197 printf(" WARNING: pfs-slave's snapshots dir is not absolute\n");
198 return;
199 } else if (mrec_tmp.pfs.pfsd.mirror_flags & HAMMER_PFSD_SLAVE) {
200 printf(" WARNING: must configure snapshot dir for PFS slave\n");
201 printf("\tWe suggest <fs>/var/slaves/<name> where "
202 "<fs> is the base HAMMER fs\n");
203 printf("\tContaining the slave\n");
204 return;
205 } else {
206 asprintf(&snapshots_path,
207 "%s%ssnapshots", path, dividing_slash(path));
211 * Create a snapshot directory if necessary, and a config file if
212 * necessary.
214 if (stat(snapshots_path, &st) < 0) {
215 if (mkdir(snapshots_path, 0755) != 0) {
216 free(snapshots_path);
217 printf(" unable to create snapshot dir \"%s\": %s\n",
218 snapshots_path, strerror(errno));
219 return;
222 asprintf(&config_path, "%s/config", snapshots_path);
223 if ((fp = fopen(config_path, "r")) == NULL) {
224 fp = fopen(config_path, "w");
225 if (fp == NULL) {
226 printf(" cannot create %s: %s\n",
227 config_path, strerror(errno));
228 return;
230 if (strcmp(path, "/tmp") == 0 ||
231 strcmp(path, "/var/tmp") == 0 ||
232 strcmp(path, "/usr/obj") == 0) {
233 fprintf(fp, "snapshots 0d 60d\n");
234 } else {
235 fprintf(fp, "snapshots 1d 60d\n");
237 fprintf(fp,
238 "prune 1d 5m\n"
239 "reblock 1d 5m\n"
240 "recopy 30d 10m\n");
241 fclose(fp);
242 fp = fopen(config_path, "r");
244 if (fp == NULL) {
245 printf(" cannot access %s: %s\n",
246 config_path, strerror(errno));
247 return;
250 printf(" handle PFS #%d using %s\n", pfs.pfs_id, snapshots_path);
253 * Process the config file
255 while (fgets(buf, sizeof(buf), fp) != NULL) {
256 cmd = strtok(buf, WS);
257 arg1 = 0;
258 arg2 = 0;
259 if ((ptr = strtok(NULL, WS)) != NULL) {
260 arg1 = strtosecs(ptr);
261 if ((ptr = strtok(NULL, WS)) != NULL)
262 arg2 = strtosecs(ptr);
265 printf("%20s - ", cmd);
266 fflush(stdout);
268 if (arg1 == 0) {
269 printf("disabled\n");
270 if (strcmp(cmd, "snapshots") == 0) {
271 if (check_softlinks(snapshots_path))
272 prune_warning = 1;
273 else
274 snapshots_disabled = 1;
276 continue;
279 r = 1;
280 if (strcmp(cmd, "snapshots") == 0) {
281 if (check_period(snapshots_path, cmd, arg1, &savet)) {
282 printf("run\n");
283 cleanup_softlinks(path, snapshots_path, arg2);
284 r = cleanup_snapshots(path, snapshots_path,
285 arg1, arg2);
286 } else {
287 printf("skip\n");
289 } else if (strcmp(cmd, "prune") == 0) {
290 if (check_period(snapshots_path, cmd, arg1, &savet)) {
291 if (prune_warning)
292 printf("run - WARNING snapshot softlinks present but snapshots disabled\n");
293 else
294 printf("run\n");
295 r = cleanup_prune(path, snapshots_path,
296 arg1, arg2, snapshots_disabled);
297 } else {
298 printf("skip\n");
300 } else if (strcmp(cmd, "reblock") == 0) {
301 if (check_period(snapshots_path, cmd, arg1, &savet)) {
302 printf("run");
303 fflush(stdout);
304 if (VerboseOpt)
305 printf("\n");
306 r = cleanup_reblock(path, snapshots_path,
307 arg1, arg2);
308 } else {
309 printf("skip\n");
311 } else if (strcmp(cmd, "recopy") == 0) {
312 if (check_period(snapshots_path, cmd, arg1, &savet)) {
313 printf("run");
314 fflush(stdout);
315 if (VerboseOpt)
316 printf("\n");
317 r = cleanup_recopy(path, snapshots_path,
318 arg1, arg2);
319 } else {
320 printf("skip\n");
322 } else {
323 printf("unknown directive\n");
324 r = 1;
326 if (r == 0)
327 save_period(snapshots_path, cmd, savet);
329 fclose(fp);
330 usleep(1000);
333 static
335 strtosecs(char *ptr)
337 int val;
339 val = strtol(ptr, &ptr, 0);
340 switch(*ptr) {
341 case 'd':
342 val *= 24;
343 /* fall through */
344 case 'h':
345 val *= 60;
346 /* fall through */
347 case 'm':
348 val *= 60;
349 /* fall through */
350 case 's':
351 break;
352 default:
353 errx(1, "illegal suffix converting %s\n", ptr);
354 break;
356 return(val);
359 static const char *
360 dividing_slash(const char *path)
362 int len = strlen(path);
363 if (len && path[len-1] == '/')
364 return("");
365 else
366 return("/");
370 * Check whether the desired period has elapsed since the last successful
371 * run. The run may take a while and cross a boundary so we remember the
372 * current time_t so we can save it later on.
374 * Periods in minutes, hours, or days are assumed to have been crossed
375 * if the local time crosses a minute, hour, or day boundary regardless
376 * of how close the last operation actually was.
378 static int
379 check_period(const char *snapshots_path, const char *cmd, int arg1,
380 time_t *savep)
382 char *check_path;
383 struct tm tp1;
384 struct tm tp2;
385 FILE *fp;
386 time_t baset, lastt;
387 char buf[256];
389 time(savep);
390 localtime_r(savep, &tp1);
393 * Retrieve the start time of the last successful operation.
395 asprintf(&check_path, "%s/.%s.period", snapshots_path, cmd);
396 fp = fopen(check_path, "r");
397 free(check_path);
398 if (fp == NULL)
399 return(1);
400 if (fgets(buf, sizeof(buf), fp) == NULL) {
401 fclose(fp);
402 return(1);
404 fclose(fp);
406 lastt = strtol(buf, NULL, 0);
407 localtime_r(&lastt, &tp2);
410 * Normalize the times. e.g. if asked to do something on a 1-day
411 * interval the operation will be performed as soon as the day
412 * turns over relative to the previous operation, even if the previous
413 * operation ran a few seconds ago just before midnight.
415 if (arg1 % 60 == 0) {
416 tp1.tm_sec = 0;
417 tp2.tm_sec = 0;
419 if (arg1 % (60 * 60) == 0) {
420 tp1.tm_min = 0;
421 tp2.tm_min = 0;
423 if (arg1 % (24 * 60 * 60) == 0) {
424 tp1.tm_hour = 0;
425 tp2.tm_hour = 0;
428 baset = mktime(&tp1);
429 lastt = mktime(&tp2);
431 #if 0
432 printf("%lld vs %lld\n", (long long)(baset - lastt), (long long)arg1);
433 #endif
435 if ((int)(baset - lastt) >= arg1)
436 return(1);
437 return(0);
441 * Store the start time of the last successful operation.
443 static void
444 save_period(const char *snapshots_path, const char *cmd,
445 time_t savet)
447 char *ocheck_path;
448 char *ncheck_path;
449 FILE *fp;
451 asprintf(&ocheck_path, "%s/.%s.period", snapshots_path, cmd);
452 asprintf(&ncheck_path, "%s/.%s.period.new", snapshots_path, cmd);
453 fp = fopen(ncheck_path, "w");
454 fprintf(fp, "0x%08llx\n", (long long)savet);
455 if (fclose(fp) == 0)
456 rename(ncheck_path, ocheck_path);
457 remove(ncheck_path);
461 * Simply count the number of softlinks in the snapshots dir
463 static int
464 check_softlinks(const char *snapshots_path)
466 struct dirent *den;
467 struct stat st;
468 DIR *dir;
469 char *fpath;
470 int res = 0;
472 if ((dir = opendir(snapshots_path)) != NULL) {
473 while ((den = readdir(dir)) != NULL) {
474 if (den->d_name[0] == '.')
475 continue;
476 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
477 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode))
478 ++res;
479 free(fpath);
481 closedir(dir);
483 return(res);
487 * Clean up expired softlinks in the snapshots dir
489 static void
490 cleanup_softlinks(const char *path __unused, const char *snapshots_path, int arg2)
492 struct dirent *den;
493 struct stat st;
494 DIR *dir;
495 char *fpath;
497 if ((dir = opendir(snapshots_path)) != NULL) {
498 while ((den = readdir(dir)) != NULL) {
499 if (den->d_name[0] == '.')
500 continue;
501 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
502 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode) &&
503 strncmp(den->d_name, "snap-", 5) == 0) {
504 if (check_expired(den->d_name, arg2)) {
505 if (VerboseOpt) {
506 printf(" expire %s\n",
507 fpath);
509 remove(fpath);
512 free(fpath);
514 closedir(dir);
519 * Take a softlink path in the form snap-yyyymmdd-hhmm and the
520 * expiration in seconds (arg2) and return non-zero if the softlink
521 * has expired.
523 static int
524 check_expired(const char *fpath, int arg2)
526 struct tm tm;
527 time_t t;
528 int year;
529 int month;
530 int day;
531 int hour;
532 int minute;
533 int r;
535 r = sscanf(fpath, "snap-%4d%2d%2d-%2d%2d",
536 &year, &month, &day, &hour, &minute);
537 if (r == 5) {
538 bzero(&tm, sizeof(tm));
539 tm.tm_isdst = -1;
540 tm.tm_min = minute;
541 tm.tm_hour = hour;
542 tm.tm_mday = day;
543 tm.tm_mon = month - 1;
544 tm.tm_year = year - 1900;
545 t = time(NULL) - mktime(&tm);
546 if ((int)t > arg2)
547 return(1);
549 return(0);
553 * Issue a snapshot.
555 static int
556 cleanup_snapshots(const char *path __unused, const char *snapshots_path,
557 int arg1 __unused, int arg2 __unused)
559 int r;
561 runcmd(&r, "hammer snapshot %s %s", path, snapshots_path);
562 return(r);
565 static int
566 cleanup_prune(const char *path __unused, const char *snapshots_path,
567 int arg1 __unused, int arg2, int snapshots_disabled)
570 * If snapshots have been disabled run prune-everything instead
571 * of prune.
573 if (snapshots_disabled && arg2) {
574 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune-everything %s",
575 snapshots_path, arg2, path);
576 } else if (snapshots_disabled) {
577 runcmd(NULL, "hammer prune-everything %s", path);
578 } else if (arg2) {
579 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune %s",
580 snapshots_path, arg2, snapshots_path);
581 } else {
582 runcmd(NULL, "hammer prune %s", snapshots_path);
584 return(0);
587 static int
588 cleanup_reblock(const char *path, const char *snapshots_path,
589 int arg1 __unused, int arg2)
591 if (VerboseOpt == 0) {
592 printf(".");
593 fflush(stdout);
595 runcmd(NULL,
596 "hammer -c %s/.reblock-1.cycle -t %d reblock-btree %s 95",
597 snapshots_path, arg2, path);
598 if (VerboseOpt == 0) {
599 printf(".");
600 fflush(stdout);
602 runcmd(NULL,
603 "hammer -c %s/.reblock-2.cycle -t %d reblock-inodes %s 95",
604 snapshots_path, arg2, path);
605 if (VerboseOpt == 0) {
606 printf(".");
607 fflush(stdout);
609 runcmd(NULL,
610 "hammer -c %s/.reblock-3.cycle -t %d reblock-data %s 95",
611 snapshots_path, arg2, path);
612 if (VerboseOpt == 0)
613 printf("\n");
614 return(0);
617 static int
618 cleanup_recopy(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/.recopy-1.cycle -t %d reblock-btree %s",
627 snapshots_path, arg2, path);
628 if (VerboseOpt == 0) {
629 printf(".");
630 fflush(stdout);
632 runcmd(NULL,
633 "hammer -c %s/.recopy-2.cycle -t %d reblock-inodes %s",
634 snapshots_path, arg2, path);
635 if (VerboseOpt == 0) {
636 printf(".");
637 fflush(stdout);
639 runcmd(NULL,
640 "hammer -c %s/.recopy-3.cycle -t %d reblock-data %s",
641 snapshots_path, arg2, path);
642 if (VerboseOpt == 0)
643 printf("\n");
644 return(0);
647 static
648 void
649 runcmd(int *resp, const char *ctl, ...)
651 va_list va;
652 char *cmd;
653 char *arg;
654 char **av;
655 int n;
656 int nmax;
657 int res;
658 pid_t pid;
661 * Generate the command
663 va_start(va, ctl);
664 vasprintf(&cmd, ctl, va);
665 va_end(va);
666 if (VerboseOpt)
667 printf(" %s\n", cmd);
670 * Break us down into arguments. We do not just use system() here
671 * because it blocks SIGINT and friends.
673 n = 0;
674 nmax = 16;
675 av = malloc(sizeof(char *) * nmax);
677 for (arg = strtok(cmd, WS); arg; arg = strtok(NULL, WS)) {
678 if (n == nmax - 1) {
679 nmax += 16;
680 av = realloc(av, sizeof(char *) * nmax);
682 av[n++] = arg;
684 av[n++] = NULL;
687 * Run the command.
689 if ((pid = fork()) == 0) {
690 if (VerboseOpt < 2) {
691 int fd = open("/dev/null", O_RDWR);
692 dup2(fd, 1);
693 close(fd);
695 execvp(av[0], av);
696 _exit(127);
697 } else if (pid < 0) {
698 res = 127;
699 } else {
700 int status;
701 while (waitpid(pid, &status, 0) != pid)
703 res = WEXITSTATUS(status);
706 free(cmd);
707 free(av);
708 if (resp)
709 *resp = res;