[Libvir] PATCH: Implement CDROM media change for QEMU/KVM driver

Daniel P. Berrange berrange at redhat.com
Sat Oct 13 02:58:14 UTC 2007


Hugh recently added support for CDROM media change to the Xen driver, so I
thought it was time I did the same for the QEMU/KVM driver. The attached
patch implements the virDomainAttachDevice API to support changing the
media of the CDROM device. To re-use the existing QEMU code for parsing
I had to re-factor a couple of the internal QEMU device parsing apis so
that they get supplied with a pre-allocated struct for the device info,
rather than allocating it themselves.  Although this isn't strictly 
needed for CDROM media change, I have prepared the virDomainAttachDevice
method so that I can easily add USB device hotplut in the near future.
It will also be useful when KVM gets paravirt driver support sooon...

The actual implementation just runs the 'change cdrom path' command over
the QEMU monitor console.

 diffstat libvirt-qemu-cdrom-change.patch
 qemu_conf.c   |  135 +++++++++++++++++++++++++++++++++++++++-------------------
 qemu_conf.h   |   21 +++++++++
 qemu_driver.c |  132 ++++++++++++++++++++++++++++++++++++++++++++++++++------
 3 files changed, 230 insertions(+), 58 deletions(-)


Dan.
-- 
|=- Red Hat, Engineering, Emerging Technologies, Boston.  +1 978 392 2496 -=|
|=-           Perl modules: http://search.cpan.org/~danberr/              -=|
|=-               Projects: http://freshmeat.net/~danielpb/               -=|
|=-  GnuPG: 7D3B9505   F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505  -=| 
-------------- next part --------------
Index: src/qemu_conf.c
===================================================================
RCS file: /data/cvs/libvirt/src/qemu_conf.c,v
retrieving revision 1.16
diff -u -p -r1.16 qemu_conf.c
--- src/qemu_conf.c	12 Oct 2007 16:05:44 -0000	1.16
+++ src/qemu_conf.c	13 Oct 2007 02:46:23 -0000
@@ -499,11 +499,14 @@ int qemudExtractVersion(virConnectPtr co
 }
 
 
-/* Parse the XML definition for a disk */
-static struct qemud_vm_disk_def *qemudParseDiskXML(virConnectPtr conn,
-                                                   struct qemud_driver *driver ATTRIBUTE_UNUSED,
-                                                   xmlNodePtr node) {
-    struct qemud_vm_disk_def *disk = calloc(1, sizeof(struct qemud_vm_disk_def));
+/* Parse the XML definition for a disk
+ * @param disk pre-allocated & zero'd disk record
+ * @param node XML nodeset to parse for disk definition
+ * @return 0 on success, -1 on failure
+ */
+static int qemudParseDiskXML(virConnectPtr conn,
+                             struct qemud_vm_disk_def *disk,
+                             xmlNodePtr node) {
     xmlNodePtr cur;
     xmlChar *device = NULL;
     xmlChar *source = NULL;
@@ -511,11 +514,6 @@ static struct qemud_vm_disk_def *qemudPa
     xmlChar *type = NULL;
     int typ = 0;
 
-    if (!disk) {
-        qemudReportError(conn, NULL, NULL, VIR_ERR_NO_MEMORY, "disk");
-        return NULL;
-    }
-
     type = xmlGetProp(node, BAD_CAST "type");
     if (type != NULL) {
         if (xmlStrEqual(type, BAD_CAST "file"))
@@ -612,7 +610,7 @@ static struct qemud_vm_disk_def *qemudPa
     xmlFree(target);
     xmlFree(source);
 
-    return disk;
+    return 0;
 
  error:
     if (type)
@@ -623,8 +621,7 @@ static struct qemud_vm_disk_def *qemudPa
         xmlFree(source);
     if (device)
         xmlFree(device);
-    free(disk);
-    return NULL;
+    return -1;
 }
 
 static void qemudRandomMAC(struct qemud_vm_net_def *net) {
@@ -637,11 +634,14 @@ static void qemudRandomMAC(struct qemud_
 }
 
 
-/* Parse the XML definition for a network interface */
-static struct qemud_vm_net_def *qemudParseInterfaceXML(virConnectPtr conn,
-                                                       struct qemud_driver *driver ATTRIBUTE_UNUSED,
-                                                       xmlNodePtr node) {
-    struct qemud_vm_net_def *net = calloc(1, sizeof(struct qemud_vm_net_def));
+/* Parse the XML definition for a network interface
+ * @param net pre-allocated & zero'd net record
+ * @param node XML nodeset to parse for net definition
+ * @return 0 on success, -1 on failure
+ */
+static int qemudParseInterfaceXML(virConnectPtr conn,
+                                  struct qemud_vm_net_def *net,
+                                  xmlNodePtr node) {
     xmlNodePtr cur;
     xmlChar *macaddr = NULL;
     xmlChar *type = NULL;
@@ -652,11 +652,6 @@ static struct qemud_vm_net_def *qemudPar
     xmlChar *address = NULL;
     xmlChar *port = NULL;
 
-    if (!net) {
-        qemudReportError(conn, NULL, NULL, VIR_ERR_NO_MEMORY, "net");
-        return NULL;
-    }
-
     net->type = QEMUD_NET_USER;
 
     type = xmlGetProp(node, BAD_CAST "type");
@@ -869,7 +864,7 @@ static struct qemud_vm_net_def *qemudPar
         xmlFree(address);
     }
 
-    return net;
+    return 0;
 
  error:
     if (network)
@@ -884,24 +879,17 @@ static struct qemud_vm_net_def *qemudPar
         xmlFree(script);
     if (bridge)
         xmlFree(bridge);
-    free(net);
-    return NULL;
+    return -1;
 }
 
 
 /* Parse the XML definition for a network interface */
-static struct qemud_vm_input_def *qemudParseInputXML(virConnectPtr conn,
-                                                   struct qemud_driver *driver ATTRIBUTE_UNUSED,
-                                                   xmlNodePtr node) {
-    struct qemud_vm_input_def *input = calloc(1, sizeof(struct qemud_vm_input_def));
+static int qemudParseInputXML(virConnectPtr conn,
+                              struct qemud_vm_input_def *input,
+                              xmlNodePtr node) {
     xmlChar *type = NULL;
     xmlChar *bus = NULL;
 
-    if (!input) {
-        qemudReportError(conn, NULL, NULL, VIR_ERR_NO_MEMORY, "input");
-        return NULL;
-    }
-
     type = xmlGetProp(node, BAD_CAST "type");
     bus = xmlGetProp(node, BAD_CAST "bus");
 
@@ -944,7 +932,7 @@ static struct qemud_vm_input_def *qemudP
     if (bus)
         xmlFree(bus);
 
-    return input;
+    return 0;
 
  error:
     if (type)
@@ -952,8 +940,7 @@ static struct qemud_vm_input_def *qemudP
     if (bus)
         xmlFree(bus);
 
-    free(input);
-    return NULL;
+    return -1;
 }
 
 
@@ -1318,8 +1305,13 @@ static struct qemud_vm_def *qemudParseXM
         (obj->nodesetval != NULL) && (obj->nodesetval->nodeNr >= 0)) {
         struct qemud_vm_disk_def *prev = NULL;
         for (i = 0; i < obj->nodesetval->nodeNr; i++) {
-            struct qemud_vm_disk_def *disk;
-            if (!(disk = qemudParseDiskXML(conn, driver, obj->nodesetval->nodeTab[i]))) {
+            struct qemud_vm_disk_def *disk = calloc(1, sizeof(struct qemud_vm_disk_def));
+            if (!disk) {
+                qemudReportError(conn, NULL, NULL, VIR_ERR_NO_MEMORY, "disk");
+                goto error;
+            }
+            if (qemudParseDiskXML(conn, disk, obj->nodesetval->nodeTab[i]) < 0) {
+                free(disk);
                 goto error;
             }
             def->ndisks++;
@@ -1341,8 +1333,13 @@ static struct qemud_vm_def *qemudParseXM
         (obj->nodesetval != NULL) && (obj->nodesetval->nodeNr >= 0)) {
         struct qemud_vm_net_def *prev = NULL;
         for (i = 0; i < obj->nodesetval->nodeNr; i++) {
-            struct qemud_vm_net_def *net;
-            if (!(net = qemudParseInterfaceXML(conn, driver, obj->nodesetval->nodeTab[i]))) {
+            struct qemud_vm_net_def *net = calloc(1, sizeof(struct qemud_vm_net_def));
+            if (!net) {
+                qemudReportError(conn, NULL, NULL, VIR_ERR_NO_MEMORY, "net");
+                goto error;
+            }
+            if (qemudParseInterfaceXML(conn, net, obj->nodesetval->nodeTab[i]) < 0) {
+                free(net);
                 goto error;
             }
             def->nnets++;
@@ -1363,8 +1360,13 @@ static struct qemud_vm_def *qemudParseXM
         (obj->nodesetval != NULL) && (obj->nodesetval->nodeNr >= 0)) {
         struct qemud_vm_input_def *prev = NULL;
         for (i = 0; i < obj->nodesetval->nodeNr; i++) {
-            struct qemud_vm_input_def *input;
-            if (!(input = qemudParseInputXML(conn, driver, obj->nodesetval->nodeTab[i]))) {
+            struct qemud_vm_input_def *input = calloc(1, sizeof(struct qemud_vm_input_def));
+            if (!input) {
+                qemudReportError(conn, NULL, NULL, VIR_ERR_NO_MEMORY, "input");
+                goto error;
+            }
+            if (qemudParseInputXML(conn, input, obj->nodesetval->nodeTab[i]) < 0) {
+                free(input);
                 goto error;
             }
             /* Mouse + PS/2 is implicit with graphics, so don't store it */
@@ -1929,6 +1931,51 @@ static int qemudSaveConfig(virConnectPtr
     return ret;
 }
 
+struct qemud_vm_device_def *
+qemudParseVMDeviceDef(virConnectPtr conn,
+                      struct qemud_driver *driver ATTRIBUTE_UNUSED,
+                      const char *xmlStr)
+{
+    xmlDocPtr xml;
+    xmlNodePtr node;
+    struct qemud_vm_device_def *dev = calloc(1, sizeof(struct qemud_vm_device_def));
+
+    if (!(xml = xmlReadDoc(BAD_CAST xmlStr, "device.xml", NULL,
+                           XML_PARSE_NOENT | XML_PARSE_NONET |
+                           XML_PARSE_NOERROR | XML_PARSE_NOWARNING))) {
+        qemudReportError(conn, NULL, NULL, VIR_ERR_XML_ERROR, NULL);
+        return NULL;
+    }
+
+    node = xmlDocGetRootElement(xml);
+    if (node == NULL) {
+        qemudReportError(conn, NULL, NULL, VIR_ERR_XML_ERROR, "missing root element");
+        goto error;
+    }
+    if (xmlStrEqual(node->name, BAD_CAST "disk")) {
+        dev->type = QEMUD_DEVICE_DISK;
+        qemudParseDiskXML(conn, &(dev->data.disk), node);
+    } else if (xmlStrEqual(node->name, BAD_CAST "net")) {
+        dev->type = QEMUD_DEVICE_NET;
+        qemudParseInterfaceXML(conn, &(dev->data.net), node);
+    } else if (xmlStrEqual(node->name, BAD_CAST "input")) {
+        dev->type = QEMUD_DEVICE_DISK;
+        qemudParseInputXML(conn, &(dev->data.input), node);
+    } else {
+        qemudReportError(conn, NULL, NULL, VIR_ERR_XML_ERROR, "unknown device type");
+        goto error;
+    }
+
+    xmlFreeDoc(xml);
+
+    return dev;
+
+  error:
+    if (xml) xmlFreeDoc(xml);
+    if (dev) free(dev);
+    return NULL;
+}
+
 struct qemud_vm_def *
 qemudParseVMDef(virConnectPtr conn,
                 struct qemud_driver *driver,
Index: src/qemu_conf.h
===================================================================
RCS file: /data/cvs/libvirt/src/qemu_conf.h,v
retrieving revision 1.10
diff -u -p -r1.10 qemu_conf.h
--- src/qemu_conf.h	12 Oct 2007 16:05:44 -0000	1.10
+++ src/qemu_conf.h	13 Oct 2007 02:46:23 -0000
@@ -125,6 +125,22 @@ struct qemud_vm_input_def {
     struct qemud_vm_input_def *next;
 };
 
+/* Flags for the 'type' field in next struct */
+enum qemud_vm_device_type {
+    QEMUD_DEVICE_DISK,
+    QEMUD_DEVICE_NET,
+    QEMUD_DEVICE_INPUT,
+};
+
+struct qemud_vm_device_def {
+    int type;
+    union {
+        struct qemud_vm_disk_def disk;
+        struct qemud_vm_net_def net;
+        struct qemud_vm_input_def input;
+    } data;
+};
+
 #define QEMUD_MAX_BOOT_DEVS 4
 
 /* 3 possible boot devices */
@@ -354,6 +370,11 @@ struct qemud_vm *
 void        qemudRemoveInactiveVM       (struct qemud_driver *driver,
                                          struct qemud_vm *vm);
 
+struct qemud_vm_device_def *
+            qemudParseVMDeviceDef       (virConnectPtr conn,
+                                         struct qemud_driver *driver,
+                                         const char *xmlStr);
+
 struct qemud_vm_def *
             qemudParseVMDef             (virConnectPtr conn,
                                          struct qemud_driver *driver,
Index: src/qemu_driver.c
===================================================================
RCS file: /data/cvs/libvirt/src/qemu_driver.c,v
retrieving revision 1.30
diff -u -p -r1.30 qemu_driver.c
--- src/qemu_driver.c	12 Oct 2007 16:05:44 -0000	1.30
+++ src/qemu_driver.c	13 Oct 2007 02:46:25 -0000
@@ -45,6 +45,7 @@
 #include <pwd.h>
 #include <stdio.h>
 #include <sys/wait.h>
+#include <termios.h>
 #include <libxml/uri.h>
 
 #include <libvirt/virterror.h>
@@ -453,7 +454,7 @@ static int qemudOpenMonitor(virConnectPt
     char buf[1024];
     int ret = -1;
 
-    if (!(monfd = open(monitor, O_RDWR))) {
+    if (!(monfd = open(monitor, O_NOCTTY |O_RDWR))) {
         qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
                          "Unable to open monitor path %s", monitor);
         return -1;
@@ -1329,9 +1330,7 @@ static int qemudMonitorCommand(struct qe
             char *b;
 
             if (got == 0) {
-                if (buf)
-                    free(buf);
-                return -1;
+                goto error;
             }
             if (got < 0) {
                 if (errno == EINTR)
@@ -1339,21 +1338,17 @@ static int qemudMonitorCommand(struct qe
                 if (errno == EAGAIN)
                     break;
 
-                if (buf)
-                    free(buf);
-                return -1;
+                goto error;
             }
             if (!(b = realloc(buf, size+got+1))) {
-                free(buf);
-                return -1;
+                goto error;
             }
             buf = b;
             memmove(buf+size, data, got);
             buf[size+got] = '\0';
             size += got;
         }
-        if (buf)
-            qemudDebug("Mon [%s]", buf);
+
         /* Look for QEMU prompt to indicate completion */
         if (buf && ((tmp = strstr(buf, "\n(qemu) ")) != NULL)) {
             tmp[0] = '\0';
@@ -1365,13 +1360,35 @@ static int qemudMonitorCommand(struct qe
             if (errno == EINTR)
                 goto pollagain;
 
-            free(buf);
-            return -1;
+            goto error;
         }
     }
 
+ retry1:
+    if (write(vm->logfile, buf, strlen(buf)) < 0) {
+        /* Log, but ignore failures to write logfile for VM */
+        if (errno == EINTR)
+            goto retry1;
+        qemudLog(QEMUD_WARN, "Unable to log VM console data: %s",
+                 strerror(errno));
+    }
+
     *reply = buf;
     return 0;
+
+ error:
+    if (buf) {
+    retry2:
+        if (write(vm->logfile, buf, strlen(buf)) < 0) {
+            /* Log, but ignore failures to write logfile for VM */
+            if (errno == EINTR)
+                goto retry2;
+            qemudLog(QEMUD_WARN, "Unable to log VM console data: %s",
+                     strerror(errno));
+        }
+        free(buf);
+    }
+    return -1;
 }
 
 
@@ -2313,6 +2330,93 @@ static int qemudDomainUndefine(virDomain
     return 0;
 }
 
+static int qemudDomainChangeCDROM(virDomainPtr dom,
+                                  struct qemud_vm *vm,
+                                  struct qemud_vm_disk_def *olddisk,
+                                  struct qemud_vm_disk_def *newdisk) {
+    struct qemud_driver *driver = (struct qemud_driver *)dom->conn->privateData;
+    char *cmd;
+    char *reply;
+    /* XXX QEMU only supports a single CDROM for now */
+    /*cmd = malloc(strlen("change ") + strlen(olddisk->dst) + 1 + strlen(newdisk->src) + 2);*/
+    cmd = malloc(strlen("change ") + strlen("cdrom") + 1 + strlen(newdisk->src) + 2);
+    if (!cmd) {
+        qemudReportError(dom->conn, dom, NULL, VIR_ERR_NO_MEMORY, "monitor command");
+        return -1;
+    }
+    strcpy(cmd, "change ");
+    /* XXX QEMU only supports a single CDROM for now */
+    /*strcat(cmd, olddisk->dst);*/
+    strcat(cmd, "cdrom");
+    strcat(cmd, " ");
+    strcat(cmd, newdisk->src);
+    strcat(cmd, "\n");
+
+    fprintf(stderr, "Send '%s'\n", cmd);
+    if (qemudMonitorCommand(driver, vm, cmd, &reply) < 0) {
+        qemudReportError(dom->conn, dom, NULL, VIR_ERR_OPERATION_FAILED, "cannot change cdrom media");
+        free(cmd);
+        return -1;
+    }
+    fprintf(stderr, "Get back %s\n", reply);
+    free(reply);
+    free(cmd);
+    strcpy(olddisk->dst, newdisk->dst);
+    olddisk->type = newdisk->type;
+    return 0;
+}
+
+static int qemudDomainAttachDevice(virDomainPtr dom,
+                                   const char *xml) {
+    struct qemud_driver *driver = (struct qemud_driver *)dom->conn->privateData;
+    struct qemud_vm *vm = qemudFindVMByUUID(driver, dom->uuid);
+    struct qemud_vm_device_def *dev;
+    struct qemud_vm_disk_def *disk;
+
+    if (!vm) {
+        qemudReportError(dom->conn, dom, NULL, VIR_ERR_INVALID_DOMAIN, "no domain with matching uuid");
+        return -1;
+    }
+
+    if (!qemudIsActiveVM(vm)) {
+        qemudReportError(dom->conn, dom, NULL, VIR_ERR_INTERNAL_ERROR, "cannot attach device on inactive domain");
+        return -1;
+    }
+
+    dev = qemudParseVMDeviceDef(dom->conn, driver, xml);
+    if (dev == NULL) {
+        return -1;
+    }
+
+    if (dev->type != QEMUD_DEVICE_DISK || dev->data.disk.device != QEMUD_DISK_CDROM) {
+        qemudReportError(dom->conn, dom, NULL, VIR_ERR_NO_SUPPORT, "only CDROM disk devices can be attached");
+        free(dev);
+        return -1;
+    }
+
+    disk = vm->def->disks;
+    while (disk) {
+        if (disk->device == QEMUD_DISK_CDROM &&
+            STREQ(disk->dst, dev->data.disk.dst))
+            break;
+        disk = disk->next;
+    }
+
+    if (!disk) {
+        qemudReportError(dom->conn, dom, NULL, VIR_ERR_NO_SUPPORT, "CDROM not attached, cannot change media");
+        free(dev);
+        return -1;
+    }
+
+    if (qemudDomainChangeCDROM(dom, vm, disk, &dev->data.disk) < 0) {
+        free(dev);
+        return -1;
+    }
+
+    free(dev);
+    return 0;
+}
+
 static int qemudDomainGetAutostart(virDomainPtr dom,
                             int *autostart) {
     struct qemud_driver *driver = (struct qemud_driver *)dom->conn->privateData;
@@ -2705,7 +2809,7 @@ static virDriver qemuDriver = {
     qemudDomainStart, /* domainCreate */
     qemudDomainDefine, /* domainDefineXML */
     qemudDomainUndefine, /* domainUndefine */
-    NULL, /* domainAttachDevice */
+    qemudDomainAttachDevice, /* domainAttachDevice */
     NULL, /* domainDetachDevice */
     qemudDomainGetAutostart, /* domainGetAutostart */
     qemudDomainSetAutostart, /* domainSetAutostart */


More information about the libvir-list mailing list