From patchwork Wed Oct 14 18:55:41 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anthony Liguori X-Patchwork-Id: 36019 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 473A1B7B7B for ; Thu, 15 Oct 2009 05:56:32 +1100 (EST) Received: from localhost ([127.0.0.1]:49353 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1My91R-0000k3-Dk for incoming@patchwork.ozlabs.org; Wed, 14 Oct 2009 14:56:29 -0400 Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1My90r-0000jc-Vb for qemu-devel@nongnu.org; Wed, 14 Oct 2009 14:55:54 -0400 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1My90n-0000is-DD for qemu-devel@nongnu.org; Wed, 14 Oct 2009 14:55:53 -0400 Received: from [199.232.76.173] (port=46854 helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1My90n-0000ik-6P for qemu-devel@nongnu.org; Wed, 14 Oct 2009 14:55:49 -0400 Received: from fg-out-1718.google.com ([72.14.220.152]:14867) by monty-python.gnu.org with esmtp (Exim 4.60) (envelope-from ) id 1My90m-0006er-Dj for qemu-devel@nongnu.org; Wed, 14 Oct 2009 14:55:48 -0400 Received: by fg-out-1718.google.com with SMTP id d23so71280fga.10 for ; Wed, 14 Oct 2009 11:55:47 -0700 (PDT) Received: by 10.86.169.3 with SMTP id r3mr7983869fge.15.1255546547501; Wed, 14 Oct 2009 11:55:47 -0700 (PDT) Received: from squirrel.codemonkey.ws ([24.174.33.212]) by mx.google.com with ESMTPS id 4sm443505fgg.8.2009.10.14.11.55.43 (version=TLSv1/SSLv3 cipher=RC4-MD5); Wed, 14 Oct 2009 11:55:45 -0700 (PDT) Message-ID: <4AD61EAD.4040402@codemonkey.ws> Date: Wed, 14 Oct 2009 13:55:41 -0500 From: Anthony Liguori User-Agent: Thunderbird 2.0.0.23 (X11/20090825) MIME-Version: 1.0 To: Gerd Hoffmann Subject: Re: [Qemu-devel] [PATCH 3/4] QemuOpts: parse config from file. References: <1255509568-10635-1-git-send-email-kraxel@redhat.com> <1255509568-10635-4-git-send-email-kraxel@redhat.com> In-Reply-To: <1255509568-10635-4-git-send-email-kraxel@redhat.com> X-detected-operating-system: by monty-python.gnu.org: GNU/Linux 2.6 (newer, 2) Cc: qemu-devel@nongnu.org X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Gerd Hoffmann wrote: > Add functions to parse QemuOpts from a git-style config file. > > Signed-off-by: Gerd Hoffmann > FWIW, here's my parser. Regards, Anthony Liguori commit 8b65f2a706e15efd40a308fd66bcf613f6569962 Author: Anthony Liguori 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, §ion, 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, §ion, 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) {