[libvirt] [PATCH 4/6] Snapshot QEMU driver.

Chris Lalancette clalance at redhat.com
Sat Apr 3 01:45:59 UTC 2010


Signed-off-by: Chris Lalancette <clalance at redhat.com>
---
 src/qemu/qemu_conf.c         |    9 +-
 src/qemu/qemu_conf.h         |    4 +-
 src/qemu/qemu_driver.c       |  830 +++++++++++++++++++++++++++++++++++++++++-
 src/qemu/qemu_monitor.c      |   39 ++
 src/qemu/qemu_monitor.h      |    4 +
 src/qemu/qemu_monitor_json.c |   66 ++++
 src/qemu/qemu_monitor_json.h |    4 +
 src/qemu/qemu_monitor_text.c |  141 +++++++
 src/qemu/qemu_monitor_text.h |    4 +
 tests/qemuxml2argvtest.c     |    2 +-
 10 files changed, 1089 insertions(+), 14 deletions(-)

diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c
index 55397cd..0e431c6 100644
--- a/src/qemu/qemu_conf.c
+++ b/src/qemu/qemu_conf.c
@@ -3373,7 +3373,9 @@ int qemudBuildCommandLine(virConnectPtr conn,
                           const char ***retenv,
                           int **tapfds,
                           int *ntapfds,
-                          const char *migrateFrom) {
+                          const char *migrateFrom,
+                          virDomainSnapshotObjPtr current_snapshot)
+{
     int i;
     char memory[50];
     char boot[VIR_DOMAIN_BOOT_LAST];
@@ -4578,6 +4580,11 @@ int qemudBuildCommandLine(virConnectPtr conn,
         ADD_ARG_LIT("virtio");
     }
 
+    if (current_snapshot && current_snapshot->def->active) {
+        ADD_ARG_LIT("-loadvm");
+        ADD_ARG_LIT(current_snapshot->def->name);
+    }
+
     ADD_ARG(NULL);
     ADD_ENV(NULL);
 
diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h
index 39518ca..44b7daa 100644
--- a/src/qemu/qemu_conf.h
+++ b/src/qemu/qemu_conf.h
@@ -120,6 +120,7 @@ struct qemud_driver {
      * the QEMU user/group */
     char *libDir;
     char *cacheDir;
+    char *snapshotDir;
     unsigned int vncTLS : 1;
     unsigned int vncTLSx509verify : 1;
     unsigned int vncSASL : 1;
@@ -198,7 +199,8 @@ int         qemudBuildCommandLine       (virConnectPtr conn,
                                          const char ***retenv,
                                          int **tapfds,
                                          int *ntapfds,
-                                         const char *migrateFrom)
+                                         const char *migrateFrom,
+                                         virDomainSnapshotObjPtr current_snapshot)
     ATTRIBUTE_NONNULL(1);
 
 /* With vlan == -1, use netdev syntax, else old hostnet */
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 02ed95f..8c9bb46 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -1342,6 +1342,93 @@ no_memory:
     return NULL;
 }
 
+static void qemuDomainSnapshotLoad(void *payload,
+                                   const char *name ATTRIBUTE_UNUSED,
+                                   void *data)
+{
+    virDomainObjPtr vm = (virDomainObjPtr)payload;
+    char *baseDir = (char *)data;
+    char *snapDir = NULL;
+    DIR *dir = NULL;
+    struct dirent *entry;
+    char *xmlStr;
+    int ret;
+    char *fullpath;
+    virDomainSnapshotObjPtr snap = NULL;
+    virDomainSnapshotDefPtr def = NULL;
+    char ebuf[1024];
+
+    virDomainObjLock(vm);
+    if (virAsprintf(&snapDir, "%s/%s", baseDir, vm->def->name) < 0) {
+        VIR_ERROR("Failed to allocate memory for snapshot directory for domain %s",
+                   vm->def->name);
+        goto cleanup;
+    }
+
+    VIR_INFO("Scanning for snapshots for domain %s in %s", vm->def->name,
+             snapDir);
+
+    if (!(dir = opendir(snapDir))) {
+        if (errno != ENOENT)
+            VIR_ERROR("Failed to open snapshot directory %s for domain %s: %s",
+                      snapDir, vm->def->name,
+                      virStrerror(errno, ebuf, sizeof(ebuf)));
+        goto cleanup;
+    }
+
+    while ((entry = readdir(dir))) {
+        if (entry->d_name[0] == '.')
+            continue;
+
+        /* NB: ignoring errors, so one malformed config doesn't
+           kill the whole process */
+        VIR_INFO("Loading snapshot file '%s'", entry->d_name);
+
+        if (virAsprintf(&fullpath, "%s/%s", snapDir, entry->d_name) < 0) {
+            VIR_ERROR0("Failed to allocate memory for path");
+            continue;
+        }
+
+        ret = virFileReadAll(fullpath, 1024*1024*1, &xmlStr);
+        VIR_FREE(fullpath);
+        if (ret < 0) {
+            /* Nothing we can do here, skip this one */
+            VIR_ERROR("Failed to read snapshot file %s: %s", fullpath,
+                      virStrerror(errno, ebuf, sizeof(ebuf)));
+            continue;
+        }
+
+        def = virDomainSnapshotDefParseString(xmlStr, 0);
+        if (def == NULL) {
+            /* Nothing we can do here, skip this one */
+            VIR_ERROR("Failed to parse snapshot XML from file '%s'", fullpath);
+            VIR_FREE(xmlStr);
+            continue;
+        }
+
+        snap = virDomainSnapshotAssignDef(&vm->snapshots, def);
+
+        VIR_FREE(xmlStr);
+    }
+
+    /* FIXME: qemu keeps internal track of snapshots.  We can get access
+     * to this info via the "info snapshots" monitor command for running
+     * domains, or via "qemu-img snapshot -l" for shutoff domains.  It would
+     * be nice to update our internal state based on that, but there is a
+     * a problem.  qemu doesn't track all of the same metadata that we do.
+     * In particular we wouldn't be able to fill in the <parent>, which is
+     * pretty important in our metadata.
+     */
+
+    virResetLastError();
+
+cleanup:
+    if (dir)
+        closedir(dir);
+    VIR_FREE(snapDir);
+    virDomainObjUnlock(vm);
+}
+
 /**
  * qemudStartup:
  *
@@ -1399,6 +1486,9 @@ qemudStartup(int privileged) {
         if (virAsprintf(&qemu_driver->cacheDir,
                       "%s/cache/libvirt/qemu", LOCAL_STATE_DIR) == -1)
             goto out_of_memory;
+        if (virAsprintf(&qemu_driver->snapshotDir,
+                        "%s/run/libvirt/qemu/snapshot", LOCAL_STATE_DIR) == -1)
+            goto out_of_memory;
     } else {
         uid_t uid = geteuid();
         char *userdir = virGetUserDirectory(uid);
@@ -1423,6 +1513,8 @@ qemudStartup(int privileged) {
             goto out_of_memory;
         if (virAsprintf(&qemu_driver->cacheDir, "%s/qemu/cache", base) == -1)
             goto out_of_memory;
+        if (virAsprintf(&qemu_driver->snapshotDir, "%s/qemu/snapshot", base) == -1)
+            goto out_of_memory;
     }
 
     if (virFileMakePath(qemu_driver->stateDir) != 0) {
@@ -1443,6 +1535,12 @@ qemudStartup(int privileged) {
                   qemu_driver->cacheDir, virStrerror(errno, ebuf, sizeof ebuf));
         goto error;
     }
+    if (virFileMakePath(qemu_driver->snapshotDir) != 0) {
+        char ebuf[1024];
+        VIR_ERROR(_("Failed to create save dir '%s': %s"),
+                  qemu_driver->snapshotDir, virStrerror(errno, ebuf, sizeof ebuf));
+        goto error;
+    }
 
     /* Configuration paths are either ~/.libvirt/qemu/... (session) or
      * /etc/libvirt/qemu/... (system).
@@ -1493,6 +1591,12 @@ qemudStartup(int privileged) {
                                  qemu_driver->cacheDir, qemu_driver->user, qemu_driver->group);
             goto error;
         }
+        if (chown(qemu_driver->snapshotDir, qemu_driver->user, qemu_driver->group) < 0) {
+            virReportSystemError(errno,
+                                 _("unable to set ownership of '%s' to %d:%d"),
+                                 qemu_driver->snapshotDir, qemu_driver->user, qemu_driver->group);
+            goto error;
+        }
     }
 
     /* If hugetlbfs is present, then we need to create a sub-directory within
@@ -1543,6 +1647,11 @@ qemudStartup(int privileged) {
                                 qemu_driver->autostartDir,
                                 0, NULL, NULL) < 0)
         goto error;
+
+
+    virHashForEach(qemu_driver->domains.objs, qemuDomainSnapshotLoad,
+                   qemu_driver->snapshotDir);
+
     qemuDriverUnlock(qemu_driver);
 
     qemudAutostartConfigs(qemu_driver);
@@ -1645,6 +1754,7 @@ qemudShutdown(void) {
     VIR_FREE(qemu_driver->stateDir);
     VIR_FREE(qemu_driver->libDir);
     VIR_FREE(qemu_driver->cacheDir);
+    VIR_FREE(qemu_driver->snapshotDir);
     VIR_FREE(qemu_driver->vncTLSx509certdir);
     VIR_FREE(qemu_driver->vncListen);
     VIR_FREE(qemu_driver->vncPassword);
@@ -3008,6 +3118,11 @@ qemuPrepareMonitorChr(struct qemud_driver *driver,
     return 0;
 }
 
+static int qemuDomainSnapshotSetActive(virDomainObjPtr vm,
+                                       char *snapshotDir);
+static int qemuDomainSnapshotSetInactive(virDomainObjPtr vm,
+                                         char *snapshotDir);
+
 static int qemudStartVMDaemon(virConnectPtr conn,
                               struct qemud_driver *driver,
                               virDomainObjPtr vm,
@@ -3175,7 +3290,11 @@ static int qemudStartVMDaemon(virConnectPtr conn,
     vm->def->id = driver->nextvmid++;
     if (qemudBuildCommandLine(conn, driver, vm->def, priv->monConfig,
                               priv->monJSON, qemuCmdFlags, &argv, &progenv,
-                              &tapfds, &ntapfds, migrateFrom) < 0)
+                              &tapfds, &ntapfds, migrateFrom,
+                              vm->current_snapshot) < 0)
+        goto cleanup;
+
+    if (qemuDomainSnapshotSetInactive(vm, driver->snapshotDir) < 0)
         goto cleanup;
 
     /* now that we know it is about to start call the hook if present */
@@ -5911,7 +6030,7 @@ static char *qemuDomainXMLToNative(virConnectPtr conn,
                               &monConfig, 0, qemuCmdFlags,
                               &retargv, &retenv,
                               NULL, NULL, /* Don't want it to create TAP devices */
-                              NULL) < 0) {
+                              NULL, NULL) < 0) {
         goto cleanup;
     }
 
@@ -10226,6 +10345,695 @@ cleanup:
     return ret;
 }
 
+static char *qemuFindQemuImgBinary(void)
+{
+    char *ret;
+
+    ret = virFindFileInPath("kvm-img");
+    if (ret == NULL)
+        ret = virFindFileInPath("qemu-img");
+    if (ret == NULL)
+        qemuReportError(VIR_ERR_INTERNAL_ERROR,
+                        "%s", _("unable to find kvm-img or qemu-img"));
+
+    return ret;
+}
+
+static int qemuDomainSnapshotWriteSnapshotMetadata(virDomainObjPtr vm,
+                                                   char *snapshotDir)
+{
+    int fd = -1;
+    char *newxml = NULL;
+    int ret = -1;
+    char *snapDir = NULL;
+    char *snapFile = NULL;
+    int err;
+    char uuidstr[VIR_UUID_STRING_BUFLEN];
+
+    virUUIDFormat(vm->def->uuid, uuidstr);
+    newxml = virDomainSnapshotDefFormat(uuidstr, vm->current_snapshot->def, 1);
+    if (newxml == NULL) {
+        virReportOOMError();
+        return -1;
+    }
+
+    if (virAsprintf(&snapDir, "%s/%s", snapshotDir, vm->def->name) < 0) {
+        virReportOOMError();
+        goto cleanup;
+    }
+    err = virFileMakePath(snapDir);
+    if (err < 0) {
+        virReportSystemError(err, _("cannot create snapshot directory '%s'"),
+                             snapDir);
+        goto cleanup;
+    }
+
+    if (virAsprintf(&snapFile, "%s/%s.xml", snapDir,
+                    vm->current_snapshot->def->name) < 0) {
+        virReportOOMError();
+        goto cleanup;
+    }
+    fd = open(snapFile, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR);
+    if (fd < 0) {
+        qemuReportError(VIR_ERR_OPERATION_FAILED,
+                        _("failed to create snapshot file '%s'"), snapFile);
+        goto cleanup;
+    }
+    if (safewrite(fd, newxml, strlen(newxml)) != strlen(newxml)) {
+        virReportSystemError(errno, _("Failed to write snapshot data to %s"),
+                             snapFile);
+        goto cleanup;
+    }
+
+    ret = 0;
+
+cleanup:
+    VIR_FREE(snapFile);
+    VIR_FREE(snapDir);
+    VIR_FREE(newxml);
+    if (fd != -1)
+        close(fd);
+    return ret;
+}
+
+static int qemuDomainSnapshotSetActive(virDomainObjPtr vm,
+                                       char *snapshotDir)
+{
+    if (vm->current_snapshot) {
+        vm->current_snapshot->def->active = 1;
+
+        return qemuDomainSnapshotWriteSnapshotMetadata(vm, snapshotDir);
+    }
+
+    return 0;
+}
+
+static int qemuDomainSnapshotSetInactive(virDomainObjPtr vm,
+                                         char *snapshotDir)
+{
+    if (vm->current_snapshot) {
+        vm->current_snapshot->def->active = 0;
+
+        return qemuDomainSnapshotWriteSnapshotMetadata(vm, snapshotDir);
+    }
+
+    return 0;
+}
+
+
+static int qemuDomainSnapshotIsAllowed(virDomainObjPtr vm)
+{
+    int i;
+
+    /* FIXME: we need to figure out what else here might succeed; in
+     * particular, if it's a raw device but on LVM, we could probably make
+     * that succeed as well
+     */
+    for (i = 0; i < vm->def->ndisks; i++) {
+        if (vm->def->disks[i]->device == VIR_DOMAIN_DISK_DEVICE_DISK &&
+            (!vm->def->disks[i]->driverType ||
+             STRNEQ(vm->def->disks[i]->driverType, "qcow2"))) {
+            qemuReportError(VIR_ERR_OPERATION_INVALID,
+                            _("Disk device '%s' does not support snapshotting"),
+                            vm->def->disks[i]->info.alias);
+            return 0;
+        }
+    }
+
+    return 1;
+}
+
+static virDomainSnapshotPtr qemuDomainSnapshotCreateXML(virDomainPtr domain,
+                                                        const char *xmlDesc,
+                                                        unsigned int flags ATTRIBUTE_UNUSED)
+{
+    struct qemud_driver *driver = domain->conn->privateData;
+    virDomainObjPtr vm = NULL;
+    virDomainSnapshotObjPtr snap = NULL;
+    virDomainSnapshotPtr snapshot = NULL;
+    char uuidstr[VIR_UUID_STRING_BUFLEN];
+    virDomainSnapshotDefPtr def;
+    qemuDomainObjPrivatePtr priv;
+    const char *qemuimgarg[] = { NULL, "snapshot", "-c", NULL, NULL, NULL };
+    int i;
+
+    qemuDriverLock(driver);
+    virUUIDFormat(domain->uuid, uuidstr);
+    vm = virDomainFindByUUID(&driver->domains, domain->uuid);
+    if (!vm) {
+        qemuReportError(VIR_ERR_NO_DOMAIN,
+                        _("no domain with matching uuid '%s'"), uuidstr);
+        goto cleanup;
+    }
+
+    /* in a perfect world, we would allow qemu to tell us this.  The problem
+     * is that qemu only does this check device-by-device; so if you had a
+     * domain that booted from a large qcow2 device, but had a secondary raw
+     * device attached, you wouldn't find out that you can't snapshot your
+     * guest until *after* it had spent the time to snapshot the boot device.
+     * This is probably a bug in qemu, but we'll work around it here for now.
+     */
+    if (!qemuDomainSnapshotIsAllowed(vm))
+        goto cleanup;
+
+    if (!(def = virDomainSnapshotDefParseString(xmlDesc, 1)))
+        goto cleanup;
+
+    if (!(snap = virDomainSnapshotAssignDef(&vm->snapshots, def)))
+        goto cleanup;
+
+    /* actually do the snapshot */
+    if (!virDomainObjIsActive(vm)) {
+        qemuimgarg[0] = qemuFindQemuImgBinary();
+        if (qemuimgarg[0] == NULL)
+            /* qemuFindQemuImgBinary set the error */
+            goto cleanup;
+
+        qemuimgarg[3] = snap->def->name;
+
+        for (i = 0; i < vm->def->ndisks; i++) {
+            /* FIXME: we also need to handle LVM here */
+            /* FIXME: if we fail halfway through this loop, we are in an
+             * inconsistent state.  I'm not quite sure what to do about that
+             */
+            if (vm->def->disks[i]->device == VIR_DOMAIN_DISK_DEVICE_DISK) {
+                if (!vm->def->disks[i]->driverType ||
+                    STRNEQ(vm->def->disks[i]->driverType, "qcow2")) {
+                    qemuReportError(VIR_ERR_OPERATION_INVALID,
+                                    _("Disk device '%s' does not support snapshotting"),
+                                    vm->def->disks[i]->info.alias);
+                    goto cleanup;
+                }
+
+                qemuimgarg[4] = vm->def->disks[i]->src;
+
+                if (virRun(qemuimgarg, NULL) < 0) {
+                    virReportSystemError(errno,
+                                         _("Failed to run '%s' to create snapshot '%s' from disk '%s'"),
+                                         qemuimgarg[0], snap->def->name,
+                                         vm->def->disks[i]->src);
+                    goto cleanup;
+                }
+            }
+        }
+    }
+    else {
+        priv = vm->privateData;
+        qemuDomainObjEnterMonitorWithDriver(driver, vm);
+        if (qemuMonitorCreateSnapshot(priv->mon, def->name) < 0) {
+            qemuDomainObjExitMonitorWithDriver(driver, vm);
+            goto cleanup;
+        }
+        qemuDomainObjExitMonitorWithDriver(driver, vm);
+
+    }
+
+    snap->def->state = vm->state;
+
+    /* FIXME: if we fail after this point, there's not a whole lot we can
+     * do; we've successfully taken the snapshot, and we are now running
+     * on it, so we have to go forward the best we can
+     */
+
+    if (vm->current_snapshot) {
+        def->parent = strdup(vm->current_snapshot->def->name);
+        if (def->parent == NULL) {
+            virReportOOMError();
+            goto cleanup;
+        }
+    }
+
+    /* Now we set the new current_snapshot for the domain */
+    vm->current_snapshot = snap;
+
+    if (qemuDomainSnapshotWriteSnapshotMetadata(vm, driver->snapshotDir) < 0)
+        /* qemuDomainSnapshotWriteSnapshotMetadata set the error */
+        goto cleanup;
+
+    snapshot = virGetDomainSnapshot(domain, snap->def->name);
+
+cleanup:
+    VIR_FREE(qemuimgarg[0]);
+    if (vm)
+        virDomainObjUnlock(vm);
+    qemuDriverUnlock(driver);
+    return snapshot;
+}
+
+static int qemuDomainSnapshotListNames(virDomainPtr domain, char **names,
+                                       int nameslen,
+                                       unsigned int flags ATTRIBUTE_UNUSED)
+{
+    struct qemud_driver *driver = domain->conn->privateData;
+    virDomainObjPtr vm = NULL;
+    int n = -1;
+
+    qemuDriverLock(driver);
+    vm = virDomainFindByUUID(&driver->domains, domain->uuid);
+    if (!vm) {
+        char uuidstr[VIR_UUID_STRING_BUFLEN];
+        virUUIDFormat(domain->uuid, uuidstr);
+        qemuReportError(VIR_ERR_NO_DOMAIN,
+                        _("no domain with matching uuid '%s'"), uuidstr);
+        goto cleanup;
+    }
+
+    n = virDomainSnapshotObjListGetNames(&vm->snapshots, names, nameslen);
+
+cleanup:
+    if (vm)
+        virDomainObjUnlock(vm);
+    qemuDriverUnlock(driver);
+    return n;
+}
+
+static int qemuDomainSnapshotNum(virDomainPtr domain,
+                                 unsigned int flags ATTRIBUTE_UNUSED)
+{
+    struct qemud_driver *driver = domain->conn->privateData;
+    virDomainObjPtr vm = NULL;
+    int n = -1;
+
+    qemuDriverLock(driver);
+    vm = virDomainFindByUUID(&driver->domains, domain->uuid);
+    if (!vm) {
+        char uuidstr[VIR_UUID_STRING_BUFLEN];
+        virUUIDFormat(domain->uuid, uuidstr);
+        qemuReportError(VIR_ERR_NO_DOMAIN,
+                        _("no domain with matching uuid '%s'"), uuidstr);
+        goto cleanup;
+    }
+
+    n = virDomainSnapshotObjListNum(&vm->snapshots);
+
+cleanup:
+    if (vm)
+        virDomainObjUnlock(vm);
+    qemuDriverUnlock(driver);
+    return n;
+}
+
+static virDomainSnapshotPtr qemuDomainSnapshotLookupByName(virDomainPtr domain,
+                                                           const char *name,
+                                                           unsigned int flags ATTRIBUTE_UNUSED)
+{
+    struct qemud_driver *driver = domain->conn->privateData;
+    virDomainObjPtr vm;
+    virDomainSnapshotObjPtr snap = NULL;
+    virDomainSnapshotPtr snapshot = NULL;
+
+    qemuDriverLock(driver);
+    vm = virDomainFindByUUID(&driver->domains, domain->uuid);
+    if (!vm) {
+        char uuidstr[VIR_UUID_STRING_BUFLEN];
+        virUUIDFormat(domain->uuid, uuidstr);
+        qemuReportError(VIR_ERR_NO_DOMAIN,
+                        _("no domain with matching uuid '%s'"), uuidstr);
+        goto cleanup;
+    }
+
+    snap = virDomainSnapshotFindByName(&vm->snapshots, name);
+    if (!snap) {
+        qemuReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT,
+                        _("no snapshot with matching name '%s'"), name);
+        goto cleanup;
+    }
+
+    snapshot = virGetDomainSnapshot(domain, snap->def->name);
+
+cleanup:
+    if (vm)
+        virDomainObjUnlock(vm);
+    qemuDriverUnlock(driver);
+    return snapshot;
+}
+
+static int qemuDomainHasCurrentSnapshot(virDomainPtr domain,
+                                        unsigned int flags ATTRIBUTE_UNUSED)
+{
+    struct qemud_driver *driver = domain->conn->privateData;
+    virDomainObjPtr vm;
+    int ret = -1;
+
+    qemuDriverLock(driver);
+    vm = virDomainFindByUUID(&driver->domains, domain->uuid);
+    if (!vm) {
+        char uuidstr[VIR_UUID_STRING_BUFLEN];
+        virUUIDFormat(domain->uuid, uuidstr);
+        qemuReportError(VIR_ERR_NO_DOMAIN,
+                        _("no domain with matching uuid '%s'"), uuidstr);
+        goto cleanup;
+    }
+
+    ret = (vm->current_snapshot != NULL);
+
+cleanup:
+    if (vm)
+        virDomainObjUnlock(vm);
+    qemuDriverUnlock(driver);
+    return ret;
+}
+
+static virDomainSnapshotPtr qemuDomainSnapshotCurrent(virDomainPtr domain,
+                                                      unsigned int flags ATTRIBUTE_UNUSED)
+{
+    struct qemud_driver *driver = domain->conn->privateData;
+    virDomainObjPtr vm;
+    virDomainSnapshotPtr snapshot = NULL;
+
+    qemuDriverLock(driver);
+    vm = virDomainFindByUUID(&driver->domains, domain->uuid);
+    if (!vm) {
+        char uuidstr[VIR_UUID_STRING_BUFLEN];
+        virUUIDFormat(domain->uuid, uuidstr);
+        qemuReportError(VIR_ERR_NO_DOMAIN,
+                        _("no domain with matching uuid '%s'"), uuidstr);
+        goto cleanup;
+    }
+
+    if (!vm->current_snapshot) {
+        qemuReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, "%s",
+                        _("the domain does not have a current snapshot"));
+        goto cleanup;
+    }
+
+    snapshot = virGetDomainSnapshot(domain, vm->current_snapshot->def->name);
+
+cleanup:
+    if (vm)
+        virDomainObjUnlock(vm);
+    qemuDriverUnlock(driver);
+    return snapshot;
+}
+
+static char *qemuDomainSnapshotDumpXML(virDomainSnapshotPtr snapshot,
+                                       unsigned int flags ATTRIBUTE_UNUSED)
+{
+    struct qemud_driver *driver = snapshot->domain->conn->privateData;
+    virDomainObjPtr vm = NULL;
+    char *xml = NULL;
+    virDomainSnapshotObjPtr snap = NULL;
+    char uuidstr[VIR_UUID_STRING_BUFLEN];
+
+    qemuDriverLock(driver);
+    virUUIDFormat(snapshot->domain->uuid, uuidstr);
+    vm = virDomainFindByUUID(&driver->domains, snapshot->domain->uuid);
+    if (!vm) {
+        qemuReportError(VIR_ERR_NO_DOMAIN,
+                        _("no domain with matching uuid '%s'"), uuidstr);
+        goto cleanup;
+    }
+
+    snap = virDomainSnapshotFindByName(&vm->snapshots, snapshot->name);
+    if (!snap) {
+        qemuReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT,
+                        _("no domain snapshot with matching name '%s'"),
+                        snapshot->name);
+        goto cleanup;
+    }
+
+    xml = virDomainSnapshotDefFormat(uuidstr, snap->def, 0);
+
+cleanup:
+    if (vm)
+        virDomainObjUnlock(vm);
+    qemuDriverUnlock(driver);
+    return xml;
+}
+
+static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot,
+                                      unsigned int flags ATTRIBUTE_UNUSED)
+{
+    struct qemud_driver *driver = snapshot->domain->conn->privateData;
+    virDomainObjPtr vm = NULL;
+    int ret = -1;
+    virDomainSnapshotObjPtr snap = NULL;
+    char uuidstr[VIR_UUID_STRING_BUFLEN];
+    virDomainEventPtr event = NULL;
+    qemuDomainObjPrivatePtr priv;
+    int rc;
+
+    qemuDriverLock(driver);
+    virUUIDFormat(snapshot->domain->uuid, uuidstr);
+    vm = virDomainFindByUUID(&driver->domains, snapshot->domain->uuid);
+    if (!vm) {
+        qemuReportError(VIR_ERR_NO_DOMAIN,
+                        _("no domain with matching uuid '%s'"), uuidstr);
+        goto cleanup;
+    }
+
+    snap = virDomainSnapshotFindByName(&vm->snapshots, snapshot->name);
+    if (!snap) {
+        qemuReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT,
+                        _("no domain snapshot with matching name '%s'"),
+                        snapshot->name);
+        goto cleanup;
+    }
+
+    vm->current_snapshot = snap;
+
+    if (qemuDomainObjBeginJobWithDriver(driver, vm) < 0)
+        goto cleanup;
+
+    if (snap->def->state == VIR_DOMAIN_RUNNING
+        || snap->def->state == VIR_DOMAIN_PAUSED) {
+
+        if (virDomainObjIsActive(vm)) {
+            priv = vm->privateData;
+            qemuDomainObjEnterMonitorWithDriver(driver, vm);
+            rc = qemuMonitorLoadSnapshot(priv->mon, snap->def->name);
+            qemuDomainObjExitMonitorWithDriver(driver, vm);
+            if (rc < 0)
+                goto cleanup;
+        }
+        else {
+            if (qemuDomainSnapshotSetActive(vm, driver->snapshotDir) < 0)
+                goto cleanup;
+
+            rc = qemudStartVMDaemon(snapshot->domain->conn, driver, vm, NULL,
+                                    -1);
+            if (qemuDomainSnapshotSetInactive(vm, driver->snapshotDir) < 0)
+                goto cleanup;
+            if (rc < 0)
+                goto cleanup;
+        }
+
+        if (snap->def->state == VIR_DOMAIN_PAUSED) {
+            /* qemu unconditionally starts the domain running again after
+             * loadvm, so let's pause it to keep consistency
+             */
+            priv = vm->privateData;
+            qemuDomainObjEnterMonitorWithDriver(driver, vm);
+            rc = qemuMonitorStopCPUs(priv->mon);
+            qemuDomainObjExitMonitorWithDriver(driver, vm);
+            if (rc < 0)
+                goto cleanup;
+        }
+
+        event = virDomainEventNewFromObj(vm,
+                                         VIR_DOMAIN_EVENT_STARTED,
+                                         VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT);
+    }
+    else {
+        /* qemu is a little funny with running guests and the restoration
+         * of snapshots.  If the snapshot was taken online,
+         * then after a "loadvm" monitor command, the VM is set running
+         * again.  If the snapshot was taken offline, then after a "loadvm"
+         * monitor command the VM is left paused.  Unpausing it leads to
+         * the memory state *before* the loadvm with the disk *after* the
+         * loadvm, which obviously is bound to corrupt something.
+         * Therefore we destroy the domain and set it to "off" in this case.
+         */
+
+        if (virDomainObjIsActive(vm)) {
+            qemudShutdownVMDaemon(driver, vm);
+            event = virDomainEventNewFromObj(vm,
+                                             VIR_DOMAIN_EVENT_STOPPED,
+                                             VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT);
+        }
+
+        if (qemuDomainSnapshotSetActive(vm, driver->snapshotDir) < 0)
+            goto cleanup;
+    }
+
+    vm->state = snap->def->state;
+
+    ret = 0;
+
+cleanup:
+    if (vm && qemuDomainObjEndJob(vm) == 0)
+        vm = NULL;
+
+    if (event)
+        qemuDomainEventQueue(driver, event);
+    if (vm)
+        virDomainObjUnlock(vm);
+    qemuDriverUnlock(driver);
+
+    return ret;
+}
+
+static int qemuDomainSnapshotDiscard(struct qemud_driver *driver,
+                                     virDomainObjPtr vm,
+                                     virDomainSnapshotObjPtr snap)
+{
+    const char *qemuimgarg[] = { NULL, "snapshot", "-d", NULL, NULL, NULL };
+    char *snapFile = NULL;
+    int ret = -1;
+    int i;
+    qemuDomainObjPrivatePtr priv;
+    virDomainSnapshotObjPtr parentsnap;
+
+    if (!virDomainObjIsActive(vm)) {
+        qemuimgarg[0] = qemuFindQemuImgBinary();
+        if (qemuimgarg[0] == NULL)
+            /* qemuFindQemuImgBinary set the error */
+            goto cleanup;
+
+        qemuimgarg[3] = snap->def->name;
+
+        for (i = 0; i < vm->def->ndisks; i++) {
+            /* FIXME: we also need to handle LVM here */
+            if (vm->def->disks[i]->device == VIR_DOMAIN_DISK_DEVICE_DISK) {
+                if (!vm->def->disks[i]->driverType ||
+                    STRNEQ(vm->def->disks[i]->driverType, "qcow2")) {
+                    /* we continue on even in the face of error, since other
+                     * disks in this VM may have this snapshot in place
+                     */
+                    continue;
+                }
+
+                qemuimgarg[4] = vm->def->disks[i]->src;
+
+                if (virRun(qemuimgarg, NULL) < 0) {
+                    /* we continue on even in the face of error, since other
+                     * disks in this VM may have this snapshot in place
+                     */
+                    continue;
+                }
+            }
+        }
+    }
+    else {
+        priv = vm->privateData;
+        qemuDomainObjEnterMonitorWithDriver(driver, vm);
+        /* we continue on even in the face of error */
+        qemuMonitorDeleteSnapshot(priv->mon, snap->def->name);
+        qemuDomainObjExitMonitorWithDriver(driver, vm);
+    }
+
+    if (snap == vm->current_snapshot) {
+        if (snap->def->parent) {
+            parentsnap = virDomainSnapshotFindByName(&vm->snapshots,
+                                                     snap->def->parent);
+            if (!parentsnap) {
+                qemuReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT,
+                                _("no domain snapshot parent with matching name '%s'"),
+                                snap->def->parent);
+                goto cleanup;
+            }
+
+            /* Now we set the new current_snapshot for the domain */
+            vm->current_snapshot = parentsnap;
+        }
+        else
+            vm->current_snapshot = NULL;
+    }
+
+    if (virAsprintf(&snapFile, "%s/%s/%s.xml", driver->snapshotDir,
+                    vm->def->name, snap->def->name) < 0) {
+        virReportOOMError();
+        goto cleanup;
+    }
+    unlink(snapFile);
+
+    virDomainSnapshotObjListRemove(&vm->snapshots, snap);
+
+    ret = 0;
+
+cleanup:
+    VIR_FREE(snapFile);
+    VIR_FREE(qemuimgarg[0]);
+
+    return ret;
+}
+
+struct snap_remove {
+    struct qemud_driver *driver;
+    virDomainObjPtr vm;
+    char *parent;
+    int err;
+};
+
+static void qemuDomainSnapshotDiscardChildren(void *payload,
+                                              const char *name ATTRIBUTE_UNUSED,
+                                              void *data)
+{
+    virDomainSnapshotObjPtr snap = payload;
+    struct snap_remove *curr = data;
+    struct snap_remove this;
+
+    if (snap->def->parent && STREQ(snap->def->parent, curr->parent)) {
+        this.driver = curr->driver;
+        this.vm = curr->vm;
+        this.parent = snap->def->name;
+        this.err = 0;
+        virHashForEach(curr->vm->snapshots.objs,
+                       qemuDomainSnapshotDiscardChildren, &this);
+
+        if (this.err)
+            curr->err = this.err;
+        else
+            this.err = qemuDomainSnapshotDiscard(curr->driver, curr->vm, snap);
+    }
+}
+
+static int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot,
+                                    unsigned int flags)
+{
+    struct qemud_driver *driver = snapshot->domain->conn->privateData;
+    virDomainObjPtr vm = NULL;
+    int ret = -1;
+    virDomainSnapshotObjPtr snap = NULL;
+    char uuidstr[VIR_UUID_STRING_BUFLEN];
+    struct snap_remove rem;
+
+    qemuDriverLock(driver);
+    virUUIDFormat(snapshot->domain->uuid, uuidstr);
+    vm = virDomainFindByUUID(&driver->domains, snapshot->domain->uuid);
+    if (!vm) {
+        qemuReportError(VIR_ERR_NO_DOMAIN,
+                        _("no domain with matching uuid '%s'"), uuidstr);
+        goto cleanup;
+    }
+
+    snap = virDomainSnapshotFindByName(&vm->snapshots, snapshot->name);
+    if (!snap) {
+        qemuReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT,
+                        _("no domain snapshot with matching name '%s'"),
+                        snapshot->name);
+        goto cleanup;
+    }
+
+    if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN) {
+        rem.driver = driver;
+        rem.vm = vm;
+        rem.parent = snap->def->name;
+        rem.err = 0;
+        virHashForEach(vm->snapshots.objs, qemuDomainSnapshotDiscardChildren,
+                       &rem);
+        if (rem.err < 0)
+            goto cleanup;
+    }
+
+    ret = qemuDomainSnapshotDiscard(driver, vm, snap);
+
+cleanup:
+    if (vm)
+        virDomainObjUnlock(vm);
+    qemuDriverUnlock(driver);
+    return ret;
+}
 
 static virDriver qemuDriver = {
     VIR_DRV_QEMU,
@@ -10312,15 +11120,15 @@ static virDriver qemuDriver = {
     qemuDomainMigrateSetMaxDowntime, /* domainMigrateSetMaxDowntime */
     qemuDomainEventRegisterAny, /* domainEventRegisterAny */
     qemuDomainEventDeregisterAny, /* domainEventDeregisterAny */
-    NULL, /* domainSnapshotCreateXML */
-    NULL, /* domainSnapshotDumpXML */
-    NULL, /* domainSnapshotNum */
-    NULL, /* domainSnapshotListNames */
-    NULL, /* domainSnapshotLookupByName */
-    NULL, /* domainHasCurrentSnapshot */
-    NULL, /* domainSnapshotCurrent */
-    NULL, /* domainRevertToSnapshot */
-    NULL, /* domainSnapshotDelete */
+    qemuDomainSnapshotCreateXML, /* domainSnapshotCreateXML */
+    qemuDomainSnapshotDumpXML, /* domainSnapshotDumpXML */
+    qemuDomainSnapshotNum, /* domainSnapshotNum */
+    qemuDomainSnapshotListNames, /* domainSnapshotListNames */
+    qemuDomainSnapshotLookupByName, /* domainSnapshotLookupByName */
+    qemuDomainHasCurrentSnapshot, /* domainHasCurrentSnapshot */
+    qemuDomainSnapshotCurrent, /* domainSnapshotCurrent */
+    qemuDomainRevertToSnapshot, /* domainRevertToSnapshot */
+    qemuDomainSnapshotDelete, /* domainSnapshotDelete */
 };
 
 
diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c
index 64779ac..01e3a46 100644
--- a/src/qemu/qemu_monitor.c
+++ b/src/qemu/qemu_monitor.c
@@ -1491,3 +1491,42 @@ int qemuMonitorSetDrivePassphrase(qemuMonitorPtr mon,
         ret = qemuMonitorTextSetDrivePassphrase(mon, alias, passphrase);
     return ret;
 }
+
+int qemuMonitorCreateSnapshot(qemuMonitorPtr mon, const char *name)
+{
+    int ret;
+
+    DEBUG("mon=%p, name=%s",mon,name);
+
+    if (mon->json)
+        ret = qemuMonitorJSONCreateSnapshot(mon, name);
+    else
+        ret = qemuMonitorTextCreateSnapshot(mon, name);
+    return ret;
+}
+
+int qemuMonitorLoadSnapshot(qemuMonitorPtr mon, const char *name)
+{
+    int ret;
+
+    DEBUG("mon=%p, name=%s",mon,name);
+
+    if (mon->json)
+        ret = qemuMonitorJSONLoadSnapshot(mon, name);
+    else
+        ret = qemuMonitorTextLoadSnapshot(mon, name);
+    return ret;
+}
+
+int qemuMonitorDeleteSnapshot(qemuMonitorPtr mon, const char *name)
+{
+    int ret;
+
+    DEBUG("mon=%p, name=%s",mon,name);
+
+    if (mon->json)
+        ret = qemuMonitorJSONDeleteSnapshot(mon, name);
+    else
+        ret = qemuMonitorTextDeleteSnapshot(mon, name);
+    return ret;
+}
diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h
index 07773bd..21b8989 100644
--- a/src/qemu/qemu_monitor.h
+++ b/src/qemu/qemu_monitor.h
@@ -344,4 +344,8 @@ int qemuMonitorSetDrivePassphrase(qemuMonitorPtr mon,
                                   const char *alias,
                                   const char *passphrase);
 
+int qemuMonitorCreateSnapshot(qemuMonitorPtr mon, const char *name);
+int qemuMonitorLoadSnapshot(qemuMonitorPtr mon, const char *name);
+int qemuMonitorDeleteSnapshot(qemuMonitorPtr mon, const char *name);
+
 #endif /* QEMU_MONITOR_H */
diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c
index eac3aca..1b0ecdf 100644
--- a/src/qemu/qemu_monitor_json.c
+++ b/src/qemu/qemu_monitor_json.c
@@ -2156,3 +2156,69 @@ int qemuMonitorJSONSetDrivePassphrase(qemuMonitorPtr mon,
     virJSONValueFree(reply);
     return ret;
 }
+
+int qemuMonitorJSONCreateSnapshot(qemuMonitorPtr mon, const char *name)
+{
+    int ret;
+    virJSONValuePtr cmd;
+    virJSONValuePtr reply = NULL;
+
+    cmd = qemuMonitorJSONMakeCommand("savevm",
+                                     "s:name", name,
+                                     NULL);
+    if (!cmd)
+        return -1;
+
+    ret = qemuMonitorJSONCommand(mon, cmd, &reply);
+
+    if (ret == 0)
+        ret = qemuMonitorJSONCheckError(cmd, reply);
+
+    virJSONValueFree(cmd);
+    virJSONValueFree(reply);
+    return ret;
+}
+
+int qemuMonitorJSONLoadSnapshot(qemuMonitorPtr mon, const char *name)
+{
+    int ret;
+    virJSONValuePtr cmd;
+    virJSONValuePtr reply = NULL;
+
+    cmd = qemuMonitorJSONMakeCommand("loadvm",
+                                     "s:name", name,
+                                     NULL);
+    if (!cmd)
+        return -1;
+
+    ret = qemuMonitorJSONCommand(mon, cmd, &reply);
+
+    if (ret == 0)
+        ret = qemuMonitorJSONCheckError(cmd, reply);
+
+    virJSONValueFree(cmd);
+    virJSONValueFree(reply);
+    return ret;
+}
+
+int qemuMonitorJSONDeleteSnapshot(qemuMonitorPtr mon, const char *name)
+{
+    int ret;
+    virJSONValuePtr cmd;
+    virJSONValuePtr reply = NULL;
+
+    cmd = qemuMonitorJSONMakeCommand("delvm",
+                                     "s:name", name,
+                                     NULL);
+    if (!cmd)
+        return -1;
+
+    ret = qemuMonitorJSONCommand(mon, cmd, &reply);
+
+    if (ret == 0)
+        ret = qemuMonitorJSONCheckError(cmd, reply);
+
+    virJSONValueFree(cmd);
+    virJSONValueFree(reply);
+    return ret;
+}
diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h
index fc05153..e7baf84 100644
--- a/src/qemu/qemu_monitor_json.h
+++ b/src/qemu/qemu_monitor_json.h
@@ -175,4 +175,8 @@ int qemuMonitorJSONSetDrivePassphrase(qemuMonitorPtr mon,
                                       const char *alias,
                                       const char *passphrase);
 
+int qemuMonitorJSONCreateSnapshot(qemuMonitorPtr mon, const char *name);
+int qemuMonitorJSONLoadSnapshot(qemuMonitorPtr mon, const char *name);
+int qemuMonitorJSONDeleteSnapshot(qemuMonitorPtr mon, const char *name);
+
 #endif /* QEMU_MONITOR_JSON_H */
diff --git a/src/qemu/qemu_monitor_text.c b/src/qemu/qemu_monitor_text.c
index a199de7..e057bbe 100644
--- a/src/qemu/qemu_monitor_text.c
+++ b/src/qemu/qemu_monitor_text.c
@@ -2290,3 +2290,144 @@ cleanup:
     VIR_FREE(safe_str);
     return ret;
 }
+
+int qemuMonitorTextCreateSnapshot(qemuMonitorPtr mon, const char *name)
+{
+    char *cmd;
+    char *reply = NULL;
+    int ret = -1;
+
+    if (virAsprintf(&cmd, "savevm \"%s\"", name) < 0) {
+        virReportOOMError();
+        return -1;
+    }
+
+    if (qemuMonitorCommand(mon, cmd, &reply)) {
+        qemuReportError(VIR_ERR_OPERATION_FAILED,
+                        _("failed to take snapshot using command '%s'"), cmd);
+        goto cleanup;
+    }
+
+    if (strstr(reply, "Error while creating snapshot") != NULL) {
+        qemuReportError(VIR_ERR_OPERATION_FAILED,
+                        _("Failed to take snapshot: %s"), reply);
+        goto cleanup;
+    }
+    else if (strstr(reply, "No block device can accept snapshots") != NULL) {
+        qemuReportError(VIR_ERR_OPERATION_INVALID, "%s",
+                        _("this domain does not have a device to take snapshots"));
+        goto cleanup;
+    }
+    else if (strstr(reply, "Could not open VM state file") != NULL) {
+        qemuReportError(VIR_ERR_OPERATION_FAILED, "%s", reply);
+        goto cleanup;
+    }
+    else if (strstr(reply, "Error") != NULL
+             && strstr(reply, "while writing VM") != NULL) {
+        qemuReportError(VIR_ERR_OPERATION_FAILED, "%s", reply);
+        goto cleanup;
+    }
+
+    ret = 0;
+
+cleanup:
+    VIR_FREE(cmd);
+    VIR_FREE(reply);
+    return ret;
+}
+
+int qemuMonitorTextLoadSnapshot(qemuMonitorPtr mon, const char *name)
+{
+    char *cmd;
+    char *reply = NULL;
+    int ret = -1;
+
+    if (virAsprintf(&cmd, "loadvm \"%s\"", name) < 0) {
+        virReportOOMError();
+        return -1;
+    }
+
+    if (qemuMonitorCommand(mon, cmd, &reply)) {
+        qemuReportError(VIR_ERR_OPERATION_FAILED,
+                         _("failed to restore snapshot using command '%s'"),
+                         cmd);
+        goto cleanup;
+    }
+
+    if (strstr(reply, "No block device supports snapshots") != NULL) {
+        qemuReportError(VIR_ERR_OPERATION_INVALID, "%s",
+                        _("this domain does not have a device to load snapshots"));
+        goto cleanup;
+    }
+    else if (strstr(reply, "Could not find snapshot") != NULL) {
+        qemuReportError(VIR_ERR_OPERATION_INVALID,
+                         _("the snapshot '%s' does not exist, and was not loaded"),
+                         name);
+        goto cleanup;
+    }
+    else if (strstr(reply, "Snapshots not supported on device") != NULL) {
+        qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", reply);
+        goto cleanup;
+    }
+    else if (strstr(reply, "Could not open VM state file") != NULL) {
+        qemuReportError(VIR_ERR_OPERATION_FAILED, "%s", reply);
+        goto cleanup;
+    }
+    else if (strstr(reply, "Error") != NULL
+             && strstr(reply, "while loading VM state") != NULL) {
+        qemuReportError(VIR_ERR_OPERATION_FAILED, "%s", reply);
+        goto cleanup;
+    }
+    else if (strstr(reply, "Error") != NULL
+             && strstr(reply, "while activating snapshot on") != NULL) {
+        qemuReportError(VIR_ERR_OPERATION_FAILED, "%s", reply);
+        goto cleanup;
+    }
+
+    ret = 0;
+
+cleanup:
+    VIR_FREE(cmd);
+    VIR_FREE(reply);
+    return ret;
+}
+
+int qemuMonitorTextDeleteSnapshot(qemuMonitorPtr mon, const char *name)
+{
+    char *cmd;
+    char *reply = NULL;
+    int ret = -1;
+
+    if (virAsprintf(&cmd, "delvm \"%s\"", name) < 0) {
+        virReportOOMError();
+        return -1;
+    }
+    if (qemuMonitorCommand(mon, cmd, &reply)) {
+        qemuReportError(VIR_ERR_OPERATION_FAILED,
+                         _("failed to delete snapshot using command '%s'"),
+                         cmd);
+        goto cleanup;
+    }
+
+    if (strstr(reply, "No block device supports snapshots") != NULL) {
+        qemuReportError(VIR_ERR_OPERATION_INVALID, "%s",
+                        _("this domain does not have a device to delete snapshots"));
+        goto cleanup;
+    }
+    else if (strstr(reply, "Snapshots not supported on device") != NULL) {
+        qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", reply);
+        goto cleanup;
+    }
+    else if (strstr(reply, "Error") != NULL
+             && strstr(reply, "while deleting snapshot") != NULL) {
+        qemuReportError(VIR_ERR_OPERATION_FAILED, "%s", reply);
+        goto cleanup;
+    }
+
+    ret = 0;
+
+cleanup:
+    VIR_FREE(cmd);
+    VIR_FREE(reply);
+    return ret;
+}
diff --git a/src/qemu/qemu_monitor_text.h b/src/qemu/qemu_monitor_text.h
index 4e1939c..fb7d08b 100644
--- a/src/qemu/qemu_monitor_text.h
+++ b/src/qemu/qemu_monitor_text.h
@@ -177,4 +177,8 @@ int qemuMonitorTextSetDrivePassphrase(qemuMonitorPtr mon,
                                       const char *alias,
                                       const char *passphrase);
 
+int qemuMonitorTextCreateSnapshot(qemuMonitorPtr mon, const char *name);
+int qemuMonitorTextLoadSnapshot(qemuMonitorPtr mon, const char *name);
+int qemuMonitorTextDeleteSnapshot(qemuMonitorPtr mon, const char *name);
+
 #endif /* QEMU_MONITOR_TEXT_H */
diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c
index c98de19..9e4d5bf 100644
--- a/tests/qemuxml2argvtest.c
+++ b/tests/qemuxml2argvtest.c
@@ -83,7 +83,7 @@ static int testCompareXMLToArgvFiles(const char *xml,
     if (qemudBuildCommandLine(conn, &driver,
                               vmdef, &monitor_chr, 0, flags,
                               &argv, &qenv,
-                              NULL, NULL, migrateFrom) < 0)
+                              NULL, NULL, migrateFrom, NULL) < 0)
         goto fail;
 
     len = 1; /* for trailing newline */
-- 
1.6.6.1




More information about the libvir-list mailing list