From e2c222ae1d89dcc05bc11cd8300376a2cdfeb9d7 Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Mon, 4 Feb 2008 08:34:22 +0000 Subject: [PATCH] HAMMER utilities: Add the 'prune' and 'history' commands. --- sbin/hammer/Makefile | 4 +- sbin/hammer/cmd_history.c | 181 ++++++++++++++++++++++++++++ sbin/hammer/cmd_prune.c | 296 ++++++++++++++++++++++++++++++++++++++++++++++ sbin/hammer/hammer.8 | 36 +++++- sbin/hammer/hammer.c | 19 ++- sbin/hammer/hammer.h | 5 +- 6 files changed, 534 insertions(+), 7 deletions(-) create mode 100644 sbin/hammer/cmd_history.c create mode 100644 sbin/hammer/cmd_prune.c diff --git a/sbin/hammer/Makefile b/sbin/hammer/Makefile index 02c6e4f5d7..506867bca8 100644 --- a/sbin/hammer/Makefile +++ b/sbin/hammer/Makefile @@ -1,9 +1,9 @@ # -# $DragonFly: src/sbin/hammer/Makefile,v 1.3 2008/01/17 04:59:48 dillon Exp $ +# $DragonFly: src/sbin/hammer/Makefile,v 1.4 2008/02/04 08:34:22 dillon Exp $ PROG= hammer SRCS= hammer.c buffer_alist.c ondisk.c cache.c super_alist.c \ - misc.c cmd_show.c + misc.c cmd_show.c cmd_prune.c cmd_history.c MAN= hammer.8 CFLAGS+= -I${.CURDIR}/../../sys -DALIST_NO_DEBUG diff --git a/sbin/hammer/cmd_history.c b/sbin/hammer/cmd_history.c new file mode 100644 index 0000000000..264e560104 --- /dev/null +++ b/sbin/hammer/cmd_history.c @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2008 The DragonFly Project. All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Matthew Dillon + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of The DragonFly Project nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific, prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $DragonFly: src/sbin/hammer/cmd_history.c,v 1.1 2008/02/04 08:34:22 dillon Exp $ + */ + +#include "hammer.h" + +static void hammer_do_history(const char *path, off_t off, int len); +static void dumpat(const char *path, off_t off, int len); + +/* + * history ... + */ +void +hammer_cmd_history(const char *offset_str, char **av, int ac) +{ + off_t off; + int i; + int len; + char *rptr; + + len = 32; + if (*offset_str == '@') { + off = strtoll(offset_str + 1, &rptr, 0); + if (*rptr == ',') + len = strtol(rptr + 1, NULL, 0); + } else { + off = -1; + } + + for (i = 0; i < ac; ++i) + hammer_do_history(av[i], off, len); +} + +static void +hammer_do_history(const char *path, off_t off, int len) +{ + struct hammer_ioc_history hist; + const char *status; + int fd; + int i; + + printf("%s\t", path); + fd = open(path, O_RDONLY); + if (fd < 0) { + printf("%s\n", strerror(errno)); + return; + } + bzero(&hist, sizeof(hist)); + hist.beg_tid = HAMMER_MIN_TID; + hist.end_tid = HAMMER_MAX_TID; + + if (off >= 0) { + hist.flags |= HAMMER_IOC_HISTORY_ATKEY; + hist.key = off; + hist.nxt_key = off + 1; + } + + + if (ioctl(fd, HAMMERIOC_GETHISTORY, &hist) < 0) { + printf("%s\n", strerror(errno)); + close(fd); + return; + } + status = ((hist.flags & HAMMER_IOC_HISTORY_UNSYNCED) ? + "dirty" : "clean"); + printf("%016llx %s {\n", hist.obj_id, status); + for (;;) { + for (i = 0; i < hist.count; ++i) { + struct stat st; + struct tm tp; + time_t t; + char timebuf1[64]; + char timebuf2[64]; + char *hist_path = NULL; + + t = (int64_t)hist.tid_ary[i] / 1000000000LL; + localtime_r(&t, &tp); + strftime(timebuf1, sizeof(timebuf1), + "%e-%b-%Y %H:%M:%S", &tp); + + asprintf(&hist_path, "%s@@0x%016llx", + path, hist.tid_ary[i]); + if (off < 0 && stat(hist_path, &st) == 0) { + localtime_r(&st.st_mtime, &tp); + strftime(timebuf2, sizeof(timebuf2), + "%e-%b-%Y %H:%M:%S %Z", &tp); + } else { + snprintf(timebuf2, sizeof(timebuf2), "?"); + } + if (off < 0) { + printf(" %016llx %s contents-to %s", + hist.tid_ary[i], + timebuf1, timebuf2); + } else { + printf(" %016llx %s", + hist.tid_ary[i], + timebuf1); + if (VerboseOpt) { + printf(" '"); + dumpat(hist_path, off, len); + printf("'"); + } + } + printf("\n"); + free(hist_path); + } + if (hist.flags & HAMMER_IOC_HISTORY_EOF) + break; + if (hist.flags & HAMMER_IOC_HISTORY_NEXT_KEY) + break; + if ((hist.flags & HAMMER_IOC_HISTORY_NEXT_TID) == 0) + break; + hist.beg_tid = hist.nxt_tid; + if (ioctl(fd, HAMMERIOC_GETHISTORY, &hist) < 0) { + printf(" error: %s\n", strerror(errno)); + break; + } + } + printf("}\n"); + close(fd); +} + +static void +dumpat(const char *path, off_t off, int len) +{ + char buf[1024]; + int fd; + int n; + int r; + + fd = open(path, O_RDONLY); + if (fd < 0) + return; + lseek(fd, off, 0); + while (len) { + n = (len > (int)sizeof(buf)) ? (int)sizeof(buf) : len; + r = read(fd, buf, n); + if (r <= 0) + break; + len -= r; + for (n = 0; n < r; ++n) { + if (isprint(buf[n])) + putc(buf[n], stdout); + else + putc('.', stdout); + } + } +} + diff --git a/sbin/hammer/cmd_prune.c b/sbin/hammer/cmd_prune.c new file mode 100644 index 0000000000..dbc004639e --- /dev/null +++ b/sbin/hammer/cmd_prune.c @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2008 The DragonFly Project. All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Matthew Dillon + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of The DragonFly Project nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific, prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $DragonFly: src/sbin/hammer/cmd_prune.c,v 1.1 2008/02/04 08:34:22 dillon Exp $ + */ + +#include "hammer.h" + +static void hammer_prune_load_file(hammer_tid_t now_tid, + struct hammer_ioc_prune *prune, + const char *filesystem, const char *filename); +static int hammer_prune_parse_line(hammer_tid_t now_tid, + struct hammer_ioc_prune *prune, + const char *filesystem, char **av, int ac); +static int parse_modulo_time(const char *str, u_int64_t *delta); +static char *tid_to_stamp_str(hammer_tid_t tid); +static void prune_usage(int code); + +/* + * prune from to every + * prune [using ] + */ +void +hammer_cmd_prune(char **av, int ac) +{ + struct hammer_ioc_prune prune; + const char *filesystem; + int fd; + hammer_tid_t now_tid = (hammer_tid_t)time(NULL) * 1000000000LL; + + bzero(&prune, sizeof(prune)); + prune.nelms = 0; + prune.beg_objid = HAMMER_MIN_OBJID; + prune.cur_objid = prune.beg_objid; + prune.end_objid = HAMMER_MAX_OBJID; + prune.cur_key = HAMMER_MIN_KEY; + + if (ac == 0) + prune_usage(1); + filesystem = av[0]; + if (ac == 1) { + hammer_prune_load_file(now_tid, &prune, filesystem, + "/etc/hammer.conf"); + } else if (strcmp(av[1], "using") == 0) { + if (ac == 2) + prune_usage(1); + hammer_prune_load_file(now_tid, &prune, filesystem, av[2]); + } else { + if (hammer_prune_parse_line(now_tid, &prune, filesystem, + av, ac) < 0) { + prune_usage(1); + } + } + fd = open(filesystem, O_RDONLY); + if (fd < 0) + err(1, "Unable to open %s", filesystem); + if (ioctl(fd, HAMMERIOC_PRUNE, &prune) < 0) + printf("Prune %s failed: %s\n", filesystem, strerror(errno)); + else + printf("Prune %s succeeded\n", filesystem); + close(fd); + printf("Pruned %lld records (%lld directory entries) and %lld bytes\n", + prune.stat_rawrecords, + prune.stat_dirrecords, + prune.stat_bytes + ); +} + +static void +hammer_prune_load_file(hammer_tid_t now_tid, struct hammer_ioc_prune *prune, + const char *filesystem, const char *filename) +{ + char buf[256]; + FILE *fp; + char *av[16]; + int ac; + int lineno; + + if ((fp = fopen(filename, "r")) == NULL) + err(1, "Unable to read %s", filename); + lineno = 0; + while (fgets(buf, sizeof(buf), fp) != NULL) { + ++lineno; + if (strncmp(buf, "prune", 5) != 0) + continue; + ac = 0; + av[ac] = strtok(buf, " \t\r\n"); + while (av[ac] != NULL) { + ++ac; + if (ac == 16) { + fclose(fp); + errx(1, "Malformed prune directive in %s " + "line %d\n", filename, lineno); + } + av[ac] = strtok(NULL, " \t\r\n"); + } + if (ac == 0) + continue; + if (strcmp(av[0], "prune") != 0) + continue; + if (hammer_prune_parse_line(now_tid, prune, filesystem, + av + 1, ac - 1) < 0) { + errx(1, "Malformed prune directive in %s line %d\n", + filename, lineno); + } + } + fclose(fp); +} + +static __inline +const char * +plural(int notplural) +{ + return(notplural ? "" : "s"); +} + +/* + * Parse the following parameters: + * + * from to every + */ +static int +hammer_prune_parse_line(hammer_tid_t now_tid, struct hammer_ioc_prune *prune, + const char *filesystem, char **av, int ac) +{ + struct hammer_ioc_prune_elm *elm; + u_int64_t from_time; + u_int64_t to_time; + u_int64_t every_time; + char *from_stamp_str; + char *to_stamp_str; + + if (ac != 7) + return(-1); + if (strcmp(av[0], filesystem) != 0) + return(0); + if (strcmp(av[1], "from") != 0) + return(-1); + if (strcmp(av[3], "to") != 0) + return(-1); + if (strcmp(av[5], "every") != 0) + return(-1); + if (parse_modulo_time(av[2], &from_time) < 0) + return(-1); + if (parse_modulo_time(av[4], &to_time) < 0) + return(-1); + if (parse_modulo_time(av[6], &every_time) < 0) + return(-1); + if (from_time > to_time) + return(-1); + if (from_time == 0 || to_time == 0) { + fprintf(stderr, "Bad from or to time specification.\n"); + return(-1); + } + if (to_time % from_time != 0) { + fprintf(stderr, "Bad TO time specification.\n" + "It must be an integral multiple of FROM time\n"); + return(-1); + } + if (every_time == 0 || + from_time % every_time != 0 || + to_time % every_time != 0) { + fprintf(stderr, "Bad 'every ' specification.\n" + "It must be an integral subdivision of FROM and TO\n"); + return(-1); + } + if (prune->nelms == HAMMER_MAX_PRUNE_ELMS) { + fprintf(stderr, "Too many prune specifications in file! " + "Max is %d\n", HAMMER_MAX_PRUNE_ELMS); + return(-1); + } + + /* + * Example: from 1m to 60m every 5m + */ + elm = &prune->elms[prune->nelms++]; + elm->beg_tid = now_tid - now_tid % to_time; + if (now_tid - elm->beg_tid < to_time) + elm->beg_tid -= to_time; + + elm->end_tid = now_tid - now_tid % from_time; + if (now_tid - elm->end_tid < from_time) + elm->end_tid -= from_time; + + elm->mod_tid = every_time; + assert(elm->beg_tid < elm->end_tid); + + /* + * Convert back to local time for pretty printing + */ + from_stamp_str = tid_to_stamp_str(elm->beg_tid); + to_stamp_str = tid_to_stamp_str(elm->end_tid); + printf("Prune %s to %s every ", from_stamp_str, to_stamp_str); + + every_time /= 1000000000; + if (every_time < 60) + printf("%lld second%s\n", every_time, plural(every_time == 1)); + every_time /= 60; + if (every_time && every_time < 60) + printf("%lld minute%s\n", every_time, plural(every_time == 1)); + every_time /= 60; + if (every_time && every_time < 24) + printf("%lld hour%s\n", every_time, plural(every_time == 1)); + every_time /= 24; + if (every_time) + printf("%lld day%s\n", every_time, plural(every_time == 1)); + + free(from_stamp_str); + free(to_stamp_str); + return(0); +} + +static +int +parse_modulo_time(const char *str, u_int64_t *delta) +{ + char *term; + + *delta = strtoull(str, &term, 10); + + switch(*term) { + case 'y': + *delta *= 12; + /* fall through */ + case 'M': + *delta *= 30; + /* fall through */ + case 'd': + *delta *= 24; + /* fall through */ + case 'h': + *delta *= 60; + /* fall through */ + case 'm': + *delta *= 60; + /* fall through */ + case 's': + break; + default: + return(-1); + } + *delta *= 1000000000LL; /* TID's are in nanoseconds */ + return(0); +} + +static char * +tid_to_stamp_str(hammer_tid_t tid) +{ + struct tm *tp; + char *buf = malloc(256); + time_t t; + + t = (time_t)(tid / 1000000000); + tp = localtime(&t); + strftime(buf, 256, "%e-%b-%Y %H:%M:%S %Z", tp); + return(buf); +} + +static void +prune_usage(int code) +{ + fprintf(stderr, "Bad prune directive, specify one of:\n" + "prune filesystem [using filename]\n" + "prune filesystem from to every \n"); + exit(code); +} diff --git a/sbin/hammer/hammer.8 b/sbin/hammer/hammer.8 index 5ea88b6e20..146718a4f4 100644 --- a/sbin/hammer/hammer.8 +++ b/sbin/hammer/hammer.8 @@ -30,7 +30,7 @@ .\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $DragonFly: src/sbin/hammer/hammer.8,v 1.4 2008/01/25 05:53:41 dillon Exp $ +.\" $DragonFly: src/sbin/hammer/hammer.8,v 1.5 2008/02/04 08:34:22 dillon Exp $ .Dd December 31, 2007 .Dt HAMMER 8 .Os @@ -74,6 +74,8 @@ specifies an exact as-of timestamp in local (not UTC) time. Set the TZ environment variable prior to running .Nm if you wish to specify the time by some other means. +.It Ar history Ar path +Show the modification history for a HAMMER file's inode and data. .It Ar show Op vol_no[:clu_no] Dump the B-Tree starting at the specified volume and cluster, or at the root volume if not specified. @@ -87,6 +89,38 @@ output as 0. .It Ar namekey32 Ar filename Generate the top 32 bits of a HAMMER 64 bit directory hash for the specified file name. +.It Ar prune Ar filesystem Ar from Ar #{smhdMy} Ar to Ar #{smhdMy} Ar every Ar #{smhdMy} +.It Ar prune Ar filesystem Op Ar using Ar filename +Prune the filesystem, removing deleted records to free up physical disk +space. Specify a time range between the nearest modulo 0 boundary +and prune the tree to the specified granularity within that range. +.Pp +The filesystem specification should be the root of any mounted HAMMER +filesystem. This command uses a filesystem ioctl to issue the pruning +operation. If you specify just the filesystem with no other parameters +all prune directives matching that filesystem in the /etc/hammer.conf file +will be used. If you specify a +.Ar using +file then those directives contained in the file matching +.Ar filesystem +will be used. Multiple directives may be specified when extracting from +a file. The directives must be in the same format: "prune ....", in +ascending time order (per filesystem). Matching prune elements must not +have overlapping time specifications. +.Pp +Both the "from" and the "to" value must be an integral multiple +of the "every" value, and the "to" value must be an integral multiple +of the "from" value. When you have multiple pruning rules you must +take care to ensure that the range being pruned does not overlap ranges +pruned later on, when the retained data is older. If they do the retained +data can wind up being destroyed. For example, if you prune your data +on a 30 minute granularity for the last 24 hours any later pruning must +use a granularity that is a multiple of 30 minutes. If you prune your +data on a 30 minute boundary, then a 1 day boundary in a later pruning (on +older data), then a pruning beyond that would have to be a multiple of +1 day. And so forth. +.Pp +Example: "hammer prune /mnt from 1h to 1d every 30m" .El .Sh EXAMPLES .Sh DIAGNOSTICS diff --git a/sbin/hammer/hammer.c b/sbin/hammer/hammer.c index 676f5236ab..53c1a5636c 100644 --- a/sbin/hammer/hammer.c +++ b/sbin/hammer/hammer.c @@ -31,7 +31,7 @@ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $DragonFly: src/sbin/hammer/hammer.c,v 1.6 2008/01/25 05:53:41 dillon Exp $ + * $DragonFly: src/sbin/hammer/hammer.c,v 1.7 2008/02/04 08:34:22 dillon Exp $ */ #include "hammer.h" @@ -111,6 +111,15 @@ main(int ac, char **av) printf("0x%08x\n", key); exit(0); } + if (strcmp(av[0], "prune") == 0) { + hammer_cmd_prune(av + 1, ac - 1); + exit(0); + } + + if (strncmp(av[0], "history", 7) == 0) { + hammer_cmd_history(av[0] + 7, av + 1, ac - 1); + exit(0); + } uuid_name_lookup(&Hammer_FSType, "DragonFly HAMMER", &status); if (status != uuid_s_ok) { @@ -230,10 +239,14 @@ usage(int exit_code) "hammer -h\n" "hammer now\n" "hammer stamp