1/*	$OpenBSD: parse.y,v 1.25 2022/10/06 21:35:52 kn Exp $	*/
2
3/*
4 * Copyright (c) 2012 Mark Kettenis <kettenis@openbsd.org>
5 * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
6 * Copyright (c) 2001 Markus Friedl.  All rights reserved.
7 * Copyright (c) 2001 Daniel Hartmeier.  All rights reserved.
8 * Copyright (c) 2001 Theo de Raadt.  All rights reserved.
9 *
10 * Permission to use, copy, modify, and distribute this software for any
11 * purpose with or without fee is hereby granted, provided that the above
12 * copyright notice and this permission notice appear in all copies.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21 */
22
23%{
24#include <sys/types.h>
25#include <sys/socket.h>
26#include <sys/queue.h>
27
28#include <net/if.h>
29#include <netinet/in.h>
30#include <netinet/if_ether.h>
31
32#include <ctype.h>
33#include <err.h>
34#include <errno.h>
35#include <limits.h>
36#include <stdarg.h>
37#include <stdio.h>
38#include <stdlib.h>
39#include <string.h>
40#include <util.h>
41
42#include "ldomctl.h"
43#include "ldom_util.h"
44
45TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
46static struct file {
47	TAILQ_ENTRY(file)	 entry;
48	FILE			*stream;
49	char			*name;
50	int			 lineno;
51	int			 errors;
52} *file, *topfile;
53struct file	*pushfile(const char *);
54int		 popfile(void);
55int		 yyparse(void);
56int		 yylex(void);
57int		 yyerror(const char *, ...)
58    __attribute__((__format__ (printf, 1, 2)))
59    __attribute__((__nonnull__ (1)));
60int		 kw_cmp(const void *, const void *);
61int		 lookup(char *);
62int		 lgetc(int);
63int		 lungetc(int);
64int		 findeol(void);
65
66struct ldom_config		*conf;
67struct domain			*domain;
68
69struct vcpu_opts {
70	uint64_t	count;
71	uint64_t	stride;
72} vcpu_opts;
73
74struct vdisk_opts {
75	const char	*devalias;
76} vdisk_opts;
77
78struct vnet_opts {
79	uint64_t	mac_addr;
80	uint64_t	mtu;
81	const char	*devalias;
82} vnet_opts;
83
84void		vcpu_opts_default(void);
85void		vdisk_opts_default(void);
86void		vnet_opts_default(void);
87
88typedef struct {
89	union {
90		int64_t			 number;
91		char			*string;
92		struct vcpu_opts	 vcpu_opts;
93		struct vdisk_opts	 vdisk_opts;
94		struct vnet_opts	 vnet_opts;
95	} v;
96	int lineno;
97} YYSTYPE;
98
99%}
100
101%token	DOMAIN
102%token	VCPU MEMORY VDISK DEVALIAS VNET VARIABLE IODEVICE
103%token	MAC_ADDR MTU
104%token	ERROR
105%token	<v.string>		STRING
106%token	<v.number>		NUMBER
107%type	<v.number>		memory
108%type	<v.vcpu_opts>		vcpu
109%type	<v.vdisk_opts>		vdisk_opts vdisk_opts_l vdisk_opt
110%type	<v.vdisk_opts>		vdisk_devalias
111%type	<v.vnet_opts>		vnet_opts vnet_opts_l vnet_opt
112%type	<v.vnet_opts>		mac_addr
113%type	<v.vnet_opts>		mtu
114%type	<v.vnet_opts>		vnet_devalias
115%%
116
117grammar		: /* empty */
118		| grammar '\n'
119		| grammar domain '\n'
120		| grammar error '\n'		{ file->errors++; }
121		;
122
123domain		: DOMAIN STRING optnl '{' optnl	{
124			struct domain *odomain;
125			SIMPLEQ_FOREACH(odomain, &conf->domain_list, entry)
126				if (strcmp(odomain->name, $2) == 0) {
127					yyerror("duplicate domain name: %s", $2);
128					YYERROR;
129				}
130			domain = xzalloc(sizeof(struct domain));
131			domain->name = $2;
132			SIMPLEQ_INIT(&domain->vdisk_list);
133			SIMPLEQ_INIT(&domain->vnet_list);
134			SIMPLEQ_INIT(&domain->var_list);
135			SIMPLEQ_INIT(&domain->iodev_list);
136		}
137		    domainopts_l '}' {
138			if (strcmp(domain->name, "primary") != 0) {
139				if (domain->vcpu == 0) {
140					yyerror("vcpu is required: %s",
141					    domain->name);
142					YYERROR;
143				}
144				if ( domain->memory == 0) {
145					yyerror("memory is required: %s",
146					    domain->name);
147					YYERROR;
148				}
149				if (SIMPLEQ_EMPTY(&domain->vdisk_list) &&
150				    SIMPLEQ_EMPTY(&domain->vnet_list) &&
151				    SIMPLEQ_EMPTY(&domain->iodev_list)) {
152					yyerror("at least one bootable device"
153					    " is required: %s", domain->name);
154					YYERROR;
155				}
156			}
157			SIMPLEQ_INSERT_TAIL(&conf->domain_list, domain, entry);
158			domain = NULL;
159		}
160		;
161
162domainopts_l	: domainopts_l domainoptsl
163		| domainoptsl
164		;
165
166domainoptsl	: domainopts nl
167		;
168
169domainopts	: VCPU vcpu {
170			if (domain->vcpu) {
171				yyerror("duplicate vcpu option");
172				YYERROR;
173			}
174			domain->vcpu = $2.count;
175			domain->vcpu_stride = $2.stride;
176		}
177		| MEMORY memory {
178			if (domain->memory) {
179				yyerror("duplicate memory option");
180				YYERROR;
181			}
182			domain->memory = $2;
183		}
184		| VDISK STRING vdisk_opts {
185			if (strcmp(domain->name, "primary") == 0) {
186				yyerror("vdisk option invalid for primary"
187				    " domain");
188				YYERROR;
189			}
190			struct vdisk *vdisk = xmalloc(sizeof(struct vdisk));
191			vdisk->path = $2;
192			vdisk->devalias = $3.devalias;
193			SIMPLEQ_INSERT_TAIL(&domain->vdisk_list, vdisk, entry);
194		}
195		| VNET vnet_opts {
196			if (strcmp(domain->name, "primary") == 0) {
197				yyerror("vnet option invalid for primary"
198				    " domain");
199				YYERROR;
200			}
201			struct vnet *vnet = xmalloc(sizeof(struct vnet));
202			vnet->mac_addr = $2.mac_addr;
203			vnet->mtu = $2.mtu;
204			vnet->devalias = $2.devalias;
205			SIMPLEQ_INSERT_TAIL(&domain->vnet_list, vnet, entry);
206		}
207		| VARIABLE STRING '=' STRING {
208			struct var *var = xmalloc(sizeof(struct var));
209			var->name = $2;
210			var->str = $4;
211			SIMPLEQ_INSERT_TAIL(&domain->var_list, var, entry);
212		}
213		| IODEVICE STRING {
214			if (strcmp(domain->name, "primary") == 0) {
215				yyerror("iodevice option invalid for primary"
216				    " domain");
217				YYERROR;
218			}
219			struct domain *odomain;
220			struct iodev *iodev;
221			SIMPLEQ_FOREACH(odomain, &conf->domain_list, entry)
222				SIMPLEQ_FOREACH(iodev, &odomain->iodev_list, entry)
223					if (strcmp(iodev->dev, $2) == 0) {
224						yyerror("iodevice %s already"
225						    " assigned", $2);
226						YYERROR;
227					}
228			iodev = xmalloc(sizeof(struct iodev));
229			iodev->dev = $2;
230			SIMPLEQ_INSERT_TAIL(&domain->iodev_list, iodev, entry);
231		}
232		;
233
234vdisk_opts	:	{ vdisk_opts_default(); }
235		  vdisk_opts_l
236			{ $$ = vdisk_opts; }
237		|	{ vdisk_opts_default(); $$ = vdisk_opts; }
238		;
239vdisk_opts_l	: vdisk_opts_l vdisk_opt
240		| vdisk_opt
241		;
242vdisk_opt	: vdisk_devalias
243		;
244
245vdisk_devalias	: DEVALIAS '=' STRING {
246			vdisk_opts.devalias = $3;
247		}
248		;
249
250vnet_opts	:	{ vnet_opts_default(); }
251		  vnet_opts_l
252			{ $$ = vnet_opts; }
253		|	{ vnet_opts_default(); $$ = vnet_opts; }
254		;
255vnet_opts_l	: vnet_opts_l vnet_opt
256		| vnet_opt
257		;
258vnet_opt	: mac_addr
259		| mtu
260		| vnet_devalias
261		;
262
263mac_addr	: MAC_ADDR '=' STRING {
264			struct ether_addr *ea;
265
266			if ((ea = ether_aton($3)) == NULL) {
267				yyerror("invalid address: %s", $3);
268				YYERROR;
269			}
270
271			vnet_opts.mac_addr =
272			    (uint64_t)ea->ether_addr_octet[0] << 40 |
273			    (uint64_t)ea->ether_addr_octet[1] << 32 |
274			    ea->ether_addr_octet[2] << 24 |
275			    ea->ether_addr_octet[3] << 16 |
276			    ea->ether_addr_octet[4] << 8 |
277			    ea->ether_addr_octet[5];
278		}
279		;
280
281mtu		: MTU '=' NUMBER {
282			vnet_opts.mtu = $3;
283		}
284		;
285
286vnet_devalias	: DEVALIAS '=' STRING {
287			vnet_opts.devalias = $3;
288		}
289		;
290
291vcpu		: STRING {
292			const char *errstr;
293			char *colon;
294
295			vcpu_opts_default();
296			colon = strchr($1, ':');
297			if (colon == NULL) {
298				yyerror("bogus stride in %s", $1);
299				YYERROR;
300			}
301			*colon++ = '\0';
302			vcpu_opts.count = strtonum($1, 0, INT_MAX, &errstr);
303			if (errstr) {
304				yyerror("number %s is %s", $1, errstr);
305				YYERROR;
306			}
307			vcpu_opts.stride = strtonum(colon, 0, INT_MAX, &errstr);
308			if (errstr) {
309				yyerror("number %s is %s", colon, errstr);
310				YYERROR;
311			}
312			$$ = vcpu_opts;
313		}
314		| NUMBER {
315			vcpu_opts_default();
316			vcpu_opts.count = $1;
317			$$ = vcpu_opts;
318		}
319		;
320
321memory		: NUMBER {
322			$$ = $1;
323		}
324		| STRING {
325			if (scan_scaled($1, &$$) == -1) {
326				yyerror("invalid size: %s", $1);
327				YYERROR;
328			}
329		}
330		;
331
332optnl		: '\n' optnl
333		|
334		;
335
336nl		: '\n' optnl		/* one newline or more */
337		;
338
339%%
340
341void
342vcpu_opts_default(void)
343{
344	vcpu_opts.count = -1;
345	vcpu_opts.stride = 1;
346}
347
348void
349vdisk_opts_default(void)
350{
351	vdisk_opts.devalias = NULL;
352}
353
354void
355vnet_opts_default(void)
356{
357	vnet_opts.mac_addr = -1;
358	vnet_opts.mtu = 1500;
359	vnet_opts.devalias = NULL;
360}
361
362struct keywords {
363	const char	*k_name;
364	int		 k_val;
365};
366
367int
368yyerror(const char *fmt, ...)
369{
370	va_list		 ap;
371
372	file->errors++;
373	va_start(ap, fmt);
374	fprintf(stderr, "%s:%d ", file->name, yylval.lineno);
375	vfprintf(stderr, fmt, ap);
376	fprintf(stderr, "\n");
377	va_end(ap);
378	return (0);
379}
380
381int
382kw_cmp(const void *k, const void *e)
383{
384	return (strcmp(k, ((const struct keywords *)e)->k_name));
385}
386
387int
388lookup(char *s)
389{
390	/* this has to be sorted always */
391	static const struct keywords keywords[] = {
392		{ "devalias",		DEVALIAS},
393		{ "domain",		DOMAIN},
394		{ "iodevice",		IODEVICE},
395		{ "mac-addr",		MAC_ADDR},
396		{ "memory",		MEMORY},
397		{ "mtu",		MTU},
398		{ "variable",		VARIABLE},
399		{ "vcpu",		VCPU},
400		{ "vdisk",		VDISK},
401		{ "vnet",		VNET}
402	};
403	const struct keywords	*p;
404
405	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
406	    sizeof(keywords[0]), kw_cmp);
407
408	if (p)
409		return (p->k_val);
410	else
411		return (STRING);
412}
413
414#define MAXPUSHBACK	128
415
416char	*parsebuf;
417int	 parseindex;
418char	 pushback_buffer[MAXPUSHBACK];
419int	 pushback_index = 0;
420
421int
422lgetc(int quotec)
423{
424	int		c, next;
425
426	if (parsebuf) {
427		/* Read character from the parsebuffer instead of input. */
428		if (parseindex >= 0) {
429			c = (unsigned char)parsebuf[parseindex++];
430			if (c != '\0')
431				return (c);
432			parsebuf = NULL;
433		} else
434			parseindex++;
435	}
436
437	if (pushback_index)
438		return ((unsigned char)pushback_buffer[--pushback_index]);
439
440	if (quotec) {
441		if ((c = getc(file->stream)) == EOF) {
442			yyerror("reached end of file while parsing "
443			    "quoted string");
444			if (file == topfile || popfile() == EOF)
445				return (EOF);
446			return (quotec);
447		}
448		return (c);
449	}
450
451	while ((c = getc(file->stream)) == '\\') {
452		next = getc(file->stream);
453		if (next != '\n') {
454			c = next;
455			break;
456		}
457		yylval.lineno = file->lineno;
458		file->lineno++;
459	}
460
461	while (c == EOF) {
462		if (file == topfile || popfile() == EOF)
463			return (EOF);
464		c = getc(file->stream);
465	}
466	return (c);
467}
468
469int
470lungetc(int c)
471{
472	if (c == EOF)
473		return (EOF);
474	if (parsebuf) {
475		parseindex--;
476		if (parseindex >= 0)
477			return (c);
478	}
479	if (pushback_index + 1 >= MAXPUSHBACK)
480		return (EOF);
481	pushback_buffer[pushback_index++] = c;
482	return (c);
483}
484
485int
486findeol(void)
487{
488	int	c;
489
490	parsebuf = NULL;
491
492	/* skip to either EOF or the first real EOL */
493	while (1) {
494		if (pushback_index)
495			c = (unsigned char)pushback_buffer[--pushback_index];
496		else
497			c = lgetc(0);
498		if (c == '\n') {
499			file->lineno++;
500			break;
501		}
502		if (c == EOF)
503			break;
504	}
505	return (ERROR);
506}
507
508int
509yylex(void)
510{
511	char	 buf[8096];
512	char	*p;
513	int	 quotec, next, c;
514	int	 token;
515
516	p = buf;
517	while ((c = lgetc(0)) == ' ' || c == '\t')
518		; /* nothing */
519
520	yylval.lineno = file->lineno;
521	if (c == '#')
522		while ((c = lgetc(0)) != '\n' && c != EOF)
523			; /* nothing */
524
525	switch (c) {
526	case '\'':
527	case '"':
528		quotec = c;
529		while (1) {
530			if ((c = lgetc(quotec)) == EOF)
531				return (0);
532			if (c == '\n') {
533				file->lineno++;
534				continue;
535			} else if (c == '\\') {
536				if ((next = lgetc(quotec)) == EOF)
537					return (0);
538				if (next == quotec || next == ' ' ||
539				    next == '\t')
540					c = next;
541				else if (next == '\n') {
542					file->lineno++;
543					continue;
544				} else
545					lungetc(next);
546			} else if (c == quotec) {
547				*p = '\0';
548				break;
549			} else if (c == '\0') {
550				yyerror("syntax error");
551				return (findeol());
552			}
553			if (p + 1 >= buf + sizeof(buf) - 1) {
554				yyerror("string too long");
555				return (findeol());
556			}
557			*p++ = c;
558		}
559		yylval.v.string = xstrdup(buf);
560		return (STRING);
561	}
562
563#define allowed_to_end_number(x) \
564	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
565
566	if (c == '-' || isdigit(c)) {
567		do {
568			*p++ = c;
569			if ((size_t)(p-buf) >= sizeof(buf)) {
570				yyerror("string too long");
571				return (findeol());
572			}
573		} while ((c = lgetc(0)) != EOF && isdigit(c));
574		lungetc(c);
575		if (p == buf + 1 && buf[0] == '-')
576			goto nodigits;
577		if (c == EOF || allowed_to_end_number(c)) {
578			const char *errstr = NULL;
579
580			*p = '\0';
581			yylval.v.number = strtonum(buf, LLONG_MIN,
582			    LLONG_MAX, &errstr);
583			if (errstr) {
584				yyerror("\"%s\" invalid number: %s",
585				    buf, errstr);
586				return (findeol());
587			}
588			return (NUMBER);
589		} else {
590nodigits:
591			while (p > buf + 1)
592				lungetc((unsigned char)*--p);
593			c = (unsigned char)*--p;
594			if (c == '-')
595				return (c);
596		}
597	}
598
599#define allowed_in_string(x) \
600	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
601	x != '{' && x != '}' && x != '<' && x != '>' && \
602	x != '!' && x != '=' && x != '/' && x != '#' && \
603	x != ','))
604
605	if (isalnum(c) || c == ':' || c == '_' || c == '*') {
606		do {
607			*p++ = c;
608			if ((size_t)(p-buf) >= sizeof(buf)) {
609				yyerror("string too long");
610				return (findeol());
611			}
612		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
613		lungetc(c);
614		*p = '\0';
615		if ((token = lookup(buf)) == STRING)
616			yylval.v.string = xstrdup(buf);
617		return (token);
618	}
619	if (c == '\n') {
620		yylval.lineno = file->lineno;
621		file->lineno++;
622	}
623	if (c == EOF)
624		return (0);
625	return (c);
626}
627
628struct file *
629pushfile(const char *name)
630{
631	struct file	*nfile;
632
633	nfile = xzalloc(sizeof(struct file));
634	nfile->name = xstrdup(name);
635	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
636		warn("%s: %s", __func__, nfile->name);
637		free(nfile->name);
638		free(nfile);
639		return (NULL);
640	}
641	nfile->lineno = 1;
642	TAILQ_INSERT_TAIL(&files, nfile, entry);
643	return (nfile);
644}
645
646int
647popfile(void)
648{
649	struct file	*prev;
650
651	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
652		prev->errors += file->errors;
653
654	TAILQ_REMOVE(&files, file, entry);
655	fclose(file->stream);
656	free(file->name);
657	free(file);
658	file = prev;
659	return (file ? 0 : EOF);
660}
661
662int
663parse_config(const char *filename, struct ldom_config *xconf)
664{
665	int		 errors = 0;
666
667	conf = xconf;
668
669	if ((file = pushfile(filename)) == NULL) {
670		return (-1);
671	}
672	topfile = file;
673
674	yyparse();
675	errors = file->errors;
676	popfile();
677
678	return (errors ? -1 : 0);
679}
680