diff --git a/ChangeLog b/ChangeLog index cc38ba1959ad0bb596011d0c5ad306cdea97d6e4..e3ca50df287667b05fdb3c4efe843acd87939bab 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +Wed May 7 16:02:07 CEST 2008 Daniel Veillard + + * 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 * src/memory.c: Add missing stddefs.h for ptrdiff_t type diff --git a/src/qemu_conf.c b/src/qemu_conf.c index 1efd9e956d6d309170e6628c420ee302f5b2794e..4f5e96ec92294e9836d425ea5b90cf7dd2064c63 100644 --- a/src/qemu_conf.c +++ b/src/qemu_conf.c @@ -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, " \n", model); + sound = sound->next; + } + virBufferAddLit(&buf, " \n"); virBufferAddLit(&buf, "\n"); diff --git a/src/qemu_conf.h b/src/qemu_conf.h index bd8d8b28af75c88248ca95f20f50c8ff1986db55..46c6fb7d5b6d98c5921770eb1eb3ed1a3396e5da 100644 --- a/src/qemu_conf.h +++ b/src/qemu_conf.h @@ -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; diff --git a/src/xend_internal.c b/src/xend_internal.c index 29a16dd55acb99e9d9b0e93c4ae095eff138b4c1..0817b7dee9a6a2fb7d7e335b221c5ea69cfb5e51 100644 --- a/src/xend_internal.c +++ b/src/xend_internal.c @@ -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, " \n", + sound_models[i]); + free(model); + break; + } + } + + if (valid && model_end) + collision = is_sound_model_conflict(model, model_end); + if (valid && !collision) + virBufferVSprintf(&buf, " \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, " \n"); virBufferAddLit(&buf, "\n"); diff --git a/src/xend_internal.h b/src/xend_internal.h index d56d73d60dc2e39bcc4f393ae3afff2de38ef31e..80ef4f6ff5241d402ea4881d6f4b3b1b5c20dd64 100644 --- a/src/xend_internal.h +++ b/src/xend_internal.h @@ -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); diff --git a/src/xm_internal.c b/src/xm_internal.c index 815f08bf208e3391411f5edb39dcc12759dbf75c..c820c5369f61dcbdb0f0cb228b20e5495194859f 100644 --- a/src/xm_internal.c +++ b/src/xm_internal.c @@ -1050,16 +1050,34 @@ char *xenXMDomainFormatXML(virConnectPtr conn, virConfPtr conf) { virBufferAddLit(&buf, " \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, " \n"); virBufferAddLit(&buf, "\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); diff --git a/src/xml.c b/src/xml.c index 5381cb1e6084dfb85def674bfc5138ce3df82b4e..e9e2e64820c9255b0da4ff3c70c6efd5a1ea55d7 100644 --- 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)"); diff --git a/src/xml.h b/src/xml.h index d91a1b0e7ba4044acb2b3130d5717cf145f7a48a..e5f21038387092c94e251d7d4b1997d14a25fc98 100644 --- 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 diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c index e7f602168b14f9a1c3969d15217cc6dce7629176..63abebeae8a3df40a6ce6d643a033f65169c3530 100644 --- a/tests/qemuxml2argvtest.c +++ b/tests/qemuxml2argvtest.c @@ -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); diff --git a/tests/sexpr2xmltest.c b/tests/sexpr2xmltest.c index b7103d624ccdd0c11be001d138943a114dd514f0..6a27b40444d12fb7937f7184db99e38090ce0a0b 100644 --- a/tests/sexpr2xmltest.c +++ b/tests/sexpr2xmltest.c @@ -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 */ diff --git a/tests/xmconfigtest.c b/tests/xmconfigtest.c index de446aad294239633d7e2970aee2547564917041..3725ae5b86c8465fbb20cb0e87eabec0e76b812a 100644 --- a/tests/xmconfigtest.c +++ b/tests/xmconfigtest.c @@ -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 */ diff --git a/tests/xml2sexprtest.c b/tests/xml2sexprtest.c index d04233af5f540591bdb09c223a1d55f7d425e439..547c66f84f4952ef8697edf590d0f4e55cd4d4a6 100644 --- a/tests/xml2sexprtest.c +++ b/tests/xml2sexprtest.c @@ -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); }