xref: /openbsd-src/usr.bin/awk/lib.c (revision 94358d69ee05fa503294e6438e1b1bbf60aa9d02)
1 /*	$OpenBSD: lib.c,v 1.51 2023/09/17 14:49:44 millert Exp $	*/
2 /****************************************************************
3 Copyright (C) Lucent Technologies 1997
4 All Rights Reserved
5 
6 Permission to use, copy, modify, and distribute this software and
7 its documentation for any purpose and without fee is hereby
8 granted, provided that the above copyright notice appear in all
9 copies and that both that the copyright notice and this
10 permission notice and warranty disclaimer appear in supporting
11 documentation, and that the name Lucent Technologies or any of
12 its entities not be used in advertising or publicity pertaining
13 to distribution of the software without specific, written prior
14 permission.
15 
16 LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
17 INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
18 IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
19 SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
21 IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
22 ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
23 THIS SOFTWARE.
24 ****************************************************************/
25 
26 #define DEBUG
27 #include <stdio.h>
28 #include <string.h>
29 #include <ctype.h>
30 #include <errno.h>
31 #include <stdlib.h>
32 #include <stdarg.h>
33 #include <limits.h>
34 #include <math.h>
35 #include "awk.h"
36 
37 extern int u8_nextlen(const char *s);
38 
39 char	EMPTY[] = { '\0' };
40 FILE	*infile	= NULL;
41 bool	innew;		/* true = infile has not been read by readrec */
42 char	*file	= EMPTY;
43 char	*record;
44 int	recsize	= RECSIZE;
45 char	*fields;
46 int	fieldssize = RECSIZE;
47 
48 Cell	**fldtab;	/* pointers to Cells */
49 static size_t	len_inputFS = 0;
50 static char	*inputFS = NULL; /* FS at time of input, for field splitting */
51 
52 #define	MAXFLD	2
53 int	nfields	= MAXFLD;	/* last allocated slot for $i */
54 
55 bool	donefld;	/* true = implies rec broken into fields */
56 bool	donerec;	/* true = record is valid (no flds have changed) */
57 
58 int	lastfld	= 0;	/* last used field */
59 int	argno	= 1;	/* current input argument number */
60 extern	Awkfloat *ARGC;
61 
62 static Cell dollar0 = { OCELL, CFLD, NULL, EMPTY, 0.0, REC|STR|DONTFREE, NULL, NULL };
63 static Cell dollar1 = { OCELL, CFLD, NULL, EMPTY, 0.0, FLD|STR|DONTFREE, NULL, NULL };
64 
65 void recinit(unsigned int n)
66 {
67 	if ( (record = (char *) malloc(n)) == NULL
68 	  || (fields = (char *) malloc(n+1)) == NULL
69 	  || (fldtab = (Cell **) calloc(nfields+2, sizeof(*fldtab))) == NULL
70 	  || (fldtab[0] = (Cell *) malloc(sizeof(**fldtab))) == NULL)
71 		FATAL("out of space for $0 and fields");
72 	*record = '\0';
73 	*fldtab[0] = dollar0;
74 	fldtab[0]->sval = record;
75 	fldtab[0]->nval = tostring("0");
76 	makefields(1, nfields);
77 }
78 
79 void makefields(int n1, int n2)		/* create $n1..$n2 inclusive */
80 {
81 	char temp[50];
82 	int i;
83 
84 	for (i = n1; i <= n2; i++) {
85 		fldtab[i] = (Cell *) malloc(sizeof(**fldtab));
86 		if (fldtab[i] == NULL)
87 			FATAL("out of space in makefields %d", i);
88 		*fldtab[i] = dollar1;
89 		snprintf(temp, sizeof(temp), "%d", i);
90 		fldtab[i]->nval = tostring(temp);
91 	}
92 }
93 
94 void initgetrec(void)
95 {
96 	int i;
97 	char *p;
98 
99 	for (i = 1; i < *ARGC; i++) {
100 		p = getargv(i); /* find 1st real filename */
101 		if (p == NULL || *p == '\0') {  /* deleted or zapped */
102 			argno++;
103 			continue;
104 		}
105 		if (!isclvar(p)) {
106 			setsval(lookup("FILENAME", symtab), p);
107 			return;
108 		}
109 		setclvar(p);	/* a commandline assignment before filename */
110 		argno++;
111 	}
112 	infile = stdin;		/* no filenames, so use stdin */
113 	innew = true;
114 }
115 
116 /*
117  * POSIX specifies that fields are supposed to be evaluated as if they were
118  * split using the value of FS at the time that the record's value ($0) was
119  * read.
120  *
121  * Since field-splitting is done lazily, we save the current value of FS
122  * whenever a new record is read in (implicitly or via getline), or when
123  * a new value is assigned to $0.
124  */
125 void savefs(void)
126 {
127 	size_t len = strlen(getsval(fsloc));
128 	if (len >= len_inputFS) {
129 		len_inputFS = len + 1;
130 		inputFS = (char *) realloc(inputFS, len_inputFS);
131 		if (inputFS == NULL)
132 			FATAL("field separator %.10s... is too long", *FS);
133 	}
134 	if (strlcpy(inputFS, *FS, len_inputFS) >= len_inputFS)
135 		FATAL("field separator %.10s... is too long", *FS);
136 }
137 
138 static bool firsttime = true;
139 
140 int getrec(char **pbuf, int *pbufsize, bool isrecord)	/* get next input record */
141 {			/* note: cares whether buf == record */
142 	int c;
143 	char *buf = *pbuf;
144 	uschar saveb0;
145 	int bufsize = *pbufsize, savebufsize = bufsize;
146 
147 	if (firsttime) {
148 		firsttime = false;
149 		initgetrec();
150 	}
151 	DPRINTF("RS=<%s>, FS=<%s>, ARGC=%g, FILENAME=%s\n",
152 		*RS, *FS, *ARGC, *FILENAME);
153 	saveb0 = buf[0];
154 	buf[0] = 0;
155 	while (argno < *ARGC || infile == stdin) {
156 		DPRINTF("argno=%d, file=|%s|\n", argno, file);
157 		if (infile == NULL) {	/* have to open a new file */
158 			file = getargv(argno);
159 			if (file == NULL || *file == '\0') {	/* deleted or zapped */
160 				argno++;
161 				continue;
162 			}
163 			if (isclvar(file)) {	/* a var=value arg */
164 				setclvar(file);
165 				argno++;
166 				continue;
167 			}
168 			*FILENAME = file;
169 			DPRINTF("opening file %s\n", file);
170 			if (*file == '-' && *(file+1) == '\0')
171 				infile = stdin;
172 			else if ((infile = fopen(file, "r")) == NULL)
173 				FATAL("can't open file %s", file);
174 			innew = true;
175 			setfval(fnrloc, 0.0);
176 		}
177 		c = readrec(&buf, &bufsize, infile, innew);
178 		if (innew)
179 			innew = false;
180 		if (c != 0 || buf[0] != '\0') {	/* normal record */
181 			if (isrecord) {
182 				double result;
183 
184 				if (freeable(fldtab[0]))
185 					xfree(fldtab[0]->sval);
186 				fldtab[0]->sval = buf;	/* buf == record */
187 				fldtab[0]->tval = REC | STR | DONTFREE;
188 				if (is_number(fldtab[0]->sval, & result)) {
189 					fldtab[0]->fval = result;
190 					fldtab[0]->tval |= NUM;
191 				}
192 				donefld = false;
193 				donerec = true;
194 				savefs();
195 			}
196 			setfval(nrloc, nrloc->fval+1);
197 			setfval(fnrloc, fnrloc->fval+1);
198 			*pbuf = buf;
199 			*pbufsize = bufsize;
200 			return 1;
201 		}
202 		/* EOF arrived on this file; set up next */
203 		if (infile != stdin)
204 			fclose(infile);
205 		infile = NULL;
206 		argno++;
207 	}
208 	buf[0] = saveb0;
209 	*pbuf = buf;
210 	*pbufsize = savebufsize;
211 	return 0;	/* true end of file */
212 }
213 
214 void nextfile(void)
215 {
216 	if (infile != NULL && infile != stdin)
217 		fclose(infile);
218 	infile = NULL;
219 	argno++;
220 }
221 
222 extern int readcsvrec(char **pbuf, int *pbufsize, FILE *inf, bool newflag);
223 
224 int readrec(char **pbuf, int *pbufsize, FILE *inf, bool newflag)	/* read one record into buf */
225 {
226 	int sep, c, isrec; // POTENTIAL BUG? isrec is a macro in awk.h
227 	char *rr = *pbuf, *buf = *pbuf;
228 	int bufsize = *pbufsize;
229 	char *rs = getsval(rsloc);
230 
231 	if (CSV) {
232 		c = readcsvrec(pbuf, pbufsize, inf, newflag);
233 		isrec = (c == EOF && rr == buf) ? false : true;
234 	} else if (*rs && rs[1]) {
235 		bool found;
236 
237 		fa *pfa = makedfa(rs, 1);
238 		if (newflag)
239 			found = fnematch(pfa, inf, &buf, &bufsize, recsize);
240 		else {
241 			int tempstat = pfa->initstat;
242 			pfa->initstat = 2;
243 			found = fnematch(pfa, inf, &buf, &bufsize, recsize);
244 			pfa->initstat = tempstat;
245 		}
246 		if (found)
247 			setptr(patbeg, '\0');
248 		isrec = (found == 0 && *buf == '\0') ? 0 : 1;
249 	} else {
250 		if ((sep = *rs) == 0) {
251 			sep = '\n';
252 			while ((c=getc(inf)) == '\n' && c != EOF)	/* skip leading \n's */
253 				;
254 			if (c != EOF)
255 				ungetc(c, inf);
256 		}
257 		for (rr = buf; ; ) {
258 			for (; (c=getc(inf)) != sep && c != EOF; ) {
259 				if (rr-buf+1 > bufsize)
260 					if (!adjbuf(&buf, &bufsize, 1+rr-buf,
261 					    recsize, &rr, "readrec 1"))
262 						FATAL("input record `%.30s...' too long", buf);
263 				*rr++ = c;
264 			}
265 			if (*rs == sep || c == EOF)
266 				break;
267 			if ((c = getc(inf)) == '\n' || c == EOF)	/* 2 in a row */
268 				break;
269 			if (!adjbuf(&buf, &bufsize, 2+rr-buf, recsize, &rr,
270 			    "readrec 2"))
271 				FATAL("input record `%.30s...' too long", buf);
272 			*rr++ = '\n';
273 			*rr++ = c;
274 		}
275 		if (!adjbuf(&buf, &bufsize, 1+rr-buf, recsize, &rr, "readrec 3"))
276 			FATAL("input record `%.30s...' too long", buf);
277 		*rr = 0;
278 		isrec = (c == EOF && rr == buf) ? 0 : 1;
279 	}
280 	*pbuf = buf;
281 	*pbufsize = bufsize;
282 	DPRINTF("readrec saw <%s>, returns %d\n", buf, isrec);
283 	return isrec;
284 }
285 
286 
287 /*******************
288  * loose ends here:
289  *   \r\n should become \n
290  *   what about bare \r?  Excel uses that for embedded newlines
291  *   can't have "" in unquoted fields, according to RFC 4180
292 */
293 
294 int readcsvrec(char **pbuf, int *pbufsize, FILE *inf, bool newflag) /* csv can have \n's */
295 {			/* so read a complete record that might be multiple lines */
296 	int sep, c;
297 	char *rr = *pbuf, *buf = *pbuf;
298 	int bufsize = *pbufsize;
299 	bool in_quote = false;
300 
301 	sep = '\n'; /* the only separator; have to skip over \n embedded in "..." */
302 	rr = buf;
303 	while ((c = getc(inf)) != EOF) {
304 		if (c == sep) {
305 			if (! in_quote)
306 				break;
307 			if (rr > buf && rr[-1] == '\r')	// remove \r if was \r\n
308 				rr--;
309 		}
310 
311 		if (rr-buf+1 > bufsize)
312 			if (!adjbuf(&buf, &bufsize, 1+rr-buf,
313 			    recsize, &rr, "readcsvrec 1"))
314 				FATAL("input record `%.30s...' too long", buf);
315 		*rr++ = c;
316 		if (c == '"')
317 			in_quote = ! in_quote;
318  	}
319 	if (c == '\n' && rr > buf && rr[-1] == '\r') 	// remove \r if was \r\n
320 		rr--;
321 
322 	if (!adjbuf(&buf, &bufsize, 1+rr-buf, recsize, &rr, "readcsvrec 4"))
323 		FATAL("input record `%.30s...' too long", buf);
324 	*rr = 0;
325 	*pbuf = buf;
326 	*pbufsize = bufsize;
327 	DPRINTF("readcsvrec saw <%s>, returns %d\n", buf, c);
328 	return c;
329 }
330 
331 char *getargv(int n)	/* get ARGV[n] */
332 {
333 	Cell *x;
334 	char *s, temp[50];
335 	extern Array *ARGVtab;
336 
337 	snprintf(temp, sizeof(temp), "%d", n);
338 	if (lookup(temp, ARGVtab) == NULL)
339 		return NULL;
340 	x = setsymtab(temp, "", 0.0, STR, ARGVtab);
341 	s = getsval(x);
342 	DPRINTF("getargv(%d) returns |%s|\n", n, s);
343 	return s;
344 }
345 
346 void setclvar(char *s)	/* set var=value from s */
347 {
348 	char *e, *p;
349 	Cell *q;
350 	double result;
351 
352 /* commit f3d9187d4e0f02294fb1b0e31152070506314e67 broke T.argv test */
353 /* I don't understand why it was changed. */
354 
355 	for (p=s; *p != '='; p++)
356 		;
357 	e = p;
358 	*p++ = 0;
359 	p = qstring(p, '\0');
360 	q = setsymtab(s, p, 0.0, STR, symtab);
361 	setsval(q, p);
362 	if (is_number(q->sval, & result)) {
363 		q->fval = result;
364 		q->tval |= NUM;
365 	}
366 	DPRINTF("command line set %s to |%s|\n", s, p);
367 	free(p);
368 	*e = '=';
369 }
370 
371 
372 void fldbld(void)	/* create fields from current record */
373 {
374 	/* this relies on having fields[] the same length as $0 */
375 	/* the fields are all stored in this one array with \0's */
376 	/* possibly with a final trailing \0 not associated with any field */
377 	char *r, *fr, sep;
378 	Cell *p;
379 	int i, j, n;
380 
381 	if (donefld)
382 		return;
383 	if (!isstr(fldtab[0]))
384 		getsval(fldtab[0]);
385 	r = fldtab[0]->sval;
386 	n = strlen(r);
387 	if (n > fieldssize) {
388 		xfree(fields);
389 		if ((fields = (char *) malloc(n+2)) == NULL) /* possibly 2 final \0s */
390 			FATAL("out of space for fields in fldbld %d", n);
391 		fieldssize = n;
392 	}
393 	fr = fields;
394 	i = 0;	/* number of fields accumulated here */
395 	if (inputFS == NULL)	/* make sure we have a copy of FS */
396 		savefs();
397 	if (strlen(inputFS) > 1) {	/* it's a regular expression */
398 		i = refldbld(r, inputFS);
399 	} else if (!CSV && (sep = *inputFS) == ' ') {	/* default whitespace */
400 		for (i = 0; ; ) {
401 			while (*r == ' ' || *r == '\t' || *r == '\n')
402 				r++;
403 			if (*r == 0)
404 				break;
405 			i++;
406 			if (i > nfields)
407 				growfldtab(i);
408 			if (freeable(fldtab[i]))
409 				xfree(fldtab[i]->sval);
410 			fldtab[i]->sval = fr;
411 			fldtab[i]->tval = FLD | STR | DONTFREE;
412 			do
413 				*fr++ = *r++;
414 			while (*r != ' ' && *r != '\t' && *r != '\n' && *r != '\0');
415 			*fr++ = 0;
416 		}
417 		*fr = 0;
418 	} else if (CSV) {	/* CSV processing.  no error handling */
419 		if (*r != 0) {
420 			for (;;) {
421 				i++;
422 				if (i > nfields)
423 					growfldtab(i);
424 				if (freeable(fldtab[i]))
425 					xfree(fldtab[i]->sval);
426 				fldtab[i]->sval = fr;
427 				fldtab[i]->tval = FLD | STR | DONTFREE;
428 				if (*r == '"' ) { /* start of "..." */
429 					for (r++ ; *r != '\0'; ) {
430 						if (*r == '"' && r[1] != '\0' && r[1] == '"') {
431 							r += 2; /* doubled quote */
432 							*fr++ = '"';
433 						} else if (*r == '"' && (r[1] == '\0' || r[1] == ',')) {
434 							r++; /* skip over closing quote */
435 							break;
436 						} else {
437 							*fr++ = *r++;
438 						}
439 					}
440 					*fr++ = 0;
441 				} else {	/* unquoted field */
442 					while (*r != ',' && *r != '\0')
443 						*fr++ = *r++;
444 					*fr++ = 0;
445 				}
446 				if (*r++ == 0)
447 					break;
448 
449 			}
450 		}
451 		*fr = 0;
452 	} else if ((sep = *inputFS) == 0) {	/* new: FS="" => 1 char/field */
453 		for (i = 0; *r != '\0'; ) {
454 			char buf[10];
455 			i++;
456 			if (i > nfields)
457 				growfldtab(i);
458 			if (freeable(fldtab[i]))
459 				xfree(fldtab[i]->sval);
460 			n = u8_nextlen(r);
461 			for (j = 0; j < n; j++)
462 				buf[j] = *r++;
463 			buf[j] = '\0';
464 			fldtab[i]->sval = tostring(buf);
465 			fldtab[i]->tval = FLD | STR;
466 		}
467 		*fr = 0;
468 	} else if (*r != 0) {	/* if 0, it's a null field */
469 		/* subtle case: if length(FS) == 1 && length(RS > 0)
470 		 * \n is NOT a field separator (cf awk book 61,84).
471 		 * this variable is tested in the inner while loop.
472 		 */
473 		int rtest = '\n';  /* normal case */
474 		if (strlen(*RS) > 0)
475 			rtest = '\0';
476 		for (;;) {
477 			i++;
478 			if (i > nfields)
479 				growfldtab(i);
480 			if (freeable(fldtab[i]))
481 				xfree(fldtab[i]->sval);
482 			fldtab[i]->sval = fr;
483 			fldtab[i]->tval = FLD | STR | DONTFREE;
484 			while (*r != sep && *r != rtest && *r != '\0')	/* \n is always a separator */
485 				*fr++ = *r++;
486 			*fr++ = 0;
487 			if (*r++ == 0)
488 				break;
489 		}
490 		*fr = 0;
491 	}
492 	if (i > nfields)
493 		FATAL("record `%.30s...' has too many fields; can't happen", r);
494 	cleanfld(i+1, lastfld);	/* clean out junk from previous record */
495 	lastfld = i;
496 	donefld = true;
497 	for (j = 1; j <= lastfld; j++) {
498 		double result;
499 
500 		p = fldtab[j];
501 		if(is_number(p->sval, & result)) {
502 			p->fval = result;
503 			p->tval |= NUM;
504 		}
505 	}
506 	setfval(nfloc, (Awkfloat) lastfld);
507 	donerec = true; /* restore */
508 	if (dbg) {
509 		for (j = 0; j <= lastfld; j++) {
510 			p = fldtab[j];
511 			printf("field %d (%s): |%s|\n", j, p->nval, p->sval);
512 		}
513 	}
514 }
515 
516 void cleanfld(int n1, int n2)	/* clean out fields n1 .. n2 inclusive */
517 {				/* nvals remain intact */
518 	Cell *p;
519 	int i;
520 
521 	for (i = n1; i <= n2; i++) {
522 		p = fldtab[i];
523 		if (freeable(p))
524 			xfree(p->sval);
525 		p->sval = EMPTY,
526 		p->tval = FLD | STR | DONTFREE;
527 	}
528 }
529 
530 void newfld(int n)	/* add field n after end of existing lastfld */
531 {
532 	if (n > nfields)
533 		growfldtab(n);
534 	cleanfld(lastfld+1, n);
535 	lastfld = n;
536 	setfval(nfloc, (Awkfloat) n);
537 }
538 
539 void setlastfld(int n)	/* set lastfld cleaning fldtab cells if necessary */
540 {
541 	if (n < 0)
542 		FATAL("cannot set NF to a negative value");
543 	if (n > nfields)
544 		growfldtab(n);
545 
546 	if (lastfld < n)
547 	    cleanfld(lastfld+1, n);
548 	else
549 	    cleanfld(n+1, lastfld);
550 
551 	lastfld = n;
552 }
553 
554 Cell *fieldadr(int n)	/* get nth field */
555 {
556 	if (n < 0)
557 		FATAL("trying to access out of range field %d", n);
558 	if (n > nfields)	/* fields after NF are empty */
559 		growfldtab(n);	/* but does not increase NF */
560 	return(fldtab[n]);
561 }
562 
563 void growfldtab(int n)	/* make new fields up to at least $n */
564 {
565 	int nf = 2 * nfields;
566 	size_t s;
567 
568 	if (n > nf)
569 		nf = n;
570 	s = (nf+1) * (sizeof (struct Cell *));  /* freebsd: how much do we need? */
571 	if (s / sizeof(struct Cell *) - 1 == (size_t)nf) /* didn't overflow */
572 		fldtab = (Cell **) realloc(fldtab, s);
573 	else					/* overflow sizeof int */
574 		xfree(fldtab);	/* make it null */
575 	if (fldtab == NULL)
576 		FATAL("out of space creating %d fields", nf);
577 	makefields(nfields+1, nf);
578 	nfields = nf;
579 }
580 
581 int refldbld(const char *rec, const char *fs)	/* build fields from reg expr in FS */
582 {
583 	/* this relies on having fields[] the same length as $0 */
584 	/* the fields are all stored in this one array with \0's */
585 	char *fr;
586 	int i, tempstat, n;
587 	fa *pfa;
588 
589 	n = strlen(rec);
590 	if (n > fieldssize) {
591 		xfree(fields);
592 		if ((fields = (char *) malloc(n+1)) == NULL)
593 			FATAL("out of space for fields in refldbld %d", n);
594 		fieldssize = n;
595 	}
596 	fr = fields;
597 	*fr = '\0';
598 	if (*rec == '\0')
599 		return 0;
600 	pfa = makedfa(fs, 1);
601 	DPRINTF("into refldbld, rec = <%s>, pat = <%s>\n", rec, fs);
602 	tempstat = pfa->initstat;
603 	for (i = 1; ; i++) {
604 		const size_t fss_rem = fields + fieldssize + 1 - fr;
605 		if (i > nfields)
606 			growfldtab(i);
607 		if (freeable(fldtab[i]))
608 			xfree(fldtab[i]->sval);
609 		fldtab[i]->tval = FLD | STR | DONTFREE;
610 		fldtab[i]->sval = fr;
611 		DPRINTF("refldbld: i=%d\n", i);
612 		if (nematch(pfa, rec)) {
613 			const size_t reclen = patbeg - rec;
614 			pfa->initstat = 2;	/* horrible coupling to b.c */
615 			DPRINTF("match %s (%d chars)\n", patbeg, patlen);
616 			if (reclen >= fss_rem)
617 				FATAL("out of space for fields in refldbld");
618 			memcpy(fr, rec, reclen);
619 			fr += reclen;
620 			*fr++ = '\0';
621 			rec = patbeg + patlen;
622 		} else {
623 			DPRINTF("no match %s\n", rec);
624 			if (strlcpy(fr, rec, fss_rem) >= fss_rem)
625 				FATAL("out of space for fields in refldbld");
626 			pfa->initstat = tempstat;
627 			break;
628 		}
629 	}
630 	return i;
631 }
632 
633 void recbld(void)	/* create $0 from $1..$NF if necessary */
634 {
635 	int i;
636 	char *r, *p;
637 	char *sep = getsval(ofsloc);
638 
639 	if (donerec)
640 		return;
641 	r = record;
642 	for (i = 1; i <= *NF; i++) {
643 		p = getsval(fldtab[i]);
644 		if (!adjbuf(&record, &recsize, 1+strlen(p)+r-record, recsize, &r, "recbld 1"))
645 			FATAL("created $0 `%.30s...' too long", record);
646 		while ((*r = *p++) != 0)
647 			r++;
648 		if (i < *NF) {
649 			if (!adjbuf(&record, &recsize, 2+strlen(sep)+r-record, recsize, &r, "recbld 2"))
650 				FATAL("created $0 `%.30s...' too long", record);
651 			for (p = sep; (*r = *p++) != 0; )
652 				r++;
653 		}
654 	}
655 	if (!adjbuf(&record, &recsize, 2+r-record, recsize, &r, "recbld 3"))
656 		FATAL("built giant record `%.30s...'", record);
657 	*r = '\0';
658 	DPRINTF("in recbld inputFS=%s, fldtab[0]=%p\n", inputFS, (void*)fldtab[0]);
659 
660 	if (freeable(fldtab[0]))
661 		xfree(fldtab[0]->sval);
662 	fldtab[0]->tval = REC | STR | DONTFREE;
663 	fldtab[0]->sval = record;
664 
665 	DPRINTF("in recbld inputFS=%s, fldtab[0]=%p\n", inputFS, (void*)fldtab[0]);
666 	DPRINTF("recbld = |%s|\n", record);
667 	donerec = true;
668 }
669 
670 int	errorflag	= 0;
671 
672 void yyerror(const char *s)
673 {
674 	SYNTAX("%s", s);
675 }
676 
677 void SYNTAX(const char *fmt, ...)
678 {
679 	extern char *cmdname, *curfname;
680 	static int been_here = 0;
681 	va_list varg;
682 
683 	if (been_here++ > 2)
684 		return;
685 	fprintf(stderr, "%s: ", cmdname);
686 	va_start(varg, fmt);
687 	vfprintf(stderr, fmt, varg);
688 	va_end(varg);
689 	fprintf(stderr, " at source line %d", lineno);
690 	if (curfname != NULL)
691 		fprintf(stderr, " in function %s", curfname);
692 	if (compile_time == COMPILING && cursource() != NULL)
693 		fprintf(stderr, " source file %s", cursource());
694 	fprintf(stderr, "\n");
695 	errorflag = 2;
696 	eprint();
697 }
698 
699 extern int bracecnt, brackcnt, parencnt;
700 
701 void bracecheck(void)
702 {
703 	int c;
704 	static int beenhere = 0;
705 
706 	if (beenhere++)
707 		return;
708 	while ((c = input()) != EOF && c != '\0')
709 		bclass(c);
710 	bcheck2(bracecnt, '{', '}');
711 	bcheck2(brackcnt, '[', ']');
712 	bcheck2(parencnt, '(', ')');
713 }
714 
715 void bcheck2(int n, int c1, int c2)
716 {
717 	if (n == 1)
718 		fprintf(stderr, "\tmissing %c\n", c2);
719 	else if (n > 1)
720 		fprintf(stderr, "\t%d missing %c's\n", n, c2);
721 	else if (n == -1)
722 		fprintf(stderr, "\textra %c\n", c2);
723 	else if (n < -1)
724 		fprintf(stderr, "\t%d extra %c's\n", -n, c2);
725 }
726 
727 void FATAL(const char *fmt, ...)
728 {
729 	extern char *cmdname;
730 	va_list varg;
731 
732 	fflush(stdout);
733 	fprintf(stderr, "%s: ", cmdname);
734 	va_start(varg, fmt);
735 	vfprintf(stderr, fmt, varg);
736 	va_end(varg);
737 	error();
738 	if (dbg > 1)		/* core dump if serious debugging on */
739 		abort();
740 	exit(2);
741 }
742 
743 void WARNING(const char *fmt, ...)
744 {
745 	extern char *cmdname;
746 	va_list varg;
747 
748 	fflush(stdout);
749 	fprintf(stderr, "%s: ", cmdname);
750 	va_start(varg, fmt);
751 	vfprintf(stderr, fmt, varg);
752 	va_end(varg);
753 	error();
754 }
755 
756 void error()
757 {
758 	extern Node *curnode;
759 
760 	fprintf(stderr, "\n");
761 	if (compile_time != ERROR_PRINTING) {
762 		if (NR && *NR > 0) {
763 			fprintf(stderr, " input record number %d", (int) (*FNR));
764 			if (strcmp(*FILENAME, "-") != 0)
765 				fprintf(stderr, ", file %s", *FILENAME);
766 			fprintf(stderr, "\n");
767 		}
768 		if (curnode)
769 			fprintf(stderr, " source line number %d", curnode->lineno);
770 		else if (lineno)
771 			fprintf(stderr, " source line number %d", lineno);
772 		if (compile_time == COMPILING && cursource() != NULL)
773 			fprintf(stderr, " source file %s", cursource());
774 		fprintf(stderr, "\n");
775 		eprint();
776 	}
777 }
778 
779 void eprint(void)	/* try to print context around error */
780 {
781 	char *p, *q;
782 	int c;
783 	static int been_here = 0;
784 	extern char ebuf[], *ep;
785 
786 	if (compile_time != COMPILING || been_here++ > 0 || ebuf == ep)
787 		return;
788 	p = ep - 1;
789 	if (p > ebuf && *p == '\n')
790 		p--;
791 	for ( ; p > ebuf && *p != '\n' && *p != '\0'; p--)
792 		;
793 	while (*p == '\n')
794 		p++;
795 	fprintf(stderr, " context is\n\t");
796 	for (q=ep-1; q>=p && *q!=' ' && *q!='\t' && *q!='\n'; q--)
797 		;
798 	for ( ; p < q; p++)
799 		if (*p)
800 			putc(*p, stderr);
801 	fprintf(stderr, " >>> ");
802 	for ( ; p < ep; p++)
803 		if (*p)
804 			putc(*p, stderr);
805 	fprintf(stderr, " <<< ");
806 	if (*ep)
807 		while ((c = input()) != '\n' && c != '\0' && c != EOF) {
808 			putc(c, stderr);
809 			bclass(c);
810 		}
811 	putc('\n', stderr);
812 	ep = ebuf;
813 }
814 
815 void bclass(int c)
816 {
817 	switch (c) {
818 	case '{': bracecnt++; break;
819 	case '}': bracecnt--; break;
820 	case '[': brackcnt++; break;
821 	case ']': brackcnt--; break;
822 	case '(': parencnt++; break;
823 	case ')': parencnt--; break;
824 	}
825 }
826 
827 double errcheck(double x, const char *s)
828 {
829 
830 	if (errno == EDOM) {
831 		errno = 0;
832 		WARNING("%s argument out of domain", s);
833 		x = 1;
834 	} else if (errno == ERANGE) {
835 		errno = 0;
836 		WARNING("%s result out of range", s);
837 		x = 1;
838 	}
839 	return x;
840 }
841 
842 int isclvar(const char *s)	/* is s of form var=something ? */
843 {
844 	const char *os = s;
845 
846 	if (!isalpha((uschar) *s) && *s != '_')
847 		return 0;
848 	for ( ; *s; s++)
849 		if (!(isalnum((uschar) *s) || *s == '_'))
850 			break;
851 	return *s == '=' && s > os;
852 }
853 
854 /* strtod is supposed to be a proper test of what's a valid number */
855 /* appears to be broken in gcc on linux: thinks 0x123 is a valid FP number */
856 /* wrong: violates 4.10.1.4 of ansi C standard */
857 
858 /* well, not quite. As of C99, hex floating point is allowed. so this is
859  * a bit of a mess. We work around the mess by checking for a hexadecimal
860  * value and disallowing it. Similarly, we now follow gawk and allow only
861  * +nan, -nan, +inf, and -inf for NaN and infinity values.
862  */
863 
864 /*
865  * This routine now has a more complicated interface, the main point
866  * being to avoid the double conversion of a string to double, and
867  * also to convey out, if requested, the information that the numeric
868  * value was a leading string or is all of the string. The latter bit
869  * is used in getfval().
870  */
871 
872 bool is_valid_number(const char *s, bool trailing_stuff_ok,
873 			bool *no_trailing, double *result)
874 {
875 	double r;
876 	char *ep;
877 	bool retval = false;
878 	bool is_nan = false;
879 	bool is_inf = false;
880 
881 	if (no_trailing)
882 		*no_trailing = false;
883 
884 	while (isspace((uschar)*s))
885 		s++;
886 
887 	/* no hex floating point, sorry */
888 	if (s[0] == '0' && tolower((uschar)s[1]) == 'x')
889 		return false;
890 
891 	/* allow +nan, -nan, +inf, -inf, any other letter, no */
892 	if (s[0] == '+' || s[0] == '-') {
893 		is_nan = (strncasecmp(s+1, "nan", 3) == 0);
894 		is_inf = (strncasecmp(s+1, "inf", 3) == 0);
895 		if ((is_nan || is_inf)
896 		    && (isspace((uschar)s[4]) || s[4] == '\0'))
897 			goto convert;
898 		else if (! isdigit((uschar)s[1]) && s[1] != '.')
899 			return false;
900 	}
901 	else if (! isdigit((uschar)s[0]) && s[0] != '.')
902 		return false;
903 
904 convert:
905 	errno = 0;
906 	r = strtod(s, &ep);
907 	if (ep == s || errno == ERANGE)
908 		return false;
909 
910 	if (isnan(r) && s[0] == '-' && signbit(r) == 0)
911 		r = -r;
912 
913 	if (result != NULL)
914 		*result = r;
915 
916 	/*
917 	 * check for trailing stuff
918 	 */
919 	while (isspace((uschar)*ep))
920 		ep++;
921 
922 	if (no_trailing != NULL)
923 		*no_trailing = (*ep == '\0');
924 
925 	/* return true if found the end, or trailing stuff is allowed */
926 	retval = *ep == '\0' || trailing_stuff_ok;
927 
928 	return retval;
929 }
930