xref: /freebsd/contrib/sendmail/libsm/vfprintf.c (revision 19261079b74319502c6ffa1249920079f0f69a72)
1 /*
2  * Copyright (c) 2000-2001, 2004 Proofpoint, Inc. and its suppliers.
3  *      All rights reserved.
4  * Copyright (c) 1990
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Chris Torek.
9  *
10  * By using this file, you agree to the terms and conditions set
11  * forth in the LICENSE file which can be found at the top level of
12  * the sendmail distribution.
13  */
14 
15 #include <sm/gen.h>
16 SM_IDSTR(id, "@(#)$Id: vfprintf.c,v 1.55 2013-11-22 20:51:44 ca Exp $")
17 
18 /*
19 **  Overall:
20 **  Actual printing innards.
21 **  This code is large and complicated...
22 */
23 
24 #include <sys/types.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <errno.h>
28 #include <sm/config.h>
29 #include <sm/varargs.h>
30 #include <sm/io.h>
31 #include <sm/heap.h>
32 #include <sm/conf.h>
33 #include "local.h"
34 #include "fvwrite.h"
35 
36 static int	sm_bprintf __P((SM_FILE_T *, const char *, va_list));
37 static void	sm_find_arguments __P((const char *, va_list , va_list **));
38 static void	sm_grow_type_table_x __P((unsigned char **, int *));
39 static int	sm_print __P((SM_FILE_T *, int, struct sm_uio *));
40 
41 /*
42 **  SM_PRINT -- print/flush to the file
43 **
44 **  Flush out all the vectors defined by the given uio,
45 **  then reset it so that it can be reused.
46 **
47 **	Parameters:
48 **		fp -- file pointer
49 **		timeout -- time to complete operation (milliseconds)
50 **		uio -- vector list of memory locations of data for printing
51 **
52 **	Results:
53 **		Success: 0 (zero)
54 **		Failure:
55 */
56 
57 static int
58 sm_print(fp, timeout, uio)
59 	SM_FILE_T *fp;
60 	int timeout;
61 	register struct sm_uio *uio;
62 {
63 	register int err;
64 
65 	if (uio->uio_resid == 0)
66 	{
67 		uio->uio_iovcnt = 0;
68 		return 0;
69 	}
70 	err = sm_fvwrite(fp, timeout, uio);
71 	uio->uio_resid = 0;
72 	uio->uio_iovcnt = 0;
73 	return err;
74 }
75 
76 /*
77 **  SM_BPRINTF -- allow formating to an unbuffered file.
78 **
79 **  Helper function for `fprintf to unbuffered unix file': creates a
80 **  temporary buffer (via a "fake" file pointer).
81 **  We only work on write-only files; this avoids
82 **  worries about ungetc buffers and so forth.
83 **
84 **	Parameters:
85 **		fp -- the file to send the o/p to
86 **		fmt -- format instructions for the o/p
87 **		ap -- vectors of data units used for formating
88 **
89 **	Results:
90 **		Failure: SM_IO_EOF and errno set
91 **		Success: number of data units used in the formating
92 **
93 **	Side effects:
94 **		formatted o/p can be SM_IO_BUFSIZ length maximum
95 */
96 
97 static int
98 sm_bprintf(fp, fmt, ap)
99 	SM_FILE_T *fp;
100 	const char *fmt;
101 	SM_VA_LOCAL_DECL
102 {
103 	int ret;
104 	SM_FILE_T fake;
105 	unsigned char buf[SM_IO_BUFSIZ];
106 	extern const char SmFileMagic[];
107 
108 	/* copy the important variables */
109 	fake.sm_magic = SmFileMagic;
110 	fake.f_timeout = SM_TIME_FOREVER;
111 	fake.f_timeoutstate = SM_TIME_BLOCK;
112 	fake.f_flags = fp->f_flags & ~SMNBF;
113 	fake.f_file = fp->f_file;
114 	fake.f_cookie = fp->f_cookie;
115 	fake.f_write = fp->f_write;
116 	fake.f_close = NULL;
117 	fake.f_open = NULL;
118 	fake.f_read = NULL;
119 	fake.f_seek = NULL;
120 	fake.f_setinfo = fake.f_getinfo = NULL;
121 	fake.f_type = "sm_bprintf:fake";
122 
123 	/* set up the buffer */
124 	fake.f_bf.smb_base = fake.f_p = buf;
125 	fake.f_bf.smb_size = fake.f_w = sizeof(buf);
126 	fake.f_lbfsize = 0;	/* not actually used, but Just In Case */
127 
128 	/* do the work, then copy any error status */
129 	ret = sm_io_vfprintf(&fake, SM_TIME_FOREVER, fmt, ap);
130 	if (ret >= 0 && sm_io_flush(&fake, SM_TIME_FOREVER))
131 		ret = SM_IO_EOF;	/* errno set by sm_io_flush */
132 	if (fake.f_flags & SMERR)
133 		fp->f_flags |= SMERR;
134 	return ret;
135 }
136 
137 
138 #define BUF		40
139 
140 #define STATIC_ARG_TBL_SIZE 8	/* Size of static argument table. */
141 
142 
143 /* Macros for converting digits to letters and vice versa */
144 #define to_digit(c)	((c) - '0')
145 #define is_digit(c)	((unsigned) to_digit(c) <= 9)
146 #define to_char(n)	((char) (n) + '0')
147 
148 /* Flags used during conversion. */
149 #define ALT		0x001		/* alternate form */
150 #define HEXPREFIX	0x002		/* add 0x or 0X prefix */
151 #define LADJUST		0x004		/* left adjustment */
152 #define LONGINT		0x010		/* long integer */
153 #define QUADINT		0x020		/* quad integer */
154 #define SHORTINT	0x040		/* short integer */
155 #define ZEROPAD		0x080		/* zero (as opposed to blank) pad */
156 #define FPT		0x100		/* Floating point number */
157 
158 /*
159 **  SM_IO_VFPRINTF -- performs actual formating for o/p
160 **
161 **	Parameters:
162 **		fp -- file pointer for o/p
163 **		timeout -- time to complete the print
164 **		fmt0 -- formating directives
165 **		ap -- vectors with data units for formating
166 **
167 **	Results:
168 **		Success: number of data units used for formatting
169 **		Failure: SM_IO_EOF and sets errno
170 */
171 
172 int
173 sm_io_vfprintf(fp, timeout, fmt0, ap)
174 	SM_FILE_T *fp;
175 	int timeout;
176 	const char *fmt0;
177 	SM_VA_LOCAL_DECL
178 {
179 	register char *fmt;	/* format string */
180 	register int ch;	/* character from fmt */
181 	register int n, m, n2;	/* handy integers (short term usage) */
182 	register char *cp;	/* handy char pointer (short term usage) */
183 	register struct sm_iov *iovp;/* for PRINT macro */
184 	register int flags;	/* flags as above */
185 	int ret;		/* return value accumulator */
186 	int width;		/* width from format (%8d), or 0 */
187 	int prec;		/* precision from format (%.3d), or -1 */
188 	char sign;		/* sign prefix (' ', '+', '-', or \0) */
189 	wchar_t wc;
190 	ULONGLONG_T _uquad;	/* integer arguments %[diouxX] */
191 	enum { OCT, DEC, HEX } base;/* base for [diouxX] conversion */
192 	int dprec;		/* a copy of prec if [diouxX], 0 otherwise */
193 	int realsz;		/* field size expanded by dprec */
194 	int size;		/* size of converted field or string */
195 	char *xdigs="0123456789abcdef"; /* digits for [xX] conversion */
196 #define NIOV 8
197 	struct sm_uio uio;	/* output information: summary */
198 	struct sm_iov iov[NIOV];/* ... and individual io vectors */
199 	char buf[BUF];		/* space for %c, %[diouxX], %[eEfgG] */
200 	char ox[2];		/* space for 0x hex-prefix */
201 	va_list *argtable;	/* args, built due to positional arg */
202 	va_list statargtable[STATIC_ARG_TBL_SIZE];
203 	int nextarg;		/* 1-based argument index */
204 	va_list orgap;		/* original argument pointer */
205 
206 	/*
207 	**  Choose PADSIZE to trade efficiency vs. size.  If larger printf
208 	**  fields occur frequently, increase PADSIZE and make the initialisers
209 	**  below longer.
210 	*/
211 #define PADSIZE	16		/* pad chunk size */
212 	static char blanks[PADSIZE] =
213 	 {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
214 	static char zeroes[PADSIZE] =
215 	 {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};
216 
217 	/*
218 	**  BEWARE, these `goto error' on error, and PAD uses `n'.
219 	*/
220 #define PRINT(ptr, len) do { \
221 	iovp->iov_base = (ptr); \
222 	iovp->iov_len = (len); \
223 	uio.uio_resid += (len); \
224 	iovp++; \
225 	if (++uio.uio_iovcnt >= NIOV) \
226 	{ \
227 		if (sm_print(fp, timeout, &uio)) \
228 			goto error; \
229 		iovp = iov; \
230 	} \
231 } while (0)
232 #define PAD(howmany, with) do \
233 { \
234 	if ((n = (howmany)) > 0) \
235 	{ \
236 		while (n > PADSIZE) { \
237 			PRINT(with, PADSIZE); \
238 			n -= PADSIZE; \
239 		} \
240 		PRINT(with, n); \
241 	} \
242 } while (0)
243 #define FLUSH() do \
244 { \
245 	if (uio.uio_resid && sm_print(fp, timeout, &uio)) \
246 		goto error; \
247 	uio.uio_iovcnt = 0; \
248 	iovp = iov; \
249 } while (0)
250 
251 	/*
252 	**  To extend shorts properly, we need both signed and unsigned
253 	**  argument extraction methods.
254 	*/
255 #define SARG() \
256 	(flags&QUADINT ? SM_VA_ARG(ap, LONGLONG_T) : \
257 	    flags&LONGINT ? GETARG(long) : \
258 	    flags&SHORTINT ? (long) (short) GETARG(int) : \
259 	    (long) GETARG(int))
260 #define UARG() \
261 	(flags&QUADINT ? SM_VA_ARG(ap, ULONGLONG_T) : \
262 	    flags&LONGINT ? GETARG(unsigned long) : \
263 	    flags&SHORTINT ? (unsigned long) (unsigned short) GETARG(int) : \
264 	    (unsigned long) GETARG(unsigned int))
265 
266 	/*
267 	**  Get * arguments, including the form *nn$.  Preserve the nextarg
268 	**  that the argument can be gotten once the type is determined.
269 	*/
270 #define GETASTER(val) \
271 	n2 = 0; \
272 	cp = fmt; \
273 	while (is_digit(*cp)) \
274 	{ \
275 		n2 = 10 * n2 + to_digit(*cp); \
276 		cp++; \
277 	} \
278 	if (*cp == '$') \
279 	{ \
280 		int hold = nextarg; \
281 		if (argtable == NULL) \
282 		{ \
283 			argtable = statargtable; \
284 			sm_find_arguments(fmt0, orgap, &argtable); \
285 		} \
286 		nextarg = n2; \
287 		val = GETARG(int); \
288 		nextarg = hold; \
289 		fmt = ++cp; \
290 	} \
291 	else \
292 	{ \
293 		val = GETARG(int); \
294 	}
295 
296 /*
297 **  Get the argument indexed by nextarg.   If the argument table is
298 **  built, use it to get the argument.  If its not, get the next
299 **  argument (and arguments must be gotten sequentially).
300 */
301 
302 #if SM_VA_STD
303 # define GETARG(type) \
304 	(((argtable != NULL) ? (void) (ap = argtable[nextarg]) : (void) 0), \
305 	 nextarg++, SM_VA_ARG(ap, type))
306 #else /* SM_VA_STD */
307 # define GETARG(type) \
308 	((argtable != NULL) ? (*((type*)(argtable[nextarg++]))) : \
309 			      (nextarg++, SM_VA_ARG(ap, type)))
310 #endif /* SM_VA_STD */
311 
312 	/* sorry, fprintf(read_only_file, "") returns SM_IO_EOF, not 0 */
313 	if (cantwrite(fp))
314 	{
315 		errno = EBADF;
316 		return SM_IO_EOF;
317 	}
318 
319 	/* optimise fprintf(stderr) (and other unbuffered Unix files) */
320 	if ((fp->f_flags & (SMNBF|SMWR|SMRW)) == (SMNBF|SMWR) &&
321 	    fp->f_file >= 0)
322 		return sm_bprintf(fp, fmt0, ap);
323 
324 	fmt = (char *) fmt0;
325 	argtable = NULL;
326 	nextarg = 1;
327 	SM_VA_COPY(orgap, ap);
328 	uio.uio_iov = iovp = iov;
329 	uio.uio_resid = 0;
330 	uio.uio_iovcnt = 0;
331 	ret = 0;
332 
333 	/* Scan the format for conversions (`%' character). */
334 	for (;;)
335 	{
336 		cp = fmt;
337 		n = 0;
338 		while ((wc = *fmt) != '\0')
339 		{
340 			if (wc == '%')
341 			{
342 				n = 1;
343 				break;
344 			}
345 			fmt++;
346 		}
347 		if ((m = fmt - cp) != 0)
348 		{
349 			PRINT(cp, m);
350 			ret += m;
351 		}
352 		if (n <= 0)
353 			goto done;
354 		fmt++;		/* skip over '%' */
355 
356 		flags = 0;
357 		dprec = 0;
358 		width = 0;
359 		prec = -1;
360 		sign = '\0';
361 
362 rflag:		ch = *fmt++;
363 reswitch:	switch (ch)
364 		{
365 		  case ' ':
366 
367 			/*
368 			**  ``If the space and + flags both appear, the space
369 			**  flag will be ignored.''
370 			**	-- ANSI X3J11
371 			*/
372 
373 			if (!sign)
374 				sign = ' ';
375 			goto rflag;
376 		  case '#':
377 			flags |= ALT;
378 			goto rflag;
379 		  case '*':
380 
381 			/*
382 			**  ``A negative field width argument is taken as a
383 			**  - flag followed by a positive field width.''
384 			**	-- ANSI X3J11
385 			**  They don't exclude field widths read from args.
386 			*/
387 
388 			GETASTER(width);
389 			if (width >= 0)
390 				goto rflag;
391 			width = -width;
392 			/* FALLTHROUGH */
393 		  case '-':
394 			flags |= LADJUST;
395 			goto rflag;
396 		  case '+':
397 			sign = '+';
398 			goto rflag;
399 		  case '.':
400 			if ((ch = *fmt++) == '*')
401 			{
402 				GETASTER(n);
403 				prec = n < 0 ? -1 : n;
404 				goto rflag;
405 			}
406 			n = 0;
407 			while (is_digit(ch))
408 			{
409 				n = 10 * n + to_digit(ch);
410 				ch = *fmt++;
411 			}
412 			if (ch == '$')
413 			{
414 				nextarg = n;
415 				if (argtable == NULL)
416 				{
417 					argtable = statargtable;
418 					sm_find_arguments(fmt0, orgap,
419 					    &argtable);
420 				}
421 				goto rflag;
422 			}
423 			prec = n < 0 ? -1 : n;
424 			goto reswitch;
425 		  case '0':
426 
427 			/*
428 			**  ``Note that 0 is taken as a flag, not as the
429 			**  beginning of a field width.''
430 			**	-- ANSI X3J11
431 			*/
432 
433 			flags |= ZEROPAD;
434 			goto rflag;
435 		  case '1': case '2': case '3': case '4':
436 		  case '5': case '6': case '7': case '8': case '9':
437 			n = 0;
438 			do
439 			{
440 				n = 10 * n + to_digit(ch);
441 				ch = *fmt++;
442 			} while (is_digit(ch));
443 			if (ch == '$')
444 			{
445 				nextarg = n;
446 				if (argtable == NULL)
447 				{
448 					argtable = statargtable;
449 					sm_find_arguments(fmt0, orgap,
450 					    &argtable);
451 				}
452 				goto rflag;
453 			}
454 			width = n;
455 			goto reswitch;
456 		  case 'h':
457 			flags |= SHORTINT;
458 			goto rflag;
459 		  case 'l':
460 			if (*fmt == 'l')
461 			{
462 				fmt++;
463 				flags |= QUADINT;
464 			}
465 			else
466 			{
467 				flags |= LONGINT;
468 			}
469 			goto rflag;
470 		  case 'q':
471 			flags |= QUADINT;
472 			goto rflag;
473 		  case 'c':
474 			*(cp = buf) = GETARG(int);
475 			size = 1;
476 			sign = '\0';
477 			break;
478 		  case 'D':
479 			flags |= LONGINT;
480 			/*FALLTHROUGH*/
481 		  case 'd':
482 		  case 'i':
483 			_uquad = SARG();
484 			if ((LONGLONG_T) _uquad < 0)
485 			{
486 				_uquad = -(LONGLONG_T) _uquad;
487 				sign = '-';
488 			}
489 			base = DEC;
490 			goto number;
491 		  case 'e':
492 		  case 'E':
493 		  case 'f':
494 		  case 'g':
495 		  case 'G':
496 			{
497 				double val;
498 				char *p;
499 				char fmt[16];
500 				char out[150];
501 				size_t len;
502 
503 				/*
504 				**  This code implements floating point output
505 				**  in the most portable manner possible,
506 				**  relying only on 'sprintf' as defined by
507 				**  the 1989 ANSI C standard.
508 				**  We silently cap width and precision
509 				**  at 120, to avoid buffer overflow.
510 				*/
511 
512 				val = GETARG(double);
513 
514 				p = fmt;
515 				*p++ = '%';
516 				if (sign)
517 					*p++ = sign;
518 				if (flags & ALT)
519 					*p++ = '#';
520 				if (flags & LADJUST)
521 					*p++ = '-';
522 				if (flags & ZEROPAD)
523 					*p++ = '0';
524 				*p++ = '*';
525 				if (prec >= 0)
526 				{
527 					*p++ = '.';
528 					*p++ = '*';
529 				}
530 				*p++ = ch;
531 				*p = '\0';
532 
533 				if (width > 120)
534 					width = 120;
535 				if (prec > 120)
536 					prec = 120;
537 				if (prec >= 0)
538 #if HASSNPRINTF
539 					snprintf(out, sizeof(out), fmt, width,
540 						prec, val);
541 #else
542 					sprintf(out, fmt, width, prec, val);
543 #endif
544 				else
545 #if HASSNPRINTF
546 					snprintf(out, sizeof(out), fmt, width,
547 						val);
548 #else
549 					sprintf(out, fmt, width, val);
550 #endif
551 				len = strlen(out);
552 				PRINT(out, len);
553 				FLUSH();
554 				continue;
555 			}
556 		case 'n':
557 			if (flags & QUADINT)
558 				*GETARG(LONGLONG_T *) = ret;
559 			else if (flags & LONGINT)
560 				*GETARG(long *) = ret;
561 			else if (flags & SHORTINT)
562 				*GETARG(short *) = ret;
563 			else
564 				*GETARG(int *) = ret;
565 			continue;	/* no output */
566 		  case 'O':
567 			flags |= LONGINT;
568 			/*FALLTHROUGH*/
569 		  case 'o':
570 			_uquad = UARG();
571 			base = OCT;
572 			goto nosign;
573 		  case 'p':
574 
575 			/*
576 			**  ``The argument shall be a pointer to void.  The
577 			**  value of the pointer is converted to a sequence
578 			**  of printable characters, in an implementation-
579 			**  defined manner.''
580 			**	-- ANSI X3J11
581 			*/
582 
583 			/* NOSTRICT */
584 			{
585 				union
586 				{
587 					void *p;
588 					ULONGLONG_T ll;
589 					unsigned long l;
590 					unsigned i;
591 				} u;
592 				u.p = GETARG(void *);
593 				if (sizeof(void *) == sizeof(ULONGLONG_T))
594 					_uquad = u.ll;
595 				else if (sizeof(void *) == sizeof(long))
596 					_uquad = u.l;
597 				else
598 					_uquad = u.i;
599 			}
600 			base = HEX;
601 			xdigs = "0123456789abcdef";
602 			flags |= HEXPREFIX;
603 			ch = 'x';
604 			goto nosign;
605 		  case 's':
606 			if ((cp = GETARG(char *)) == NULL)
607 				cp = "(null)";
608 			if (prec >= 0)
609 			{
610 				/*
611 				**  can't use strlen; can only look for the
612 				**  NUL in the first `prec' characters, and
613 				**  strlen() will go further.
614 				*/
615 
616 				char *p = memchr(cp, 0, prec);
617 
618 				if (p != NULL)
619 				{
620 					size = p - cp;
621 					if (size > prec)
622 						size = prec;
623 				}
624 				else
625 					size = prec;
626 			}
627 			else
628 				size = strlen(cp);
629 			sign = '\0';
630 			break;
631 		  case 'U':
632 			flags |= LONGINT;
633 			/*FALLTHROUGH*/
634 		  case 'u':
635 			_uquad = UARG();
636 			base = DEC;
637 			goto nosign;
638 		  case 'X':
639 			xdigs = "0123456789ABCDEF";
640 			goto hex;
641 		  case 'x':
642 			xdigs = "0123456789abcdef";
643 hex:			_uquad = UARG();
644 			base = HEX;
645 			/* leading 0x/X only if non-zero */
646 			if (flags & ALT && _uquad != 0)
647 				flags |= HEXPREFIX;
648 
649 			/* unsigned conversions */
650 nosign:			sign = '\0';
651 
652 			/*
653 			**  ``... diouXx conversions ... if a precision is
654 			**  specified, the 0 flag will be ignored.''
655 			**	-- ANSI X3J11
656 			*/
657 
658 number:			if ((dprec = prec) >= 0)
659 				flags &= ~ZEROPAD;
660 
661 			/*
662 			**  ``The result of converting a zero value with an
663 			**  explicit precision of zero is no characters.''
664 			**	-- ANSI X3J11
665 			*/
666 
667 			cp = buf + BUF;
668 			if (_uquad != 0 || prec != 0)
669 			{
670 				/*
671 				**  Unsigned mod is hard, and unsigned mod
672 				**  by a constant is easier than that by
673 				**  a variable; hence this switch.
674 				*/
675 
676 				switch (base)
677 				{
678 				  case OCT:
679 					do
680 					{
681 						*--cp = to_char(_uquad & 7);
682 						_uquad >>= 3;
683 					} while (_uquad);
684 					/* handle octal leading 0 */
685 					if (flags & ALT && *cp != '0')
686 						*--cp = '0';
687 					break;
688 
689 				  case DEC:
690 					/* many numbers are 1 digit */
691 					while (_uquad >= 10)
692 					{
693 						*--cp = to_char(_uquad % 10);
694 						_uquad /= 10;
695 					}
696 					*--cp = to_char(_uquad);
697 					break;
698 
699 				  case HEX:
700 					do
701 					{
702 						*--cp = xdigs[_uquad & 15];
703 						_uquad >>= 4;
704 					} while (_uquad);
705 					break;
706 
707 				  default:
708 					cp = "bug in sm_io_vfprintf: bad base";
709 					size = strlen(cp);
710 					goto skipsize;
711 				}
712 			}
713 			size = buf + BUF - cp;
714 		  skipsize:
715 			break;
716 		  default:	/* "%?" prints ?, unless ? is NUL */
717 			if (ch == '\0')
718 				goto done;
719 			/* pretend it was %c with argument ch */
720 			cp = buf;
721 			*cp = ch;
722 			size = 1;
723 			sign = '\0';
724 			break;
725 		}
726 
727 		/*
728 		**  All reasonable formats wind up here.  At this point, `cp'
729 		**  points to a string which (if not flags&LADJUST) should be
730 		**  padded out to `width' places.  If flags&ZEROPAD, it should
731 		**  first be prefixed by any sign or other prefix; otherwise,
732 		**  it should be blank padded before the prefix is emitted.
733 		**  After any left-hand padding and prefixing, emit zeroes
734 		**  required by a decimal [diouxX] precision, then print the
735 		**  string proper, then emit zeroes required by any leftover
736 		**  floating precision; finally, if LADJUST, pad with blanks.
737 		**
738 		**  Compute actual size, so we know how much to pad.
739 		**  size excludes decimal prec; realsz includes it.
740 		*/
741 
742 		realsz = dprec > size ? dprec : size;
743 		if (sign)
744 			realsz++;
745 		else if (flags & HEXPREFIX)
746 			realsz+= 2;
747 
748 		/* right-adjusting blank padding */
749 		if ((flags & (LADJUST|ZEROPAD)) == 0)
750 			PAD(width - realsz, blanks);
751 
752 		/* prefix */
753 		if (sign)
754 		{
755 			PRINT(&sign, 1);
756 		}
757 		else if (flags & HEXPREFIX)
758 		{
759 			ox[0] = '0';
760 			ox[1] = ch;
761 			PRINT(ox, 2);
762 		}
763 
764 		/* right-adjusting zero padding */
765 		if ((flags & (LADJUST|ZEROPAD)) == ZEROPAD)
766 			PAD(width - realsz, zeroes);
767 
768 		/* leading zeroes from decimal precision */
769 		PAD(dprec - size, zeroes);
770 
771 		/* the string or number proper */
772 		PRINT(cp, size);
773 		/* left-adjusting padding (always blank) */
774 		if (flags & LADJUST)
775 			PAD(width - realsz, blanks);
776 
777 		/* finally, adjust ret */
778 		ret += width > realsz ? width : realsz;
779 
780 		FLUSH();	/* copy out the I/O vectors */
781 	}
782 done:
783 	FLUSH();
784 error:
785 	SM_VA_END_COPY(orgap);
786 	if ((argtable != NULL) && (argtable != statargtable))
787 		sm_free(argtable);
788 	return sm_error(fp) ? SM_IO_EOF : ret;
789 	/* NOTREACHED */
790 }
791 
792 /* Type ids for argument type table. */
793 #define T_UNUSED	0
794 #define T_SHORT		1
795 #define T_U_SHORT	2
796 #define TP_SHORT	3
797 #define T_INT		4
798 #define T_U_INT		5
799 #define TP_INT		6
800 #define T_LONG		7
801 #define T_U_LONG	8
802 #define TP_LONG		9
803 #define T_QUAD		10
804 #define T_U_QUAD	11
805 #define TP_QUAD		12
806 #define T_DOUBLE	13
807 #define TP_CHAR		15
808 #define TP_VOID		16
809 
810 /*
811 **  SM_FIND_ARGUMENTS -- find all args when a positional parameter is found.
812 **
813 **  Find all arguments when a positional parameter is encountered.  Returns a
814 **  table, indexed by argument number, of pointers to each arguments.  The
815 **  initial argument table should be an array of STATIC_ARG_TBL_SIZE entries.
816 **  It will be replaced with a malloc-ed one if it overflows.
817 **
818 **	Parameters:
819 **		fmt0 -- formating directives
820 **		ap -- vector list of data unit for formating consumption
821 **		argtable -- an indexable table (returned) of 'ap'
822 **
823 **	Results:
824 **		none.
825 */
826 
827 static void
828 sm_find_arguments(fmt0, ap, argtable)
829 	const char *fmt0;
830 	SM_VA_LOCAL_DECL
831 	va_list **argtable;
832 {
833 	register char *fmt;	/* format string */
834 	register int ch;	/* character from fmt */
835 	register int n, n2;	/* handy integer (short term usage) */
836 	register char *cp;	/* handy char pointer (short term usage) */
837 	register int flags;	/* flags as above */
838 	unsigned char *typetable; /* table of types */
839 	unsigned char stattypetable[STATIC_ARG_TBL_SIZE];
840 	int tablesize;		/* current size of type table */
841 	int tablemax;		/* largest used index in table */
842 	int nextarg;		/* 1-based argument index */
843 
844 	/* Add an argument type to the table, expanding if necessary. */
845 #define ADDTYPE(type) \
846 	((nextarg >= tablesize) ? \
847 		(sm_grow_type_table_x(&typetable, &tablesize), 0) : 0, \
848 	typetable[nextarg++] = type, \
849 	(nextarg > tablemax) ? tablemax = nextarg : 0)
850 
851 #define ADDSARG() \
852 	((flags & LONGINT) ? ADDTYPE(T_LONG) : \
853 		((flags & SHORTINT) ? ADDTYPE(T_SHORT) : ADDTYPE(T_INT)))
854 
855 #define ADDUARG() \
856 	((flags & LONGINT) ? ADDTYPE(T_U_LONG) : \
857 		((flags & SHORTINT) ? ADDTYPE(T_U_SHORT) : ADDTYPE(T_U_INT)))
858 
859 	/* Add * arguments to the type array. */
860 #define ADDASTER() \
861 	n2 = 0; \
862 	cp = fmt; \
863 	while (is_digit(*cp)) \
864 	{ \
865 		n2 = 10 * n2 + to_digit(*cp); \
866 		cp++; \
867 	} \
868 	if (*cp == '$') \
869 	{ \
870 		int hold = nextarg; \
871 		nextarg = n2; \
872 		ADDTYPE (T_INT); \
873 		nextarg = hold; \
874 		fmt = ++cp; \
875 	} \
876 	else \
877 	{ \
878 		ADDTYPE (T_INT); \
879 	}
880 	fmt = (char *) fmt0;
881 	typetable = stattypetable;
882 	tablesize = STATIC_ARG_TBL_SIZE;
883 	tablemax = 0;
884 	nextarg = 1;
885 	(void) memset(typetable, T_UNUSED, STATIC_ARG_TBL_SIZE);
886 
887 	/* Scan the format for conversions (`%' character). */
888 	for (;;)
889 	{
890 		for (cp = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++)
891 			/* void */;
892 		if (ch == '\0')
893 			goto done;
894 		fmt++;		/* skip over '%' */
895 
896 		flags = 0;
897 
898 rflag:		ch = *fmt++;
899 reswitch:	switch (ch)
900 		{
901 		  case ' ':
902 		  case '#':
903 			goto rflag;
904 		  case '*':
905 			ADDASTER();
906 			goto rflag;
907 		  case '-':
908 		  case '+':
909 			goto rflag;
910 		  case '.':
911 			if ((ch = *fmt++) == '*')
912 			{
913 				ADDASTER();
914 				goto rflag;
915 			}
916 			while (is_digit(ch))
917 			{
918 				ch = *fmt++;
919 			}
920 			goto reswitch;
921 		  case '0':
922 			goto rflag;
923 		  case '1': case '2': case '3': case '4':
924 		  case '5': case '6': case '7': case '8': case '9':
925 			n = 0;
926 			do
927 			{
928 				n = 10 * n + to_digit(ch);
929 				ch = *fmt++;
930 			} while (is_digit(ch));
931 			if (ch == '$')
932 			{
933 				nextarg = n;
934 				goto rflag;
935 			}
936 			goto reswitch;
937 		  case 'h':
938 			flags |= SHORTINT;
939 			goto rflag;
940 		  case 'l':
941 			flags |= LONGINT;
942 			goto rflag;
943 		  case 'q':
944 			flags |= QUADINT;
945 			goto rflag;
946 		  case 'c':
947 			ADDTYPE(T_INT);
948 			break;
949 		  case 'D':
950 			flags |= LONGINT;
951 			/*FALLTHROUGH*/
952 		  case 'd':
953 		  case 'i':
954 			if (flags & QUADINT)
955 			{
956 				ADDTYPE(T_QUAD);
957 			}
958 			else
959 			{
960 				ADDSARG();
961 			}
962 			break;
963 		  case 'e':
964 		  case 'E':
965 		  case 'f':
966 		  case 'g':
967 		  case 'G':
968 			ADDTYPE(T_DOUBLE);
969 			break;
970 		  case 'n':
971 			if (flags & QUADINT)
972 				ADDTYPE(TP_QUAD);
973 			else if (flags & LONGINT)
974 				ADDTYPE(TP_LONG);
975 			else if (flags & SHORTINT)
976 				ADDTYPE(TP_SHORT);
977 			else
978 				ADDTYPE(TP_INT);
979 			continue;	/* no output */
980 		  case 'O':
981 			flags |= LONGINT;
982 			/*FALLTHROUGH*/
983 		  case 'o':
984 			if (flags & QUADINT)
985 				ADDTYPE(T_U_QUAD);
986 			else
987 				ADDUARG();
988 			break;
989 		  case 'p':
990 			ADDTYPE(TP_VOID);
991 			break;
992 		  case 's':
993 			ADDTYPE(TP_CHAR);
994 			break;
995 		  case 'U':
996 			flags |= LONGINT;
997 			/*FALLTHROUGH*/
998 		  case 'u':
999 			if (flags & QUADINT)
1000 				ADDTYPE(T_U_QUAD);
1001 			else
1002 				ADDUARG();
1003 			break;
1004 		  case 'X':
1005 		  case 'x':
1006 			if (flags & QUADINT)
1007 				ADDTYPE(T_U_QUAD);
1008 			else
1009 				ADDUARG();
1010 			break;
1011 		  default:	/* "%?" prints ?, unless ? is NUL */
1012 			if (ch == '\0')
1013 				goto done;
1014 			break;
1015 		}
1016 	}
1017 done:
1018 	/* Build the argument table. */
1019 	if (tablemax >= STATIC_ARG_TBL_SIZE)
1020 	{
1021 		*argtable = (va_list *)
1022 		    sm_malloc(sizeof(va_list) * (tablemax + 1));
1023 	}
1024 
1025 	for (n = 1; n <= tablemax; n++)
1026 	{
1027 		SM_VA_COPY((*argtable)[n], ap);
1028 		switch (typetable [n])
1029 		{
1030 		  case T_UNUSED:
1031 			(void) SM_VA_ARG(ap, int);
1032 			break;
1033 		  case T_SHORT:
1034 			(void) SM_VA_ARG(ap, int);
1035 			break;
1036 		  case T_U_SHORT:
1037 			(void) SM_VA_ARG(ap, int);
1038 			break;
1039 		  case TP_SHORT:
1040 			(void) SM_VA_ARG(ap, short *);
1041 			break;
1042 		  case T_INT:
1043 			(void) SM_VA_ARG(ap, int);
1044 			break;
1045 		  case T_U_INT:
1046 			(void) SM_VA_ARG(ap, unsigned int);
1047 			break;
1048 		  case TP_INT:
1049 			(void) SM_VA_ARG(ap, int *);
1050 			break;
1051 		  case T_LONG:
1052 			(void) SM_VA_ARG(ap, long);
1053 			break;
1054 		  case T_U_LONG:
1055 			(void) SM_VA_ARG(ap, unsigned long);
1056 			break;
1057 		  case TP_LONG:
1058 			(void) SM_VA_ARG(ap, long *);
1059 			break;
1060 		  case T_QUAD:
1061 			(void) SM_VA_ARG(ap, LONGLONG_T);
1062 			break;
1063 		  case T_U_QUAD:
1064 			(void) SM_VA_ARG(ap, ULONGLONG_T);
1065 			break;
1066 		  case TP_QUAD:
1067 			(void) SM_VA_ARG(ap, LONGLONG_T *);
1068 			break;
1069 		  case T_DOUBLE:
1070 			(void) SM_VA_ARG(ap, double);
1071 			break;
1072 		  case TP_CHAR:
1073 			(void) SM_VA_ARG(ap, char *);
1074 			break;
1075 		  case TP_VOID:
1076 			(void) SM_VA_ARG(ap, void *);
1077 			break;
1078 		}
1079 	}
1080 
1081 	if ((typetable != NULL) && (typetable != stattypetable))
1082 		sm_free(typetable);
1083 }
1084 
1085 /*
1086 **  SM_GROW_TYPE_TABLE -- Increase the size of the type table.
1087 **
1088 **	Parameters:
1089 **		tabletype -- type of table to grow
1090 **		tablesize -- requested new table size
1091 **
1092 **	Results:
1093 **		Raises an exception if can't allocate memory.
1094 */
1095 
1096 static void
1097 sm_grow_type_table_x(typetable, tablesize)
1098 	unsigned char **typetable;
1099 	int *tablesize;
1100 {
1101 	unsigned char *oldtable = *typetable;
1102 	int newsize = *tablesize * 2;
1103 
1104 	if (*tablesize == STATIC_ARG_TBL_SIZE)
1105 	{
1106 		*typetable = (unsigned char *) sm_malloc_x(sizeof(unsigned char)
1107 							   * newsize);
1108 		(void) memmove(*typetable, oldtable, *tablesize);
1109 	}
1110 	else
1111 	{
1112 		*typetable = (unsigned char *) sm_realloc_x(typetable,
1113 					sizeof(unsigned char) * newsize);
1114 	}
1115 	(void) memset(&typetable [*tablesize], T_UNUSED,
1116 		       (newsize - *tablesize));
1117 
1118 	*tablesize = newsize;
1119 }
1120