diff --git a/docs/apibuild.py b/docs/apibuild.py
index 3f0ecd939d..ae84be974e 100755
--- a/docs/apibuild.py
+++ b/docs/apibuild.py
@@ -1759,6 +1759,7 @@ class CParser:
"virDomainSetMaxMemory" : (False, ("memory")),
"virDomainSetMemory" : (False, ("memory")),
"virDomainSetMemoryFlags" : (False, ("memory")),
+ "virDomainBlockCommit" : (False, ("bandwidth")),
"virDomainBlockJobSetSpeed" : (False, ("bandwidth")),
"virDomainBlockPull" : (False, ("bandwidth")),
"virDomainBlockRebase" : (False, ("bandwidth")),
diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in
index b12f7e3e49..d9bfb6e76d 100644
--- a/include/libvirt/libvirt.h.in
+++ b/include/libvirt/libvirt.h.in
@@ -2062,11 +2062,14 @@ int virDomainUpdateDeviceFlags(virDomainPtr domain,
* virDomainBlockRebase without flags), job ends on completion
* VIR_DOMAIN_BLOCK_JOB_TYPE_COPY: Block Copy (virDomainBlockRebase with
* flags), job exists as long as mirroring is active
+ * VIR_DOMAIN_BLOCK_JOB_TYPE_COMMIT: Block Commit (virDomainBlockCommit),
+ * job ends on completion
*/
typedef enum {
VIR_DOMAIN_BLOCK_JOB_TYPE_UNKNOWN = 0,
VIR_DOMAIN_BLOCK_JOB_TYPE_PULL = 1,
VIR_DOMAIN_BLOCK_JOB_TYPE_COPY = 2,
+ VIR_DOMAIN_BLOCK_JOB_TYPE_COMMIT = 3,
#ifdef VIR_ENUM_SENTINELS
VIR_DOMAIN_BLOCK_JOB_TYPE_LAST
@@ -2131,6 +2134,23 @@ int virDomainBlockRebase(virDomainPtr dom, const char *disk,
const char *base, unsigned long bandwidth,
unsigned int flags);
+/**
+ * virDomainBlockCommitFlags:
+ *
+ * Flags available for virDomainBlockCommit().
+ */
+typedef enum {
+ VIR_DOMAIN_BLOCK_COMMIT_SHALLOW = 1 << 0, /* NULL base means next backing
+ file, not whole chain */
+ VIR_DOMAIN_BLOCK_COMMIT_DELETE = 1 << 1, /* Delete any files that are now
+ invalid after their contents
+ have been committed */
+} virDomainBlockCommitFlags;
+
+int virDomainBlockCommit(virDomainPtr dom, const char *disk, const char *base,
+ const char *top, unsigned long bandwidth,
+ unsigned int flags);
+
/* Block I/O throttling support */
diff --git a/src/driver.h b/src/driver.h
index bb470fea09..063bbbfaa0 100644
--- a/src/driver.h
+++ b/src/driver.h
@@ -832,6 +832,10 @@ typedef int
(*virDrvDomainBlockRebase)(virDomainPtr dom, const char *path,
const char *base, unsigned long bandwidth,
unsigned int flags);
+typedef int
+ (*virDrvDomainBlockCommit)(virDomainPtr dom, const char *disk,
+ const char *base, const char *top,
+ unsigned long bandwidth, unsigned int flags);
typedef int
(*virDrvSetKeepAlive)(virConnectPtr conn,
@@ -1071,6 +1075,7 @@ struct _virDriver {
virDrvDomainBlockJobSetSpeed domainBlockJobSetSpeed;
virDrvDomainBlockPull domainBlockPull;
virDrvDomainBlockRebase domainBlockRebase;
+ virDrvDomainBlockCommit domainBlockCommit;
virDrvSetKeepAlive setKeepAlive;
virDrvConnectIsAlive isAlive;
virDrvNodeSuspendForDuration nodeSuspendForDuration;
diff --git a/src/libvirt.c b/src/libvirt.c
index dc8f4e481b..5438fe4cd1 100644
--- a/src/libvirt.c
+++ b/src/libvirt.c
@@ -19027,7 +19027,8 @@ error:
* image. This function pulls data for the entire device in the background.
* Progress of the operation can be checked with virDomainGetBlockJobInfo() and
* the operation can be aborted with virDomainBlockJobAbort(). When finished,
- * an asynchronous event is raised to indicate the final status.
+ * an asynchronous event is raised to indicate the final status. To move
+ * data in the opposite direction, see virDomainBlockCommit().
*
* The @disk parameter is either an unambiguous source name of the
* block device (the sub-element, such as
@@ -19170,7 +19171,7 @@ int virDomainBlockRebase(virDomainPtr dom, const char *disk,
{
virConnectPtr conn;
- VIR_DOMAIN_DEBUG(dom, "disk=%s, base=%s bandwidth=%lu, flags=%x",
+ VIR_DOMAIN_DEBUG(dom, "disk=%s, base=%s, bandwidth=%lu, flags=%x",
disk, NULLSTR(base), bandwidth, flags);
virResetLastError();
@@ -19217,6 +19218,115 @@ error:
}
+/**
+ * virDomainBlockCommit:
+ * @dom: pointer to domain object
+ * @disk: path to the block device, or device shorthand
+ * @base: path to backing file to merge into, or NULL for default
+ * @top: path to file within backing chain that contains data to be merged,
+ * or NULL to merge all possible data
+ * @bandwidth: (optional) specify commit bandwidth limit in MiB/s
+ * @flags: bitwise-OR of virDomainBlockCommitFlags
+ *
+ * Commit changes that were made to temporary top-level files within a disk
+ * image backing file chain into a lower-level base file. In other words,
+ * take all the difference between @base and @top, and update @base to contain
+ * that difference; after the commit, any portion of the chain that previously
+ * depended on @top will now depend on @base, and all files after @base up
+ * to and including @top will now be invalidated. A typical use of this
+ * command is to reduce the length of a backing file chain after taking an
+ * external disk snapshot. To move data in the opposite direction, see
+ * virDomainBlockPull().
+ *
+ * This command starts a long-running commit block job, whose status may
+ * be tracked by virDomainBlockJobInfo() with a job type of
+ * VIR_DOMAIN_BLOCK_JOB_TYPE_COMMIT, and the operation can be aborted with
+ * virDomainBlockJobAbort(). When finished, an asynchronous event is
+ * raised to indicate the final status, and the job no longer exists. If
+ * the job is aborted, it is up to the hypervisor whether starting a new
+ * job will resume from the same point, or start over.
+ *
+ * Be aware that this command may invalidate files even if it is aborted;
+ * the user is cautioned against relying on the contents of invalidated
+ * intermediate files such as @top without manually rebasing those files
+ * to use a backing file of a read-only copy of @base prior to the point
+ * where the commit operation was started (although such a rebase cannot
+ * be safely done until the commit has successfully completed). However,
+ * the domain itself will not have any issues; the active layer remains
+ * valid throughout the entire commit operation. As a convenience,
+ * if @flags contains VIR_DOMAIN_BLOCK_COMMIT_DELETE, this command will
+ * unlink all files that were invalidated, after the commit successfully
+ * completes.
+ *
+ * By default, if @base is NULL, the commit target will be the bottom of
+ * the backing chain; if @flags contains VIR_DOMAIN_BLOCK_COMMIT_SHALLOW,
+ * then the immediate backing file of @top will be used instead. If @top
+ * is NULL, the active image at the top of the chain will be used. Some
+ * hypervisors place restrictions on how much can be committed, and might
+ * fail if @base is not the immediate backing file of @top, or if @top is
+ * the active layer in use by a running domain, or if @top is not the
+ * top-most file; restrictions may differ for online vs. offline domains.
+ *
+ * The @disk parameter is either an unambiguous source name of the
+ * block device (the sub-element, such as
+ * "/path/to/image"), or the device target shorthand (the
+ * sub-element, such as "xvda"). Valid names
+ * can be found by calling virDomainGetXMLDesc() and inspecting
+ * elements within //domain/devices/disk.
+ *
+ * The maximum bandwidth (in MiB/s) that will be used to do the commit can be
+ * specified with the bandwidth parameter. If set to 0, libvirt will choose a
+ * suitable default. Some hypervisors do not support this feature and will
+ * return an error if bandwidth is not 0; in this case, it might still be
+ * possible for a later call to virDomainBlockJobSetSpeed() to succeed.
+ * The actual speed can be determined with virDomainGetBlockJobInfo().
+ *
+ * Returns 0 if the operation has started, -1 on failure.
+ */
+int virDomainBlockCommit(virDomainPtr dom, const char *disk,
+ const char *base, const char *top,
+ unsigned long bandwidth, unsigned int flags)
+{
+ virConnectPtr conn;
+
+ VIR_DOMAIN_DEBUG(dom, "disk=%s, base=%s, top=%s, bandwidth=%lu, flags=%x",
+ disk, NULLSTR(base), NULLSTR(top), bandwidth, flags);
+
+ virResetLastError();
+
+ if (!VIR_IS_CONNECTED_DOMAIN(dom)) {
+ virLibDomainError(VIR_ERR_INVALID_DOMAIN, __FUNCTION__);
+ virDispatchError(NULL);
+ return -1;
+ }
+ conn = dom->conn;
+
+ if (dom->conn->flags & VIR_CONNECT_RO) {
+ virLibDomainError(VIR_ERR_OPERATION_DENIED, __FUNCTION__);
+ goto error;
+ }
+
+ virCheckNonNullArgGoto(disk, error);
+ if (flags & VIR_DOMAIN_BLOCK_COMMIT_SHALLOW)
+ virCheckNullArgGoto(base, error);
+
+ if (conn->driver->domainBlockCommit) {
+ int ret;
+ ret = conn->driver->domainBlockCommit(dom, disk, base, top, bandwidth,
+ flags);
+ if (ret < 0)
+ goto error;
+ return ret;
+ }
+
+ virLibDomainError(VIR_ERR_NO_SUPPORT, __FUNCTION__);
+
+error:
+ virDispatchError(dom->conn);
+ return -1;
+}
+
+
/**
* virDomainOpenGraphics:
* @dom: pointer to domain object
diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms
index 28b92add3c..d965c7f2b8 100644
--- a/src/libvirt_public.syms
+++ b/src/libvirt_public.syms
@@ -562,6 +562,7 @@ LIBVIRT_0.10.2 {
virConnectListAllNWFilters;
virConnectListAllSecrets;
virConnectListAllStoragePools;
+ virDomainBlockCommit;
virNodeGetMemoryParameters;
virNodeSetMemoryParameters;
virStoragePoolListAllVolumes;