qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [RFC] Configuration file format


From: Anthony Liguori
Subject: [Qemu-devel] [RFC] Configuration file format
Date: Sun, 18 Feb 2007 17:04:51 -0600
User-agent: Thunderbird 1.5.0.9 (X11/20070103)

Howdy,

Lately, the lack of a configuration file is making my life a bit painful. I thought it was time to at least take a shot at it and perhaps get the ball rolling. Here's what I came up with.

The configuration file is INI-style. I thought since we have so many devices, each with their own configuration needs, the structure made sense logically. The first section in the file is implicit so you can write a very simple config file without even realizing it's an INI. It also supports python-style interpolation. This allows you to specify an option based on another option. For instance:

root = /home/anthony
image = %(root)s/disk.img

Would expand to:

root = /home/anthony
image = /home/anthony/disk.img

The 's' is a modifier. The only other modifier is currently 'b' which expands to true or false depending on whether the interpolated value has been defined yet.

When QEMU starts up, we read $(PREFIX)/share/qemu-<arch>.conf to get all the defaults. This would include things like memory, devices attached, etc. I'm hoping we can use this as a machine description. It will also check for /etc/qemurc and ~/.qemurc for local defaults. Finally, you can specify an explicit config file with the -config option.

The best way to demonstrate what I'm thinking is with an example. Attached is a proof-of-concept that reworks the IDE and SCSI disks to use configuration options. It's mostly backwards compatible. This is only a proof of concept. Also attached is the global configuration file that I'm using. You'll see that with the exception of backwards compatibility, the code has no concept of things like hda, hdb, cdrom, sda, sdb, etc. Instead, it's all done with interpolation.

If people like this approach, I think the best way we could proceed is to clean up this code and get it working nicely for the disks. Then we could expand to include other devices over time.

Regards,

Anthony Liguori
diff -r 20356d2fe017 Makefile.target
--- a/Makefile.target   Sun Feb 18 10:42:33 2007 -0600
+++ b/Makefile.target   Sun Feb 18 15:37:03 2007 -0600
@@ -321,7 +321,7 @@ endif
 
 # must use static linking to avoid leaving stuff in virtual address space
 VL_OBJS=vl.o osdep.o readline.o monitor.o pci.o console.o loader.o isa_mmio.o
-VL_OBJS+=cutils.o migration.o
+VL_OBJS+=cutils.o migration.o conf.o
 VL_OBJS+=block.o block-raw.o
 VL_OBJS+=block-cow.o block-qcow.o aes.o block-vmdk.o block-cloop.o block-dmg.o 
block-bochs.o block-vpc.o block-vvfat.o block-qcow2.o
 ifdef CONFIG_WIN32
diff -r 20356d2fe017 hw/ide.c
--- a/hw/ide.c  Sun Feb 18 10:42:33 2007 -0600
+++ b/hw/ide.c  Sun Feb 18 16:50:00 2007 -0600
@@ -2460,6 +2460,7 @@ void pci_cmd646_ide_init(PCIBus *bus, Bl
     
     for(i = 0; i < 4; i++)
         d->ide_if[i].pci_dev = (PCIDevice *)d;
+
     ide_init2(&d->ide_if[0], hd_table[0], hd_table[1],
               cmd646_set_irq, d, 0);
     ide_init2(&d->ide_if[2], hd_table[2], hd_table[3],
diff -r 20356d2fe017 hw/pc.c
--- a/hw/pc.c   Sun Feb 18 10:42:33 2007 -0600
+++ b/hw/pc.c   Sun Feb 18 16:32:24 2007 -0600
@@ -898,23 +898,36 @@ static void pc_init1(int ram_size, int v
     if (i440fx_state) {
         i440fx_init_memory_mappings(i440fx_state);
     }
-#if 0
-    /* ??? Need to figure out some way for the user to
-       specify SCSI devices.  */
-    if (pci_enabled) {
+
+    if (pci_enabled && config_get_bool(config_state, "lsi53c895a", "enabled", 
0)) {
         void *scsi;
         BlockDriverState *bdrv;
-
+       char name[256];
+       const char *devices;
+               
         scsi = lsi_scsi_init(pci_bus, -1);
-        bdrv = bdrv_new("scsidisk");
-        bdrv_open(bdrv, "scsi_disk.img", 0);
-        lsi_scsi_attach(scsi, bdrv, -1);
-        bdrv = bdrv_new("scsicd");
-        bdrv_open(bdrv, "scsi_cd.iso", 0);
-        bdrv_set_type_hint(bdrv, BDRV_TYPE_CDROM);
-        lsi_scsi_attach(scsi, bdrv, -1);
-    }
-#endif
+
+       devices = config_get(config_state, "lsi53c895a", "devices");
+       while (devices && *devices) {
+           const char *ptr = strchr(devices, ' ');
+           if (ptr) {
+               memcpy(name, devices, ptr - devices);
+               name[ptr - devices] = 0;
+               devices = ptr + 1;
+           } else {
+               strcpy(name, devices);
+               devices = ptr;
+           }
+
+           if (config_get_bool(config_state, name, "enabled", 0)) {
+               bdrv = bdrv_new(name);
+               bdrv_open(bdrv, config_get(config_state, name, "device"), 0);
+               if (config_get_bool(config_state, name, "cdrom", 0))
+                   bdrv_set_type_hint(bdrv, BDRV_TYPE_CDROM);
+               lsi_scsi_attach(scsi, bdrv, -1);
+           }
+       }
+    }
 }
 
 static void pc_init_pci(int ram_size, int vga_ram_size, int boot_device,
diff -r 20356d2fe017 vl.c
--- a/vl.c      Sun Feb 18 10:42:33 2007 -0600
+++ b/vl.c      Sun Feb 18 16:49:25 2007 -0600
@@ -175,6 +175,8 @@ int autostart = 1;
 int autostart = 1;
 CharDriverState *cga_hd = NULL;
 const char *qemu_name;
+const char *disk_section[] = {"ide0-master", "ide0-slave",
+                             "ide1-master", "ide1-slave"};
 
 /***********************************************************/
 /* x86 ISA bus support */
@@ -6631,6 +6633,7 @@ enum {
     QEMU_OPTION_incoming,
     QEMU_OPTION_name,
     QEMU_OPTION_gui,
+    QEMU_OPTION_config,
 };
 
 typedef struct QEMUOption {
@@ -6722,6 +6725,7 @@ const QEMUOption qemu_options[] = {
 #endif
     { "name", HAS_ARG, QEMU_OPTION_name },
     { "gui", 0, QEMU_OPTION_gui },
+    { "config", HAS_ARG, QEMU_OPTION_config },
     { NULL },
 };
 
@@ -6935,6 +6939,41 @@ void qemu_get_launch_info(int *argc, cha
     *argv = saved_argv;
     *opt_daemonize = daemonize;
     *opt_incoming = incoming;
+}
+
+ConfigState *config_state;
+
+static int load_config(const char *filename)
+{
+    int ret = -1;
+    int fd;
+
+    fd = open(filename, O_RDONLY);
+    if (fd != -1) {
+       if (config_parse(config_state, fd) == -1) {
+           printf("error parsing config\n");
+       } else
+           ret = 0;
+       close(fd);
+    }
+
+    return ret;
+}
+
+static void load_default_config(void)
+{
+    char *home, *path;
+
+    config_state = config_new();
+    load_config("/etc/qemurc");
+
+    home = getenv("HOME");
+    if (!home)
+       return;
+    if (asprintf(&path, "%s/.qemurc", home) == -1)
+       return;
+    load_config(path);
+    free(path);
 }
 
 int main(int argc, char **argv)
@@ -6968,6 +7007,8 @@ int main(int argc, char **argv)
     const char *pid_file = NULL;
     int gui = 0;
 
+    load_default_config();
+
     saved_argc = argc;
     saved_argv = argv;
 
@@ -7050,9 +7091,7 @@ int main(int argc, char **argv)
         if (optind >= argc)
             break;
         r = argv[optind];
-        if (r[0] != '-') {
-            hd_filename[0] = argv[optind++];
-        } else {
+       {
             const QEMUOption *popt;
 
             optind++;
@@ -7099,16 +7138,16 @@ int main(int argc, char **argv)
                 initrd_filename = optarg;
                 break;
             case QEMU_OPTION_hda:
+               config_set(config_state, NULL, "hda", optarg);
+               break;
             case QEMU_OPTION_hdb:
+               config_set(config_state, NULL, "hdb", optarg);
+               break;
             case QEMU_OPTION_hdc:
+               config_set(config_state, NULL, "hdc", optarg);
+               break;
             case QEMU_OPTION_hdd:
-                {
-                    int hd_index;
-                    hd_index = popt->index - QEMU_OPTION_hda;
-                    hd_filename[hd_index] = optarg;
-                    if (hd_index == cdrom_index)
-                        cdrom_index = -1;
-                }
+               config_set(config_state, NULL, "hdd", optarg);
                 break;
             case QEMU_OPTION_snapshot:
                 snapshot = 1;
@@ -7161,9 +7200,7 @@ int main(int argc, char **argv)
                 kernel_cmdline = optarg;
                 break;
             case QEMU_OPTION_cdrom:
-                if (cdrom_index >= 0) {
-                    hd_filename[cdrom_index] = optarg;
-                }
+               config_set(config_state, NULL, "cdrom", optarg);
                 break;
             case QEMU_OPTION_boot:
                 boot_device = optarg[0];
@@ -7419,6 +7456,13 @@ int main(int argc, char **argv)
            case QEMU_OPTION_gui:
                gui = 1;
                break;
+           case QEMU_OPTION_config:
+               if (load_config(optarg) == -1) {
+                   fprintf(stderr,
+                           "Could not parse config file: %s\n", optarg);
+                   exit(1);
+               }
+               break;
             }
         }
     }
@@ -7508,19 +7552,8 @@ int main(int argc, char **argv)
 #endif
     linux_boot = (kernel_filename != NULL);
 
-    if (!linux_boot &&
-        hd_filename[0] == '\0' && 
-        (cdrom_index >= 0 && hd_filename[cdrom_index] == '\0') &&
-        fd_filename[0] == '\0')
-        help();
-
     /* boot to floppy or the default cd if no hard disk defined yet */
-    if (hd_filename[0] == '\0' && boot_device == 'c') {
-        if (fd_filename[0] != '\0')
-            boot_device = 'a';
-        else
-            boot_device = 'd';
-    }
+    boot_device = 'c';
 
     setvbuf(stdout, NULL, _IOLBF, 0);
     
@@ -7580,24 +7613,27 @@ int main(int argc, char **argv)
 
     /* we always create the cdrom drive, even if no disk is there */
     bdrv_init();
-    if (cdrom_index >= 0) {
-        bs_table[cdrom_index] = bdrv_new("cdrom");
-        bdrv_set_type_hint(bs_table[cdrom_index], BDRV_TYPE_CDROM);
-    }
 
     /* open the virtual block devices */
     for(i = 0; i < MAX_DISKS; i++) {
-        if (hd_filename[i]) {
-            if (!bs_table[i]) {
-                char buf[64];
-                snprintf(buf, sizeof(buf), "hd%c", i + 'a');
-                bs_table[i] = bdrv_new(buf);
-            }
-            if (bdrv_open(bs_table[i], hd_filename[i], snapshot ? 
BDRV_O_SNAPSHOT : 0) < 0) {
+       const char *filename;
+
+       filename = config_get(config_state, disk_section[i], "device");
+        if (*filename) {
+           char buf[64];
+
+           snprintf(buf, sizeof(buf), "hd%c", i + 'a');
+           bs_table[i] = bdrv_new(buf);
+
+           if (config_get_bool(config_state, disk_section[i], "cdrom", 0))
+               bdrv_set_type_hint(bs_table[i], BDRV_TYPE_CDROM);
+
+            if (bdrv_open(bs_table[i], filename, snapshot ? BDRV_O_SNAPSHOT : 
0) < 0) {
                 fprintf(stderr, "qemu: could not open hard disk image '%s'\n",
-                        hd_filename[i]);
+                        filename);
                 exit(1);
             }
+
             if (i == 0 && cyls != 0) {
                 bdrv_set_geometry_hint(bs_table[i], cyls, heads, secs);
                 bdrv_set_translation_hint(bs_table[i], translation);
diff -r 20356d2fe017 vl.h
--- a/vl.h      Sun Feb 18 10:42:33 2007 -0600
+++ b/vl.h      Sun Feb 18 15:47:22 2007 -0600
@@ -83,6 +83,9 @@ static inline char *realpath(const char 
 
 #include "audio/audio.h"
 #include "cpu.h"
+#include "conf.h"
+
+extern ConfigState *config_state;
 
 #endif /* !defined(QEMU_TOOL) */
 
diff -r 20356d2fe017 conf.c
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/conf.c    Sun Feb 18 16:31:48 2007 -0600
@@ -0,0 +1,383 @@
+#include <sys/types.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <malloc.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "conf.h"
+
+static char *strndup_and_trim(const char *ptr, size_t length)
+{
+    char *ret;
+
+    while (isspace(*ptr)) {
+       ptr++;
+       length--;
+    }
+
+    while (length && isspace(ptr[length - 1]))
+       length--;
+
+    ret = malloc(length + 1);
+    memcpy(ret, ptr, length);
+    ret[length] = 0;
+
+    return ret;
+}
+
+static char *strappend(char *lhs, const char *rhs, size_t n_rhs)
+{
+    size_t n_lhs = lhs ? strlen(lhs) : 0;
+    char *ret;
+
+    ret = realloc(lhs, n_lhs + n_rhs + 1);
+    if (ret == NULL) {
+       free(lhs);
+       return NULL;
+    }
+
+    memcpy(ret + n_lhs, rhs, n_rhs);
+    ret[n_lhs + n_rhs] = 0;
+
+    return ret;
+}
+
+static const char *config_interpolate(ConfigState *s, ConfigSection *c, 
ConfigEntry *e)
+{
+    const char *ptr = e->value;
+    char *interpolation = NULL;
+
+    if (e->interpolation)
+       return e->interpolation;
+
+    /* actually interpolate */
+    while (*ptr) {
+       if (*ptr != '%') {
+           interpolation = strappend(interpolation, ptr, 1);
+           ptr++;
+           continue;
+       }
+
+       ptr++;
+       if (*ptr == '(') {
+           const char *end = strchr(ptr, ')');
+           char *name;
+           const char *ivalue;
+
+           if (!end) {
+               fprintf(stderr, "Missing interpolation terminator\n");
+               return "";
+           }
+
+           name = strndup_and_trim(ptr + 1, end - ptr - 1);
+
+           ivalue = config_get(s, c->name, name);
+           if (!ivalue || !*ivalue)
+               ivalue = config_get(s, "DEFAULTS", name);
+
+           free(name);
+           ptr = end + 1;
+
+           switch (*ptr) {
+           case 's':
+               interpolation = strappend(interpolation, ivalue, 
strlen(ivalue));
+               ptr++;
+               break;
+           case 'b':
+               if (!*ivalue)
+                   interpolation = strappend(interpolation, "false", 5);
+               else 
+                   interpolation = strappend(interpolation, "true", 4);
+               ptr++;
+               break;
+           default:
+               fprintf(stderr, "Unknown interpolation modifier %c\n", *ptr);
+               break;
+           }
+       }
+    }
+
+    e->interpolation = interpolation;
+
+    return e->interpolation;
+}
+
+ConfigEntry *config_get_entry(ConfigSection *c, const char *key)
+{
+    ConfigEntry *e;
+
+    for (e = c->entries; e; e = e->next) {
+       if (strcmp(e->key, key) == 0)
+           return e;
+    }
+
+    return NULL;
+}
+
+int config_get_bool(ConfigState *s, const char *section, const char *key, int 
def)
+{
+    const char *value;
+
+    value = config_get(s, section, key);
+    if (!*value)
+       return def;
+
+    if (strcasecmp(value, "true") == 0 ||
+       strcasecmp(value, "on") == 0 ||
+       strcasecmp(value, "1") == 0)
+       return 1;
+    else if (strcasecmp(value, "false") == 0 ||
+            strcasecmp(value, "off") == 0 ||
+            strcasecmp(value, "0") == 0)
+       return 0;
+
+    fprintf(stderr,
+           "Unknown boolean value `%s' parsing `%s/%s'\n",
+           value, section, key);
+
+    return def;
+}
+
+const char *config_get(ConfigState *s, const char *section, const char *key)
+{
+    ConfigSection *c;
+    ConfigEntry *e;
+
+    section = section ?: "DEFAULTS";
+
+    c = config_get_section(s, section);
+    if (!c)
+       return "";
+
+    e = config_get_entry(c, key);
+    if (e)
+       return config_interpolate(s, c, e);
+
+    return "";
+}
+
+static ConfigEntry *config_set_entry(ConfigSection *c,
+                                    const char *key, const char *value)
+{
+    ConfigEntry *e;
+
+    e = config_get_entry(c, key);
+    if (!e) {
+       e = malloc(sizeof(ConfigEntry));
+       if (!e)
+           return NULL;
+
+       e->key = strdup(key);
+       if (!e->key) {
+           free(e);
+           return NULL;
+       }
+       e->value = NULL;
+       e->interpolation = NULL;
+
+       e->next = c->entries;
+       c->entries = e;
+    }
+
+    free(e->value);
+    e->value = strdup(value);
+
+    return e;
+}
+
+ConfigSection *config_get_section(ConfigState *s, const char *name)
+{
+    ConfigSection *c;
+
+    for (c = s->sections; c; c = c->next) {
+       if (strcmp(c->name, name) == 0)
+           return c;
+    }
+
+    return NULL;
+}
+
+static ConfigSection *config_add_section(ConfigState *s, const char *name)
+{
+    ConfigSection *c;
+
+    c = config_get_section(s, name);
+    if (c)
+       return c;
+
+    c = malloc(sizeof(ConfigSection));
+    if (!c)
+       return NULL;
+
+    c->name = strdup(name);
+    if (!c->name) {
+       free(c);
+       errno = ENOMEM;
+       return NULL;
+    }
+
+    c->entries = NULL;
+    c->next = s->sections;
+    s->sections = c;
+
+    return c;
+}
+
+int config_set(ConfigState *s, const char *section,
+              const char *key, const char *value)
+{
+    ConfigSection *c;
+
+    section = section ?: "DEFAULTS";
+
+    c = config_add_section(s, section);
+    if (!c)
+       return -1;
+
+    if (!config_set_entry(c, key, value))
+       return -1;
+    
+    return 0;
+}
+
+typedef struct ConfigParserState
+{
+    int fd;
+    ConfigState *s;
+    ConfigSection *section;
+} ConfigParserState;
+
+static char line[4096];
+static FILE *fp;
+
+static char *read_line(int fd)
+{
+    if (!fp)
+       fp = fdopen(fd, "r");
+
+    return fgets(line, sizeof(line), fp);
+}
+
+static int config_parse_line(ConfigParserState *p, const char *line)
+{
+    const char *ptr = line;
+
+    while (isspace(*ptr)) ptr++;
+    if (!*ptr || *ptr == '#')
+       return 0;
+
+    if (*ptr == '[') { /* in a section */
+       const char *end;
+       char *section;
+
+       ptr++;
+       end = strchr(ptr, ']');
+       if (!end)
+           return -1;
+
+       section = strndup_and_trim(ptr, end - ptr);
+       p->section = config_add_section(p->s, section);
+       free(section);
+    } else { /* in a key/value pair */
+       const char *equals = strchr(ptr, '=');
+       const char *end = ptr + strlen(ptr);
+       char *key, *value;
+
+       if (!equals)
+           return -1;
+
+       key = strndup_and_trim(ptr, equals - ptr);
+       value = strndup_and_trim(equals + 1, end - equals - 1);
+
+       if (!key || !value)
+           return -1;
+
+       config_set_entry(p->section, key, value);
+
+       free(key);
+       free(value);
+    }
+
+    return 0;
+}
+
+ConfigState *config_new(void)
+{
+    ConfigState *s;
+    ConfigSection *defaults;
+
+    s = malloc(sizeof(ConfigState));
+    if (!s) {
+       return NULL;
+    }
+
+    s->sections = NULL;
+    
+    defaults = config_add_section(s, "DEFAULTS");
+    if (!defaults) {
+       free(s);
+       errno = ENOMEM;
+       return NULL;
+    }
+
+    return s;
+}
+
+int config_parse(ConfigState *s, int fd)
+{
+    ConfigParserState *p;
+    int ret = 0;
+
+    p = malloc(sizeof(ConfigParserState));
+    if (!p)
+       return -1;
+
+    p->fd = fd;
+    p->s = s;
+    p->section = config_get_section(s, "DEFAULTS");
+    fp = NULL;
+
+    do {
+       char *line = read_line(fd);
+       if (line == NULL)
+           break;
+
+       ret = config_parse_line(p, line);
+       if (ret == -1)
+           break;
+    } while (1);
+
+    free(p);
+
+    return ret;
+}
+
+static void config_section_free(ConfigSection *c)
+{
+    while (c->entries) {
+       ConfigEntry *e = c->entries;
+       c->entries = e->next;
+
+       free(e->key);
+       free(e->value);
+       free(e->interpolation);
+       free(e);
+    }
+}
+
+void config_free(ConfigState *s)
+{
+    while (s->sections) {
+       ConfigSection *c = s->sections;
+       s->sections = c->next;
+
+       config_section_free(c);
+
+       free(c->name);
+       free(c);
+    }
+
+    free(s);
+}
diff -r 20356d2fe017 conf.h
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/conf.h    Sun Feb 18 15:36:47 2007 -0600
@@ -0,0 +1,47 @@
+#ifndef _CONF_H_
+#define _CONF_H_
+
+typedef struct ConfigEntry ConfigEntry;
+typedef struct ConfigSection ConfigSection;
+typedef struct ConfigState ConfigState;
+
+struct ConfigEntry
+{
+    char *key;
+    char *value;
+    char *interpolation;
+
+    ConfigEntry *next;
+};
+
+struct ConfigSection
+{
+    char *name;
+    ConfigEntry *entries;
+
+    ConfigSection *next;
+};
+
+struct ConfigState
+{
+    ConfigSection *sections;
+};
+
+ConfigState *config_new(void);
+
+int config_parse(ConfigState *s, int fd);
+
+void config_free(ConfigState *s);
+
+ConfigEntry *config_get_entry(ConfigSection *c, const char *key);
+
+const char *config_get(ConfigState *s, const char *section, const char *key);
+
+int config_get_bool(ConfigState *s, const char *section, const char *key, int 
def);
+
+ConfigSection *config_get_section(ConfigState *s, const char *name);
+
+int config_set(ConfigState *s, const char *section,
+              const char *key, const char *value);
+
+#endif
hdc = %(cdrom)s

[ide0-master]
device = %(hda)s
cdrom = false

[ide0-slave]
device = %(hdb)s
cdrom = false

[ide1-master]
device = %(hdc)s
cdrom = true

[ide1-slave]
device = %(hdd)s
cdrom = false

[lsi53c895a]
enabled = true
devices = scsi-disk0 scsi-disk1 scsi-disk2 scsi-disk3

[scsi-disk0]
enabled = %(sda)b
device = %(sda)s
cdrom = false

[scsi-disk1]
enabled = %(sdb)b
device = %(sdb)s
cdrom = false

[scsi-disk2]
enabled = %(sdc)b
device = %(sdc)s
cdrom = false

[scsi-disk3]
enabled = %(sdd)b
device = %(sdd)s
cdrom = false

reply via email to

[Prev in Thread] Current Thread [Next in Thread]