]> git.ipfire.org Git - thirdparty/libvirt.git/commitdiff
Sound support for QEmu and Xen
authorDaniel Veillard <veillard@redhat.com>
Wed, 7 May 2008 14:04:40 +0000 (14:04 +0000)
committerDaniel Veillard <veillard@redhat.com>
Wed, 7 May 2008 14:04:40 +0000 (14:04 +0000)
* src/qemu_conf.c src/qemu_conf.h src/xend_internal.c
  src/xend_internal.h src/xm_internal.c src/xml.c src/xml.h:
  Patch from Cole Robinson adding sound support for QEmu and Xen
* tests/qemuxml2argvtest.c tests/sexpr2xmltest.c
  tests/xmconfigtest.c tests/xml2sexprtest.c:
  Associated regression tests
Daniel

12 files changed:
ChangeLog
src/qemu_conf.c
src/qemu_conf.h
src/xend_internal.c
src/xend_internal.h
src/xm_internal.c
src/xml.c
src/xml.h
tests/qemuxml2argvtest.c
tests/sexpr2xmltest.c
tests/xmconfigtest.c
tests/xml2sexprtest.c

index cc38ba1959ad0bb596011d0c5ad306cdea97d6e4..e3ca50df287667b05fdb3c4efe843acd87939bab 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+Wed May  7 16:02:07 CEST 2008 Daniel Veillard <veillard@redhat.com>
+
+       * src/qemu_conf.c src/qemu_conf.h src/xend_internal.c
+         src/xend_internal.h src/xm_internal.c src/xml.c src/xml.h:
+         Patch from Cole Robinson adding sound support for QEmu and Xen
+       * tests/qemuxml2argvtest.c tests/sexpr2xmltest.c
+         tests/xmconfigtest.c tests/xml2sexprtest.c:
+         Associated regression tests
+
 Mon May  6 17:43:28 EST 2008 Daniel P. Berrange <berrange@redhat.com>
 
        * src/memory.c: Add missing stddefs.h for ptrdiff_t type
index 1efd9e956d6d309170e6628c420ee302f5b2794e..4f5e96ec92294e9836d425ea5b90cf7dd2064c63 100644 (file)
@@ -1373,6 +1373,58 @@ static int qemudParseInputXML(virConnectPtr conn,
     return -1;
 }
 
+/* Sound device helper functions */
+static int qemudSoundModelFromString(const char *model) {
+    if (STREQ(model, "sb16")) {
+        return QEMU_SOUND_SB16;
+    } else if (STREQ(model, "es1370")) {
+        return QEMU_SOUND_ES1370;
+    } else if (STREQ(model, "pcspk")) {
+        return QEMU_SOUND_PCSPK;
+    }
+
+    return -1;
+}
+
+static const char *qemudSoundModelToString(const int model) {
+
+    if (model == QEMU_SOUND_SB16) {
+        return "sb16";
+    } else if (model == QEMU_SOUND_ES1370) {
+        return "es1370";
+    } else if (model == QEMU_SOUND_PCSPK) {
+        return "pcspk";
+    }
+
+    return NULL;
+}
+
+
+static int qemudParseSoundXML(virConnectPtr conn,
+                              struct qemud_vm_sound_def *sound,
+                              const xmlNodePtr node) {
+
+    int err = -1;
+    xmlChar *model = NULL;
+    model = xmlGetProp(node, BAD_CAST "model");
+
+    if (!model) {
+        qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+                         "%s", _("missing sound model"));
+        goto error;
+    }
+    if ((sound->model = qemudSoundModelFromString((char *) model)) < 0) {
+        qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+                         _("invalid sound model '%s'"), (char *) model);
+        goto error;
+    }
+
+    err = 0;
+ error:
+    xmlFree(model);
+    return err;
+}
+
 
 /*
  * Parses a libvirt XML definition of a guest, and populates the
@@ -1887,6 +1939,50 @@ static struct qemud_vm_def *qemudParseXML(virConnectPtr conn,
         }
     }
     xmlXPathFreeObject(obj);
+
+    /* Parse sound driver xml */
+    obj = xmlXPathEval(BAD_CAST "/domain/devices/sound", ctxt);
+    if ((obj != NULL) && (obj->type == XPATH_NODESET) &&
+        (obj->nodesetval != NULL) && (obj->nodesetval->nodeNr >= 0)) {
+        struct qemud_vm_sound_def *prev = NULL;
+        for (i = 0; i < obj->nodesetval->nodeNr; i++) {
+
+            struct qemud_vm_sound_def *sound = calloc(1, sizeof(*sound));
+            struct qemud_vm_sound_def *check = def->sounds;
+            int collision = 0;
+            if (!sound) {
+                qemudReportError(conn, NULL, NULL, VIR_ERR_NO_MEMORY,
+                         "%s", _("failed to allocate space for sound dev"));
+                goto error;
+            }
+            if (qemudParseSoundXML(conn, sound,
+                                   obj->nodesetval->nodeTab[i]) < 0) {
+                free(sound);
+                goto error;
+            }
+
+            // Check that model type isn't already present in sound dev list
+            while(check) {
+                if (check->model == sound->model) {
+                    collision = 1;
+                    break;
+                }
+                check = check->next;
+            }
+            if (collision)
+                continue;
+
+            def->nsounds++;
+            sound->next = NULL;
+            if (def->sounds == NULL) {
+                def->sounds = sound;
+            } else {
+                prev->next = sound;
+            }
+            prev = sound;
+        }
+    }
+    xmlXPathFreeObject(obj);
     obj = NULL;
 
     /* If graphics are enabled, there's an implicit PS2 mouse */
@@ -2106,6 +2202,7 @@ int qemudBuildCommandLine(virConnectPtr conn,
     struct qemud_vm_disk_def *disk = vm->def->disks;
     struct qemud_vm_net_def *net = vm->def->nets;
     struct qemud_vm_input_def *input = vm->def->inputs;
+    struct qemud_vm_sound_def *sound = vm->def->sounds;
     struct qemud_vm_chr_def *serial = vm->def->serials;
     struct qemud_vm_chr_def *parallel = vm->def->parallels;
     struct utsname ut;
@@ -2156,6 +2253,7 @@ int qemudBuildCommandLine(virConnectPtr conn,
         (vm->def->nnets > 0 ? (4 * vm->def->nnets) : 2) + /* networks */
         1 + /* usb */
         2 * vm->def->ninputs + /* input devices */
+        ((vm->def->nsounds > 0) ? 2 : 0) + /* sound */
         (vm->def->nserials > 0 ? (2 * vm->def->nserials) : 2) + /* character devices */
         (vm->def->nparallels > 0 ? (2 * vm->def->nparallels) : 2) + /* character devices */
         2 + /* memory*/
@@ -2491,6 +2589,32 @@ int qemudBuildCommandLine(virConnectPtr conn,
         /* SDL is the default. no args needed */
     }
 
+    /* Add sound hardware */
+    if (sound) {
+        int size = 100;
+        char *modstr = calloc(1, size+1);
+        if (!modstr)
+            goto no_memory;
+        if (!((*argv)[++n] = strdup("-soundhw")))
+            goto no_memory;
+
+        while(sound && size > 0) {
+            const char *model = qemudSoundModelToString(sound->model);
+            if (!model) {
+               qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+                                _("invalid sound model"));
+               goto error;
+            }
+            strncat(modstr, model, size);
+            size -= strlen(model);
+            sound = sound->next;
+            if (sound)
+               strncat(modstr, ",", size--);
+        }
+        if (!((*argv)[++n] = modstr))
+            goto no_memory;
+    }
+
     if (vm->migrateFrom[0]) {
         if (!((*argv)[++n] = strdup("-S")))
             goto no_memory;
@@ -2602,6 +2726,9 @@ qemudParseVMDeviceDef(virConnectPtr conn,
     } else if (xmlStrEqual(node->name, BAD_CAST "input")) {
         dev->type = QEMUD_DEVICE_DISK;
         qemudParseInputXML(conn, &(dev->data.input), node);
+    } else if (xmlStrEqual(node->name, BAD_CAST "sound")) {
+        dev->type = QEMUD_DEVICE_SOUND;
+        qemudParseSoundXML(conn, &(dev->data.sound), node);
     } else {
         qemudReportError(conn, NULL, NULL, VIR_ERR_XML_ERROR,
                          "%s", _("unknown device type"));
@@ -3475,6 +3602,7 @@ char *qemudGenerateXML(virConnectPtr conn,
     const struct qemud_vm_disk_def *disk;
     const struct qemud_vm_net_def *net;
     const struct qemud_vm_input_def *input;
+    const struct qemud_vm_sound_def *sound;
     const struct qemud_vm_chr_def *chr;
     const char *type = NULL;
     int n;
@@ -3717,6 +3845,18 @@ char *qemudGenerateXML(virConnectPtr conn,
         break;
     }
 
+    sound = def->sounds;
+    while(sound) {
+        const char *model = qemudSoundModelToString(sound->model);
+        if (!model) {
+            qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+                             _("invalid sound model"));
+            goto cleanup;
+        }
+        virBufferVSprintf(&buf, "    <sound model='%s'/>\n", model);
+        sound = sound->next;
+    }
+
     virBufferAddLit(&buf, "  </devices>\n");
     virBufferAddLit(&buf, "</domain>\n");
 
index bd8d8b28af75c88248ca95f20f50c8ff1986db55..46c6fb7d5b6d98c5921770eb1eb3ed1a3396e5da 100644 (file)
@@ -186,11 +186,24 @@ struct qemud_vm_input_def {
     struct qemud_vm_input_def *next;
 };
 
+enum qemu_vm_sound_model {
+    QEMU_SOUND_NONE   = 0,
+    QEMU_SOUND_SB16,
+    QEMU_SOUND_ES1370,
+    QEMU_SOUND_PCSPK,
+};
+
+struct qemud_vm_sound_def {
+    int model;
+    struct qemud_vm_sound_def *next;
+};
+
 /* Flags for the 'type' field in next struct */
 enum qemud_vm_device_type {
     QEMUD_DEVICE_DISK,
     QEMUD_DEVICE_NET,
     QEMUD_DEVICE_INPUT,
+    QEMUD_DEVICE_SOUND,
 };
 
 struct qemud_vm_device_def {
@@ -199,6 +212,7 @@ struct qemud_vm_device_def {
         struct qemud_vm_disk_def disk;
         struct qemud_vm_net_def net;
         struct qemud_vm_input_def input;
+        struct qemud_vm_sound_def sound;
     } data;
 };
 
@@ -274,6 +288,9 @@ struct qemud_vm_def {
     unsigned int ninputs;
     struct qemud_vm_input_def *inputs;
 
+    unsigned int nsounds;
+    struct qemud_vm_sound_def *sounds;
+
     unsigned int nserials;
     struct qemud_vm_chr_def *serials;
 
index 29a16dd55acb99e9d9b0e93c4ae095eff138b4c1..0817b7dee9a6a2fb7d7e335b221c5ea69cfb5e51 100644 (file)
@@ -851,6 +851,107 @@ urlencode(const char *string)
 
     return buffer;
 }
+
+/* Applicable sound models */
+const char *sound_models[] = { "sb16", "es1370" };
+
+/**
+ * is_sound_model_valid:
+ * @model : model string to check against whitelist
+ *
+ * checks passed model string against whitelist of acceptable models
+ *
+ * Returns 0 if invalid, 1 otherwise
+ */
+int is_sound_model_valid(const char *model) {
+    int i;
+
+    for (i = 0; i < sizeof(sound_models)/sizeof(*sound_models); ++i) {
+        if (STREQ(model, sound_models[i])) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+/**
+ * is_sound_model_conflict:
+ * @model : model string to look for duplicates of
+ * @soundstr : soundhw string for the form m1,m2,m3 ...
+ *
+ * Returns 0 if no conflict, 1 otherwise
+ */
+int is_sound_model_conflict(const char *model, const char *soundstr) {
+
+    char *dupe;
+    char *cur = (char *) soundstr;
+    while ((dupe = strstr(cur, model))) {
+        if (( (dupe == cur) ||                     // (Start of line |
+              (*(dupe - 1) == ',') ) &&            //  Preceded by comma) &
+            ( (dupe[strlen(model)] == ',') ||      // (Ends with comma |
+               (dupe[strlen(model)] == '\0') ))    //  Ends whole string)
+            return 1;
+        else
+            cur = dupe + strlen(model);
+    }
+    return 0;
+}
+
+/**
+ * sound_string_to_xml:
+ * @soundstr : soundhw string for the form m1,m2,m3 ...
+ *
+ * Parses the passed string and returns a heap allocated string containing
+ * the valid libvirt soundxml. Must be free'd by caller.
+ *
+ * Returns NULL on fail, xml string on success (can be the empty string).
+ */
+char *sound_string_to_xml(const char *sound) {
+
+    virBuffer buf = VIR_BUFFER_INITIALIZER;
+
+    while (sound) {
+        int modelsize, valid, collision = 0;
+        char *model = NULL;
+        char *model_end = strchr(sound, ',');
+        modelsize = (model_end ? (model_end - sound) : strlen(sound));
+
+        if(!(model = strndup(sound, modelsize)))
+            goto error;
+
+        if (!(valid = is_sound_model_valid(model))) {
+            // Check for magic 'all' model. If found, throw out current xml
+            // and build with all available models
+            if (STREQ(model, "all")) {
+                int i;
+                free(virBufferContentAndReset(&buf));
+
+                for (i=0; i < sizeof(sound_models)/sizeof(*sound_models); ++i)
+                    virBufferVSprintf(&buf, "    <sound model='%s'/>\n",
+                                      sound_models[i]);
+                free(model);
+                break;
+            }
+        }
+
+        if (valid && model_end)
+            collision = is_sound_model_conflict(model, model_end);
+        if (valid && !collision)
+            virBufferVSprintf(&buf, "    <sound model='%s'/>\n", model);
+
+        sound = (model_end ? ++model_end : NULL);
+        free(model);
+    }
+
+    if (virBufferError(&buf))
+        goto error;
+    return virBufferContentAndReset(&buf);
+
+  error:
+    free(virBufferContentAndReset(&buf));
+    return NULL;
+}
+
 #endif /* ! PROXY */
 
 /* PUBLIC FUNCTIONS */
@@ -2079,6 +2180,23 @@ xend_parse_sexp_desc(virConnectPtr conn, struct sexpr *root,
     }
     free(tty);
 
+    if (hvm) {
+        if (sexpr_node(root, "domain/image/hvm/soundhw")) {
+            char *soundxml;
+            tmp = sexpr_node(root, "domain/image/hvm/soundhw");
+            if (tmp && *tmp) {
+                if ((soundxml = sound_string_to_xml(tmp))) {
+                    virBufferVSprintf(&buf, "%s", soundxml);
+                    free(soundxml);
+                } else {
+                    virXendError(conn, VIR_ERR_INTERNAL_ERROR,
+                                 _("parsing soundhw string failed."));
+                    goto error;
+                }
+            }
+        }
+    }
+
     virBufferAddLit(&buf, "  </devices>\n");
     virBufferAddLit(&buf, "</domain>\n");
 
index d56d73d60dc2e39bcc4f393ae3afff2de38ef31e..80ef4f6ff5241d402ea4881d6f4b3b1b5c20dd64 100644 (file)
@@ -189,6 +189,11 @@ char *xenDaemonDomainDumpXMLByName(virConnectPtr xend,
 
   char *xend_parse_domain_sexp(virConnectPtr conn,  char *root, int xendConfigVersion);
 
+  int is_sound_model_valid(const char *model);
+  int is_sound_model_conflict(const char *model, const char *soundstr);
+  char *sound_string_to_xml(const char *sound);
+
+
 /* refactored ones */
 int xenDaemonOpen(virConnectPtr conn, xmlURIPtr uri, virConnectAuthPtr auth, int flags);
 int xenDaemonClose(virConnectPtr conn);
index 815f08bf208e3391411f5edb39dcc12759dbf75c..c820c5369f61dcbdb0f0cb228b20e5495194859f 100644 (file)
@@ -1050,16 +1050,34 @@ char *xenXMDomainFormatXML(virConnectPtr conn, virConfPtr conf) {
         virBufferAddLit(&buf, "    </console>\n");
     }
 
+    if (hvm) {
+        if ((xenXMConfigGetString(conf, "soundhw", &str) == 0) && str) {
+            char *soundxml;
+            if ((soundxml = sound_string_to_xml(str))) {
+                virBufferVSprintf(&buf, "%s", soundxml);
+                free(soundxml);
+            } else {
+                xenXMError(conn, VIR_ERR_INTERNAL_ERROR,
+                           _("parsing soundhw string failed."));
+                goto error;
+            }
+        }
+    }
+
     virBufferAddLit(&buf, "  </devices>\n");
 
     virBufferAddLit(&buf, "</domain>\n");
 
     if (virBufferError(&buf)) {
         xenXMError(conn, VIR_ERR_NO_MEMORY, _("allocate buffer"));
-        return NULL;
+        goto error;
     }
 
     return virBufferContentAndReset(&buf);
+
+  error:
+    free(virBufferContentAndReset(&buf));
+    return NULL;
 }
 
 
@@ -2311,6 +2329,17 @@ virConfPtr xenXMParseXMLToConfig(virConnectPtr conn, const char *xml) {
                     goto error;
             }
         }
+
+        if (virXPathNode("/domain/devices/sound", ctxt)) {
+            char *soundstr;
+            if (!(soundstr = virBuildSoundStringFromXML(conn, ctxt)))
+                goto error;
+            if (xenXMConfigSetString(conf, "soundhw", soundstr) < 0) {
+                free(soundstr);
+                goto error;
+            }
+            free(soundstr);
+        }
     }
 
     xmlFreeDoc(doc);
index 5381cb1e6084dfb85def674bfc5138ce3df82b4e..e9e2e64820c9255b0da4ff3c70c6efd5a1ea55d7 100644 (file)
--- a/src/xml.c
+++ b/src/xml.c
@@ -29,6 +29,7 @@
 #include "util.h"
 #include "xs_internal.h"        /* for xenStoreDomainGetNetworkID */
 #include "xen_unified.h"
+#include "xend_internal.h"      /* for is_sound_* functions */
 
 /**
  * virXMLError:
@@ -288,6 +289,78 @@ virConvertCpuSet(virConnectPtr conn, const char *str, int maxcpu) {
     free(cpuset);
     return (res);
 }
+
+/**
+ * virBuildSoundStringFromXML
+ * @sound buffer to populate
+ * @len size of preallocated buffer 'sound'
+ * @ctxt xml context to pull sound info from
+ *
+ * Builds a string of the form m1,m2,m3 from the different sound models
+ * in the xml. String must be free'd by caller.
+ *
+ * Returns string on success, NULL on error
+ */
+char * virBuildSoundStringFromXML(virConnectPtr conn,
+                                  xmlXPathContextPtr ctxt) {
+
+    int nb_nodes, size = 256;
+    char *sound;
+    xmlNodePtr *nodes = NULL;
+
+    if (!(sound = calloc(1, size+1))) {
+        virXMLError(conn, VIR_ERR_NO_MEMORY,
+                    _("failed to allocate sound string"), 0);
+        return NULL;
+    }
+
+    nb_nodes = virXPathNodeSet("/domain/devices/sound", ctxt, &nodes);
+    if (nb_nodes > 0) {
+        int i;
+        for (i = 0; i < nb_nodes && size > 0; i++) {
+            char *model = NULL;
+            int collision = 0;
+
+            model = (char *) xmlGetProp(nodes[i], (xmlChar *) "model");
+            if (!model) {
+                virXMLError(conn, VIR_ERR_XML_ERROR,
+                            _("no model for sound device"), 0);
+                goto error;
+            }
+
+            if (!is_sound_model_valid(model)) {
+                virXMLError(conn, VIR_ERR_XML_ERROR,
+                            _("unknown sound model type"), 0);
+                free(model);
+                goto error;
+            }
+
+            // Check for duplicates in currently built string
+            if (*sound)
+                collision = is_sound_model_conflict(model, sound);
+
+            // If no collision, add to string
+            if (!collision) {
+                if (*sound && (size >= (strlen(model) + 1))) {
+                    strncat(sound, ",", size--);
+                } else if (*sound || size < strlen(model)) {
+                    free(model);
+                    continue;
+                }
+                strncat(sound, model, size);
+                size -= strlen(model);
+            }
+
+            free(model);
+        }
+    }
+    free(nodes);
+    return sound;
+
+  error:
+    free(nodes);
+    return NULL;
+}
 #endif /* WITH_XEN */
 #ifndef PROXY
 
@@ -969,7 +1042,6 @@ virDomainParseXMLOSDescHVM(virConnectPtr conn, xmlNodePtr node,
         }
     }
 
-
     /* get the cdrom device file */
     /* Only XenD <= 3.0.2 wants cdrom config here */
     if (xendConfigVersion == 1) {
@@ -1077,6 +1149,15 @@ virDomainParseXMLOSDescHVM(virConnectPtr conn, xmlNodePtr node,
         }
     }
 
+    cur = virXPathNode("/domain/devices/sound", ctxt);
+    if (cur) {
+        char *soundstr;
+        if (!(soundstr = virBuildSoundStringFromXML(conn, ctxt)))
+            goto error;
+        virBufferVSprintf(buf, "(soundhw '%s')", soundstr);
+        free(soundstr);
+    }
+
     str = virXPathString("string(/domain/clock/@offset)", ctxt);
     if (str != NULL && STREQ(str, "localtime")) {
         virBufferAddLit(buf, "(localtime 1)");
index d91a1b0e7ba4044acb2b3130d5717cf145f7a48a..e5f21038387092c94e251d7d4b1997d14a25fc98 100644 (file)
--- a/src/xml.h
+++ b/src/xml.h
@@ -61,6 +61,8 @@ int           virDomainXMLDevID(virDomainPtr domain,
                                  char *class,
                                  char *ref,
                                  int ref_len);
+char * virBuildSoundStringFromXML(virConnectPtr conn,
+                                  xmlXPathContextPtr ctxt);
 #endif
 
 #ifdef __cplusplus
index e7f602168b14f9a1c3969d15217cc6dce7629176..63abebeae8a3df40a6ce6d643a033f65169c3530 100644 (file)
@@ -159,6 +159,7 @@ main(int argc, char **argv)
     DO_TEST("serial-many");
     DO_TEST("parallel-tcp");
     DO_TEST("console-compat");
+    DO_TEST("sound");
 
     virCapabilitiesFree(driver.caps);
 
index b7103d624ccdd0c11be001d138943a114dd514f0..6a27b40444d12fb7937f7184db99e38090ce0a0b 100644 (file)
@@ -136,6 +136,9 @@ main(int argc, char **argv)
     DO_TEST("fv-serial-unix", "fv-serial-unix", 1);
     DO_TEST("fv-parallel-tcp", "fv-parallel-tcp", 1);
 
+    DO_TEST("fv-sound", "fv-sound", 1);
+    DO_TEST("fv-sound-all", "fv-sound-all", 1);
+
     exit(ret==0 ? EXIT_SUCCESS : EXIT_FAILURE);
 }
 #else /* WITHOUT_XEN */
index de446aad294239633d7e2970aee2547564917041..3725ae5b86c8465fbb20cb0e87eabec0e76b812a 100644 (file)
@@ -221,6 +221,8 @@ main(int argc, char **argv)
 
     DO_TEST("fullvirt-parallel-tcp", 2);
 
+    DO_TEST("fullvirt-sound", 2);
+
     exit(ret==0 ? EXIT_SUCCESS : EXIT_FAILURE);
 }
 #else /* WITHOUT_XEN */
index d04233af5f540591bdb09c223a1d55f7d425e439..547c66f84f4952ef8697edf590d0f4e55cd4d4a6 100644 (file)
@@ -143,6 +143,8 @@ main(int argc, char **argv)
     DO_TEST("fv-serial-unix", "fv-serial-unix", "fvtest", 1);
     DO_TEST("fv-parallel-tcp", "fv-parallel-tcp", "fvtest", 1);
 
+    DO_TEST("fv-sound", "fv-sound", "fvtest", 1);
+
     exit(ret==0 ? EXIT_SUCCESS : EXIT_FAILURE);
 }