1 /* @(#)printf.c	1.19 19/08/29 Copyright 2015-2019 J. Schilling */
2 #include <schily/mconfig.h>
3 /*
4  *	printf builtin or standalone
5  *
6  *	Note that in the Bourne Shell builtin variant, this module needs
7  *	libschily, but we can use -zlazyload
8  *
9  *	Libschily is needed because the Bourne Shell does not use stdio
10  *	internally and we thus need a printf() like formatter that allows
11  *	to use a callback function to output a character. This can be done
12  *	using the format() function from libschily.
13  *
14  *	Copyright (c) 2015-2019 J. Schilling
15  */
16 /*
17  * The contents of this file are subject to the terms of the
18  * Common Development and Distribution License, Version 1.0 only
19  * (the "License").  You may not use this file except in compliance
20  * with the License.
21  *
22  * See the file CDDL.Schily.txt in this distribution for details.
23  * A copy of the CDDL is also available via the Internet at
24  * http://www.opensource.org/licenses/cddl1.txt
25  *
26  * When distributing Covered Code, include this CDDL HEADER in each
27  * file and include the License file CDDL.Schily.txt from this distribution.
28  */
29 
30 #ifndef	BOURNE_SHELL
31 #include <defs.h>	/* Allow local defs.h for standalone printf via -I. */
32 #else
33 #include "defs.h"
34 #endif
35 #ifdef DO_SYSPRINTF
36 
37 static	UConst char sccsid[] =
38 	"@(#)printf.c	1.19 19/08/29 Copyright 2015-2019 J. Schilling";
39 
40 #include <schily/errno.h>
41 #include <schily/alloca.h>
42 
43 #ifndef	HAVE_STRTOD
44 #undef	DO_SYSPRINTF_FLOAT
45 #endif
46 #ifdef	NO_FLOATINGPOINT
47 #undef	DO_SYSPRINTF_FLOAT
48 #endif
49 
50 #define	LOCAL	static
51 #if	!defined(HAVE_DYN_ARRAYS) && !defined(HAVE_ALLOCA)
52 #define	exit(a)	flushb(); free(fm); return (a)
53 #else
54 #define	exit(a)	flushb(); return (a)
55 #endif
56 
57 #define	DOTSEEN	1	/* Did encounter a '.' in format    */
58 #define	FMINUS	2	/* Did encounter a '-' in format    */
59 #define	IGNSEEN	4	/* Did encounter a '\c' in argument */
60 
61 const char printfuse[] = "printf format [string ...]";
62 const char stardigit[]	= "*1234567890";
63 const char *digit	= &stardigit[1];
64 
65 LOCAL	int	xprp	__PR((unsigned char *fmt, char *p, int *width));
66 LOCAL	int	xprc	__PR((unsigned char *fmt, int c, int *width));
67 LOCAL	int	xpri	__PR((unsigned char *fmt, Intmax_t i, int *width));
68 LOCAL	Uchar	*intfmt	__PR((unsigned char *fmt, int c));
69 #ifdef	DO_SYSPRINTF_FLOAT
70 LOCAL	int	xprd	__PR((unsigned char *fmt, double d, int *width));
71 #endif
72 LOCAL	char	*gstr	__PR((unsigned char ***appp));
73 LOCAL	char	gchar	__PR((unsigned char ***appp));
74 LOCAL	void	grangechk __PR((unsigned char *p, unsigned char *ep));
75 LOCAL	Intmax_t strtoc	__PR((unsigned char **epp));
76 LOCAL	Intmax_t gintmax __PR((unsigned char ***appp));
77 LOCAL	UIntmax_t guintmax __PR((unsigned char ***appp));
78 #ifdef	DO_SYSPRINTF_FLOAT
79 LOCAL	double	gdouble __PR((unsigned char ***appp));
80 #endif
81 
82 EXPORT int	bprintf	__PR((const char *form, ...));
83 
84 /*
85  * Low level support for %s format.
86  */
87 LOCAL int
xprp(fmt,p,width)88 xprp(fmt, p, width)
89 	unsigned char	*fmt;
90 	char		*p;
91 	int		*width;
92 {
93 	switch (*width) {
94 	case 1: return (bprintf(C fmt, p));
95 	case 2: return (bprintf(C fmt, width[1], p));
96 	default: return (bprintf(C fmt, width[1], width[2], p));
97 	}
98 }
99 
100 /*
101  * Low level support for %s format.
102  */
103 LOCAL int
xprc(fmt,c,width)104 xprc(fmt, c, width)
105 	unsigned char	*fmt;
106 	int		c;
107 	int		*width;
108 {
109 	switch (*width) {
110 	case 1: return (bprintf(C fmt, c));
111 	case 2: return (bprintf(C fmt, width[1], c));
112 	default: return (bprintf(C fmt, width[1], width[2], c));
113 	}
114 }
115 
116 /*
117  * Low level support for %[diouxX] format.
118  * We need to modify the format for Intmax_t.
119  */
120 LOCAL int
xpri(fmt,i,width)121 xpri(fmt, i, width)
122 	unsigned char	*fmt;
123 	Intmax_t	i;
124 	int		*width;
125 {
126 	switch (*width) {
127 	case 1: return (bprintf(C fmt, i));
128 	case 2: return (bprintf(C fmt, width[1], i));
129 	default: return (bprintf(C fmt, width[1], width[2], i));
130 	}
131 }
132 
133 /*
134  * Modify the integer format strings to include the 'j' length modifier
135  * as we deal with integers of type maxint_t.
136  */
137 LOCAL unsigned char *
intfmt(fmt,c)138 intfmt(fmt, c)
139 	unsigned char	*fmt;
140 	int		c;
141 {
142 	UIntptr_t	ostakoff = relstak();	/* Current staktop offset */
143 	char	*p = C movstrstak(fmt, locstak());
144 
145 	--p;		/* Backspace over format specifier */
146 	*p++ = 'j';	/* Add maxint_t length modifier	*/
147 	*p++ = c;	/* Add format specifier again	*/
148 	staktop = UC p;
149 	GROWSTAKTOP();
150 	pushstak('\0');
151 	staktop = absstak(ostakoff);
152 
153 	return (stakbot);			/* locstak() returns stakbot */
154 }
155 
156 #ifdef	DO_SYSPRINTF_FLOAT
157 /*
158  * Low level support for %[eEfFgG] format.
159  */
160 LOCAL int
xprd(fmt,d,width)161 xprd(fmt, d, width)
162 	unsigned char	*fmt;
163 	double		d;
164 	int		*width;
165 {
166 	switch (*width) {
167 	case 1: return (bprintf(C fmt, d));
168 	case 2: return (bprintf(C fmt, width[1], d));
169 	default: return (bprintf(C fmt, width[1], width[2], d));
170 	}
171 }
172 #endif
173 
174 /*
175  * Fetch string argument
176  */
177 LOCAL char *
gstr(appp)178 gstr(appp)
179 	unsigned char	***appp;
180 {
181 	if (**appp)
182 		return (C *(*appp)++);
183 	return (C nullstr);
184 }
185 
186 /*
187  * Fetch char argument
188  */
189 LOCAL char
gchar(appp)190 gchar(appp)
191 	unsigned char	***appp;
192 {
193 	if (**appp)
194 		return (**(*appp)++);
195 	return (0);
196 }
197 
198 LOCAL void
grangechk(p,ep)199 grangechk(p, ep)
200 	unsigned char	*p;
201 	unsigned char	*ep;
202 {
203 	unsigned char	*av0 = UC "printf";
204 
205 	if (p == ep) {
206 		bfailure(av0, "expected numeric value: ", p);
207 	} else if (*ep) {
208 		bfailure(av0, "not completely converted: ", p);
209 	}
210 	if (errno == ERANGE) {
211 		bfailure(av0, "range error: ", p);
212 	}
213 }
214 
215 /*
216  * Fetch "c type argument
217  */
218 LOCAL Intmax_t
strtoc(epp)219 strtoc(epp)
220 	unsigned char	**epp;
221 {
222 	unsigned char	*ep = *epp;
223 	Intmax_t	val;
224 	wchar_t		wc;
225 	size_t		len = strlen(C ep);
226 
227 	len = mbtowc(&wc, C ep, len);
228 	if (wc && len > 0)
229 		ep += len;
230 	*epp = ep;
231 	val = wc;
232 
233 	return (val);
234 }
235 
236 /*
237  * Fetch Intmax_t argument
238  */
239 LOCAL Intmax_t
gintmax(appp)240 gintmax(appp)
241 	unsigned char	***appp;
242 {
243 	if (**appp) {
244 		unsigned char	*cp = *(*appp)++;
245 		unsigned char	*ep;
246 		Llong		val;
247 
248 		errno = 0;
249 		if (*cp == '"' || *cp == '\'') {
250 			ep = &cp[1];
251 			val = strtoc(&ep);
252 		} else {
253 #ifdef	HAVE_STRTOLL
254 			val = strtoll(C cp, CP &ep, 0);
255 #else
256 			ep = UC astoll(C cp, &val);
257 #endif
258 		}
259 		grangechk(cp, ep);
260 		return ((Intmax_t)val);
261 	}
262 	return ((Intmax_t)0);
263 }
264 
265 /*
266  * Fetch UIntmax_t argument
267  */
268 LOCAL UIntmax_t
guintmax(appp)269 guintmax(appp)
270 	unsigned char	***appp;
271 {
272 	if (**appp) {
273 		unsigned char	*cp = *(*appp)++;
274 		unsigned char	*ep;
275 		ULlong		val;
276 
277 		errno = 0;
278 		if (*cp == '"' || *cp == '\'') {
279 			ep = &cp[1];
280 			val = strtoc(&ep);
281 		} else {
282 #ifdef	HAVE_STRTOULL
283 			val = strtoull(C cp, CP &ep, 0);
284 #else
285 			ep = UC astoull(C cp, &val);
286 #endif
287 		}
288 		grangechk(cp, ep);
289 		return ((UIntmax_t)val);
290 	}
291 	return ((UIntmax_t)0);
292 }
293 
294 #ifdef	DO_SYSPRINTF_FLOAT
295 /*
296  * Fetch double argument
297  */
298 LOCAL double
gdouble(appp)299 gdouble(appp)
300 	unsigned char	***appp;
301 {
302 	if (**appp) {
303 		unsigned char	*cp = *(*appp)++;
304 		unsigned char	*ep;
305 		double		val;
306 
307 		errno = 0;
308 		if (*cp == '"' || *cp == '\'') {
309 			ep = &cp[1];
310 			val = strtoc(&ep);
311 		} else {
312 			val = strtod(C cp, CP &ep);
313 		}
314 		grangechk(cp, ep);
315 		return ((double)val);
316 	}
317 	return ((double)0.0);
318 }
319 #endif
320 
321 #ifndef	BOURNE_SHELL
322 int
main(argc,argv)323 main(argc, argv)
324 	int	argc;
325 	char	**argv;
326 {
327 	/*
328 	 * Need to set this in the standalone version
329 	 */
330 	(void) setlocale(LC_ALL, "");
331 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
332 #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
333 #endif
334 	(void) textdomain(TEXT_DOMAIN);
335 
336 	return (sysprintf(argc, UCP argv));
337 }
338 #endif	/* BOURNE_SHELL */
339 
340 int
sysprintf(argc,argv)341 sysprintf(argc, argv)
342 	int	argc;
343 	unsigned char	**argv;
344 {
345 	int		ind = optskip(argc, argv, printfuse);
346 	unsigned char	*fmt;
347 	unsigned char	*per;
348 	unsigned char	*cp;
349 	unsigned char	**oargv;	/* old argv	*/
350 	unsigned char	**dargv;	/* $ argv	*/
351 	unsigned char	**eargv;	/* end argv	*/
352 	unsigned char	**fargv;	/* format argv	*/
353 	unsigned char	**maxargv;	/* maxused argv	*/
354 	unsigned char	*av0 = *argv;
355 	int		len = strlen((ind < 0 || ind >= argc) ? \
356 					C argv[0] : C argv[ind]) + 1;
357 	wchar_t		wc;
358 #ifdef	HAVE_DYN_ARRAYS
359 	unsigned char	fm[len];
360 #else
361 #ifdef	HAVE_ALLOCA
362 	unsigned char	*fm = alloca(len);
363 #else
364 	unsigned char	*fm = malloc(len);
365 #endif
366 #endif
367 	unsigned char	*pfmt;
368 
369 	if (ind < 0)		/* Test for missing fmt is below */
370 		return (ERROR);
371 
372 	argc -= ind;
373 	argv += ind;
374 	fmt = *argv++;
375 	oargv = argv;
376 	eargv = &argv[argc-1];
377 	if (!fmt) {
378 		/*
379 		 * No args: give usage.
380 		 */
381 		gfailure((unsigned char *)usage, printfuse);
382 		return (ERROR);
383 	}
384 	do {
385 		maxargv = dargv = argv;
386 
387 		(void) mbtowc(NULL, NULL, 0);
388 		for (cp = fmt; *cp; cp++) {
389 			int		width[3];
390 			int		*widthp = &width[1];
391 			int		pflags;
392 			int		fldwidth;
393 			int		signif;
394 			Llong		val;
395 			unsigned char	fc;
396 			unsigned char	*p;
397 
398 
399 			if ((len = mbtowc(&wc, (char *)cp,
400 					MB_LEN_MAX)) <= 0) {
401 				(void) mbtowc(NULL, NULL, 0);
402 				prc_buff(*cp);
403 				continue;
404 			}
405 			if (wc == '\\') {
406 				unsigned char	cc;
407 
408 				cp = escape_char(cp, &cc, FALSE);
409 				if (cp == NULL) {
410 					exit(exitval);
411 				}
412 				prc_buff(cc);
413 				continue;
414 			} else if (wc == '%' && cp[len] == '%') {
415 				cp += len;
416 				prc_buff(wc);
417 				continue;
418 			} else if (wc != '%') {
419 	outc:
420 				for (; len > 0; len--)
421 					prc_buff(*cp++);
422 				cp--;
423 				continue;
424 			}
425 			fldwidth = pflags = 0;
426 			signif = -1;
427 			per = cp++;		/* Start of new printf format */
428 			pfmt = fm;
429 			*pfmt++ = '%';
430 
431 			len = strspn(C cp, digit);
432 			if (len > 0 && cp[len] == '$') {
433 				int	n = atoi(C cp);
434 
435 				if (--n < 0) {
436 					bfailure(av0, "illegal n$ format: ",
437 						--cp);
438 					exit(ERROR);
439 				}
440 				if (argv + n > eargv)
441 					dargv = eargv;
442 				else
443 					dargv = &argv[n];
444 				fargv = dargv;
445 				if (dargv > maxargv)
446 					maxargv = dargv;
447 				cp += len + 1;	/* skip n$ */
448 			} else {
449 				fargv = NULL;
450 			}
451 
452 			p = cp;
453 			cp += strspn(C cp, "+- #0"); /* Skip flag characters  */
454 			while (p < cp) {
455 				*pfmt++ = *p;
456 				if (*p++ == '-')
457 					pflags |= FMINUS;
458 			}
459 			p = NULL;
460 			if (*cp == '*') {
461 				len = strspn(C ++cp, digit);
462 				if (len > 0 && cp[len] == '$') {
463 					int	n = atoi(C cp);
464 
465 					if (--n < 0 || fargv == NULL) {
466 						bfailure(av0,
467 							"illegal n$ format: ",
468 							per);
469 						exit(ERROR);
470 					}
471 					if (argv + n > eargv)
472 						dargv = eargv;
473 					else
474 						dargv = &argv[n];
475 					cp += len + 1;	/* skip n$ */
476 				} else if (fargv != NULL) {
477 					bfailure(av0, "illegal n$ format: ",
478 						per);
479 					exit(ERROR);
480 				}
481 				*widthp++ = fldwidth = gintmax(&dargv);
482 				*pfmt++ = '*';
483 				if (dargv > maxargv)
484 					maxargv = dargv;
485 			} else {
486 #ifdef	HAVE_STRTOLL
487 				val = strtoll(C cp, NULL, 10);
488 #else
489 				(void) astollb(C cp, &val, 10);
490 #endif
491 				fldwidth = val;
492 				p = cp;
493 			}
494 			if (fldwidth < 0) {
495 				pflags |= FMINUS;
496 				fldwidth = -fldwidth;
497 			}
498 			cp += strspn(C cp, stardigit); /* Skip fldwitdh  */
499 			if (p) {
500 				while (p < cp)
501 					*pfmt++ = *p++;
502 			}
503 			if (*cp == '.') {
504 				pflags |= DOTSEEN;
505 				cp++;
506 				*pfmt++ = '.';
507 				p = NULL;
508 				if (*cp == '*') {
509 					len = strspn(C ++cp, digit);
510 					if (len > 0 && cp[len] == '$') {
511 						int	n = atoi(C cp);
512 
513 						if (--n < 0 || fargv == NULL) {
514 							bfailure(av0,
515 							"illegal n$ format: ",
516 							per);
517 							exit(ERROR);
518 						}
519 						if (argv + n > eargv)
520 							dargv = eargv;
521 						else
522 							dargv = &argv[n];
523 						cp += len + 1;	/* skip n$ */
524 					} else if (fargv != NULL) {
525 						bfailure(av0,
526 							"illegal n$ format: ",
527 							per);
528 						exit(ERROR);
529 					}
530 					*widthp++ = signif = gintmax(&dargv);
531 					*pfmt++ = '*';
532 					if (dargv > maxargv)
533 						maxargv = dargv;
534 				} else {
535 #ifdef	HAVE_STRTOLL
536 					val = strtoll(C cp, NULL, 10);
537 #else
538 					(void) astollb(C cp, &val, 10);
539 #endif
540 					signif = val;
541 					p = cp;
542 				}
543 				cp += strspn(C cp, stardigit); /* Sk prec */
544 				if (p) {
545 					while (p < cp)
546 						*pfmt++ = *p++;
547 				}
548 			}
549 			width[0] = widthp - width;
550 			if ((fc = *cp) == '\0') {
551 				len = cp - per;
552 				cp = per;
553 				goto outc;
554 			}
555 			*pfmt++ = fc;
556 			*pfmt = '\0';
557 			if (fargv)
558 				dargv = fargv;
559 
560 			switch (fc) {
561 			case 'b': {
562 				UIntptr_t	ostakoff = relstak();
563 				int		pre = 0;
564 				int		post = 0;
565 
566 				/*
567 				 * We cannot simply forward to printf() here as
568 				 * we need to support "%b" "\0" and printf()
569 				 * does not handle nul bytes.
570 				 */
571 				staktop = locstak();
572 				p = UC gstr(&dargv);
573 				for (; *p; p++) {
574 					if ((len = mbtowc(&wc, (char *)p,
575 							MB_LEN_MAX)) <= 0) {
576 						(void) mbtowc(NULL, NULL, 0);
577 						len = 1;
578 						wc = *p;
579 					}
580 					if (signif >= 0 &&
581 					    (staktop - stakbot + len) > signif)
582 						break;
583 					if (wc == '\\') {
584 						unsigned char	cc;
585 
586 						p = escape_char(p, &cc, TRUE);
587 						if (p == NULL) {
588 							pflags |= IGNSEEN;
589 							break;
590 						}
591 						GROWSTAKTOP();
592 						pushstak(cc);
593 					} else {
594 						for (; len > 0; len--) {
595 							GROWSTAKTOP();
596 							pushstak(*p++);
597 						}
598 						p--;
599 					}
600 				}
601 				if ((staktop - stakbot) < fldwidth) {
602 					if (pflags & FMINUS) {
603 						post = fldwidth -
604 						    (staktop - stakbot);
605 					} else {
606 						pre = fldwidth -
607 						    (staktop - stakbot);
608 					}
609 				}
610 				while (--pre >= 0)
611 					prc_buff(' ');
612 				for (p = stakbot; p < staktop; )
613 					prc_buff(*p++);
614 				while (--post >= 0)
615 					prc_buff(' ');
616 				staktop = absstak(ostakoff);
617 				if (pflags & IGNSEEN) {
618 					exit(0);
619 				}
620 				break;
621 			}
622 			case 'c': {
623 				char c = gchar(&dargv);
624 
625 				xprc(fm, c, width);
626 				break;
627 			}
628 			case 's': {
629 				p = UC gstr(&dargv);
630 
631 				xprp(fm, C p, width);
632 				break;
633 			}
634 			case 'd':
635 			case 'i': {
636 				Intmax_t	i = gintmax(&dargv);
637 				unsigned char	*fp = intfmt(fm, fc);
638 
639 				xpri(fp, i, width);
640 				break;
641 			}
642 			case 'o':
643 			case 'u':
644 			case 'x':
645 			case 'X': {
646 				Intmax_t	i = guintmax(&dargv);
647 				unsigned char	*fp = intfmt(fm, fc);
648 
649 				xpri(fp, i, width);
650 				break;
651 			}
652 #ifdef	DO_SYSPRINTF_FLOAT
653 #ifdef	HAVE_PRINTF_A
654 			case 'a': case 'A':
655 #endif
656 			case 'e': case 'E':
657 			case 'f': case 'F':
658 			case 'g': case 'G': {
659 				double	d = gdouble(&dargv);
660 
661 				xprd(fm, d, width);
662 				break;
663 			}
664 #endif
665 			default:
666 				bfailure(av0,
667 					"unknown format specifier: ", cp);
668 				exit(ERROR);
669 			}
670 			if (dargv > maxargv)
671 				maxargv = dargv;
672 		}
673 		argv = maxargv;
674 	/*
675 	 * If the format consumed any argument, loop over all arguments until
676 	 * all of them have been useed.
677 	 */
678 	} while (oargv != argv && *argv);
679 	exit(exitval);
680 }
681 
682 #ifndef	bprintf
683 #include <schily/varargs.h>
684 
685 /* VARARGS1 */
686 #ifdef	PROTOTYPES
687 EXPORT int
bprintf(const char * form,...)688 bprintf(const char *form, ...)
689 #else
690 EXPORT int
691 bprintf(form, va_alist)
692 	char	*form;
693 	va_dcl
694 #endif
695 {
696 	va_list	args;
697 	int	cnt;
698 
699 #ifdef	PROTOTYPES
700 	va_start(args, form);
701 #else
702 	va_start(args);
703 #endif
704 	/*
705 	 * Note that this requires libschily, but on Solaris we can
706 	 * use lazy linking via -zlazyload and avoid to link agaist
707 	 * libschily as long as the printf(1) builtin is not used.
708 	 */
709 	cnt = format((void (*)__PR((char, void *)))prc_buff, NULL,
710 			form, args);
711 	va_end(args);
712 	return (cnt);
713 }
714 #endif	/* bprintf */
715 
716 #endif /* DO_SYSBUILTIN */
717