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