1 /* $NetBSD: printf.c,v 1.52 2021/04/16 18:31:28 christos Exp $ */ 2 3 /* 4 * Copyright (c) 1989, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 #ifndef lint 34 #if !defined(BUILTIN) && !defined(SHELL) 35 __COPYRIGHT("@(#) Copyright (c) 1989, 1993\ 36 The Regents of the University of California. All rights reserved."); 37 #endif 38 #endif 39 40 #ifndef lint 41 #if 0 42 static char sccsid[] = "@(#)printf.c 8.2 (Berkeley) 3/22/95"; 43 #else 44 __RCSID("$NetBSD: printf.c,v 1.52 2021/04/16 18:31:28 christos Exp $"); 45 #endif 46 #endif /* not lint */ 47 48 #include <sys/types.h> 49 50 #include <ctype.h> 51 #include <err.h> 52 #include <errno.h> 53 #include <inttypes.h> 54 #include <limits.h> 55 #include <locale.h> 56 #include <stdarg.h> 57 #include <stdio.h> 58 #include <stdlib.h> 59 #include <string.h> 60 #include <unistd.h> 61 62 #ifdef __GNUC__ 63 #define ESCAPE '\e' 64 #else 65 #define ESCAPE 033 66 #endif 67 68 static void conv_escape_str(char *, void (*)(int), int); 69 static char *conv_escape(char *, char *, int); 70 static char *conv_expand(const char *); 71 static char getchr(void); 72 static double getdouble(void); 73 static int getwidth(void); 74 static intmax_t getintmax(void); 75 static char *getstr(void); 76 static char *mklong(const char *, char); 77 static void check_conversion(const char *, const char *); 78 static void usage(void); 79 80 static void b_count(int); 81 static void b_output(int); 82 static size_t b_length; 83 static char *b_fmt; 84 85 static int rval; 86 static char **gargv; 87 88 #ifdef BUILTIN /* csh builtin */ 89 #define main progprintf 90 #endif 91 92 #ifdef SHELL /* sh (aka ash) builtin */ 93 #define main printfcmd 94 #include "../../bin/sh/bltin/bltin.h" 95 #endif /* SHELL */ 96 97 #define PF(f, func) { \ 98 if (fieldwidth != -1) { \ 99 if (precision != -1) \ 100 error = printf(f, fieldwidth, precision, func); \ 101 else \ 102 error = printf(f, fieldwidth, func); \ 103 } else if (precision != -1) \ 104 error = printf(f, precision, func); \ 105 else \ 106 error = printf(f, func); \ 107 } 108 109 #define APF(cpp, f, func) { \ 110 if (fieldwidth != -1) { \ 111 if (precision != -1) \ 112 error = asprintf(cpp, f, fieldwidth, precision, func); \ 113 else \ 114 error = asprintf(cpp, f, fieldwidth, func); \ 115 } else if (precision != -1) \ 116 error = asprintf(cpp, f, precision, func); \ 117 else \ 118 error = asprintf(cpp, f, func); \ 119 } 120 121 #define isodigit(c) ((c) >= '0' && (c) <= '7') 122 #define octtobin(c) ((c) - '0') 123 #define check(c, a) (c) >= (a) && (c) <= (a) + 5 ? (c) - (a) + 10 124 #define hextobin(c) (check(c, 'a') : check(c, 'A') : (c) - '0') 125 #ifdef main 126 int main(int, char *[]); 127 #endif 128 129 int 130 main(int argc, char *argv[]) 131 { 132 char *fmt, *start; 133 int fieldwidth, precision; 134 char nextch; 135 char *format; 136 char ch; 137 int error; 138 139 #if !defined(SHELL) && !defined(BUILTIN) 140 (void)setlocale (LC_ALL, ""); 141 #endif 142 143 rval = 0; /* clear for builtin versions (avoid holdover) */ 144 145 /* 146 * printf does not comply with Posix XBD 12.2 - there are no opts, 147 * not even the -- end of options marker. Do not run getoot(). 148 */ 149 if (argc > 2 && strchr(argv[1], '%') == NULL) { 150 int o; 151 152 /* 153 * except that if there are multiple args and 154 * the first (the nominal format) contains no '%' 155 * conversions (which we will approximate as no '%' 156 * characters at all, conversions or not) then the 157 * results are unspecified, and we can do what we 158 * like. So in that case, for some backward compat 159 * to scripts which (stupidly) do: 160 * printf -- format args 161 * process this case the old way. 162 */ 163 164 while ((o = getopt(argc, argv, "")) != -1) { 165 switch (o) { 166 case '?': 167 default: 168 usage(); 169 return 1; 170 } 171 } 172 argc -= optind; 173 argv += optind; 174 } else { 175 argc -= 1; /* drop argv[0] (the program name) */ 176 argv += 1; 177 } 178 179 if (argc < 1) { 180 usage(); 181 return 1; 182 } 183 184 format = *argv; 185 gargv = ++argv; 186 187 #define SKIP1 "#-+ 0'" 188 #define SKIP2 "0123456789" 189 do { 190 /* 191 * Basic algorithm is to scan the format string for conversion 192 * specifications -- once one is found, find out if the field 193 * width or precision is a '*'; if it is, gather up value. 194 * Note, format strings are reused as necessary to use up the 195 * provided arguments, arguments of zero/null string are 196 * provided to use up the format string. 197 */ 198 199 /* find next format specification */ 200 for (fmt = format; (ch = *fmt++) != '\0';) { 201 if (ch == '\\') { 202 char c_ch; 203 fmt = conv_escape(fmt, &c_ch, 0); 204 putchar(c_ch); 205 continue; 206 } 207 if (ch != '%' || (*fmt == '%' && ++fmt)) { 208 (void)putchar(ch); 209 continue; 210 } 211 212 /* 213 * Ok - we've found a format specification, 214 * Save its address for a later printf(). 215 */ 216 start = fmt - 1; 217 218 /* skip to field width */ 219 fmt += strspn(fmt, SKIP1); 220 if (*fmt == '*') { 221 fmt++; 222 fieldwidth = getwidth(); 223 } else { 224 fieldwidth = -1; 225 226 /* skip to possible '.' for precision */ 227 fmt += strspn(fmt, SKIP2); 228 } 229 230 if (*fmt == '.') { 231 /* get following precision */ 232 fmt++; 233 if (*fmt == '*') { 234 fmt++; 235 precision = getwidth(); 236 } else { 237 precision = -1; 238 fmt += strspn(fmt, SKIP2); 239 } 240 } else 241 precision = -1; 242 243 ch = *fmt; 244 if (!ch) { 245 warnx("%s: missing format character", start); 246 return 1; 247 } 248 249 /* 250 * null terminate format string to we can use it 251 * as an argument to printf. 252 */ 253 nextch = fmt[1]; 254 fmt[1] = 0; 255 256 switch (ch) { 257 258 case 'B': { 259 const char *p = conv_expand(getstr()); 260 261 if (p == NULL) 262 goto out; 263 *fmt = 's'; 264 PF(start, p); 265 if (error < 0) 266 goto out; 267 break; 268 } 269 case 'b': { 270 /* 271 * There has to be a better way to do this, 272 * but the string we generate might have 273 * embedded nulls 274 */ 275 static char *a, *t; 276 char *cp = getstr(); 277 278 /* Free on entry in case shell longjumped out */ 279 if (a != NULL) 280 free(a); 281 a = NULL; 282 if (t != NULL) 283 free(t); 284 t = NULL; 285 286 /* Count number of bytes we want to output */ 287 b_length = 0; 288 conv_escape_str(cp, b_count, 0); 289 t = malloc(b_length + 1); 290 if (t == NULL) 291 goto out; 292 (void)memset(t, 'x', b_length); 293 t[b_length] = 0; 294 295 /* Get printf to calculate the lengths */ 296 *fmt = 's'; 297 APF(&a, start, t); 298 if (error == -1) 299 goto out; 300 b_fmt = a; 301 302 /* Output leading spaces and data bytes */ 303 conv_escape_str(cp, b_output, 1); 304 305 /* Add any trailing spaces */ 306 printf("%s", b_fmt); 307 break; 308 } 309 case 'c': { 310 char p = getchr(); 311 312 PF(start, p); 313 if (error < 0) 314 goto out; 315 break; 316 } 317 case 's': { 318 char *p = getstr(); 319 320 PF(start, p); 321 if (error < 0) 322 goto out; 323 break; 324 } 325 case 'd': 326 case 'i': { 327 intmax_t p = getintmax(); 328 char *f = mklong(start, ch); 329 330 PF(f, p); 331 if (error < 0) 332 goto out; 333 break; 334 } 335 case 'o': 336 case 'u': 337 case 'x': 338 case 'X': { 339 uintmax_t p = (uintmax_t)getintmax(); 340 char *f = mklong(start, ch); 341 342 PF(f, p); 343 if (error < 0) 344 goto out; 345 break; 346 } 347 case 'a': 348 case 'A': 349 case 'e': 350 case 'E': 351 case 'f': 352 case 'F': 353 case 'g': 354 case 'G': { 355 double p = getdouble(); 356 357 PF(start, p); 358 if (error < 0) 359 goto out; 360 break; 361 } 362 case '%': 363 /* Don't ask, but this is useful ... */ 364 if (fieldwidth == 'N' && precision == 'B') 365 return 0; 366 /* FALLTHROUGH */ 367 default: 368 warnx("%s: invalid directive", start); 369 return 1; 370 } 371 *fmt++ = ch; 372 *fmt = nextch; 373 /* escape if a \c was encountered */ 374 if (rval & 0x100) 375 return rval & ~0x100; 376 } 377 } while (gargv != argv && *gargv); 378 379 return rval & ~0x100; 380 out: 381 warn("print failed"); 382 return 1; 383 } 384 385 /* helper functions for conv_escape_str */ 386 387 static void 388 /*ARGSUSED*/ 389 b_count(int ch) 390 { 391 b_length++; 392 } 393 394 /* Output one converted character for every 'x' in the 'format' */ 395 396 static void 397 b_output(int ch) 398 { 399 for (;;) { 400 switch (*b_fmt++) { 401 case 0: 402 b_fmt--; 403 return; 404 case ' ': 405 putchar(' '); 406 break; 407 default: 408 putchar(ch); 409 return; 410 } 411 } 412 } 413 414 415 /* 416 * Print SysV echo(1) style escape string 417 * Halts processing string if a \c escape is encountered. 418 */ 419 static void 420 conv_escape_str(char *str, void (*do_putchar)(int), int quiet) 421 { 422 int value; 423 int ch; 424 char c; 425 426 while ((ch = *str++) != '\0') { 427 if (ch != '\\') { 428 do_putchar(ch); 429 continue; 430 } 431 432 ch = *str++; 433 if (ch == 'c') { 434 /* \c as in SYSV echo - abort all processing.... */ 435 rval |= 0x100; 436 break; 437 } 438 439 /* 440 * %b string octal constants are not like those in C. 441 * They start with a \0, and are followed by 0, 1, 2, 442 * or 3 octal digits. 443 */ 444 if (ch == '0') { 445 int octnum = 0, i; 446 for (i = 0; i < 3; i++) { 447 if (!isdigit((unsigned char)*str) || *str > '7') 448 break; 449 octnum = (octnum << 3) | (*str++ - '0'); 450 } 451 do_putchar(octnum); 452 continue; 453 } 454 455 /* \[M][^|-]C as defined by vis(3) */ 456 if (ch == 'M' && *str == '-') { 457 do_putchar(0200 | str[1]); 458 str += 2; 459 continue; 460 } 461 if (ch == 'M' && *str == '^') { 462 str++; 463 value = 0200; 464 ch = '^'; 465 } else 466 value = 0; 467 if (ch == '^') { 468 ch = *str++; 469 if (ch == '?') 470 value |= 0177; 471 else 472 value |= ch & 037; 473 do_putchar(value); 474 continue; 475 } 476 477 /* Finally test for sequences valid in the format string */ 478 str = conv_escape(str - 1, &c, quiet); 479 do_putchar(c); 480 } 481 } 482 483 /* 484 * Print "standard" escape characters 485 */ 486 static char * 487 conv_escape(char *str, char *conv_ch, int quiet) 488 { 489 int value = 0; 490 char ch, *begin; 491 int c; 492 493 ch = *str++; 494 495 switch (ch) { 496 case '\0': 497 if (!quiet) 498 warnx("incomplete escape sequence"); 499 rval = 1; 500 value = '\\'; 501 --str; 502 break; 503 504 case '0': case '1': case '2': case '3': 505 case '4': case '5': case '6': case '7': 506 str--; 507 for (c = 3; c-- && isodigit(*str); str++) { 508 value <<= 3; 509 value += octtobin(*str); 510 } 511 break; 512 513 case 'x': 514 /* 515 * Hexadecimal character constants are not required to be 516 * supported (by SuS v1) because there is no consistent 517 * way to detect the end of the constant. 518 * Supporting 2 byte constants is a compromise. 519 */ 520 begin = str; 521 for (c = 2; c-- && isxdigit((unsigned char)*str); str++) { 522 value <<= 4; 523 value += hextobin(*str); 524 } 525 if (str == begin) { 526 if (!quiet) 527 warnx("\\x%s: missing hexadecimal number " 528 "in escape", begin); 529 rval = 1; 530 } 531 break; 532 533 case '\\': value = '\\'; break; /* backslash */ 534 case '\'': value = '\''; break; /* single quote */ 535 case '"': value = '"'; break; /* double quote */ 536 case 'a': value = '\a'; break; /* alert */ 537 case 'b': value = '\b'; break; /* backspace */ 538 case 'e': value = ESCAPE; break; /* escape */ 539 case 'E': value = ESCAPE; break; /* escape */ 540 case 'f': value = '\f'; break; /* form-feed */ 541 case 'n': value = '\n'; break; /* newline */ 542 case 'r': value = '\r'; break; /* carriage-return */ 543 case 't': value = '\t'; break; /* tab */ 544 case 'v': value = '\v'; break; /* vertical-tab */ 545 546 default: 547 if (!quiet) 548 warnx("unknown escape sequence `\\%c'", ch); 549 rval = 1; 550 value = ch; 551 break; 552 } 553 554 *conv_ch = (char)value; 555 return str; 556 } 557 558 /* expand a string so that everything is printable */ 559 560 static char * 561 conv_expand(const char *str) 562 { 563 static char *conv_str; 564 char *cp; 565 char ch; 566 567 if (conv_str) 568 free(conv_str); 569 /* get a buffer that is definitely large enough.... */ 570 conv_str = malloc(4 * strlen(str) + 1); 571 if (!conv_str) 572 return NULL; 573 cp = conv_str; 574 575 while ((ch = *(const char *)str++) != '\0') { 576 switch (ch) { 577 /* Use C escapes for expected control characters */ 578 case '\\': ch = '\\'; break; /* backslash */ 579 case '\'': ch = '\''; break; /* single quote */ 580 case '"': ch = '"'; break; /* double quote */ 581 case '\a': ch = 'a'; break; /* alert */ 582 case '\b': ch = 'b'; break; /* backspace */ 583 case ESCAPE: ch = 'e'; break; /* escape */ 584 case '\f': ch = 'f'; break; /* form-feed */ 585 case '\n': ch = 'n'; break; /* newline */ 586 case '\r': ch = 'r'; break; /* carriage-return */ 587 case '\t': ch = 't'; break; /* tab */ 588 case '\v': ch = 'v'; break; /* vertical-tab */ 589 default: 590 /* Copy anything printable */ 591 if (isprint((unsigned char)ch)) { 592 *cp++ = ch; 593 continue; 594 } 595 /* Use vis(3) encodings for the rest */ 596 *cp++ = '\\'; 597 if (ch & 0200) { 598 *cp++ = 'M'; 599 ch &= (char)~0200; 600 } 601 if (ch == 0177) { 602 *cp++ = '^'; 603 *cp++ = '?'; 604 continue; 605 } 606 if (ch < 040) { 607 *cp++ = '^'; 608 *cp++ = ch | 0100; 609 continue; 610 } 611 *cp++ = '-'; 612 *cp++ = ch; 613 continue; 614 } 615 *cp++ = '\\'; 616 *cp++ = ch; 617 } 618 619 *cp = 0; 620 return conv_str; 621 } 622 623 static char * 624 mklong(const char *str, char ch) 625 { 626 static char copy[64]; 627 size_t len; 628 629 len = strlen(str) + 2; 630 if (len > sizeof copy) { 631 warnx("format %s too complex", str); 632 len = 4; 633 } 634 (void)memmove(copy, str, len - 3); 635 copy[len - 3] = 'j'; 636 copy[len - 2] = ch; 637 copy[len - 1] = '\0'; 638 return copy; 639 } 640 641 static char 642 getchr(void) 643 { 644 if (!*gargv) 645 return 0; 646 return **gargv++; 647 } 648 649 static char * 650 getstr(void) 651 { 652 static char empty[] = ""; 653 if (!*gargv) 654 return empty; 655 return *gargv++; 656 } 657 658 static int 659 getwidth(void) 660 { 661 unsigned long val; 662 char *s, *ep; 663 664 s = *gargv; 665 if (s == NULL) 666 return 0; 667 gargv++; 668 669 errno = 0; 670 val = strtoul(s, &ep, 0); 671 check_conversion(s, ep); 672 673 /* Arbitrarily 'restrict' field widths to 1Mbyte */ 674 if (val > 1 << 20) { 675 warnx("%s: invalid field width", s); 676 return 0; 677 } 678 679 return (int)val; 680 } 681 682 static intmax_t 683 getintmax(void) 684 { 685 intmax_t val; 686 char *cp, *ep; 687 688 cp = *gargv; 689 if (cp == NULL) 690 return 0; 691 gargv++; 692 693 if (*cp == '\"' || *cp == '\'') 694 return *(cp + 1); 695 696 errno = 0; 697 val = strtoimax(cp, &ep, 0); 698 check_conversion(cp, ep); 699 return val; 700 } 701 702 static double 703 getdouble(void) 704 { 705 double val; 706 char *ep; 707 708 if (!*gargv) 709 return 0.0; 710 711 if (**gargv == '\"' || **gargv == '\'') 712 return (double) *((*gargv++)+1); 713 714 errno = 0; 715 val = strtod(*gargv, &ep); 716 check_conversion(*gargv++, ep); 717 return val; 718 } 719 720 static void 721 check_conversion(const char *s, const char *ep) 722 { 723 if (*ep) { 724 if (ep == s) 725 warnx("%s: expected numeric value", s); 726 else 727 warnx("%s: not completely converted", s); 728 rval = 1; 729 } else if (errno == ERANGE) { 730 warnx("%s: %s", s, strerror(ERANGE)); 731 rval = 1; 732 } 733 } 734 735 static void 736 usage(void) 737 { 738 (void)fprintf(stderr, "Usage: %s format [arg ...]\n", getprogname()); 739 } 740