1216417Sdelphij/*-
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 * 4. Neither the name of the University nor the names of its contributors
141590Srgrimes *    may be used to endorse or promote products derived from this software
151590Srgrimes *    without specific prior written permission.
161590Srgrimes *
171590Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
181590Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
191590Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
201590Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
211590Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
221590Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
231590Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
241590Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
251590Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
261590Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
271590Srgrimes * SUCH DAMAGE.
281590Srgrimes */
29219153Sjilles/*
30219153Sjilles * Important: This file is used both as a standalone program /usr/bin/printf
31219153Sjilles * and as a builtin for /bin/sh (#define SHELL).
32219153Sjilles */
331590Srgrimes
34216310Sjilles#ifndef SHELL
351590Srgrimes#ifndef lint
3620409Sstevestatic char const copyright[] =
371590Srgrimes"@(#) Copyright (c) 1989, 1993\n\
381590Srgrimes	The Regents of the University of California.  All rights reserved.\n";
391590Srgrimes#endif /* not lint */
401590Srgrimes#endif
411590Srgrimes
421590Srgrimes#ifndef lint
4365429Simp#if 0
4420409Sstevestatic char const sccsid[] = "@(#)printf.c	8.1 (Berkeley) 7/20/93";
4565429Simp#endif
4659435Scracauerstatic const char rcsid[] =
4759435Scracauer  "$FreeBSD$";
481590Srgrimes#endif /* not lint */
491590Srgrimes
501590Srgrimes#include <sys/types.h>
511590Srgrimes
521590Srgrimes#include <err.h>
531590Srgrimes#include <errno.h>
54148721Sstefanf#include <inttypes.h>
551590Srgrimes#include <limits.h>
56216417Sdelphij#include <locale.h>
571590Srgrimes#include <stdio.h>
581590Srgrimes#include <stdlib.h>
591590Srgrimes#include <string.h>
6027966Ssteve#include <unistd.h>
61222418Sjilles#include <wchar.h>
621590Srgrimes
631590Srgrimes#ifdef SHELL
64265161Spfg#define	main printfcmd
6512730Sjoerg#include "bltin/bltin.h"
66215520Sjilles#include "error.h"
671590Srgrimes#endif
681590Srgrimes
69265161Spfg#define	PF(f, func) do {						\
70230961Spfg	char *b = NULL;							\
71230961Spfg	if (havewidth)							\
72230961Spfg		if (haveprec)						\
7318613Speter			(void)asprintf(&b, f, fieldwidth, precision, func); \
74230961Spfg		else							\
75230961Spfg			(void)asprintf(&b, f, fieldwidth, func);	\
76230961Spfg	else if (haveprec)						\
77230961Spfg		(void)asprintf(&b, f, precision, func);			\
78230961Spfg	else								\
79230961Spfg		(void)asprintf(&b, f, func);				\
80230961Spfg	if (b) {							\
81230961Spfg		(void)fputs(b, stdout);					\
82230961Spfg		free(b);						\
83230961Spfg	}								\
8495409Stjr} while (0)
851590Srgrimes
8692921Simpstatic int	 asciicode(void);
87215520Sjillesstatic char	*printf_doformat(char *, int *);
88145078Sstefanfstatic int	 escape(char *, int, size_t *);
8992921Simpstatic int	 getchr(void);
90143906Sdasstatic int	 getfloating(long double *, int);
9192921Simpstatic int	 getint(int *);
92148721Sstefanfstatic int	 getnum(intmax_t *, uintmax_t *, int);
9395409Stjrstatic const char
9495409Stjr		*getstr(void);
95216418Sdelphijstatic char	*mknum(char *, char);
9692921Simpstatic void	 usage(void);
971590Srgrimes
981590Srgrimesstatic char **gargv;
991590Srgrimes
1001590Srgrimesint
101102944Sdwmalonemain(int argc, char *argv[])
1021590Srgrimes{
103145078Sstefanf	size_t len;
104216447Sdelphij	int ch, chopped, end, rval;
105145061Sstefanf	char *format, *fmt, *start;
1061590Srgrimes
107216310Sjilles#ifndef SHELL
108216424Sdelphij	(void) setlocale(LC_ALL, "");
10972304Sache#endif
110215520Sjilles#ifdef SHELL
111215520Sjilles	optreset = 1; optind = 1; opterr = 0; /* initialize getopt */
112215520Sjilles#endif
113216447Sdelphij	while ((ch = getopt(argc, argv, "")) != -1)
114216447Sdelphij		switch (ch) {
115216447Sdelphij		case '?':
116216447Sdelphij		default:
117216447Sdelphij			usage();
118216447Sdelphij			return (1);
119216447Sdelphij		}
120216447Sdelphij	argc -= optind;
121216447Sdelphij	argv += optind;
1221590Srgrimes
1231590Srgrimes	if (argc < 1) {
1241590Srgrimes		usage();
125216439Sdelphij		return (1);
1261590Srgrimes	}
1271590Srgrimes
128215520Sjilles#ifdef SHELL
129215520Sjilles	INTOFF;
130215520Sjilles#endif
1311590Srgrimes	/*
1321590Srgrimes	 * Basic algorithm is to scan the format string for conversion
1331590Srgrimes	 * specifications -- once one is found, find out if the field
1341590Srgrimes	 * width or precision is a '*'; if it is, gather up value.  Note,
1351590Srgrimes	 * format strings are reused as necessary to use up the provided
1361590Srgrimes	 * arguments, arguments of zero/null string are provided to use
1371590Srgrimes	 * up the format string.
1381590Srgrimes	 */
139145078Sstefanf	fmt = format = *argv;
140145078Sstefanf	chopped = escape(fmt, 1, &len);		/* backslash interpretation */
141145061Sstefanf	rval = end = 0;
1421590Srgrimes	gargv = ++argv;
1431590Srgrimes	for (;;) {
144145061Sstefanf		start = fmt;
145145078Sstefanf		while (fmt < format + len) {
146145061Sstefanf			if (fmt[0] == '%') {
147145061Sstefanf				fwrite(start, 1, fmt - start, stdout);
148145061Sstefanf				if (fmt[1] == '%') {
149145061Sstefanf					/* %% prints a % */
150145061Sstefanf					putchar('%');
151145061Sstefanf					fmt += 2;
152145061Sstefanf				} else {
153215520Sjilles					fmt = printf_doformat(fmt, &rval);
154215520Sjilles					if (fmt == NULL) {
155215520Sjilles#ifdef SHELL
156215520Sjilles						INTON;
157215520Sjilles#endif
158145061Sstefanf						return (1);
159215520Sjilles					}
160145061Sstefanf					end = 0;
16195300Sjmallett				}
162145061Sstefanf				start = fmt;
163145061Sstefanf			} else
16498420Stjr				fmt++;
1651590Srgrimes		}
1661590Srgrimes
167145061Sstefanf		if (end == 1) {
168216606Sjilles			warnx("missing format character");
169215520Sjilles#ifdef SHELL
170215520Sjilles			INTON;
171215520Sjilles#endif
172145061Sstefanf			return (1);
173145061Sstefanf		}
174145061Sstefanf		fwrite(start, 1, fmt - start, stdout);
175215520Sjilles		if (chopped || !*gargv) {
176215520Sjilles#ifdef SHELL
177215520Sjilles			INTON;
178215520Sjilles#endif
179145061Sstefanf			return (rval);
180215520Sjilles		}
181145061Sstefanf		/* Restart at the beginning of the format string. */
182145061Sstefanf		fmt = format;
183145061Sstefanf		end = 1;
184145061Sstefanf	}
185145061Sstefanf	/* NOTREACHED */
186145061Sstefanf}
187145061Sstefanf
188145061Sstefanf
189145061Sstefanfstatic char *
190215520Sjillesprintf_doformat(char *start, int *rval)
191145061Sstefanf{
192145061Sstefanf	static const char skip1[] = "#'-+ 0";
193145061Sstefanf	static const char skip2[] = "0123456789";
194145061Sstefanf	char *fmt;
195145061Sstefanf	int fieldwidth, haveprec, havewidth, mod_ldbl, precision;
196145061Sstefanf	char convch, nextch;
197145061Sstefanf
198145061Sstefanf	fmt = start + 1;
199145061Sstefanf	/* skip to field width */
200145061Sstefanf	fmt += strspn(fmt, skip1);
201145061Sstefanf	if (*fmt == '*') {
202145061Sstefanf		if (getint(&fieldwidth))
203145061Sstefanf			return (NULL);
204145061Sstefanf		havewidth = 1;
205145061Sstefanf		++fmt;
206145061Sstefanf	} else {
207145061Sstefanf		havewidth = 0;
208145061Sstefanf
209145061Sstefanf		/* skip to possible '.', get following precision */
210145061Sstefanf		fmt += strspn(fmt, skip2);
211145061Sstefanf	}
212145061Sstefanf	if (*fmt == '.') {
213145061Sstefanf		/* precision present? */
214145061Sstefanf		++fmt;
2151590Srgrimes		if (*fmt == '*') {
216145061Sstefanf			if (getint(&precision))
217145061Sstefanf				return (NULL);
218145061Sstefanf			haveprec = 1;
2198323Sjoerg			++fmt;
2208323Sjoerg		} else {
221145061Sstefanf			haveprec = 0;
2221590Srgrimes
223145061Sstefanf			/* skip to conversion char */
224144902Sstefanf			fmt += strspn(fmt, skip2);
2258323Sjoerg		}
226145061Sstefanf	} else
227145061Sstefanf		haveprec = 0;
228145061Sstefanf	if (!*fmt) {
229216606Sjilles		warnx("missing format character");
230145061Sstefanf		return (NULL);
231145061Sstefanf	}
2328323Sjoerg
233145061Sstefanf	/*
234145061Sstefanf	 * Look for a length modifier.  POSIX doesn't have these, so
235145061Sstefanf	 * we only support them for floating-point conversions, which
236145061Sstefanf	 * are extensions.  This is useful because the L modifier can
237145061Sstefanf	 * be used to gain extra range and precision, while omitting
238145061Sstefanf	 * it is more likely to produce consistent results on different
239145061Sstefanf	 * architectures.  This is not so important for integers
240145061Sstefanf	 * because overflow is the only bad thing that can happen to
241145061Sstefanf	 * them, but consider the command  printf %a 1.1
242145061Sstefanf	 */
243145061Sstefanf	if (*fmt == 'L') {
244145061Sstefanf		mod_ldbl = 1;
245145061Sstefanf		fmt++;
246145061Sstefanf		if (!strchr("aAeEfFgG", *fmt)) {
247216606Sjilles			warnx("bad modifier L for %%%c", *fmt);
248145061Sstefanf			return (NULL);
2491590Srgrimes		}
250145061Sstefanf	} else {
251145061Sstefanf		mod_ldbl = 0;
252145061Sstefanf	}
2531590Srgrimes
254145061Sstefanf	convch = *fmt;
255145061Sstefanf	nextch = *++fmt;
256145061Sstefanf	*fmt = '\0';
257145061Sstefanf	switch (convch) {
258145061Sstefanf	case 'b': {
259145078Sstefanf		size_t len;
260145061Sstefanf		char *p;
261145061Sstefanf		int getout;
262143906Sdas
263145061Sstefanf		p = strdup(getstr());
264145061Sstefanf		if (p == NULL) {
265216606Sjilles			warnx("%s", strerror(ENOMEM));
266145061Sstefanf			return (NULL);
267145061Sstefanf		}
268145078Sstefanf		getout = escape(p, 0, &len);
269145061Sstefanf		*(fmt - 1) = 's';
270145061Sstefanf		PF(start, p);
271145061Sstefanf		*(fmt - 1) = 'b';
272145061Sstefanf		free(p);
273145061Sstefanf		if (getout)
274145061Sstefanf			return (fmt);
275145061Sstefanf		break;
276145061Sstefanf	}
277145061Sstefanf	case 'c': {
278145061Sstefanf		char p;
2791590Srgrimes
280145061Sstefanf		p = getchr();
281145061Sstefanf		PF(start, p);
282145061Sstefanf		break;
283145061Sstefanf	}
284145061Sstefanf	case 's': {
285145061Sstefanf		const char *p;
2861590Srgrimes
287145061Sstefanf		p = getstr();
288145061Sstefanf		PF(start, p);
289145061Sstefanf		break;
290145061Sstefanf	}
291145061Sstefanf	case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': {
292145061Sstefanf		char *f;
293148721Sstefanf		intmax_t val;
294148721Sstefanf		uintmax_t uval;
295145061Sstefanf		int signedconv;
2968874Srgrimes
297145061Sstefanf		signedconv = (convch == 'd' || convch == 'i');
298148721Sstefanf		if ((f = mknum(start, convch)) == NULL)
299145061Sstefanf			return (NULL);
300148721Sstefanf		if (getnum(&val, &uval, signedconv))
301145061Sstefanf			*rval = 1;
302145061Sstefanf		if (signedconv)
303145061Sstefanf			PF(f, val);
304145061Sstefanf		else
305145061Sstefanf			PF(f, uval);
306145061Sstefanf		break;
307145061Sstefanf	}
308145061Sstefanf	case 'e': case 'E':
309145061Sstefanf	case 'f': case 'F':
310145061Sstefanf	case 'g': case 'G':
311145061Sstefanf	case 'a': case 'A': {
312145061Sstefanf		long double p;
3131590Srgrimes
314145061Sstefanf		if (getfloating(&p, mod_ldbl))
315145061Sstefanf			*rval = 1;
316145061Sstefanf		if (mod_ldbl)
317145061Sstefanf			PF(start, p);
318145061Sstefanf		else
319145061Sstefanf			PF(start, (double)p);
320145061Sstefanf		break;
3211590Srgrimes	}
322145061Sstefanf	default:
323216606Sjilles		warnx("illegal format character %c", convch);
324145061Sstefanf		return (NULL);
325145061Sstefanf	}
326145061Sstefanf	*fmt = nextch;
327145061Sstefanf	return (fmt);
3281590Srgrimes}
3291590Srgrimes
3301590Srgrimesstatic char *
331216418Sdelphijmknum(char *str, char ch)
3321590Srgrimes{
33370256Sben	static char *copy;
33470256Sben	static size_t copy_size;
33595409Stjr	char *newcopy;
33670256Sben	size_t len, newlen;
3371590Srgrimes
3381590Srgrimes	len = strlen(str) + 2;
33970256Sben	if (len > copy_size) {
34070256Sben		newlen = ((len + 1023) >> 10) << 10;
34170256Sben		if ((newcopy = realloc(copy, newlen)) == NULL)
34295409Stjr		{
343216606Sjilles			warnx("%s", strerror(ENOMEM));
34470256Sben			return (NULL);
34595409Stjr		}
34670256Sben		copy = newcopy;
34770256Sben		copy_size = newlen;
34870256Sben	}
34962928Sse
3501590Srgrimes	memmove(copy, str, len - 3);
351148721Sstefanf	copy[len - 3] = 'j';
3521590Srgrimes	copy[len - 2] = ch;
3531590Srgrimes	copy[len - 1] = '\0';
3541590Srgrimes	return (copy);
3551590Srgrimes}
3561590Srgrimes
35795300Sjmallettstatic int
358145078Sstefanfescape(char *fmt, int percent, size_t *len)
3591590Srgrimes{
360230961Spfg	char *save, *store, c;
361230961Spfg	int value;
3621590Srgrimes
363230961Spfg	for (save = store = fmt; ((c = *fmt) != 0); ++fmt, ++store) {
3641590Srgrimes		if (c != '\\') {
3651590Srgrimes			*store = c;
3661590Srgrimes			continue;
3671590Srgrimes		}
3681590Srgrimes		switch (*++fmt) {
3691590Srgrimes		case '\0':		/* EOS, user error */
3701590Srgrimes			*store = '\\';
3711590Srgrimes			*++store = '\0';
372145078Sstefanf			*len = store - save;
37395300Sjmallett			return (0);
3741590Srgrimes		case '\\':		/* backslash */
3751590Srgrimes		case '\'':		/* single quote */
3761590Srgrimes			*store = *fmt;
3771590Srgrimes			break;
3781590Srgrimes		case 'a':		/* bell/alert */
379145074Sstefanf			*store = '\a';
3801590Srgrimes			break;
3811590Srgrimes		case 'b':		/* backspace */
3821590Srgrimes			*store = '\b';
3831590Srgrimes			break;
38495300Sjmallett		case 'c':
38595300Sjmallett			*store = '\0';
386145078Sstefanf			*len = store - save;
38795300Sjmallett			return (1);
3881590Srgrimes		case 'f':		/* form-feed */
3891590Srgrimes			*store = '\f';
3901590Srgrimes			break;
3911590Srgrimes		case 'n':		/* newline */
3921590Srgrimes			*store = '\n';
3931590Srgrimes			break;
3941590Srgrimes		case 'r':		/* carriage-return */
3951590Srgrimes			*store = '\r';
3961590Srgrimes			break;
3971590Srgrimes		case 't':		/* horizontal tab */
3981590Srgrimes			*store = '\t';
3991590Srgrimes			break;
4001590Srgrimes		case 'v':		/* vertical tab */
401145074Sstefanf			*store = '\v';
4021590Srgrimes			break;
4031590Srgrimes					/* octal constant */
4041590Srgrimes		case '0': case '1': case '2': case '3':
4051590Srgrimes		case '4': case '5': case '6': case '7':
406181153Sdas			c = (!percent && *fmt == '0') ? 4 : 3;
407181153Sdas			for (value = 0;
4081590Srgrimes			    c-- && *fmt >= '0' && *fmt <= '7'; ++fmt) {
4091590Srgrimes				value <<= 3;
4101590Srgrimes				value += *fmt - '0';
4111590Srgrimes			}
4121590Srgrimes			--fmt;
41398426Stjr			if (percent && value == '%') {
41498419Stjr				*store++ = '%';
41598419Stjr				*store = '%';
41698419Stjr			} else
417230961Spfg				*store = (char)value;
4181590Srgrimes			break;
4191590Srgrimes		default:
4201590Srgrimes			*store = *fmt;
4211590Srgrimes			break;
4221590Srgrimes		}
4231590Srgrimes	}
4241590Srgrimes	*store = '\0';
425145078Sstefanf	*len = store - save;
42695300Sjmallett	return (0);
4271590Srgrimes}
4281590Srgrimes
4291590Srgrimesstatic int
430102944Sdwmalonegetchr(void)
4311590Srgrimes{
4321590Srgrimes	if (!*gargv)
4331590Srgrimes		return ('\0');
4341590Srgrimes	return ((int)**gargv++);
4351590Srgrimes}
4361590Srgrimes
43787298Sdwmalonestatic const char *
438102944Sdwmalonegetstr(void)
4391590Srgrimes{
4401590Srgrimes	if (!*gargv)
4411590Srgrimes		return ("");
4421590Srgrimes	return (*gargv++);
4431590Srgrimes}
4441590Srgrimes
4451590Srgrimesstatic int
446102944Sdwmalonegetint(int *ip)
4471590Srgrimes{
448148721Sstefanf	intmax_t val;
449148721Sstefanf	uintmax_t uval;
45095409Stjr	int rval;
4511590Srgrimes
452148721Sstefanf	if (getnum(&val, &uval, 1))
4531590Srgrimes		return (1);
45495409Stjr	rval = 0;
45595409Stjr	if (val < INT_MIN || val > INT_MAX) {
456216606Sjilles		warnx("%s: %s", *gargv, strerror(ERANGE));
45795409Stjr		rval = 1;
45895409Stjr	}
45962928Sse	*ip = (int)val;
46095409Stjr	return (rval);
4611590Srgrimes}
4621590Srgrimes
4631590Srgrimesstatic int
464148721Sstefanfgetnum(intmax_t *ip, uintmax_t *uip, int signedconv)
4651590Srgrimes{
4661590Srgrimes	char *ep;
46795409Stjr	int rval;
4681590Srgrimes
4691590Srgrimes	if (!*gargv) {
470246277Seadler		*ip = *uip = 0;
4711590Srgrimes		return (0);
4721590Srgrimes	}
47395300Sjmallett	if (**gargv == '"' || **gargv == '\'') {
47495409Stjr		if (signedconv)
475148721Sstefanf			*ip = asciicode();
47695409Stjr		else
477148721Sstefanf			*uip = asciicode();
4781590Srgrimes		return (0);
4791590Srgrimes	}
48095409Stjr	rval = 0;
48195300Sjmallett	errno = 0;
48295409Stjr	if (signedconv)
483148721Sstefanf		*ip = strtoimax(*gargv, &ep, 0);
48495409Stjr	else
485148721Sstefanf		*uip = strtoumax(*gargv, &ep, 0);
48695409Stjr	if (ep == *gargv) {
487216606Sjilles		warnx("%s: expected numeric value", *gargv);
48895409Stjr		rval = 1;
48995409Stjr	}
49095409Stjr	else if (*ep != '\0') {
491216606Sjilles		warnx("%s: not completely converted", *gargv);
49295409Stjr		rval = 1;
49395409Stjr	}
49495409Stjr	if (errno == ERANGE) {
495216606Sjilles		warnx("%s: %s", *gargv, strerror(ERANGE));
49695409Stjr		rval = 1;
49795409Stjr	}
49895300Sjmallett	++gargv;
49995409Stjr	return (rval);
5001590Srgrimes}
5011590Srgrimes
50295409Stjrstatic int
503143906Sdasgetfloating(long double *dp, int mod_ldbl)
5041590Srgrimes{
50595300Sjmallett	char *ep;
50695409Stjr	int rval;
50795300Sjmallett
508145027Sstefanf	if (!*gargv) {
509145027Sstefanf		*dp = 0.0;
51095409Stjr		return (0);
511145027Sstefanf	}
51295300Sjmallett	if (**gargv == '"' || **gargv == '\'') {
51395409Stjr		*dp = asciicode();
51495409Stjr		return (0);
51595300Sjmallett	}
516126729Scperciva	rval = 0;
51795300Sjmallett	errno = 0;
518143906Sdas	if (mod_ldbl)
519143906Sdas		*dp = strtold(*gargv, &ep);
520143906Sdas	else
521143906Sdas		*dp = strtod(*gargv, &ep);
52295409Stjr	if (ep == *gargv) {
523216606Sjilles		warnx("%s: expected numeric value", *gargv);
52495409Stjr		rval = 1;
52595409Stjr	} else if (*ep != '\0') {
526216606Sjilles		warnx("%s: not completely converted", *gargv);
52795409Stjr		rval = 1;
52895409Stjr	}
52995409Stjr	if (errno == ERANGE) {
530216606Sjilles		warnx("%s: %s", *gargv, strerror(ERANGE));
53195409Stjr		rval = 1;
53295409Stjr	}
53395300Sjmallett	++gargv;
53495409Stjr	return (rval);
5351590Srgrimes}
5361590Srgrimes
5371590Srgrimesstatic int
538102944Sdwmaloneasciicode(void)
5391590Srgrimes{
540102944Sdwmalone	int ch;
541222418Sjilles	wchar_t wch;
542222418Sjilles	mbstate_t mbs;
5431590Srgrimes
544222418Sjilles	ch = (unsigned char)**gargv;
545222418Sjilles	if (ch == '\'' || ch == '"') {
546222418Sjilles		memset(&mbs, 0, sizeof(mbs));
547222418Sjilles		switch (mbrtowc(&wch, *gargv + 1, MB_LEN_MAX, &mbs)) {
548222418Sjilles		case (size_t)-2:
549222418Sjilles		case (size_t)-1:
550222418Sjilles			wch = (unsigned char)gargv[0][1];
551222418Sjilles			break;
552222418Sjilles		case 0:
553222418Sjilles			wch = 0;
554222418Sjilles			break;
555222418Sjilles		}
556222418Sjilles		ch = wch;
557222418Sjilles	}
5581590Srgrimes	++gargv;
5591590Srgrimes	return (ch);
5601590Srgrimes}
5611590Srgrimes
5621590Srgrimesstatic void
563102944Sdwmaloneusage(void)
5641590Srgrimes{
565146466Sru	(void)fprintf(stderr, "usage: printf format [arguments ...]\n");
5661590Srgrimes}
567