summaryrefslogtreecommitdiff
path: root/usr.sbin
diff options
context:
space:
mode:
authorReyk Floeter <reyk@cvs.openbsd.org>2015-11-22 20:55:19 +0000
committerReyk Floeter <reyk@cvs.openbsd.org>2015-11-22 20:55:19 +0000
commit6f0b9075f4beea2770c23e044037335ac54f6a64 (patch)
tree321b055a3461820f3663ecac990d993d008a8583 /usr.sbin
parentfa0f3767a64aa01ea0879ce5a586c06a5fda7ca1 (diff)
Add initial parser to support a vmm.conf(5) configuration file format
for vmm and virtual machines. Additionally, add a matching vmmctl command line grammar that replaces (most of the) getopt arguments. The goal is to provide a sane way to configure vmm(4) and vmd(8). "There is still a lot to be done, and fixed" in this as well. OK mlarkin@
Diffstat (limited to 'usr.sbin')
-rw-r--r--usr.sbin/vmmctl/Makefile4
-rw-r--r--usr.sbin/vmmctl/main.c323
-rw-r--r--usr.sbin/vmmctl/parse.y673
-rw-r--r--usr.sbin/vmmctl/parser.c317
-rw-r--r--usr.sbin/vmmctl/parser.h81
-rw-r--r--usr.sbin/vmmctl/vmm.conf.5166
-rw-r--r--usr.sbin/vmmctl/vmmctl.8117
-rw-r--r--usr.sbin/vmmctl/vmmctl.c368
8 files changed, 1636 insertions, 413 deletions
diff --git a/usr.sbin/vmmctl/Makefile b/usr.sbin/vmmctl/Makefile
index 2c0c1e878f1..858a4b27473 100644
--- a/usr.sbin/vmmctl/Makefile
+++ b/usr.sbin/vmmctl/Makefile
@@ -2,7 +2,7 @@
.if ${MACHINE} == "amd64"
PROG= vmmctl
-SRCS= vmmctl.c
+SRCS= vmmctl.c parser.c main.c parse.y
CFLAGS+= -Wall
CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes
CFLAGS+= -Wmissing-declarations
@@ -18,7 +18,7 @@ NOPROG= yes
.endif
-MAN= vmmctl.8
+MAN= vmmctl.8 vmm.conf.5
MANSUBDIR=${MACHINE}
.include <bsd.prog.mk>
diff --git a/usr.sbin/vmmctl/main.c b/usr.sbin/vmmctl/main.c
new file mode 100644
index 00000000000..7711d6eaa5b
--- /dev/null
+++ b/usr.sbin/vmmctl/main.c
@@ -0,0 +1,323 @@
+/* $OpenBSD: main.c,v 1.1 2015/11/22 20:55:18 reyk Exp $ */
+
+/*
+ * Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
+ *
+ * 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 <sys/un.h>
+#include <sys/cdefs.h>
+
+#include <machine/vmmvar.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <limits.h>
+#include <string.h>
+#include <unistd.h>
+#include <imsg.h>
+
+#include "vmd.h"
+#include "parser.h"
+
+static const char *socket_name = SOCKET_NAME;
+
+__dead void usage(void);
+int vmm_action(struct parse_result *);
+
+__dead void
+usage(void)
+{
+ extern char *__progname;
+
+ fprintf(stderr, "usage: %s [-q] [-s socket] command [arg ...]\n",
+ __progname);
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct parse_result *res;
+ int ch;
+ int ret;
+ const char *paths[2];
+
+ while ((ch = getopt(argc, argv, "s:")) != -1) {
+ switch (ch) {
+ case 's':
+ socket_name = optarg;
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* parse command line options */
+ if ((res = parse(argc, argv)) == NULL)
+ exit(1);
+
+ switch (res->action) {
+ case NONE:
+ usage();
+ break;
+ case CMD_CREATE:
+ paths[0] = res->path;
+ paths[1] = NULL;
+ if (pledge("stdio rpath wpath cpath", paths) == -1)
+ err(1, "pledge");
+ if (res->size < 1)
+ errx(1, "specified image size too small");
+
+ ret = create_imagefile(res->path, res->size);
+ if (ret != 0) {
+ errno = ret;
+ err(1, "create imagefile operation failed");
+ } else
+ warnx("imagefile created");
+ return (0);
+ case CMD_LOAD:
+ if (pledge("stdio rpath unix", NULL) == -1)
+ err(1, "pledge");
+
+ /* parse configuration file options */
+ if (res->path == NULL)
+ ret = parse_config(VMM_CONF);
+ else
+ ret = parse_config(res->path);
+ break;
+ default:
+ if (pledge("stdio unix", NULL) == -1)
+ err(1, "pledge");
+ ret = vmmaction(res);
+ break;
+ }
+
+ return (ret);
+}
+
+int
+vmmaction(struct parse_result *res)
+{
+ struct sockaddr_un sun;
+ struct imsg imsg;
+ int done = 0;
+ int n;
+ int ret;
+ int ctl_sock;
+
+ /*
+ * Connect to vmd control socket.
+ * XXX vmd currently only accepts one request per connection,
+ * XXX so we have to open the control socket each time this
+ * XXX function is called. This should be changed later.
+ */
+ if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
+ err(1, "socket");
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ strlcpy(sun.sun_path, socket_name, sizeof(sun.sun_path));
+
+ if (connect(ctl_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1)
+ err(1, "connect: %s", socket_name);
+
+ if ((ibuf = malloc(sizeof(struct imsgbuf))) == NULL)
+ err(1, "malloc");
+ imsg_init(ibuf, ctl_sock);
+
+ switch (res->action) {
+ case CMD_ENABLE:
+ enable_vmm();
+ break;
+ case CMD_DISABLE:
+ disable_vmm();
+ break;
+ case CMD_START:
+ /* XXX validation should be done in start_vm() */
+ if (res->size < 1)
+ errx(1, "specified memory size too small");
+ if (res->path == NULL)
+ errx(1, "no kernel specified");
+ if (res->ndisks > VMM_MAX_DISKS_PER_VM)
+ errx(1, "too many disks");
+ else if (res->ndisks == 0)
+ warnx("starting without disks");
+
+ ret = start_vm(res->name, res->size, res->nifs,
+ res->ndisks, res->disks, res->path);
+ if (ret) {
+ errno = ret;
+ err(1, "start VM operation failed");
+ }
+ break;
+ case CMD_TERMINATE:
+ terminate_vm(res->id);
+ break;
+ case CMD_INFO:
+ get_info_vm(res->id);
+ break;
+ case CMD_CREATE:
+ case CMD_LOAD:
+ case NONE:
+ break;
+ }
+
+ while (ibuf->w.queued)
+ if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN)
+ err(1, "write error");
+
+ while (!done) {
+ if ((n = imsg_read(ibuf)) == -1)
+ errx(1, "imsg_read error");
+ if (n == 0)
+ errx(1, "pipe closed");
+
+ while (!done) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ errx(1, "imsg_get error");
+ if (n == 0)
+ break;
+
+ ret = 0;
+ switch (res->action) {
+ case CMD_DISABLE:
+ done = disable_vmm_complete(&imsg, &ret);
+ break;
+ case CMD_ENABLE:
+ done = enable_vmm_complete(&imsg, &ret);
+ break;
+ case CMD_START:
+ done = start_vm_complete(&imsg, &ret);
+ break;
+ case CMD_TERMINATE:
+ done = terminate_vm_complete(&imsg, &ret);
+ break;
+ case CMD_INFO:
+ done = add_info(&imsg, &ret);
+ break;
+ default:
+ done = 1;
+ break;
+ }
+
+ imsg_free(&imsg);
+ }
+ }
+
+ close(ibuf->fd);
+ free(ibuf);
+
+ return (0);
+}
+
+int
+parse_ifs(struct parse_result *res, char *word, int val)
+{
+ const char *error;
+
+ if (word != NULL) {
+ val = strtonum(word, 0, INT_MAX, &error);
+ if (error != NULL) {
+ warnx("invalid count \"%s\": %s", word, error);
+ return (-1);
+ }
+ }
+ res->nifs = val;
+ return (0);
+}
+
+int
+parse_size(struct parse_result *res, char *word,
+ long long val)
+{
+ char *s;
+
+ if (word != NULL) {
+ val = strtol(word, &s, 10);
+ if (errno == ERANGE &&
+ (val == LLONG_MIN || val == LLONG_MAX)) {
+ warnx("out of range: %s", word);
+ return (-1);
+ }
+ }
+
+ /* Convert to megabytes */
+ if (*s == '\0')
+ res->size = val / 1024 / 1024;
+ else if (strcmp(s, "K") == 0)
+ res->size = val / 1024;
+ else if (strcmp(s, "M") == 0)
+ res->size = val;
+ else if (strcmp(s, "G") == 0)
+ res->size = val * 1024;
+ else {
+ warnx("invalid unit: %s", s);
+ return (-1);
+ }
+ if (res->size > LLONG_MAX) {
+ warnx("size too large: %s", word);
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+parse_disk(struct parse_result *res, char *word)
+{
+ char **disks;
+ char *s;
+
+ if ((disks = reallocarray(res->disks, res->ndisks + 1,
+ sizeof(char *))) == NULL) {
+ warn("reallocarray");
+ return (-1);
+ }
+ if ((s = strdup(word)) == NULL) {
+ warn("strdup");
+ return (-1);
+ }
+ disks[res->ndisks] = s;
+ res->disks = disks;
+ res->ndisks++;
+
+ return (0);
+}
+
+int
+parse_vmid(struct parse_result *res, char *word, uint32_t id)
+{
+ const char *error;
+
+ if (word != NULL) {
+ id = strtonum(word, 0, UINT32_MAX, &error);
+ if (error != NULL) {
+ warnx("invalid id: %s", error);
+ return (-1);
+ }
+ }
+ res->id = id;
+
+ return (0);
+}
diff --git a/usr.sbin/vmmctl/parse.y b/usr.sbin/vmmctl/parse.y
new file mode 100644
index 00000000000..8461ff396f1
--- /dev/null
+++ b/usr.sbin/vmmctl/parse.y
@@ -0,0 +1,673 @@
+/* $OpenBSD: parse.y,v 1.1 2015/11/22 20:55:18 reyk Exp $ */
+
+/*
+ * Copyright (c) 2007-2015 Reyk Floeter <reyk@openbsd.org>
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2004 Ryan McBride <mcbride@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/queue.h>
+#include <sys/uio.h>
+
+#include <machine/vmmvar.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <err.h>
+
+#include "vmd.h"
+#include "parser.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);
+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);
+
+TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
+struct sym {
+ TAILQ_ENTRY(sym) entry;
+ int used;
+ int persist;
+ char *nam;
+ char *val;
+};
+int symset(const char *, const char *, int);
+char *symget(const char *);
+
+struct parse_result res;
+static int errors = 0;
+
+typedef struct {
+ union {
+ int64_t number;
+ char *string;
+ } v;
+ int lineno;
+} YYSTYPE;
+
+%}
+
+
+%token INCLUDE ERROR
+%token DISK NIFS PATH SIZE VMID
+%token ENABLE DISABLE VM VMM KERNEL MEMORY
+%token <v.string> STRING
+%token <v.number> NUMBER
+%type <v.number> disable
+
+%%
+
+grammar : /* empty */
+ | grammar include '\n'
+ | grammar '\n'
+ | grammar varset '\n'
+ | grammar main '\n'
+ | grammar error '\n' { file->errors++; }
+ ;
+
+include : INCLUDE STRING {
+ struct file *nfile;
+
+ if ((nfile = pushfile($2, 0)) == NULL) {
+ yyerror("failed to include file %s", $2);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+
+ file = nfile;
+ lungetc('\n');
+ }
+ ;
+
+varset : STRING '=' STRING {
+ if (symset($1, $3, 0) == -1)
+ errx(1, "cannot store variable");
+ free($1);
+ free($3);
+ }
+ ;
+
+main : VMM disable {
+ memset(&res, 0, sizeof(res));
+ res.action = $2 ? CMD_DISABLE : CMD_ENABLE;
+ if (vmmaction(&res) != 0)
+ errx(1, "vmmaction");
+ }
+ | VM STRING {
+ memset(&res, 0, sizeof(res));
+ res.name = $2;
+ } '{' optnl vm_opts_l '}' {
+ if (res.disable) {
+ yyerror("vm \"%s\" disabled", res.name);
+ YYACCEPT;
+ }
+
+ res.action = CMD_START;
+ if (vmmaction(&res) != 0)
+ errx(1, "vmmaction");
+ }
+ ;
+
+vm_opts_l : vm_opts_l vm_opts nl
+ | vm_opts optnl
+ ;
+
+vm_opts : disable {
+ res.disable = $1;
+ }
+ | DISK STRING {
+ if (parse_disk(&res, $2) != 0) {
+ yyerror("failed to parse disks: %s", $2);
+ free($2);
+ YYERROR;
+ }
+ }
+ | KERNEL STRING {
+ if (res.path != NULL) {
+ yyerror("argument specified more than once");
+ YYERROR;
+ }
+ res.path = $2;
+ }
+ | NIFS NUMBER {
+ if (res.nifs) {
+ yyerror("argument specified more than once");
+ YYERROR;
+ }
+ if (parse_ifs(&res, NULL, $2) != 0) {
+ yyerror("failed to parse interfaces: %lld", $2);
+ YYERROR;
+ }
+ }
+ | MEMORY NUMBER {
+ if (res.size != 0) {
+ yyerror("argument specified more than once");
+ YYERROR;
+ }
+ if (parse_size(&res, NULL, $2) != 0) {
+ yyerror("failed to parse size: %lld", $2);
+ YYERROR;
+ }
+ }
+ | MEMORY STRING {
+ if (res.size != 0) {
+ yyerror("argument specified more than once");
+ YYERROR;
+ }
+ if (parse_size(&res, $2, 0) != 0) {
+ yyerror("failed to parse size: %s", $2);
+ free($2);
+ YYERROR;
+ }
+ }
+ ;
+
+disable : ENABLE { $$ = 0; }
+ | DISABLE { $$ = 1; }
+ ;
+
+optnl : '\n' optnl
+ |
+ ;
+
+nl : '\n' optnl
+ ;
+
+%%
+
+struct keywords {
+ const char *k_name;
+ int k_val;
+};
+
+int
+yyerror(const char *fmt, ...)
+{
+ va_list ap;
+ char *msg;
+
+ file->errors++;
+ va_start(ap, fmt);
+ if (vasprintf(&msg, fmt, ap) == -1)
+ errx(1, "yyerror vasprintf");
+ va_end(ap);
+ warnx("%s:%d: %s", file->name, yylval.lineno, msg);
+ free(msg);
+ 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[] = {
+ { "disable", DISABLE },
+ { "disk", DISK },
+ { "enable", ENABLE },
+ { "id", VMID },
+ { "include", INCLUDE },
+ { "interfaces", NIFS },
+ { "kernel", KERNEL },
+ { "memory", MEMORY },
+ { "size", SIZE },
+ { "vm", VM },
+ { "vmm", VMM }
+ };
+ 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
+
+u_char *parsebuf;
+int parseindex;
+u_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 = parsebuf[parseindex++];
+ if (c != '\0')
+ return (c);
+ parsebuf = NULL;
+ } else
+ parseindex++;
+ }
+
+ if (pushback_index)
+ return (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++;
+ }
+ if (c == '\t' || c == ' ') {
+ /* Compress blanks to a single space. */
+ do {
+ c = getc(file->stream);
+ } while (c == '\t' || c == ' ');
+ ungetc(c, file->stream);
+ c = ' ';
+ }
+
+ 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 < MAXPUSHBACK-1)
+ return (pushback_buffer[pushback_index++] = c);
+ else
+ return (EOF);
+}
+
+int
+findeol(void)
+{
+ int c;
+
+ parsebuf = NULL;
+
+ /* skip to either EOF or the first real EOL */
+ while (1) {
+ if (pushback_index)
+ c = pushback_buffer[--pushback_index];
+ else
+ c = lgetc(0);
+ if (c == '\n') {
+ file->lineno++;
+ break;
+ }
+ if (c == EOF)
+ break;
+ }
+ return (ERROR);
+}
+
+int
+yylex(void)
+{
+ u_char buf[8096];
+ u_char *p, *val;
+ int quotec, next, c;
+ int token;
+
+top:
+ p = buf;
+ while ((c = lgetc(0)) == ' ' || c == '\t')
+ ; /* nothing */
+
+ yylval.lineno = file->lineno;
+ if (c == '#')
+ while ((c = lgetc(0)) != '\n' && c != EOF)
+ ; /* nothing */
+ if (c == '$' && parsebuf == NULL) {
+ while (1) {
+ if ((c = lgetc(0)) == EOF)
+ return (0);
+
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ if (isalnum(c) || c == '_') {
+ *p++ = c;
+ continue;
+ }
+ *p = '\0';
+ lungetc(c);
+ break;
+ }
+ val = symget(buf);
+ if (val == NULL) {
+ yyerror("macro '%s' not defined", buf);
+ return (findeol());
+ }
+ parsebuf = val;
+ parseindex = 0;
+ goto top;
+ }
+
+ 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 || c == ' ' || c == '\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 = strdup(buf);
+ if (yylval.v.string == NULL)
+ err(1, "yylex: strdup");
+ return (STRING);
+ }
+
+#define allowed_to_end_number(x) \
+ (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
+
+ if (c == '-' || isdigit(c)) {
+ do {
+ *p++ = c;
+ if ((unsigned)(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(*--p);
+ c = *--p;
+ if (c == '-')
+ return (c);
+ }
+ }
+
+#define allowed_in_string(x) \
+ (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
+ x != '{' && x != '}' && \
+ x != '!' && x != '=' && x != '#' && \
+ x != ','))
+
+ if (isalnum(c) || c == ':' || c == '_') {
+ do {
+ *p++ = c;
+ if ((unsigned)(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)
+ if ((yylval.v.string = strdup(buf)) == NULL)
+ err(1, "yylex: strdup");
+ return (token);
+ }
+ if (c == '\n') {
+ yylval.lineno = file->lineno;
+ file->lineno++;
+ }
+ if (c == EOF)
+ return (0);
+ return (c);
+}
+
+struct file *
+pushfile(const char *name, int secret)
+{
+ struct file *nfile;
+
+ if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
+ warn("malloc");
+ return (NULL);
+ }
+ if ((nfile->name = strdup(name)) == NULL) {
+ warn("malloc");
+ free(nfile);
+ return (NULL);
+ }
+ if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
+ warn("%s", 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 sym *sym, *next;
+
+ if ((file = pushfile(filename, 0)) == NULL) {
+ return (-1);
+ }
+ topfile = file;
+ setservent(1);
+
+ yyparse();
+ errors = file->errors;
+ popfile();
+
+ endservent();
+
+ /* Free macros and check which have not been used. */
+ for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) {
+ next = TAILQ_NEXT(sym, entry);
+ if (!sym->used)
+ fprintf(stderr, "warning: macro '%s' not "
+ "used\n", sym->nam);
+ if (!sym->persist) {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entry);
+ free(sym);
+ }
+ }
+
+ if (errors)
+ return (-1);
+
+ return (0);
+}
+
+int
+symset(const char *nam, const char *val, int persist)
+{
+ struct sym *sym;
+
+ for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam);
+ sym = TAILQ_NEXT(sym, entry))
+ ; /* nothing */
+
+ if (sym != NULL) {
+ if (sym->persist == 1)
+ return (0);
+ else {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entry);
+ free(sym);
+ }
+ }
+ if ((sym = calloc(1, sizeof(*sym))) == NULL)
+ return (-1);
+
+ sym->nam = strdup(nam);
+ if (sym->nam == NULL) {
+ free(sym);
+ return (-1);
+ }
+ sym->val = strdup(val);
+ if (sym->val == NULL) {
+ free(sym->nam);
+ free(sym);
+ return (-1);
+ }
+ sym->used = 0;
+ sym->persist = persist;
+ TAILQ_INSERT_TAIL(&symhead, sym, entry);
+ return (0);
+}
+
+int
+cmdline_symset(char *s)
+{
+ char *sym, *val;
+ int ret;
+ size_t len;
+
+ if ((val = strrchr(s, '=')) == NULL)
+ return (-1);
+
+ len = strlen(s) - strlen(val) + 1;
+ if ((sym = malloc(len)) == NULL)
+ errx(1, "cmdline_symset: malloc");
+
+ (void)strlcpy(sym, s, len);
+
+ ret = symset(sym, val + 1, 1);
+ free(sym);
+
+ return (ret);
+}
+
+char *
+symget(const char *nam)
+{
+ struct sym *sym;
+
+ TAILQ_FOREACH(sym, &symhead, entry)
+ if (strcmp(nam, sym->nam) == 0) {
+ sym->used = 1;
+ return (sym->val);
+ }
+ return (NULL);
+}
diff --git a/usr.sbin/vmmctl/parser.c b/usr.sbin/vmmctl/parser.c
new file mode 100644
index 00000000000..b095067b3b1
--- /dev/null
+++ b/usr.sbin/vmmctl/parser.c
@@ -0,0 +1,317 @@
+/* $OpenBSD: parser.c,v 1.1 2015/11/22 20:55:18 reyk Exp $ */
+
+/*
+ * Copyright (c) 2010-2015 Reyk Floeter <reyk@openbsd.org>
+ * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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/uio.h>
+
+#include <machine/vmmvar.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <err.h>
+
+#include "vmd.h"
+#include "parser.h"
+
+enum token_type {
+ NOTOKEN,
+ ENDTOKEN,
+ KEYWORD,
+ DISK,
+ NAME,
+ NIFS,
+ PATH,
+ SIZE,
+ VMID
+};
+
+struct token {
+ enum token_type type;
+ const char *keyword;
+ int value;
+ const struct token *next;
+};
+
+static const struct token t_main[];
+static const struct token t_show[];
+static const struct token t_create[];
+static const struct token t_imgsize[];
+static const struct token t_imgsize_val[];
+static const struct token t_start[];
+static const struct token t_start_name[];
+static const struct token t_disk[];
+static const struct token t_kernel[];
+static const struct token t_memory[];
+static const struct token t_ifs[];
+static const struct token t_vm[];
+static const struct token t_opt_id[];
+static const struct token t_id[];
+static const struct token t_opt_path[];
+
+static const struct token t_main[] = {
+ { KEYWORD, "enable", CMD_ENABLE, NULL },
+ { KEYWORD, "create", CMD_CREATE, t_create },
+ { KEYWORD, "disable", CMD_DISABLE, NULL },
+ { KEYWORD, "load", CMD_LOAD, t_opt_path },
+ { KEYWORD, "show", NONE, t_show },
+ { KEYWORD, "start", CMD_START, t_start_name },
+ { KEYWORD, "terminate", CMD_TERMINATE, t_id },
+ { ENDTOKEN, "", NONE, NULL }
+};
+
+static const struct token t_create[] = {
+ { PATH, "", NONE, t_imgsize },
+ { ENDTOKEN, "", NONE, NULL }
+};
+
+static const struct token t_imgsize[] = {
+ { KEYWORD, "size", NONE, t_imgsize_val },
+ { ENDTOKEN, "", NONE, NULL }
+};
+
+static const struct token t_imgsize_val[] = {
+ { SIZE, "", NONE, NULL },
+ { ENDTOKEN, "", NONE, NULL }
+};
+
+static const struct token t_start[] = {
+ { NOTOKEN, "", NONE, NULL },
+ { KEYWORD, "disk", NONE, t_disk },
+ { KEYWORD, "interfaces", NONE, t_ifs },
+ { KEYWORD, "kernel", NONE, t_kernel },
+ { KEYWORD, "memory", NONE, t_memory },
+ { ENDTOKEN, "", NONE, NULL }
+};
+
+static const struct token t_ifs[] = {
+ { NIFS, "", NONE, t_start },
+ { ENDTOKEN, "", NONE, NULL }
+};
+
+static const struct token t_disk[] = {
+ { DISK, "", NONE, t_start },
+ { ENDTOKEN, "", NONE, NULL }
+};
+
+static const struct token t_memory[] = {
+ { SIZE, "", NONE, t_start },
+ { ENDTOKEN, "", NONE, NULL }
+};
+
+static const struct token t_kernel[] = {
+ { PATH, "", NONE, t_start },
+ { ENDTOKEN, "", NONE, NULL }
+};
+
+static const struct token t_start_name[] = {
+ { NAME, "", NONE, t_start },
+ { ENDTOKEN, "", NONE, NULL }
+};
+
+static const struct token t_show[] = {
+ { KEYWORD, "info", CMD_INFO, t_opt_id },
+ { ENDTOKEN, "", NONE, NULL }
+};
+
+static const struct token t_opt_id[] = {
+ { NOTOKEN, "", NONE, NULL },
+ { VMID, "", NONE, NULL },
+ { ENDTOKEN, "", NONE, NULL }
+};
+
+static const struct token t_id[] = {
+ { VMID, "", NONE, NULL },
+ { ENDTOKEN, "", NONE, NULL }
+};
+
+static const struct token t_opt_path[] = {
+ { NOTOKEN, "", NONE, NULL },
+ { PATH, "", NONE, NULL },
+ { ENDTOKEN, "", NONE, NULL }
+};
+
+static struct parse_result res;
+
+const struct token *match_token(char *, const struct token []);
+void show_valid_args(const struct token []);
+int parse_addr(const char *);
+
+struct parse_result *
+parse(int argc, char *argv[])
+{
+ const struct token *table = t_main;
+ const struct token *match;
+
+ bzero(&res, sizeof(res));
+
+ while (argc >= 0) {
+ if ((match = match_token(argv[0], table)) == NULL) {
+ fprintf(stderr, "valid commands/args:\n");
+ show_valid_args(table);
+ return (NULL);
+ }
+
+ argc--;
+ argv++;
+
+ if (match->type == NOTOKEN || match->next == NULL)
+ break;
+
+ table = match->next;
+ }
+
+ if (argc > 0) {
+ fprintf(stderr, "superfluous argument: %s\n", argv[0]);
+ return (NULL);
+ }
+
+ return (&res);
+}
+
+const struct token *
+match_token(char *word, const struct token table[])
+{
+ unsigned int i, match = 0;
+ const struct token *t = NULL;
+
+ for (i = 0; table[i].type != ENDTOKEN; i++) {
+ switch (table[i].type) {
+ case NOTOKEN:
+ if (word == NULL || strlen(word) == 0) {
+ match++;
+ t = &table[i];
+ }
+ break;
+ case KEYWORD:
+ if (word != NULL && strncmp(word, table[i].keyword,
+ strlen(word)) == 0) {
+ match++;
+ t = &table[i];
+ if (t->value)
+ res.action = t->value;
+ }
+ break;
+ case PATH:
+ if (!match && word != NULL && strlen(word) > 0) {
+ res.path = strdup(word);
+ match++;
+ t = &table[i];
+ }
+ break;
+ case NAME:
+ if (!match && word != NULL && strlen(word) > 0) {
+ res.name = strdup(word);
+ match++;
+ t = &table[i];
+ }
+ break;
+ case NIFS:
+ if (match || word == NULL || *word == '\0')
+ break;
+
+ if (parse_ifs(&res, word, 0) == -1)
+ return (NULL);
+
+ match++;
+ t = &table[i];
+ break;
+ case SIZE:
+ if (match || word == NULL || *word == '\0')
+ break;
+
+ if (parse_size(&res, word, 0) == -1)
+ return (NULL);
+
+ match++;
+ t = &table[i];
+ break;
+ case VMID:
+ if (match || word == NULL || *word == '\0')
+ break;
+
+ if (parse_vmid(&res, word, 0) == -1)
+ return (NULL);
+
+ match++;
+ t = &table[i];
+ break;
+ case DISK:
+ if (match || word == NULL || *word == '\0')
+ break;
+
+ if (parse_disk(&res, word) == -1)
+ return (NULL);
+
+ match++;
+ t = &table[i];
+ break;
+ case ENDTOKEN:
+ break;
+ }
+ }
+
+ if (match != 1) {
+ if (word == NULL)
+ fprintf(stderr, "missing argument:\n");
+ else if (match > 1)
+ fprintf(stderr, "ambiguous argument: %s\n", word);
+ else if (match < 1)
+ fprintf(stderr, "unknown argument: %s\n", word);
+ return (NULL);
+ }
+
+ return (t);
+}
+
+void
+show_valid_args(const struct token table[])
+{
+ int i;
+
+ for (i = 0; table[i].type != ENDTOKEN; i++) {
+ switch (table[i].type) {
+ case NOTOKEN:
+ fprintf(stderr, " <cr>\n");
+ break;
+ case KEYWORD:
+ fprintf(stderr, " %s\n", table[i].keyword);
+ break;
+ case NAME:
+ fprintf(stderr, " <name>\n");
+ break;
+ case PATH:
+ case DISK:
+ fprintf(stderr, " <path>\n");
+ break;
+ case NIFS:
+ fprintf(stderr, " <count>\n");
+ break;
+ case VMID:
+ fprintf(stderr, " <id>\n");
+ break;
+ case SIZE:
+ fprintf(stderr, " <size>(K|M|G)\n");
+ break;
+ case ENDTOKEN:
+ break;
+ }
+ }
+}
diff --git a/usr.sbin/vmmctl/parser.h b/usr.sbin/vmmctl/parser.h
new file mode 100644
index 00000000000..66b86b7bc53
--- /dev/null
+++ b/usr.sbin/vmmctl/parser.h
@@ -0,0 +1,81 @@
+/* $OpenBSD: parser.h,v 1.1 2015/11/22 20:55:18 reyk Exp $ */
+
+/*
+ * Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
+ *
+ * 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/queue.h>
+#include <imsg.h>
+
+#ifndef VMMCTL_PARSER_H
+#define VMMCTL_PARSER_H
+
+#define VMM_CONF "/etc/vmm.conf"
+
+enum actions {
+ NONE,
+ CMD_CREATE,
+ CMD_ENABLE,
+ CMD_DISABLE,
+ CMD_START,
+ CMD_TERMINATE,
+ CMD_INFO,
+ CMD_LOAD
+};
+
+struct parse_result {
+ enum actions action;
+ uint32_t id;
+ char *name;
+ char *path;
+ size_t size;
+ size_t nifs;
+ size_t ndisks;
+ char **disks;
+ int disable;
+};
+
+struct imsgbuf *ibuf;
+
+/* main.c */
+int vmmaction(struct parse_result *);
+int parse_ifs(struct parse_result *, char *, int);
+int parse_size(struct parse_result *, char *, long long);
+int parse_disk(struct parse_result *, char *);
+int parse_vmid(struct parse_result *, char *, uint32_t);
+
+/* parser.c */
+struct parse_result *
+ parse(int, char *[]);
+
+/* parse.y */
+int parse_config(const char *);
+int cmdline_symset(char *);
+
+/* vmmctl.c */
+int create_imagefile(char *, long);
+int start_vm(const char *, int, int, int, char **, char *);
+int start_vm_complete(struct imsg *, int *);
+void enable_vmm(void);
+int enable_vmm_complete(struct imsg *, int *);
+void disable_vmm(void);
+int disable_vmm_complete(struct imsg *, int *);
+void terminate_vm(uint32_t);
+int terminate_vm_complete(struct imsg *, int *);
+void get_info_vm(uint32_t);
+int add_info(struct imsg *, int *);
+void print_vm_info(struct vm_info_result *, size_t);
+
+#endif /* VMMCTL_PARSER_H */
diff --git a/usr.sbin/vmmctl/vmm.conf.5 b/usr.sbin/vmmctl/vmm.conf.5
new file mode 100644
index 00000000000..cacefa96a0d
--- /dev/null
+++ b/usr.sbin/vmmctl/vmm.conf.5
@@ -0,0 +1,166 @@
+.\" $OpenBSD: vmm.conf.5,v 1.1 2015/11/22 20:55:18 reyk Exp $
+.\"
+.\" Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org>
+.\" Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
+.\"
+.\" 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.
+.\"
+.Dd $Mdocdate: November 22 2015 $
+.Dt VMM.CONF 5
+.Os
+.Sh NAME
+.Nm vmm.conf
+.Nd virtual machine configuration
+.Sh DESCRIPTION
+.Nm
+is the configuration file to configure the the virtual machine monitor
+(VMM) subsystem.
+A VMM manages virtual machines (VMs) on a
+.Ar host .
+The VMM subsystem is responsible for creating, destroying, and
+executing VMs.
+.Sh SECTIONS
+.Nm
+is divided into three main sections:
+.Bl -tag -width xxxx
+.It Sy Macros
+User-defined variables may be defined and used later, simplifying the
+configuration file.
+.It Sy VMM Configuration
+Global settings for the
+.Xr vmm 4
+subsystem.
+.It Sy VM Configuration
+Configuration for each individual virtual machine.
+.El
+.Pp
+Withint the sections,
+the current line can be extended over multiple lines using a backslash
+.Pq Sq \e .
+Comments can be put anywhere in the file using a hash mark
+.Pq Sq # ,
+and extend to the end of the current line.
+Care should be taken when commenting out multi-line text:
+the comment is effective until the end of the entire block.
+.Pp
+Argument names not beginning with a letter, digit, or underscore
+must be quoted.
+.Pp
+Additional configuration files can be included with the
+.Ic include
+keyword, for example:
+.Bd -literal -offset indent
+include "/etc/vm1.example.com.conf"
+.Ed
+.Sh MACROS
+Macros can be defined that will later be expanded in context.
+Macro names must start with a letter, digit, or underscore,
+and may contain any of those characters.
+Macro names may not be reserved words (for example,
+.Ic vm ,
+.Ic memory ,
+or
+.Ic disk ) .
+Macros are not expanded inside quotes.
+.Pp
+For example:
+.Bd -literal -offset indent
+ramdisk="/bsd.rd"
+vm "vm1.example.com" {
+ memory 512M
+ kernel $ramdisk
+}
+.Ed
+.Sh VMM CONFIGURATION
+The options are as follows:
+.Bl -tag -width Ds
+.It Cm vmm disable
+Disable the VMM subsystem.
+Virtual machines running on the host should be terminated first.
+.It Cm vmm enable
+Enable the VMM subsystem.
+The VMM subsystem must be enabled before VMs can be managed on the host.
+.El
+.Pp
+Generally, the
+.Nm
+utility is run with
+.Cm enable
+option during system startup to enable the VMM subsystem on boot.
+This can be automated via the
+.Xr rc 8
+and
+.Xr rc.conf 8
+facilities used during system startup.
+.Sh VM CONFIGURATION
+Each
+.Ic vm
+section starts with a declaration of the virtual machine
+.Ar name :
+.Bl -tag -width Ds
+.It Ic vm Ar name Brq ...
+This name can be any string, and is typically a hostname.
+.El
+.Pp
+Followed by a block of parameters that is enclosed in curly brackets:
+.Bl -tag -width Ds
+.It Cm enable
+Automatically start the VM.
+This is the default if neither
+.Cm enable
+nor
+.Cm disable
+is specified.
+.It Cm disable
+Do not start this VM.
+.It Cm disk Ar path
+Disk image file (may be specified multiple times to add multiple disk images).
+.It Cm kernel Ar path
+Kernel to load when booting the VM.
+.It Cm memory Ar size
+Memory size of the VM, in bytes.
+Optionally, the units 'K', 'M', or 'G', for kilo-, mega-, and gigabytes
+can be used.
+.It Cm interfaces Ar count
+Number of network interfaces to add to the VM.
+.El
+.Sh EXAMPLES
+Enable the VMM subsystem:
+.Bd -literal -offset indent
+vmm enable
+.Ed
+.Pp
+Create a new VM with 512MB memory, 1 network interface, one disk image
+('disk.img') and boot from kernel '/bsd':
+.Bd -literal -offset indent
+vm "vm2.example.com" {
+ memory 512M
+ interfaces 1
+ disk "/var/vmm/vm2-disk.img"
+ kernel "/bsd"
+}
+.Ed
+.Pp
+.Sh SEE ALSO
+.Xr vmm 4 ,
+.Xr vmd 8 ,
+.Xr vmmctl 8
+.Sh HISTORY
+The
+.Nm
+file format first appeared in
+.Ox 5.9 .
+.Sh AUTHORS
+.An Mike Larkin Aq Mt mlarkin@openbsd.org
+and
+.An Reyk Floeter Aq Mt reyk@openbsd.org .
diff --git a/usr.sbin/vmmctl/vmmctl.8 b/usr.sbin/vmmctl/vmmctl.8
index 3ece162b75d..cc06350929e 100644
--- a/usr.sbin/vmmctl/vmmctl.8
+++ b/usr.sbin/vmmctl/vmmctl.8
@@ -19,24 +19,10 @@
.Nm vmmctl
.Nd control VMM subsystem
.Sh SYNOPSIS
-.Nm
-.Op Fl de
.Nm
-.Op Fl C
-.Op Fl i Ar imagefile path
-.Op Fl s Ar size in MB
-.Nm
-.Op Fl S
-.Op Fl m Ar memory size
-.Op Fl n Ar nr nics
-.Op Fl b Ar diskfile
-.Op Fl k Ar kernel
-.Nm
-.Op Fl T
-.Op id
-.Nm
-.Op Fl I
-.Op id
+.Op Fl s Ar socket
+.Ar command
+.Op Ar arg ...
.Sh DESCRIPTION
The
.Nm
@@ -45,48 +31,60 @@ A VMM manages virtual machines (VMs) on a
.Ar host .
The VMM subsystem is responsible for creating, destroying, and executing
VMs.
-
.Sh VMM OPERATIONS
The options are as follows:
-.Bl -tag -width Dsssigfile
-.It Fl d
-Disable the VMM subsystem. Virtual machines running on the host should be
-terminated first.
-.It Fl e
+.Bl -tag -width Ds
+.It Cm enable
Enable the VMM subsystem. The VMM subsystem must be enabled before VMs
can be managed on the host.
+.It Cm disable
+Disable the VMM subsystem. Virtual machines running on the host should be
+terminated first.
+.It Cm load Op Ar path
+Load the
+.Xr vmm.conf 5
+configuration file from
+.Pa /etc/vmm.conf
+or the specified
+.Ar path.
.El
.Pp
-Generally, the
+Generally, the
.Nm
-utility is run with -e option during system startup to enable the VMM
-subsystem on boot. This can be automated via the
+utility is run with
+.Cm enable
+option during system startup to enable the VMM subsystem on boot.
+This can be automated via the
.Xr rc 8
and
.Xr rc.conf 8
facilities used during system startup.
.Sh VM OPERATIONS
The options are as follows:
-.Bl -tag -width Dsssignature
-.It Fl C
+.Bl -tag -width Ds
+.It Cm create Ar path Cm size Ar number
Creates a VM disk image file with the specified pathname and size in MB.
-.It Fl S
-Starts a VM defined by the specified parameters.
-.It Fl m
-Memory size (in MB) of the VM
-.It Fl n
-Number of network interfaces to add to the VM
-.It Fl b
+.It Cm start Ar name Op Ar parameters
+Starts a VM defined by the specified name and parameters:
+.Bl -tag -width "memory size"
+.It Cm disk Ar path
Disk image file (may be specified multiple times to add multiple disk images).
-.It Fl k
-Kernel to load when booting the VM
-.It Fl T
-Terminates (stops) a VM defined by the specified VM ID.
-.It Fl I
-Lists VMs running on the host, optionally listing just the selected VM ID.
+.It Cm kernel Ar path
+Kernel to load when booting the VM.
+.It Cm memory Ar size
+Memory size of the VM, in bytes.
+Optionally, the units 'K', 'M', or 'G', for kilo-, mega-, and gigabytes
+can be used.
+.It Cm interfaces Ar count
+Number of network interfaces to add to the VM.
+.El
+.It Cm terminate Ar id
+Terminates (stops) a VM defined by the specified VM
+.Ar id .
+.It Cm show info Op Ar id
+Lists VMs running on the host, optionally listing just the selected VM
+.Ar id .
.El
-
-.Bl -tag -width Dsssignature
.Sh EXIT STATUS
.Ex -std vmmctl
.Nm
@@ -94,26 +92,39 @@ may fail due to one of the following reasons:
.Pp
.Bl -bullet -compact
.It
-The VMM subsystem could not be enabled or disabled as requested
+The VMM subsystem could not be enabled or disabled as requested.
.It
-A requested VM-based operation could not be completed
+A requested VM-based operation could not be completed.
.El
.Sh EXAMPLES
-Enable the VMM subsystem
-.Dl $ vmmctl -e
+Enable the VMM subsystem:
+.Dl $ vmmctl enable
+.Pp
+Disable the VMM subsystem:
+.Dl $ vmmctl disable
.Pp
-Disable the VMM subsystem
-.Dl $ vmmctl -d
+Create a new disk image ('disk.img') of 4 gigabyte size:
+.Dl $ vmmctl create disk.img size 4G
.Pp
Create a new VM with 512MB memory, 1 network interface, one disk image
-('disk.img') and boot from kernel '/bsd'.
-.Dl $ vmmctl -S -m 512 -n 1 -b disk.img -k /bsd
+('disk.img') and boot from kernel '/bsd':
+.Dl $ vmmctl start "myvm" memory 512M interfaces 1 disk disk.img kernel /bsd
.Pp
Terminate VM number 1
-.Dl $ vmmctl -T1
+.Dl $ vmmctl terminate 1
.Pp
+.Sh FILES
+.Bl -tag -width "/etc/var/run/vmd.sockXX" -compact
+.It Pa /etc/vmm.conf
+Default configuration file.
+.It Pa /var/run/vmd.sock
+.Ux Ns -domain
+socket used for communication with
+.Xr vmd 8 .
+.El
.Sh SEE ALSO
.Xr vmm 4 ,
+.Xr vmm.conf 5 ,
.Xr vmd 8 ,
.Xr rc.conf 8
.Sh HISTORY
@@ -123,3 +134,5 @@ command first appeared in
.Ox 5.9 .
.Sh AUTHORS
.An Mike Larkin Aq Mt mlarkin@openbsd.org
+and
+.An Reyk Floeter Aq Mt reyk@openbsd.org .
diff --git a/usr.sbin/vmmctl/vmmctl.c b/usr.sbin/vmmctl/vmmctl.c
index 967641a425f..0a61b6af853 100644
--- a/usr.sbin/vmmctl/vmmctl.c
+++ b/usr.sbin/vmmctl/vmmctl.c
@@ -38,367 +38,12 @@
#include <unistd.h>
#include "vmd.h"
+#include "parser.h"
-__dead void usage(void);
-int main(int, char **);
-int create_imagefile(char *, long);
-void enable_vmm(void);
-int enable_vmm_complete(struct imsg *, int *);
-void disable_vmm(void);
-int disable_vmm_complete(struct imsg *, int *);
-int start_vm(int, int, int, char **, char *);
-int start_vm_complete(struct imsg *, int *);
-void terminate_vm(uint32_t);
-int terminate_vm_complete(struct imsg *, int *);
-void get_info_vm(void);
-int add_info(struct imsg *, int *);
-void print_vm_info(struct vm_info_result *, size_t);
-
-#define CMD_ENABLE 0x1
-#define CMD_DISABLE 0x2
-#define CMD_CREATE 0x4
-#define CMD_START 0x8
-#define CMD_TERMINATE 0x10
-#define CMD_INFO 0x20
-
-struct imsgbuf *ibuf;
extern char *__progname;
uint32_t info_id;
/*
- * usage
- *
- * print program usage. does not return
- */
-__dead void
-usage(void)
-{
- fprintf(stderr, "usage: %s [-de]\n"
- " %s [-C] [-i imagefile path] [-s size in MB]\n"
- " %s [-S] [-m memory size][-n nr nics][-b diskfile]"
- "[-k kernel]\n"
- " %s [-T[id]]\n"
- " %s [-I[id]]\n",
- __progname, __progname, __progname, __progname,
- __progname);
- exit(1);
-}
-
-int
-main(int argc, char **argv)
-{
- int ch, command, ret, fd, processed, n, memsize, nnics, ndisks, i;
- const char *errstr;
- char *imgfile, *sockname;
- char *disks[VMM_MAX_DISKS_PER_VM];
- char *kernel;
- long imgsize;
- struct sockaddr_un sun;
- struct imsg imsg;
- uint32_t terminate_id;
-
- command = 0;
- imgfile = NULL;
- imgsize = 0;
- info_id = 0;
- terminate_id = 0;
- memsize = 0;
- nnics = 0;
- ndisks = 0;
- kernel = NULL;
-
- for (i = 0 ; i < VMM_MAX_DISKS_PER_VM; i++) {
- disks[i] = malloc(VMM_MAX_PATH_DISK);
- if (disks[i] == NULL) {
- fprintf(stderr, "memory allocation error\n");
- exit(1);
- }
-
- bzero(disks[i], VMM_MAX_PATH_DISK);
- }
-
-
- while ((ch = getopt(argc, argv, "CST::I::dei:s:m:n:b:k:")) != -1) {
- switch(ch) {
- case 'C':
- /* Create imagefile command */
- if (command)
- usage();
-
- command = CMD_CREATE;
- break;
- case 'S':
- /* Start command */
- if (command)
- usage();
-
- command = CMD_START;
- break;
- case 'T':
- /* Terminate command */
- if (command)
- usage();
-
- command = CMD_TERMINATE;
- if (optarg != NULL) {
- terminate_id = strtonum(optarg, 1, UINT_MAX,
- &errstr);
- if (errstr) {
- fprintf(stderr,
- "%s: invalid VM ID (%s) specified: "
- "%s\n", __progname, optarg, errstr);
- usage();
- }
- } else {
- fprintf(stderr, "%s: missing VM ID parameter\n",
- __progname);
- usage();
- }
-
- if (terminate_id == 0) {
- fprintf(stderr, "%s: invalid vm ID supplied\n",
- __progname);
- usage();
- }
- break;
- case 'I':
- /* Info command */
- if (command)
- usage();
-
- command = CMD_INFO;
- if (optarg != NULL) {
- info_id = strtonum(optarg, 1, UINT_MAX,
- &errstr);
- if (errstr) {
- fprintf(stderr,
- "%s: invalid VM ID (%s) specified: "
- "%s\n", __progname, optarg, errstr);
- usage();
- }
- }
- break;
- case 'd':
- /* Disable VMM mode */
- if (command)
- usage();
-
- command = CMD_DISABLE;
- break;
- case 'e':
- /* Enable VMM mode */
- if (command)
- usage();
-
- command = CMD_ENABLE;
- break;
- case 'i':
- /* Imagefile name parameter */
- if (imgfile)
- usage();
-
- imgfile = strdup(optarg);
- break;
- case 's':
- /* Imagefile size parameter */
- if (imgsize != 0)
- usage();
-
- imgsize = strtonum(optarg, 1, LONG_MAX, &errstr);
- if (errstr) {
- fprintf(stderr,
- "%s: invalid image size (%s) specified: "
- "%s\n", __progname, optarg, errstr);
- usage();
- }
- break;
- case 'm':
- /* VM memory parameter */
- if (memsize !=0)
- usage();
-
- memsize = strtonum(optarg, 1, VMM_MAX_VM_MEM_SIZE,
- &errstr);
-
- if (errstr) {
- fprintf(stderr," %s: invalid memory size (%s) "
- "specified: %s\n", __progname, optarg,
- errstr);
- }
- break;
- case 'n':
- /* VM num nics parameter */
- if (nnics !=0)
- usage();
-
- nnics = strtonum(optarg, 1, VMM_MAX_VM_MEM_SIZE,
- &errstr);
-
- if (errstr) {
- fprintf(stderr," %s: invalid number of nics "
- " (%s) specified: %s\n", __progname,
- optarg, errstr);
- }
- break;
- case 'b':
- /* VM disk parameter */
- if (ndisks < VMM_MAX_DISKS_PER_VM) {
- strlcpy(disks[ndisks], optarg,
- VMM_MAX_PATH_DISK);
- ndisks++;
- } else {
- fprintf(stderr, "%s: maximum number of disks "
- "reached, ignoring disk %s\n", __progname,
- optarg);
- }
- break;
- case 'k':
- /* VM kernel parameter */
- if (kernel != NULL)
- usage();
-
- kernel = malloc(VMM_MAX_KERNEL_PATH);
- if (kernel == NULL) {
- fprintf(stderr, "memory allocation error\n");
- exit(1);
- }
-
- strlcpy(kernel, optarg, VMM_MAX_KERNEL_PATH);
- break;
- default:
- usage();
- }
- }
-
- if (!command)
- usage();
-
- /* Set up comms via imsg with vmd, unless CMD_CREATE (imgfile) */
- if (command != CMD_CREATE) {
- sockname = SOCKET_NAME;
- if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
- err(1, "socket init failed");
-
- bzero(&sun, sizeof(sun));
- sun.sun_family = AF_UNIX;
- if (strlcpy(sun.sun_path, sockname, sizeof(sun.sun_path)) >=
- sizeof(sun.sun_path))
- errx(1, "socket name too long");
-
- if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1)
- err(1, "connect failed to vmd control socket");
-
- if ((ibuf = malloc(sizeof(struct imsgbuf))) == NULL)
- err(1, NULL);
- imsg_init(ibuf, fd);
- }
-
- if (command == CMD_DISABLE)
- disable_vmm();
-
- if (command == CMD_ENABLE)
- enable_vmm();
-
- if (command == CMD_CREATE) {
- if (imgfile == NULL) {
- fprintf(stderr, "%s: missing -i (imagefile path) "
- "argument\n", __progname);
- exit(1);
- }
-
- if (imgsize <= 0) {
- fprintf(stderr, "%s: missing/invalid -s (imgfile size) "
- "argument\n", __progname);
- exit(1);
- }
-
- ret = create_imagefile(imgfile, imgsize);
- if (ret) {
- fprintf(stderr, "%s: create imagefile operation failed "
- "(%s)\n", __progname, strerror(ret));
- exit(ret);
- } else {
- fprintf(stdout, "%s: imagefile created\n", __progname);
- exit(0);
- }
- }
-
- if (command == CMD_START) {
- if (memsize == 0) {
- fprintf(stderr, "%s: missing -m (memory size) "
- "argument\n", __progname);
- exit(1);
- }
-
- ret = start_vm(memsize, nnics, ndisks, disks, kernel);
- if (ret) {
- fprintf(stderr, "%s: start VM operation failed "
- "(%s)\n", __progname, strerror(ret));
- exit(ret);
- }
- }
-
- if (command == CMD_TERMINATE)
- terminate_vm(terminate_id);
-
- if (command == CMD_INFO)
- get_info_vm();
-
- /* Send request message */
- while (ibuf->w.queued)
- if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN)
- err(1, "write error");
-
- /*
- * We expect vmd to send us one reply message, and that the reply
- * message we receive will be of the proper _REPLY type.
- *
- * The exception to this is the -I (get info) option, where vmd
- * may send us arbitrary number of _REPLY messages followed by
- * and _END message to indicate no more messages are forthcoming.
- */
- processed = 0;
- while (!processed) {
- if ((n = imsg_read(ibuf)) == -1)
- err(1, "imsg_read error");
- if (n == 0)
- errx(1, "pipe closed");
-
- while (!processed) {
- if ((n = imsg_get(ibuf, &imsg)) == -1)
- err(1, "imsg_get error");
- if (n == 0)
- break;
-
- switch (command) {
- case CMD_DISABLE:
- processed = disable_vmm_complete(&imsg,
- &ret);
- break;
- case CMD_ENABLE:
- processed = enable_vmm_complete(&imsg,
- &ret);
- break;
- case CMD_START:
- processed = start_vm_complete(&imsg,
- &ret);
- break;
- case CMD_TERMINATE:
- processed = terminate_vm_complete(&imsg,
- &ret);
- break;
- case CMD_INFO:
- processed = add_info(&imsg, &ret);
- break;
- }
- imsg_free(&imsg);
- }
- }
-
- return (ret);
-}
-
-/*
* enable_vmm
*
* Request vmd to enable VMM mode on the machine. This will result in the
@@ -518,6 +163,7 @@ disable_vmm_complete(struct imsg *imsg, int *ret)
* Request vmd to start the VM defined by the supplied parameters
*
* Parameters:
+ * name: optional name of the VM
* memsize: memory size (MB) of the VM to create
* nnics: number of vionet network interfaces to create
* ndisks: number of disk images
@@ -529,7 +175,8 @@ disable_vmm_complete(struct imsg *imsg, int *ret)
* ENOMEM if a memory allocation failure occurred.
*/
int
-start_vm(int memsize, int nnics, int ndisks, char **disks, char *kernel)
+start_vm(const char *name, int memsize, int nnics, int ndisks, char **disks,
+ char *kernel)
{
struct vm_create_params *vcp;
int i;
@@ -547,7 +194,9 @@ start_vm(int memsize, int nnics, int ndisks, char **disks, char *kernel)
for (i = 0 ; i < ndisks; i++)
strlcpy(vcp->vcp_disks[i], disks[i], VMM_MAX_PATH_DISK);
- strlcpy(vcp->vcp_kernel, kernel, VMM_MAX_KERNEL_PATH);
+ if (name != NULL)
+ strlcpy(vcp->vcp_name, name, VMM_MAX_NAME_LEN);
+ strlcpy(vcp->vcp_kernel, kernel, VMM_MAX_KERNEL_PATH);
vcp->vcp_nnics = nnics;
imsg_compose(ibuf, IMSG_VMDOP_START_VM_REQUEST, 0, 0, -1,
@@ -673,8 +322,9 @@ terminate_vm_complete(struct imsg *imsg, int *ret)
* Request a list of running VMs from vmd
*/
void
-get_info_vm(void)
+get_info_vm(uint32_t id)
{
+ info_id = id;
imsg_compose(ibuf, IMSG_VMDOP_GET_INFO_VM_REQUEST, 0, 0, -1, NULL, 0);
}