parse.c revision 1590
11590Srgrimes/*
21590Srgrimes * Copyright (c) 1989, 1993
31590Srgrimes *	The Regents of the University of California.  All rights reserved.
41590Srgrimes *
51590Srgrimes * Redistribution and use in source and binary forms, with or without
61590Srgrimes * modification, are permitted provided that the following conditions
71590Srgrimes * are met:
81590Srgrimes * 1. Redistributions of source code must retain the above copyright
91590Srgrimes *    notice, this list of conditions and the following disclaimer.
101590Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
111590Srgrimes *    notice, this list of conditions and the following disclaimer in the
121590Srgrimes *    documentation and/or other materials provided with the distribution.
131590Srgrimes * 3. All advertising materials mentioning features or use of this software
141590Srgrimes *    must display the following acknowledgement:
151590Srgrimes *	This product includes software developed by the University of
161590Srgrimes *	California, Berkeley and its contributors.
171590Srgrimes * 4. Neither the name of the University nor the names of its contributors
181590Srgrimes *    may be used to endorse or promote products derived from this software
191590Srgrimes *    without specific prior written permission.
201590Srgrimes *
211590Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
221590Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
231590Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
241590Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
251590Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
261590Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
271590Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
281590Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
291590Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
301590Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
311590Srgrimes * SUCH DAMAGE.
321590Srgrimes */
331590Srgrimes
341590Srgrimes#ifndef lint
351590Srgrimesstatic char sccsid[] = "@(#)parse.c	8.1 (Berkeley) 6/6/93";
361590Srgrimes#endif /* not lint */
371590Srgrimes
381590Srgrimes#include <sys/types.h>
391590Srgrimes
401590Srgrimes#include <errno.h>
411590Srgrimes#include <fcntl.h>
421590Srgrimes#include <stdio.h>
431590Srgrimes#include <stdlib.h>
441590Srgrimes#include <ctype.h>
451590Srgrimes#include <string.h>
461590Srgrimes#include "hexdump.h"
471590Srgrimes
481590SrgrimesFU *endfu;					/* format at end-of-data */
491590Srgrimes
501590Srgrimesvoid
511590Srgrimesaddfile(name)
521590Srgrimes	char *name;
531590Srgrimes{
541590Srgrimes	register char *p;
551590Srgrimes	FILE *fp;
561590Srgrimes	int ch;
571590Srgrimes	char buf[2048 + 1];
581590Srgrimes
591590Srgrimes	if ((fp = fopen(name, "r")) == NULL)
601590Srgrimes		err("%s: %s\n", name, strerror(errno));
611590Srgrimes	while (fgets(buf, sizeof(buf), fp)) {
621590Srgrimes		if (!(p = index(buf, '\n'))) {
631590Srgrimes			(void)fprintf(stderr, "hexdump: line too long.\n");
641590Srgrimes			while ((ch = getchar()) != '\n' && ch != EOF);
651590Srgrimes			continue;
661590Srgrimes		}
671590Srgrimes		*p = '\0';
681590Srgrimes		for (p = buf; *p && isspace(*p); ++p);
691590Srgrimes		if (!*p || *p == '#')
701590Srgrimes			continue;
711590Srgrimes		add(p);
721590Srgrimes	}
731590Srgrimes	(void)fclose(fp);
741590Srgrimes}
751590Srgrimes
761590Srgrimesvoid
771590Srgrimesadd(fmt)
781590Srgrimes	char *fmt;
791590Srgrimes{
801590Srgrimes	register char *p;
811590Srgrimes	static FS **nextfs;
821590Srgrimes	FS *tfs;
831590Srgrimes	FU *tfu, **nextfu;
841590Srgrimes	char *savep;
851590Srgrimes
861590Srgrimes	/* start new linked list of format units */
871590Srgrimes	tfs = emalloc(sizeof(FS));
881590Srgrimes	if (!fshead)
891590Srgrimes		fshead = tfs;
901590Srgrimes	else
911590Srgrimes		*nextfs = tfs;
921590Srgrimes	nextfs = &tfs->nextfs;
931590Srgrimes	nextfu = &tfs->nextfu;
941590Srgrimes
951590Srgrimes	/* take the format string and break it up into format units */
961590Srgrimes	for (p = fmt;;) {
971590Srgrimes		/* skip leading white space */
981590Srgrimes		for (; isspace(*p); ++p);
991590Srgrimes		if (!*p)
1001590Srgrimes			break;
1011590Srgrimes
1021590Srgrimes		/* allocate a new format unit and link it in */
1031590Srgrimes		tfu = emalloc(sizeof(FU));
1041590Srgrimes		*nextfu = tfu;
1051590Srgrimes		nextfu = &tfu->nextfu;
1061590Srgrimes		tfu->reps = 1;
1071590Srgrimes
1081590Srgrimes		/* if leading digit, repetition count */
1091590Srgrimes		if (isdigit(*p)) {
1101590Srgrimes			for (savep = p; isdigit(*p); ++p);
1111590Srgrimes			if (!isspace(*p) && *p != '/')
1121590Srgrimes				badfmt(fmt);
1131590Srgrimes			/* may overwrite either white space or slash */
1141590Srgrimes			tfu->reps = atoi(savep);
1151590Srgrimes			tfu->flags = F_SETREP;
1161590Srgrimes			/* skip trailing white space */
1171590Srgrimes			for (++p; isspace(*p); ++p);
1181590Srgrimes		}
1191590Srgrimes
1201590Srgrimes		/* skip slash and trailing white space */
1211590Srgrimes		if (*p == '/')
1221590Srgrimes			while (isspace(*++p));
1231590Srgrimes
1241590Srgrimes		/* byte count */
1251590Srgrimes		if (isdigit(*p)) {
1261590Srgrimes			for (savep = p; isdigit(*p); ++p);
1271590Srgrimes			if (!isspace(*p))
1281590Srgrimes				badfmt(fmt);
1291590Srgrimes			tfu->bcnt = atoi(savep);
1301590Srgrimes			/* skip trailing white space */
1311590Srgrimes			for (++p; isspace(*p); ++p);
1321590Srgrimes		}
1331590Srgrimes
1341590Srgrimes		/* format */
1351590Srgrimes		if (*p != '"')
1361590Srgrimes			badfmt(fmt);
1371590Srgrimes		for (savep = ++p; *p != '"';)
1381590Srgrimes			if (*p++ == 0)
1391590Srgrimes				badfmt(fmt);
1401590Srgrimes		if (!(tfu->fmt = malloc(p - savep + 1)))
1411590Srgrimes			nomem();
1421590Srgrimes		(void) strncpy(tfu->fmt, savep, p - savep);
1431590Srgrimes		tfu->fmt[p - savep] = '\0';
1441590Srgrimes		escape(tfu->fmt);
1451590Srgrimes		p++;
1461590Srgrimes	}
1471590Srgrimes}
1481590Srgrimes
1491590Srgrimesstatic char *spec = ".#-+ 0123456789";
1501590Srgrimes
1511590Srgrimesint
1521590Srgrimessize(fs)
1531590Srgrimes	FS *fs;
1541590Srgrimes{
1551590Srgrimes	register FU *fu;
1561590Srgrimes	register int bcnt, cursize;
1571590Srgrimes	register char *fmt;
1581590Srgrimes	int prec;
1591590Srgrimes
1601590Srgrimes	/* figure out the data block size needed for each format unit */
1611590Srgrimes	for (cursize = 0, fu = fs->nextfu; fu; fu = fu->nextfu) {
1621590Srgrimes		if (fu->bcnt) {
1631590Srgrimes			cursize += fu->bcnt * fu->reps;
1641590Srgrimes			continue;
1651590Srgrimes		}
1661590Srgrimes		for (bcnt = prec = 0, fmt = fu->fmt; *fmt; ++fmt) {
1671590Srgrimes			if (*fmt != '%')
1681590Srgrimes				continue;
1691590Srgrimes			/*
1701590Srgrimes			 * skip any special chars -- save precision in
1711590Srgrimes			 * case it's a %s format.
1721590Srgrimes			 */
1731590Srgrimes			while (index(spec + 1, *++fmt));
1741590Srgrimes			if (*fmt == '.' && isdigit(*++fmt)) {
1751590Srgrimes				prec = atoi(fmt);
1761590Srgrimes				while (isdigit(*++fmt));
1771590Srgrimes			}
1781590Srgrimes			switch(*fmt) {
1791590Srgrimes			case 'c':
1801590Srgrimes				bcnt += 1;
1811590Srgrimes				break;
1821590Srgrimes			case 'd': case 'i': case 'o': case 'u':
1831590Srgrimes			case 'x': case 'X':
1841590Srgrimes				bcnt += 4;
1851590Srgrimes				break;
1861590Srgrimes			case 'e': case 'E': case 'f': case 'g': case 'G':
1871590Srgrimes				bcnt += 8;
1881590Srgrimes				break;
1891590Srgrimes			case 's':
1901590Srgrimes				bcnt += prec;
1911590Srgrimes				break;
1921590Srgrimes			case '_':
1931590Srgrimes				switch(*++fmt) {
1941590Srgrimes				case 'c': case 'p': case 'u':
1951590Srgrimes					bcnt += 1;
1961590Srgrimes					break;
1971590Srgrimes				}
1981590Srgrimes			}
1991590Srgrimes		}
2001590Srgrimes		cursize += bcnt * fu->reps;
2011590Srgrimes	}
2021590Srgrimes	return (cursize);
2031590Srgrimes}
2041590Srgrimes
2051590Srgrimesvoid
2061590Srgrimesrewrite(fs)
2071590Srgrimes	FS *fs;
2081590Srgrimes{
2091590Srgrimes	enum { NOTOKAY, USEBCNT, USEPREC } sokay;
2101590Srgrimes	register PR *pr, **nextpr;
2111590Srgrimes	register FU *fu;
2121590Srgrimes	register char *p1, *p2;
2131590Srgrimes	char savech, *fmtp, cs[3];
2141590Srgrimes	int nconv, prec;
2151590Srgrimes
2161590Srgrimes	for (fu = fs->nextfu; fu; fu = fu->nextfu) {
2171590Srgrimes		/*
2181590Srgrimes		 * Break each format unit into print units; each conversion
2191590Srgrimes		 * character gets its own.
2201590Srgrimes		 */
2211590Srgrimes		for (nconv = 0, fmtp = fu->fmt; *fmtp; nextpr = &pr->nextpr) {
2221590Srgrimes			pr = emalloc(sizeof(PR));
2231590Srgrimes			if (!fu->nextpr)
2241590Srgrimes				fu->nextpr = pr;
2251590Srgrimes			else
2261590Srgrimes				*nextpr = pr;
2271590Srgrimes
2281590Srgrimes			/* Skip preceding text and up to the next % sign. */
2291590Srgrimes			for (p1 = fmtp; *p1 && *p1 != '%'; ++p1);
2301590Srgrimes
2311590Srgrimes			/* Only text in the string. */
2321590Srgrimes			if (!*p1) {
2331590Srgrimes				pr->fmt = fmtp;
2341590Srgrimes				pr->flags = F_TEXT;
2351590Srgrimes				break;
2361590Srgrimes			}
2371590Srgrimes
2381590Srgrimes			/*
2391590Srgrimes			 * Get precision for %s -- if have a byte count, don't
2401590Srgrimes			 * need it.
2411590Srgrimes			 */
2421590Srgrimes			if (fu->bcnt) {
2431590Srgrimes				sokay = USEBCNT;
2441590Srgrimes				/* Skip to conversion character. */
2451590Srgrimes				for (++p1; index(spec, *p1); ++p1);
2461590Srgrimes			} else {
2471590Srgrimes				/* Skip any special chars, field width. */
2481590Srgrimes				while (index(spec + 1, *++p1));
2491590Srgrimes				if (*p1 == '.' && isdigit(*++p1)) {
2501590Srgrimes					sokay = USEPREC;
2511590Srgrimes					prec = atoi(p1);
2521590Srgrimes					while (isdigit(*++p1));
2531590Srgrimes				} else
2541590Srgrimes					sokay = NOTOKAY;
2551590Srgrimes			}
2561590Srgrimes
2571590Srgrimes			p2 = p1 + 1;		/* Set end pointer. */
2581590Srgrimes			cs[0] = *p1;		/* Set conversion string. */
2591590Srgrimes			cs[1] = '\0';
2601590Srgrimes
2611590Srgrimes			/*
2621590Srgrimes			 * Figure out the byte count for each conversion;
2631590Srgrimes			 * rewrite the format as necessary, set up blank-
2641590Srgrimes			 * padding for end of data.
2651590Srgrimes			 */
2661590Srgrimes			switch(cs[0]) {
2671590Srgrimes			case 'c':
2681590Srgrimes				pr->flags = F_CHAR;
2691590Srgrimes				switch(fu->bcnt) {
2701590Srgrimes				case 0: case 1:
2711590Srgrimes					pr->bcnt = 1;
2721590Srgrimes					break;
2731590Srgrimes				default:
2741590Srgrimes					p1[1] = '\0';
2751590Srgrimes					badcnt(p1);
2761590Srgrimes				}
2771590Srgrimes				break;
2781590Srgrimes			case 'd': case 'i':
2791590Srgrimes				pr->flags = F_INT;
2801590Srgrimes				goto isint;
2811590Srgrimes			case 'o': case 'u': case 'x': case 'X':
2821590Srgrimes				pr->flags = F_UINT;
2831590Srgrimesisint:				cs[2] = '\0';
2841590Srgrimes				cs[1] = cs[0];
2851590Srgrimes				cs[0] = 'q';
2861590Srgrimes				switch(fu->bcnt) {
2871590Srgrimes				case 0: case 4:
2881590Srgrimes					pr->bcnt = 4;
2891590Srgrimes					break;
2901590Srgrimes				case 1:
2911590Srgrimes					pr->bcnt = 1;
2921590Srgrimes					break;
2931590Srgrimes				case 2:
2941590Srgrimes					pr->bcnt = 2;
2951590Srgrimes					break;
2961590Srgrimes				default:
2971590Srgrimes					p1[1] = '\0';
2981590Srgrimes					badcnt(p1);
2991590Srgrimes				}
3001590Srgrimes				break;
3011590Srgrimes			case 'e': case 'E': case 'f': case 'g': case 'G':
3021590Srgrimes				pr->flags = F_DBL;
3031590Srgrimes				switch(fu->bcnt) {
3041590Srgrimes				case 0: case 8:
3051590Srgrimes					pr->bcnt = 8;
3061590Srgrimes					break;
3071590Srgrimes				case 4:
3081590Srgrimes					pr->bcnt = 4;
3091590Srgrimes					break;
3101590Srgrimes				default:
3111590Srgrimes					p1[1] = '\0';
3121590Srgrimes					badcnt(p1);
3131590Srgrimes				}
3141590Srgrimes				break;
3151590Srgrimes			case 's':
3161590Srgrimes				pr->flags = F_STR;
3171590Srgrimes				switch(sokay) {
3181590Srgrimes				case NOTOKAY:
3191590Srgrimes					badsfmt();
3201590Srgrimes				case USEBCNT:
3211590Srgrimes					pr->bcnt = fu->bcnt;
3221590Srgrimes					break;
3231590Srgrimes				case USEPREC:
3241590Srgrimes					pr->bcnt = prec;
3251590Srgrimes					break;
3261590Srgrimes				}
3271590Srgrimes				break;
3281590Srgrimes			case '_':
3291590Srgrimes				++p2;
3301590Srgrimes				switch(p1[1]) {
3311590Srgrimes				case 'A':
3321590Srgrimes					endfu = fu;
3331590Srgrimes					fu->flags |= F_IGNORE;
3341590Srgrimes					/* FALLTHROUGH */
3351590Srgrimes				case 'a':
3361590Srgrimes					pr->flags = F_ADDRESS;
3371590Srgrimes					++p2;
3381590Srgrimes					switch(p1[2]) {
3391590Srgrimes					case 'd': case 'o': case'x':
3401590Srgrimes						cs[0] = 'q';
3411590Srgrimes						cs[1] = p1[2];
3421590Srgrimes						cs[2] = '\0';
3431590Srgrimes						break;
3441590Srgrimes					default:
3451590Srgrimes						p1[3] = '\0';
3461590Srgrimes						badconv(p1);
3471590Srgrimes					}
3481590Srgrimes					break;
3491590Srgrimes				case 'c':
3501590Srgrimes					pr->flags = F_C;
3511590Srgrimes					/* cs[0] = 'c';	set in conv_c */
3521590Srgrimes					goto isint2;
3531590Srgrimes				case 'p':
3541590Srgrimes					pr->flags = F_P;
3551590Srgrimes					cs[0] = 'c';
3561590Srgrimes					goto isint2;
3571590Srgrimes				case 'u':
3581590Srgrimes					pr->flags = F_U;
3591590Srgrimes					/* cs[0] = 'c';	set in conv_u */
3601590Srgrimesisint2:					switch(fu->bcnt) {
3611590Srgrimes					case 0: case 1:
3621590Srgrimes						pr->bcnt = 1;
3631590Srgrimes						break;
3641590Srgrimes					default:
3651590Srgrimes						p1[2] = '\0';
3661590Srgrimes						badcnt(p1);
3671590Srgrimes					}
3681590Srgrimes					break;
3691590Srgrimes				default:
3701590Srgrimes					p1[2] = '\0';
3711590Srgrimes					badconv(p1);
3721590Srgrimes				}
3731590Srgrimes				break;
3741590Srgrimes			default:
3751590Srgrimes				p1[1] = '\0';
3761590Srgrimes				badconv(p1);
3771590Srgrimes			}
3781590Srgrimes
3791590Srgrimes			/*
3801590Srgrimes			 * Copy to PR format string, set conversion character
3811590Srgrimes			 * pointer, update original.
3821590Srgrimes			 */
3831590Srgrimes			savech = *p2;
3841590Srgrimes			p1[0] = '\0';
3851590Srgrimes			pr->fmt = emalloc(strlen(fmtp) + 2);
3861590Srgrimes			(void)strcpy(pr->fmt, fmtp);
3871590Srgrimes			(void)strcat(pr->fmt, cs);
3881590Srgrimes			*p2 = savech;
3891590Srgrimes			pr->cchar = pr->fmt + (p1 - fmtp);
3901590Srgrimes			fmtp = p2;
3911590Srgrimes
3921590Srgrimes			/* Only one conversion character if byte count. */
3931590Srgrimes			if (!(pr->flags&F_ADDRESS) && fu->bcnt && nconv++)
3941590Srgrimes	    err("byte count with multiple conversion characters");
3951590Srgrimes		}
3961590Srgrimes		/*
3971590Srgrimes		 * If format unit byte count not specified, figure it out
3981590Srgrimes		 * so can adjust rep count later.
3991590Srgrimes		 */
4001590Srgrimes		if (!fu->bcnt)
4011590Srgrimes			for (pr = fu->nextpr; pr; pr = pr->nextpr)
4021590Srgrimes				fu->bcnt += pr->bcnt;
4031590Srgrimes	}
4041590Srgrimes	/*
4051590Srgrimes	 * If the format string interprets any data at all, and it's
4061590Srgrimes	 * not the same as the blocksize, and its last format unit
4071590Srgrimes	 * interprets any data at all, and has no iteration count,
4081590Srgrimes	 * repeat it as necessary.
4091590Srgrimes	 *
4101590Srgrimes	 * If, rep count is greater than 1, no trailing whitespace
4111590Srgrimes	 * gets output from the last iteration of the format unit.
4121590Srgrimes	 */
4131590Srgrimes	for (fu = fs->nextfu;; fu = fu->nextfu) {
4141590Srgrimes		if (!fu->nextfu && fs->bcnt < blocksize &&
4151590Srgrimes		    !(fu->flags&F_SETREP) && fu->bcnt)
4161590Srgrimes			fu->reps += (blocksize - fs->bcnt) / fu->bcnt;
4171590Srgrimes		if (fu->reps > 1) {
4181590Srgrimes			for (pr = fu->nextpr;; pr = pr->nextpr)
4191590Srgrimes				if (!pr->nextpr)
4201590Srgrimes					break;
4211590Srgrimes			for (p1 = pr->fmt, p2 = NULL; *p1; ++p1)
4221590Srgrimes				p2 = isspace(*p1) ? p1 : NULL;
4231590Srgrimes			if (p2)
4241590Srgrimes				pr->nospace = p2;
4251590Srgrimes		}
4261590Srgrimes		if (!fu->nextfu)
4271590Srgrimes			break;
4281590Srgrimes	}
4291590Srgrimes#ifdef DEBUG
4301590Srgrimes	for (fu = fs->nextfu; fu; fu = fu->nextfu) {
4311590Srgrimes		(void)printf("fmt:");
4321590Srgrimes		for (pr = fu->nextpr; pr; pr = pr->nextpr)
4331590Srgrimes			(void)printf(" {%s}", pr->fmt);
4341590Srgrimes		(void)printf("\n");
4351590Srgrimes	}
4361590Srgrimes#endif
4371590Srgrimes}
4381590Srgrimes
4391590Srgrimesvoid
4401590Srgrimesescape(p1)
4411590Srgrimes	register char *p1;
4421590Srgrimes{
4431590Srgrimes	register char *p2;
4441590Srgrimes
4451590Srgrimes	/* alphabetic escape sequences have to be done in place */
4461590Srgrimes	for (p2 = p1;; ++p1, ++p2) {
4471590Srgrimes		if (!*p1) {
4481590Srgrimes			*p2 = *p1;
4491590Srgrimes			break;
4501590Srgrimes		}
4511590Srgrimes		if (*p1 == '\\')
4521590Srgrimes			switch(*++p1) {
4531590Srgrimes			case 'a':
4541590Srgrimes			     /* *p2 = '\a'; */
4551590Srgrimes				*p2 = '\007';
4561590Srgrimes				break;
4571590Srgrimes			case 'b':
4581590Srgrimes				*p2 = '\b';
4591590Srgrimes				break;
4601590Srgrimes			case 'f':
4611590Srgrimes				*p2 = '\f';
4621590Srgrimes				break;
4631590Srgrimes			case 'n':
4641590Srgrimes				*p2 = '\n';
4651590Srgrimes				break;
4661590Srgrimes			case 'r':
4671590Srgrimes				*p2 = '\r';
4681590Srgrimes				break;
4691590Srgrimes			case 't':
4701590Srgrimes				*p2 = '\t';
4711590Srgrimes				break;
4721590Srgrimes			case 'v':
4731590Srgrimes				*p2 = '\v';
4741590Srgrimes				break;
4751590Srgrimes			default:
4761590Srgrimes				*p2 = *p1;
4771590Srgrimes				break;
4781590Srgrimes			}
4791590Srgrimes	}
4801590Srgrimes}
4811590Srgrimes
4821590Srgrimesvoid
4831590Srgrimesbadcnt(s)
4841590Srgrimes	char *s;
4851590Srgrimes{
4861590Srgrimes	err("%s: bad byte count", s);
4871590Srgrimes}
4881590Srgrimes
4891590Srgrimesvoid
4901590Srgrimesbadsfmt()
4911590Srgrimes{
4921590Srgrimes	err("%%s: requires a precision or a byte count\n");
4931590Srgrimes}
4941590Srgrimes
4951590Srgrimesvoid
4961590Srgrimesbadfmt(fmt)
4971590Srgrimes	char *fmt;
4981590Srgrimes{
4991590Srgrimes	err("\"%s\": bad format\n", fmt);
5001590Srgrimes}
5011590Srgrimes
5021590Srgrimesvoid
5031590Srgrimesbadconv(ch)
5041590Srgrimes	char *ch;
5051590Srgrimes{
5061590Srgrimes	err("%%%s: bad conversion character\n", ch);
5071590Srgrimes}
508