fdisk - Use heads = 255 on file images
[dragonfly.git] / sbin / hammer / cmd_cleanup.c
blobdb46e2d2fdf08255efd54e30d5607a84e04f2237
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 create_snapshot(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 char *fstype, *fs, *path;
95 struct statfs *stfsbuf;
96 int mntsize, i;
98 tzset();
99 if (ac == 0) {
100 mntsize = getmntinfo(&stfsbuf, MNT_NOWAIT);
101 if (mntsize > 0) {
102 for (i=0; i < mntsize; i++) {
104 * We will cleanup in the case fstype is hammer.
105 * If we have null-mounted PFS, we check the
106 * mount source. If it looks like a PFS, we
107 * proceed to cleanup also.
109 fstype = stfsbuf[i].f_fstypename;
110 fs = stfsbuf[i].f_mntfromname;
111 if ((strcmp(fstype, "hammer") == 0) ||
112 ((strcmp(fstype, "null") == 0) &&
113 (strstr(fs, "/@@0x") != NULL ||
114 strstr(fs, "/@@-1") != NULL))) {
115 path = stfsbuf[i].f_mntonname;
116 do_cleanup(path);
121 } else {
122 while (ac) {
123 do_cleanup(*av);
124 --ac;
125 ++av;
130 static
131 void
132 do_cleanup(const char *path)
134 struct hammer_ioc_pseudofs_rw pfs;
135 union hammer_ioc_mrecord_any mrec_tmp;
136 char *snapshots_path;
137 char *config_path;
138 struct stat st;
139 char *cmd;
140 char *ptr;
141 int arg1;
142 int arg2;
143 char *arg3;
144 time_t savet;
145 char buf[256];
146 FILE *fp;
147 struct didpfs *didpfs;
148 int snapshots_disabled = 0;
149 int prune_warning = 0;
150 int fd;
151 int r;
153 bzero(&pfs, sizeof(pfs));
154 bzero(&mrec_tmp, sizeof(mrec_tmp));
155 pfs.ondisk = &mrec_tmp.pfs.pfsd;
156 pfs.bytes = sizeof(mrec_tmp.pfs.pfsd);
157 pfs.pfs_id = -1;
159 printf("cleanup %-20s -", path);
160 fd = open(path, O_RDONLY);
161 if (fd < 0) {
162 printf(" unable to access directory: %s\n", strerror(errno));
163 return;
165 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) != 0) {
166 printf(" not a HAMMER filesystem: %s\n", strerror(errno));
167 return;
169 close(fd);
170 if (pfs.version != HAMMER_IOC_PSEUDOFS_VERSION) {
171 printf(" unrecognized HAMMER version\n");
172 return;
176 * Make sure we have not already handled this PFS. Several nullfs
177 * mounts might alias the same PFS.
179 for (didpfs = FirstPFS; didpfs; didpfs = didpfs->next) {
180 if (bcmp(&didpfs->uuid, &mrec_tmp.pfs.pfsd.unique_uuid, sizeof(uuid_t)) == 0) {
181 printf(" PFS #%d already handled\n", pfs.pfs_id);
182 return;
185 didpfs = malloc(sizeof(*didpfs));
186 didpfs->next = FirstPFS;
187 FirstPFS = didpfs;
188 didpfs->uuid = mrec_tmp.pfs.pfsd.unique_uuid;
191 * Figure out where the snapshot directory is.
193 if (mrec_tmp.pfs.pfsd.snapshots[0] == '/') {
194 asprintf(&snapshots_path, "%s", mrec_tmp.pfs.pfsd.snapshots);
195 } else if (mrec_tmp.pfs.pfsd.snapshots[0]) {
196 printf(" WARNING: pfs-slave's snapshots dir is not absolute\n");
197 return;
198 } else if (mrec_tmp.pfs.pfsd.mirror_flags & HAMMER_PFSD_SLAVE) {
199 printf(" WARNING: must configure snapshot dir for PFS slave\n");
200 printf("\tWe suggest <fs>/var/slaves/<name> where "
201 "<fs> is the base HAMMER fs\n");
202 printf("\tcontaining the slave\n");
203 return;
204 } else {
205 asprintf(&snapshots_path,
206 "%s%ssnapshots", path, dividing_slash(path));
210 * Create a snapshot directory if necessary, and a config file if
211 * necessary.
213 if (stat(snapshots_path, &st) < 0) {
214 if (mkdir(snapshots_path, 0755) != 0) {
215 free(snapshots_path);
216 printf(" unable to create snapshot dir \"%s\": %s\n",
217 snapshots_path, strerror(errno));
218 return;
221 asprintf(&config_path, "%s/config", snapshots_path);
222 if ((fp = fopen(config_path, "r")) == NULL) {
223 fp = fopen(config_path, "w");
224 if (fp == NULL) {
225 printf(" cannot create %s: %s\n",
226 config_path, strerror(errno));
227 return;
229 if (strcmp(path, "/tmp") == 0 ||
230 strcmp(path, "/var/tmp") == 0 ||
231 strcmp(path, "/usr/obj") == 0) {
232 fprintf(fp, "snapshots 0d 0d\n");
233 } else {
234 fprintf(fp, "snapshots 1d 60d\n");
236 fprintf(fp,
237 "prune 1d 5m\n"
238 "reblock 1d 5m\n"
239 "recopy 30d 10m\n");
240 fclose(fp);
241 fp = fopen(config_path, "r");
243 if (fp == NULL) {
244 printf(" cannot access %s: %s\n",
245 config_path, strerror(errno));
246 return;
249 if (flock(fileno(fp), LOCK_EX|LOCK_NB) == -1) {
250 if (errno == EWOULDBLOCK)
251 printf(" PFS #%d locked by other process\n", pfs.pfs_id);
252 else
253 printf(" can not lock %s: %s\n", config_path, strerror(errno));
254 fclose(fp);
255 return;
258 printf(" handle PFS #%d using %s\n", pfs.pfs_id, snapshots_path);
261 * Process the config file
263 while (fgets(buf, sizeof(buf), fp) != NULL) {
264 cmd = strtok(buf, WS);
265 arg1 = 0;
266 arg2 = 0;
267 arg3 = NULL;
268 if ((ptr = strtok(NULL, WS)) != NULL) {
269 arg1 = strtosecs(ptr);
270 if ((ptr = strtok(NULL, WS)) != NULL) {
271 arg2 = strtosecs(ptr);
272 arg3 = strtok(NULL, WS);
276 printf("%20s - ", cmd);
277 fflush(stdout);
279 r = 1;
280 if (strcmp(cmd, "snapshots") == 0) {
281 if (arg1 == 0) {
282 if (arg2 && check_softlinks(snapshots_path)) {
283 printf("only removing old snapshots\n");
284 prune_warning = 1;
285 cleanup_softlinks(path, snapshots_path,
286 arg2, arg3);
287 } else {
288 printf("disabled\n");
289 snapshots_disabled = 1;
291 } else
292 if (check_period(snapshots_path, cmd, arg1, &savet)) {
293 printf("run\n");
294 cleanup_softlinks(path, snapshots_path,
295 arg2, arg3);
296 r = create_snapshot(path, snapshots_path,
297 arg1, arg2);
298 } else {
299 printf("skip\n");
301 } else if (arg1 == 0) {
303 * The commands following this check can't handle
304 * a period of 0, so call the feature disabled and
305 * ignore the directive.
307 printf("disabled\n");
308 } else if (strcmp(cmd, "prune") == 0) {
309 if (check_period(snapshots_path, cmd, arg1, &savet)) {
310 if (prune_warning) {
311 printf("run - WARNING snapshot "
312 "softlinks present "
313 "but snapshots disabled\n");
314 } else {
315 printf("run\n");
317 r = cleanup_prune(path, snapshots_path,
318 arg1, arg2, snapshots_disabled);
319 } else {
320 printf("skip\n");
322 } else if (strcmp(cmd, "reblock") == 0) {
323 if (check_period(snapshots_path, cmd, arg1, &savet)) {
324 printf("run");
325 fflush(stdout);
326 if (VerboseOpt)
327 printf("\n");
328 r = cleanup_reblock(path, snapshots_path,
329 arg1, arg2);
330 } else {
331 printf("skip\n");
333 } else if (strcmp(cmd, "recopy") == 0) {
334 if (check_period(snapshots_path, cmd, arg1, &savet)) {
335 printf("run");
336 fflush(stdout);
337 if (VerboseOpt)
338 printf("\n");
339 r = cleanup_recopy(path, snapshots_path,
340 arg1, arg2);
341 } else {
342 printf("skip\n");
344 } else {
345 printf("unknown directive\n");
346 r = 1;
348 if (r == 0)
349 save_period(snapshots_path, cmd, savet);
351 fclose(fp);
352 usleep(1000);
355 static
357 strtosecs(char *ptr)
359 int val;
361 val = strtol(ptr, &ptr, 0);
362 switch(*ptr) {
363 case 'd':
364 val *= 24;
365 /* fall through */
366 case 'h':
367 val *= 60;
368 /* fall through */
369 case 'm':
370 val *= 60;
371 /* fall through */
372 case 's':
373 break;
374 default:
375 errx(1, "illegal suffix converting %s\n", ptr);
376 break;
378 return(val);
381 static const char *
382 dividing_slash(const char *path)
384 int len = strlen(path);
385 if (len && path[len-1] == '/')
386 return("");
387 else
388 return("/");
392 * Check whether the desired period has elapsed since the last successful
393 * run. The run may take a while and cross a boundary so we remember the
394 * current time_t so we can save it later on.
396 * Periods in minutes, hours, or days are assumed to have been crossed
397 * if the local time crosses a minute, hour, or day boundary regardless
398 * of how close the last operation actually was.
400 static int
401 check_period(const char *snapshots_path, const char *cmd, int arg1,
402 time_t *savep)
404 char *check_path;
405 struct tm tp1;
406 struct tm tp2;
407 FILE *fp;
408 time_t baset, lastt;
409 char buf[256];
411 time(savep);
412 localtime_r(savep, &tp1);
415 * Retrieve the start time of the last successful operation.
417 asprintf(&check_path, "%s/.%s.period", snapshots_path, cmd);
418 fp = fopen(check_path, "r");
419 free(check_path);
420 if (fp == NULL)
421 return(1);
422 if (fgets(buf, sizeof(buf), fp) == NULL) {
423 fclose(fp);
424 return(1);
426 fclose(fp);
428 lastt = strtol(buf, NULL, 0);
429 localtime_r(&lastt, &tp2);
432 * Normalize the times. e.g. if asked to do something on a 1-day
433 * interval the operation will be performed as soon as the day
434 * turns over relative to the previous operation, even if the previous
435 * operation ran a few seconds ago just before midnight.
437 if (arg1 % 60 == 0) {
438 tp1.tm_sec = 0;
439 tp2.tm_sec = 0;
441 if (arg1 % (60 * 60) == 0) {
442 tp1.tm_min = 0;
443 tp2.tm_min = 0;
445 if (arg1 % (24 * 60 * 60) == 0) {
446 tp1.tm_hour = 0;
447 tp2.tm_hour = 0;
450 baset = mktime(&tp1);
451 lastt = mktime(&tp2);
453 #if 0
454 printf("%lld vs %lld\n", (long long)(baset - lastt), (long long)arg1);
455 #endif
457 if ((int)(baset - lastt) >= arg1)
458 return(1);
459 return(0);
463 * Store the start time of the last successful operation.
465 static void
466 save_period(const char *snapshots_path, const char *cmd,
467 time_t savet)
469 char *ocheck_path;
470 char *ncheck_path;
471 FILE *fp;
473 asprintf(&ocheck_path, "%s/.%s.period", snapshots_path, cmd);
474 asprintf(&ncheck_path, "%s/.%s.period.new", snapshots_path, cmd);
475 fp = fopen(ncheck_path, "w");
476 if (fp) {
477 fprintf(fp, "0x%08llx\n", (long long)savet);
478 if (fclose(fp) == 0)
479 rename(ncheck_path, ocheck_path);
480 remove(ncheck_path);
481 } else {
482 fprintf(stderr, "hammer: Unable to create period-file %s: %s\n",
483 ncheck_path, strerror(errno));
488 * Simply count the number of softlinks in the snapshots dir
490 static int
491 check_softlinks(const char *snapshots_path)
493 struct dirent *den;
494 struct stat st;
495 DIR *dir;
496 char *fpath;
497 int res = 0;
499 if ((dir = opendir(snapshots_path)) != NULL) {
500 while ((den = readdir(dir)) != NULL) {
501 if (den->d_name[0] == '.')
502 continue;
503 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
504 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode))
505 ++res;
506 free(fpath);
508 closedir(dir);
510 return(res);
514 * Clean up expired softlinks in the snapshots dir
516 static void
517 cleanup_softlinks(const char *path __unused, const char *snapshots_path,
518 int arg2, char *arg3)
520 struct dirent *den;
521 struct stat st;
522 DIR *dir;
523 char *fpath;
524 int anylink = 0;
526 if (arg3 != NULL && strstr(arg3, "any") != NULL)
527 anylink = 1;
529 if ((dir = opendir(snapshots_path)) != NULL) {
530 while ((den = readdir(dir)) != NULL) {
531 if (den->d_name[0] == '.')
532 continue;
533 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
534 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode) &&
535 (anylink || strncmp(den->d_name, "snap-", 5) == 0)
537 if (check_expired(den->d_name, arg2)) {
538 if (VerboseOpt) {
539 printf(" expire %s\n",
540 fpath);
542 remove(fpath);
545 free(fpath);
547 closedir(dir);
552 * Take a softlink path in the form snap-yyyymmdd-hhmm and the
553 * expiration in seconds (arg2) and return non-zero if the softlink
554 * has expired.
556 static int
557 check_expired(const char *fpath, int arg2)
559 struct tm tm;
560 time_t t;
561 int year;
562 int month;
563 int day = 0;
564 int hour = 0;
565 int minute = 0;
566 int r;
568 while (*fpath && *fpath != '-' && *fpath != '.')
569 ++fpath;
570 if (*fpath)
571 ++fpath;
573 r = sscanf(fpath, "%4d%2d%2d-%2d%2d",
574 &year, &month, &day, &hour, &minute);
576 if (r >= 3) {
577 bzero(&tm, sizeof(tm));
578 tm.tm_isdst = -1;
579 tm.tm_min = minute;
580 tm.tm_hour = hour;
581 tm.tm_mday = day;
582 tm.tm_mon = month - 1;
583 tm.tm_year = year - 1900;
584 t = mktime(&tm);
585 if (t == (time_t)-1)
586 return(0);
587 t = time(NULL) - t;
588 if ((int)t > arg2)
589 return(1);
591 return(0);
595 * Issue a snapshot.
597 static int
598 create_snapshot(const char *path __unused, const char *snapshots_path,
599 int arg1 __unused, int arg2 __unused)
601 int r;
603 runcmd(&r, "hammer snapshot %s %s", path, snapshots_path);
604 return(r);
607 static int
608 cleanup_prune(const char *path __unused, const char *snapshots_path,
609 int arg1 __unused, int arg2, int snapshots_disabled)
612 * If snapshots have been disabled run prune-everything instead
613 * of prune.
615 if (snapshots_disabled && arg2) {
616 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune-everything %s",
617 snapshots_path, arg2, path);
618 } else if (snapshots_disabled) {
619 runcmd(NULL, "hammer prune-everything %s", path);
620 } else if (arg2) {
621 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune %s",
622 snapshots_path, arg2, snapshots_path);
623 } else {
624 runcmd(NULL, "hammer prune %s", snapshots_path);
626 return(0);
629 static int
630 cleanup_reblock(const char *path, const char *snapshots_path,
631 int arg1 __unused, int arg2)
633 if (VerboseOpt == 0) {
634 printf(".");
635 fflush(stdout);
639 * When reblocking the B-Tree always reblock everything in normal
640 * mode.
642 runcmd(NULL,
643 "hammer -c %s/.reblock-1.cycle -t %d reblock-btree %s",
644 snapshots_path, arg2, path);
645 if (VerboseOpt == 0) {
646 printf(".");
647 fflush(stdout);
651 * When reblocking the inodes always reblock everything in normal
652 * mode.
654 runcmd(NULL,
655 "hammer -c %s/.reblock-2.cycle -t %d reblock-inodes %s",
656 snapshots_path, arg2, path);
657 if (VerboseOpt == 0) {
658 printf(".");
659 fflush(stdout);
663 * When reblocking the directories always reblock everything in normal
664 * mode.
666 runcmd(NULL,
667 "hammer -c %s/.reblock-4.cycle -t %d reblock-dirs %s",
668 snapshots_path, arg2, path);
669 if (VerboseOpt == 0) {
670 printf(".");
671 fflush(stdout);
675 * Do not reblock all the data in normal mode.
677 runcmd(NULL,
678 "hammer -c %s/.reblock-3.cycle -t %d reblock-data %s 95",
679 snapshots_path, arg2, path);
680 if (VerboseOpt == 0)
681 printf("\n");
682 return(0);
685 static int
686 cleanup_recopy(const char *path, const char *snapshots_path,
687 int arg1 __unused, int arg2)
689 if (VerboseOpt == 0) {
690 printf(".");
691 fflush(stdout);
693 runcmd(NULL,
694 "hammer -c %s/.recopy-1.cycle -t %d reblock-btree %s",
695 snapshots_path, arg2, path);
696 if (VerboseOpt == 0) {
697 printf(".");
698 fflush(stdout);
700 runcmd(NULL,
701 "hammer -c %s/.recopy-2.cycle -t %d reblock-inodes %s",
702 snapshots_path, arg2, path);
703 if (VerboseOpt == 0) {
704 printf(".");
705 fflush(stdout);
707 runcmd(NULL,
708 "hammer -c %s/.recopy-4.cycle -t %d reblock-dirs %s",
709 snapshots_path, arg2, path);
710 if (VerboseOpt == 0) {
711 printf(".");
712 fflush(stdout);
714 runcmd(NULL,
715 "hammer -c %s/.recopy-3.cycle -t %d reblock-data %s",
716 snapshots_path, arg2, path);
717 if (VerboseOpt == 0)
718 printf("\n");
719 return(0);
722 static
723 void
724 runcmd(int *resp, const char *ctl, ...)
726 va_list va;
727 char *cmd;
728 char *arg;
729 char **av;
730 int n;
731 int nmax;
732 int res;
733 pid_t pid;
736 * Generate the command
738 va_start(va, ctl);
739 vasprintf(&cmd, ctl, va);
740 va_end(va);
741 if (VerboseOpt)
742 printf(" %s\n", cmd);
745 * Break us down into arguments. We do not just use system() here
746 * because it blocks SIGINT and friends.
748 n = 0;
749 nmax = 16;
750 av = malloc(sizeof(char *) * nmax);
752 for (arg = strtok(cmd, WS); arg; arg = strtok(NULL, WS)) {
753 if (n == nmax - 1) {
754 nmax += 16;
755 av = realloc(av, sizeof(char *) * nmax);
757 av[n++] = arg;
759 av[n++] = NULL;
762 * Run the command.
764 RunningIoctl = 1;
765 if ((pid = fork()) == 0) {
766 if (VerboseOpt < 2) {
767 int fd = open("/dev/null", O_RDWR);
768 dup2(fd, 1);
769 close(fd);
771 execvp(av[0], av);
772 _exit(127);
773 } else if (pid < 0) {
774 res = 127;
775 } else {
776 int status;
778 while (waitpid(pid, &status, 0) != pid)
780 res = WEXITSTATUS(status);
782 RunningIoctl = 0;
783 if (DidInterrupt)
784 _exit(1);
786 free(cmd);
787 free(av);
788 if (resp)
789 *resp = res;