From ce5e5ac4c90e9b2b818374657408b15978bc06db Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Mon, 7 Mar 2005 02:38:28 +0000 Subject: [PATCH] First cut of the jscan utility. This will become the core utility for scanning journaling files (e.g. as created by mountctl). The utility is currently able to dump a journaling file in human readable format forwards or backwards. It will eventually be capable of tracking and mirroring, undo, security audits, partial restores, sanity checks, and other operations. Most of the scanning infrastructure is in as of this commit, but the code currently tries to cache the entire transaction into memory which will fail for large (e.g. multi gigabyte) transactions. The API's are abstracted with the intent of being able to page out or do on-the-fly mmaping of the underlying data in the future rather then copying it into memory. --- sbin/Makefile | 3 +- sbin/jscan/Makefile | 8 + sbin/jscan/dump_debug.c | 337 +++++++++++++++++++++++++++++++++++++ sbin/jscan/jfile.c | 144 ++++++++++++++++ sbin/jscan/jscan.8 | 72 ++++++++ sbin/jscan/jscan.c | 97 +++++++++++ sbin/jscan/jscan.h | 107 ++++++++++++ sbin/jscan/jstream.c | 363 ++++++++++++++++++++++++++++++++++++++++ sbin/jscan/subs.c | 214 +++++++++++++++++++++++ 9 files changed, 1344 insertions(+), 1 deletion(-) create mode 100644 sbin/jscan/Makefile create mode 100644 sbin/jscan/dump_debug.c create mode 100644 sbin/jscan/jfile.c create mode 100644 sbin/jscan/jscan.8 create mode 100644 sbin/jscan/jscan.c create mode 100644 sbin/jscan/jscan.h create mode 100644 sbin/jscan/jstream.c create mode 100644 sbin/jscan/subs.c diff --git a/sbin/Makefile b/sbin/Makefile index 4ee4199e3f..836aaf4d78 100644 --- a/sbin/Makefile +++ b/sbin/Makefile @@ -1,6 +1,6 @@ # @(#)Makefile 8.5 (Berkeley) 3/31/94 # $FreeBSD: src/sbin/Makefile,v 1.77.2.9 2002/08/08 09:03:46 ru Exp $ -# $DragonFly: src/sbin/Makefile,v 1.7 2005/01/09 03:06:48 dillon Exp $ +# $DragonFly: src/sbin/Makefile,v 1.8 2005/03/07 02:38:27 dillon Exp $ # # XXX MISSING: icheck ncheck @@ -28,6 +28,7 @@ SUBDIR= adjkerntz \ init \ ip6fw \ ipfw \ + jscan \ kldconfig \ kldload \ kldstat \ diff --git a/sbin/jscan/Makefile b/sbin/jscan/Makefile new file mode 100644 index 0000000000..c2a3d0a0ed --- /dev/null +++ b/sbin/jscan/Makefile @@ -0,0 +1,8 @@ +# +# $DragonFly: src/sbin/jscan/Makefile,v 1.1 2005/03/07 02:38:28 dillon Exp $ + +PROG= jscan +SRCS= jscan.c jfile.c jstream.c subs.c dump_debug.c +MAN= jscan.8 + +.include diff --git a/sbin/jscan/dump_debug.c b/sbin/jscan/dump_debug.c new file mode 100644 index 0000000000..3f84458dd4 --- /dev/null +++ b/sbin/jscan/dump_debug.c @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2003,2004 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/jscan/dump_debug.c,v 1.1 2005/03/07 02:38:28 dillon Exp $ + */ + +#include "jscan.h" + +static void dump_debug_stream(struct jstream *js); +static int dump_subrecord(struct jstream *js, off_t off, int recsize, int level); +static int dump_payload(int16_t rectype, struct jstream *js, off_t off, + int recsize, int level); + +void +dump_debug(struct jfile *jf) +{ + struct jstream *js; + + while ((js = jscan_stream(jf)) != NULL) { + dump_debug_stream(js); + jscan_dispose(js); + } +} + +static void +dump_debug_stream(struct jstream *js) +{ + struct journal_rawrecbeg head; + int16_t sid; + + jsread(js, 0, &head, sizeof(head)); + + sid = head.streamid & JREC_STREAMID_MASK; + printf("STREAM %04x {\n", (int)(u_int16_t)head.streamid); + if (sid >= JREC_STREAMID_JMIN && sid < JREC_STREAMID_JMAX) { + dump_subrecord(js, sizeof(head), + head.recsize - sizeof(struct journal_rawrecbeg) - + sizeof(struct journal_rawrecend), + 1); + } else { + switch(head.streamid & JREC_STREAMID_MASK) { + case JREC_STREAMID_SYNCPT & JREC_STREAMID_MASK: + printf(" SYNCPT\n"); + break; + case JREC_STREAMID_PAD & JREC_STREAMID_MASK: + printf(" PAD\n"); + break; + case JREC_STREAMID_DISCONT & JREC_STREAMID_MASK: + printf(" DISCONT\n"); + break; + case JREC_STREAMID_ANNOTATE & JREC_STREAMID_MASK: + printf(" ANNOTATION\n"); + break; + default: + printf(" UNKNOWN\n"); + break; + } + } + printf("}\n"); +} + +static int +dump_subrecord(struct jstream *js, off_t off, int recsize, int level) +{ + struct journal_subrecord sub; + int payload; + int subsize; + int error; + int i; + + error = 0; + while (recsize > 0) { + if ((error = jsread(js, off, &sub, sizeof(sub))) != 0) + break; + printf("%*.*s", level * 4, level * 4, ""); + printf("@%lld ", off); + printf("RECORD %s [%04x/%d]", type_to_name(sub.rectype), + (int)(u_int16_t)sub.rectype, sub.recsize); + payload = sub.recsize - sizeof(sub); + subsize = (sub.recsize + 7) & ~7; + if (sub.rectype & JMASK_NESTED) { + printf(" {\n"); + if (payload) + error = dump_subrecord(js, off + sizeof(sub), payload, level + 1); + printf("%*.*s}\n", level * 4, level * 4, ""); + } else if (sub.rectype & JMASK_SUBRECORD) { + printf(" DATA (%d)", payload); + error = dump_payload(sub.rectype, js, off + sizeof(sub), payload, level); + printf("\n"); + if (error) + break; + } else { + printf("[unknown content]\n", sub.recsize); + } + if (subsize == 0) + subsize = sizeof(sub); + recsize -= subsize; + off += subsize; + } + return(error); +} + +static int +dump_payload(int16_t rectype, struct jstream *js, off_t off, + int recsize, int level) +{ + enum { DT_NONE, DT_STRING, DT_DEC, DT_HEX, DT_OCT, + DT_DATA, DT_TIMESTAMP } dt = DT_DATA; + const char *buf; + int didalloc; + int error; + int i; + int j; + + error = jsreadp(js, off, (const void **)&buf, recsize); + if (error) + return (error); + + switch(rectype & ~JMASK_LAST) { + case JLEAF_PAD: + case JLEAF_ABORT: + break; + case JLEAF_FILEDATA: + break; + case JLEAF_PATH1: + case JLEAF_PATH2: + case JLEAF_PATH3: + case JLEAF_PATH4: + dt = DT_STRING; + break; + case JLEAF_UID: + case JLEAF_GID: + dt = DT_DEC; + break; + case JLEAF_MODES: + dt = DT_OCT; + break; + case JLEAF_FFLAGS: + dt = DT_HEX; + break; + case JLEAF_PID: + case JLEAF_PPID: + dt = DT_DEC; + break; + case JLEAF_COMM: + dt = DT_STRING; + break; + case JLEAF_ATTRNAME: + dt = DT_STRING; + break; + case JLEAF_PATH_REF: + dt = DT_STRING; + break; + case JLEAF_RESERVED_0F: + break; + case JLEAF_SYMLINKDATA: + dt = DT_STRING; + break; + case JLEAF_SEEKPOS: + dt = DT_HEX; + break; + case JLEAF_INUM: + dt = DT_HEX; + break; + case JLEAF_NLINK: + dt = DT_DEC; + break; + case JLEAF_FSID: + dt = DT_HEX; + break; + case JLEAF_SIZE: + dt = DT_HEX; + break; + case JLEAF_ATIME: + case JLEAF_MTIME: + case JLEAF_CTIME: + dt = DT_TIMESTAMP; + break; + case JLEAF_GEN: + dt = DT_HEX; + break; + case JLEAF_FLAGS: + dt = DT_HEX; + break; + case JLEAF_UDEV: + dt = DT_HEX; + break; + case JLEAF_FILEREV: + dt = DT_HEX; + break; + default: + break; + } + switch(dt) { + case DT_NONE: + break; + case DT_STRING: + printf(" \""); + for (i = 0; i < recsize; ++i) { + char c = buf[i]; + if ((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + c == '_' || c == '@' || c == ',' || c == '-' || + c == '/' || c == '+' + ) { + putc(c, stdout); + } else if (c == 0) { + printf("\\0"); + } else if (c == '\n') { + printf("\\n"); + } else { + printf("\\x%02x", (int)(unsigned char)c); + } + } + printf("\""); + break; + case DT_DEC: + switch(recsize) { + case 1: + printf(" %d", (int)*(u_int8_t *)buf); + break; + case 2: + printf(" %d", (int)*(u_int16_t *)buf); + break; + case 4: + printf(" %d", (int)*(u_int32_t *)buf); + break; + case 8: + printf(" %d", (int64_t)*(u_int64_t *)buf); + break; + default: + printf(" ?"); + break; + } + break; + case DT_HEX: + switch(recsize) { + case 1: + printf(" 0x%02x", (int)*(u_int8_t *)buf); + break; + case 2: + printf(" 0x%04x", (int)*(u_int16_t *)buf); + break; + case 4: + printf(" 0x%08x", (int)*(u_int32_t *)buf); + break; + case 8: + printf(" 0x%016llx", (int64_t)*(u_int64_t *)buf); + break; + default: + printf(" ?"); + break; + } + break; + case DT_OCT: + switch(recsize) { + case 1: + printf(" %03o", (int)*(u_int8_t *)buf); + break; + case 2: + printf(" %06o", (int)*(u_int16_t *)buf); + break; + case 4: + printf(" %011o", (int)*(u_int32_t *)buf); + break; + case 8: + printf(" %022llo", (int64_t)*(u_int64_t *)buf); + break; + default: + printf(" ?"); + break; + } + break; + case DT_TIMESTAMP: + { + struct tm *tp; + time_t t = ((struct timespec *)buf)->tv_sec; + char outbuf[64]; + + tp = localtime(&t); + strftime(outbuf, sizeof(outbuf), " <%d-%b-%Y %H:%M:%S>", tp); + printf("%s", outbuf); + } + break; + case DT_DATA: + default: + if (recsize < 16) { + for (i = 0; i < recsize; ++i) { + printf(" %02x", (int)(unsigned char)buf[i]); + } + } else { + printf(" {\n"); + for (i = 0; i < recsize; i += 16) { + printf("%*.*s", level * 4 + 4, level * 4 + 4, ""); + for (j = i; j < i + 16 && j < recsize; ++j) { + printf(" %02x", (int)(unsigned char)buf[i]); + } + printf("\n"); + } + printf(" }"); + } + break; + } + return (0); +} + diff --git a/sbin/jscan/jfile.c b/sbin/jscan/jfile.c new file mode 100644 index 0000000000..aed2a4bcac --- /dev/null +++ b/sbin/jscan/jfile.c @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2004,2005 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/jscan/jfile.c,v 1.1 2005/03/07 02:38:28 dillon Exp $ + */ + +#include "jscan.h" + +/* + * Open a journal for directional scanning + */ +struct jfile * +jopen_stream(const char *path, enum jdirection jdir) +{ + struct jfile *jf; + + jf = malloc(sizeof(struct jfile)); + bzero(jf, sizeof(struct jfile)); + if ((jf->jf_fp = fopen(path, "r")) == NULL) { + free(jf); + return (NULL); + } + jf->jf_direction = jdir; + jf->jf_setpt = -1; + if (jdir == JF_BACKWARDS) { + fseeko(jf->jf_fp, 0L, SEEK_END); + jf->jf_pos = ftello(jf->jf_fp); + } + return(jf); +} + +/* + * Close a previously opened journal, clean up any side allocations. + */ +void +jclose_stream(struct jfile *jf) +{ + struct jdata *jd; + + fclose(jf->jf_fp); + jf->jf_fp = NULL; + while ((jd = jf->jf_saved) != NULL) { + jf->jf_saved = jd->jd_next; + free(jd); + } + free(jf); +} + +/* + * Align us to the next 16 byte boundary. If scanning forwards we align + * forwards if not already aligned. If scanning backwards we align + * backwards if not already aligned. + */ +void +jalign(struct jfile *jf) +{ + if (jf->jf_direction == JF_FORWARDS) { + jf->jf_pos = (jf->jf_pos + 15) & ~15; + fseeko(jf->jf_fp, jf->jf_pos, SEEK_SET); + } else { + jf->jf_pos = jf->jf_pos & ~15; + } +} + +/* + * Read data from a journal forwards or backwards. Note that the file + * pointer's actual seek position does not match jf_pos in the reverse + * scan case. Callers should never access jf_fp directly. + */ +int +jread(struct jfile *jf, void *buf, int bytes) +{ + if (jf->jf_direction == JF_FORWARDS) { + if (fread(buf, bytes, 1, jf->jf_fp) == 1) { + jf->jf_pos += bytes; + return (0); + } else { + fseeko(jf->jf_fp, jf->jf_pos, SEEK_SET); + return (errno); + } + } else { + if (bytes > jf->jf_pos) + return (ENOENT); + jf->jf_pos -= bytes; + fseeko(jf->jf_fp, jf->jf_pos, SEEK_SET); + if (fread(buf, bytes, 1, jf->jf_fp) == 1) { + return (0); + } else { + jf->jf_pos += bytes; + return (errno); + } + } +} + +void +jset(struct jfile *jf) +{ + jf->jf_setpt = jf->jf_pos; +} + +void +jreturn(struct jfile *jf) +{ + jf->jf_pos = jf->jf_setpt; + jf->jf_setpt = -1; + fseeko(jf->jf_fp, jf->jf_pos, SEEK_SET); +} + +void +jflush(struct jfile *jf) +{ + jf->jf_setpt = -1; +} + diff --git a/sbin/jscan/jscan.8 b/sbin/jscan/jscan.8 new file mode 100644 index 0000000000..52232eba35 --- /dev/null +++ b/sbin/jscan/jscan.8 @@ -0,0 +1,72 @@ +.\" Copyright (c) 2004,2005 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/jscan/jscan.8,v 1.1 2005/03/07 02:38:28 dillon Exp $ +.\" +.Dd March 6, 2005 +.Dt JSCAN 8 +.Os +.Sh NAME +.Nm jscan +.Nd journal file processing program +.Sh SYNOPSIS +.Nm +.Fl rd +.Op Ar journal_files +.Pp +.Sh DESCRIPTION +The +.Nm +utility scans journal files for the purposes of debugging dumps, restoration, +undo, mirroring, and other journaling features. +.Bl -tag -width indent +.It Fl d +dumps the contents of the journaling file in a human readable format. +.It Fl r +will cause the journal be scanned backwards. Transactions will be dumped in +reverse order. However, note that due to parallelism the order will not be +the exact reverse of a forward scan, since large transactions are not dumped +until all stream segments have been collected for them. +.El +.Pp +.Sh FILES +.Sh SEE ALSO +.Xr mountctl 8 , +.Sh BUGS +.Sh CAVEATS +This utility is currently under construction and not all features have been +implemented yet. In fact, most have not. +.Sh HISTORY +The +.Nm +utility first appeared in DragonFly . diff --git a/sbin/jscan/jscan.c b/sbin/jscan/jscan.c new file mode 100644 index 0000000000..ae4eb9b2f4 --- /dev/null +++ b/sbin/jscan/jscan.c @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2003,2004 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/jscan/jscan.c,v 1.1 2005/03/07 02:38:28 dillon Exp $ + */ + +#include "jscan.h" + +enum jmode { JS_NONE, JS_DEBUG }; + +static void usage(const char *av0); + +int +main(int ac, char **av) +{ + int fd; + int ch; + int i; + enum jdirection direction = JF_FORWARDS; + enum jmode jmode = JS_NONE; + struct jfile *jf; + + while ((ch = getopt(ac, av, "dr")) != -1) { + switch(ch) { + case 'd': + if (jmode == JS_NONE) + jmode = JS_DEBUG; + break; + case 'r': + direction = JF_BACKWARDS; + break; + default: + fprintf(stderr, "unknown option: -%c\n", optopt); + usage(av[0]); + } + } + if (jmode == JS_NONE) + usage(av[0]); + + /* + * Using specified input streams. If no files are specified, stdin + * is used. + */ + if (ac == optind) { + usage(av[0]); + } else { + for (i = optind; i < ac; ++i) { + if ((jf = jopen_stream(av[i], direction)) != NULL) { + switch(jmode) { + case JS_DEBUG: + dump_debug(jf); + break; + } + jclose_stream(jf); + } + } + } + return(0); +} + +static void +usage(const char *av0) +{ + fprintf(stderr, "%s [-d] [journal files]\n", av0); + exit(1); +} + diff --git a/sbin/jscan/jscan.h b/sbin/jscan/jscan.h new file mode 100644 index 0000000000..67295d7ead --- /dev/null +++ b/sbin/jscan/jscan.h @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2004,2005 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/jscan/jscan.h,v 1.1 2005/03/07 02:38:28 dillon Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum jdirection { JF_FORWARDS, JF_BACKWARDS}; + +struct jfile { + off_t jf_pos; /* current seek position */ + off_t jf_setpt; /* saved seek position */ + struct jdata *jf_saved; /* saved data */ + FILE *jf_fp; + enum jdirection jf_direction; + int jf_error; +}; + +struct jdata { + struct jdata *jd_next; + int jd_size; + off_t jd_pos; + char jd_data[4]; /* must be last field */ +}; + +struct jstream { + struct jstream *js_next; /* linked list / same transaction */ + int js_size; /* amount of data, in bytes */ + off_t js_off; + char *js_alloc_buf; + int js_alloc_size; + struct jstream *js_cache; + off_t js_cache_off; + char js_data[4]; /* variable length */ +}; + +struct jhash { + struct jhash *jh_hash; + struct jstream *jh_first; + struct jstream *jh_last; + int16_t jh_transid; +}; + +#define JHASH_SIZE 1024 +#define JHASH_MASK (JHASH_SIZE - 1) + +const char *type_to_name(int16_t rectype); + +struct jstream *jscan_stream(struct jfile *jf); +void jscan_dispose(struct jstream *js); +struct jfile *jopen_stream(const char *path, enum jdirection jdir); +void jclose_stream(struct jfile *jf); +void jalign(struct jfile *jf); +int jread(struct jfile *jf, void *buf, int bytes); +void jset(struct jfile *jf); +void jreturn(struct jfile *jf); +void jflush(struct jfile *jf); +void jf_warn(struct jfile *jf, const char *ctl, ...); + +void dump_debug(struct jfile *jf); + +int jsreadany(struct jstream *js, off_t off, const void **bufp); +int jsreadp(struct jstream *js, off_t off, const void **bufp, int bytes); +int jsread(struct jstream *js, off_t off, void *buf, int bytes); + diff --git a/sbin/jscan/jstream.c b/sbin/jscan/jstream.c new file mode 100644 index 0000000000..2f3826c49f --- /dev/null +++ b/sbin/jscan/jstream.c @@ -0,0 +1,363 @@ +/* + * Copyright (c) 2004,2005 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/jscan/jstream.c,v 1.1 2005/03/07 02:38:28 dillon Exp $ + */ + +#include "jscan.h" + +static struct jhash *JHashAry[JHASH_SIZE]; + +static struct jstream *jaddrecord(struct jfile *jf, struct jstream *js); + +/* + * Locate the next (or previous) complete virtual stream transaction given a + * file descriptor and direction. Keep track of partial stream records as + * a side effect. + * + * Note that a transaction might represent a huge I/O operation, resulting + * in an overall node structure that spans gigabytes, but individual + * subrecord leaf nodes are limited in size and we depend on this to simplify + * the handling of leaf records. + * + * A transaction may cover several raw records. The jstream collection for + * a transaction is only returned when the entire transaction has been + * successfully scanned. Due to the interleaving of transactions the ordering + * of returned JS's may be different (not exactly reversed) when scanning a + * journal backwards verses forwards. Since parallel operations are + * theoretically non-conflicting, this should not present a problem. + */ +struct jstream * +jscan_stream(struct jfile *jf) +{ + struct journal_rawrecbeg head; + struct journal_rawrecend tail; + int recsize; + int search; + int error; + struct jstream *js; + + /* + * Get the current offset and make sure it is 16-byte aligned. If it + * isn't, align it and enter search mode. + */ + if (jf->jf_pos & 15) { + jf_warn(jf, "realigning bad offset and entering search mode"); + jalign(jf); + search = 1; + } else { + search = 0; + } + + error = 0; + js = NULL; + + if (jf->jf_direction == JF_FORWARDS) { + /* + * Scan the journal forwards. Note that the file pointer might not + * be seekable. + */ + while ((error = jread(jf, &head, sizeof(head))) == 0) { + if (head.begmagic != JREC_BEGMAGIC) { + if (search == 0) + jf_warn(jf, "bad beginmagic, searching for new record"); + search = 1; + jalign(jf); + continue; + } + recsize = (head.recsize + 15) & ~15; + if (recsize <= 0) { + jf_warn(jf, "bad recordsize: %d\n", recsize); + search = 1; + jalign(jf); + continue; + } + jset(jf); + js = malloc(offsetof(struct jstream, js_data[recsize])); + bzero(js, sizeof(struct jstream)); + bcopy(&head, js->js_data, sizeof(head)); + error = jread(jf, js->js_data + sizeof(head), recsize - sizeof(head)); + if (error) { + jf_warn(jf, "Incomplete stream record\n"); + jreturn(jf); + free(js); + js = NULL; + break; + } + js->js_size = recsize; + bcopy(js->js_data + recsize - sizeof(tail), &tail, sizeof(tail)); + if (tail.endmagic != JREC_ENDMAGIC) { + jf_warn(jf, "bad endmagic, searching for new record"); + search = 1; + jreturn(jf); + free(js); + js = NULL; + continue; + } + jflush(jf); + if ((js = jaddrecord(jf, js)) != NULL) + break; + } + } else { + /* + * Scan the journal backwards. Note that jread()'s reverse-seek and + * read. The data read will be forward ordered, however. + */ + while ((error = jread(jf, &tail, sizeof(tail))) == 0) { + if (tail.endmagic != JREC_ENDMAGIC) { + if (search == 0) + jf_warn(jf, "bad endmagic, searching for new record"); + search = 1; + jalign(jf); + continue; + } + recsize = (tail.recsize + 15) & ~15; + if (recsize <= 0) { + jf_warn(jf, "bad recordsize: %d\n", recsize); + search = 1; + jalign(jf); + continue; + } + jset(jf); + js = malloc(offsetof(struct jstream, js_data[recsize])); + bzero(js, sizeof(struct jstream)); + bcopy(&tail, js->js_data + recsize - sizeof(tail), sizeof(tail)); + error = jread(jf, js->js_data, recsize - sizeof(tail)); + + if (error) { + jf_warn(jf, "Incomplete stream record\n"); + jreturn(jf); + free(js); + js = NULL; + break; + } + js->js_size = recsize; + bcopy(js->js_data + recsize - sizeof(tail), &tail, sizeof(tail)); + bcopy(js->js_data, &head, sizeof(head)); + if (head.begmagic != JREC_BEGMAGIC) { + jf_warn(jf, "bad begmagic, searching for new record"); + search = 1; + jreturn(jf); + free(js); + continue; + } + jflush(jf); + if ((js = jaddrecord(jf, js)) != NULL) + break; + } + } + jf->jf_error = error; + return(js); +} + +/* + * Integrate a jstream record. Deal with the transaction begin and end flags + * to create a forward-referenced collection of jstream records. If we are + * able to complete a transaction, the first js associated with that + * transaction is returned. + * + * XXX we need to store the data for very large multi-record transactions + * separately since it might not fit into memory. + */ +static struct jstream * +jaddrecord(struct jfile *jf, struct jstream *js) +{ + struct journal_rawrecbeg *head = (void *)js->js_data; + struct jhash *jh; + struct jhash **jhp; + struct jstream *jscan; + off_t off; + + /* + * Check for a completely self-contained transaction, just return the + * js if possible. + */ + if ((head->streamid & (JREC_STREAMCTL_BEGIN|JREC_STREAMCTL_END)) == + (JREC_STREAMCTL_BEGIN|JREC_STREAMCTL_END) + ) { + return (js); + } + + /* + * Check for an open transaction in the hash table, create a new one + * if necessary. + */ + jhp = &JHashAry[head->streamid & JHASH_MASK]; + while ((jh = *jhp) != NULL) { + if (((jh->jh_transid ^ head->streamid) & JREC_STREAMID_MASK) == 0) + break; + jhp = &jh->jh_hash; + } + if (jh == NULL) { + jh = malloc(sizeof(*jh)); + bzero(jh, sizeof(*jh)); + *jhp = jh; + jh->jh_first = js; + jh->jh_last = js; + jh->jh_transid = head->streamid; + return (NULL); + } + + /* + * Emplace the stream segment + */ + jh->jh_transid |= head->streamid & JREC_STREAMCTL_MASK; + if (jf->jf_direction == JF_FORWARDS) { + jh->jh_last->js_next = js; + jh->jh_last = js; + } else { + js->js_next = jh->jh_first; + jh->jh_first = js; + } + + /* + * If the transaction is complete, remove the hash entry and return the + * js representing the beginning of the transaction. + */ + if ((jh->jh_transid & (JREC_STREAMCTL_BEGIN|JREC_STREAMCTL_END)) == + (JREC_STREAMCTL_BEGIN|JREC_STREAMCTL_END) + ) { + *jhp = jh->jh_hash; + js = jh->jh_first; + free(jh); + off = 0; + for (jscan = js; jscan; jscan = jscan->js_next) { + jscan->js_off = off; + off += jscan->js_size; + } + } else { + js = NULL; + } + return (js); +} + +void +jscan_dispose(struct jstream *js) +{ + struct jstream *jnext; + + if (js->js_alloc_buf) { + free(js->js_alloc_buf); + js->js_alloc_buf = NULL; + js->js_alloc_size = 0; + } + + while (js) { + jnext = js->js_next; + free(js); + js = jnext; + } +} + +/* + * Read the specified block of data out of a linked set of jstream + * structures. Returns 0 on success or an error code on error. + */ +int +jsread(struct jstream *js, off_t off, void *buf, int bytes) +{ + const void *ptr; + int n; + + while (bytes) { + n = jsreadany(js, off, &ptr); + if (n == 0) + return (ENOENT); + if (n > bytes) + n = bytes; + bcopy(ptr, buf, n); + buf = (char *)buf + n; + off += n; + bytes -= n; + } + return(0); +} + +/* + * Read the specified block of data out of a linked set of jstream + * structures. Attempt to return a pointer into the data set but + * allocate and copy if that is not possible. Returns 0 on success + * or an error code on error. + */ +int +jsreadp(struct jstream *js, off_t off, const void **bufp, + int bytes) +{ + char *ptr; + int error; + int n; + int o; + + n = jsreadany(js, off, bufp); + if (n < bytes) { + if (js->js_alloc_size < bytes) { + if (js->js_alloc_buf) + free(js->js_alloc_buf); + js->js_alloc_buf = malloc(bytes); + js->js_alloc_size = bytes; + } + error = jsread(js, off, js->js_alloc_buf, bytes); + if (error) { + *bufp = NULL; + } else { + *bufp = js->js_alloc_buf; + } + } + return(error); +} + +/* + * Return the largest contiguous buffer starting at the specified offset, + * or 0. + */ +int +jsreadany(struct jstream *js, off_t off, const void **bufp) +{ + struct jstream *scan; + int n; + + if ((scan = js->js_cache) == NULL || scan->js_cache_off > off) + scan = js; + while (scan && scan->js_off <= off) { + js->js_cache = scan; + js->js_cache_off = scan->js_off; + if (scan->js_off + scan->js_size > off) { + n = (int)(off - scan->js_off); + *bufp = scan->js_data + n; + return(scan->js_size - n); + } + scan = scan->js_next; + } + return(0); +} + diff --git a/sbin/jscan/subs.c b/sbin/jscan/subs.c new file mode 100644 index 0000000000..c8f833f432 --- /dev/null +++ b/sbin/jscan/subs.c @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2004,2005 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/jscan/subs.c,v 1.1 2005/03/07 02:38:28 dillon Exp $ + */ + +#include "jscan.h" + +void +jf_warn(struct jfile *jf, const char *ctl, ...) +{ + va_list va; + + fprintf(stderr, "@0x%016llx ", jf->jf_pos); + va_start(va, ctl); + vfprintf(stderr, ctl, va); + va_end(va); + fprintf(stderr, "\n"); +} + +const char * +type_to_name(int16_t rectype) +{ + const char *str; + + switch((u_int16_t)rectype & ~JMASK_LAST) { + case JLEAF_PAD: + str = "PAD"; + break; + case JLEAF_ABORT: + str = "ABORT"; + break; + case JTYPE_ASSOCIATE: + str = "ASSOCIATE"; + break; + case JTYPE_DISASSOCIATE: + str = "DISASSOCIATE"; + break; + case JTYPE_UNDO: + str = "UNDO"; + break; + case JTYPE_AUDIT: + str = "AUDIT"; + break; + case JTYPE_SETATTR: + str = "SETATTR"; + break; + case JTYPE_WRITE: + str = "WRITE"; + break; + case JTYPE_PUTPAGES: + str = "PUTPAGES"; + break; + case JTYPE_SETACL: + str = "SETACL"; + break; + case JTYPE_SETEXTATTR: + str = "SETEXTATTR"; + break; + case JTYPE_CREATE: + str = "CREATE"; + break; + case JTYPE_MKNOD: + str = "MKNOD"; + break; + case JTYPE_LINK: + str = "LINK"; + break; + case JTYPE_SYMLINK: + str = "SYMLINK"; + break; + case JTYPE_WHITEOUT: + str = "WHITEOUT"; + break; + case JTYPE_REMOVE: + str = "REMOVE"; + break; + case JTYPE_MKDIR: + str = "MKDIR"; + break; + case JTYPE_RMDIR: + str = "RMDIR"; + break; + case JTYPE_RENAME: + str = "RENAME"; + break; + case JTYPE_VATTR: + str = "vattr"; + break; + case JTYPE_CRED: + str = "cred"; + break; + case JLEAF_FILEDATA: + str = "filedata"; + break; + case JLEAF_PATH1: + str = "path1"; + break; + case JLEAF_PATH2: + str = "path2"; + break; + case JLEAF_PATH3: + str = "path3"; + break; + case JLEAF_PATH4: + str = "path4"; + break; + case JLEAF_UID: + str = "uid"; + break; + case JLEAF_GID: + str = "gid"; + break; + case JLEAF_MODES: + str = "modes"; + break; + case JLEAF_FFLAGS: + str = "fflags"; + break; + case JLEAF_PID: + str = "pid"; + break; + case JLEAF_PPID: + str = "ppid"; + break; + case JLEAF_COMM: + str = "comm"; + break; + case JLEAF_ATTRNAME: + str = "attrname"; + break; + case JLEAF_PATH_REF: + str = "path_ref"; + break; + case JLEAF_RESERVED_0F: + str = "?"; + break; + case JLEAF_SYMLINKDATA: + str = "symlinkdata"; + break; + case JLEAF_SEEKPOS: + str = "seekpos"; + break; + case JLEAF_INUM: + str = "inum"; + break; + case JLEAF_NLINK: + str = "nlink"; + break; + case JLEAF_FSID: + str = "fsid"; + break; + case JLEAF_SIZE: + str = "size"; + break; + case JLEAF_ATIME: + str = "atime"; + break; + case JLEAF_MTIME: + str = "mtime"; + break; + case JLEAF_CTIME: + str = "ctime"; + break; + case JLEAF_GEN: + str = "gen"; + break; + case JLEAF_FLAGS: + str = "flags"; + break; + case JLEAF_UDEV: + str = "udev"; + break; + case JLEAF_FILEREV: + str = "filerev"; + break; + default: + str = "?"; + break; + } + return (str); +} + + -- 2.41.0