[BACK]Return to parse.y CVS log [TXT][DIR] Up to [local] / src / usr.sbin / ldomctl

File: [local] / src / usr.sbin / ldomctl / parse.y (download)

Revision 1.25, Thu Oct 6 21:35:52 2022 UTC (19 months, 3 weeks ago) by kn
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, HEAD
Changes since 1.24: +3 -3 lines

accept iodevices as NACs as well

Assignable PCIe devices have a root complex path and a more descriptive
I/O slot path;  example output from a T4-2:

	# ldomctl list-io | head -n2
	PATH             NAME
	/@400/@2/@0/@8   /SYS/MB/PCIE0

ldom.conf(5) `iodevice' currently accepts PATH values, which are cryptic and
completely hardware specific, whereas NAME values are obvious (partially
same across machines) and match physical slot labels ("0 PCIe2 x8") besides
information from ILOM:
    /System/PCI_Devices/Add-on/Device_0 location = PCIE0 (PCIe Slot 0).

Make ldom.conf `iodevice' accept either value;  internally nothing changes.

Rename struct iodev's path member to dev to clarify this further.

OK kettenis

/*	$OpenBSD: parse.y,v 1.25 2022/10/06 21:35:52 kn Exp $	*/

/*
 * Copyright (c) 2012 Mark Kettenis <kettenis@openbsd.org>
 * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
 * Copyright (c) 2001 Markus Friedl.  All rights reserved.
 * Copyright (c) 2001 Daniel Hartmeier.  All rights reserved.
 * Copyright (c) 2001 Theo de Raadt.  All rights reserved.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

%{
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/queue.h>

#include <net/if.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>

#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <util.h>

#include "ldomctl.h"
#include "ldom_util.h"

TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
static struct file {
	TAILQ_ENTRY(file)	 entry;
	FILE			*stream;
	char			*name;
	int			 lineno;
	int			 errors;
} *file, *topfile;
struct file	*pushfile(const char *);
int		 popfile(void);
int		 yyparse(void);
int		 yylex(void);
int		 yyerror(const char *, ...)
    __attribute__((__format__ (printf, 1, 2)))
    __attribute__((__nonnull__ (1)));
int		 kw_cmp(const void *, const void *);
int		 lookup(char *);
int		 lgetc(int);
int		 lungetc(int);
int		 findeol(void);

struct ldom_config		*conf;
struct domain			*domain;

struct vcpu_opts {
	uint64_t	count;
	uint64_t	stride;
} vcpu_opts;

struct vdisk_opts {
	const char	*devalias;
} vdisk_opts;

struct vnet_opts {
	uint64_t	mac_addr;
	uint64_t	mtu;
	const char	*devalias;
} vnet_opts;

void		vcpu_opts_default(void);
void		vdisk_opts_default(void);
void		vnet_opts_default(void);

typedef struct {
	union {
		int64_t			 number;
		char			*string;
		struct vcpu_opts	 vcpu_opts;
		struct vdisk_opts	 vdisk_opts;
		struct vnet_opts	 vnet_opts;
	} v;
	int lineno;
} YYSTYPE;

%}

%token	DOMAIN
%token	VCPU MEMORY VDISK DEVALIAS VNET VARIABLE IODEVICE
%token	MAC_ADDR MTU
%token	ERROR
%token	<v.string>		STRING
%token	<v.number>		NUMBER
%type	<v.number>		memory
%type	<v.vcpu_opts>		vcpu
%type	<v.vdisk_opts>		vdisk_opts vdisk_opts_l vdisk_opt
%type	<v.vdisk_opts>		vdisk_devalias
%type	<v.vnet_opts>		vnet_opts vnet_opts_l vnet_opt
%type	<v.vnet_opts>		mac_addr
%type	<v.vnet_opts>		mtu
%type	<v.vnet_opts>		vnet_devalias
%%

grammar		: /* empty */
		| grammar '\n'
		| grammar domain '\n'
		| grammar error '\n'		{ file->errors++; }
		;

domain		: DOMAIN STRING optnl '{' optnl	{
			struct domain *odomain;
			SIMPLEQ_FOREACH(odomain, &conf->domain_list, entry)
				if (strcmp(odomain->name, $2) == 0) {
					yyerror("duplicate domain name: %s", $2);
					YYERROR;
				}
			domain = xzalloc(sizeof(struct domain));
			domain->name = $2;
			SIMPLEQ_INIT(&domain->vdisk_list);
			SIMPLEQ_INIT(&domain->vnet_list);
			SIMPLEQ_INIT(&domain->var_list);
			SIMPLEQ_INIT(&domain->iodev_list);
		}
		    domainopts_l '}' {
			if (strcmp(domain->name, "primary") != 0) {
				if (domain->vcpu == 0) {
					yyerror("vcpu is required: %s",
					    domain->name);
					YYERROR;
				}
				if ( domain->memory == 0) {
					yyerror("memory is required: %s",
					    domain->name);
					YYERROR;
				}
				if (SIMPLEQ_EMPTY(&domain->vdisk_list) &&
				    SIMPLEQ_EMPTY(&domain->vnet_list) &&
				    SIMPLEQ_EMPTY(&domain->iodev_list)) {
					yyerror("at least one bootable device"
					    " is required: %s", domain->name);
					YYERROR;
				}
			}
			SIMPLEQ_INSERT_TAIL(&conf->domain_list, domain, entry);
			domain = NULL;
		}
		;

domainopts_l	: domainopts_l domainoptsl
		| domainoptsl
		;

domainoptsl	: domainopts nl
		;

domainopts	: VCPU vcpu {
			if (domain->vcpu) {
				yyerror("duplicate vcpu option");
				YYERROR;
			}
			domain->vcpu = $2.count;
			domain->vcpu_stride = $2.stride;
		}
		| MEMORY memory {
			if (domain->memory) {
				yyerror("duplicate memory option");
				YYERROR;
			}
			domain->memory = $2;
		}
		| VDISK STRING vdisk_opts {
			if (strcmp(domain->name, "primary") == 0) {
				yyerror("vdisk option invalid for primary"
				    " domain");
				YYERROR;
			}
			struct vdisk *vdisk = xmalloc(sizeof(struct vdisk));
			vdisk->path = $2;
			vdisk->devalias = $3.devalias;
			SIMPLEQ_INSERT_TAIL(&domain->vdisk_list, vdisk, entry);
		}
		| VNET vnet_opts {
			if (strcmp(domain->name, "primary") == 0) {
				yyerror("vnet option invalid for primary"
				    " domain");
				YYERROR;
			}
			struct vnet *vnet = xmalloc(sizeof(struct vnet));
			vnet->mac_addr = $2.mac_addr;
			vnet->mtu = $2.mtu;
			vnet->devalias = $2.devalias;
			SIMPLEQ_INSERT_TAIL(&domain->vnet_list, vnet, entry);
		}
		| VARIABLE STRING '=' STRING {
			struct var *var = xmalloc(sizeof(struct var));
			var->name = $2;
			var->str = $4;
			SIMPLEQ_INSERT_TAIL(&domain->var_list, var, entry);
		}
		| IODEVICE STRING {
			if (strcmp(domain->name, "primary") == 0) {
				yyerror("iodevice option invalid for primary"
				    " domain");
				YYERROR;
			}
			struct domain *odomain;
			struct iodev *iodev;
			SIMPLEQ_FOREACH(odomain, &conf->domain_list, entry)
				SIMPLEQ_FOREACH(iodev, &odomain->iodev_list, entry)
					if (strcmp(iodev->dev, $2) == 0) {
						yyerror("iodevice %s already"
						    " assigned", $2);
						YYERROR;
					}
			iodev = xmalloc(sizeof(struct iodev));
			iodev->dev = $2;
			SIMPLEQ_INSERT_TAIL(&domain->iodev_list, iodev, entry);
		}
		;

vdisk_opts	:	{ vdisk_opts_default(); }
		  vdisk_opts_l
			{ $$ = vdisk_opts; }
		|	{ vdisk_opts_default(); $$ = vdisk_opts; }
		;
vdisk_opts_l	: vdisk_opts_l vdisk_opt
		| vdisk_opt
		;
vdisk_opt	: vdisk_devalias
		;

vdisk_devalias	: DEVALIAS '=' STRING {
			vdisk_opts.devalias = $3;
		}
		;

vnet_opts	:	{ vnet_opts_default(); }
		  vnet_opts_l
			{ $$ = vnet_opts; }
		|	{ vnet_opts_default(); $$ = vnet_opts; }
		;
vnet_opts_l	: vnet_opts_l vnet_opt
		| vnet_opt
		;
vnet_opt	: mac_addr
		| mtu
		| vnet_devalias
		;

mac_addr	: MAC_ADDR '=' STRING {
			struct ether_addr *ea;

			if ((ea = ether_aton($3)) == NULL) {
				yyerror("invalid address: %s", $3);
				YYERROR;
			}

			vnet_opts.mac_addr =
			    (uint64_t)ea->ether_addr_octet[0] << 40 |
			    (uint64_t)ea->ether_addr_octet[1] << 32 |
			    ea->ether_addr_octet[2] << 24 |
			    ea->ether_addr_octet[3] << 16 |
			    ea->ether_addr_octet[4] << 8 |
			    ea->ether_addr_octet[5];
		}
		;

mtu		: MTU '=' NUMBER {
			vnet_opts.mtu = $3;
		}
		;

vnet_devalias	: DEVALIAS '=' STRING {
			vnet_opts.devalias = $3;
		}
		;

vcpu		: STRING {
			const char *errstr;
			char *colon;

			vcpu_opts_default();
			colon = strchr($1, ':');
			if (colon == NULL) {
				yyerror("bogus stride in %s", $1);
				YYERROR;
			}
			*colon++ = '\0';
			vcpu_opts.count = strtonum($1, 0, INT_MAX, &errstr);
			if (errstr) {
				yyerror("number %s is %s", $1, errstr);
				YYERROR;
			}
			vcpu_opts.stride = strtonum(colon, 0, INT_MAX, &errstr);
			if (errstr) {
				yyerror("number %s is %s", colon, errstr);
				YYERROR;
			}
			$$ = vcpu_opts;
		}
		| NUMBER {
			vcpu_opts_default();
			vcpu_opts.count = $1;
			$$ = vcpu_opts;
		}
		;

memory		: NUMBER {
			$$ = $1;
		}
		| STRING {
			if (scan_scaled($1, &$$) == -1) {
				yyerror("invalid size: %s", $1);
				YYERROR;
			}
		}
		;

optnl		: '\n' optnl
		|
		;

nl		: '\n' optnl		/* one newline or more */
		;

%%

void
vcpu_opts_default(void)
{
	vcpu_opts.count = -1;
	vcpu_opts.stride = 1;
}

void
vdisk_opts_default(void)
{
	vdisk_opts.devalias = NULL;
}

void
vnet_opts_default(void)
{
	vnet_opts.mac_addr = -1;
	vnet_opts.mtu = 1500;
	vnet_opts.devalias = NULL;
}

struct keywords {
	const char	*k_name;
	int		 k_val;
};

int
yyerror(const char *fmt, ...)
{
	va_list		 ap;

	file->errors++;
	va_start(ap, fmt);
	fprintf(stderr, "%s:%d ", file->name, yylval.lineno);
	vfprintf(stderr, fmt, ap);
	fprintf(stderr, "\n");
	va_end(ap);
	return (0);
}

int
kw_cmp(const void *k, const void *e)
{
	return (strcmp(k, ((const struct keywords *)e)->k_name));
}

int
lookup(char *s)
{
	/* this has to be sorted always */
	static const struct keywords keywords[] = {
		{ "devalias",		DEVALIAS},
		{ "domain",		DOMAIN},
		{ "iodevice",		IODEVICE},
		{ "mac-addr",		MAC_ADDR},
		{ "memory",		MEMORY},
		{ "mtu",		MTU},
		{ "variable",		VARIABLE},
		{ "vcpu",		VCPU},
		{ "vdisk",		VDISK},
		{ "vnet",		VNET}
	};
	const struct keywords	*p;

	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
	    sizeof(keywords[0]), kw_cmp);

	if (p)
		return (p->k_val);
	else
		return (STRING);
}

#define MAXPUSHBACK	128

char	*parsebuf;
int	 parseindex;
char	 pushback_buffer[MAXPUSHBACK];
int	 pushback_index = 0;

int
lgetc(int quotec)
{
	int		c, next;

	if (parsebuf) {
		/* Read character from the parsebuffer instead of input. */
		if (parseindex >= 0) {
			c = (unsigned char)parsebuf[parseindex++];
			if (c != '\0')
				return (c);
			parsebuf = NULL;
		} else
			parseindex++;
	}

	if (pushback_index)
		return ((unsigned char)pushback_buffer[--pushback_index]);

	if (quotec) {
		if ((c = getc(file->stream)) == EOF) {
			yyerror("reached end of file while parsing "
			    "quoted string");
			if (file == topfile || popfile() == EOF)
				return (EOF);
			return (quotec);
		}
		return (c);
	}

	while ((c = getc(file->stream)) == '\\') {
		next = getc(file->stream);
		if (next != '\n') {
			c = next;
			break;
		}
		yylval.lineno = file->lineno;
		file->lineno++;
	}

	while (c == EOF) {
		if (file == topfile || popfile() == EOF)
			return (EOF);
		c = getc(file->stream);
	}
	return (c);
}

int
lungetc(int c)
{
	if (c == EOF)
		return (EOF);
	if (parsebuf) {
		parseindex--;
		if (parseindex >= 0)
			return (c);
	}
	if (pushback_index + 1 >= MAXPUSHBACK)
		return (EOF);
	pushback_buffer[pushback_index++] = c;
	return (c);
}

int
findeol(void)
{
	int	c;

	parsebuf = NULL;

	/* skip to either EOF or the first real EOL */
	while (1) {
		if (pushback_index)
			c = (unsigned char)pushback_buffer[--pushback_index];
		else
			c = lgetc(0);
		if (c == '\n') {
			file->lineno++;
			break;
		}
		if (c == EOF)
			break;
	}
	return (ERROR);
}

int
yylex(void)
{
	char	 buf[8096];
	char	*p;
	int	 quotec, next, c;
	int	 token;

	p = buf;
	while ((c = lgetc(0)) == ' ' || c == '\t')
		; /* nothing */

	yylval.lineno = file->lineno;
	if (c == '#')
		while ((c = lgetc(0)) != '\n' && c != EOF)
			; /* nothing */

	switch (c) {
	case '\'':
	case '"':
		quotec = c;
		while (1) {
			if ((c = lgetc(quotec)) == EOF)
				return (0);
			if (c == '\n') {
				file->lineno++;
				continue;
			} else if (c == '\\') {
				if ((next = lgetc(quotec)) == EOF)
					return (0);
				if (next == quotec || next == ' ' ||
				    next == '\t')
					c = next;
				else if (next == '\n') {
					file->lineno++;
					continue;
				} else
					lungetc(next);
			} else if (c == quotec) {
				*p = '\0';
				break;
			} else if (c == '\0') {
				yyerror("syntax error");
				return (findeol());
			}
			if (p + 1 >= buf + sizeof(buf) - 1) {
				yyerror("string too long");
				return (findeol());
			}
			*p++ = c;
		}
		yylval.v.string = xstrdup(buf);
		return (STRING);
	}

#define allowed_to_end_number(x) \
	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')

	if (c == '-' || isdigit(c)) {
		do {
			*p++ = c;
			if ((size_t)(p-buf) >= sizeof(buf)) {
				yyerror("string too long");
				return (findeol());
			}
		} while ((c = lgetc(0)) != EOF && isdigit(c));
		lungetc(c);
		if (p == buf + 1 && buf[0] == '-')
			goto nodigits;
		if (c == EOF || allowed_to_end_number(c)) {
			const char *errstr = NULL;

			*p = '\0';
			yylval.v.number = strtonum(buf, LLONG_MIN,
			    LLONG_MAX, &errstr);
			if (errstr) {
				yyerror("\"%s\" invalid number: %s",
				    buf, errstr);
				return (findeol());
			}
			return (NUMBER);
		} else {
nodigits:
			while (p > buf + 1)
				lungetc((unsigned char)*--p);
			c = (unsigned char)*--p;
			if (c == '-')
				return (c);
		}
	}

#define allowed_in_string(x) \
	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
	x != '{' && x != '}' && x != '<' && x != '>' && \
	x != '!' && x != '=' && x != '/' && x != '#' && \
	x != ','))

	if (isalnum(c) || c == ':' || c == '_' || c == '*') {
		do {
			*p++ = c;
			if ((size_t)(p-buf) >= sizeof(buf)) {
				yyerror("string too long");
				return (findeol());
			}
		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
		lungetc(c);
		*p = '\0';
		if ((token = lookup(buf)) == STRING)
			yylval.v.string = xstrdup(buf);
		return (token);
	}
	if (c == '\n') {
		yylval.lineno = file->lineno;
		file->lineno++;
	}
	if (c == EOF)
		return (0);
	return (c);
}

struct file *
pushfile(const char *name)
{
	struct file	*nfile;

	nfile = xzalloc(sizeof(struct file));
	nfile->name = xstrdup(name);
	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
		warn("%s: %s", __func__, nfile->name);
		free(nfile->name);
		free(nfile);
		return (NULL);
	}
	nfile->lineno = 1;
	TAILQ_INSERT_TAIL(&files, nfile, entry);
	return (nfile);
}

int
popfile(void)
{
	struct file	*prev;

	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
		prev->errors += file->errors;

	TAILQ_REMOVE(&files, file, entry);
	fclose(file->stream);
	free(file->name);
	free(file);
	file = prev;
	return (file ? 0 : EOF);
}

int
parse_config(const char *filename, struct ldom_config *xconf)
{
	int		 errors = 0;

	conf = xconf;

	if ((file = pushfile(filename)) == NULL) {
		return (-1);
	}
	topfile = file;

	yyparse();
	errors = file->errors;
	popfile();

	return (errors ? -1 : 0);
}