1 /* $NetBSD: printf.c,v 1.50 2019/07/22 17:34:31 kre 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.50 2019/07/22 17:34:31 kre 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 #ifdef main 122 int main(int, char *[]); 123 #endif 124 125 int 126 main(int argc, char *argv[]) 127 { 128 char *fmt, *start; 129 int fieldwidth, precision; 130 char nextch; 131 char *format; 132 char ch; 133 int error; 134 135 #if !defined(SHELL) && !defined(BUILTIN) 136 (void)setlocale (LC_ALL, ""); 137 #endif 138 139 rval = 0; /* clear for builtin versions (avoid holdover) */ 140 141 /* 142 * printf does not comply with Posix XBD 12.2 - there are no opts, 143 * not even the -- end of options marker. Do not run getoot(). 144 */ 145 if (argc > 2 && strchr(argv[1], '%') == NULL) { 146 int o; 147 148 /* 149 * except that if there are multiple args and 150 * the first (the nominal format) contains no '%' 151 * conversions (which we will approximate as no '%' 152 * characters at all, conversions or not) then the 153 * results are unspecified, and we can do what we 154 * like. So in that case, for some backward compat 155 * to scripts which (stupidly) do: 156 * printf -- format args 157 * process this case the old way. 158 */ 159 160 while ((o = getopt(argc, argv, "")) != -1) { 161 switch (o) { 162 case '?': 163 default: 164 usage(); 165 return 1; 166 } 167 } 168 argc -= optind; 169 argv += optind; 170 } else { 171 argc -= 1; /* drop argv[0] (the program name) */ 172 argv += 1; 173 } 174 175 if (argc < 1) { 176 usage(); 177 return 1; 178 } 179 180 format = *argv; 181 gargv = ++argv; 182 183 #define SKIP1 "#-+ 0'" 184 #define SKIP2 "0123456789" 185 do { 186 /* 187 * Basic algorithm is to scan the format string for conversion 188 * specifications -- once one is found, find out if the field 189 * width or precision is a '*'; if it is, gather up value. 190 * Note, format strings are reused as necessary to use up the 191 * provided arguments, arguments of zero/null string are 192 * provided to use up the format string. 193 */ 194 195 /* find next format specification */ 196 for (fmt = format; (ch = *fmt++) != '\0';) { 197 if (ch == '\\') { 198 char c_ch; 199 fmt = conv_escape(fmt, &c_ch, 0); 200 putchar(c_ch); 201 continue; 202 } 203 if (ch != '%' || (*fmt == '%' && ++fmt)) { 204 (void)putchar(ch); 205 continue; 206 } 207 208 /* 209 * Ok - we've found a format specification, 210 * Save its address for a later printf(). 211 */ 212 start = fmt - 1; 213 214 /* skip to field width */ 215 fmt += strspn(fmt, SKIP1); 216 if (*fmt == '*') { 217 fmt++; 218 fieldwidth = getwidth(); 219 } else { 220 fieldwidth = -1; 221 222 /* skip to possible '.' for precision */ 223 fmt += strspn(fmt, SKIP2); 224 } 225 226 if (*fmt == '.') { 227 /* get following precision */ 228 fmt++; 229 if (*fmt == '*') { 230 fmt++; 231 precision = getwidth(); 232 } else { 233 precision = -1; 234 fmt += strspn(fmt, SKIP2); 235 } 236 } else 237 precision = -1; 238 239 ch = *fmt; 240 if (!ch) { 241 warnx("%s: missing format character", start); 242 return 1; 243 } 244 245 /* 246 * null terminate format string to we can use it 247 * as an argument to printf. 248 */ 249 nextch = fmt[1]; 250 fmt[1] = 0; 251 252 switch (ch) { 253 254 case 'B': { 255 const char *p = conv_expand(getstr()); 256 257 if (p == NULL) 258 goto out; 259 *fmt = 's'; 260 PF(start, p); 261 if (error < 0) 262 goto out; 263 break; 264 } 265 case 'b': { 266 /* 267 * There has to be a better way to do this, 268 * but the string we generate might have 269 * embedded nulls 270 */ 271 static char *a, *t; 272 char *cp = getstr(); 273 274 /* Free on entry in case shell longjumped out */ 275 if (a != NULL) 276 free(a); 277 a = NULL; 278 if (t != NULL) 279 free(t); 280 t = NULL; 281 282 /* Count number of bytes we want to output */ 283 b_length = 0; 284 conv_escape_str(cp, b_count, 0); 285 t = malloc(b_length + 1); 286 if (t == NULL) 287 goto out; 288 (void)memset(t, 'x', b_length); 289 t[b_length] = 0; 290 291 /* Get printf to calculate the lengths */ 292 *fmt = 's'; 293 APF(&a, start, t); 294 if (error == -1) 295 goto out; 296 b_fmt = a; 297 298 /* Output leading spaces and data bytes */ 299 conv_escape_str(cp, b_output, 1); 300 301 /* Add any trailing spaces */ 302 printf("%s", b_fmt); 303 break; 304 } 305 case 'c': { 306 char p = getchr(); 307 308 PF(start, p); 309 if (error < 0) 310 goto out; 311 break; 312 } 313 case 's': { 314 char *p = getstr(); 315 316 PF(start, p); 317 if (error < 0) 318 goto out; 319 break; 320 } 321 case 'd': 322 case 'i': { 323 intmax_t p = getintmax(); 324 char *f = mklong(start, ch); 325 326 PF(f, p); 327 if (error < 0) 328 goto out; 329 break; 330 } 331 case 'o': 332 case 'u': 333 case 'x': 334 case 'X': { 335 uintmax_t p = (uintmax_t)getintmax(); 336 char *f = mklong(start, ch); 337 338 PF(f, p); 339 if (error < 0) 340 goto out; 341 break; 342 } 343 case 'a': 344 case 'A': 345 case 'e': 346 case 'E': 347 case 'f': 348 case 'F': 349 case 'g': 350 case 'G': { 351 double p = getdouble(); 352 353 PF(start, p); 354 if (error < 0) 355 goto out; 356 break; 357 } 358 case '%': 359 /* Don't ask, but this is useful ... */ 360 if (fieldwidth == 'N' && precision == 'B') 361 return 0; 362 /* FALLTHROUGH */ 363 default: 364 warnx("%s: invalid directive", start); 365 return 1; 366 } 367 *fmt++ = ch; 368 *fmt = nextch; 369 /* escape if a \c was encountered */ 370 if (rval & 0x100) 371 return rval & ~0x100; 372 } 373 } while (gargv != argv && *gargv); 374 375 return rval & ~0x100; 376 out: 377 warn("print failed"); 378 return 1; 379 } 380 381 /* helper functions for conv_escape_str */ 382 383 static void 384 /*ARGSUSED*/ 385 b_count(int ch) 386 { 387 b_length++; 388 } 389 390 /* Output one converted character for every 'x' in the 'format' */ 391 392 static void 393 b_output(int ch) 394 { 395 for (;;) { 396 switch (*b_fmt++) { 397 case 0: 398 b_fmt--; 399 return; 400 case ' ': 401 putchar(' '); 402 break; 403 default: 404 putchar(ch); 405 return; 406 } 407 } 408 } 409 410 411 /* 412 * Print SysV echo(1) style escape string 413 * Halts processing string if a \c escape is encountered. 414 */ 415 static void 416 conv_escape_str(char *str, void (*do_putchar)(int), int quiet) 417 { 418 int value; 419 int ch; 420 char c; 421 422 while ((ch = *str++) != '\0') { 423 if (ch != '\\') { 424 do_putchar(ch); 425 continue; 426 } 427 428 ch = *str++; 429 if (ch == 'c') { 430 /* \c as in SYSV echo - abort all processing.... */ 431 rval |= 0x100; 432 break; 433 } 434 435 /* 436 * %b string octal constants are not like those in C. 437 * They start with a \0, and are followed by 0, 1, 2, 438 * or 3 octal digits. 439 */ 440 if (ch == '0') { 441 int octnum = 0, i; 442 for (i = 0; i < 3; i++) { 443 if (!isdigit((unsigned char)*str) || *str > '7') 444 break; 445 octnum = (octnum << 3) | (*str++ - '0'); 446 } 447 do_putchar(octnum); 448 continue; 449 } 450 451 /* \[M][^|-]C as defined by vis(3) */ 452 if (ch == 'M' && *str == '-') { 453 do_putchar(0200 | str[1]); 454 str += 2; 455 continue; 456 } 457 if (ch == 'M' && *str == '^') { 458 str++; 459 value = 0200; 460 ch = '^'; 461 } else 462 value = 0; 463 if (ch == '^') { 464 ch = *str++; 465 if (ch == '?') 466 value |= 0177; 467 else 468 value |= ch & 037; 469 do_putchar(value); 470 continue; 471 } 472 473 /* Finally test for sequences valid in the format string */ 474 str = conv_escape(str - 1, &c, quiet); 475 do_putchar(c); 476 } 477 } 478 479 /* 480 * Print "standard" escape characters 481 */ 482 static char * 483 conv_escape(char *str, char *conv_ch, int quiet) 484 { 485 char value; 486 char ch; 487 char num_buf[4], *num_end; 488 489 ch = *str++; 490 491 switch (ch) { 492 case '\0': 493 if (!quiet) 494 warnx("incomplete escape sequence"); 495 rval = 1; 496 value = '\\'; 497 --str; 498 break; 499 500 case '0': case '1': case '2': case '3': 501 case '4': case '5': case '6': case '7': 502 num_buf[0] = ch; 503 ch = str[0]; 504 num_buf[1] = ch; 505 num_buf[2] = (char)(ch != '\0' ? str[1] : '\0'); 506 num_buf[3] = '\0'; 507 value = (char)strtoul(num_buf, &num_end, 8); 508 str += num_end - (num_buf + 1); 509 break; 510 511 case 'x': 512 /* 513 * Hexadecimal character constants are not required to be 514 * supported (by SuS v1) because there is no consistent 515 * way to detect the end of the constant. 516 * Supporting 2 byte constants is a compromise. 517 */ 518 ch = str[0]; 519 num_buf[0] = ch; 520 num_buf[1] = (char)(ch != '\0' ? str[1] : '\0'); 521 num_buf[2] = '\0'; 522 value = (char)strtoul(num_buf, &num_end, 16); 523 str += num_end - num_buf; 524 break; 525 526 case '\\': value = '\\'; break; /* backslash */ 527 case '\'': value = '\''; break; /* single quote */ 528 case '"': value = '"'; break; /* double quote */ 529 case 'a': value = '\a'; break; /* alert */ 530 case 'b': value = '\b'; break; /* backspace */ 531 case 'e': value = ESCAPE; break; /* escape */ 532 case 'E': value = ESCAPE; break; /* escape */ 533 case 'f': value = '\f'; break; /* form-feed */ 534 case 'n': value = '\n'; break; /* newline */ 535 case 'r': value = '\r'; break; /* carriage-return */ 536 case 't': value = '\t'; break; /* tab */ 537 case 'v': value = '\v'; break; /* vertical-tab */ 538 539 default: 540 if (!quiet) 541 warnx("unknown escape sequence `\\%c'", ch); 542 rval = 1; 543 value = ch; 544 break; 545 } 546 547 *conv_ch = value; 548 return str; 549 } 550 551 /* expand a string so that everything is printable */ 552 553 static char * 554 conv_expand(const char *str) 555 { 556 static char *conv_str; 557 char *cp; 558 char ch; 559 560 if (conv_str) 561 free(conv_str); 562 /* get a buffer that is definitely large enough.... */ 563 conv_str = malloc(4 * strlen(str) + 1); 564 if (!conv_str) 565 return NULL; 566 cp = conv_str; 567 568 while ((ch = *(const char *)str++) != '\0') { 569 switch (ch) { 570 /* Use C escapes for expected control characters */ 571 case '\\': ch = '\\'; break; /* backslash */ 572 case '\'': ch = '\''; break; /* single quote */ 573 case '"': ch = '"'; break; /* double quote */ 574 case '\a': ch = 'a'; break; /* alert */ 575 case '\b': ch = 'b'; break; /* backspace */ 576 case ESCAPE: ch = 'e'; break; /* escape */ 577 case '\f': ch = 'f'; break; /* form-feed */ 578 case '\n': ch = 'n'; break; /* newline */ 579 case '\r': ch = 'r'; break; /* carriage-return */ 580 case '\t': ch = 't'; break; /* tab */ 581 case '\v': ch = 'v'; break; /* vertical-tab */ 582 default: 583 /* Copy anything printable */ 584 if (isprint((unsigned char)ch)) { 585 *cp++ = ch; 586 continue; 587 } 588 /* Use vis(3) encodings for the rest */ 589 *cp++ = '\\'; 590 if (ch & 0200) { 591 *cp++ = 'M'; 592 ch &= (char)~0200; 593 } 594 if (ch == 0177) { 595 *cp++ = '^'; 596 *cp++ = '?'; 597 continue; 598 } 599 if (ch < 040) { 600 *cp++ = '^'; 601 *cp++ = ch | 0100; 602 continue; 603 } 604 *cp++ = '-'; 605 *cp++ = ch; 606 continue; 607 } 608 *cp++ = '\\'; 609 *cp++ = ch; 610 } 611 612 *cp = 0; 613 return conv_str; 614 } 615 616 static char * 617 mklong(const char *str, char ch) 618 { 619 static char copy[64]; 620 size_t len; 621 622 len = strlen(str) + 2; 623 if (len > sizeof copy) { 624 warnx("format %s too complex", str); 625 len = 4; 626 } 627 (void)memmove(copy, str, len - 3); 628 copy[len - 3] = 'j'; 629 copy[len - 2] = ch; 630 copy[len - 1] = '\0'; 631 return copy; 632 } 633 634 static char 635 getchr(void) 636 { 637 if (!*gargv) 638 return 0; 639 return **gargv++; 640 } 641 642 static char * 643 getstr(void) 644 { 645 static char empty[] = ""; 646 if (!*gargv) 647 return empty; 648 return *gargv++; 649 } 650 651 static int 652 getwidth(void) 653 { 654 unsigned long val; 655 char *s, *ep; 656 657 s = *gargv; 658 if (s == NULL) 659 return 0; 660 gargv++; 661 662 errno = 0; 663 val = strtoul(s, &ep, 0); 664 check_conversion(s, ep); 665 666 /* Arbitrarily 'restrict' field widths to 1Mbyte */ 667 if (val > 1 << 20) { 668 warnx("%s: invalid field width", s); 669 return 0; 670 } 671 672 return (int)val; 673 } 674 675 static intmax_t 676 getintmax(void) 677 { 678 intmax_t val; 679 char *cp, *ep; 680 681 cp = *gargv; 682 if (cp == NULL) 683 return 0; 684 gargv++; 685 686 if (*cp == '\"' || *cp == '\'') 687 return *(cp + 1); 688 689 errno = 0; 690 val = strtoimax(cp, &ep, 0); 691 check_conversion(cp, ep); 692 return val; 693 } 694 695 static double 696 getdouble(void) 697 { 698 double val; 699 char *ep; 700 701 if (!*gargv) 702 return 0.0; 703 704 if (**gargv == '\"' || **gargv == '\'') 705 return (double) *((*gargv++)+1); 706 707 errno = 0; 708 val = strtod(*gargv, &ep); 709 check_conversion(*gargv++, ep); 710 return val; 711 } 712 713 static void 714 check_conversion(const char *s, const char *ep) 715 { 716 if (*ep) { 717 if (ep == s) 718 warnx("%s: expected numeric value", s); 719 else 720 warnx("%s: not completely converted", s); 721 rval = 1; 722 } else if (errno == ERANGE) { 723 warnx("%s: %s", s, strerror(ERANGE)); 724 rval = 1; 725 } 726 } 727 728 static void 729 usage(void) 730 { 731 (void)fprintf(stderr, "Usage: %s format [arg ...]\n", getprogname()); 732 } 733