xref: /netbsd/usr.bin/config/scan.l (revision 6a8b577c)
1 %{
2 /*	$NetBSD: scan.l,v 1.34 2021/09/10 21:52:17 rillig Exp $	*/
3 
4 /*
5  * Copyright (c) 1992, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * This software was developed by the Computer Systems Engineering group
9  * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
10  * contributed to Berkeley.
11  *
12  * All advertising materials mentioning features or use of this software
13  * must display the following acknowledgement:
14  *	This product includes software developed by the University of
15  *	California, Lawrence Berkeley Laboratories.
16  *
17  * Redistribution and use in source and binary forms, with or without
18  * modification, are permitted provided that the following conditions
19  * are met:
20  * 1. Redistributions of source code must retain the above copyright
21  *    notice, this list of conditions and the following disclaimer.
22  * 2. Redistributions in binary form must reproduce the above copyright
23  *    notice, this list of conditions and the following disclaimer in the
24  *    documentation and/or other materials provided with the distribution.
25  * 3. Neither the name of the University nor the names of its contributors
26  *    may be used to endorse or promote products derived from this software
27  *    without specific prior written permission.
28  *
29  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
30  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
32  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
33  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
34  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
35  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
37  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
38  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
39  * SUCH DAMAGE.
40  *
41  *	from: @(#)scan.l	8.1 (Berkeley) 6/6/93
42  */
43 
44 #include <sys/cdefs.h>
45 __RCSID("$NetBSD: scan.l,v 1.34 2021/09/10 21:52:17 rillig Exp $");
46 
47 #include <sys/param.h>
48 #include <errno.h>
49 #include <libgen.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <unistd.h>
54 #include <stddef.h>
55 #include <ctype.h>
56 #include <util.h>
57 #undef ECHO
58 #include "defs.h"
59 #include "gram.h"
60 
61 int	yyline;
62 const char *yyfile;
63 const char *lastfile;
64 char curinclpath[PATH_MAX];
65 uint64_t ifdefstate;
66 int	 ifdefshift = -1;
67 
68 /*
69  * The state is represented by 3 bits.
70  */
71 #define	IDS_ENABLED	1ll
72 #define	IDS_MATCH	2ll
73 #define	IDS_ELIF	4ll
74 #define	IDS_ELSE	8ll
75 
76 #define	IDS_BITS	0xf
77 #define	IDS_SHIFT	4
78 
79 #define	IDS_ISMATCH(st) (((st) & IDS_MATCH) != 0)
80 #define	IDS_ISENABLED(st) (((st) & IDS_ENABLED) != 0)
81 #define	IDS_PARENT_DISABLED \
82 	(ifdefshift > 0 && !IDS_ISENABLED(ifdefstate >> IDS_SHIFT))
83 #define IDS_MAX_DEPTH	16 /* 64 / 4 */
84 
85 #ifdef	IDS_DEBUG
86 # define IDS_PRINT(s, st, x) \
87 	do { \
88 		for (int i = 0; i < ifdefshift + 1; i++) \
89 			fprintf(stderr, " "); \
90 		printf("%s%s [%d,%d,%d] %#" PRIx64 "\n", x, # s, \
91 		    IDS_PARENT_DISABLED, IDS_ISMATCH(st), getcurifdef(), \
92 		    ifdefstate); \
93 	} while (0)
94 #else
95 # define IDS_PRINT(s, st, x) ((void)0)
96 #endif
97 
98 #define	IDS_ENTER(s, st) \
99 	IDS_PRINT(s, st, ">")
100 #define	IDS_EXIT(s, st) \
101 	IDS_PRINT(s, st, "<")
102 
103 /*
104  * Data for returning to previous files from include files.
105  */
106 struct incl {
107 	struct	incl *in_prev;	/* previous includes in effect, if any */
108 	YY_BUFFER_STATE in_buf;	/* previous lex state */
109 	struct where in_where;
110 	int	in_ateof;	/* token to insert at EOF */
111 	int	in_interesting;	/* previous value for "interesting" */
112 	uint64_t	in_ifdefstate;	/* conditional level */
113 	int	in_ifdefshift;	/* conditional level */
114 };
115 static struct incl *incl;
116 static int endinclude(void);
117 static int getincludepath(void);
118 static int getcurifdef(void);
119 
120 SLIST_HEAD(, prefix)	curdirs;	/* curdir stack */
121 
122 %}
123 
124 %option  noyywrap nounput noinput
125 
126 PATH	[A-Za-z_0-9]*[./][-A-Za-z_0-9./]*
127 QCHARS	\"(\\.|[^\\"])*\"
128 WORD	[A-Za-z_][-A-Za-z_0-9]*
129 FILENAME	({PATH}|{QCHARS})
130 RESTOFLINE	[ \t]*(#[^\n]*)?\n
131 WS	^[ \t]*
132 
133 %x	IGNORED
134 
135 %%
136 		/* Local variables for yylex() */
137 		int tok;
138 
139 and		return AND;
140 at		return AT;
141 attach		return ATTACH;
142 block		return BLOCK;
143 build		return BUILD;
144 char		return CHAR;
145 compile-with	return COMPILE_WITH;
146 config		return CONFIG;
147 deffs		return DEFFS;
148 define		return DEFINE;
149 defflag		return DEFFLAG;
150 defopt		return DEFOPT;
151 defparam	return DEFPARAM;
152 defpseudo	return DEFPSEUDO;
153 defpseudodev	return DEFPSEUDODEV;
154 devclass	return DEVCLASS;
155 device		return DEVICE;
156 device-major	return DEVICE_MAJOR;
157 dumps		return DUMPS;
158 file		return XFILE;
159 file-system	return FILE_SYSTEM;
160 flags		return FLAGS;
161 ident		return IDENT;
162 ioconf		return IOCONF;
163 linkzero	return LINKZERO;
164 machine		return XMACHINE;
165 major		return MAJOR;
166 makeoptions	return MAKEOPTIONS;
167 maxpartitions	return MAXPARTITIONS;
168 maxusers	return MAXUSERS;
169 minor		return MINOR;
170 needs-count	return NEEDS_COUNT;
171 needs-flag	return NEEDS_FLAG;
172 no		return NO;
173 -no		return CNO;
174 object		return XOBJECT;
175 obsolete	return OBSOLETE;
176 on		return ON;
177 options		return OPTIONS;
178 prefix		return PREFIX;
179 buildprefix	return BUILDPREFIX;
180 pseudo-device	return PSEUDO_DEVICE;
181 pseudo-root	return PSEUDO_ROOT;
182 root		return ROOT;
183 select		return SELECT;
184 single		return SINGLE;
185 source		return SOURCE;
186 type		return TYPE;
187 vector 		return VECTOR;
188 version 	return VERSION;
189 with		return WITH;
190 
191 \+=		return PLUSEQ;
192 :=		return COLONEQ;
193 
194 <*>{WS}ifdef[ \t]+{WORD}{RESTOFLINE} {
195 		ifdefstate <<= IDS_SHIFT;
196 		if (++ifdefshift >= IDS_MAX_DEPTH) {
197 			yyerror("too many levels of conditional");
198 		}
199 		IDS_ENTER(ifdef, 0);
200 		if (IDS_PARENT_DISABLED || !getcurifdef()) {
201 			ifdefstate &= (uint64_t)~IDS_ENABLED;
202 			BEGIN(IGNORED);
203 		} else {
204 			ifdefstate |= IDS_MATCH|IDS_ENABLED;
205 			BEGIN(INITIAL);
206 		}
207 		IDS_EXIT(ifdef, 0);
208 		yyline++;
209 	}
210 
211 <*>{WS}ifndef[ \t]+{WORD}{RESTOFLINE} {
212 		ifdefstate <<= IDS_SHIFT;
213 		if (++ifdefshift >= IDS_MAX_DEPTH) {
214 			yyerror("too many levels of conditional");
215 		}
216 		IDS_ENTER(ifndef, 0);
217 		if (IDS_PARENT_DISABLED || getcurifdef()) {
218 			ifdefstate &= (uint64_t)~IDS_ENABLED;
219 			BEGIN(IGNORED);
220 		} else {
221 			ifdefstate |= IDS_MATCH|IDS_ENABLED;
222 			BEGIN(INITIAL);
223 		}
224 		IDS_EXIT(ifndef, 0);
225 		yyline++;
226 	}
227 
228 
229 <*>{WS}elifdef[ \t]+{WORD}{RESTOFLINE} {
230 		int st = ifdefstate & IDS_BITS;
231 		IDS_ENTER(elifdef, st);
232 		if (ifdefshift == -1 || (st & IDS_ELSE) != 0) {
233 			yyerror("mismatched elifdef");
234 		}
235 		if (IDS_PARENT_DISABLED || IDS_ISMATCH(st) || !getcurifdef()) {
236 			ifdefstate &= (uint64_t)~IDS_ENABLED;
237 			BEGIN(IGNORED);
238 		} else {
239 			ifdefstate |= IDS_MATCH|IDS_ENABLED;
240 			BEGIN(INITIAL);
241 		}
242 		ifdefstate |= IDS_ELIF;
243 		IDS_EXIT(elifdef, st);
244 		yyline++;
245 	}
246 
247 <*>{WS}elifndef[ \t]+{WORD}{RESTOFLINE} {
248 		int st = ifdefstate & IDS_BITS;
249 		IDS_ENTER(elifndef, st);
250 		if (ifdefshift == -1 || (st & IDS_ELSE) != 0) {
251 			yyerror("mismatched elifndef");
252 		}
253 		if (IDS_PARENT_DISABLED || IDS_ISMATCH(st) || getcurifdef()) {
254 			ifdefstate &= (uint64_t)~IDS_ENABLED;
255 			BEGIN(IGNORED);
256 		} else {
257 			ifdefstate |= IDS_MATCH|IDS_ENABLED;
258 			BEGIN(INITIAL);
259 		}
260 		ifdefstate |= IDS_ELIF;
261 		IDS_EXIT(elifndef, st);
262 		yyline++;
263 	}
264 
265 <*>{WS}else{RESTOFLINE} {
266 		int st = ifdefstate & IDS_BITS;
267 		IDS_ENTER(else, st);
268 		if (ifdefshift == -1 || (st & IDS_ELSE) != 0) {
269 			yyerror("mismatched else");
270 		}
271 		if (IDS_PARENT_DISABLED || IDS_ISMATCH(st)) {
272 			ifdefstate &= (uint64_t)~IDS_ENABLED;
273 			BEGIN(IGNORED);
274 		} else {
275 			ifdefstate |= IDS_MATCH|IDS_ENABLED;
276 			BEGIN(INITIAL);
277 		}
278 		ifdefstate |= IDS_ELSE;
279 		IDS_ENTER(else, st);
280 		yyline++;
281 	}
282 
283 <*>{WS}endif{RESTOFLINE} {
284 		IDS_ENTER(endif, 0);
285 		if (ifdefshift == -1) {
286 			yyerror("mismatched endif");
287 		}
288 		if (!IDS_PARENT_DISABLED) {
289 			BEGIN(INITIAL);
290 		}
291 		IDS_EXIT(endif, 0);
292 		ifdefshift--;
293 		ifdefstate >>= IDS_SHIFT;
294 		yyline++;
295 	}
296 
297 <IGNORED>\n		{
298 		yyline++;
299 	}
300 
301 <IGNORED>.	/* ignore */
302 
303 include[ \t]+{FILENAME}{RESTOFLINE}	{
304 		yyline++;
305 		if (getincludepath()) {
306 			include(curinclpath, 0, 0, 1);
307 		} else {
308 			yyerror("bad include path-name");
309 		}
310 	}
311 
312 cinclude[ \t]+{FILENAME}{RESTOFLINE}	{
313 		yyline++;
314 		if (getincludepath()) {
315 			include(curinclpath, 0, 1, 1);
316 		} else {
317 			yyerror("bad cinclude path-name");
318 		}
319 	}
320 
321 package[ \t]+{FILENAME}{RESTOFLINE}	{
322 		yyline++;
323 		if (!oktopackage) {
324 			yyerror("package not allowed here");
325 		} else if (getincludepath()) {
326 			package(curinclpath);
327 		} else {
328 			yyerror("bad package path-name");
329 		}
330 	}
331 
332 {PATH}	{
333 		yylval.str = intern(yytext);
334 		return PATHNAME;
335 	}
336 
337 {WORD}	{
338 		yylval.str = intern(yytext);
339 		return WORD;
340 	}
341 
342 \"\" {
343 		yylval.str = intern("");
344 		return EMPTYSTRING;
345 	}
346 
347 {QCHARS}	{
348 		size_t l = strlen(yytext);
349 		if (l > 1 && yytext[l - 1] == '"')
350 			yytext[l - 1] = '\0';
351 
352 		yylval.str = intern(yytext + 1);
353 		return QSTRING;
354 	}
355 0[0-7]*	{
356 		yylval.num.fmt = 8;
357 		yylval.num.val = strtoll(yytext, NULL, 8);
358 		return NUMBER;
359 	}
360 0[xX][0-9a-fA-F]+ {
361 		yylval.num.fmt = 16;
362 		yylval.num.val = (long long)strtoull(yytext + 2, NULL, 16);
363 		return NUMBER;
364 	}
365 [1-9][0-9]* {
366 		yylval.num.fmt = 10;
367 		yylval.num.val = strtoll(yytext, NULL, 10);
368 		return NUMBER;
369 	}
370 \n[ \t] {
371 		/*
372 		 * Note: newline followed by whitespace is always a
373 		 * continuation of the previous line, so do NOT
374 		 * return a token in this case.
375 		 */
376 		yyline++;
377 	}
378 \n	{
379 		yyline++;
380 		return '\n';
381 	}
382 \00	{
383 		/* Detect NUL characters in the config file and
384 		 * error out.
385 		 */
386 		cfgerror("NUL character detected at line %i", yyline);
387 	}
388 #.*	{ /* ignored (comment) */; }
389 [ \t]+	{ /* ignored (white space) */; }
390 .	{ return yytext[0]; }
391 <*><<EOF>> {
392 		if (ifdefshift > (incl == NULL ? -1 : incl->in_ifdefshift)) {
393 			yyerror("reached EOF while looking for endif");
394 		}
395 		if (incl == NULL)
396 			return YY_NULL;
397 		tok = endinclude();
398 		if (tok)
399 			return tok;
400 		/* otherwise continue scanning */
401 	}
402 
403 %%
404 
405 int interesting = 1;
406 
407 static int
408 curdir_push(const char *fname)
409 {
410 	struct prefix *pf;
411 	char *p, *d, *f;
412 
413 	/* Set up the initial "current directory" for include directives. */
414 	d = dirname(f = estrdup(fname));
415 	if (*d == '/')
416 		p = estrdup(d);
417 	else {
418 		char *cwd, buf[PATH_MAX];
419 
420 		if ((cwd = getcwd(buf, sizeof(buf))) == NULL) {
421 			free(f);
422 			return (-1);
423 		}
424 		easprintf(&p, "%s/%s", cwd, d);
425 	}
426 	free(f);
427 	pf = ecalloc(1, sizeof(*pf));
428 	pf->pf_prefix = p;
429 	SLIST_INSERT_HEAD(&curdirs, pf, pf_next);
430 
431 	return (0);
432 }
433 
434 static void
435 curdir_pop(void)
436 {
437 	struct prefix *pf;
438 
439 	pf = SLIST_FIRST(&curdirs);
440 	SLIST_REMOVE_HEAD(&curdirs, pf_next);
441 	if (SLIST_EMPTY(&curdirs))
442 		panic("curdirs is empty");
443 	/* LINTED cast away const (pf_prefix is malloc'd for curdirs) */
444 	free((void *)__UNCONST(pf->pf_prefix));
445 	free(pf);
446 }
447 
448 /*
449  * Open the "main" file (conffile).
450  */
451 int
452 firstfile(const char *fname)
453 {
454 
455 #if defined(__NetBSD__)
456 	if ((yyin = fopen(fname, "rf")) == NULL)
457 #else
458 	if ((yyin = fopen(fname, "r")) == NULL)
459 #endif
460 		return (-1);
461 
462 	if (curdir_push(fname) == -1)
463 		return (-1);
464 
465 	yyfile = conffile = fname;
466 	yyline = 1;
467 	return (0);
468 }
469 
470 /*
471  * Add a "package" to the configuration.  This is essentially
472  * syntactic sugar around the sequence:
473  *
474  *	prefix ../some/directory
475  *	include "files.package"
476  *	prefix
477  */
478 void
479 package(const char *fname)
480 {
481 	char *fname1 = estrdup(fname);
482 	char *fname2 = estrdup(fname);
483 	char *dir = dirname(fname1);
484 	char *file = basename(fname2);
485 
486 	/*
487 	 * Push the prefix on to the prefix stack and process the include
488 	 * file.  When we reach the end of the include file, inserting
489 	 * the PREFIX token into the input stream will pop the prefix off
490 	 * of the prefix stack.
491 	 */
492 	prefix_push(dir);
493 	(void) include(file, PREFIX, 0, 1);
494 
495 	free(fname1);
496 	free(fname2);
497 }
498 
499 int includedepth;
500 
501 /*
502  * Open the named file for inclusion at the current point.  Returns 0 on
503  * success (file opened and previous state pushed), nonzero on failure
504  * (fopen failed, complaint made).  The `ateof' parameter controls the
505  * token to be inserted at the end of the include file (i.e. ENDFILE).
506  * If ateof == 0 then nothing is inserted.
507  */
508 int
509 include(const char *fname, int ateof, int conditional, int direct)
510 {
511 	FILE *fp;
512 	struct incl *in;
513 	char *s;
514 	static int havedirs;
515 	extern int vflag;
516 
517 	if (havedirs == 0) {
518 		havedirs = 1;
519 		setupdirs();
520 	}
521 
522 	if (fname[0] == '/')
523 		s = estrdup(fname);
524 	else if (fname[0] == '.' && fname[1] == '/') {
525 		struct prefix *pf = SLIST_FIRST(&curdirs);
526 		easprintf(&s, "%s/%s", pf->pf_prefix, fname + 2);
527 	} else
528 		s = sourcepath(fname);
529 	if ((fp = fopen(s, "r")) == NULL) {
530 		if (conditional == 0)
531 			cfgerror("cannot open %s for reading: %s", s,
532 			    strerror(errno));
533 		else if (vflag)
534 			cfgwarn("cannot open conditional include file %s: %s",
535 			     s, strerror(errno));
536 		free(s);
537 		return (-1);
538 	}
539 	if (curdir_push(s) == -1) {
540 		cfgerror("cannot record current working directory for %s", s);
541 		fclose(fp);
542 		free(s);
543 		return (-1);
544 	}
545 	in = ecalloc(1, sizeof *in);
546 	in->in_prev = incl;
547 	in->in_buf = YY_CURRENT_BUFFER;
548 	in->in_where.w_srcfile = yyfile;
549 	in->in_where.w_srcline = (u_short)yyline;
550 	in->in_ateof = ateof;
551 	in->in_interesting = interesting;
552 	in->in_ifdefstate = ifdefstate;
553 	in->in_ifdefshift = ifdefshift;
554 	interesting = direct & interesting;
555 	if (interesting)
556 		logconfig_include(fp, fname);
557 	incl = in;
558 	CFGDBG(1, "include `%s'", fname);
559 	yy_switch_to_buffer(yy_create_buffer(fp, YY_BUF_SIZE));
560 	yyfile = intern(s);
561 	yyline = 1;
562 	free(s);
563 	includedepth++;
564 	return (0);
565 }
566 
567 /*
568  * Extract the pathname from a include/cinclude/package into curinclpath
569  */
570 static int
571 getincludepath(void)
572 {
573 	const char *p = yytext;
574 	ptrdiff_t len;
575 	const char *e;
576 
577 	while (*p && isascii((unsigned int)*p) && !isspace((unsigned int)*p))
578 		p++;
579 	while (*p && isascii((unsigned int)*p) && isspace((unsigned int)*p))
580 		p++;
581 	if (!*p)
582 		return 0;
583 	if (*p == '"') {
584 		p++;
585 		e = strchr(p, '"');
586 		if (!e) return 0;
587 	} else {
588 		e = p;
589 		while (*e && isascii((unsigned int)*e)
590 		    && !isspace((unsigned int)*e))
591 			e++;
592 	}
593 
594 	len = e-p;
595 	if (len > (ptrdiff_t)sizeof(curinclpath)-1)
596 		len = sizeof(curinclpath)-1;
597 	strncpy(curinclpath, p, sizeof(curinclpath));
598 	curinclpath[len] = '\0';
599 
600 	return 1;
601 }
602 
603 /*
604  * Terminate the most recent inclusion.
605  */
606 static int
607 endinclude(void)
608 {
609 	struct incl *in;
610 	int ateof;
611 
612 	curdir_pop();
613 	if ((in = incl) == NULL)
614 		panic("endinclude");
615 	incl = in->in_prev;
616 	lastfile = yyfile;
617 	yy_delete_buffer(YY_CURRENT_BUFFER);
618 	(void)fclose(yyin);
619 	yy_switch_to_buffer(in->in_buf);
620 	yyfile = in->in_where.w_srcfile;
621 	yyline = in->in_where.w_srcline;
622 	ateof  = in->in_ateof;
623 	interesting = in->in_interesting;
624 	ifdefstate = in->in_ifdefstate;
625 	ifdefshift = in->in_ifdefshift;
626 	free(in);
627 
628 	includedepth--;
629 
630 	return (ateof);
631 }
632 
633 /*
634  * Return the current line number.  If yacc has looked ahead and caused
635  * us to consume a newline, we have to subtract one.  yychar is yacc's
636  * token lookahead, so we can tell.
637  */
638 u_short
639 currentline(void)
640 {
641 	extern int yychar;
642 
643 	return (u_short)(yyline - (yychar == '\n'));
644 }
645 
646 static int
647 getcurifdef(void)
648 {
649 	char *p = yytext, *q;
650 
651 	while (*p && isascii((unsigned int)*p) && !isspace((unsigned int)*p))
652 		p++;
653 	while (*p && isascii((unsigned int)*p) && isspace((unsigned int)*p))
654 		p++;
655 	q = p;
656 	while (*q && isascii((unsigned int)*q) && !isspace((unsigned int)*q))
657 		q++;
658 	*q = '\0';
659 
660 	return ht_lookup(attrtab, intern(p)) != NULL;
661 }
662