| /* |
| * Copyright (c) 1989 The Regents of the University of California. |
| * All rights reserved. |
| * |
| * 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. All advertising materials mentioning features or use of this software |
| * must display the following acknowledgement: |
| * This product includes software developed by the University of |
| * California, Berkeley and its contributors. |
| * 4. Neither the name of the University 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 REGENTS 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 REGENTS 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. |
| */ |
| |
| /* 1999-02-22 Arkadiusz MiĆkiewicz <misiek@pld.ORG.PL> |
| * - added Native Language Support |
| */ |
| |
| #include <sys/types.h> |
| #include <sys/file.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <ctype.h> |
| #include <string.h> |
| #include "hexdump.h" |
| #include "nls.h" |
| #include "xalloc.h" |
| #include "strutils.h" |
| #include "colors.h" |
| |
| static void escape(char *p1); |
| static struct list_head *color_fmt(char *cfmt, int bcnt); |
| |
| static void __attribute__ ((__noreturn__)) badcnt(const char *s) |
| { |
| errx(EXIT_FAILURE, _("bad byte count for conversion character %s"), s); |
| } |
| |
| static void __attribute__ ((__noreturn__)) badsfmt(void) |
| { |
| errx(EXIT_FAILURE, _("%%s requires a precision or a byte count")); |
| } |
| |
| static void __attribute__ ((__noreturn__)) badfmt(const char *fmt) |
| { |
| errx(EXIT_FAILURE, _("bad format {%s}"), fmt); |
| } |
| |
| static void __attribute__ ((__noreturn__)) badconv(const char *ch) |
| { |
| errx(EXIT_FAILURE, _("bad conversion character %%%s"), ch); |
| } |
| |
| #define first_letter(s,f) strchr(f, *(s)) |
| |
| struct hexdump_fu *endfu; /* format at end-of-data */ |
| |
| void addfile(char *name, struct hexdump *hex) |
| { |
| char *fmt, *buf = NULL; |
| FILE *fp; |
| size_t n; |
| |
| if ((fp = fopen(name, "r")) == NULL) |
| err(EXIT_FAILURE, _("can't read %s"), name); |
| |
| while (getline(&buf, &n, fp) != -1) { |
| fmt = buf; |
| |
| while (*fmt && isspace(*fmt)) |
| ++fmt; |
| if (!*fmt || *fmt == '#') |
| continue; |
| |
| add_fmt(fmt, hex); |
| } |
| |
| free(buf); |
| fclose(fp); |
| } |
| |
| void add_fmt(const char *fmt, struct hexdump *hex) |
| { |
| const char *p, *savep; |
| struct hexdump_fs *tfs; |
| struct hexdump_fu *tfu; |
| |
| /* Start new linked list of format units. */ |
| tfs = xcalloc(1, sizeof(struct hexdump_fs)); |
| INIT_LIST_HEAD(&tfs->fslist); |
| INIT_LIST_HEAD(&tfs->fulist); |
| list_add_tail(&tfs->fslist, &hex->fshead); |
| |
| /* Take the format string and break it up into format units. */ |
| p = fmt; |
| while (TRUE) { |
| /* Skip leading white space. */ |
| if (!*(p = skip_space(p))) |
| break; |
| |
| /* Allocate a new format unit and link it in. */ |
| tfu = xcalloc(1, sizeof(struct hexdump_fu)); |
| tfu->reps = 1; |
| |
| INIT_LIST_HEAD(&tfu->fulist); |
| INIT_LIST_HEAD(&tfu->prlist); |
| list_add_tail(&tfu->fulist, &tfs->fulist); |
| |
| /* If leading digit, repetition count. */ |
| if (isdigit(*p)) { |
| savep = p; |
| while (isdigit(*p) && ++p) |
| ; |
| if (!isspace(*p) && *p != '/') |
| badfmt(fmt); |
| /* may overwrite either white space or slash */ |
| tfu->reps = atoi(savep); |
| tfu->flags = F_SETREP; |
| /* skip trailing white space */ |
| p = skip_space(++p); |
| } |
| |
| /* Skip slash and trailing white space. */ |
| if (*p == '/') |
| p = skip_space(++p); |
| |
| /* byte count */ |
| if (isdigit(*p)) { |
| savep = p; |
| while (isdigit(*p) && ++p) |
| ; |
| if (!isspace(*p)) |
| badfmt(fmt); |
| tfu->bcnt = atoi(savep); |
| /* skip trailing white space */ |
| p = skip_space(++p); |
| } |
| |
| /* format */ |
| if (*p != '"') |
| badfmt(fmt); |
| savep = ++p; |
| while (*p != '"') { |
| if (!*p++) |
| badfmt(fmt); |
| } |
| tfu->fmt = xmalloc(p - savep + 1); |
| xstrncpy(tfu->fmt, savep, p - savep + 1); |
| escape(tfu->fmt); |
| ++p; |
| } |
| } |
| |
| static const char *spec = ".#-+ 0123456789"; |
| |
| int block_size(struct hexdump_fs *fs) |
| { |
| struct hexdump_fu *fu; |
| int bcnt, prec, cursize = 0; |
| char *fmt; |
| struct list_head *p; |
| |
| /* figure out the data block size needed for each format unit */ |
| list_for_each (p, &fs->fulist) { |
| fu = list_entry(p, struct hexdump_fu, fulist); |
| if (fu->bcnt) { |
| cursize += fu->bcnt * fu->reps; |
| continue; |
| } |
| bcnt = prec = 0; |
| fmt = fu->fmt; |
| while (*fmt) { |
| if (*fmt != '%') { |
| ++fmt; |
| continue; |
| } |
| /* |
| * skip any special chars -- save precision in |
| * case it's a %s format. |
| */ |
| while (strchr(spec + 1, *++fmt)) |
| ; |
| if (*fmt == '.' && isdigit(*++fmt)) { |
| prec = atoi(fmt); |
| while (isdigit(*++fmt)) |
| ; |
| } |
| if (first_letter(fmt, "diouxX")) |
| bcnt += 4; |
| else if (first_letter(fmt, "efgEG")) |
| bcnt += 8; |
| else if (*fmt == 's') |
| bcnt += prec; |
| else if (*fmt == 'c' || (*fmt == '_' && first_letter(++fmt, "cpu"))) |
| ++bcnt; |
| ++fmt; |
| } |
| cursize += bcnt * fu->reps; |
| } |
| return(cursize); |
| } |
| |
| void rewrite_rules(struct hexdump_fs *fs, struct hexdump *hex) |
| { |
| enum { NOTOKAY, USEBCNT, USEPREC } sokay; |
| struct hexdump_pr *pr; |
| struct hexdump_fu *fu; |
| struct list_head *p, *q; |
| char *p1, *p2, *fmtp; |
| char savech, cs[4]; |
| int nconv, prec = 0; |
| |
| list_for_each (p, &fs->fulist) { |
| fu = list_entry(p, struct hexdump_fu, fulist); |
| /* |
| * Break each format unit into print units; each |
| * conversion character gets its own. |
| */ |
| nconv = 0; |
| fmtp = fu->fmt; |
| while (*fmtp) { |
| pr = xcalloc(1, sizeof(struct hexdump_pr)); |
| INIT_LIST_HEAD(&pr->prlist); |
| list_add_tail(&pr->prlist, &fu->prlist); |
| |
| /* Skip preceding text and up to the next % sign. */ |
| p1 = fmtp; |
| while (*p1 && *p1 != '%') |
| ++p1; |
| |
| /* Only text in the string. */ |
| if (!*p1) { |
| pr->fmt = xstrdup(fmtp); |
| pr->flags = F_TEXT; |
| break; |
| } |
| |
| /* |
| * Get precision for %s -- if have a byte count, don't |
| * need it. |
| */ |
| if (fu->bcnt) { |
| sokay = USEBCNT; |
| /* skip to conversion character */ |
| while (++p1 && strchr(spec, *p1)) |
| ; |
| } else { |
| /* skip any special chars, field width */ |
| while (strchr(spec + 1, *++p1)) |
| ; |
| if (*p1 == '.' && isdigit(*++p1)) { |
| sokay = USEPREC; |
| prec = atoi(p1); |
| while (isdigit(*++p1)) |
| ; |
| } else |
| sokay = NOTOKAY; |
| } |
| |
| p2 = p1 + 1; /* Set end pointer. */ |
| cs[0] = *p1; /* Set conversion string. */ |
| cs[1] = 0; |
| |
| /* |
| * Figure out the byte count for each conversion; |
| * rewrite the format as necessary, set up blank- |
| * padding for end of data. |
| */ |
| if (*cs == 'c') { |
| pr->flags = F_CHAR; |
| switch(fu->bcnt) { |
| case 0: |
| case 1: |
| pr->bcnt = 1; |
| break; |
| default: |
| p1[1] = '\0'; |
| badcnt(p1); |
| } |
| } else if (first_letter(cs, "di")) { |
| pr->flags = F_INT; |
| goto isint; |
| } else if (first_letter(cs, "ouxX")) { |
| pr->flags = F_UINT; |
| isint: cs[3] = '\0'; |
| cs[2] = cs[0]; |
| cs[1] = 'l'; |
| cs[0] = 'l'; |
| switch(fu->bcnt) { |
| case 0: |
| pr->bcnt = 4; |
| break; |
| case 1: |
| case 2: |
| case 4: |
| case 8: |
| pr->bcnt = fu->bcnt; |
| break; |
| default: |
| p1[1] = '\0'; |
| badcnt(p1); |
| } |
| } else if (first_letter(cs, "efgEG")) { |
| pr->flags = F_DBL; |
| switch(fu->bcnt) { |
| case 0: |
| pr->bcnt = 8; |
| break; |
| case 4: |
| case 8: |
| pr->bcnt = fu->bcnt; |
| break; |
| default: |
| p1[1] = '\0'; |
| badcnt(p1); |
| } |
| } else if(*cs == 's') { |
| pr->flags = F_STR; |
| switch(sokay) { |
| case NOTOKAY: |
| badsfmt(); |
| case USEBCNT: |
| pr->bcnt = fu->bcnt; |
| break; |
| case USEPREC: |
| pr->bcnt = prec; |
| break; |
| } |
| } else if (*cs == '_') { |
| ++p2; |
| switch(p1[1]) { |
| case 'A': |
| endfu = fu; |
| fu->flags |= F_IGNORE; |
| /* FALLTHROUGH */ |
| case 'a': |
| pr->flags = F_ADDRESS; |
| ++p2; |
| if (first_letter(p1 + 2, "dox")) { |
| cs[0] = 'l'; |
| cs[1] = 'l'; |
| cs[2] = p1[2]; |
| cs[3] = '\0'; |
| } else { |
| p1[3] = '\0'; |
| badconv(p1); |
| } |
| break; |
| case 'c': |
| pr->flags = F_C; |
| /* cs[0] = 'c'; set in conv_c */ |
| goto isint2; |
| case 'p': |
| pr->flags = F_P; |
| cs[0] = 'c'; |
| goto isint2; |
| case 'u': |
| pr->flags = F_U; |
| /* cs[0] = 'c'; set in conv_u */ |
| isint2: switch(fu->bcnt) { |
| case 0: |
| case 1: |
| pr->bcnt = 1; |
| break; |
| default: |
| p1[2] = '\0'; |
| badcnt(p1); |
| } |
| break; |
| default: |
| p1[2] = '\0'; |
| badconv(p1); |
| } |
| } else { |
| p1[1] = '\0'; |
| badconv(p1); |
| } |
| |
| /* Color unit(s) specified */ |
| if (*p2 == '_' && p2[1] == 'L') { |
| if (colors_wanted()) { |
| char *a; |
| |
| /* "cut out" the color_unit(s) */ |
| a = strchr(p2, '['); |
| p2 = strrchr(p2, ']'); |
| if (a++ && p2) |
| pr->colorlist = color_fmt(xstrndup(a, p2++ - a), pr->bcnt); |
| else |
| badconv(p2); |
| } |
| /* we don't want colors, quietly skip over them */ |
| else { |
| p2 = strrchr(p2, ']'); |
| /* be a bit louder if we don't know how to skip over them */ |
| if (!p2) |
| badconv("_L"); |
| ++p2; |
| } |
| } |
| /* |
| * Copy to hexdump_pr format string, set conversion character |
| * pointer, update original. |
| */ |
| savech = *p2; |
| p1[0] = '\0'; |
| pr->fmt = xmalloc(strlen(fmtp) + strlen(cs) + 1); |
| strcpy(pr->fmt, fmtp); |
| strcat(pr->fmt, cs); |
| *p2 = savech; |
| pr->cchar = pr->fmt + (p1 - fmtp); |
| fmtp = p2; |
| |
| /* Only one conversion character if byte count */ |
| if (!(pr->flags&F_ADDRESS) && fu->bcnt && nconv++) |
| errx(EXIT_FAILURE, |
| _("byte count with multiple conversion characters")); |
| } |
| /* |
| * If format unit byte count not specified, figure it out |
| * so can adjust rep count later. |
| */ |
| if (!fu->bcnt) |
| list_for_each(q, &fu->prlist) |
| fu->bcnt |
| += (list_entry(q, struct hexdump_pr, prlist))->bcnt; |
| } |
| /* |
| * If the format string interprets any data at all, and it's |
| * not the same as the blocksize, and its last format unit |
| * interprets any data at all, and has no iteration count, |
| * repeat it as necessary. |
| * |
| * If rep count is greater than 1, no trailing whitespace |
| * gets output from the last iteration of the format unit. |
| */ |
| list_for_each (p, &fs->fulist) { |
| fu = list_entry(p, struct hexdump_fu, fulist); |
| |
| if (list_entry_is_last(&fu->fulist, &fs->fulist) && |
| fs->bcnt < hex->blocksize && |
| !(fu->flags&F_SETREP) && fu->bcnt) |
| fu->reps += (hex->blocksize - fs->bcnt) / fu->bcnt; |
| if (fu->reps > 1) { |
| if (!list_empty(&fu->prlist)) { |
| pr = list_last_entry(&fu->prlist, |
| struct hexdump_pr, prlist); |
| for (p1 = pr->fmt, p2 = NULL; *p1; ++p1) |
| p2 = isspace(*p1) ? p1 : NULL; |
| if (p2) |
| pr->nospace = p2; |
| } |
| } |
| } |
| } |
| |
| /* [!]color[:string|:hex_number|:oct_number][@offt|@offt_start-offt_end],... */ |
| static struct list_head *color_fmt(char *cfmt, int bcnt) |
| { |
| struct hexdump_clr *hc, *hcnext; |
| struct list_head *ret_head; |
| char *clr, *fmt; |
| |
| ret_head = xmalloc(sizeof(struct list_head)); |
| hcnext = hc = xcalloc(1, sizeof(struct hexdump_clr)); |
| |
| INIT_LIST_HEAD(&hc->colorlist); |
| INIT_LIST_HEAD(ret_head); |
| list_add_tail(&hc->colorlist, ret_head); |
| |
| fmt = cfmt; |
| while (cfmt && *cfmt) { |
| char *end; |
| /* invert this condition */ |
| if (*cfmt == '!') { |
| hcnext->invert = 1; |
| ++cfmt; |
| } |
| |
| clr = xstrndup(cfmt, strcspn(cfmt, ":@,")); |
| cfmt += strlen(clr); |
| hcnext->fmt = color_sequence_from_colorname(clr); |
| free(clr); |
| |
| if (!hcnext->fmt) |
| return NULL; |
| |
| /* only colorize this specific value */ |
| if (*cfmt == ':') { |
| ++cfmt; |
| /* a hex or oct value */ |
| if (*cfmt == '0') { |
| /* hex */ |
| errno = 0; |
| end = NULL; |
| if (cfmt[1] == 'x' || cfmt[1] == 'X') |
| hcnext->val = strtoul(cfmt + 2, &end, 16); |
| else |
| hcnext->val = strtoul(cfmt, &end, 8); |
| if (errno || end == cfmt) |
| badfmt(fmt); |
| cfmt = end; |
| /* a string */ |
| } else { |
| off_t fmt_end; |
| char endchar; |
| char *endstr; |
| |
| hcnext->val = -1; |
| /* temporarily null-delimit the format, so we can reverse-search |
| * for the start of an offset specifier */ |
| fmt_end = strcspn(cfmt, ","); |
| endchar = cfmt[fmt_end]; |
| cfmt[fmt_end] = '\0'; |
| endstr = strrchr(cfmt, '@'); |
| |
| if (endstr) { |
| if (endstr[1] != '\0') |
| --endstr; |
| hcnext->str = xstrndup(cfmt, endstr - cfmt + 1); |
| } else |
| hcnext->str = xstrndup(cfmt, fmt_end); |
| |
| /* restore the character */ |
| cfmt[fmt_end] = endchar; |
| cfmt += strlen(hcnext->str); |
| } |
| |
| /* no specific value */ |
| } else |
| hcnext->val = -1; |
| |
| /* only colorize at this offset */ |
| hcnext->range = bcnt; |
| if (cfmt && *cfmt == '@') { |
| errno = 0; |
| hcnext->offt = strtoul(++cfmt, &cfmt, 10); |
| if (errno) |
| badfmt(fmt); |
| |
| /* offset range */ |
| if (*cfmt == '-') { |
| ++cfmt; |
| errno = 0; |
| |
| hcnext->range = |
| strtoul(cfmt, &cfmt, 10) - hcnext->offt + 1; |
| if (errno) |
| badfmt(fmt); |
| /* offset range must be between 0 and format byte count */ |
| if (hcnext->range < 0) |
| badcnt("_L"); |
| /* the offset extends over several print units, clone |
| * the condition, link it in and adjust the address/offset */ |
| while (hcnext->range > bcnt) { |
| hc = xcalloc(1, sizeof(struct hexdump_clr)); |
| memcpy(hc, hcnext, sizeof(struct hexdump_clr)); |
| |
| hc->range = bcnt; |
| |
| INIT_LIST_HEAD(&hc->colorlist); |
| list_add_tail(&hc->colorlist, ret_head); |
| |
| hcnext->offt += bcnt; |
| hcnext->range -= bcnt; |
| } |
| } |
| /* no specific offset */ |
| } else |
| hcnext->offt = (off_t)-1; |
| |
| /* check if the string we're looking for is the same length as the range */ |
| if (hcnext->str && (int)strlen(hcnext->str) != hcnext->range) |
| badcnt("_L"); |
| |
| /* link in another condition */ |
| if (cfmt && *cfmt == ',') { |
| ++cfmt; |
| |
| hcnext = xcalloc(1, sizeof(struct hexdump_clr)); |
| INIT_LIST_HEAD(&hcnext->colorlist); |
| list_add_tail(&hcnext->colorlist, ret_head); |
| } |
| } |
| return ret_head; |
| } |
| |
| static void escape(char *p1) |
| { |
| char *p2; |
| |
| /* alphabetic escape sequences have to be done in place */ |
| p2 = p1; |
| while (TRUE) { |
| if (!*p1) { |
| *p2 = *p1; |
| break; |
| } |
| if (*p1 == '\\') |
| switch(*++p1) { |
| case 'a': |
| /* *p2 = '\a'; */ |
| *p2 = '\007'; |
| break; |
| case 'b': |
| *p2 = '\b'; |
| break; |
| case 'f': |
| *p2 = '\f'; |
| break; |
| case 'n': |
| *p2 = '\n'; |
| break; |
| case 'r': |
| *p2 = '\r'; |
| break; |
| case 't': |
| *p2 = '\t'; |
| break; |
| case 'v': |
| *p2 = '\v'; |
| break; |
| default: |
| *p2 = *p1; |
| break; |
| } |
| ++p1; ++p2; |
| } |
| } |