1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* 28 * Copyright 1986, 1994 by Mortice Kern Systems Inc. All rights reserved. 29 */ 30 31 #pragma ident "%Z%%M% %I% %E% SMI" 32 33 /* 34 * awk -- process input files, field extraction, output 35 * 36 * Based on MKS awk(1) ported to be /usr/xpg4/bin/awk with POSIX/XCU4 changes 37 */ 38 39 #include "awk.h" 40 #include "y.tab.h" 41 42 static FILE *awkinfp; /* Input file pointer */ 43 static int reclen; /* Length of last record */ 44 static int exstat; /* Exit status */ 45 46 static FILE *openfile(NODE *np, int flag, int fatal); 47 static FILE *newfile(void); 48 static NODE *nextarg(NODE **npp); 49 static void adjust_buf(wchar_t **, int *, wchar_t **, char *, size_t); 50 static void awk_putwc(wchar_t, FILE *); 51 52 /* 53 * mainline for awk execution 54 */ 55 void 56 awk() 57 { 58 running = 1; 59 dobegin(); 60 while (nextrecord(linebuf, awkinfp) > 0) 61 execute(yytree); 62 doend(exstat); 63 } 64 65 /* 66 * "cp" is the buffer to fill. There is a special case if this buffer is 67 * "linebuf" ($0) 68 * Return 1 if OK, zero on EOF, -1 on error. 69 */ 70 int 71 nextrecord(wchar_t *cp, FILE *fp) 72 { 73 wchar_t *ep = cp; 74 75 nextfile: 76 if (fp == FNULL && (fp = newfile()) == FNULL) 77 return (0); 78 if ((*awkrecord)(ep, NLINE, fp) == NULL) { 79 if (fp == awkinfp) { 80 if (fp != stdin) 81 (void) fclose(awkinfp); 82 awkinfp = fp = FNULL; 83 goto nextfile; 84 } 85 if (ferror(fp)) 86 return (-1); 87 return (0); 88 } 89 if (fp == awkinfp) { 90 if (varNR->n_flags & FINT) 91 ++varNR->n_int; 92 else 93 (void) exprreduce(incNR); 94 if (varFNR->n_flags & FINT) 95 ++varFNR->n_int; 96 else 97 (void) exprreduce(incFNR); 98 } 99 if (cp == linebuf) { 100 lbuflen = reclen; 101 splitdone = 0; 102 if (needsplit) 103 fieldsplit(); 104 } 105 /* if record length is too long then bail out */ 106 if (reclen > NLINE - 2) { 107 awkerr(gettext("Record too long (LIMIT: %d bytes)"), 108 NLINE - 1); 109 /* Not Reached */ 110 } 111 return (1); 112 } 113 114 /* 115 * isclvar() 116 * 117 * Returns 1 if the input string, arg, is a variable assignment, 118 * otherwise returns 0. 119 * 120 * An argument to awk can be either a pathname of a file, or a variable 121 * assignment. An operand that begins with an undersore or alphabetic 122 * character from the portable character set, followed by a sequence of 123 * underscores, digits, and alphabetics from the portable character set, 124 * followed by the '=' character, shall specify a variable assignment 125 * rather than a pathname. 126 */ 127 int 128 isclvar(wchar_t *arg) 129 { 130 wchar_t *tmpptr = arg; 131 132 if (tmpptr != NULL) { 133 134 /* Begins with an underscore or alphabetic character */ 135 if (iswalpha(*tmpptr) || *tmpptr == '_') { 136 137 /* 138 * followed by a sequence of underscores, digits, 139 * and alphabetics 140 */ 141 for (tmpptr++; *tmpptr; tmpptr++) { 142 if (!(isalnum(*tmpptr) || (*tmpptr == '_'))) { 143 break; 144 } 145 } 146 return (*tmpptr == '='); 147 } 148 } 149 150 return (0); 151 } 152 153 /* 154 * Return the next file from the command line. 155 * Return FNULL when no more files. 156 * Sets awkinfp variable to the new current input file. 157 */ 158 static FILE * 159 newfile() 160 { 161 static int argindex = 1; 162 static int filedone; 163 wchar_t *ap; 164 int argc; 165 wchar_t *arg; 166 extern void strescape(wchar_t *); 167 168 argc = (int)exprint(varARGC); 169 for (;;) { 170 if (argindex >= argc) { 171 if (filedone) 172 return (FNULL); 173 ++filedone; 174 awkinfp = stdin; 175 arg = M_MB_L("-"); 176 break; 177 } 178 constant->n_int = argindex++; 179 arg = (wchar_t *)exprstring(ARGVsubi); 180 /* 181 * If the argument contains a '=', determine if the 182 * argument needs to be treated as a variable assignment 183 * or as the pathname of a file. 184 */ 185 if (((ap = wcschr(arg, '=')) != NULL) && isclvar(arg)) { 186 *ap = '\0'; 187 strescape(ap+1); 188 strassign(vlook(arg), linebuf, FALLOC|FSENSE, 189 wcslen(linebuf)); 190 *ap = '='; 191 continue; 192 } 193 if (arg[0] == '\0') 194 continue; 195 ++filedone; 196 if (arg[0] == '-' && arg[1] == '\0') { 197 awkinfp = stdin; 198 break; 199 } 200 if ((awkinfp = fopen(mbunconvert(arg), r)) == FNULL) { 201 (void) fprintf(stderr, gettext("input file \"%s\""), 202 mbunconvert(arg)); 203 exstat = 1; 204 continue; 205 } 206 break; 207 } 208 strassign(varFILENAME, arg, FALLOC, wcslen(arg)); 209 if (varFNR->n_flags & FINT) 210 varFNR->n_int = 0; 211 else 212 (void) exprreduce(clrFNR); 213 return (awkinfp); 214 } 215 216 /* 217 * Default record reading code 218 * Uses fgets for potential speedups found in some (e.g. MKS) 219 * stdio packages. 220 */ 221 wchar_t * 222 defrecord(wchar_t *bp, int lim, FILE *fp) 223 { 224 wchar_t *endp; 225 226 if (fgetws(bp, lim, fp) == NULL) { 227 *bp = '\0'; 228 return (NULL); 229 } 230 /* 231 * XXXX 232 * switch (fgetws(bp, lim, fp)) { 233 * case M_FGETS_EOF: 234 * *bp = '\0'; 235 * return (NULL); 236 * case M_FGETS_BINARY: 237 * awkerr(gettext("file is binary")); 238 * case M_FGETS_LONG: 239 * awkerr(gettext("line too long: limit %d"), 240 * lim); 241 * case M_FGETS_ERROR: 242 * awkperr(gettext("error reading file")); 243 * } 244 */ 245 246 if (*(endp = (bp + (reclen = wcslen(bp))-1)) == '\n') { 247 *endp = '\0'; 248 reclen--; 249 } 250 return (bp); 251 } 252 253 /* 254 * Read a record separated by one character in the RS. 255 * Compatible calling sequence with fgets, but don't include 256 * record separator character in string. 257 */ 258 wchar_t * 259 charrecord(wchar_t *abp, int alim, FILE *fp) 260 { 261 wchar_t *bp; 262 wint_t c; 263 int limit = alim; 264 wint_t endc; 265 266 bp = abp; 267 endc = *(wchar_t *)varRS->n_string; 268 while (--limit > 0 && (c = getwc(fp)) != endc && c != WEOF) 269 *bp++ = c; 270 *bp = '\0'; 271 reclen = bp-abp; 272 return (c == WEOF && bp == abp ? NULL : abp); 273 } 274 275 /* 276 * Special routine for multiple line records. 277 */ 278 wchar_t * 279 multirecord(wchar_t *abp, int limit, FILE *fp) 280 { 281 wchar_t *bp; 282 int c; 283 284 while ((c = getwc(fp)) == '\n') 285 ; 286 bp = abp; 287 if (c != WEOF) do { 288 if (--limit == 0) 289 break; 290 if (c == '\n' && bp[-1] == '\n') 291 break; 292 293 *bp++ = c; 294 } while ((c = getwc(fp)) != WEOF); 295 *bp = '\0'; 296 if (bp > abp) 297 *--bp = '\0'; 298 reclen = bp-abp; 299 return (c == WEOF && bp == abp ? NULL : abp); 300 } 301 302 /* 303 * Look for fields separated by spaces, tabs or newlines. 304 * Extract the next field, given pointer to start address. 305 * Return pointer to beginning of field or NULL. 306 * Reset end of field reference, which is the beginning of the 307 * next field. 308 */ 309 wchar_t * 310 whitefield(wchar_t **endp) 311 { 312 wchar_t *sp; 313 wchar_t *ep; 314 315 sp = *endp; 316 while (*sp == ' ' || *sp == '\t' || *sp == '\n') 317 ++sp; 318 if (*sp == '\0') 319 return (NULL); 320 for (ep = sp; *ep != ' ' && *ep != '\0' && *ep != '\t' && 321 *ep != '\n'; ++ep) 322 ; 323 *endp = ep; 324 return (sp); 325 } 326 327 /* 328 * Look for fields separated by non-whitespace characters. 329 * Same calling sequence as whitefield(). 330 */ 331 wchar_t * 332 blackfield(wchar_t **endp) 333 { 334 wchar_t *cp; 335 int endc; 336 337 endc = *(wchar_t *)varFS->n_string; 338 cp = *endp; 339 if (*cp == '\0') 340 return (NULL); 341 if (*cp == endc && fcount != 0) 342 cp++; 343 if ((*endp = wcschr(cp, endc)) == NULL) 344 *endp = wcschr(cp, '\0'); 345 return (cp); 346 } 347 348 /* 349 * This field separation routine uses the same logic as 350 * blackfield but uses a regular expression to separate 351 * the fields. 352 */ 353 wchar_t * 354 refield(wchar_t **endpp) 355 { 356 wchar_t *cp, *start; 357 int flags; 358 static REGWMATCH_T match[10]; 359 int result; 360 361 cp = *endpp; 362 if (*cp == '\0') { 363 match[0].rm_ep = NULL; 364 return (NULL); 365 } 366 if (match[0].rm_ep != NULL) { 367 flags = REG_NOTBOL; 368 cp = (wchar_t *)match[0].rm_ep; 369 } else 370 flags = 0; 371 start = cp; 372 again: 373 switch ((result = REGWEXEC(resep, cp, 10, match, flags))) { 374 case REG_OK: 375 /* 376 * Check to see if a null string was matched. If this is the 377 * case, then move the current pointer beyond this position. 378 */ 379 if (match[0].rm_sp == match[0].rm_ep) { 380 cp = (wchar_t *)match[0].rm_sp; 381 if (*cp++ != '\0') { 382 goto again; 383 } 384 } 385 *endpp = (wchar_t *)match[0].rm_sp; 386 break; 387 case REG_NOMATCH: 388 match[0].rm_ep = NULL; 389 *endpp = wcschr(cp, '\0'); 390 break; 391 default: 392 (void) regerror(result, resep, (char *)linebuf, 393 sizeof (linebuf)); 394 awkerr(gettext("error splitting record: %s"), 395 (char *)linebuf); 396 } 397 return (start); 398 } 399 400 /* 401 * do begin processing 402 */ 403 void 404 dobegin() 405 { 406 /* 407 * Free all keyword nodes to save space. 408 */ 409 { 410 NODE *np; 411 int nbuck; 412 NODE *knp; 413 414 np = NNULL; 415 nbuck = 0; 416 while ((knp = symwalk(&nbuck, &np)) != NNULL) 417 if (knp->n_type == KEYWORD) 418 delsymtab(knp, 1); 419 } 420 /* 421 * Copy ENVIRON array only if needed. 422 * Note the convoluted work to assign to an array 423 * and that the temporary nodes will be freed by 424 * freetemps() because we are "running". 425 */ 426 if (needenviron) { 427 char **app; 428 wchar_t *name, *value; 429 NODE *namep = stringnode(_null, FSTATIC, 0); 430 NODE *valuep = stringnode(_null, FSTATIC, 0); 431 NODE *ENVsubname = node(INDEX, varENVIRON, namep); 432 extern char **environ; 433 434 /* (void) m_setenv(); XXX what's this do? */ 435 for (app = environ; *app != NULL; /* empty */) { 436 name = mbstowcsdup(*app++); 437 438 if ((value = wcschr(name, '=')) != NULL) { 439 *value++ = '\0'; 440 valuep->n_strlen = wcslen(value); 441 valuep->n_string = value; 442 } else { 443 valuep->n_strlen = 0; 444 valuep->n_string = _null; 445 } 446 namep->n_strlen = wcslen(namep->n_string = name); 447 (void) assign(ENVsubname, valuep); 448 if (value != NULL) 449 value[-1] = '='; 450 } 451 } 452 phase = BEGIN; 453 execute(yytree); 454 phase = 0; 455 if (npattern == 0) 456 doend(0); 457 /* 458 * Delete all pattern/action rules that are BEGIN at this 459 * point to save space. 460 * NOTE: this is not yet implemented. 461 */ 462 } 463 464 /* 465 * Do end processing. 466 * Exit with a status 467 */ 468 void 469 doend(int s) 470 { 471 OFILE *op; 472 473 if (phase != END) { 474 phase = END; 475 awkinfp = stdin; 476 execute(yytree); 477 } 478 for (op = &ofiles[0]; op < &ofiles[NIOSTREAM]; op++) 479 if (op->f_fp != FNULL) 480 awkclose(op); 481 if (awkinfp == stdin) 482 (void) fflush(awkinfp); 483 exit(s); 484 } 485 486 /* 487 * Print statement. 488 */ 489 void 490 s_print(NODE *np) 491 { 492 FILE *fp; 493 NODE *listp; 494 char *ofs; 495 int notfirst = 0; 496 497 fp = openfile(np->n_right, 1, 1); 498 if (np->n_left == NNULL) 499 (void) fputs(mbunconvert(linebuf), fp); 500 else { 501 ofs = wcstombsdup((isstring(varOFS->n_flags)) ? 502 (wchar_t *)varOFS->n_string : 503 (wchar_t *)exprstring(varOFS)); 504 listp = np->n_left; 505 while ((np = getlist(&listp)) != NNULL) { 506 if (notfirst++) 507 (void) fputs(ofs, fp); 508 np = exprreduce(np); 509 if (np->n_flags & FINT) 510 (void) fprintf(fp, "%lld", (INT)np->n_int); 511 else if (isstring(np->n_flags)) 512 (void) fprintf(fp, "%S", np->n_string); 513 else 514 (void) fprintf(fp, 515 mbunconvert((wchar_t *)exprstring(varOFMT)), 516 (double)np->n_real); 517 } 518 free(ofs); 519 } 520 (void) fputs(mbunconvert(isstring(varORS->n_flags) ? 521 (wchar_t *)varORS->n_string : (wchar_t *)exprstring(varORS)), 522 fp); 523 if (ferror(fp)) 524 awkperr("error on print"); 525 } 526 527 /* 528 * printf statement. 529 */ 530 void 531 s_prf(NODE *np) 532 { 533 FILE *fp; 534 535 fp = openfile(np->n_right, 1, 1); 536 (void) xprintf(np->n_left, fp, (wchar_t **)NULL); 537 if (ferror(fp)) 538 awkperr("error on printf"); 539 } 540 541 /* 542 * Get next input line. 543 * Read into variable on left of node (or $0 if NULL). 544 * Read from pipe or file on right of node (or from regular 545 * input if NULL). 546 * This is an oddball inasmuch as it is a function 547 * but parses more like the keywords print, etc. 548 */ 549 NODE * 550 f_getline(NODE *np) 551 { 552 wchar_t *cp; 553 INT ret; 554 FILE *fp; 555 size_t len; 556 557 if (np->n_right == NULL && phase == END) { 558 /* Pretend we've reached end of (the non-existant) file. */ 559 return (intnode(0)); 560 } 561 562 if ((fp = openfile(np->n_right, 0, 0)) != FNULL) { 563 if (np->n_left == NNULL) { 564 ret = nextrecord(linebuf, fp); 565 } else { 566 cp = emalloc(NLINE * sizeof (wchar_t)); 567 ret = nextrecord(cp, fp); 568 np = np->n_left; 569 len = wcslen(cp); 570 cp = erealloc(cp, (len+1)*sizeof (wchar_t)); 571 if (isleaf(np->n_flags)) { 572 if (np->n_type == PARM) 573 np = np->n_next; 574 strassign(np, cp, FNOALLOC, len); 575 } else 576 (void) assign(np, stringnode(cp, 577 FNOALLOC, len)); 578 } 579 } else 580 ret = -1; 581 return (intnode(ret)); 582 } 583 584 /* 585 * Open a file. Flag is non-zero for output. 586 */ 587 static FILE * 588 openfile(NODE *np, int flag, int fatal) 589 { 590 OFILE *op; 591 char *cp; 592 FILE *fp; 593 int type; 594 OFILE *fop; 595 596 if (np == NNULL) { 597 if (flag) 598 return (stdout); 599 if (awkinfp == FNULL) 600 awkinfp = newfile(); 601 return (awkinfp); 602 } 603 if ((type = np->n_type) == APPEND) 604 type = WRITE; 605 cp = mbunconvert(exprstring(np->n_left)); 606 fop = (OFILE *)NULL; 607 for (op = &ofiles[0]; op < &ofiles[NIOSTREAM]; op++) { 608 if (op->f_fp == FNULL) { 609 if (fop == (OFILE *)NULL) 610 fop = op; 611 continue; 612 } 613 if (op->f_mode == type && strcmp(op->f_name, cp) == 0) 614 return (op->f_fp); 615 } 616 if (fop == (OFILE *)NULL) 617 awkerr(gettext("too many open streams to %s onto \"%s\""), 618 flag ? "print/printf" : "getline", cp); 619 (void) fflush(stdout); 620 op = fop; 621 if (cp[0] == '-' && cp[1] == '\0') { 622 fp = flag ? stdout : stdin; 623 } else { 624 switch (np->n_type) { 625 case WRITE: 626 if ((fp = fopen(cp, w)) != FNULL) { 627 if (isatty(fileno(fp))) 628 (void) setvbuf(fp, 0, _IONBF, 0); 629 } 630 break; 631 632 case APPEND: 633 fp = fopen(cp, "a"); 634 break; 635 636 case PIPE: 637 fp = popen(cp, w); 638 (void) setvbuf(fp, (char *)0, _IOLBF, 0); 639 break; 640 641 case PIPESYM: 642 fp = popen(cp, r); 643 break; 644 645 case LT: 646 fp = fopen(cp, r); 647 break; 648 649 default: 650 awkerr(interr, "openfile"); 651 } 652 } 653 if (fp != FNULL) { 654 op->f_name = strdup(cp); 655 op->f_fp = fp; 656 op->f_mode = type; 657 } else if (fatal) { 658 awkperr(flag ? gettext("output file \"%s\"") : 659 gettext("input file \"%s\""), cp); 660 } 661 return (fp); 662 } 663 664 /* 665 * Close a stream. 666 */ 667 void 668 awkclose(OFILE *op) 669 { 670 if (op->f_mode == PIPE || op->f_mode == PIPESYM) 671 (void) pclose(op->f_fp); 672 else if (fclose(op->f_fp) == EOF) 673 awkperr("error on stream \"%s\"", op->f_name); 674 op->f_fp = FNULL; 675 free(op->f_name); 676 op->f_name = NULL; 677 } 678 679 /* 680 * Internal routine common to printf, sprintf. 681 * The node is that describing the arguments. 682 * Returns the number of characters written to file 683 * pointer `fp' or the length of the string return 684 * in cp. If cp is NULL then the file pointer is used. If 685 * cp points to a string pointer, a pointer to an allocated 686 * buffer will be returned in it. 687 */ 688 size_t 689 xprintf(NODE *np, FILE *fp, wchar_t **cp) 690 { 691 wchar_t *fmt; 692 int c; 693 wchar_t *bptr = (wchar_t *)NULL; 694 char fmtbuf[40]; 695 size_t length = 0; 696 char *ofmtp; 697 NODE *fnp; 698 wchar_t *fmtsave; 699 int slen; 700 int cplen; 701 702 fnp = getlist(&np); 703 if (isleaf(fnp->n_flags) && fnp->n_type == PARM) 704 fnp = fnp->n_next; 705 if (isstring(fnp->n_flags)) { 706 fmt = fnp->n_string; 707 fmtsave = NULL; 708 } else 709 fmtsave = fmt = (wchar_t *)strsave(exprstring(fnp)); 710 711 /* 712 * if a char * pointer has been passed in then allocate an initial 713 * buffer for the string. Make it LINE_MAX plus the length of 714 * the format string but do reallocs only based LINE_MAX. 715 */ 716 if (cp != (wchar_t **)NULL) { 717 cplen = LINE_MAX; 718 bptr = *cp = emalloc(sizeof (wchar_t) * (cplen + wcslen(fmt))); 719 } 720 721 while ((c = *fmt++) != '\0') { 722 if (c != '%') { 723 if (bptr == (wchar_t *)NULL) 724 awk_putwc(c, fp); 725 else 726 *bptr++ = c; 727 ++length; 728 continue; 729 } 730 ofmtp = fmtbuf; 731 *ofmtp++ = (char)c; 732 nextc: 733 switch (c = *fmt++) { 734 case '%': 735 if (bptr == (wchar_t *)NULL) 736 awk_putwc(c, fp); 737 else 738 *bptr++ = c; 739 ++length; 740 continue; 741 742 case 'c': 743 *ofmtp++ = 'w'; 744 *ofmtp++ = 'c'; 745 *ofmtp = '\0'; 746 fnp = exprreduce(nextarg(&np)); 747 if (isnumber(fnp->n_flags)) 748 c = exprint(fnp); 749 else 750 c = *(wchar_t *)exprstring(fnp); 751 if (bptr == (wchar_t *)NULL) 752 length += fprintf(fp, fmtbuf, c); 753 else { 754 /* 755 * Make sure that the buffer is long 756 * enough to hold the formatted string. 757 */ 758 adjust_buf(cp, &cplen, &bptr, fmtbuf, 0); 759 /* 760 * Since the call to adjust_buf() has already 761 * guaranteed that the buffer will be long 762 * enough, just pass in INT_MAX as 763 * the length. 764 */ 765 (void) wsprintf(bptr, (const char *) fmtbuf, c); 766 bptr += (slen = wcslen(bptr)); 767 length += slen; 768 } 769 continue; 770 /* XXXX Is this bogus? Figure out what s & S mean - look at original code */ 771 case 's': 772 case 'S': 773 *ofmtp++ = 'w'; 774 *ofmtp++ = 's'; 775 *ofmtp = '\0'; 776 if (bptr == (wchar_t *)NULL) 777 length += fprintf(fp, fmtbuf, 778 (wchar_t *)exprstring(nextarg(&np))); 779 else { 780 wchar_t *ts = exprstring(nextarg(&np)); 781 782 adjust_buf(cp, &cplen, &bptr, fmtbuf, 783 wcslen(ts)); 784 (void) wsprintf(bptr, (const char *) fmtbuf, 785 ts); 786 bptr += (slen = wcslen(bptr)); 787 length += slen; 788 } 789 continue; 790 791 case 'o': 792 case 'O': 793 case 'X': 794 case 'x': 795 case 'd': 796 case 'i': 797 case 'D': 798 case 'U': 799 case 'u': 800 *ofmtp++ = 'l'; 801 *ofmtp++ = 'l'; /* now dealing with long longs */ 802 *ofmtp++ = c; 803 *ofmtp = '\0'; 804 if (bptr == (wchar_t *)NULL) 805 length += fprintf(fp, fmtbuf, 806 exprint(nextarg(&np))); 807 else { 808 adjust_buf(cp, &cplen, &bptr, fmtbuf, 0); 809 (void) wsprintf(bptr, (const char *) fmtbuf, 810 exprint(nextarg(&np))); 811 bptr += (slen = wcslen(bptr)); 812 length += slen; 813 } 814 continue; 815 816 case 'e': 817 case 'E': 818 case 'f': 819 case 'F': 820 case 'g': 821 case 'G': 822 *ofmtp++ = c; 823 *ofmtp = '\0'; 824 if (bptr == (wchar_t *)NULL) 825 length += fprintf(fp, fmtbuf, 826 exprreal(nextarg(&np))); 827 else { 828 adjust_buf(cp, &cplen, &bptr, fmtbuf, 0); 829 (void) wsprintf(bptr, (const char *) fmtbuf, 830 exprreal(nextarg(&np))); 831 bptr += (slen = wcslen(bptr)); 832 length += slen; 833 } 834 continue; 835 836 case 'l': 837 case 'L': 838 break; 839 840 case '*': 841 #ifdef M_BSD_SPRINTF 842 sprintf(ofmtp, "%lld", (INT)exprint(nextarg(&np))); 843 ofmtp += strlen(ofmtp); 844 #else 845 ofmtp += sprintf(ofmtp, "%lld", 846 (INT)exprint(nextarg(&np))); 847 #endif 848 break; 849 850 default: 851 if (c == '\0') { 852 *ofmtp = (wchar_t)NULL; 853 (void) fprintf(fp, "%s", fmtbuf); 854 continue; 855 } else { 856 *ofmtp++ = (wchar_t)c; 857 break; 858 } 859 } 860 goto nextc; 861 } 862 if (fmtsave != NULL) 863 free(fmtsave); 864 /* 865 * If printing to a character buffer then make sure it is 866 * null-terminated and only uses as much space as required. 867 */ 868 if (bptr != (wchar_t *)NULL) { 869 *bptr = '\0'; 870 *cp = erealloc(*cp, (length+1) * sizeof (wchar_t)); 871 } 872 return (length); 873 } 874 875 /* 876 * Return the next argument from the list. 877 */ 878 static NODE * 879 nextarg(NODE **npp) 880 { 881 NODE *np; 882 883 if ((np = getlist(npp)) == NNULL) 884 awkerr(gettext("insufficient arguments to printf or sprintf")); 885 if (isleaf(np->n_flags) && np->n_type == PARM) 886 return (np->n_next); 887 return (np); 888 } 889 890 891 /* 892 * Check and adjust the length of the buffer that has been passed in 893 * to make sure that it has space to accomodate the sequence string 894 * described in fmtstr. This routine is used by xprintf() to allow 895 * for arbitrarily long sprintf() strings. 896 * 897 * bp = start of current buffer 898 * len = length of current buffer 899 * offset = offset in current buffer 900 * fmtstr = format string to check 901 * slen = size of string for %s formats 902 */ 903 static void 904 adjust_buf(wchar_t **bp, int *len, wchar_t **offset, char *fmtstr, size_t slen) 905 { 906 int ioff; 907 int width = 0; 908 int prec = 0; 909 910 do { 911 fmtstr++; 912 } while (strchr("-+ 0", *fmtstr) != (char *)0 || *fmtstr == ('#')); 913 if (*fmtstr != '*') { 914 if (isdigit(*fmtstr)) { 915 width = *fmtstr-'0'; 916 while (isdigit(*++fmtstr)) 917 width = width * 10 + *fmtstr - '0'; 918 } 919 } else 920 fmtstr++; 921 if (*fmtstr == '.') { 922 if (*++fmtstr != '*') { 923 prec = *fmtstr-'0'; 924 while (isdigit(*++fmtstr)) 925 prec = prec * 10 + *fmtstr - '0'; 926 } else 927 fmtstr++; 928 } 929 if (strchr("Llh", *fmtstr) != (char *)0) 930 fmtstr++; 931 if (*fmtstr == 'S') { 932 if (width && slen < width) 933 slen = width; 934 if (prec && slen > prec) 935 slen = prec; 936 width = slen+1; 937 } else 938 if (width == 0) 939 width = NUMSIZE; 940 941 if (*offset+ width > *bp+ *len) { 942 ioff = *offset-*bp; 943 *len += width+1; 944 *bp = erealloc(*bp, *len * sizeof (wchar_t)); 945 *offset = *bp+ioff; 946 } 947 } 948 949 static void 950 awk_putwc(wchar_t c, FILE *fp) 951 { 952 char mb[MB_LEN_MAX]; 953 size_t mbl; 954 955 if ((mbl = wctomb(mb, c)) > 0) { 956 mb[mbl] = '\0'; 957 (void) fputs(mb, fp); 958 } else 959 awkerr(gettext("invalid wide character %x"), c); 960 } 961