/* $NetBSD: parse.c,v 1.27 2011/09/04 20:27:27 joerg Exp $ */ /* * Copyright (c) 1989, 1993 * 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. 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. */ #if HAVE_NBTOOL_CONFIG_H #include "nbtool_config.h" #endif #include #if !defined(lint) #if 0 static char sccsid[] = "@(#)parse.c 8.1 (Berkeley) 6/6/93"; #else __RCSID("$NetBSD: parse.c,v 1.27 2011/09/04 20:27:27 joerg Exp $"); #endif #endif /* not lint */ #include #include #include #include #include #include #include #include #include #include #include #include "hexdump.h" __dead static void badcnt(char *); __dead static void badconv(char *); __dead static void badfmt(const char *); __dead static void badsfmt(void); FU *endfu; /* format at end-of-data */ void addfile(char *name) { char *p; FILE *fp; int ch; char buf[2048 + 1]; if ((fp = fopen(name, "r")) == NULL) err(1, "fopen %s", name); while (fgets(buf, sizeof(buf), fp)) { if (!(p = strchr(buf, '\n'))) { warnx("line too long."); while ((ch = getchar()) != '\n' && ch != EOF); continue; } *p = '\0'; for (p = buf; *p && isspace((unsigned char)*p); ++p); if (!*p || *p == '#') continue; add(p); } (void)fclose(fp); } void add(const char *fmt) { const char *p; static FS **nextfs; FS *tfs; FU *tfu, **nextfu; const char *savep; /* start new linked list of format units */ tfs = ecalloc(1, sizeof(FS)); if (!fshead) fshead = tfs; else *nextfs = tfs; nextfs = &tfs->nextfs; nextfu = &tfs->nextfu; /* take the format string and break it up into format units */ for (p = fmt;;) { /* skip leading white space */ for (; isspace((unsigned char)*p); ++p); if (!*p) break; /* allocate a new format unit and link it in */ tfu = ecalloc(1, sizeof(FU)); *nextfu = tfu; nextfu = &tfu->nextfu; tfu->reps = 1; /* if leading digit, repetition count */ if (isdigit((unsigned char)*p)) { for (savep = p; isdigit((unsigned char)*p); ++p); if (!isspace((unsigned char)*p) && *p != '/') badfmt(fmt); /* may overwrite either white space or slash */ tfu->reps = atoi(savep); tfu->flags = F_SETREP; /* skip trailing white space */ for (++p; isspace((unsigned char)*p); ++p); } /* skip slash and trailing white space */ if (*p == '/') while (isspace((unsigned char)*++p)); /* byte count */ if (isdigit((unsigned char)*p)) { for (savep = p; isdigit((unsigned char)*p); ++p); if (!isspace((unsigned char)*p)) badfmt(fmt); tfu->bcnt = atoi(savep); /* skip trailing white space */ for (++p; isspace((unsigned char)*p); ++p); } /* format */ if (*p != '"') badfmt(fmt); for (savep = ++p; *p != '"';) if (*p++ == 0) badfmt(fmt); tfu->fmt = emalloc(p - savep + 1); (void) strncpy(tfu->fmt, savep, p - savep); tfu->fmt[p - savep] = '\0'; escape(tfu->fmt); p++; } } static const char *spec = ".#-+ 0123456789"; int size(FS *fs) { FU *fu; int bcnt, cursize; char *fmt; int prec; /* figure out the data block size needed for each format unit */ for (cursize = 0, fu = fs->nextfu; fu; fu = fu->nextfu) { if (fu->bcnt) { cursize += fu->bcnt * fu->reps; continue; } for (bcnt = prec = 0, fmt = fu->fmt; *fmt; ++fmt) { if (*fmt != '%') continue; /* * skip any special chars -- save precision in * case it's a %s format. */ while (strchr(spec + 1, *++fmt)); if (*fmt == '.' && isdigit((unsigned char)*++fmt)) { prec = atoi(fmt); while (isdigit((unsigned char)*++fmt)); } switch(*fmt) { case 'c': bcnt += 1; break; case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': bcnt += 4; break; case 'e': case 'E': case 'f': case 'g': case 'G': bcnt += 8; break; case 's': bcnt += prec; break; case '_': switch(*++fmt) { case 'c': case 'p': case 'u': bcnt += 1; break; } } } cursize += bcnt * fu->reps; } return (cursize); } void rewrite(FS *fs) { enum { NOTOKAY, USEBCNT, USEPREC } sokay; PR *pr, **nextpr; FU *fu; char *p1, *p2; char savech, *fmtp, cs[sizeof(PRId64)]; int nconv, prec; prec = 0; for (fu = fs->nextfu; fu; fu = fu->nextfu) { /* * Break each format unit into print units; each conversion * character gets its own. */ nextpr = &fu->nextpr; for (nconv = 0, fmtp = fu->fmt; *fmtp; nextpr = &pr->nextpr) { pr = ecalloc(1, sizeof(*pr)); *nextpr = pr; /* Skip preceding text and up to the next % sign. */ for (p1 = fmtp; *p1 && *p1 != '%'; ++p1); /* Only text in the string. */ if (!*p1) { pr->fmt = 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. */ for (++p1; *p1 && strchr(spec, *p1); ++p1); } else { /* Skip any special chars, field width. */ while (*++p1 && strchr(spec + 1, *p1)); if (*p1 == '.' && isdigit((unsigned char)*++p1)) { sokay = USEPREC; prec = atoi(p1); while (isdigit((unsigned char)*++p1)) continue; } else sokay = NOTOKAY; } p2 = *p1 ? p1 + 1 : p1; /* 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. */ switch(cs[0]) { case 'c': pr->flags = F_CHAR; switch(fu->bcnt) { case 0: case 1: pr->bcnt = 1; break; default: p1[1] = '\0'; badcnt(p1); } break; case 'd': case 'i': pr->flags = F_INT; goto isint; case 'o': case 'u': case 'x': case 'X': pr->flags = F_UINT; isint: /* * Regardless of pr->bcnt, all integer * values are cast to [u]int64_t before * being printed by display(). We * therefore need to use PRI?64 as the * format, where '?' could actually * be any of [diouxX]. We make the * assumption (not guaranteed by the * C99 standard) that we can derive * all the other PRI?64 values from * PRId64 simply by changing the last * character. For example, if PRId64 is * "lld" or "qd", and cs[0] is 'o', then * we end up with "llo" or "qo". */ savech = cs[0]; strncpy(cs, PRId64, sizeof(PRId64) - 2); cs[sizeof(PRId64) - 2] = savech; cs[sizeof(PRId64) - 1] = '\0'; switch(fu->bcnt) { case 0: case 4: pr->bcnt = 4; break; case 1: pr->bcnt = 1; break; case 2: pr->bcnt = 2; break; case 8: pr->bcnt = 8; break; default: p1[1] = '\0'; badcnt(p1); } break; case 'e': case 'E': case 'f': case 'g': case 'G': pr->flags = F_DBL; switch(fu->bcnt) { case 0: case 8: pr->bcnt = 8; break; case 4: pr->bcnt = 4; break; default: p1[1] = '\0'; badcnt(p1); } break; case 's': pr->flags = F_STR; switch(sokay) { case NOTOKAY: badsfmt(); case USEBCNT: pr->bcnt = fu->bcnt; break; case USEPREC: pr->bcnt = prec; break; } break; case '_': ++p2; switch(p1[1]) { case 'A': endfu = fu; fu->flags |= F_IGNORE; /* FALLTHROUGH */ case 'a': pr->flags = F_ADDRESS; ++p2; switch(p1[2]) { case 'd': case 'o': case'x': /* * See comments above for * the way we use PRId64. */ strncpy(cs, PRId64, sizeof(PRId64) - 2); cs[sizeof(PRId64) - 2] = p1[2]; cs[sizeof(PRId64) - 1] = '\0'; break; default: 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); } break; default: p1[1] = '\0'; badconv(p1); } /* * Copy to PR format string, set conversion character * pointer, update original. */ savech = *p2; p1[0] = '\0'; pr->fmt = emalloc(strlen(fmtp) + strlen(cs) + 1); (void)strcpy(pr->fmt, fmtp); (void)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(1, "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) for (pr = fu->nextpr; pr; pr = pr->nextpr) fu->bcnt += pr->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. */ for (fu = fs->nextfu; fu; fu = fu->nextfu) { if (!fu->nextfu && fs->bcnt < blocksize && !(fu->flags&F_SETREP) && fu->bcnt) fu->reps += (blocksize - fs->bcnt) / fu->bcnt; if (fu->reps > 1) { if (!fu->nextpr) break; for (pr = fu->nextpr;; pr = pr->nextpr) if (!pr->nextpr) break; for (p1 = pr->fmt, p2 = NULL; *p1; ++p1) p2 = isspace((unsigned char)*p1) ? p1 : NULL; if (p2) pr->nospace = p2; } } #ifdef DEBUG for (fu = fs->nextfu; fu; fu = fu->nextfu) { (void)printf("fmt:"); for (pr = fu->nextpr; pr; pr = pr->nextpr) (void)printf(" {%s}", pr->fmt); (void)printf("\n"); } #endif } void escape(char *p1) { char *p2; /* alphabetic escape sequences have to be done in place */ for (p2 = p1;; ++p1, ++p2) { if (!*p1) { *p2 = *p1; break; } if (*p1 == '\\') switch(*++p1) { case '\0': *p2 = '\\'; *++p2 = '\0'; return; /* incomplete escape sequence */ 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; } else *p2 = *p1; } } static void badcnt(char *s) { errx(1, "%s: bad byte count", s); } static void badsfmt(void) { errx(1, "%%s: requires a precision or a byte count"); } static void badfmt(const char *fmt) { errx(1, "\"%s\": bad format", fmt); } static void badconv(char *ch) { errx(1, "%%%s: bad conversion character", ch); }