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