[libvirt] [PATCH 0/9] Storage volume wiping API

Here's the revised volume wiping API patchset (thanks to Dan for the improved name). I'm sending a replacement patchset since the name change made for a very large and messy incremental. This set contains everybody's feedback, which was all very helpful. Many thanks. Dave David Allan (9): Add public API for volume wiping Define the internal driver API for vol wiping Add vol wiping to ESX storage driver struct Implement the public API for vol wiping Define wire protocol format for vol wiping Implement RPC client for vol wiping Implement the remote dispatch bits of vol wiping Simplified version of volume wiping based on feedback from the list. Virsh support for vol wiping daemon/remote.c | 32 +++++ daemon/remote_dispatch_args.h | 1 + daemon/remote_dispatch_prototypes.h | 8 ++ daemon/remote_dispatch_table.h | 5 + include/libvirt/libvirt.h.in | 2 + src/driver.h | 5 + src/esx/esx_storage_driver.c | 1 + src/libvirt.c | 47 ++++++++ src/libvirt_public.syms | 1 + src/remote/remote_driver.c | 27 ++++ src/remote/remote_protocol.c | 11 ++ src/remote/remote_protocol.h | 9 ++ src/remote/remote_protocol.x | 8 +- src/storage/storage_driver.c | 224 +++++++++++++++++++++++++++++++++++ tools/virsh.c | 42 +++++++ 15 files changed, 422 insertions(+), 1 deletions(-)

--- include/libvirt/libvirt.h.in | 2 ++ src/libvirt_public.syms | 1 + 2 files changed, 3 insertions(+), 0 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index 0d1b5b5..1430a95 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -1224,6 +1224,8 @@ virStorageVolPtr virStorageVolCreateXMLFrom (virStoragePoolPtr pool, unsigned int flags); int virStorageVolDelete (virStorageVolPtr vol, unsigned int flags); +int virStorageVolWipe (virStorageVolPtr vol, + unsigned int flags); int virStorageVolRef (virStorageVolPtr vol); int virStorageVolFree (virStorageVolPtr vol); diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index 64e7505..ebf13e6 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -356,6 +356,7 @@ LIBVIRT_0.7.7 { virConnectBaselineCPU; virDomainGetJobInfo; virDomainAbortJob; + virStorageVolWipe; } LIBVIRT_0.7.5; # .... define new API here using predicted next version number .... -- 1.6.5.5

--- src/driver.h | 5 +++++ 1 files changed, 5 insertions(+), 0 deletions(-) diff --git a/src/driver.h b/src/driver.h index 7b7dfea..6e1403a 100644 --- a/src/driver.h +++ b/src/driver.h @@ -722,6 +722,10 @@ typedef int unsigned int flags); typedef int + (*virDrvStorageVolWipe) (virStorageVolPtr vol, + unsigned int flags); + +typedef int (*virDrvStorageVolGetInfo) (virStorageVolPtr vol, virStorageVolInfoPtr info); typedef char * @@ -790,6 +794,7 @@ struct _virStorageDriver { virDrvStorageVolCreateXML volCreateXML; virDrvStorageVolCreateXMLFrom volCreateXMLFrom; virDrvStorageVolDelete volDelete; + virDrvStorageVolWipe volWipe; virDrvStorageVolGetInfo volGetInfo; virDrvStorageVolGetXMLDesc volGetXMLDesc; virDrvStorageVolGetPath volGetPath; -- 1.6.5.5

--- src/esx/esx_storage_driver.c | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diff --git a/src/esx/esx_storage_driver.c b/src/esx/esx_storage_driver.c index d09831a..3062473 100644 --- a/src/esx/esx_storage_driver.c +++ b/src/esx/esx_storage_driver.c @@ -101,6 +101,7 @@ static virStorageDriver esxStorageDriver = { NULL, /* volCreateXML */ NULL, /* volCreateXMLFrom */ NULL, /* volDelete */ + NULL, /* volWipe */ NULL, /* volGetInfo */ NULL, /* volGetXMLDesc */ NULL, /* volGetPath */ -- 1.6.5.5

--- src/libvirt.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 47 insertions(+), 0 deletions(-) diff --git a/src/libvirt.c b/src/libvirt.c index d9242bc..6abf74f 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -8468,6 +8468,53 @@ error: /** + * virStorageVolWipe: + * @vol: pointer to storage volume + * @flags: future flags, use 0 for now + * + * Ensure data previously on a volume is not accessible to future reads + * + * Returns 0 on success, or -1 on error + */ +int +virStorageVolWipe(virStorageVolPtr vol, + unsigned int flags) +{ + virConnectPtr conn; + VIR_DEBUG("vol=%p, flags=%u", vol, flags); + + virResetLastError(); + + if (!VIR_IS_CONNECTED_STORAGE_VOL(vol)) { + virLibStorageVolError(NULL, VIR_ERR_INVALID_STORAGE_VOL, __FUNCTION__); + virDispatchError(NULL); + return (-1); + } + + conn = vol->conn; + if (conn->flags & VIR_CONNECT_RO) { + virLibStorageVolError(vol, VIR_ERR_OPERATION_DENIED, __FUNCTION__); + goto error; + } + + if (conn->storageDriver && conn->storageDriver->volWipe) { + int ret; + ret = conn->storageDriver->volWipe(vol, flags); + if (ret < 0) { + goto error; + } + return ret; + } + + virLibConnError(conn, VIR_ERR_NO_SUPPORT, __FUNCTION__); + +error: + virDispatchError(vol->conn); + return -1; +} + + +/** * virStorageVolFree: * @vol: pointer to storage volume * -- 1.6.5.5

--- src/remote/remote_protocol.c | 11 +++++++++++ src/remote/remote_protocol.h | 9 +++++++++ src/remote/remote_protocol.x | 8 +++++++- 3 files changed, 27 insertions(+), 1 deletions(-) diff --git a/src/remote/remote_protocol.c b/src/remote/remote_protocol.c index 701acab..6bd9019 100644 --- a/src/remote/remote_protocol.c +++ b/src/remote/remote_protocol.c @@ -2286,6 +2286,17 @@ xdr_remote_storage_vol_delete_args (XDR *xdrs, remote_storage_vol_delete_args *o } bool_t +xdr_remote_storage_vol_wipe_args (XDR *xdrs, remote_storage_vol_wipe_args *objp) +{ + + if (!xdr_remote_nonnull_storage_vol (xdrs, &objp->vol)) + return FALSE; + if (!xdr_u_int (xdrs, &objp->flags)) + return FALSE; + return TRUE; +} + +bool_t xdr_remote_storage_vol_dump_xml_args (XDR *xdrs, remote_storage_vol_dump_xml_args *objp) { diff --git a/src/remote/remote_protocol.h b/src/remote/remote_protocol.h index e06d73f..1b606e9 100644 --- a/src/remote/remote_protocol.h +++ b/src/remote/remote_protocol.h @@ -1295,6 +1295,12 @@ struct remote_storage_vol_delete_args { }; typedef struct remote_storage_vol_delete_args remote_storage_vol_delete_args; +struct remote_storage_vol_wipe_args { + remote_nonnull_storage_vol vol; + u_int flags; +}; +typedef struct remote_storage_vol_wipe_args remote_storage_vol_wipe_args; + struct remote_storage_vol_dump_xml_args { remote_nonnull_storage_vol vol; u_int flags; @@ -1872,6 +1878,7 @@ enum remote_procedure { REMOTE_PROC_CPU_BASELINE = 162, REMOTE_PROC_DOMAIN_GET_JOB_INFO = 163, REMOTE_PROC_DOMAIN_ABORT_JOB = 164, + REMOTE_PROC_STORAGE_VOL_WIPE = 165, }; typedef enum remote_procedure remote_procedure; @@ -2110,6 +2117,7 @@ extern bool_t xdr_remote_storage_vol_create_xml_ret (XDR *, remote_storage_vol_ extern bool_t xdr_remote_storage_vol_create_xml_from_args (XDR *, remote_storage_vol_create_xml_from_args*); extern bool_t xdr_remote_storage_vol_create_xml_from_ret (XDR *, remote_storage_vol_create_xml_from_ret*); extern bool_t xdr_remote_storage_vol_delete_args (XDR *, remote_storage_vol_delete_args*); +extern bool_t xdr_remote_storage_vol_wipe_args (XDR *, remote_storage_vol_wipe_args*); extern bool_t xdr_remote_storage_vol_dump_xml_args (XDR *, remote_storage_vol_dump_xml_args*); extern bool_t xdr_remote_storage_vol_dump_xml_ret (XDR *, remote_storage_vol_dump_xml_ret*); extern bool_t xdr_remote_storage_vol_get_info_args (XDR *, remote_storage_vol_get_info_args*); @@ -2393,6 +2401,7 @@ extern bool_t xdr_remote_storage_vol_create_xml_ret (); extern bool_t xdr_remote_storage_vol_create_xml_from_args (); extern bool_t xdr_remote_storage_vol_create_xml_from_ret (); extern bool_t xdr_remote_storage_vol_delete_args (); +extern bool_t xdr_remote_storage_vol_wipe_args (); extern bool_t xdr_remote_storage_vol_dump_xml_args (); extern bool_t xdr_remote_storage_vol_dump_xml_ret (); extern bool_t xdr_remote_storage_vol_get_info_args (); diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index 5e33da5..868a2c2 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -1169,6 +1169,11 @@ struct remote_storage_vol_delete_args { unsigned flags; }; +struct remote_storage_vol_wipe_args { + remote_nonnull_storage_vol vol; + unsigned flags; +}; + struct remote_storage_vol_dump_xml_args { remote_nonnull_storage_vol vol; unsigned flags; @@ -1703,7 +1708,8 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_DETACH_DEVICE_FLAGS = 161, REMOTE_PROC_CPU_BASELINE = 162, REMOTE_PROC_DOMAIN_GET_JOB_INFO = 163, - REMOTE_PROC_DOMAIN_ABORT_JOB = 164 + REMOTE_PROC_DOMAIN_ABORT_JOB = 164, + REMOTE_PROC_STORAGE_VOL_WIPE = 165 /* * Notice how the entries are grouped in sets of 10 ? -- 1.6.5.5

--- src/remote/remote_driver.c | 27 +++++++++++++++++++++++++++ 1 files changed, 27 insertions(+), 0 deletions(-) diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index b7b2e09..9d28c84 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -5538,6 +5538,32 @@ done: } static int +remoteStorageVolWipe(virStorageVolPtr vol, + unsigned int flags) +{ + int rv = -1; + remote_storage_vol_wipe_args args; + struct private_data *priv = vol->conn->storagePrivateData; + + remoteDriverLock(priv); + + make_nonnull_storage_vol(&args.vol, vol); + args.flags = flags; + + if (call(vol->conn, priv, 0, REMOTE_PROC_STORAGE_VOL_WIPE, + (xdrproc_t) xdr_remote_storage_vol_wipe_args, (char *) &args, + (xdrproc_t) xdr_void, (char *) NULL) == -1) + goto done; + + rv = 0; + +done: + remoteDriverUnlock(priv); + return rv; +} + + +static int remoteStorageVolGetInfo (virStorageVolPtr vol, virStorageVolInfoPtr info) { int rv = -1; @@ -9202,6 +9228,7 @@ static virStorageDriver storage_driver = { .volCreateXML = remoteStorageVolCreateXML, .volCreateXMLFrom = remoteStorageVolCreateXMLFrom, .volDelete = remoteStorageVolDelete, + .volWipe = remoteStorageVolWipe, .volGetInfo = remoteStorageVolGetInfo, .volGetXMLDesc = remoteStorageVolDumpXML, .volGetPath = remoteStorageVolGetPath, -- 1.6.5.5

* I had to remove daemon/remote_dispatch* and do a make remote.c in daemon in order to get the dispatch files regenerated properly. The make was failing before I did that. --- daemon/remote.c | 32 ++++++++++++++++++++++++++++++++ daemon/remote_dispatch_args.h | 1 + daemon/remote_dispatch_prototypes.h | 8 ++++++++ daemon/remote_dispatch_table.h | 5 +++++ 4 files changed, 46 insertions(+), 0 deletions(-) diff --git a/daemon/remote.c b/daemon/remote.c index d4713b2..9548169 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -4273,6 +4273,38 @@ remoteDispatchStorageVolDelete (struct qemud_server *server ATTRIBUTE_UNUSED, } static int +remoteDispatchStorageVolWipe(struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_client *client ATTRIBUTE_UNUSED, + virConnectPtr conn, + remote_message_header *hdr ATTRIBUTE_UNUSED, + remote_error *rerr, + remote_storage_vol_wipe_args *args, + void *ret ATTRIBUTE_UNUSED) +{ + int retval = -1; + virStorageVolPtr vol; + + vol = get_nonnull_storage_vol(conn, args->vol); + if (vol == NULL) { + remoteDispatchConnError(rerr, conn); + goto out; + } + + if (virStorageVolWipe(vol, args->flags) == -1) { + remoteDispatchConnError(rerr, conn); + goto out; + } + + retval = 0; + +out: + if (vol != NULL) { + virStorageVolFree(vol); + } + return retval; +} + +static int remoteDispatchStorageVolGetInfo (struct qemud_server *server ATTRIBUTE_UNUSED, struct qemud_client *client ATTRIBUTE_UNUSED, virConnectPtr conn, diff --git a/daemon/remote_dispatch_args.h b/daemon/remote_dispatch_args.h index f97155b..32a6d78 100644 --- a/daemon/remote_dispatch_args.h +++ b/daemon/remote_dispatch_args.h @@ -140,3 +140,4 @@ remote_cpu_baseline_args val_remote_cpu_baseline_args; remote_domain_get_job_info_args val_remote_domain_get_job_info_args; remote_domain_abort_job_args val_remote_domain_abort_job_args; + remote_storage_vol_wipe_args val_remote_storage_vol_wipe_args; diff --git a/daemon/remote_dispatch_prototypes.h b/daemon/remote_dispatch_prototypes.h index b81c8c3..4c944f6 100644 --- a/daemon/remote_dispatch_prototypes.h +++ b/daemon/remote_dispatch_prototypes.h @@ -1298,6 +1298,14 @@ static int remoteDispatchStorageVolLookupByPath( remote_error *err, remote_storage_vol_lookup_by_path_args *args, remote_storage_vol_lookup_by_path_ret *ret); +static int remoteDispatchStorageVolWipe( + struct qemud_server *server, + struct qemud_client *client, + virConnectPtr conn, + remote_message_header *hdr, + remote_error *err, + remote_storage_vol_wipe_args *args, + void *ret); static int remoteDispatchSupportsFeature( struct qemud_server *server, struct qemud_client *client, diff --git a/daemon/remote_dispatch_table.h b/daemon/remote_dispatch_table.h index 5ad6bff..28e7d9a 100644 --- a/daemon/remote_dispatch_table.h +++ b/daemon/remote_dispatch_table.h @@ -827,3 +827,8 @@ .args_filter = (xdrproc_t) xdr_remote_domain_abort_job_args, .ret_filter = (xdrproc_t) xdr_void, }, +{ /* StorageVolWipe => 165 */ + .fn = (dispatch_fn) remoteDispatchStorageVolWipe, + .args_filter = (xdrproc_t) xdr_remote_storage_vol_wipe_args, + .ret_filter = (xdrproc_t) xdr_void, +}, -- 1.6.5.5

--- src/storage/storage_driver.c | 224 ++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 224 insertions(+), 0 deletions(-) diff --git a/src/storage/storage_driver.c b/src/storage/storage_driver.c index f3be6de..b0afb05 100644 --- a/src/storage/storage_driver.c +++ b/src/storage/storage_driver.c @@ -26,6 +26,9 @@ #include <stdio.h> #include <unistd.h> #include <sys/types.h> +#include <sys/param.h> +#include <fcntl.h> + #if HAVE_PWD_H #include <pwd.h> #endif @@ -1518,6 +1521,226 @@ cleanup: return ret; } + +/* If the volume we're wiping is already a sparse file, we simply + * truncate and extend it to its original size, filling it with + * zeroes. This behavior is guaranteed by POSIX: + * + * http://www.opengroup.org/onlinepubs/9699919799/functions/ftruncate.html + * + * If fildes refers to a regular file, the ftruncate() function shall + * cause the size of the file to be truncated to length. If the size + * of the file previously exceeded length, the extra data shall no + * longer be available to reads on the file. If the file previously + * was smaller than this size, ftruncate() shall increase the size of + * the file. If the file size is increased, the extended area shall + * appear as if it were zero-filled. + */ +static int +storageVolumeZeroSparseFile(virStorageVolDefPtr vol, + off_t size, + int fd) +{ + int ret = -1; + char errbuf[64]; + + ret = ftruncate(fd, 0); + if (ret == -1) { + virReportSystemError(ret, + _("Failed to truncate volume with " + "path '%s' to 0 bytes: '%s'"), + vol->target.path, + virStrerror(errno, errbuf, sizeof(errbuf))); + goto out; + } + + ret = ftruncate(fd, size); + if (ret == -1) { + virReportSystemError(ret, + _("Failed to truncate volume with " + "path '%s' to %ju bytes: '%s'\n"), + vol->target.path, (intmax_t)size, + virStrerror(errno, errbuf, sizeof(errbuf))); + } + +out: + return ret; +} + + +static int +storageWipeExtent(virStorageVolDefPtr vol, + int fd, + off_t extent_start, + off_t extent_length, + char *writebuf, + size_t writebuf_length, + size_t *bytes_wiped) +{ + int ret = -1, written = 0; + off_t remaining = 0; + size_t write_size = 0; + char errbuf[64]; + + VIR_DEBUG("extent logical start: %ju len: %ju", + (intmax_t)extent_start, (intmax_t)extent_length); + + if ((ret = lseek(fd, extent_start, SEEK_SET)) < 0) { + virReportSystemError(ret, + _("Failed to seek to position %ju in volume " + "with path '%s': '%s'"), + (intmax_t)extent_start, vol->target.path, + virStrerror(errno, errbuf, sizeof(errbuf))); + goto out; + } + + remaining = extent_length; + while (remaining > 0) { + + write_size = (writebuf_length < remaining) ? writebuf_length : remaining; + written = safewrite(fd, writebuf, write_size); + if (written < 0) { + virReportSystemError(written, + _("Failed to write to storage volume with " + "path '%s': '%s' " + "(attempted to write %zu bytes)"), + vol->target.path, + virStrerror(errno, errbuf, sizeof(errbuf)), + write_size); + goto out; + } + + *bytes_wiped += written; + remaining -= written; + } + + VIR_DEBUG("Wrote %zu bytes to volume with path '%s'", + *bytes_wiped, vol->target.path); + + ret = 0; + +out: + return ret; +} + + +static int +storageVolumeWipeInternal(virStorageVolDefPtr def) +{ + int ret = -1, fd = -1; + char errbuf[64]; + struct stat st; + char *writebuf = NULL; + size_t bytes_wiped = 0; + + VIR_DEBUG("Wiping volume with path '%s'", def->target.path); + + fd = open(def->target.path, O_RDWR); + if (fd == -1) { + VIR_ERROR("Failed to open storage volume with path '%s': '%s'", + def->target.path, + virStrerror(errno, errbuf, sizeof(errbuf))); + goto out; + } + + if (fstat(fd, &st) == -1) { + VIR_ERROR("Failed to stat storage volume with path '%s': '%s'", + def->target.path, + virStrerror(errno, errbuf, sizeof(errbuf))); + goto out; + } + + if (S_ISREG(st.st_mode) && st.st_blocks < (st.st_size / DEV_BSIZE)) { + ret = storageVolumeZeroSparseFile(def, st.st_size, fd); + } else { + + if (VIR_ALLOC_N(writebuf, st.st_blksize) != 0) { + virReportOOMError(); + goto out; + } + + ret = storageWipeExtent(def, + fd, + 0, + def->allocation, + writebuf, + st.st_blksize, + &bytes_wiped); + } + +out: + VIR_FREE(writebuf); + + if (fd != -1) { + close(fd); + } + + return ret; +} + + +static int +storageVolumeWipe(virStorageVolPtr obj, + unsigned int flags) +{ + virStorageDriverStatePtr driver = obj->conn->storagePrivateData; + virStoragePoolObjPtr pool = NULL; + virStorageVolDefPtr vol = NULL; + int ret = -1; + + if (flags != 0) { + virStorageReportError(VIR_ERR_INVALID_ARG, + _("Unsupported flags (0x%x) passed to '%s'"), flags, __FUNCTION__); + goto out; + } + + storageDriverLock(driver); + pool = virStoragePoolObjFindByName(&driver->pools, obj->pool); + storageDriverUnlock(driver); + + if (!pool) { + virStorageReportError(VIR_ERR_INVALID_STORAGE_POOL, + "%s", _("no storage pool with matching uuid")); + goto out; + } + + if (!virStoragePoolObjIsActive(pool)) { + virStorageReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("storage pool is not active")); + goto out; + } + + vol = virStorageVolDefFindByName(pool, obj->name); + + if (vol == NULL) { + virStorageReportError(VIR_ERR_NO_STORAGE_VOL, + _("no storage vol with matching name '%s'"), + obj->name); + goto out; + } + + if (vol->building) { + virStorageReportError(VIR_ERR_INTERNAL_ERROR, + _("volume '%s' is still being allocated."), + vol->name); + goto out; + } + + if (storageVolumeWipeInternal(vol) == -1) { + goto out; + } + + ret = 0; + +out: + if (pool) { + virStoragePoolObjUnlock(pool); + } + + return ret; + +} + static int storageVolumeDelete(virStorageVolPtr obj, unsigned int flags) { @@ -1775,6 +1998,7 @@ static virStorageDriver storageDriver = { .volCreateXML = storageVolumeCreateXML, .volCreateXMLFrom = storageVolumeCreateXMLFrom, .volDelete = storageVolumeDelete, + .volWipe = storageVolumeWipe, .volGetInfo = storageVolumeGetInfo, .volGetXMLDesc = storageVolumeGetXMLDesc, .volGetPath = storageVolumeGetPath, -- 1.6.5.5

--- tools/virsh.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 42 insertions(+), 0 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index 65487ed..00f412e 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -5221,6 +5221,47 @@ cmdVolDelete(vshControl *ctl, const vshCmd *cmd) /* + * "vol-wipe" command + */ +static const vshCmdInfo info_vol_wipe[] = { + {"help", gettext_noop("wipe a vol")}, + {"desc", gettext_noop("Ensure data previously on a volume is not accessible to future reads")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_vol_wipe[] = { + {"pool", VSH_OT_STRING, 0, gettext_noop("pool name or uuid")}, + {"vol", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("vol name, key or path")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdVolWipe(vshControl *ctl, const vshCmd *cmd) +{ + virStorageVolPtr vol; + int ret = TRUE; + char *name; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + + if (!(vol = vshCommandOptVol(ctl, cmd, "vol", "pool", &name))) { + return FALSE; + } + + if (virStorageVolWipe(vol, 0) == 0) { + vshPrint(ctl, _("Vol %s wiped\n"), name); + } else { + vshError(ctl, _("Failed to wipe vol %s"), name); + ret = FALSE; + } + + virStorageVolFree(vol); + return ret; +} + + +/* * "vol-info" command */ static const vshCmdInfo info_vol_info[] = { @@ -7758,6 +7799,7 @@ static const vshCmdDef commands[] = { {"vol-create-as", cmdVolCreateAs, opts_vol_create_as, info_vol_create_as}, {"vol-clone", cmdVolClone, opts_vol_clone, info_vol_clone}, {"vol-delete", cmdVolDelete, opts_vol_delete, info_vol_delete}, + {"vol-wipe", cmdVolWipe, opts_vol_wipe, info_vol_wipe}, {"vol-dumpxml", cmdVolDumpXML, opts_vol_dumpxml, info_vol_dumpxml}, {"vol-info", cmdVolInfo, opts_vol_info, info_vol_info}, {"vol-list", cmdVolList, opts_vol_list, info_vol_list}, -- 1.6.5.5
participants (1)
-
David Allan