diff --git a/src/qemu_conf.c b/src/qemu_conf.c index 1efd9e9..4f5e96e 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 bd8d8b2..46c6fb7 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 29a16dd..0817b7d 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 d56d73d..80ef4f6 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 815f08b..c820c53 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 5381cb1..e9e2e64 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 d91a1b0..e5f2103 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/qemuxml2argvdata/qemuxml2argv-sound.args b/tests/qemuxml2argvdata/qemuxml2argv-sound.args new file mode 100644 index 0000000..b8d7533 --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-sound.args @@ -0,0 +1 @@ +/usr/bin/qemu -M pc -m 214 -smp 1 -nographic -monitor pty -no-acpi -boot c -hda /dev/HostVG/QEMUGuest1 -net none -serial none -parallel none -usb -soundhw pcspk,es1370,sb16 \ No newline at end of file diff --git a/tests/qemuxml2argvdata/qemuxml2argv-sound.xml b/tests/qemuxml2argvdata/qemuxml2argv-sound.xml new file mode 100644 index 0000000..a351f8d --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-sound.xml @@ -0,0 +1,30 @@ + + QEMUGuest1 + c7a5fdbd-edaf-9455-926a-d65c16db1809 + 219200 + 219200 + 1 + + hvm + + + + destroy + restart + destroy + + /usr/bin/qemu + + + + + + + + + + + + + + diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c index e7f6021..63abebe 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/sexpr2xmldata/sexpr2xml-fv-sound-all.sexpr b/tests/sexpr2xmldata/sexpr2xml-fv-sound-all.sexpr new file mode 100644 index 0000000..415fb7b --- /dev/null +++ b/tests/sexpr2xmldata/sexpr2xml-fv-sound-all.sexpr @@ -0,0 +1 @@ +(domain (domid 3)(name 'fvtest')(memory 400)(maxmem 400)(vcpus 1)(uuid 'b5d70dd275cdaca517769660b059d8bc')(on_poweroff 'destroy')(on_reboot 'restart')(on_crash 'restart')(image (hvm (kernel '/usr/lib/xen/boot/hvmloader')(device_model '/usr/lib64/xen/bin/qemu-dm')(boot c)(cdrom '/root/boot.iso')(acpi 1)(vnc 1)(keymap ja)(soundhw 'idontexit,es1370,all')))(device (vbd (dev 'ioemu:hda')(uname 'file:/root/foo.img')(mode 'w')))(device (vif (mac '00:16:3e:1b:b1:47')(bridge 'xenbr0')(script 'vif-bridge')(type ioemu)))) diff --git a/tests/sexpr2xmldata/sexpr2xml-fv-sound-all.xml b/tests/sexpr2xmldata/sexpr2xml-fv-sound-all.xml new file mode 100644 index 0000000..e316a85 --- /dev/null +++ b/tests/sexpr2xmldata/sexpr2xml-fv-sound-all.xml @@ -0,0 +1,42 @@ + + fvtest + b5d70dd2-75cd-aca5-1776-9660b059d8bc + + hvm + /usr/lib/xen/boot/hvmloader + + + 409600 + 1 + destroy + restart + restart + + + + + + /usr/lib64/xen/bin/qemu-dm + + + + + + + + + +