diff mbox

[3/4] QemuOpts: parse config from file.

Message ID 4AD61EAD.4040402@codemonkey.ws
State New
Headers show

Commit Message

Anthony Liguori Oct. 14, 2009, 6:55 p.m. UTC
Gerd Hoffmann wrote:
> Add functions to parse QemuOpts from a git-style config file.
>
> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
>   

FWIW, here's my parser.

Regards,

Anthony Liguori
diff mbox

Patch

commit 8b65f2a706e15efd40a308fd66bcf613f6569962
Author: Anthony Liguori <aliguori@us.ibm.com>
Date:   Mon Oct 12 09:25:21 2009 -0500

    Config parser

diff --git a/Makefile b/Makefile
index 8d78dc1..0a8dadf 100644
--- a/Makefile
+++ b/Makefile
@@ -125,6 +125,7 @@  obj-y += qemu-char.o aio.o net-checksum.o savevm.o
 obj-y += msmouse.o ps2.o
 obj-y += qdev.o qdev-properties.o
 obj-y += qint.o qstring.o qdict.o qlist.o qemu-config.o
+obj-y += config-parser.o
 
 obj-$(CONFIG_BRLAPI) += baum.o
 obj-$(CONFIG_WIN32) += tap-win32.o
@@ -212,6 +213,7 @@  check-qint: check-qint.o qint.o qemu-malloc.o
 check-qstring: check-qstring.o qstring.o qemu-malloc.o
 check-qdict: check-qdict.o qdict.o qint.o qstring.o qemu-malloc.o
 check-qlist: check-qlist.o qlist.o qint.o qemu-malloc.o
+check-config-parser: check-config-parser.o config-parser.o qlist.o qstring.o qemu-malloc.o
 
 clean:
 # avoid old build problems by removing potentially incorrect old files
diff --git a/config-parser.c b/config-parser.c
new file mode 100644
index 0000000..aa8c08a
--- /dev/null
+++ b/config-parser.c
@@ -0,0 +1,384 @@ 
+#include "qemu-common.h"
+#include "qemu-queue.h"
+#include "qlist.h"
+#include "qstring.h"
+
+#include "config-parser.h"
+
+typedef struct ConfigParser
+{
+} ConfigParser;
+
+static void parser_error(ConfigParser *parser, const char *ptr, const char *fmt, ...)
+{
+    va_list ap;
+
+    fprintf(stderr, "parse error: ");
+    va_start(ap, fmt);
+    vfprintf(stderr, fmt, ap);
+    va_end(ap);
+    fprintf(stderr, "\n");
+    exit(1);
+}
+
+static int parse_is_whitespace(char ch)
+{
+    if (ch == ' ' || ch == '\t' || ch == '\r') {
+        return 1;
+    }
+
+    return 0;
+}
+
+static int parse_is_alpha(char ch)
+{
+    if ((ch >= 'A' && ch <= 'Z') ||
+        (ch >= 'a' && ch <= 'z') ||
+        (ch == '-') ||
+        (ch == '_')) {
+        return 1;
+    }
+
+    return 0;
+}
+
+static int parse_is_digit(char ch)
+{
+    if (ch >= '0' && ch <= '9') {
+        return 1;
+    }
+
+    return 0;
+}
+
+static int parse_is_hexdigit(char ch)
+{
+    if (parse_is_digit(ch) ||
+        (ch >= 'a' && ch <= 'f') ||
+        (ch >= 'A' && ch <= 'F')) {
+        return 1;
+    }
+
+    return 0;
+}
+
+static size_t parse_whitespace(ConfigParser *parser, const char *input)
+{
+    const char *ptr = input;
+
+    while (parse_is_whitespace(*ptr)) {
+        ptr++;
+    }
+
+    return (ptr - input);
+}
+
+static size_t parse_skip(ConfigParser *parser, const char *input)
+{
+    const char *ptr = input;
+
+    while (*ptr == '#' || parse_is_whitespace(*ptr)) {
+        ptr += parse_whitespace(parser, ptr);
+
+        if (*ptr == '#') {
+            ptr++;
+
+            while (*ptr != '\n' && *ptr) {
+                ptr++;
+            }
+        }
+    }
+
+    return (ptr - input);
+}
+
+static size_t parse_symbol(ConfigParser *parser, QString **str, const char *input)
+{
+    const char *ptr = input;
+
+    if (parse_is_alpha(*ptr)) {
+        ptr++;
+
+        while (parse_is_alpha(*ptr) ||
+               (*ptr >= '0' && *ptr <= '9')) {
+            ptr++;
+        }
+    }
+
+    if (ptr != input) {
+        *str = qstring_from_substr(input, (ptr - input));
+    }
+
+    return (ptr - input);
+}
+
+static size_t parse_escaped_string(ConfigParser *parser, QString **str, const char *input)
+{
+    const char *ptr = input;
+
+    if (*ptr != '\"') {
+        goto err;
+    }
+
+    ptr++;
+
+    while (*ptr && *ptr != '\"' && *ptr != '\n') {
+        if (*ptr == '\\') {
+            ptr++;
+            
+            if (*ptr == 'x') { /* hex */
+                ptr++;
+                
+                if (parse_is_hexdigit(*ptr)) {
+                    ptr++;
+                }
+                if (parse_is_hexdigit(*ptr)) {
+                    ptr++;
+                }
+            } else if (parse_is_digit(*ptr)) { /* decimal/octal */
+                ptr++;
+                
+                if (parse_is_digit(*ptr)) {
+                    ptr++;
+                }
+                if (parse_is_digit(*ptr)) {
+                    ptr++;
+                }
+            } else if (*ptr && *ptr != '\n') {
+                ptr++;
+            }
+        } else {
+            ptr++;
+        }
+    }
+
+    if (*ptr != '\"') {
+        parser_error(parser, ptr, "Unterminated string literal");
+        goto err;
+    }
+    ptr++;
+
+    /* FIXME unquote */
+    *str = qstring_from_substr(input + 1, (ptr - input) - 2);
+    return (ptr - input);
+
+err:
+    return 0;
+}
+
+static size_t parse_string(ConfigParser *parser, QString **str, const char *input)
+{
+    const char *ptr = input;
+
+    while (*ptr && *ptr != '\n' && *ptr != '#') {
+        size_t ret;
+
+        if (parse_is_whitespace(*ptr)) {
+            ret = parse_whitespace(parser, ptr);
+
+            if (ptr[ret] == 0 || ptr[ret] == '\n' || ptr[ret] == '#') {
+                break;
+            }
+
+            ptr += ret;
+        } else {
+            ptr++;
+        }
+    }
+
+    if (ptr != input) {
+        *str = qstring_from_substr(input, (ptr - input));
+    }
+
+    return (ptr - input);
+}
+
+static size_t parse_empty_lines(ConfigParser *parser, const char *input)
+{
+    const char *ptr = input;
+
+    do {
+        if (*ptr == '\n') {
+            ptr++;
+        }
+
+        ptr += parse_skip(parser, ptr);
+    } while (*ptr == '\n');
+
+    return (ptr - input);
+}
+
+static size_t parse_item(ConfigParser *parser, QList **item, const char *input)
+{
+    QString *key = NULL;
+    QString *value = NULL;
+    const char *ptr = input;
+    size_t ret;
+
+    ptr += parse_empty_lines(parser, input);
+
+    ptr += parse_skip(parser, ptr);
+
+    ret = parse_symbol(parser, &key, ptr);
+    if (!ret) {
+        goto err;
+    }
+
+    ptr += ret;
+
+    ptr += parse_skip(parser, ptr);
+    if (*ptr != '=') {
+        parser_error(parser, ptr, "For key '%s', expected '=', got '%c'",
+                     input, *ptr);
+        goto err;
+    }
+    ptr++;
+
+    ptr += parse_skip(parser, ptr);
+
+    ret = parse_escaped_string(parser, &value, ptr);
+    if (ret == 0) {
+        ret = parse_string(parser, &value, ptr);
+    }
+
+    if (!ret) {
+        parser_error(parser, ptr, "No value for key `%s'", qstring_get_str(key));
+        goto err;
+    }
+    ptr += ret;
+
+    ptr += parse_skip(parser, ptr);
+    if (*ptr != '\n' && *ptr != 0) {
+        parser_error(parser, ptr, "Unexpected input at end of line");
+        goto err;
+    }
+
+    if (*ptr == '\n') {
+        ptr++;
+    }
+
+    *item = qlist_new();
+
+    qlist_append(*item, key);
+    qlist_append(*item, value);
+
+    return (ptr - input);
+
+err:
+    QDECREF(key);
+    QDECREF(value);
+    return 0;
+}
+
+static size_t parse_section_name(ConfigParser *parser, QString **name, const char *input)
+{
+    const char *ptr = input;
+    size_t ret = 0;
+
+    ptr += parse_empty_lines(parser, input);
+
+    ptr += parse_skip(parser, ptr);
+    if (*ptr != '[') {
+        goto err;
+    }
+    ptr++;
+
+    ptr += parse_skip(parser, ptr);
+
+    ret = parse_symbol(parser, name, ptr);
+    if (!ret) {
+        goto err;
+    }
+    ptr += ret;
+
+    ptr += parse_skip(parser, ptr);
+    if (*ptr != ']') {
+        parser_error(parser, ptr, "Expected ']', got '%c'", *ptr);
+        goto err;
+    }
+    ptr++;
+
+    ptr += parse_skip(parser, ptr);
+    if (*ptr != '\n' && *ptr != 0) {
+        parser_error(parser, ptr, "Unexpected input at end of line");
+        goto err;
+    }
+
+    ptr++;
+
+    return (ptr - input);
+
+err:
+    QDECREF(*name);
+    *name = NULL;
+    return 0;
+}
+
+static size_t parse_items(ConfigParser *parser, QList **section, const char *input)
+{
+    const char *ptr = input;
+    size_t ret;
+
+    do {
+        QList *item = NULL;
+
+        ret = parse_item(parser, &item, ptr);
+        if (ret) {
+            qlist_append(*section, item);
+            ptr += ret;
+        }
+    } while (ret);
+
+    return (ptr - input);
+}
+
+QList *parse_config(const char *input)
+{
+    ConfigParser parser;
+    QList *sections = NULL;
+    QList *section = NULL;
+    const char *ptr = input;
+    size_t ret;
+
+    memset(&parser, 0, sizeof(parser));
+
+    sections = qlist_new();
+
+    section = qlist_new();
+    qlist_append(section, qstring_from_str("DEFAULT"));
+
+    ret = parse_items(&parser, &section, ptr);
+    if (ret) {
+        qlist_append(sections, section);
+        ptr += ret;
+    } else {
+        QDECREF(section);
+    }
+
+    do {
+        QString *name = NULL;
+
+        ret = parse_section_name(&parser, &name, ptr);
+        if (ret) {
+            ptr += ret;
+
+            section = qlist_new();
+            qlist_append(section, name);
+
+            ptr += parse_items(&parser, &section, ptr);
+
+            qlist_append(sections, section);
+        }
+    } while (ret);
+
+    ptr += parse_empty_lines(&parser, ptr);
+
+    ptr += parse_skip(&parser, ptr);
+
+    if (*ptr != 0) {
+        parser_error(&parser, ptr, "Garbage at end of input");
+        return NULL;
+    }
+
+    return sections;
+}
diff --git a/config-parser.h b/config-parser.h
new file mode 100644
index 0000000..cb113d4
--- /dev/null
+++ b/config-parser.h
@@ -0,0 +1,8 @@ 
+#ifndef CONFIG_PARSER_H
+#define CONFIG_PARSER_H
+
+#include "qlist.h"
+
+QList *parse_config(const char *input);
+
+#endif
diff --git a/qemu-config.c b/qemu-config.c
index bafaea2..6960874 100644
--- a/qemu-config.c
+++ b/qemu-config.c
@@ -2,6 +2,8 @@ 
 #include "qemu-option.h"
 #include "qemu-config.h"
 #include "sysemu.h"
+#include "qlist.h"
+#include "qstring.h"
 
 QemuOptsList qemu_drive_opts = {
     .name = "drive",
@@ -193,6 +195,51 @@  static QemuOptsList *lists[] = {
     NULL,
 };
 
+typedef struct QOptionSectionState
+{
+    int index;
+} QOptionSectionState;
+
+static void qemu_set_qoption_items(QObject *obj, void *opaque)
+{
+    QOptionSectionState *s = opaque;
+
+    if (s->index == 0) {
+        QString *name;
+        int i;
+
+        name = qobject_to_qstring(obj);
+
+        for (i = 0; lists[i] != NULL; i++) {
+            if (strcmp(lists[i]->name, qstring_get_str(name)) == 0) {
+                break;
+            }
+        }
+
+        if (lists[i] == NULL) {
+            qemu_error("there is no option group '%s'\n",
+                       qstring_get_str(name));
+            return;
+        }
+
+        opts = qemu_opts_find(lists[i], id);
+    }
+}
+
+static void qemu_set_qoption_section(QObject *obj, void *opaque)
+{
+    QOptionSectionState s = {};
+
+    qlist_iter(qobject_to_qlist(obj), qemu_set_qoption_items, &s);
+}
+
+int qemu_set_qoption(QList *options)
+{
+    qlist_iter(options, qemu_set_qoption_section, NULL);
+
+    return 0;
+}
+
 int qemu_set_option(const char *str)
 {
     char group[64], id[64], arg[64];
diff --git a/qemu-config.h b/qemu-config.h
index cdad5ac..bb3693a 100644
--- a/qemu-config.h
+++ b/qemu-config.h
@@ -1,6 +1,8 @@ 
 #ifndef QEMU_CONFIG_H
 #define QEMU_CONFIG_H
 
+#include "qlist.h"
+
 extern QemuOptsList qemu_drive_opts;
 extern QemuOptsList qemu_chardev_opts;
 extern QemuOptsList qemu_device_opts;
@@ -8,5 +10,6 @@  extern QemuOptsList qemu_net_opts;
 extern QemuOptsList qemu_rtc_opts;
 
 int qemu_set_option(const char *str);
+int qemu_set_qoption(QList *options);
 
 #endif /* QEMU_CONFIG_H */
diff --git a/qemu-options.hx b/qemu-options.hx
index 3dd76b3..074956b 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -1665,6 +1665,13 @@  Immediately before starting guest execution, drop root privileges, switching
 to the specified user.
 ETEXI
 
+DEF("config", HAS_ARG, QEMU_OPTION_config, \
+    "-config file    Load configuratino file.")
+STEXI
+@item -config file
+Load configuration file.
+ETEXI
+
 STEXI
 @end table
 ETEXI
diff --git a/qobject.h b/qobject.h
index 4cc9287..76f669f 100644
--- a/qobject.h
+++ b/qobject.h
@@ -60,7 +60,7 @@  typedef struct QObject {
     QObject base
 
 /* Get the 'base' part of an object */
-#define QOBJECT(obj) (&obj->base)
+#define QOBJECT(obj) (&(obj)->base)
 
 /* High-level interface for qobject_incref() */
 #define QINCREF(obj)      \
diff --git a/qstring.c b/qstring.c
index 6d411da..8b6e9ba 100644
--- a/qstring.c
+++ b/qstring.c
@@ -37,6 +37,23 @@  QString *qstring_from_str(const char *str)
 }
 
 /**
+ * qstring_from_str(): Create a new QString from a C string up to a certain length
+ *
+ * Return strong reference.
+ */
+QString *qstring_from_substr(const char *str, size_t len)
+{
+    QString *qstring;
+
+    qstring = qemu_malloc(sizeof(*qstring));
+    qstring->string = qemu_mallocz(len + 1);
+    strncpy(qstring->string, str, len);
+    QOBJECT_INIT(qstring, &qstring_type);
+
+    return qstring;
+}
+
+/**
  * qobject_to_qstring(): Convert a QObject to a QString
  */
 QString *qobject_to_qstring(const QObject *obj)
diff --git a/qstring.h b/qstring.h
index e012cb7..ca7d87d 100644
--- a/qstring.h
+++ b/qstring.h
@@ -9,6 +9,7 @@  typedef struct QString {
 } QString;
 
 QString *qstring_from_str(const char *str);
+QString *qstring_from_substr(const char *str, size_t len);
 const char *qstring_get_str(const QString *qstring);
 QString *qobject_to_qstring(const QObject *obj);
 
diff --git a/vl.c b/vl.c
index 374f85b..87afd75 100644
--- a/vl.c
+++ b/vl.c
@@ -1,7 +1,7 @@ 
 /*
  * QEMU System Emulator
  *
- * Copyright (c) 2003-2008 Fabrice Bellard
+ * Copyright (c) 2008 Fabrice Bellard
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -171,6 +171,8 @@  int main(int argc, char **argv)
 #include "slirp/libslirp.h"
 
 #include "qemu-queue.h"
+#include "config-parser.h"
+#include "qlist.h"
 
 //#define DEBUG_NET
 //#define DEBUG_SLIRP
@@ -4770,6 +4772,7 @@  int main(int argc, char **argv, char **envp)
 #endif
     CPUState *env;
     int show_vnc_port = 0;
+    const char *config_file = NULL;
 
     init_clocks();
 
@@ -5513,10 +5516,51 @@  int main(int argc, char **argv, char **envp)
                 xen_mode = XEN_ATTACH;
                 break;
 #endif
+            case QEMU_OPTION_config:
+                config_file = optarg;
+                break;
             }
         }
     }
 
+    if (config_file) {
+        struct stat stbuf;
+        int fd;
+        char *buffer;
+        size_t offset = 0;
+        QList *config;
+
+        fd = open(config_file, O_RDONLY);
+        if (fd == -1)
+            abort();
+
+        if (fstat(fd, &stbuf) == -1)
+            abort();
+
+        buffer = qemu_malloc(stbuf.st_size + 1);
+        while (offset < stbuf.st_size) {
+            ssize_t len;
+
+            len = read(fd, buffer + offset, stbuf.st_size - offset);
+            if (len < 1)
+                abort();
+
+            offset += len;
+        }
+
+        close(fd);
+
+        buffer[offset] = 0;
+
+        config = parse_config(buffer);
+        if (config == NULL)
+            abort();
+
+        printf("successfully parsed config\n");
+
+        QDECREF(config);
+    }
+
     /* If no data_dir is specified then try to find it relative to the
        executable path.  */
     if (!data_dir) {