1 /*- 2 * Copyright 2010 Nexenta Systems, Inc. All rights reserved. 3 * Copyright 2015 John Marino <draco@marino.st> 4 * 5 * This source code is derived from the illumos localedef command, and 6 * provided under BSD-style license terms by Nexenta Systems, Inc. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 * POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 /* 32 * This file contains the "scanner", which tokenizes the input files 33 * for localedef for processing by the higher level grammar processor. 34 */ 35 #include <sys/cdefs.h> 36 __FBSDID("$FreeBSD$"); 37 38 #include <stdio.h> 39 #include <stdlib.h> 40 #include <ctype.h> 41 #include <limits.h> 42 #include <string.h> 43 #include <wchar.h> 44 #include <sys/types.h> 45 #include <assert.h> 46 #include "localedef.h" 47 #include "parser.h" 48 49 int com_char = '#'; 50 int esc_char = '\\'; 51 int mb_cur_min = 1; 52 int mb_cur_max = 1; 53 int lineno = 1; 54 int warnings = 0; 55 int is_stdin = 1; 56 FILE *input; 57 static int nextline; 58 //static FILE *input = stdin; 59 static const char *filename = "<stdin>"; 60 static int instring = 0; 61 static int escaped = 0; 62 63 /* 64 * Token space ... grows on demand. 65 */ 66 static char *token = NULL; 67 static int tokidx; 68 static int toksz = 0; 69 static int hadtok = 0; 70 71 /* 72 * Wide string space ... grows on demand. 73 */ 74 static wchar_t *widestr = NULL; 75 static int wideidx = 0; 76 static int widesz = 0; 77 78 /* 79 * The last keyword seen. This is useful to trigger the special lexer rules 80 * for "copy" and also collating symbols and elements. 81 */ 82 int last_kw = 0; 83 static int category = T_END; 84 85 static struct token { 86 int id; 87 const char *name; 88 } keywords[] = { 89 { T_COM_CHAR, "comment_char" }, 90 { T_ESC_CHAR, "escape_char" }, 91 { T_END, "END" }, 92 { T_COPY, "copy" }, 93 { T_MESSAGES, "LC_MESSAGES" }, 94 { T_YESSTR, "yesstr" }, 95 { T_YESEXPR, "yesexpr" }, 96 { T_NOSTR, "nostr" }, 97 { T_NOEXPR, "noexpr" }, 98 { T_MONETARY, "LC_MONETARY" }, 99 { T_INT_CURR_SYMBOL, "int_curr_symbol" }, 100 { T_CURRENCY_SYMBOL, "currency_symbol" }, 101 { T_MON_DECIMAL_POINT, "mon_decimal_point" }, 102 { T_MON_THOUSANDS_SEP, "mon_thousands_sep" }, 103 { T_POSITIVE_SIGN, "positive_sign" }, 104 { T_NEGATIVE_SIGN, "negative_sign" }, 105 { T_MON_GROUPING, "mon_grouping" }, 106 { T_INT_FRAC_DIGITS, "int_frac_digits" }, 107 { T_FRAC_DIGITS, "frac_digits" }, 108 { T_P_CS_PRECEDES, "p_cs_precedes" }, 109 { T_P_SEP_BY_SPACE, "p_sep_by_space" }, 110 { T_N_CS_PRECEDES, "n_cs_precedes" }, 111 { T_N_SEP_BY_SPACE, "n_sep_by_space" }, 112 { T_P_SIGN_POSN, "p_sign_posn" }, 113 { T_N_SIGN_POSN, "n_sign_posn" }, 114 { T_INT_P_CS_PRECEDES, "int_p_cs_precedes" }, 115 { T_INT_N_CS_PRECEDES, "int_n_cs_precedes" }, 116 { T_INT_P_SEP_BY_SPACE, "int_p_sep_by_space" }, 117 { T_INT_N_SEP_BY_SPACE, "int_n_sep_by_space" }, 118 { T_INT_P_SIGN_POSN, "int_p_sign_posn" }, 119 { T_INT_N_SIGN_POSN, "int_n_sign_posn" }, 120 { T_COLLATE, "LC_COLLATE" }, 121 { T_COLLATING_SYMBOL, "collating-symbol" }, 122 { T_COLLATING_ELEMENT, "collating-element" }, 123 { T_FROM, "from" }, 124 { T_ORDER_START, "order_start" }, 125 { T_ORDER_END, "order_end" }, 126 { T_FORWARD, "forward" }, 127 { T_BACKWARD, "backward" }, 128 { T_POSITION, "position" }, 129 { T_IGNORE, "IGNORE" }, 130 { T_UNDEFINED, "UNDEFINED" }, 131 { T_NUMERIC, "LC_NUMERIC" }, 132 { T_DECIMAL_POINT, "decimal_point" }, 133 { T_THOUSANDS_SEP, "thousands_sep" }, 134 { T_GROUPING, "grouping" }, 135 { T_TIME, "LC_TIME" }, 136 { T_ABDAY, "abday" }, 137 { T_DAY, "day" }, 138 { T_ABMON, "abmon" }, 139 { T_MON, "mon" }, 140 { T_D_T_FMT, "d_t_fmt" }, 141 { T_D_FMT, "d_fmt" }, 142 { T_T_FMT, "t_fmt" }, 143 { T_AM_PM, "am_pm" }, 144 { T_T_FMT_AMPM, "t_fmt_ampm" }, 145 { T_ERA, "era" }, 146 { T_ERA_D_FMT, "era_d_fmt" }, 147 { T_ERA_T_FMT, "era_t_fmt" }, 148 { T_ERA_D_T_FMT, "era_d_t_fmt" }, 149 { T_ALT_DIGITS, "alt_digits" }, 150 { T_CTYPE, "LC_CTYPE" }, 151 { T_ISUPPER, "upper" }, 152 { T_ISLOWER, "lower" }, 153 { T_ISALPHA, "alpha" }, 154 { T_ISDIGIT, "digit" }, 155 { T_ISPUNCT, "punct" }, 156 { T_ISXDIGIT, "xdigit" }, 157 { T_ISSPACE, "space" }, 158 { T_ISPRINT, "print" }, 159 { T_ISGRAPH, "graph" }, 160 { T_ISBLANK, "blank" }, 161 { T_ISCNTRL, "cntrl" }, 162 /* 163 * These entries are local additions, and not specified by 164 * TOG. Note that they are not guaranteed to be accurate for 165 * all locales, and so applications should not depend on them. 166 */ 167 { T_ISSPECIAL, "special" }, 168 { T_ISENGLISH, "english" }, 169 { T_ISPHONOGRAM, "phonogram" }, 170 { T_ISIDEOGRAM, "ideogram" }, 171 { T_ISNUMBER, "number" }, 172 /* 173 * We have to support this in the grammar, but it would be a 174 * syntax error to define a character as one of these without 175 * also defining it as an alpha or digit. We ignore it in our 176 * parsing. 177 */ 178 { T_ISALNUM, "alnum" }, 179 { T_TOUPPER, "toupper" }, 180 { T_TOLOWER, "tolower" }, 181 182 /* 183 * These are keywords used in the charmap file. Note that 184 * Solaris originally used angle brackets to wrap some of them, 185 * but we removed that to simplify our parser. The first of these 186 * items are "global items." 187 */ 188 { T_CHARMAP, "CHARMAP" }, 189 { T_WIDTH, "WIDTH" }, 190 191 { -1, NULL }, 192 }; 193 194 /* 195 * These special words are only used in a charmap file, enclosed in <>. 196 */ 197 static struct token symwords[] = { 198 { T_COM_CHAR, "comment_char" }, 199 { T_ESC_CHAR, "escape_char" }, 200 { T_CODE_SET, "code_set_name" }, 201 { T_MB_CUR_MAX, "mb_cur_max" }, 202 { T_MB_CUR_MIN, "mb_cur_min" }, 203 { -1, NULL }, 204 }; 205 206 static int categories[] = { 207 T_CHARMAP, 208 T_CTYPE, 209 T_COLLATE, 210 T_MESSAGES, 211 T_MONETARY, 212 T_NUMERIC, 213 T_TIME, 214 T_WIDTH, 215 0 216 }; 217 218 void 219 reset_scanner(const char *fname) 220 { 221 if (fname == NULL) { 222 filename = "<stdin>"; 223 is_stdin = 1; 224 } else { 225 if (!is_stdin) 226 (void) fclose(input); 227 if ((input = fopen(fname, "r")) == NULL) { 228 perror("fopen"); 229 exit(4); 230 } else { 231 is_stdin = 0; 232 } 233 filename = fname; 234 } 235 com_char = '#'; 236 esc_char = '\\'; 237 instring = 0; 238 escaped = 0; 239 lineno = 1; 240 nextline = 1; 241 tokidx = 0; 242 wideidx = 0; 243 } 244 245 #define hex(x) \ 246 (isdigit(x) ? (x - '0') : ((islower(x) ? (x - 'a') : (x - 'A')) + 10)) 247 #define isodigit(x) ((x >= '0') && (x <= '7')) 248 249 static int 250 scanc(void) 251 { 252 int c; 253 254 if (is_stdin) 255 c = getc(stdin); 256 else 257 c = getc(input); 258 lineno = nextline; 259 if (c == '\n') { 260 nextline++; 261 } 262 return (c); 263 } 264 265 static void 266 unscanc(int c) 267 { 268 if (c == '\n') { 269 nextline--; 270 } 271 if (ungetc(c, is_stdin ? stdin : input) < 0) { 272 yyerror("ungetc failed"); 273 } 274 } 275 276 static int 277 scan_hex_byte(void) 278 { 279 int c1, c2; 280 int v; 281 282 c1 = scanc(); 283 if (!isxdigit(c1)) { 284 yyerror("malformed hex digit"); 285 return (0); 286 } 287 c2 = scanc(); 288 if (!isxdigit(c2)) { 289 yyerror("malformed hex digit"); 290 return (0); 291 } 292 v = ((hex(c1) << 4) | hex(c2)); 293 return (v); 294 } 295 296 static int 297 scan_dec_byte(void) 298 { 299 int c1, c2, c3; 300 int b; 301 302 c1 = scanc(); 303 if (!isdigit(c1)) { 304 yyerror("malformed decimal digit"); 305 return (0); 306 } 307 b = c1 - '0'; 308 c2 = scanc(); 309 if (!isdigit(c2)) { 310 yyerror("malformed decimal digit"); 311 return (0); 312 } 313 b *= 10; 314 b += (c2 - '0'); 315 c3 = scanc(); 316 if (!isdigit(c3)) { 317 unscanc(c3); 318 } else { 319 b *= 10; 320 b += (c3 - '0'); 321 } 322 return (b); 323 } 324 325 static int 326 scan_oct_byte(void) 327 { 328 int c1, c2, c3; 329 int b; 330 331 b = 0; 332 333 c1 = scanc(); 334 if (!isodigit(c1)) { 335 yyerror("malformed octal digit"); 336 return (0); 337 } 338 b = c1 - '0'; 339 c2 = scanc(); 340 if (!isodigit(c2)) { 341 yyerror("malformed octal digit"); 342 return (0); 343 } 344 b *= 8; 345 b += (c2 - '0'); 346 c3 = scanc(); 347 if (!isodigit(c3)) { 348 unscanc(c3); 349 } else { 350 b *= 8; 351 b += (c3 - '0'); 352 } 353 return (b); 354 } 355 356 void 357 add_tok(int c) 358 { 359 if ((tokidx + 1) >= toksz) { 360 toksz += 64; 361 if ((token = realloc(token, toksz)) == NULL) { 362 yyerror("out of memory"); 363 tokidx = 0; 364 toksz = 0; 365 return; 366 } 367 } 368 369 token[tokidx++] = (char)c; 370 token[tokidx] = 0; 371 } 372 void 373 add_wcs(wchar_t c) 374 { 375 if ((wideidx + 1) >= widesz) { 376 widesz += 64; 377 widestr = realloc(widestr, (widesz * sizeof (wchar_t))); 378 if (widestr == NULL) { 379 yyerror("out of memory"); 380 wideidx = 0; 381 widesz = 0; 382 return; 383 } 384 } 385 386 widestr[wideidx++] = c; 387 widestr[wideidx] = 0; 388 } 389 390 wchar_t * 391 get_wcs(void) 392 { 393 wchar_t *ws = widestr; 394 wideidx = 0; 395 widestr = NULL; 396 widesz = 0; 397 if (ws == NULL) { 398 if ((ws = wcsdup(L"")) == NULL) { 399 yyerror("out of memory"); 400 } 401 } 402 return (ws); 403 } 404 405 static int 406 get_byte(void) 407 { 408 int c; 409 410 if ((c = scanc()) != esc_char) { 411 unscanc(c); 412 return (EOF); 413 } 414 c = scanc(); 415 416 switch (c) { 417 case 'd': 418 case 'D': 419 return (scan_dec_byte()); 420 case 'x': 421 case 'X': 422 return (scan_hex_byte()); 423 case '0': 424 case '1': 425 case '2': 426 case '3': 427 case '4': 428 case '5': 429 case '6': 430 case '7': 431 /* put the character back so we can get it */ 432 unscanc(c); 433 return (scan_oct_byte()); 434 default: 435 unscanc(c); 436 unscanc(esc_char); 437 return (EOF); 438 } 439 } 440 441 int 442 get_escaped(int c) 443 { 444 switch (c) { 445 case 'n': 446 return ('\n'); 447 case 'r': 448 return ('\r'); 449 case 't': 450 return ('\t'); 451 case 'f': 452 return ('\f'); 453 case 'v': 454 return ('\v'); 455 case 'b': 456 return ('\b'); 457 case 'a': 458 return ('\a'); 459 default: 460 return (c); 461 } 462 } 463 464 int 465 get_wide(void) 466 { 467 static char mbs[MB_LEN_MAX + 1] = ""; 468 static int mbi = 0; 469 int c; 470 wchar_t wc; 471 472 if (mb_cur_max >= (int)sizeof (mbs)) { 473 yyerror("max multibyte character size too big"); 474 mbi = 0; 475 return (T_NULL); 476 } 477 for (;;) { 478 if ((mbi == mb_cur_max) || ((c = get_byte()) == EOF)) { 479 /* 480 * end of the byte sequence reached, but no 481 * valid wide decoding. fatal error. 482 */ 483 mbi = 0; 484 yyerror("not a valid character encoding"); 485 return (T_NULL); 486 } 487 mbs[mbi++] = c; 488 mbs[mbi] = 0; 489 490 /* does it decode? */ 491 if (to_wide(&wc, mbs) >= 0) { 492 break; 493 } 494 } 495 496 mbi = 0; 497 if ((category != T_CHARMAP) && (category != T_WIDTH)) { 498 if (check_charmap(wc) < 0) { 499 yyerror("no symbolic name for character"); 500 return (T_NULL); 501 } 502 } 503 504 yylval.wc = wc; 505 return (T_CHAR); 506 } 507 508 int 509 get_symbol(void) 510 { 511 int c; 512 513 while ((c = scanc()) != EOF) { 514 if (escaped) { 515 escaped = 0; 516 if (c == '\n') 517 continue; 518 add_tok(get_escaped(c)); 519 continue; 520 } 521 if (c == esc_char) { 522 escaped = 1; 523 continue; 524 } 525 if (c == '\n') { /* well that's strange! */ 526 yyerror("unterminated symbolic name"); 527 continue; 528 } 529 if (c == '>') { /* end of symbol */ 530 531 /* 532 * This restarts the token from the beginning 533 * the next time we scan a character. (This 534 * token is complete.) 535 */ 536 537 if (token == NULL) { 538 yyerror("missing symbolic name"); 539 return (T_NULL); 540 } 541 tokidx = 0; 542 543 /* 544 * A few symbols are handled as keywords outside 545 * of the normal categories. 546 */ 547 if (category == T_END) { 548 int i; 549 for (i = 0; symwords[i].name != 0; i++) { 550 if (strcmp(token, symwords[i].name) == 551 0) { 552 last_kw = symwords[i].id; 553 return (last_kw); 554 } 555 } 556 } 557 /* 558 * Contextual rule: Only literal characters are 559 * permitted in CHARMAP. Anywhere else the symbolic 560 * forms are fine. 561 */ 562 if ((category != T_CHARMAP) && 563 (lookup_charmap(token, &yylval.wc)) != -1) { 564 return (T_CHAR); 565 } 566 if ((yylval.collsym = lookup_collsym(token)) != NULL) { 567 return (T_COLLSYM); 568 } 569 if ((yylval.collelem = lookup_collelem(token)) != 570 NULL) { 571 return (T_COLLELEM); 572 } 573 /* its an undefined symbol */ 574 yylval.token = strdup(token); 575 token = NULL; 576 toksz = 0; 577 tokidx = 0; 578 return (T_SYMBOL); 579 } 580 add_tok(c); 581 } 582 583 yyerror("unterminated symbolic name"); 584 return (EOF); 585 } 586 587 int 588 get_category(void) 589 { 590 return (category); 591 } 592 593 static int 594 consume_token(void) 595 { 596 int len = tokidx; 597 int i; 598 599 tokidx = 0; 600 if (token == NULL) 601 return (T_NULL); 602 603 /* 604 * this one is special, because we don't want it to alter the 605 * last_kw field. 606 */ 607 if (strcmp(token, "...") == 0) { 608 return (T_ELLIPSIS); 609 } 610 611 /* search for reserved words first */ 612 for (i = 0; keywords[i].name; i++) { 613 int j; 614 if (strcmp(keywords[i].name, token) != 0) { 615 continue; 616 } 617 618 last_kw = keywords[i].id; 619 620 /* clear the top level category if we're done with it */ 621 if (last_kw == T_END) { 622 category = T_END; 623 } 624 625 /* set the top level category if we're changing */ 626 for (j = 0; categories[j]; j++) { 627 if (categories[j] != last_kw) 628 continue; 629 category = last_kw; 630 } 631 632 return (keywords[i].id); 633 } 634 635 /* maybe its a numeric constant? */ 636 if (isdigit(*token) || (*token == '-' && isdigit(token[1]))) { 637 char *eptr; 638 yylval.num = strtol(token, &eptr, 10); 639 if (*eptr != 0) 640 yyerror("malformed number"); 641 return (T_NUMBER); 642 } 643 644 /* 645 * A single lone character is treated as a character literal. 646 * To avoid duplication of effort, we stick in the charmap. 647 */ 648 if (len == 1) { 649 yylval.wc = token[0]; 650 return (T_CHAR); 651 } 652 653 /* anything else is treated as a symbolic name */ 654 yylval.token = strdup(token); 655 token = NULL; 656 toksz = 0; 657 tokidx = 0; 658 return (T_NAME); 659 } 660 661 void 662 scan_to_eol(void) 663 { 664 int c; 665 while ((c = scanc()) != '\n') { 666 if (c == EOF) { 667 /* end of file without newline! */ 668 errf("missing newline"); 669 return; 670 } 671 } 672 assert(c == '\n'); 673 } 674 675 int 676 yylex(void) 677 { 678 int c; 679 680 while ((c = scanc()) != EOF) { 681 682 /* special handling for quoted string */ 683 if (instring) { 684 if (escaped) { 685 escaped = 0; 686 687 /* if newline, just eat and forget it */ 688 if (c == '\n') 689 continue; 690 691 if (strchr("xXd01234567", c)) { 692 unscanc(c); 693 unscanc(esc_char); 694 return (get_wide()); 695 } 696 yylval.wc = get_escaped(c); 697 return (T_CHAR); 698 } 699 if (c == esc_char) { 700 escaped = 1; 701 continue; 702 } 703 switch (c) { 704 case '<': 705 return (get_symbol()); 706 case '>': 707 /* oops! should generate syntax error */ 708 return (T_GT); 709 case '"': 710 instring = 0; 711 return (T_QUOTE); 712 default: 713 yylval.wc = c; 714 return (T_CHAR); 715 } 716 } 717 718 /* escaped characters first */ 719 if (escaped) { 720 escaped = 0; 721 if (c == '\n') { 722 /* eat the newline */ 723 continue; 724 } 725 hadtok = 1; 726 if (tokidx) { 727 /* an escape mid-token is nonsense */ 728 return (T_NULL); 729 } 730 731 /* numeric escapes are treated as wide characters */ 732 if (strchr("xXd01234567", c)) { 733 unscanc(c); 734 unscanc(esc_char); 735 return (get_wide()); 736 } 737 738 add_tok(get_escaped(c)); 739 continue; 740 } 741 742 /* if it is the escape charter itself note it */ 743 if (c == esc_char) { 744 escaped = 1; 745 continue; 746 } 747 748 /* remove from the comment char to end of line */ 749 if (c == com_char) { 750 while (c != '\n') { 751 if ((c = scanc()) == EOF) { 752 /* end of file without newline! */ 753 return (EOF); 754 } 755 } 756 assert(c == '\n'); 757 if (!hadtok) { 758 /* 759 * If there were no tokens on this line, 760 * then just pretend it didn't exist at all. 761 */ 762 continue; 763 } 764 hadtok = 0; 765 return (T_NL); 766 } 767 768 if (strchr(" \t\n;()<>,\"", c) && (tokidx != 0)) { 769 /* 770 * These are all token delimiters. If there 771 * is a token already in progress, we need to 772 * process it. 773 */ 774 unscanc(c); 775 return (consume_token()); 776 } 777 778 switch (c) { 779 case '\n': 780 if (!hadtok) { 781 /* 782 * If the line was completely devoid of tokens, 783 * then just ignore it. 784 */ 785 continue; 786 } 787 /* we're starting a new line, reset the token state */ 788 hadtok = 0; 789 return (T_NL); 790 case ',': 791 hadtok = 1; 792 return (T_COMMA); 793 case ';': 794 hadtok = 1; 795 return (T_SEMI); 796 case '(': 797 hadtok = 1; 798 return (T_LPAREN); 799 case ')': 800 hadtok = 1; 801 return (T_RPAREN); 802 case '>': 803 hadtok = 1; 804 return (T_GT); 805 case '<': 806 /* symbol start! */ 807 hadtok = 1; 808 return (get_symbol()); 809 case ' ': 810 case '\t': 811 /* whitespace, just ignore it */ 812 continue; 813 case '"': 814 hadtok = 1; 815 instring = 1; 816 return (T_QUOTE); 817 default: 818 hadtok = 1; 819 add_tok(c); 820 continue; 821 } 822 } 823 return (EOF); 824 } 825 826 void 827 yyerror(const char *msg) 828 { 829 (void) fprintf(stderr, "%s: %d: error: %s\n", 830 filename, lineno, msg); 831 exit(4); 832 } 833 834 void 835 errf(const char *fmt, ...) 836 { 837 char *msg; 838 839 va_list va; 840 va_start(va, fmt); 841 (void) vasprintf(&msg, fmt, va); 842 va_end(va); 843 844 (void) fprintf(stderr, "%s: %d: error: %s\n", 845 filename, lineno, msg); 846 free(msg); 847 exit(4); 848 } 849 850 void 851 warn(const char *fmt, ...) 852 { 853 char *msg; 854 855 va_list va; 856 va_start(va, fmt); 857 (void) vasprintf(&msg, fmt, va); 858 va_end(va); 859 860 (void) fprintf(stderr, "%s: %d: warning: %s\n", 861 filename, lineno, msg); 862 free(msg); 863 warnings++; 864 if (!warnok) 865 exit(4); 866 } 867