NetBSD/usr.bin/hexdump/parse.c
2011-09-04 20:27:05 +00:00

549 lines
12 KiB
C

/* $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 <sys/cdefs.h>
#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 <sys/types.h>
#include <sys/file.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <util.h>
#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);
}