Blob


1 /*
2 * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
3 * Copyright (c) 2018 Florian Obser <florian@openbsd.org>
4 * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
5 * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
6 * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
7 * Copyright (c) 2001 Markus Friedl. All rights reserved.
8 * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
9 * Copyright (c) 2001 Theo de Raadt. All rights reserved.
10 *
11 * Permission to use, copy, modify, and distribute this software for any
12 * purpose with or without fee is hereby granted, provided that the above
13 * copyright notice and this permission notice appear in all copies.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
16 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
18 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
21 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22 */
24 %{
26 #include "compat.h"
28 #include <ctype.h>
29 #include <inttypes.h>
30 #include <limits.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <syslog.h>
36 #include "log.h"
37 #include "kamid.h"
38 #include "utils.h"
40 #include "script.h"
42 TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
43 static struct file {
44 TAILQ_ENTRY(file) entry;
45 FILE *stream;
46 char *name;
47 size_t ungetpos;
48 size_t ungetsize;
49 u_char *ungetbuf;
50 int eof_reached;
51 int lineno;
52 int errors;
53 } *file, *topfile;
54 struct file *pushfile(const char *);
55 int popfile(void);
56 int yyparse(void);
57 int yylex(void);
58 int yyerror(const char *, ...)
59 __attribute__((__format__ (printf, 1, 2)))
60 __attribute__((__nonnull__ (1)));
61 int kw_cmp(const void *, const void *);
62 int lookup(char *);
63 int igetc(void);
64 int lgetc(int);
65 void lungetc(int);
66 int findeol(void);
68 static int shouldfail;
70 typedef struct {
71 union {
72 struct op *op;
73 struct proc *proc;
74 char *str;
75 int64_t num;
76 } v;
77 int lineno;
78 } YYSTYPE;
80 %}
82 /*
83 * for bison:
84 * %define parse.error verbose
85 */
87 %token ASSERT
88 %token CONST
89 %token DIR
90 %token ERROR
91 %token INCLUDE
92 %token PROC
93 %token REPEAT
94 %token SHOULD_FAIL STR
95 %token TESTING
96 %token U8 U16 U32
98 %token <v.str> STRING SYMBOL
99 %token <v.num> NUMBER
101 %type <v.op> cast cexpr check expr faccess funcall
102 %type <v.op> literal sfail var varref
104 %type <v.proc> procname
106 %%
108 program : /* empty */
109 | program '\n'
110 | program include '\n'
111 | program const '\n'
112 | program proc '\n'
113 | program test '\n'
116 optnl : '\n' optnl /* zero or more newlines */
117 | /*empty*/
120 nl : '\n' optnl ;
122 include : INCLUDE STRING {
123 struct file *nfile;
125 if ((nfile = pushfile($2)) == NULL) {
126 yyerror("failed to include file %s", $2);
127 free($2);
128 YYERROR;
130 free($2);
132 file = nfile;
133 lungetc('\n');
137 const : CONST consti
138 | CONST '(' optnl mconst ')'
141 mconst : consti nl | mconst consti nl ;
143 consti : SYMBOL '=' expr {
144 if (!global_set($1, $3)) {
145 yyerror("can't set %s: illegal expression", $1);
146 free($1);
147 free_op($3);
148 YYERROR;
153 var : SYMBOL '=' expr { $$ = op_assign($1, $3); } ;
154 varref : SYMBOL { $$ = op_var($1); } ;
155 literal : STRING { $$ = op_lit_str($1); }
156 | NUMBER { $$ = op_lit_num($1); } ;
158 /*
159 * `expr '=' '=' expr` is ambiguous. furthermore, we're not
160 * interested in checking all the possibilities here.
161 */
162 cexpr : literal | varref | funcall | faccess ;
163 check : cexpr '=' '=' cexpr { $$ = op_cmp_eq($1, $4); } ;
165 expr : literal | funcall | varref | check | cast | faccess ;
167 cast : expr ':' U8 { $$ = op_cast($1, V_U8); }
168 | expr ':' U16 { $$ = op_cast($1, V_U16); }
169 | expr ':' U32 { $$ = op_cast($1, V_U32); }
170 | expr ':' STR { $$ = op_cast($1, V_STR); }
173 faccess : varref '.' SYMBOL { $$ = op_faccess($1, $3); }
174 | faccess '.' SYMBOL { $$ = op_faccess($1, $3); }
177 procname: SYMBOL {
178 if (($$ = proc_by_name($1)) == NULL) {
179 yyerror("unknown proc %s", $1);
180 free($1);
181 YYERROR;
183 free($1);
187 funcall : procname {
188 prepare_funcall();
189 } '(' args optcomma ')' {
190 struct proc *proc;
191 int argc;
193 $$ = op_funcall($1);
194 proc = $$->v.funcall.proc;
195 argc = $$->v.funcall.argc;
197 if (argc != proc->minargs && !proc->vararg) {
198 yyerror("invalid arity for `%s': want %d arguments "
199 "but %d given.", $1->name, proc->minargs, argc);
200 /* TODO: recursively free $$ */
201 YYERROR;
204 if (argc < proc->minargs && proc->vararg) {
205 yyerror("invalid arity for `%s': want at least %d "
206 "arguments but %d given.", $1->name, proc->minargs,
207 argc);
208 /* TODO: recursively free $$ */
209 YYERROR;
214 optcomma: /* empty */ | ',' ;
216 dots : '.' '.' '.' ;
218 args : /* empty */
219 | args ',' expr { push_arg($3); }
220 | args ',' dots { push_arg(op_rest()); }
221 | expr { push_arg($1); }
222 | dots { push_arg(op_rest()); }
225 proc : PROC SYMBOL {
226 prepare_proc();
227 } '(' args ')' {
228 if (!proc_setup_body()) {
229 yyerror("invalid argument in proc `%s' definition",
230 $2);
231 free($2);
232 YYERROR;
234 } '{' optnl block '}' {
235 proc_done($2);
239 block : /* empty */
240 | block var nl { block_push($2); }
241 | block funcall nl { block_push($2); }
242 | block assert nl
243 | block sfail nl { block_push($2); }
246 sfail : SHOULD_FAIL expr { $$ = op_sfail($2, NULL); }
247 | SHOULD_FAIL expr ':' STRING { $$ = op_sfail($2, $4); }
250 assert : ASSERT asserti
251 | ASSERT '(' optnl massert ')'
254 massert : asserti nl | massert asserti nl ;
256 asserti : check { block_push(op_assert($1)); }
259 test : TESTING STRING DIR STRING {
260 prepare_test();
261 } testopt '{' optnl block '}' {
262 test_done(shouldfail, $2, $4);
263 shouldfail = 0;
267 testopt : /* empty */
268 | SHOULD_FAIL { shouldfail = 1; }
271 %%
273 struct keywords {
274 const char *k_name;
275 int k_val;
276 };
278 int
279 yyerror(const char *fmt, ...)
281 va_list ap;
282 char *msg;
284 file->errors++;
285 va_start(ap, fmt);
286 if (vasprintf(&msg, fmt, ap) == -1)
287 fatalx("yyerror vasprintf");
288 va_end(ap);
289 logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
290 free(msg);
291 return 0;
294 int
295 kw_cmp(const void *k, const void *e)
297 return strcmp(k, ((const struct keywords *)e)->k_name);
300 int
301 lookup(char *s)
303 /* This has to be sorted always. */
304 static const struct keywords keywords[] = {
305 {"assert", ASSERT},
306 {"const", CONST},
307 {"dir", DIR},
308 {"include", INCLUDE},
309 {"proc", PROC},
310 {"repeat", REPEAT},
311 {"should-fail", SHOULD_FAIL},
312 {"str", STR},
313 {"testing", TESTING},
314 {"u16", U16},
315 {"u32", U32},
316 {"u8", U8},
317 };
318 const struct keywords *p;
320 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
321 sizeof(keywords[0]), kw_cmp);
323 if (p)
324 return p->k_val;
325 else
326 return SYMBOL;
329 #define START_EXPAND 1
330 #define DONE_EXPAND 2
332 static int expanding;
334 int
335 igetc(void)
337 int c;
339 while (1) {
340 if (file->ungetpos > 0)
341 c = file->ungetbuf[--file->ungetpos];
342 else
343 c = getc(file->stream);
345 if (c == START_EXPAND)
346 expanding = 1;
347 else if (c == DONE_EXPAND)
348 expanding = 0;
349 else
350 break;
352 return c;
355 int
356 lgetc(int quotec)
358 int c, next;
360 if (quotec) {
361 if ((c = igetc()) == EOF) {
362 yyerror("reached end of file while parsing "
363 "quoted string");
364 if (file == topfile || popfile() == EOF)
365 return EOF;
366 return quotec;
368 return c;
371 while ((c = igetc()) == '\\') {
372 next = igetc();
373 if (next != '\n') {
374 c = next;
375 break;
377 yylval.lineno = file->lineno;
378 file->lineno++;
381 if (c == EOF) {
382 /*
383 * Fake EOL when hit EOF for the first time. This gets line
384 * count right if last line in included file is syntactically
385 * invalid and has no newline.
386 */
387 if (file->eof_reached == 0) {
388 file->eof_reached = 1;
389 return '\n';
391 while (c == EOF) {
392 if (file == topfile || popfile() == EOF)
393 return EOF;
394 c = igetc();
397 return c;
400 void
401 lungetc(int c)
403 if (c == EOF)
404 return;
406 if (file->ungetpos >= file->ungetsize) {
407 void *p = reallocarray(file->ungetbuf, file->ungetsize, 2);
408 if (p == NULL)
409 err(1, "lungetc");
410 file->ungetbuf = p;
411 file->ungetsize *= 2;
413 file->ungetbuf[file->ungetpos++] = c;
416 int
417 findeol(void)
419 int c;
421 /* Skip to either EOF or the first real EOL. */
422 while (1) {
423 c = lgetc(0);
424 if (c == '\n') {
425 file->lineno++;
426 break;
428 if (c == EOF)
429 break;
431 return ERROR;
435 #if 0
436 int my_yylex(void);
438 int
439 yylex(void)
441 int x;
443 switch (x = my_yylex()) {
444 case ASSERT: puts("assert"); break;
445 case CONST: puts("const"); break;
446 case DIR: puts("dir"); break;
447 case ERROR: puts("error"); break;
448 case INCLUDE: puts("include"); break;
449 case PROC: puts("proc"); break;
450 case REPEAT: puts("repeat"); break;
451 case STR: puts(":str"); break;
452 case TESTING: puts("testing"); break;
453 case U8: puts(":u8"); break;
454 case U16: puts(":u16"); break;
455 case U32: puts(":u32"); break;
457 case STRING: printf("string \"%s\"\n", yylval.v.str); break;
458 case SYMBOL: printf("symbol %s\n", yylval.v.str); break;
459 case NUMBER: printf("number %"PRIu64"\n", yylval.v.num); break;
461 default:
462 printf("character ");
463 if (x == '\n')
464 printf("\\n");
465 else
466 printf("%c", x);
467 printf(" [0x%x]", x);
468 printf("\n");
469 break;
472 return x;
475 int
476 my_yylex(void)
477 #else
478 int
479 yylex(void)
480 #endif
482 unsigned char buf[8096];
483 unsigned char *p;
484 int quotec, next, c;
485 int token;
487 p = buf;
488 while ((c = lgetc(0)) == ' ' || c == '\t' || c == '\f')
489 ; /* nop */
491 yylval.lineno = file->lineno;
492 if (c == '#')
493 while ((c = lgetc(0)) != '\n' && c != EOF)
494 ; /* nop */
496 switch (c) {
497 case ':':
498 return c;
499 break;
500 case '\'':
501 case '\"':
502 quotec = c;
503 while (1) {
504 if ((c = lgetc(quotec)) == EOF)
505 return 0;
506 if (c == '\n') {
507 file->lineno++;
508 continue;
509 } else if (c == '\\') {
510 if ((next = lgetc(quotec)) == EOF)
511 return 0;
512 if (next == quotec || next == ' ' ||
513 next == '\t')
514 c = next;
515 else if (next == '\n') {
516 file->lineno++;
517 continue;
518 } else
519 lungetc(next);
520 } else if (c == quotec) {
521 *p = '\0';
522 break;
523 } else if (c == '\0') {
524 yyerror("syntax error");
525 return findeol();
528 if (p + 1 >= buf + sizeof(buf) - 1) {
529 yyerror("string too long");
530 return findeol();
533 *p++ = c;
536 yylval.v.str = xstrdup(buf);
537 return STRING;
540 #define allowed_to_end_number(x) \
541 (isspace(x) || x == ')' || x == ',' || x == '/' || x == '}' \
542 || x == '=' || x == ':')
544 if (c == '-' || isdigit(c)) {
545 do {
546 *p++ = c;
547 if ((size_t)(p-buf) >= sizeof(buf)) {
548 yyerror("string too long");
549 return findeol();
551 } while ((c = lgetc(0)) != EOF && isdigit(c));
552 lungetc(c);
553 if (p == buf + 1 && buf[0] == '-')
554 goto nodigits;
555 if (c == EOF || allowed_to_end_number(c)) {
556 const char *errstr = NULL;
558 *p = '\0';
559 yylval.v.num = strtonum(buf, INT64_MIN, INT64_MAX,
560 &errstr);
561 if (errstr) {
562 yyerror("\"%s\" invalid number: %s",
563 buf, errstr);
564 return findeol();
566 return NUMBER;
567 } else {
568 nodigits:
569 while (p > buf + 1)
570 lungetc(*--p);
571 c = *--p;
572 if (c == '-')
573 return c;
577 #define allowed_in_symbol(x) \
578 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
579 x != '{' && x != '}' && \
580 x != '!' && x != '=' && \
581 x != '#' && x != ',' && \
582 x != '.'))
584 if (isalnum(c) || c == ':' || c == '_') {
585 do {
586 *p++ = c;
587 if ((size_t)(p-buf) >= sizeof(buf)) {
588 yyerror("string too long");
589 return findeol();
591 } while ((c = lgetc(0)) != EOF && (allowed_in_symbol(c)));
592 lungetc(c);
593 *p = '\0';
594 if ((token = lookup(buf)) == SYMBOL)
595 yylval.v.str = xstrdup(buf);
596 return token;
599 if (c == '\n') {
600 yylval.lineno = file->lineno;
601 file->lineno++;
603 if (c == EOF)
604 return 0;
605 return c;
608 struct file *
609 pushfile(const char *name)
611 struct file *nfile;
613 if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
614 log_warn("calloc");
615 return NULL;
617 if ((nfile->name = strdup(name)) == NULL) {
618 log_warn("strdup");
619 free(nfile);
620 return NULL;
622 if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
623 log_warn("%s", nfile->name);
624 free(nfile->name);
625 free(nfile);
626 return NULL;
628 nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0;
629 nfile->ungetsize = 16;
630 nfile->ungetbuf = malloc(nfile->ungetsize);
631 if (nfile->ungetbuf == NULL) {
632 log_warn("malloc");
633 fclose(nfile->stream);
634 free(nfile->name);
635 free(nfile);
636 return NULL;
638 TAILQ_INSERT_TAIL(&files, nfile, entry);
639 return nfile;
642 int
643 popfile(void)
645 struct file *prev;
647 if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
648 prev->errors += file->errors;
650 TAILQ_REMOVE(&files, file, entry);
651 fclose(file->stream);
652 free(file->name);
653 free(file->ungetbuf);
654 free(file);
655 file = prev;
656 return file ? 0 : EOF;
659 void
660 loadfile(const char *path)
662 int errors;
664 file = pushfile(path);
665 if (file == NULL)
666 err(1, "pushfile");
667 topfile = file;
669 yyparse();
670 errors = file->errors;
671 popfile();
673 if (errors)
674 errx(1, "can't load %s because of errors", path);