Richard W.M. Jones
2014-Mar-11 23:13 UTC
[Libguestfs] [PATCH v2 00/18] Add discard support.
This still isn't working at the moment. See: http://marc.info/?t=139457409300003&r=1&w=2 This set of patches: - Adds new APIs to support discard in libguestfs. - Adds discard support to virt-format. - Adds discard support to virt-sysprep. - Implements virt-sparsify --in-place. Rich.
Richard W.M. Jones
2014-Mar-11 23:13 UTC
[Libguestfs] [PATCH v2 01/18] daemon: fstrim: When debugging, capture and print fstrim -v output.
---
daemon/fstrim.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/daemon/fstrim.c b/daemon/fstrim.c
index cb844d1..bf9dad8 100644
--- a/daemon/fstrim.c
+++ b/daemon/fstrim.c
@@ -46,7 +46,7 @@ do_fstrim (const char *path,
const char *argv[MAX_ARGS];
size_t i = 0;
char offset_s[64], length_s[64], mfe_s[64];
- CLEANUP_FREE char *err = NULL;
+ CLEANUP_FREE char *out = NULL, *err = NULL;
int r;
ADD_ARG (argv, i, str_fstrim);
@@ -84,14 +84,21 @@ do_fstrim (const char *path,
ADD_ARG (argv, i, mfe_s);
}
+ /* When running in debug mode, use -v, capture stdout and print it below. */
+ if (verbose)
+ ADD_ARG (argv, i, "-v");
+
ADD_ARG (argv, i, path);
ADD_ARG (argv, i, NULL);
- r = commandv (NULL, &err, argv);
+ r = commandv (&out, &err, argv);
if (r == -1) {
reply_with_error ("%s", err);
return -1;
}
+ if (verbose)
+ fprintf (stderr, "%s\n", out);
+
return 0;
}
--
1.8.5.3
Richard W.M. Jones
2014-Mar-11 23:13 UTC
[Libguestfs] [PATCH v2 02/18] launch: libvirt: Move the is_kvm flag (derived from libvirt capabilities) to backend data struct.
This is just rearranging the data between structs. There should be no
functional change.
---
src/launch-libvirt.c | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/launch-libvirt.c b/src/launch-libvirt.c
index fd6e6d2..da1d665 100644
--- a/src/launch-libvirt.c
+++ b/src/launch-libvirt.c
@@ -106,6 +106,7 @@ struct backend_libvirt_data {
char *selinux_imagelabel;
bool selinux_norelabel_disks;
char name[DOMAIN_NAME_LEN]; /* random name */
+ bool is_kvm; /* false = qemu, true = kvm (from
capabilities)*/
};
/* Parameters passed to construct_libvirt_xml and subfunctions. We
@@ -122,11 +123,10 @@ struct libvirt_xml_params {
char guestfsd_path[UNIX_PATH_MAX]; /* paths to sockets */
char console_path[UNIX_PATH_MAX];
bool enable_svirt; /* false if we decided to disable sVirt */
- bool is_kvm; /* false = qemu, true = kvm */
bool current_proc_is_root; /* true = euid is root */
};
-static int parse_capabilities (guestfs_h *g, const char *capabilities_xml,
struct libvirt_xml_params *params);
+static int parse_capabilities (guestfs_h *g, const char *capabilities_xml,
struct backend_libvirt_data *data);
static xmlChar *construct_libvirt_xml (guestfs_h *g, const struct
libvirt_xml_params *params);
static void debug_appliance_permissions (guestfs_h *g);
static void debug_socket_permissions (guestfs_h *g);
@@ -299,7 +299,7 @@ launch_libvirt (guestfs_h *g, void *datav, const char
*libvirt_uri)
if (g->verbose)
guestfs___print_timestamped_message (g, "parsing capabilities
XML");
- if (parse_capabilities (g, capabilities_xml, ¶ms) == -1)
+ if (parse_capabilities (g, capabilities_xml, data) == -1)
goto cleanup;
/* Locate and/or build the appliance. */
@@ -575,7 +575,7 @@ launch_libvirt (guestfs_h *g, void *datav, const char
*libvirt_uri)
static int
parse_capabilities (guestfs_h *g, const char *capabilities_xml,
- struct libvirt_xml_params *params)
+ struct backend_libvirt_data *data)
{
CLEANUP_XMLFREEDOC xmlDocPtr doc = NULL;
CLEANUP_XMLXPATHFREECONTEXT xmlXPathContextPtr xpathCtx = NULL;
@@ -654,9 +654,9 @@ parse_capabilities (guestfs_h *g, const char
*capabilities_xml,
force_tcg = guestfs___get_backend_setting_bool (g, "force_tcg");
if (!force_tcg)
- params->is_kvm = seen_kvm;
+ data->is_kvm = seen_kvm;
else
- params->is_kvm = 0;
+ data->is_kvm = 0;
return 0;
}
@@ -929,7 +929,7 @@ construct_libvirt_xml_domain (guestfs_h *g,
xmlTextWriterPtr xo)
{
start_element ("domain") {
- attribute ("type", params->is_kvm ? "kvm" :
"qemu");
+ attribute ("type", params->data->is_kvm ? "kvm" :
"qemu");
attribute_ns ("xmlns", "qemu", NULL,
"http://libvirt.org/schemas/domain/qemu/1.0");
@@ -987,7 +987,7 @@ construct_libvirt_xml_cpu (guestfs_h *g,
* Only do this with KVM. It is broken in subtle ways on TCG, and
* fairly pointless anyway.
*/
- if (params->is_kvm) {
+ if (params->data->is_kvm) {
start_element ("cpu") {
attribute ("mode", "host-passthrough");
start_element ("model") {
@@ -1039,7 +1039,7 @@ construct_libvirt_xml_boot (guestfs_h *g,
/* Linux kernel command line. */
flags = 0;
- if (!params->is_kvm)
+ if (!params->data->is_kvm)
flags |= APPLIANCE_COMMAND_LINE_IS_TCG;
cmdline = guestfs___appliance_command_line (g, params->appliance_dev,
flags);
--
1.8.5.3
Richard W.M. Jones
2014-Mar-11 23:13 UTC
[Libguestfs] [PATCH v2 03/18] New API parameter: Add discard parameter to guestfs_add_drive_opts.
This adds a discard parameter to the guestfs_add_drive_opts which
approximately maps to the discard=ignore|unmap parameter supported by
qemu.
If discard is set to "enable" then we force discard=unmap (and try to
fail if it is not possible). If discard is set to the more useful
"besteffort" option, then we enable discard if possible. The default
is "disable".
---
generator/actions.ml | 32 +++++++++++++-
src/drives.c | 107 +++++++++++++++++++++++++++++---------------
src/guestfs-internal.h | 9 ++++
src/launch-direct.c | 117 ++++++++++++++++++++++++++++++++++++++++++++++++-
src/launch-libvirt.c | 94 ++++++++++++++++++++++++++++++++++++---
src/launch-uml.c | 6 +++
6 files changed, 321 insertions(+), 44 deletions(-)
diff --git a/generator/actions.ml b/generator/actions.ml
index a6ef194..b25194c 100644
--- a/generator/actions.ml
+++ b/generator/actions.ml
@@ -1286,7 +1286,7 @@ not all belong to a single logical operating system
{ defaults with
name = "add_drive";
- style = RErr, [String "filename"], [OBool "readonly";
OString "format"; OString "iface"; OString "name";
OString "label"; OString "protocol"; OStringList
"server"; OString "username"; OString "secret";
OString "cachemode"];
+ style = RErr, [String "filename"], [OBool "readonly";
OString "format"; OString "iface"; OString "name";
OString "label"; OString "protocol"; OStringList
"server"; OString "username"; OString "secret";
OString "cachemode"; OString "discard"];
once_had_no_optargs = true;
blocking = false;
fish_alias = ["add"];
@@ -1504,6 +1504,36 @@ for scratch or temporary disks.
=back
+=item C<discard>
+
+Enable or disable discard (a.k.a. trim or unmap) support on this
+drive. If enabled, operations such as C<guestfs_fstrim> will be able
+to discard / make thin / punch holes in the underlying host file
+or device.
+
+Possible discard settings are:
+
+=over 4
+
+=item C<discard = \"disable\">
+
+Disable discard support. This is the default.
+
+=item C<discard = \"enable\">
+
+Enable discard support. Fail if discard is not possible.
+
+=item C<discard = \"besteffort\">
+
+Enable discard support if possible, but don't fail if it is not
+supported.
+
+Since not all backends and not all underlying systems support
+discard, this is a good choice if you want to use discard if
+possible, but don't mind if it doesn't work.
+
+=back
+
=back" };
{ defaults with
diff --git a/src/drives.c b/src/drives.c
index 2c85b52..68e37f7 100644
--- a/src/drives.c
+++ b/src/drives.c
@@ -115,7 +115,8 @@ static struct drive *
create_drive_file (guestfs_h *g, const char *path,
bool readonly, const char *format,
const char *iface, const char *name,
- const char *disk_label, const char *cachemode)
+ const char *disk_label, const char *cachemode,
+ enum discard discard)
{
struct drive *drv = safe_calloc (g, 1, sizeof *drv);
@@ -128,6 +129,7 @@ create_drive_file (guestfs_h *g, const char *path,
drv->name = name ? safe_strdup (g, name) : NULL;
drv->disk_label = disk_label ? safe_strdup (g, disk_label) : NULL;
drv->cachemode = cachemode ? safe_strdup (g, cachemode) : NULL;
+ drv->discard = discard;
if (readonly) {
if (create_overlay (g, drv) == -1) {
@@ -151,7 +153,8 @@ create_drive_non_file (guestfs_h *g,
const char *username, const char *secret,
bool readonly, const char *format,
const char *iface, const char *name,
- const char *disk_label, const char *cachemode)
+ const char *disk_label, const char *cachemode,
+ enum discard discard)
{
struct drive *drv = safe_calloc (g, 1, sizeof *drv);
@@ -168,6 +171,7 @@ create_drive_non_file (guestfs_h *g,
drv->name = name ? safe_strdup (g, name) : NULL;
drv->disk_label = disk_label ? safe_strdup (g, disk_label) : NULL;
drv->cachemode = cachemode ? safe_strdup (g, cachemode) : NULL;
+ drv->discard = discard;
if (readonly) {
if (create_overlay (g, drv) == -1) {
@@ -191,7 +195,8 @@ create_drive_curl (guestfs_h *g,
const char *username, const char *secret,
bool readonly, const char *format,
const char *iface, const char *name,
- const char *disk_label, const char *cachemode)
+ const char *disk_label, const char *cachemode,
+ enum discard discard)
{
if (secret != NULL) {
error (g, _("curl: you cannot specify a secret with this
protocol"));
@@ -223,7 +228,7 @@ create_drive_curl (guestfs_h *g,
servers, nr_servers, exportname,
username, secret,
readonly, format, iface, name, disk_label,
- cachemode);
+ cachemode, discard);
}
static struct drive *
@@ -233,7 +238,8 @@ create_drive_gluster (guestfs_h *g,
const char *username, const char *secret,
bool readonly, const char *format,
const char *iface, const char *name,
- const char *disk_label, const char *cachemode)
+ const char *disk_label, const char *cachemode,
+ enum discard discard)
{
if (username != NULL) {
error (g, _("gluster: you cannot specify a username with this
protocol"));
@@ -263,7 +269,7 @@ create_drive_gluster (guestfs_h *g,
servers, nr_servers, exportname,
username, secret,
readonly, format, iface, name, disk_label,
- cachemode);
+ cachemode, discard);
}
static int
@@ -285,7 +291,8 @@ create_drive_nbd (guestfs_h *g,
const char *username, const char *secret,
bool readonly, const char *format,
const char *iface, const char *name,
- const char *disk_label, const char *cachemode)
+ const char *disk_label, const char *cachemode,
+ enum discard discard)
{
if (username != NULL) {
error (g, _("nbd: you cannot specify a username with this
protocol"));
@@ -308,7 +315,7 @@ create_drive_nbd (guestfs_h *g,
servers, nr_servers, exportname,
username, secret,
readonly, format, iface, name, disk_label,
- cachemode);
+ cachemode, discard);
}
static struct drive *
@@ -318,7 +325,8 @@ create_drive_rbd (guestfs_h *g,
const char *username, const char *secret,
bool readonly, const char *format,
const char *iface, const char *name,
- const char *disk_label, const char *cachemode)
+ const char *disk_label, const char *cachemode,
+ enum discard discard)
{
size_t i;
@@ -348,7 +356,7 @@ create_drive_rbd (guestfs_h *g,
servers, nr_servers, exportname,
username, secret,
readonly, format, iface, name, disk_label,
- cachemode);
+ cachemode, discard);
}
static struct drive *
@@ -358,7 +366,8 @@ create_drive_sheepdog (guestfs_h *g,
const char *username, const char *secret,
bool readonly, const char *format,
const char *iface, const char *name,
- const char *disk_label, const char *cachemode)
+ const char *disk_label, const char *cachemode,
+ enum discard discard)
{
size_t i;
@@ -397,7 +406,7 @@ create_drive_sheepdog (guestfs_h *g,
servers, nr_servers, exportname,
username, secret,
readonly, format, iface, name, disk_label,
- cachemode);
+ cachemode, discard);
}
static struct drive *
@@ -407,7 +416,8 @@ create_drive_ssh (guestfs_h *g,
const char *username, const char *secret,
bool readonly, const char *format,
const char *iface, const char *name,
- const char *disk_label, const char *cachemode)
+ const char *disk_label, const char *cachemode,
+ enum discard discard)
{
if (secret != NULL) {
error (g, _("ssh: you cannot specify a secret with this
protocol"));
@@ -444,7 +454,7 @@ create_drive_ssh (guestfs_h *g,
servers, nr_servers, exportname,
username, secret,
readonly, format, iface, name, disk_label,
- cachemode);
+ cachemode, discard);
}
static struct drive *
@@ -454,7 +464,8 @@ create_drive_iscsi (guestfs_h *g,
const char *username, const char *secret,
bool readonly, const char *format,
const char *iface, const char *name,
- const char *disk_label, const char *cachemode)
+ const char *disk_label, const char *cachemode,
+ enum discard discard)
{
if (username != NULL) {
error (g, _("iscsi: you cannot specify a username with this
protocol"));
@@ -491,7 +502,7 @@ create_drive_iscsi (guestfs_h *g,
servers, nr_servers, exportname,
username, secret,
readonly, format, iface, name, disk_label,
- cachemode);
+ cachemode, discard);
}
/* Traditionally you have been able to use /dev/null as a filename, as
@@ -528,14 +539,15 @@ create_drive_dev_null (guestfs_h *g, bool readonly, const
char *format,
return NULL;
return create_drive_file (g, tmpfile, readonly, format, iface, name,
- disk_label, 0);
+ disk_label, NULL, discard_disable);
}
static struct drive *
create_drive_dummy (guestfs_h *g)
{
/* A special drive struct that is used as a dummy slot for the appliance. */
- return create_drive_file (g, "", 0, NULL, NULL, NULL, NULL, 0);
+ return create_drive_file (g, "", 0, NULL, NULL, NULL, NULL, NULL,
+ discard_disable);
}
static void
@@ -563,8 +575,8 @@ free_drive_struct (struct drive *drv)
free (drv);
}
-static const char *
-protocol_to_string (enum drive_protocol protocol)
+const char *
+guestfs___drive_protocol_to_string (enum drive_protocol protocol)
{
switch (protocol) {
case drive_protocol_file: return "file";
@@ -590,12 +602,12 @@ static char *
drive_to_string (guestfs_h *g, const struct drive *drv)
{
return safe_asprintf
- (g, "%s%s%s%s protocol=%s%s%s%s%s%s%s%s%s",
+ (g, "%s%s%s%s protocol=%s%s%s%s%s%s%s%s%s%s",
drv->src.u.path,
drv->readonly ? " readonly" : "",
drv->src.format ? " format=" : "",
drv->src.format ? : "",
- protocol_to_string (drv->src.protocol),
+ guestfs___drive_protocol_to_string (drv->src.protocol),
drv->iface ? " iface=" : "",
drv->iface ? : "",
drv->name ? " name=" : "",
@@ -603,7 +615,9 @@ drive_to_string (guestfs_h *g, const struct drive *drv)
drv->disk_label ? " label=" : "",
drv->disk_label ? : "",
drv->cachemode ? " cache=" : "",
- drv->cachemode ? : "");
+ drv->cachemode ? : "",
+ drv->discard == discard_disable ? "" :
+ drv->discard == discard_enable ? " discard=enable" : "
discard=besteffort");
}
/* Add struct drive to the g->drives vector at the given index. */
@@ -824,6 +838,7 @@ guestfs__add_drive_opts (guestfs_h *g, const char *filename,
const char *username;
const char *secret;
const char *cachemode;
+ enum discard discard;
struct drive *drv;
size_t i, drv_index;
@@ -852,6 +867,28 @@ guestfs__add_drive_opts (guestfs_h *g, const char
*filename,
cachemode = optargs->bitmask &
GUESTFS_ADD_DRIVE_OPTS_CACHEMODE_BITMASK
? optargs->cachemode : NULL;
+ if (optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_DISCARD_BITMASK) {
+ if (STREQ (optargs->discard, "disable"))
+ discard = discard_disable;
+ else if (STREQ (optargs->discard, "enable"))
+ discard = discard_enable;
+ else if (STREQ (optargs->discard, "besteffort"))
+ discard = discard_besteffort;
+ else {
+ error (g, _("discard parameter must be 'disable',
'enable' or 'besteffort'"));
+ free_drive_servers (servers, nr_servers);
+ return -1;
+ }
+ }
+ else
+ discard = discard_disable;
+
+ if (readonly && discard == discard_enable) {
+ error (g, _("discard support cannot be enabled on read-only
drives"));
+ free_drive_servers (servers, nr_servers);
+ return -1;
+ }
+
if (format && !valid_format_iface (format)) {
error (g, _("%s parameter is empty or contains disallowed
characters"),
"format");
@@ -904,7 +941,7 @@ guestfs__add_drive_opts (guestfs_h *g, const char *filename,
}
drv = create_drive_file (g, filename, readonly, format, iface, name,
- disk_label, cachemode);
+ disk_label, cachemode, discard);
}
}
else if (STREQ (protocol, "ftp")) {
@@ -912,71 +949,71 @@ guestfs__add_drive_opts (guestfs_h *g, const char
*filename,
servers, nr_servers, filename,
username, secret,
readonly, format, iface, name,
- disk_label, cachemode);
+ disk_label, cachemode, discard);
}
else if (STREQ (protocol, "ftps")) {
drv = create_drive_curl (g, drive_protocol_ftps,
servers, nr_servers, filename,
username, secret,
readonly, format, iface, name,
- disk_label, cachemode);
+ disk_label, cachemode, discard);
}
else if (STREQ (protocol, "gluster")) {
drv = create_drive_gluster (g, servers, nr_servers, filename,
username, secret,
readonly, format, iface, name,
- disk_label, cachemode);
+ disk_label, cachemode, discard);
}
else if (STREQ (protocol, "http")) {
drv = create_drive_curl (g, drive_protocol_http,
servers, nr_servers, filename,
username, secret,
readonly, format, iface, name,
- disk_label, cachemode);
+ disk_label, cachemode, discard);
}
else if (STREQ (protocol, "https")) {
drv = create_drive_curl (g, drive_protocol_https,
servers, nr_servers, filename,
username, secret,
readonly, format, iface, name,
- disk_label, cachemode);
+ disk_label, cachemode, discard);
}
else if (STREQ (protocol, "iscsi")) {
drv = create_drive_iscsi (g, servers, nr_servers, filename,
username, secret,
readonly, format, iface, name,
- disk_label, cachemode);
+ disk_label, cachemode, discard);
}
else if (STREQ (protocol, "nbd")) {
drv = create_drive_nbd (g, servers, nr_servers, filename,
username, secret,
readonly, format, iface, name,
- disk_label, cachemode);
+ disk_label, cachemode, discard);
}
else if (STREQ (protocol, "rbd")) {
drv = create_drive_rbd (g, servers, nr_servers, filename,
username, secret,
readonly, format, iface, name,
- disk_label, cachemode);
+ disk_label, cachemode, discard);
}
else if (STREQ (protocol, "sheepdog")) {
drv = create_drive_sheepdog (g, servers, nr_servers, filename,
username, secret,
readonly, format, iface, name,
- disk_label, cachemode);
+ disk_label, cachemode, discard);
}
else if (STREQ (protocol, "ssh")) {
drv = create_drive_ssh (g, servers, nr_servers, filename,
username, secret,
readonly, format, iface, name,
- disk_label, cachemode);
+ disk_label, cachemode, discard);
}
else if (STREQ (protocol, "tftp")) {
drv = create_drive_curl (g, drive_protocol_tftp,
servers, nr_servers, filename,
username, secret,
readonly, format, iface, name,
- disk_label, cachemode);
+ disk_label, cachemode, discard);
}
else {
error (g, _("unknown protocol '%s'"), protocol);
diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h
index 545146f..0f3a928 100644
--- a/src/guestfs-internal.h
+++ b/src/guestfs-internal.h
@@ -231,6 +231,12 @@ struct drive_source {
char *secret;
};
+enum discard {
+ discard_disable = 0,
+ discard_enable = 1,
+ discard_besteffort = 2,
+};
+
/* There is one 'struct drive' per drive, including hot-plugged drives.
*/
struct drive {
/* Original source of the drive, eg. file:..., http:... */
@@ -252,6 +258,7 @@ struct drive {
char *name;
char *disk_label;
char *cachemode;
+ enum discard discard;
};
/* Extra hv parameters (from guestfs_config). */
@@ -713,6 +720,7 @@ extern size_t guestfs___checkpoint_drives (guestfs_h *g);
extern void guestfs___rollback_drives (guestfs_h *g, size_t);
extern void guestfs___add_dummy_appliance_drive (guestfs_h *g);
extern void guestfs___free_drives (guestfs_h *g);
+extern const char *guestfs___drive_protocol_to_string (enum drive_protocol
protocol);
/* appliance.c */
extern int guestfs___build_appliance (guestfs_h *g, char **kernel, char **dtb,
char **initrd, char **appliance);
@@ -832,6 +840,7 @@ extern void guestfs___cleanup_cmd_close (struct command **);
/* launch-direct.c */
extern char *guestfs___drive_source_qemu_param (guestfs_h *g, const struct
drive_source *src);
+extern bool guestfs___discard_possible (guestfs_h *g, struct drive *drv,
unsigned long qemu_version);
/* utils.c */
extern int guestfs___validate_guid (const char *);
diff --git a/src/launch-direct.c b/src/launch-direct.c
index 1e84061..3d478c7 100644
--- a/src/launch-direct.c
+++ b/src/launch-direct.c
@@ -509,6 +509,30 @@ launch_direct (guestfs_h *g, void *datav, const char *arg)
CLEANUP_FREE char *file = NULL, *escaped_file = NULL, *param = NULL;
if (!drv->overlay) {
+ const char *discard_mode = "";
+ int major = data->qemu_version_major, minor =
data->qemu_version_minor;
+ unsigned long qemu_version = major * 1000000 + minor * 1000;
+
+ switch (drv->discard) {
+ case discard_disable:
+ /* Since the default is always discard=ignore, don't specify it
+ * on the command line. This also avoids unnecessary breakage
+ * with qemu < 1.5 which didn't have the option at all.
+ */
+ break;
+ case discard_enable:
+ if (!guestfs___discard_possible (g, drv, qemu_version))
+ goto cleanup0;
+ /*FALLTHROUGH*/
+ case discard_besteffort:
+ /* I believe from reading the code that this is always safe as
+ * long as qemu >= 1.5.
+ */
+ if (major > 1 || (major == 1 && minor >= 5))
+ discard_mode = ",discard=unmap";
+ break;
+ }
+
/* Make the file= parameter. */
file = guestfs___drive_source_qemu_param (g, &drv->src);
escaped_file = qemu_escape_param (g, file);
@@ -517,10 +541,11 @@ launch_direct (guestfs_h *g, void *datav, const char *arg)
* the if=... at the end.
*/
param = safe_asprintf
- (g, "file=%s%s,cache=%s%s%s%s%s,id=hd%zu",
+ (g, "file=%s%s,cache=%s%s%s%s%s%s,id=hd%zu",
escaped_file,
drv->readonly ? ",snapshot=on" : "",
drv->cachemode ? drv->cachemode : "writeback",
+ discard_mode,
drv->src.format ? ",format=" : "",
drv->src.format ? drv->src.format : "",
drv->disk_label ? ",serial=" : "",
@@ -1370,6 +1395,96 @@ guestfs___drive_source_qemu_param (guestfs_h *g, const
struct drive_source *src)
abort ();
}
+/* Test if discard is both supported by qemu AND possible with the
+ * underlying file or device. This returns 1 if discard is possible.
+ * It returns 0 if not possible and sets the error to the reason why.
+ *
+ * This function is called when the user set discard == "enable".
+ *
+ * qemu_version is the version of qemu in the form returned by libvirt:
+ * major * 1,000,000 + minor * 1,000 + release
+ */
+bool
+guestfs___discard_possible (guestfs_h *g, struct drive *drv,
+ unsigned long qemu_version)
+{
+ /* qemu >= 1.5. This was the first version that supported the
+ * discard option on -drive at all.
+ */
+ bool qemu15 = qemu_version >= 1005000;
+ /* qemu >= 1.6. This was the first version that supported unmap on
+ * qcow2 backing files.
+ */
+ bool qemu16 = qemu_version >= 1006000;
+
+ if (!qemu15) {
+ error (g, _("discard cannot be enabled on this drive: "
+ "qemu < 1.5"));
+ return false;
+ }
+
+ /* If it's an overlay, discard is not possible (on the underlying
+ * file). This has probably been caught earlier since we already
+ * checked that the drive is !readonly. Nevertheless ...
+ */
+ if (drv->overlay) {
+ error (g, _("discard cannot be enabled on this drive: "
+ "the drive has a read-only overlay"));
+ return false;
+ }
+
+ /* Look at the source format. */
+ if (drv->src.format == NULL) {
+ /* We could autodetect the format, but we don't ... yet. XXX */
+ error (g, _("discard cannot be enabled on this drive: "
+ "you have to specify the format of the file"));
+ return false;
+ }
+ else if (STREQ (drv->src.format, "raw"))
+ /* OK */ ;
+ else if (STREQ (drv->src.format, "qcow2")) {
+ if (!qemu16) {
+ error (g, _("discard cannot be enabled on this drive: "
+ "qemu < 1.6 cannot do discard on qcow2 files"));
+ return false;
+ }
+ }
+ else {
+ /* It's possible in future other formats will support discard, but
+ * currently (qemu 1.7) none of them do.
+ */
+ error (g, _("discard cannot be enabled on this drive: "
+ "qemu does not support discard for '%s' format
files"),
+ drv->src.format);
+ return false;
+ }
+
+ switch (drv->src.protocol) {
+ /* Protocols which support discard. */
+ case drive_protocol_file:
+ case drive_protocol_gluster:
+ case drive_protocol_iscsi:
+ case drive_protocol_nbd:
+ case drive_protocol_rbd:
+ case drive_protocol_sheepdog: /* XXX depends on server version */
+ break;
+
+ /* Protocols which don't support discard. */
+ case drive_protocol_ftp:
+ case drive_protocol_ftps:
+ case drive_protocol_http:
+ case drive_protocol_https:
+ case drive_protocol_ssh:
+ case drive_protocol_tftp:
+ error (g, _("discard cannot be enabled on this drive: "
+ "protocol '%s' does not support discard"),
+ guestfs___drive_protocol_to_string (drv->src.protocol));
+ return false;
+ }
+
+ return true;
+}
+
static int
shutdown_direct (guestfs_h *g, void *datav, int check_for_errors)
{
diff --git a/src/launch-libvirt.c b/src/launch-libvirt.c
index da1d665..1f5a79a 100644
--- a/src/launch-libvirt.c
+++ b/src/launch-libvirt.c
@@ -107,6 +107,7 @@ struct backend_libvirt_data {
bool selinux_norelabel_disks;
char name[DOMAIN_NAME_LEN]; /* random name */
bool is_kvm; /* false = qemu, true = kvm (from
capabilities)*/
+ unsigned long qemu_version; /* qemu version (from libvirt) */
};
/* Parameters passed to construct_libvirt_xml and subfunctions. We
@@ -131,6 +132,7 @@ static xmlChar *construct_libvirt_xml (guestfs_h *g, const
struct libvirt_xml_pa
static void debug_appliance_permissions (guestfs_h *g);
static void debug_socket_permissions (guestfs_h *g);
static void libvirt_error (guestfs_h *g, const char *fs, ...)
__attribute__((format (printf,2,3)));
+static void libvirt_debug (guestfs_h *g, const char *fs, ...)
__attribute__((format (printf,2,3)));
static int is_custom_hv (guestfs_h *g);
static int is_blk (const char *path);
static void ignore_errors (void *ignore, virErrorPtr ignore2);
@@ -283,6 +285,19 @@ launch_libvirt (guestfs_h *g, void *datav, const char
*libvirt_uri)
*/
virConnSetErrorFunc (conn, NULL, ignore_errors);
+ /* Get hypervisor (hopefully qemu) version. */
+ if (virConnectGetVersion (conn, &data->qemu_version) == 0) {
+ debug (g, "qemu version (reported by libvirt) = %lu
(%lu.%lu.%lu)",
+ data->qemu_version,
+ data->qemu_version / 1000000UL,
+ data->qemu_version / 1000UL % 1000UL,
+ data->qemu_version % 1000UL);
+ }
+ else {
+ libvirt_debug (g, "unable to read qemu version from libvirt");
+ data->qemu_version = 0;
+ }
+
if (g->verbose)
guestfs___print_timestamped_message (g, "get libvirt
capabilities");
@@ -789,7 +804,7 @@ static int construct_libvirt_xml_devices (guestfs_h *g,
const struct libvirt_xml
static int construct_libvirt_xml_qemu_cmdline (guestfs_h *g, const struct
libvirt_xml_params *params, xmlTextWriterPtr xo);
static int construct_libvirt_xml_disk (guestfs_h *g, const struct
backend_libvirt_data *data, xmlTextWriterPtr xo, struct drive *drv, size_t
drv_index);
static int construct_libvirt_xml_disk_target (guestfs_h *g, xmlTextWriterPtr
xo, size_t drv_index);
-static int construct_libvirt_xml_disk_driver_qemu (guestfs_h *g,
xmlTextWriterPtr xo, const char *format, const char *cachemode);
+static int construct_libvirt_xml_disk_driver_qemu (guestfs_h *g, const struct
backend_libvirt_data *data, struct drive *drv, xmlTextWriterPtr xo, const char
*format, const char *cachemode, enum discard discard);
static int construct_libvirt_xml_disk_address (guestfs_h *g, xmlTextWriterPtr
xo, size_t drv_index);
static int construct_libvirt_xml_disk_source_hosts (guestfs_h *g,
xmlTextWriterPtr xo, const struct drive_source *src);
static int construct_libvirt_xml_disk_source_seclabel (guestfs_h *g, const
struct backend_libvirt_data *data, xmlTextWriterPtr xo);
@@ -1233,7 +1248,9 @@ construct_libvirt_xml_disk (guestfs_h *g,
if (construct_libvirt_xml_disk_target (g, xo, drv_index) == -1)
return -1;
- if (construct_libvirt_xml_disk_driver_qemu (g, xo, "qcow2",
"unsafe")
+ if (construct_libvirt_xml_disk_driver_qemu (g, NULL, NULL,
+ xo, "qcow2",
"unsafe",
+ discard_disable)
== -1)
return -1;
}
@@ -1370,8 +1387,9 @@ construct_libvirt_xml_disk (guestfs_h *g,
return -1;
}
- if (construct_libvirt_xml_disk_driver_qemu (g, xo, format,
- drv->cachemode ? :
"writeback")
+ if (construct_libvirt_xml_disk_driver_qemu (g, data, drv, xo, format,
+ drv->cachemode ? :
"writeback",
+ drv->discard)
== -1)
return -1;
}
@@ -1407,14 +1425,41 @@ construct_libvirt_xml_disk_target (guestfs_h *g,
xmlTextWriterPtr xo,
}
static int
-construct_libvirt_xml_disk_driver_qemu (guestfs_h *g, xmlTextWriterPtr xo,
+construct_libvirt_xml_disk_driver_qemu (guestfs_h *g,
+ const struct backend_libvirt_data
*data,
+ struct drive *drv,
+ xmlTextWriterPtr xo,
const char *format,
- const char *cachemode)
+ const char *cachemode,
+ enum discard discard)
{
+ bool discard_unmap = false;
+
+ switch (discard) {
+ case discard_disable:
+ /* Since the default is always discard=ignore, don't specify it
+ * in the XML.
+ */
+ break;
+ case discard_enable:
+ if (!guestfs___discard_possible (g, drv, data->qemu_version))
+ return -1;
+ /*FALLTHROUGH*/
+ case discard_besteffort:
+ /* I believe from reading the code that this is always safe as
+ * long as qemu >= 1.5.
+ */
+ if (data->qemu_version >= 1005000)
+ discard_unmap = true;
+ break;
+ }
+
start_element ("driver") {
attribute ("name", "qemu");
attribute ("type", format);
attribute ("cache", cachemode);
+ if (discard_unmap)
+ attribute ("discard", "unmap");
} end_element ();
return 0;
@@ -1499,7 +1544,9 @@ construct_libvirt_xml_appliance (guestfs_h *g,
attribute ("bus", "scsi");
} end_element ();
- if (construct_libvirt_xml_disk_driver_qemu (g, xo, "qcow2",
"unsafe") == -1)
+ if (construct_libvirt_xml_disk_driver_qemu (g, NULL, NULL, xo,
+ "qcow2",
"unsafe",
+ discard_disable) == -1)
return -1;
if (construct_libvirt_xml_disk_address (g, xo, params->appliance_index)
@@ -1659,6 +1706,39 @@ libvirt_error (guestfs_h *g, const char *fs, ...)
free (msg);
}
+/* Same as 'libvirt_error' but calls debug instead. */
+static void
+libvirt_debug (guestfs_h *g, const char *fs, ...)
+{
+ va_list args;
+ char *msg;
+ int len;
+ virErrorPtr err;
+
+ if (!g->verbose)
+ return;
+
+ va_start (args, fs);
+ len = vasprintf (&msg, fs, args);
+ va_end (args);
+
+ if (len < 0)
+ msg = safe_asprintf (g,
+ _("%s: internal error forming error
message"),
+ __func__);
+
+ /* In all recent libvirt, this retrieves the thread-local error. */
+ err = virGetLastError ();
+ if (err)
+ debug (g, "%s: %s [code=%d domain=%d]",
+ msg, err->message, err->code, err->domain);
+ else
+ debug (g, "%s", msg);
+
+ /* NB. 'err' must not be freed! */
+ free (msg);
+}
+
#if HAVE_LIBSELINUX
static void
selinux_warning (guestfs_h *g, const char *func,
diff --git a/src/launch-uml.c b/src/launch-uml.c
index deda366..2a6ddaf 100644
--- a/src/launch-uml.c
+++ b/src/launch-uml.c
@@ -122,6 +122,12 @@ uml_supported (guestfs_h *g)
_("uml backend does not support drives with 'label'
parameter"));
return false;
}
+ /* Note that discard == "besteffort" is fine. */
+ if (drv->discard == discard_enable) {
+ error (g,
+ _("uml backend does not support drives with 'discard'
parameter set to 'enable'"));
+ return false;
+ }
}
return true;
--
1.8.5.3
Richard W.M. Jones
2014-Mar-11 23:13 UTC
[Libguestfs] [PATCH v2 04/18] New API: blkdiscard - discard all blocks on a block device.
---
daemon/Makefile.am | 1 +
daemon/blkdiscard.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++
generator/actions.ml | 15 ++++++++++
po/POTFILES | 1 +
src/MAX_PROC_NR | 2 +-
5 files changed, 95 insertions(+), 1 deletion(-)
create mode 100644 daemon/blkdiscard.c
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index e64caca..b18f9ff 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -83,6 +83,7 @@ guestfsd_SOURCES = \
available.c \
augeas.c \
base64.c \
+ blkdiscard.c \
blkid.c \
blockdev.c \
btrfs.c \
diff --git a/daemon/blkdiscard.c b/daemon/blkdiscard.c
new file mode 100644
index 0000000..26d5f7d
--- /dev/null
+++ b/daemon/blkdiscard.c
@@ -0,0 +1,77 @@
+/* libguestfs - the guestfsd daemon
+ * Copyright (C) 2014 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <linux/fs.h>
+
+#include "daemon.h"
+#include "actions.h"
+
+/*
http://rwmj.wordpress.com/2014/03/11/blkdiscard-blkzeroout-blkdiscardzeroes-blksecdiscard/
*/
+
+int
+do_blkdiscard (const char *device)
+{
+ /* XXX We could read /sys/block/<device>/queue/discard_* in order to
+ * determine if discard is supported and the largest request size we
+ * are allowed to make. However:
+ *
+ * (1) Mapping the device name to /sys/block/<device> is quite hard
+ * (cf. the lv_canonical function in daemon/lvm.c)
+ *
+ * (2) We don't really need to do this in modern libguestfs since
+ * we're very likely to be using virtio-scsi, which supports
+ * arbitrary block discards.
+ *
+ * Let's wait to see if it causes a problem in real world
+ * situations.
+ */
+ uint64_t range[2];
+ int64_t size;
+ int fd;
+
+ size = do_blockdev_getsize64 (device);
+ if (size == -1)
+ return -1;
+
+ range[0] = 0;
+ range[1] = (uint64_t) size;
+
+ fd = open (device, O_WRONLY|O_CLOEXEC);
+ if (fd == -1) {
+ reply_with_perror ("open: %s", device);
+ return -1;
+ }
+
+ if (ioctl (fd, BLKDISCARD, range) == -1) {
+ reply_with_perror ("ioctl: %s: BLKDISCARD", device);
+ close (fd);
+ return -1;
+ }
+
+ close (fd);
+ return 0;
+}
diff --git a/generator/actions.ml b/generator/actions.ml
index b25194c..beb1182 100644
--- a/generator/actions.ml
+++ b/generator/actions.ml
@@ -11818,6 +11818,21 @@ device C<device>. Note that partitions are
numbered from 1.
The partition name can only be read on certain types of partition
table. This works on C<gpt> but not on C<mbr> partitions." };
+ { defaults with
+ name = "blkdiscard";
+ style = RErr, [Device "device"], [];
+ proc_nr = Some 417;
+ shortdesc = "discard all blocks on a device";
+ longdesc = "\
+This discards all blocks on the block device C<device>, giving
+the free space back to the host.
+
+This operation requires support in libguestfs, the host filesystem,
+qemu and the host kernel. If this support isn't present it may give
+an error or even appear to run but do nothing. You must also
+set the C<discard> attribute on the block device (see
+C<guestfs_add_drive_opts>)." };
+
]
(* Non-API meta-commands available only in guestfish.
diff --git a/po/POTFILES b/po/POTFILES
index 5b2ec57..7b2ab8a 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -15,6 +15,7 @@ daemon/acl.c
daemon/augeas.c
daemon/available.c
daemon/base64.c
+daemon/blkdiscard.c
daemon/blkid.c
daemon/blockdev.c
daemon/btrfs.c
diff --git a/src/MAX_PROC_NR b/src/MAX_PROC_NR
index 1c105f1..53c86ff 100644
--- a/src/MAX_PROC_NR
+++ b/src/MAX_PROC_NR
@@ -1 +1 @@
-416
+417
--
1.8.5.3
Richard W.M. Jones
2014-Mar-11 23:13 UTC
[Libguestfs] [PATCH v2 05/18] New API: blkdiscardzeroes - do discarded blocks read back as zeroes?
---
daemon/blkdiscard.c | 23 +++++++++++++++++++++++
generator/actions.ml | 13 +++++++++++++
src/MAX_PROC_NR | 2 +-
3 files changed, 37 insertions(+), 1 deletion(-)
diff --git a/daemon/blkdiscard.c b/daemon/blkdiscard.c
index 26d5f7d..0651788 100644
--- a/daemon/blkdiscard.c
+++ b/daemon/blkdiscard.c
@@ -75,3 +75,26 @@ do_blkdiscard (const char *device)
close (fd);
return 0;
}
+
+int
+do_blkdiscardzeroes (const char *device)
+{
+ int fd;
+ unsigned int arg;
+
+ fd = open (device, O_RDONLY|O_CLOEXEC);
+ if (fd == -1) {
+ reply_with_perror ("open: %s", device);
+ return -1;
+ }
+
+ if (ioctl (fd, BLKDISCARDZEROES, &arg) == -1) {
+ reply_with_perror ("ioctl: %s: BLKDISCARDZEROES", device);
+ close (fd);
+ return -1;
+ }
+
+ close (fd);
+
+ return arg != 0;
+}
diff --git a/generator/actions.ml b/generator/actions.ml
index beb1182..4e2c9e0 100644
--- a/generator/actions.ml
+++ b/generator/actions.ml
@@ -11833,6 +11833,19 @@ an error or even appear to run but do nothing. You
must also
set the C<discard> attribute on the block device (see
C<guestfs_add_drive_opts>)." };
+ { defaults with
+ name = "blkdiscardzeroes";
+ style = RBool "zeroes", [Device "device"], [];
+ proc_nr = Some 418;
+ shortdesc = "return true if discarded blocks are read as zeroes";
+ longdesc = "\
+This call returns true if blocks on C<device> that have been
+discarded by a call to C<guestfs_blkdiscard> are returned as
+blocks of zero bytes when read the next time.
+
+If it returns false, then it may be that discarded blocks are
+read as stale or random data." };
+
]
(* Non-API meta-commands available only in guestfish.
diff --git a/src/MAX_PROC_NR b/src/MAX_PROC_NR
index 53c86ff..29aae8e 100644
--- a/src/MAX_PROC_NR
+++ b/src/MAX_PROC_NR
@@ -1 +1 @@
-417
+418
--
1.8.5.3
Richard W.M. Jones
2014-Mar-11 23:13 UTC
[Libguestfs] [PATCH v2 06/18] tests: Add tests of discard support.
Test that both fstrim and blkdiscard work in reality, end-to-end.
---
Makefile.am | 1 +
configure.ac | 1 +
tests/discard/Makefile.am | 27 +++++++++
tests/discard/test-blkdiscard.pl | 111 +++++++++++++++++++++++++++++++++++
tests/discard/test-fstrim.pl | 124 +++++++++++++++++++++++++++++++++++++++
5 files changed, 264 insertions(+)
create mode 100644 tests/discard/Makefile.am
create mode 100755 tests/discard/test-blkdiscard.pl
create mode 100755 tests/discard/test-fstrim.pl
diff --git a/Makefile.am b/Makefile.am
index 020628e..11b12c2 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -46,6 +46,7 @@ SUBDIRS += tests/events
SUBDIRS += tests/parallel
SUBDIRS += tests/create
SUBDIRS += tests/disks
+SUBDIRS += tests/discard
SUBDIRS += tests/mountable
SUBDIRS += tests/network
SUBDIRS += tests/lvm
diff --git a/configure.ac b/configure.ac
index 35460e2..3785ea8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1673,6 +1673,7 @@ AC_CONFIG_FILES([Makefile
tests/charsets/Makefile
tests/create/Makefile
tests/data/Makefile
+ tests/discard/Makefile
tests/disks/Makefile
tests/disks/test-qemu-drive-libvirt.xml
tests/disk-labels/Makefile
diff --git a/tests/discard/Makefile.am b/tests/discard/Makefile.am
new file mode 100644
index 0000000..c1ff68b
--- /dev/null
+++ b/tests/discard/Makefile.am
@@ -0,0 +1,27 @@
+# libguestfs
+# Copyright (C) 2014 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+include $(top_srcdir)/subdir-rules.mk
+
+TESTS = \
+ test-blkdiscard.pl \
+ test-fstrim.pl
+
+TESTS_ENVIRONMENT = $(top_builddir)/run --test
+
+EXTRA_DIST = \
+ $(TESTS)
diff --git a/tests/discard/test-blkdiscard.pl b/tests/discard/test-blkdiscard.pl
new file mode 100755
index 0000000..22602b8
--- /dev/null
+++ b/tests/discard/test-blkdiscard.pl
@@ -0,0 +1,111 @@
+#!/usr/bin/perl
+# Copyright (C) 2014 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# Test that blkdiscard works.
+
+use strict;
+use warnings;
+
+use Sys::Guestfs;
+
+# Since we read error messages, we want to ensure they are printed
+# in English, hence:
+$ENV{"LANG"} = "C";
+
+$| = 1;
+
+if ($ENV{SKIP_TEST_BLKDISCARD_PL}) {
+ print "$0: skipped test because environment variable is set\n";
+ exit 77;
+}
+
+my $g = Sys::Guestfs->new ();
+
+# Discard is only supported when using qemu.
+if ($g->get_backend () ne "libvirt" &&
+ $g->get_backend () !~ /^libvirt:/ &&
+ $g->get_backend () ne "direct") {
+ print "$0: skipped test because discard is only supported when using
qemu\n";
+ exit 77;
+}
+
+# You can set this to "raw" or "qcow2".
+my $format = "raw";
+
+my $size = 5 * 1024 * 1024;
+
+my $disk;
+my @args;
+if ($format eq "raw") {
+ $disk = "test-blkdiscard.img";
+ @args = ( preallocation => "sparse" );
+} elsif ($format eq "qcow2") {
+ $disk = "test-blkdiscard.qcow2";
+ @args = ( preallocation => "off", compat => "1.1"
);
+} else {
+ die "$0: invalid disk format: $format\n";
+}
+
+# Create a disk and add it with discard enabled. This is allowed to
+# fail, eg because qemu is too old, but libguestfs must tell us that
+# it failed (since we're using 'enable', not 'besteffort').
+$g->disk_create ($disk, $format, $size, @args);
+END { unlink ($disk); };
+
+eval {
+ $g->add_drive ($disk, format => $format, readonly => 0, discard
=> "enable");
+ $g->launch ();
+};
+if ($@) {
+ if ($@ =~ /discard cannot be enabled on this drive/) {
+ # This is OK. Libguestfs says it's not possible to enable
+ # discard on this drive (eg. because qemu is too old). Print
+ # the reason and skip the test.
+ print "$0: skipped test: $@\n";
+ exit 77;
+ }
+ die # propagate the unexpected error
+}
+
+# At this point we've got a disk which claims to support discard.
+# Let's test that theory.
+
+my $orig_size = (stat ($disk))[12];
+print "original size:\t$orig_size (blocks)\n";
+
+# Fill the block device with some random data.
+
+$g->copy_device_to_device ("/dev/urandom", "/dev/sda",
size => $size);
+$g->sync ();
+
+my $full_size = (stat ($disk))[12];
+print "full size:\t$full_size (blocks)\n";
+
+die "surprising result: full size <= original size"
+ if $full_size <= $orig_size;
+
+# Discard the data on the device.
+
+$g->blkdiscard ("/dev/sda");
+$g->shutdown ();
+$g->close ();
+
+my $trimmed_size = (stat ($disk))[12];
+print "trimmed size:\t$trimmed_size (blocks)\n";
+
+die "looks like the blkdiscard operation did not work"
+ if $trimmed_size >= $full_size;
diff --git a/tests/discard/test-fstrim.pl b/tests/discard/test-fstrim.pl
new file mode 100755
index 0000000..dea5203
--- /dev/null
+++ b/tests/discard/test-fstrim.pl
@@ -0,0 +1,124 @@
+#!/usr/bin/perl
+# Copyright (C) 2014 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# Test that fstrim works.
+
+use strict;
+use warnings;
+
+use Sys::Guestfs;
+
+# Since we read error messages, we want to ensure they are printed
+# in English, hence:
+$ENV{"LANG"} = "C";
+
+$| = 1;
+
+if ($ENV{SKIP_TEST_FSTRIM_PL}) {
+ print "$0: skipped test because environment variable is set\n";
+ exit 77;
+}
+
+my $g = Sys::Guestfs->new ();
+
+# Discard is only supported when using qemu.
+if ($g->get_backend () ne "libvirt" &&
+ $g->get_backend () !~ /^libvirt:/ &&
+ $g->get_backend () ne "direct") {
+ print "$0: skipped test because discard is only supported when using
qemu\n";
+ exit 77;
+}
+
+# You can set this to "raw" or "qcow2".
+my $format = "raw";
+
+# Size needs to be at least 32 MB so we can fit an ext4 filesystem on it.
+my $size = 64 * 1024 * 1024;
+
+my $disk;
+my @args;
+if ($format eq "raw") {
+ $disk = "test-fstrim.img";
+ @args = ( preallocation => "sparse" );
+} elsif ($format eq "qcow2") {
+ $disk = "test-fstrim.qcow2";
+ @args = ( preallocation => "off", compat => "1.1"
);
+} else {
+ die "$0: invalid disk format: $format\n";
+}
+
+# Create a disk and add it with discard enabled. This is allowed to
+# fail, eg because qemu is too old, but libguestfs must tell us that
+# it failed (since we're using 'enable', not 'besteffort').
+$g->disk_create ($disk, $format, $size, @args);
+END { unlink ($disk); };
+
+eval {
+ $g->add_drive ($disk, format => $format, readonly => 0, discard
=> "enable");
+ $g->launch ();
+};
+if ($@) {
+ if ($@ =~ /discard cannot be enabled on this drive/) {
+ # This is OK. Libguestfs says it's not possible to enable
+ # discard on this drive (eg. because qemu is too old). Print
+ # the reason and skip the test.
+ print "$0: skipped test: $@\n";
+ exit 77;
+ }
+ die # propagate the unexpected error
+}
+
+# Is fstrim available in the appliance?
+unless ($g->feature_available (["fstrim"])) {
+ print "$0: skipped test because fstrim is not available\n";
+ exit 77;
+}
+
+# At this point we've got a disk which claims to support discard.
+# Let's test that theory.
+
+my $orig_size = (stat ($disk))[12];
+print "original size:\t$orig_size (blocks)\n";
+#system "du -sh $disk";
+
+# Write a filesystem onto the disk and fill it with data.
+
+$g->mkfs ("ext4", "/dev/sda");
+$g->mount_options ("discard", "/dev/sda",
"/");
+$g->fill (33, 10000000, "/data");
+$g->sync ();
+
+my $full_size = (stat ($disk))[12];
+print "full size:\t$full_size (blocks)\n";
+#system "du -sh $disk";
+
+die "surprising result: full size <= original size"
+ if $full_size <= $orig_size;
+
+# Remove the file and then try to trim the filesystem.
+
+$g->rm ("/data");
+$g->fstrim ("/");
+$g->sync ();
+$g->close ();
+
+my $trimmed_size = (stat ($disk))[12];
+print "trimmed size:\t$trimmed_size (blocks)\n";
+#system "du -sh $disk";
+
+die "looks like the fstrim operation did not work"
+ if $trimmed_size >= $full_size;
--
1.8.5.3
Richard W.M. Jones
2014-Mar-11 23:13 UTC
[Libguestfs] [PATCH v2 07/18] Pass cachemode parameter from add-domain to add-drive.
Allow callers to specify that all the disks from a domain are added
with a specific cachemode (instead of always having to use the
default, writeback).
---
generator/actions.ml | 4 ++--
src/guestfs-internal-frontend.h | 2 ++
src/libvirt-domain.c | 16 ++++++++++++++++
3 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/generator/actions.ml b/generator/actions.ml
index 4e2c9e0..597138c 100644
--- a/generator/actions.ml
+++ b/generator/actions.ml
@@ -1576,7 +1576,7 @@ not part of the formal API and can be removed or changed
at any time." };
{ defaults with
name = "add_domain";
- style = RInt "nrdisks", [String "dom"], [OString
"libvirturi"; OBool "readonly"; OString "iface";
OBool "live"; OBool "allowuuid"; OString
"readonlydisk"];
+ style = RInt "nrdisks", [String "dom"], [OString
"libvirturi"; OBool "readonly"; OString "iface";
OBool "live"; OBool "allowuuid"; OString
"readonlydisk"; OString "cachemode"];
fish_alias = ["domain"]; config_only = true;
shortdesc = "add the disk(s) from a named libvirt domain";
longdesc = "\
@@ -1668,7 +1668,7 @@ C<guestfs_add_drive_opts>." };
This interface is not quite baked yet. -- RWMJ 2010-11-11
{ defaults with
name = "add_libvirt_dom";
- style = RInt "nrdisks", [Pointer ("virDomainPtr",
"dom")], [Bool "readonly"; String "iface"; Bool
"live"; String "readonlydisk"];
+ style = RInt "nrdisks", [Pointer ("virDomainPtr",
"dom")], [Bool "readonly"; String "iface"; Bool
"live"; String "readonlydisk"; OString
"cachemode"];
in_fish = false;
shortdesc = "add the disk(s) from a libvirt domain";
longdesc = "\
diff --git a/src/guestfs-internal-frontend.h b/src/guestfs-internal-frontend.h
index 3b0a480..80ebf50 100644
--- a/src/guestfs-internal-frontend.h
+++ b/src/guestfs-internal-frontend.h
@@ -142,6 +142,8 @@ struct guestfs___add_libvirt_dom_argv {
int live;
#define GUESTFS___ADD_LIBVIRT_DOM_READONLYDISK_BITMASK (UINT64_C(1)<<3)
const char *readonlydisk;
+#define GUESTFS___ADD_LIBVIRT_DOM_CACHEMODE_BITMASK (UINT64_C(1)<<4)
+ const char *cachemode;
};
extern GUESTFS_DLL_PUBLIC int guestfs___add_libvirt_dom (guestfs_h *g,
virDomainPtr dom, const struct guestfs___add_libvirt_dom_argv *optargs);
diff --git a/src/libvirt-domain.c b/src/libvirt-domain.c
index 414e996..3a1c691 100644
--- a/src/libvirt-domain.c
+++ b/src/libvirt-domain.c
@@ -63,6 +63,7 @@ guestfs__add_domain (guestfs_h *g, const char *domain_name,
int allowuuid;
const char *readonlydisk;
const char *iface;
+ const char *cachemode;
struct guestfs___add_libvirt_dom_argv optargs2 = { .bitmask = 0 };
libvirturi = optargs->bitmask & GUESTFS_ADD_DOMAIN_LIBVIRTURI_BITMASK
@@ -77,6 +78,8 @@ guestfs__add_domain (guestfs_h *g, const char *domain_name,
? optargs->allowuuid : 0;
readonlydisk = optargs->bitmask &
GUESTFS_ADD_DOMAIN_READONLYDISK_BITMASK
? optargs->readonlydisk : NULL;
+ cachemode = optargs->bitmask & GUESTFS_ADD_DOMAIN_CACHEMODE_BITMASK
+ ? optargs->cachemode : NULL;
if (live && readonly) {
error (g, _("you cannot set both live and readonly flags"));
@@ -129,6 +132,10 @@ guestfs__add_domain (guestfs_h *g, const char *domain_name,
optargs2.bitmask |= GUESTFS___ADD_LIBVIRT_DOM_READONLYDISK_BITMASK;
optargs2.readonlydisk = readonlydisk;
}
+ if (cachemode) {
+ optargs2.bitmask |= GUESTFS___ADD_LIBVIRT_DOM_CACHEMODE_BITMASK;
+ optargs2.cachemode = cachemode;
+ }
r = guestfs___add_libvirt_dom (g, dom, &optargs2);
@@ -163,6 +170,7 @@ guestfs___add_libvirt_dom (guestfs_h *g, virDomainPtr dom,
ssize_t r;
int readonly;
const char *iface;
+ const char *cachemode;
int live;
/* Default for back-compat reasons: */
enum readonlydisk readonlydisk = readonlydisk_write;
@@ -196,6 +204,10 @@ guestfs___add_libvirt_dom (guestfs_h *g, virDomainPtr dom,
}
}
+ cachemode + optargs->bitmask &
GUESTFS___ADD_LIBVIRT_DOM_CACHEMODE_BITMASK
+ ? optargs->cachemode : NULL;
+
if (live && readonly) {
error (g, _("you cannot set both live and readonly flags"));
return -1;
@@ -256,6 +268,10 @@ guestfs___add_libvirt_dom (guestfs_h *g, virDomainPtr dom,
data.optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_IFACE_BITMASK;
data.optargs.iface = iface;
}
+ if (cachemode) {
+ data.optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_CACHEMODE_BITMASK;
+ data.optargs.cachemode = cachemode;
+ }
/* Checkpoint the command line around the operation so that either
* all disks are added or none are added.
--
1.8.5.3
Richard W.M. Jones
2014-Mar-11 23:13 UTC
[Libguestfs] [PATCH v2 08/18] Pass discard parameter from add-domain to add-drive.
Allow callers to specify that all the disks from a domain are added
with a specific discard mode (instead of always having discard
disabled).
---
generator/actions.ml | 4 ++--
src/guestfs-internal-frontend.h | 2 ++
src/libvirt-domain.c | 16 ++++++++++++++++
3 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/generator/actions.ml b/generator/actions.ml
index 597138c..742df6a 100644
--- a/generator/actions.ml
+++ b/generator/actions.ml
@@ -1576,7 +1576,7 @@ not part of the formal API and can be removed or changed
at any time." };
{ defaults with
name = "add_domain";
- style = RInt "nrdisks", [String "dom"], [OString
"libvirturi"; OBool "readonly"; OString "iface";
OBool "live"; OBool "allowuuid"; OString
"readonlydisk"; OString "cachemode"];
+ style = RInt "nrdisks", [String "dom"], [OString
"libvirturi"; OBool "readonly"; OString "iface";
OBool "live"; OBool "allowuuid"; OString
"readonlydisk"; OString "cachemode"; OString
"discard"];
fish_alias = ["domain"]; config_only = true;
shortdesc = "add the disk(s) from a named libvirt domain";
longdesc = "\
@@ -1668,7 +1668,7 @@ C<guestfs_add_drive_opts>." };
This interface is not quite baked yet. -- RWMJ 2010-11-11
{ defaults with
name = "add_libvirt_dom";
- style = RInt "nrdisks", [Pointer ("virDomainPtr",
"dom")], [Bool "readonly"; String "iface"; Bool
"live"; String "readonlydisk"; OString
"cachemode"];
+ style = RInt "nrdisks", [Pointer ("virDomainPtr",
"dom")], [Bool "readonly"; String "iface"; Bool
"live"; String "readonlydisk"; OString
"cachemode"; OString "discard"];
in_fish = false;
shortdesc = "add the disk(s) from a libvirt domain";
longdesc = "\
diff --git a/src/guestfs-internal-frontend.h b/src/guestfs-internal-frontend.h
index 80ebf50..6bf0a94 100644
--- a/src/guestfs-internal-frontend.h
+++ b/src/guestfs-internal-frontend.h
@@ -144,6 +144,8 @@ struct guestfs___add_libvirt_dom_argv {
const char *readonlydisk;
#define GUESTFS___ADD_LIBVIRT_DOM_CACHEMODE_BITMASK (UINT64_C(1)<<4)
const char *cachemode;
+#define GUESTFS___ADD_LIBVIRT_DOM_DISCARD_BITMASK (UINT64_C(1)<<5)
+ const char *discard;
};
extern GUESTFS_DLL_PUBLIC int guestfs___add_libvirt_dom (guestfs_h *g,
virDomainPtr dom, const struct guestfs___add_libvirt_dom_argv *optargs);
diff --git a/src/libvirt-domain.c b/src/libvirt-domain.c
index 3a1c691..cadae3e 100644
--- a/src/libvirt-domain.c
+++ b/src/libvirt-domain.c
@@ -64,6 +64,7 @@ guestfs__add_domain (guestfs_h *g, const char *domain_name,
const char *readonlydisk;
const char *iface;
const char *cachemode;
+ const char *discard;
struct guestfs___add_libvirt_dom_argv optargs2 = { .bitmask = 0 };
libvirturi = optargs->bitmask & GUESTFS_ADD_DOMAIN_LIBVIRTURI_BITMASK
@@ -80,6 +81,8 @@ guestfs__add_domain (guestfs_h *g, const char *domain_name,
? optargs->readonlydisk : NULL;
cachemode = optargs->bitmask & GUESTFS_ADD_DOMAIN_CACHEMODE_BITMASK
? optargs->cachemode : NULL;
+ discard = optargs->bitmask & GUESTFS_ADD_DOMAIN_DISCARD_BITMASK
+ ? optargs->discard : NULL;
if (live && readonly) {
error (g, _("you cannot set both live and readonly flags"));
@@ -136,6 +139,10 @@ guestfs__add_domain (guestfs_h *g, const char *domain_name,
optargs2.bitmask |= GUESTFS___ADD_LIBVIRT_DOM_CACHEMODE_BITMASK;
optargs2.cachemode = cachemode;
}
+ if (discard) {
+ optargs2.bitmask |= GUESTFS___ADD_LIBVIRT_DOM_DISCARD_BITMASK;
+ optargs2.discard = discard;
+ }
r = guestfs___add_libvirt_dom (g, dom, &optargs2);
@@ -171,6 +178,7 @@ guestfs___add_libvirt_dom (guestfs_h *g, virDomainPtr dom,
int readonly;
const char *iface;
const char *cachemode;
+ const char *discard;
int live;
/* Default for back-compat reasons: */
enum readonlydisk readonlydisk = readonlydisk_write;
@@ -208,6 +216,10 @@ guestfs___add_libvirt_dom (guestfs_h *g, virDomainPtr dom,
optargs->bitmask & GUESTFS___ADD_LIBVIRT_DOM_CACHEMODE_BITMASK
? optargs->cachemode : NULL;
+ discard + optargs->bitmask &
GUESTFS___ADD_LIBVIRT_DOM_DISCARD_BITMASK
+ ? optargs->discard : NULL;
+
if (live && readonly) {
error (g, _("you cannot set both live and readonly flags"));
return -1;
@@ -272,6 +284,10 @@ guestfs___add_libvirt_dom (guestfs_h *g, virDomainPtr dom,
data.optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_CACHEMODE_BITMASK;
data.optargs.cachemode = cachemode;
}
+ if (discard) {
+ data.optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_DISCARD_BITMASK;
+ data.optargs.discard = discard;
+ }
/* Checkpoint the command line around the operation so that either
* all disks are added or none are added.
--
1.8.5.3
Richard W.M. Jones
2014-Mar-11 23:13 UTC
[Libguestfs] [PATCH v2 09/18] virt-format: Discard the data on the disks.
If possible, this means that the host will be able to reclaim most of
the space used by formatted disks.
---
fish/options.c | 5 +++++
fish/options.h | 1 +
format/format.c | 13 +++++++++++++
3 files changed, 19 insertions(+)
diff --git a/fish/options.c b/fish/options.c
index cfdbdfe..80b71ec 100644
--- a/fish/options.c
+++ b/fish/options.c
@@ -134,6 +134,10 @@ add_drives_handle (guestfs_h *g, struct drv *drv, char
next_drive)
ad_optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_CACHEMODE_BITMASK;
ad_optargs.cachemode = drv->a.cachemode;
}
+ if (drv->a.discard) {
+ ad_optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_DISCARD_BITMASK;
+ ad_optargs.discard = drv->a.discard;
+ }
r = guestfs_add_drive_opts_argv (g, drv->a.filename, &ad_optargs);
if (r == -1)
@@ -279,6 +283,7 @@ free_drives (struct drv *drv)
free (drv->a.filename);
/* a.format is an optarg, so don't free it */
/* a.cachemode is a static string, so don't free it */
+ /* a.discard is a static string, so don't free it */
break;
case drv_uri:
free (drv->uri.path);
diff --git a/fish/options.h b/fish/options.h
index a7b8277..dbf9163 100644
--- a/fish/options.h
+++ b/fish/options.h
@@ -61,6 +61,7 @@ struct drv {
char *filename; /* disk filename */
const char *format; /* format (NULL == autodetect) */
const char *cachemode;/* cachemode (NULL == default) */
+ const char *discard; /* discard (NULL == disable) */
} a;
struct {
char *path; /* disk path */
diff --git a/format/format.c b/format/format.c
index cff0d82..684bffd 100644
--- a/format/format.c
+++ b/format/format.c
@@ -188,6 +188,11 @@ main (int argc, char *argv[])
case 'a':
OPTION_a;
+
+ /* Enable discard on all drives added on the command line. */
+ assert (drvs != NULL);
+ assert (drvs->type == drv_a);
+ drvs->a.discard = "besteffort";
break;
case 'v':
@@ -342,6 +347,14 @@ do_format (void)
}
}
+ /* Send TRIM/UNMAP to all block devices, to give back the space to
+ * the host. However don't fail if this doesn't work.
+ */
+ guestfs_push_error_handler (g, NULL, NULL);
+ for (i = 0; devices[i] != NULL; ++i)
+ guestfs_blkdiscard (g, devices[i]);
+ guestfs_pop_error_handler (g);
+
if (do_rescan (devices))
return 1; /* which means, reopen the handle and retry */
--
1.8.5.3
Richard W.M. Jones
2014-Mar-11 23:13 UTC
[Libguestfs] [PATCH v2 10/18] sysprep: Add disks with discard = "besteffort".
Since virt-sysprep tends to delete a lot of files, adding discard
support to it makes some sense.
Note that this probably won't have any effect for most filesystems
since:
(a) ext4 mounts also need to use -o discard,
(b) ext4, and maybe others, require you to call fstrim explicitly,
they don't discard automatically (except for userspace tools like
mkfs.ext4 but that doesn't apply in this case).
---
sysprep/main.ml | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/sysprep/main.ml b/sysprep/main.ml
index 6f631d5..9047a74 100644
--- a/sysprep/main.ml
+++ b/sysprep/main.ml
@@ -198,7 +198,11 @@ read the man page virt-sysprep(1).
fun (g : Guestfs.guestfs) readonly ->
let allowuuid = true in
let readonlydisk = "ignore" (* ignore CDs, data drives *) in
- ignore (g#add_domain ~readonly ?libvirturi ~allowuuid ~readonlydisk
dom)
+ let discard = if readonly then None else Some "besteffort" in
+ ignore (g#add_domain
+ ~readonly ?discard
+ ?libvirturi ~allowuuid ~readonlydisk
+ dom)
| _, Some _ ->
eprintf (f_"%s: you cannot give -a and -d options together\n")
prog;
eprintf (f_"Read virt-sysprep(1) man page for further
information.\n");
@@ -209,7 +213,11 @@ read the man page virt-sysprep(1).
fun (uri, format) ->
let { URI.path = path; protocol = protocol;
server = server; username = username } = uri in
- g#add_drive ~readonly ?format ~protocol ?server ?username path
+ let discard = if readonly then None else Some
"besteffort" in
+ g#add_drive
+ ~readonly ?discard
+ ?format ~protocol ?server ?username
+ path
) files
in
--
1.8.5.3
Richard W.M. Jones
2014-Mar-11 23:13 UTC
[Libguestfs] [PATCH v2 11/18] sparsify: Remove unused variable definition.
--- sparsify/sparsify.ml | 3 --- 1 file changed, 3 deletions(-) diff --git a/sparsify/sparsify.ml b/sparsify/sparsify.ml index 212a23f..b124406 100644 --- a/sparsify/sparsify.ml +++ b/sparsify/sparsify.ml @@ -296,9 +296,6 @@ let () in Sys.set_signal Sys.sigint (Sys.Signal_handle do_sigint) -(* Get the size in bytes of the input disk. *) -let insize = g#blockdev_getsize64 "/dev/sda" - (* Write zeroes for non-ignored filesystems that we are able to mount, * and selected swap partitions. *) -- 1.8.5.3
Richard W.M. Jones
2014-Mar-11 23:13 UTC
[Libguestfs] [PATCH v2 12/18] sparsify: Capture any exceptions and display nicer error messages.
This is just code motion, there is no functional change.
---
sparsify/sparsify.ml | 418 ++++++++++++++++++++++++++-------------------------
1 file changed, 217 insertions(+), 201 deletions(-)
diff --git a/sparsify/sparsify.ml b/sparsify/sparsify.ml
index b124406..e79fe78 100644
--- a/sparsify/sparsify.ml
+++ b/sparsify/sparsify.ml
@@ -31,68 +31,69 @@ external statvfs_free_space : string -> int64
let () = Random.self_init ()
-(* Command line argument parsing. *)
let prog = Filename.basename Sys.executable_name
let error fs = error ~prog fs
-let indisk, outdisk, check_tmpdir, compress, convert, debug_gc,
- format, ignores, machine_readable,
- option, quiet, verbose, trace, zeroes - let display_version () - printf
"virt-sparsify %s\n" Config.package_version;
- exit 0
- in
+let main () + (* Command line argument parsing. *)
+ let indisk, outdisk, check_tmpdir, compress, convert, debug_gc,
+ format, ignores, machine_readable,
+ option, quiet, verbose, trace, zeroes + let display_version () +
printf "virt-sparsify %s\n" Config.package_version;
+ exit 0
+ in
- let add xs s = xs := s :: !xs in
+ let add xs s = xs := s :: !xs in
- let check_tmpdir = ref `Warn in
- let set_check_tmpdir = function
- | "ignore" | "i" -> check_tmpdir := `Ignore
- | "continue" | "cont" | "c" ->
check_tmpdir := `Continue
- | "warn" | "warning" | "w" -> check_tmpdir
:= `Warn
- | "fail" | "f" | "error" -> check_tmpdir
:= `Fail
- | str ->
- eprintf (f_"--check-tmpdir: unknown argument `%s'\n") str;
- exit 1
- in
+ let check_tmpdir = ref `Warn in
+ let set_check_tmpdir = function
+ | "ignore" | "i" -> check_tmpdir := `Ignore
+ | "continue" | "cont" | "c" ->
check_tmpdir := `Continue
+ | "warn" | "warning" | "w" ->
check_tmpdir := `Warn
+ | "fail" | "f" | "error" -> check_tmpdir
:= `Fail
+ | str ->
+ eprintf (f_"--check-tmpdir: unknown argument `%s'\n")
str;
+ exit 1
+ in
- let compress = ref false in
- let convert = ref "" in
- let debug_gc = ref false in
- let format = ref "" in
- let ignores = ref [] in
- let machine_readable = ref false in
- let option = ref "" in
- let quiet = ref false in
- let verbose = ref false in
- let trace = ref false in
- let zeroes = ref [] in
+ let compress = ref false in
+ let convert = ref "" in
+ let debug_gc = ref false in
+ let format = ref "" in
+ let ignores = ref [] in
+ let machine_readable = ref false in
+ let option = ref "" in
+ let quiet = ref false in
+ let verbose = ref false in
+ let trace = ref false in
+ let zeroes = ref [] in
- let ditto = " -\"-" in
- let argspec = Arg.align [
- "--check-tmpdir", Arg.String set_check_tmpdir,
"ignore|..." ^ " " ^ s_"Check there is enough space in
$TMPDIR";
- "--compress", Arg.Set compress, " " ^
s_"Compressed output format";
- "--convert", Arg.Set_string convert, s_"format" ^
" " ^ s_"Format of output disk (default: same as input)";
- "--debug-gc", Arg.Set debug_gc, " " ^
s_"Debug GC and memory allocations";
- "--format", Arg.Set_string format, s_"format" ^
" " ^ s_"Format of input disk";
- "--ignore", Arg.String (add ignores), s_"fs" ^ "
" ^ s_"Ignore filesystem";
- "--long-options", Arg.Unit display_long_options, " " ^
s_"List long options";
- "--machine-readable", Arg.Set machine_readable, " " ^
s_"Make output machine readable";
- "-o", Arg.Set_string option, s_"option" ^
" " ^ s_"Add qemu-img options";
- "-q", Arg.Set quiet, " " ^
s_"Quiet output";
- "--quiet", Arg.Set quiet, ditto;
- "-v", Arg.Set verbose, " " ^
s_"Enable debugging messages";
- "--verbose", Arg.Set verbose, ditto;
- "-V", Arg.Unit display_version, " " ^
s_"Display version and exit";
- "--version", Arg.Unit display_version, ditto;
- "-x", Arg.Set trace, " " ^
s_"Enable tracing of libguestfs calls";
- "--zero", Arg.String (add zeroes), s_"fs" ^ "
" ^ s_"Zero filesystem";
- ] in
- long_options := argspec;
- let disks = ref [] in
- let anon_fun s = disks := s :: !disks in
- let usage_msg - sprintf (f_"\
+ let ditto = " -\"-" in
+ let argspec = Arg.align [
+ "--check-tmpdir", Arg.String set_check_tmpdir,
"ignore|..." ^ " " ^ s_"Check there is enough space in
$TMPDIR";
+ "--compress", Arg.Set compress, " " ^
s_"Compressed output format";
+ "--convert", Arg.Set_string convert, s_"format" ^
" " ^ s_"Format of output disk (default: same as input)";
+ "--debug-gc", Arg.Set debug_gc, " " ^
s_"Debug GC and memory allocations";
+ "--format", Arg.Set_string format, s_"format" ^
" " ^ s_"Format of input disk";
+ "--ignore", Arg.String (add ignores), s_"fs" ^
" " ^ s_"Ignore filesystem";
+ "--long-options", Arg.Unit display_long_options, " "
^ s_"List long options";
+ "--machine-readable", Arg.Set machine_readable, " " ^
s_"Make output machine readable";
+ "-o", Arg.Set_string option, s_"option" ^
" " ^ s_"Add qemu-img options";
+ "-q", Arg.Set quiet, " " ^
s_"Quiet output";
+ "--quiet", Arg.Set quiet, ditto;
+ "-v", Arg.Set verbose, " " ^
s_"Enable debugging messages";
+ "--verbose", Arg.Set verbose, ditto;
+ "-V", Arg.Unit display_version, " " ^
s_"Display version and exit";
+ "--version", Arg.Unit display_version, ditto;
+ "-x", Arg.Set trace, " " ^
s_"Enable tracing of libguestfs calls";
+ "--zero", Arg.String (add zeroes), s_"fs" ^
" " ^ s_"Zero filesystem";
+ ] in
+ long_options := argspec;
+ let disks = ref [] in
+ let anon_fun s = disks := s :: !disks in
+ let usage_msg + sprintf (f_"\
%s: sparsify a virtual machine disk
virt-sparsify [--options] indisk outdisk
@@ -100,118 +101,114 @@ let indisk, outdisk, check_tmpdir, compress, convert,
debug_gc,
A short summary of the options is given below. For detailed help please
read the man page virt-sparsify(1).
")
- prog in
- Arg.parse argspec anon_fun usage_msg;
+ prog in
+ Arg.parse argspec anon_fun usage_msg;
- (* Dereference the rest of the args. *)
- let check_tmpdir = !check_tmpdir in
- let compress = !compress in
- let convert = match !convert with "" -> None | str -> Some
str in
- let debug_gc = !debug_gc in
- let format = match !format with "" -> None | str -> Some str
in
- let ignores = List.rev !ignores in
- let machine_readable = !machine_readable in
- let option = match !option with "" -> None | str -> Some str
in
- let quiet = !quiet in
- let verbose = !verbose in
- let trace = !trace in
- let zeroes = List.rev !zeroes in
+ (* Dereference the rest of the args. *)
+ let check_tmpdir = !check_tmpdir in
+ let compress = !compress in
+ let convert = match !convert with "" -> None | str -> Some
str in
+ let debug_gc = !debug_gc in
+ let format = match !format with "" -> None | str -> Some
str in
+ let ignores = List.rev !ignores in
+ let machine_readable = !machine_readable in
+ let option = match !option with "" -> None | str -> Some
str in
+ let quiet = !quiet in
+ let verbose = !verbose in
+ let trace = !trace in
+ let zeroes = List.rev !zeroes in
- (* No arguments and machine-readable mode? Print out some facts
- * about what this binary supports.
- *)
- if !disks = [] && machine_readable then (
- printf "virt-sparsify\n";
- printf "linux-swap\n";
- printf "zero\n";
- printf "check-tmpdir\n";
- let g = new G.guestfs () in
- g#add_drive "/dev/null";
- g#launch ();
- if g#feature_available [| "ntfsprogs"; "ntfs3g" |] then
- printf "ntfs\n";
- if g#feature_available [| "btrfs" |] then
- printf "btrfs\n";
- exit 0
- );
+ (* No arguments and machine-readable mode? Print out some facts
+ * about what this binary supports.
+ *)
+ if !disks = [] && machine_readable then (
+ printf "virt-sparsify\n";
+ printf "linux-swap\n";
+ printf "zero\n";
+ printf "check-tmpdir\n";
+ let g = new G.guestfs () in
+ g#add_drive "/dev/null";
+ g#launch ();
+ if g#feature_available [| "ntfsprogs"; "ntfs3g" |]
then
+ printf "ntfs\n";
+ if g#feature_available [| "btrfs" |] then
+ printf "btrfs\n";
+ exit 0
+ );
- (* Verify we got exactly 2 disks. *)
- let indisk, outdisk - match List.rev !disks with
- | [indisk; outdisk] -> indisk, outdisk
- | _ ->
+ (* Verify we got exactly 2 disks. *)
+ let indisk, outdisk + match List.rev !disks with
+ | [indisk; outdisk] -> indisk, outdisk
+ | _ ->
error "usage is: %s [--options] indisk outdisk" prog in
- (* Simple-minded check that the user isn't trying to use the
- * same disk for input and output.
- *)
- if indisk = outdisk then
- error (f_"you cannot use the same disk image for input and
output");
+ (* Simple-minded check that the user isn't trying to use the
+ * same disk for input and output.
+ *)
+ if indisk = outdisk then
+ error (f_"you cannot use the same disk image for input and
output");
- (* The input disk must be an absolute path, so we can store the name
- * in the overlay disk.
- *)
- let indisk - if not (Filename.is_relative indisk) then
- indisk
- else
- Sys.getcwd () // indisk in
+ (* The input disk must be an absolute path, so we can store the name
+ * in the overlay disk.
+ *)
+ let indisk + if not (Filename.is_relative indisk) then
+ indisk
+ else
+ Sys.getcwd () // indisk in
- (* Check the output is not a block or char special (RHBZ#1056290). *)
- if is_block_device outdisk then
- error (f_"output '%s' cannot be a block device, it must be a
regular file")
- outdisk;
+ (* Check the output is not a block or char special (RHBZ#1056290). *)
+ if is_block_device outdisk then
+ error (f_"output '%s' cannot be a block device, it must be a
regular file")
+ outdisk;
- if is_char_device outdisk then
- error (f_"output '%s' cannot be a character device, it must be
a regular file")
- outdisk;
+ if is_char_device outdisk then
+ error (f_"output '%s' cannot be a character device, it must
be a regular file")
+ outdisk;
- indisk, outdisk, check_tmpdir, compress, convert,
- debug_gc, format, ignores, machine_readable,
- option, quiet, verbose, trace, zeroes
+ indisk, outdisk, check_tmpdir, compress, convert,
+ debug_gc, format, ignores, machine_readable,
+ option, quiet, verbose, trace, zeroes in
-(* Once we have got past argument parsing and start to create
- * temporary files (including the potentially massive overlay file), we
- * need to catch SIGINT (^C) and exit cleanly so the temporary file
- * goes away. Note that we don't delete temporaries in the signal
- * handler.
- *)
-let () + (* Once we have got past argument parsing and start to create
+ * temporary files (including the potentially massive overlay file), we
+ * need to catch SIGINT (^C) and exit cleanly so the temporary file
+ * goes away. Note that we don't delete temporaries in the signal
+ * handler.
+ *)
let do_sigint _ = exit 1 in
- Sys.set_signal Sys.sigint (Sys.Signal_handle do_sigint)
+ Sys.set_signal Sys.sigint (Sys.Signal_handle do_sigint);
-(* What should the output format be? If the user specified an
- * input format, use that, else detect it from the source image.
- *)
-let output_format - match convert with
- | Some fmt -> fmt (* user specified output conversion *)
- | None ->
- match format with
- | Some fmt -> fmt (* user specified input format, use that *)
+ (* What should the output format be? If the user specified an
+ * input format, use that, else detect it from the source image.
+ *)
+ let output_format + match convert with
+ | Some fmt -> fmt (* user specified output conversion *)
| None ->
- (* Don't know, so we must autodetect. *)
- match (new G.guestfs ())#disk_format indisk with
- | "unknown" ->
- error (f_"cannot detect input disk format; use the --format
parameter")
- | fmt -> fmt
+ match format with
+ | Some fmt -> fmt (* user specified input format, use that *)
+ | None ->
+ (* Don't know, so we must autodetect. *)
+ match (new G.guestfs ())#disk_format indisk with
+ | "unknown" ->
+ error (f_"cannot detect input disk format; use the --format
parameter")
+ | fmt -> fmt in
-(* Compression is not supported by raw output (RHBZ#852194). *)
-let () + (* Compression is not supported by raw output (RHBZ#852194). *)
if output_format = "raw" && compress then
- error (f_"--compress cannot be used for raw output. Remove this
option or use --convert qcow2.")
+ error (f_"--compress cannot be used for raw output. Remove this
option or use --convert qcow2.");
-(* Get virtual size of the input disk. *)
-let virtual_size = (new G.guestfs ())#disk_virtual_size indisk
-let () + (* Get virtual size of the input disk. *)
+ let virtual_size = (new G.guestfs ())#disk_virtual_size indisk in
if not quiet then
printf (f_"Input disk virtual size = %Ld bytes (%s)\n%!")
- virtual_size (human_size virtual_size)
+ virtual_size (human_size virtual_size);
-(* Check there is enough space in $TMPDIR. *)
-let tmpdir = Filename.temp_dir_name
+ (* Check there is enough space in $TMPDIR. *)
+ let tmpdir = Filename.temp_dir_name in
-let () let print_warning () let free_space = statvfs_free_space tmpdir
in
let extra_needed = virtual_size -^ free_space in
@@ -236,7 +233,7 @@ You can ignore this warning or change it to a hard failure
using the
) else false
in
- match check_tmpdir with
+ (match check_tmpdir with
| `Ignore -> ()
| `Continue -> ignore (print_warning ())
| `Warn ->
@@ -249,57 +246,54 @@ You can ignore this warning or change it to a hard failure
using the
eprintf "Exiting because --check-tmpdir=fail was set.\n%!";
exit 2
)
+ );
-let () if not quiet then
- printf (f_"Create overlay file in %s to protect source disk
...\n%!") tmpdir
+ printf (f_"Create overlay file in %s to protect source disk
...\n%!") tmpdir;
-(* Create the temporary overlay file. *)
-let overlaydisk - let tmp = Filename.temp_file "sparsify"
".qcow2" in
- unlink_on_exit tmp;
+ (* Create the temporary overlay file. *)
+ let overlaydisk + let tmp = Filename.temp_file "sparsify"
".qcow2" in
+ unlink_on_exit tmp;
- (* Create it with the indisk as the backing file. *)
- (* XXX Old code used to:
- * - detect if compat=1.1 was supported
- * - add lazy_refcounts option
- *)
- (new G.guestfs ())#disk_create
- ~backingfile:indisk ?backingformat:format ~compat:"1.1"
- tmp "qcow2" Int64.minus_one;
+ (* Create it with the indisk as the backing file. *)
+ (* XXX Old code used to:
+ * - detect if compat=1.1 was supported
+ * - add lazy_refcounts option
+ *)
+ (new G.guestfs ())#disk_create
+ ~backingfile:indisk ?backingformat:format ~compat:"1.1"
+ tmp "qcow2" Int64.minus_one;
- tmp
+ tmp in
-let () if not quiet then
- printf (f_"Examine source disk ...\n%!")
+ printf (f_"Examine source disk ...\n%!");
-(* Connect to libguestfs. *)
-let g - let g = new G.guestfs () in
- if trace then g#set_trace true;
- if verbose then g#set_verbose true;
+ (* Connect to libguestfs. *)
+ let g + let g = new G.guestfs () in
+ if trace then g#set_trace true;
+ if verbose then g#set_verbose true;
- (* Note that the temporary overlay disk is always qcow2 format. *)
- g#add_drive ~format:"qcow2" ~readonly:false
~cachemode:"unsafe" overlaydisk;
+ (* Note that the temporary overlay disk is always qcow2 format. *)
+ g#add_drive ~format:"qcow2" ~readonly:false
~cachemode:"unsafe" overlaydisk;
- if not quiet then Progress.set_up_progress_bar ~machine_readable g;
- g#launch ();
+ if not quiet then Progress.set_up_progress_bar ~machine_readable g;
+ g#launch ();
- g
+ g in
-(* Modify SIGINT handler (set first above) to cancel the handle. *)
-let () + (* Modify SIGINT handler (set first above) to cancel the handle. *)
let do_sigint _ g#user_cancel ();
exit 1
in
- Sys.set_signal Sys.sigint (Sys.Signal_handle do_sigint)
+ Sys.set_signal Sys.sigint (Sys.Signal_handle do_sigint);
-(* Write zeroes for non-ignored filesystems that we are able to mount,
- * and selected swap partitions.
- *)
-let () + (* Write zeroes for non-ignored filesystems that we are able to
mount,
+ * and selected swap partitions.
+ *)
let filesystems = g#list_filesystems () in
let filesystems = List.map fst filesystems in
let filesystems = List.sort compare filesystems in
@@ -356,10 +350,9 @@ let ()
g#umount_all ()
)
- ) filesystems
+ ) filesystems;
-(* Fill unused space in volume groups. *)
-let () + (* Fill unused space in volume groups. *)
let vgs = g#vgs () in
let vgs = Array.to_list vgs in
let vgs = List.sort compare vgs in
@@ -382,22 +375,19 @@ let () g#lvremove lvdev
)
)
- ) vgs
+ ) vgs;
-(* Don't need libguestfs now. *)
-let () + (* Don't need libguestfs now. *)
g#shutdown ();
- g#close ()
+ g#close ();
-(* Modify SIGINT handler (set first above) to just exit. *)
-let () + (* Modify SIGINT handler (set first above) to just exit. *)
let do_sigint _ = exit 1 in
- Sys.set_signal Sys.sigint (Sys.Signal_handle do_sigint)
+ Sys.set_signal Sys.sigint (Sys.Signal_handle do_sigint);
-(* Now run qemu-img convert which copies the overlay to the
- * destination and automatically does sparsification.
- *)
-let () + (* Now run qemu-img convert which copies the overlay to the
+ * destination and automatically does sparsification.
+ *)
if not quiet then
printf (f_"Copy to destination and make sparse ...\n%!");
@@ -412,16 +402,42 @@ let () if verbose then
printf "%s\n%!" cmd;
if Sys.command cmd <> 0 then
- error (f_"external command failed: %s") cmd
+ error (f_"external command failed: %s") cmd;
-(* Finished. *)
-let () + (* Finished. *)
if not quiet then (
print_newline ();
wrap (s_"Sparsify operation completed with no errors. Before deleting
the old disk, carefully check that the target disk boots and works
correctly.\n");
);
if debug_gc then
- Gc.compact ();
+ Gc.compact ()
- exit 0
+let () + try main ()
+ with
+ | Unix.Unix_error (code, fname, "") -> (* from a syscall *)
+ eprintf (f_"%s: error: %s: %s\n") prog fname (Unix.error_message
code);
+ exit 1
+ | Unix.Unix_error (code, fname, param) -> (* from a syscall *)
+ eprintf (f_"%s: error: %s: %s: %s\n") prog fname
(Unix.error_message code)
+ param;
+ exit 1
+ | G.Error msg -> (* from libguestfs *)
+ eprintf (f_"%s: libguestfs error: %s\n") prog msg;
+ exit 1
+ | Failure msg -> (* from failwith/failwithf *)
+ eprintf (f_"%s: failure: %s\n") prog msg;
+ exit 1
+ | Invalid_argument msg -> (* probably should never happen *)
+ eprintf (f_"%s: internal error: invalid argument: %s\n") prog
msg;
+ exit 1
+ | Assert_failure (file, line, char) -> (* should never happen *)
+ eprintf (f_"%s: internal error: assertion failed at %s, line %d, char
%d\n") prog file line char;
+ exit 1
+ | Not_found -> (* should never happen *)
+ eprintf (f_"%s: internal error: Not_found exception was
thrown\n") prog;
+ exit 1
+ | exn -> (* something not matched above *)
+ eprintf (f_"%s: exception: %s\n") prog (Printexc.to_string exn);
+ exit 1
--
1.8.5.3
Richard W.M. Jones
2014-Mar-11 23:13 UTC
[Libguestfs] [PATCH v2 13/18] sparsify: Remove unused 'open' directive.
--- sparsify/sparsify.ml | 1 - 1 file changed, 1 deletion(-) diff --git a/sparsify/sparsify.ml b/sparsify/sparsify.ml index e79fe78..80c875c 100644 --- a/sparsify/sparsify.ml +++ b/sparsify/sparsify.ml @@ -17,7 +17,6 @@ *) open Unix -open Scanf open Printf open Common_gettext.Gettext -- 1.8.5.3
Richard W.M. Jones
2014-Mar-11 23:13 UTC
[Libguestfs] [PATCH v2 14/18] sparsify: Move command line parsing code to separate file.
This is just code motion.
---
po/POTFILES-ml | 1 +
sparsify/Makefile.am | 2 +
sparsify/cmdline.ml | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++
sparsify/sparsify.ml | 137 +------------------------------------------
4 files changed, 165 insertions(+), 135 deletions(-)
create mode 100644 sparsify/cmdline.ml
diff --git a/po/POTFILES-ml b/po/POTFILES-ml
index 32e4206..ca29c25 100644
--- a/po/POTFILES-ml
+++ b/po/POTFILES-ml
@@ -31,6 +31,7 @@ mllib/timezone.ml
mllib/uRI.ml
mllib/urandom.ml
resize/resize.ml
+sparsify/cmdline.ml
sparsify/sparsify.ml
sysprep/main.ml
sysprep/sysprep_operation.ml
diff --git a/sparsify/Makefile.am b/sparsify/Makefile.am
index 516069a..3a6a011 100644
--- a/sparsify/Makefile.am
+++ b/sparsify/Makefile.am
@@ -26,6 +26,7 @@ CLEANFILES = *~ *.cmi *.cmo *.cmx *.cmxa *.o virt-sparsify
# Alphabetical order.
SOURCES = \
+ cmdline.ml \
sparsify.ml \
statvfs-c.c
@@ -42,6 +43,7 @@ deps = \
$(top_builddir)/mllib/progress.cmx \
$(top_builddir)/mllib/config.cmx \
statvfs-c.o \
+ cmdline.cmx \
sparsify.cmx
if HAVE_OCAMLOPT
diff --git a/sparsify/cmdline.ml b/sparsify/cmdline.ml
new file mode 100644
index 0000000..d803ab1
--- /dev/null
+++ b/sparsify/cmdline.ml
@@ -0,0 +1,160 @@
+(* virt-sparsify
+ * Copyright (C) 2011-2014 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *)
+
+(* Command line argument parsing. *)
+
+open Printf
+
+open Common_gettext.Gettext
+open Common_utils
+
+let prog = Filename.basename Sys.executable_name
+let error fs = error ~prog fs
+
+let parse_cmdline () + let display_version () + printf "virt-sparsify
%s\n" Config.package_version;
+ exit 0
+ in
+
+ let add xs s = xs := s :: !xs in
+
+ let check_tmpdir = ref `Warn in
+ let set_check_tmpdir = function
+ | "ignore" | "i" -> check_tmpdir := `Ignore
+ | "continue" | "cont" | "c" ->
check_tmpdir := `Continue
+ | "warn" | "warning" | "w" -> check_tmpdir
:= `Warn
+ | "fail" | "f" | "error" -> check_tmpdir
:= `Fail
+ | str ->
+ eprintf (f_"--check-tmpdir: unknown argument `%s'\n") str;
+ exit 1
+ in
+
+ let compress = ref false in
+ let convert = ref "" in
+ let debug_gc = ref false in
+ let format = ref "" in
+ let ignores = ref [] in
+ let machine_readable = ref false in
+ let option = ref "" in
+ let quiet = ref false in
+ let verbose = ref false in
+ let trace = ref false in
+ let zeroes = ref [] in
+
+ let ditto = " -\"-" in
+ let argspec = Arg.align [
+ "--check-tmpdir", Arg.String set_check_tmpdir,
"ignore|..." ^ " " ^ s_"Check there is enough space in
$TMPDIR";
+ "--compress", Arg.Set compress, " " ^
s_"Compressed output format";
+ "--convert", Arg.Set_string convert, s_"format" ^
" " ^ s_"Format of output disk (default: same as input)";
+ "--debug-gc", Arg.Set debug_gc, " " ^
s_"Debug GC and memory allocations";
+ "--format", Arg.Set_string format, s_"format" ^
" " ^ s_"Format of input disk";
+ "--ignore", Arg.String (add ignores), s_"fs" ^ "
" ^ s_"Ignore filesystem";
+ "--long-options", Arg.Unit display_long_options, " " ^
s_"List long options";
+ "--machine-readable", Arg.Set machine_readable, " " ^
s_"Make output machine readable";
+ "-o", Arg.Set_string option, s_"option" ^
" " ^ s_"Add qemu-img options";
+ "-q", Arg.Set quiet, " " ^
s_"Quiet output";
+ "--quiet", Arg.Set quiet, ditto;
+ "-v", Arg.Set verbose, " " ^
s_"Enable debugging messages";
+ "--verbose", Arg.Set verbose, ditto;
+ "-V", Arg.Unit display_version, " " ^
s_"Display version and exit";
+ "--version", Arg.Unit display_version, ditto;
+ "-x", Arg.Set trace, " " ^
s_"Enable tracing of libguestfs calls";
+ "--zero", Arg.String (add zeroes), s_"fs" ^ "
" ^ s_"Zero filesystem";
+ ] in
+ long_options := argspec;
+ let disks = ref [] in
+ let anon_fun s = disks := s :: !disks in
+ let usage_msg + sprintf (f_"\
+%s: sparsify a virtual machine disk
+
+ virt-sparsify [--options] indisk outdisk
+
+A short summary of the options is given below. For detailed help please
+read the man page virt-sparsify(1).
+")
+ prog in
+ Arg.parse argspec anon_fun usage_msg;
+
+ (* Dereference the rest of the args. *)
+ let check_tmpdir = !check_tmpdir in
+ let compress = !compress in
+ let convert = match !convert with "" -> None | str -> Some
str in
+ let debug_gc = !debug_gc in
+ let format = match !format with "" -> None | str -> Some str
in
+ let ignores = List.rev !ignores in
+ let machine_readable = !machine_readable in
+ let option = match !option with "" -> None | str -> Some str
in
+ let quiet = !quiet in
+ let verbose = !verbose in
+ let trace = !trace in
+ let zeroes = List.rev !zeroes in
+
+ (* No arguments and machine-readable mode? Print out some facts
+ * about what this binary supports.
+ *)
+ if !disks = [] && machine_readable then (
+ printf "virt-sparsify\n";
+ printf "linux-swap\n";
+ printf "zero\n";
+ printf "check-tmpdir\n";
+ let g = new G.guestfs () in
+ g#add_drive "/dev/null";
+ g#launch ();
+ if g#feature_available [| "ntfsprogs"; "ntfs3g" |] then
+ printf "ntfs\n";
+ if g#feature_available [| "btrfs" |] then
+ printf "btrfs\n";
+ exit 0
+ );
+
+ (* Verify we got exactly 2 disks. *)
+ let indisk, outdisk + match List.rev !disks with
+ | [indisk; outdisk] -> indisk, outdisk
+ | _ ->
+ error "usage is: %s [--options] indisk outdisk" prog in
+
+ (* Simple-minded check that the user isn't trying to use the
+ * same disk for input and output.
+ *)
+ if indisk = outdisk then
+ error (f_"you cannot use the same disk image for input and
output");
+
+ (* The input disk must be an absolute path, so we can store the name
+ * in the overlay disk.
+ *)
+ let indisk + if not (Filename.is_relative indisk) then
+ indisk
+ else
+ Sys.getcwd () // indisk in
+
+ (* Check the output is not a block or char special (RHBZ#1056290). *)
+ if is_block_device outdisk then
+ error (f_"output '%s' cannot be a block device, it must be a
regular file")
+ outdisk;
+
+ if is_char_device outdisk then
+ error (f_"output '%s' cannot be a character device, it must be
a regular file")
+ outdisk;
+
+ indisk, outdisk, check_tmpdir, compress, convert,
+ debug_gc, format, ignores, machine_readable,
+ option, quiet, verbose, trace, zeroes
diff --git a/sparsify/sparsify.ml b/sparsify/sparsify.ml
index 80c875c..c9692d4 100644
--- a/sparsify/sparsify.ml
+++ b/sparsify/sparsify.ml
@@ -24,151 +24,18 @@ open Common_gettext.Gettext
module G = Guestfs
open Common_utils
+open Cmdline
external statvfs_free_space : string -> int64
"virt_sparsify_statvfs_free_space"
let () = Random.self_init ()
-let prog = Filename.basename Sys.executable_name
-let error fs = error ~prog fs
-
let main () - (* Command line argument parsing. *)
let indisk, outdisk, check_tmpdir, compress, convert, debug_gc,
format, ignores, machine_readable,
option, quiet, verbose, trace, zeroes - let display_version () -
printf "virt-sparsify %s\n" Config.package_version;
- exit 0
- in
-
- let add xs s = xs := s :: !xs in
-
- let check_tmpdir = ref `Warn in
- let set_check_tmpdir = function
- | "ignore" | "i" -> check_tmpdir := `Ignore
- | "continue" | "cont" | "c" ->
check_tmpdir := `Continue
- | "warn" | "warning" | "w" ->
check_tmpdir := `Warn
- | "fail" | "f" | "error" -> check_tmpdir
:= `Fail
- | str ->
- eprintf (f_"--check-tmpdir: unknown argument `%s'\n")
str;
- exit 1
- in
-
- let compress = ref false in
- let convert = ref "" in
- let debug_gc = ref false in
- let format = ref "" in
- let ignores = ref [] in
- let machine_readable = ref false in
- let option = ref "" in
- let quiet = ref false in
- let verbose = ref false in
- let trace = ref false in
- let zeroes = ref [] in
-
- let ditto = " -\"-" in
- let argspec = Arg.align [
- "--check-tmpdir", Arg.String set_check_tmpdir,
"ignore|..." ^ " " ^ s_"Check there is enough space in
$TMPDIR";
- "--compress", Arg.Set compress, " " ^
s_"Compressed output format";
- "--convert", Arg.Set_string convert, s_"format" ^
" " ^ s_"Format of output disk (default: same as input)";
- "--debug-gc", Arg.Set debug_gc, " " ^
s_"Debug GC and memory allocations";
- "--format", Arg.Set_string format, s_"format" ^
" " ^ s_"Format of input disk";
- "--ignore", Arg.String (add ignores), s_"fs" ^
" " ^ s_"Ignore filesystem";
- "--long-options", Arg.Unit display_long_options, " "
^ s_"List long options";
- "--machine-readable", Arg.Set machine_readable, " " ^
s_"Make output machine readable";
- "-o", Arg.Set_string option, s_"option" ^
" " ^ s_"Add qemu-img options";
- "-q", Arg.Set quiet, " " ^
s_"Quiet output";
- "--quiet", Arg.Set quiet, ditto;
- "-v", Arg.Set verbose, " " ^
s_"Enable debugging messages";
- "--verbose", Arg.Set verbose, ditto;
- "-V", Arg.Unit display_version, " " ^
s_"Display version and exit";
- "--version", Arg.Unit display_version, ditto;
- "-x", Arg.Set trace, " " ^
s_"Enable tracing of libguestfs calls";
- "--zero", Arg.String (add zeroes), s_"fs" ^
" " ^ s_"Zero filesystem";
- ] in
- long_options := argspec;
- let disks = ref [] in
- let anon_fun s = disks := s :: !disks in
- let usage_msg - sprintf (f_"\
-%s: sparsify a virtual machine disk
-
- virt-sparsify [--options] indisk outdisk
-
-A short summary of the options is given below. For detailed help please
-read the man page virt-sparsify(1).
-")
- prog in
- Arg.parse argspec anon_fun usage_msg;
-
- (* Dereference the rest of the args. *)
- let check_tmpdir = !check_tmpdir in
- let compress = !compress in
- let convert = match !convert with "" -> None | str -> Some
str in
- let debug_gc = !debug_gc in
- let format = match !format with "" -> None | str -> Some
str in
- let ignores = List.rev !ignores in
- let machine_readable = !machine_readable in
- let option = match !option with "" -> None | str -> Some
str in
- let quiet = !quiet in
- let verbose = !verbose in
- let trace = !trace in
- let zeroes = List.rev !zeroes in
-
- (* No arguments and machine-readable mode? Print out some facts
- * about what this binary supports.
- *)
- if !disks = [] && machine_readable then (
- printf "virt-sparsify\n";
- printf "linux-swap\n";
- printf "zero\n";
- printf "check-tmpdir\n";
- let g = new G.guestfs () in
- g#add_drive "/dev/null";
- g#launch ();
- if g#feature_available [| "ntfsprogs"; "ntfs3g" |]
then
- printf "ntfs\n";
- if g#feature_available [| "btrfs" |] then
- printf "btrfs\n";
- exit 0
- );
-
- (* Verify we got exactly 2 disks. *)
- let indisk, outdisk - match List.rev !disks with
- | [indisk; outdisk] -> indisk, outdisk
- | _ ->
- error "usage is: %s [--options] indisk outdisk" prog in
-
- (* Simple-minded check that the user isn't trying to use the
- * same disk for input and output.
- *)
- if indisk = outdisk then
- error (f_"you cannot use the same disk image for input and
output");
-
- (* The input disk must be an absolute path, so we can store the name
- * in the overlay disk.
- *)
- let indisk - if not (Filename.is_relative indisk) then
- indisk
- else
- Sys.getcwd () // indisk in
-
- (* Check the output is not a block or char special (RHBZ#1056290). *)
- if is_block_device outdisk then
- error (f_"output '%s' cannot be a block device, it must be a
regular file")
- outdisk;
-
- if is_char_device outdisk then
- error (f_"output '%s' cannot be a character device, it must
be a regular file")
- outdisk;
-
- indisk, outdisk, check_tmpdir, compress, convert,
- debug_gc, format, ignores, machine_readable,
- option, quiet, verbose, trace, zeroes in
+ parse_cmdline () in
(* Once we have got past argument parsing and start to create
* temporary files (including the potentially massive overlay file), we
--
1.8.5.3
Richard W.M. Jones
2014-Mar-11 23:13 UTC
[Libguestfs] [PATCH v2 15/18] sparsify: Refactor command line parsing to pass back a mode.
This is just code motion, but sets the ground-work for adding a second
mode (in-place image modification).
---
sparsify/cmdline.ml | 10 +++++++---
sparsify/sparsify.ml | 24 ++++++++++++++++--------
2 files changed, 23 insertions(+), 11 deletions(-)
diff --git a/sparsify/cmdline.ml b/sparsify/cmdline.ml
index d803ab1..b714a9e 100644
--- a/sparsify/cmdline.ml
+++ b/sparsify/cmdline.ml
@@ -26,6 +26,10 @@ open Common_utils
let prog = Filename.basename Sys.executable_name
let error fs = error ~prog fs
+type mode_t + Mode_copying of string * check_t * bool * string option * string
option
+and check_t = [`Ignore|`Continue|`Warn|`Fail]
+
let parse_cmdline () let display_version () printf "virt-sparsify
%s\n" Config.package_version;
@@ -155,6 +159,6 @@ read the man page virt-sparsify(1).
error (f_"output '%s' cannot be a character device, it must be
a regular file")
outdisk;
- indisk, outdisk, check_tmpdir, compress, convert,
- debug_gc, format, ignores, machine_readable,
- option, quiet, verbose, trace, zeroes
+ indisk, debug_gc, format, ignores, machine_readable,
+ quiet, verbose, trace, zeroes,
+ Mode_copying (outdisk, check_tmpdir, compress, convert, option)
diff --git a/sparsify/sparsify.ml b/sparsify/sparsify.ml
index c9692d4..0091d95 100644
--- a/sparsify/sparsify.ml
+++ b/sparsify/sparsify.ml
@@ -31,12 +31,23 @@ external statvfs_free_space : string -> int64
let () = Random.self_init ()
-let main () - let indisk, outdisk, check_tmpdir, compress, convert, debug_gc,
- format, ignores, machine_readable,
- option, quiet, verbose, trace, zeroes +let rec main () + let indisk,
debug_gc, format, ignores, machine_readable,
+ quiet, verbose, trace, zeroes, mode parse_cmdline () in
+ (match mode with
+ | Mode_copying (outdisk, check_tmpdir, compress, convert, option) ->
+ copying indisk outdisk check_tmpdir compress convert
+ format ignores machine_readable option quiet verbose trace zeroes
+ );
+
+ if debug_gc then
+ Gc.compact ()
+
+and copying indisk outdisk check_tmpdir compress convert
+ format ignores machine_readable option quiet verbose trace zeroes +
(* Once we have got past argument parsing and start to create
* temporary files (including the potentially massive overlay file), we
* need to catch SIGINT (^C) and exit cleanly so the temporary file
@@ -274,10 +285,7 @@ You can ignore this warning or change it to a hard failure
using the
if not quiet then (
print_newline ();
wrap (s_"Sparsify operation completed with no errors. Before deleting
the old disk, carefully check that the target disk boots and works
correctly.\n");
- );
-
- if debug_gc then
- Gc.compact ()
+ )
let () try main ()
--
1.8.5.3
Richard W.M. Jones
2014-Mar-11 23:13 UTC
[Libguestfs] [PATCH v2 16/18] sparsify: Move copying-mode code to a separate file.
This is just code motion.
---
po/POTFILES-ml | 1 +
sparsify/Makefile.am | 2 +
sparsify/copying.ml | 276 +++++++++++++++++++++++++++++++++++++++++++++++++++
sparsify/sparsify.ml | 247 +--------------------------------------------
4 files changed, 280 insertions(+), 246 deletions(-)
create mode 100644 sparsify/copying.ml
diff --git a/po/POTFILES-ml b/po/POTFILES-ml
index ca29c25..fd2e886 100644
--- a/po/POTFILES-ml
+++ b/po/POTFILES-ml
@@ -32,6 +32,7 @@ mllib/uRI.ml
mllib/urandom.ml
resize/resize.ml
sparsify/cmdline.ml
+sparsify/copying.ml
sparsify/sparsify.ml
sysprep/main.ml
sysprep/sysprep_operation.ml
diff --git a/sparsify/Makefile.am b/sparsify/Makefile.am
index 3a6a011..2f32093 100644
--- a/sparsify/Makefile.am
+++ b/sparsify/Makefile.am
@@ -27,6 +27,7 @@ CLEANFILES = *~ *.cmi *.cmo *.cmx *.cmxa *.o virt-sparsify
# Alphabetical order.
SOURCES = \
cmdline.ml \
+ copying.ml \
sparsify.ml \
statvfs-c.c
@@ -44,6 +45,7 @@ deps = \
$(top_builddir)/mllib/config.cmx \
statvfs-c.o \
cmdline.cmx \
+ copying.cmx \
sparsify.cmx
if HAVE_OCAMLOPT
diff --git a/sparsify/copying.ml b/sparsify/copying.ml
new file mode 100644
index 0000000..2765cf4
--- /dev/null
+++ b/sparsify/copying.ml
@@ -0,0 +1,276 @@
+(* virt-sparsify
+ * Copyright (C) 2011-2014 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *)
+
+(* This is the traditional virt-sparsify mode: We copy from a
+ * source disk to a destination disk.
+ *)
+
+open Unix
+open Printf
+
+open Common_gettext.Gettext
+
+module G = Guestfs
+
+open Common_utils
+open Cmdline
+
+external statvfs_free_space : string -> int64 +
"virt_sparsify_statvfs_free_space"
+
+let run indisk outdisk check_tmpdir compress convert
+ format ignores machine_readable option quiet verbose trace zeroes +
+ (* Once we have got past argument parsing and start to create
+ * temporary files (including the potentially massive overlay file), we
+ * need to catch SIGINT (^C) and exit cleanly so the temporary file
+ * goes away. Note that we don't delete temporaries in the signal
+ * handler.
+ *)
+ let do_sigint _ = exit 1 in
+ Sys.set_signal Sys.sigint (Sys.Signal_handle do_sigint);
+
+ (* What should the output format be? If the user specified an
+ * input format, use that, else detect it from the source image.
+ *)
+ let output_format + match convert with
+ | Some fmt -> fmt (* user specified output conversion *)
+ | None ->
+ match format with
+ | Some fmt -> fmt (* user specified input format, use that *)
+ | None ->
+ (* Don't know, so we must autodetect. *)
+ match (new G.guestfs ())#disk_format indisk with
+ | "unknown" ->
+ error (f_"cannot detect input disk format; use the --format
parameter")
+ | fmt -> fmt in
+
+ (* Compression is not supported by raw output (RHBZ#852194). *)
+ if output_format = "raw" && compress then
+ error (f_"--compress cannot be used for raw output. Remove this
option or use --convert qcow2.");
+
+ (* Get virtual size of the input disk. *)
+ let virtual_size = (new G.guestfs ())#disk_virtual_size indisk in
+ if not quiet then
+ printf (f_"Input disk virtual size = %Ld bytes (%s)\n%!")
+ virtual_size (human_size virtual_size);
+
+ (* Check there is enough space in $TMPDIR. *)
+ let tmpdir = Filename.temp_dir_name in
+
+ let print_warning () + let free_space = statvfs_free_space tmpdir in
+ let extra_needed = virtual_size -^ free_space in
+ if extra_needed > 0L then (
+ eprintf (f_"\
+
+WARNING: There may not be enough free space on %s.
+You may need to set TMPDIR to point to a directory with more free space.
+
+Max needed: %s. Free: %s. May need another %s.
+
+Note this is an overestimate. If the guest disk is full of data
+then not as much free space would be required.
+
+You can ignore this warning or change it to a hard failure using the
+--check-tmpdir=(ignore|continue|warn|fail) option. See virt-sparsify(1).
+
+%!")
+ tmpdir (human_size virtual_size)
+ (human_size free_space) (human_size extra_needed);
+ true
+ ) else false
+ in
+
+ (match check_tmpdir with
+ | `Ignore -> ()
+ | `Continue -> ignore (print_warning ())
+ | `Warn ->
+ if print_warning () then (
+ eprintf "Press RETURN to continue or ^C to quit.\n%!";
+ ignore (read_line ())
+ );
+ | `Fail ->
+ if print_warning () then (
+ eprintf "Exiting because --check-tmpdir=fail was set.\n%!";
+ exit 2
+ )
+ );
+
+ if not quiet then
+ printf (f_"Create overlay file in %s to protect source disk
...\n%!") tmpdir;
+
+ (* Create the temporary overlay file. *)
+ let overlaydisk + let tmp = Filename.temp_file "sparsify"
".qcow2" in
+ unlink_on_exit tmp;
+
+ (* Create it with the indisk as the backing file. *)
+ (* XXX Old code used to:
+ * - detect if compat=1.1 was supported
+ * - add lazy_refcounts option
+ *)
+ (new G.guestfs ())#disk_create
+ ~backingfile:indisk ?backingformat:format ~compat:"1.1"
+ tmp "qcow2" Int64.minus_one;
+
+ tmp in
+
+ if not quiet then
+ printf (f_"Examine source disk ...\n%!");
+
+ (* Connect to libguestfs. *)
+ let g + let g = new G.guestfs () in
+ if trace then g#set_trace true;
+ if verbose then g#set_verbose true;
+
+ (* Note that the temporary overlay disk is always qcow2 format. *)
+ g#add_drive ~format:"qcow2" ~readonly:false
~cachemode:"unsafe" overlaydisk;
+
+ if not quiet then Progress.set_up_progress_bar ~machine_readable g;
+ g#launch ();
+
+ g in
+
+ (* Modify SIGINT handler (set first above) to cancel the handle. *)
+ let do_sigint _ + g#user_cancel ();
+ exit 1
+ in
+ Sys.set_signal Sys.sigint (Sys.Signal_handle do_sigint);
+
+ (* Write zeroes for non-ignored filesystems that we are able to mount,
+ * and selected swap partitions.
+ *)
+ let filesystems = g#list_filesystems () in
+ let filesystems = List.map fst filesystems in
+ let filesystems = List.sort compare filesystems in
+
+ let is_ignored fs + let fs = g#canonical_device_name fs in
+ List.exists (fun fs' -> fs = g#canonical_device_name fs')
ignores
+ in
+
+ List.iter (
+ fun fs ->
+ if not (is_ignored fs) then (
+ if List.mem fs zeroes then (
+ if not quiet then
+ printf (f_"Zeroing %s ...\n%!") fs;
+
+ g#zero_device fs
+ ) else (
+ let mounted + try g#mount fs "/"; true
+ with _ -> false in
+
+ if mounted then (
+ if not quiet then
+ printf (f_"Fill free space in %s with zero ...\n%!")
fs;
+
+ g#zero_free_space "/"
+ ) else (
+ let is_linux_x86_swap + (* Look for the signature for
Linux swap on i386.
+ * Location depends on page size, so it definitely won't
+ * work on non-x86 architectures (eg. on PPC, page size is
+ * 64K). Also this avoids hibernated swap space: in those,
+ * the signature is moved to a different location.
+ *)
+ try g#pread_device fs 10 4086L = "SWAPSPACE2"
+ with _ -> false in
+
+ if is_linux_x86_swap then (
+ if not quiet then
+ printf (f_"Clearing Linux swap on %s ...\n%!") fs;
+
+ (* Don't use mkswap. Just preserve the header containing
+ * the label, UUID and swap format version (libguestfs
+ * mkswap may differ from guest's own).
+ *)
+ let header = g#pread_device fs 4096 0L in
+ g#zero_device fs;
+ if g#pwrite_device fs header 0L <> 4096 then
+ error (f_"pwrite: short write restoring swap partition
header")
+ )
+ )
+ );
+
+ g#umount_all ()
+ )
+ ) filesystems;
+
+ (* Fill unused space in volume groups. *)
+ let vgs = g#vgs () in
+ let vgs = Array.to_list vgs in
+ let vgs = List.sort compare vgs in
+ List.iter (
+ fun vg ->
+ if not (List.mem vg ignores) then (
+ let lvname = string_random8 () in
+ let lvdev = "/dev/" ^ vg ^ "/" ^ lvname in
+
+ let created + try g#lvcreate_free lvname vg 100; true
+ with _ -> false in
+
+ if created then (
+ if not quiet then
+ printf (f_"Fill free space in volgroup %s with zero
...\n%!") vg;
+
+ g#zero_device lvdev;
+ g#sync ();
+ g#lvremove lvdev
+ )
+ )
+ ) vgs;
+
+ (* Don't need libguestfs now. *)
+ g#shutdown ();
+ g#close ();
+
+ (* Modify SIGINT handler (set first above) to just exit. *)
+ let do_sigint _ = exit 1 in
+ Sys.set_signal Sys.sigint (Sys.Signal_handle do_sigint);
+
+ (* Now run qemu-img convert which copies the overlay to the
+ * destination and automatically does sparsification.
+ *)
+ if not quiet then
+ printf (f_"Copy to destination and make sparse ...\n%!");
+
+ let cmd + sprintf "qemu-img convert -f qcow2 -O %s%s%s %s %s"
+ (Filename.quote output_format)
+ (if compress then " -c" else "")
+ (match option with
+ | None -> ""
+ | Some option -> " -o " ^ Filename.quote option)
+ (Filename.quote overlaydisk) (Filename.quote outdisk) in
+ if verbose then
+ printf "%s\n%!" cmd;
+ if Sys.command cmd <> 0 then
+ error (f_"external command failed: %s") cmd;
+
+ (* Finished. *)
+ if not quiet then (
+ print_newline ();
+ wrap (s_"Sparsify operation completed with no errors. Before deleting
the old disk, carefully check that the target disk boots and works
correctly.\n");
+ )
diff --git a/sparsify/sparsify.ml b/sparsify/sparsify.ml
index 0091d95..faefb23 100644
--- a/sparsify/sparsify.ml
+++ b/sparsify/sparsify.ml
@@ -26,9 +26,6 @@ module G = Guestfs
open Common_utils
open Cmdline
-external statvfs_free_space : string -> int64 -
"virt_sparsify_statvfs_free_space"
-
let () = Random.self_init ()
let rec main () @@ -38,255 +35,13 @@ let rec main ()
(match mode with
| Mode_copying (outdisk, check_tmpdir, compress, convert, option) ->
- copying indisk outdisk check_tmpdir compress convert
+ Copying.run indisk outdisk check_tmpdir compress convert
format ignores machine_readable option quiet verbose trace zeroes
);
if debug_gc then
Gc.compact ()
-and copying indisk outdisk check_tmpdir compress convert
- format ignores machine_readable option quiet verbose trace zeroes -
- (* Once we have got past argument parsing and start to create
- * temporary files (including the potentially massive overlay file), we
- * need to catch SIGINT (^C) and exit cleanly so the temporary file
- * goes away. Note that we don't delete temporaries in the signal
- * handler.
- *)
- let do_sigint _ = exit 1 in
- Sys.set_signal Sys.sigint (Sys.Signal_handle do_sigint);
-
- (* What should the output format be? If the user specified an
- * input format, use that, else detect it from the source image.
- *)
- let output_format - match convert with
- | Some fmt -> fmt (* user specified output conversion *)
- | None ->
- match format with
- | Some fmt -> fmt (* user specified input format, use that *)
- | None ->
- (* Don't know, so we must autodetect. *)
- match (new G.guestfs ())#disk_format indisk with
- | "unknown" ->
- error (f_"cannot detect input disk format; use the --format
parameter")
- | fmt -> fmt in
-
- (* Compression is not supported by raw output (RHBZ#852194). *)
- if output_format = "raw" && compress then
- error (f_"--compress cannot be used for raw output. Remove this
option or use --convert qcow2.");
-
- (* Get virtual size of the input disk. *)
- let virtual_size = (new G.guestfs ())#disk_virtual_size indisk in
- if not quiet then
- printf (f_"Input disk virtual size = %Ld bytes (%s)\n%!")
- virtual_size (human_size virtual_size);
-
- (* Check there is enough space in $TMPDIR. *)
- let tmpdir = Filename.temp_dir_name in
-
- let print_warning () - let free_space = statvfs_free_space tmpdir in
- let extra_needed = virtual_size -^ free_space in
- if extra_needed > 0L then (
- eprintf (f_"\
-
-WARNING: There may not be enough free space on %s.
-You may need to set TMPDIR to point to a directory with more free space.
-
-Max needed: %s. Free: %s. May need another %s.
-
-Note this is an overestimate. If the guest disk is full of data
-then not as much free space would be required.
-
-You can ignore this warning or change it to a hard failure using the
---check-tmpdir=(ignore|continue|warn|fail) option. See virt-sparsify(1).
-
-%!")
- tmpdir (human_size virtual_size)
- (human_size free_space) (human_size extra_needed);
- true
- ) else false
- in
-
- (match check_tmpdir with
- | `Ignore -> ()
- | `Continue -> ignore (print_warning ())
- | `Warn ->
- if print_warning () then (
- eprintf "Press RETURN to continue or ^C to quit.\n%!";
- ignore (read_line ())
- );
- | `Fail ->
- if print_warning () then (
- eprintf "Exiting because --check-tmpdir=fail was set.\n%!";
- exit 2
- )
- );
-
- if not quiet then
- printf (f_"Create overlay file in %s to protect source disk
...\n%!") tmpdir;
-
- (* Create the temporary overlay file. *)
- let overlaydisk - let tmp = Filename.temp_file "sparsify"
".qcow2" in
- unlink_on_exit tmp;
-
- (* Create it with the indisk as the backing file. *)
- (* XXX Old code used to:
- * - detect if compat=1.1 was supported
- * - add lazy_refcounts option
- *)
- (new G.guestfs ())#disk_create
- ~backingfile:indisk ?backingformat:format ~compat:"1.1"
- tmp "qcow2" Int64.minus_one;
-
- tmp in
-
- if not quiet then
- printf (f_"Examine source disk ...\n%!");
-
- (* Connect to libguestfs. *)
- let g - let g = new G.guestfs () in
- if trace then g#set_trace true;
- if verbose then g#set_verbose true;
-
- (* Note that the temporary overlay disk is always qcow2 format. *)
- g#add_drive ~format:"qcow2" ~readonly:false
~cachemode:"unsafe" overlaydisk;
-
- if not quiet then Progress.set_up_progress_bar ~machine_readable g;
- g#launch ();
-
- g in
-
- (* Modify SIGINT handler (set first above) to cancel the handle. *)
- let do_sigint _ - g#user_cancel ();
- exit 1
- in
- Sys.set_signal Sys.sigint (Sys.Signal_handle do_sigint);
-
- (* Write zeroes for non-ignored filesystems that we are able to mount,
- * and selected swap partitions.
- *)
- let filesystems = g#list_filesystems () in
- let filesystems = List.map fst filesystems in
- let filesystems = List.sort compare filesystems in
-
- let is_ignored fs - let fs = g#canonical_device_name fs in
- List.exists (fun fs' -> fs = g#canonical_device_name fs')
ignores
- in
-
- List.iter (
- fun fs ->
- if not (is_ignored fs) then (
- if List.mem fs zeroes then (
- if not quiet then
- printf (f_"Zeroing %s ...\n%!") fs;
-
- g#zero_device fs
- ) else (
- let mounted - try g#mount fs "/"; true
- with _ -> false in
-
- if mounted then (
- if not quiet then
- printf (f_"Fill free space in %s with zero ...\n%!")
fs;
-
- g#zero_free_space "/"
- ) else (
- let is_linux_x86_swap - (* Look for the signature for
Linux swap on i386.
- * Location depends on page size, so it definitely won't
- * work on non-x86 architectures (eg. on PPC, page size is
- * 64K). Also this avoids hibernated swap space: in those,
- * the signature is moved to a different location.
- *)
- try g#pread_device fs 10 4086L = "SWAPSPACE2"
- with _ -> false in
-
- if is_linux_x86_swap then (
- if not quiet then
- printf (f_"Clearing Linux swap on %s ...\n%!") fs;
-
- (* Don't use mkswap. Just preserve the header containing
- * the label, UUID and swap format version (libguestfs
- * mkswap may differ from guest's own).
- *)
- let header = g#pread_device fs 4096 0L in
- g#zero_device fs;
- if g#pwrite_device fs header 0L <> 4096 then
- error (f_"pwrite: short write restoring swap partition
header")
- )
- )
- );
-
- g#umount_all ()
- )
- ) filesystems;
-
- (* Fill unused space in volume groups. *)
- let vgs = g#vgs () in
- let vgs = Array.to_list vgs in
- let vgs = List.sort compare vgs in
- List.iter (
- fun vg ->
- if not (List.mem vg ignores) then (
- let lvname = string_random8 () in
- let lvdev = "/dev/" ^ vg ^ "/" ^ lvname in
-
- let created - try g#lvcreate_free lvname vg 100; true
- with _ -> false in
-
- if created then (
- if not quiet then
- printf (f_"Fill free space in volgroup %s with zero
...\n%!") vg;
-
- g#zero_device lvdev;
- g#sync ();
- g#lvremove lvdev
- )
- )
- ) vgs;
-
- (* Don't need libguestfs now. *)
- g#shutdown ();
- g#close ();
-
- (* Modify SIGINT handler (set first above) to just exit. *)
- let do_sigint _ = exit 1 in
- Sys.set_signal Sys.sigint (Sys.Signal_handle do_sigint);
-
- (* Now run qemu-img convert which copies the overlay to the
- * destination and automatically does sparsification.
- *)
- if not quiet then
- printf (f_"Copy to destination and make sparse ...\n%!");
-
- let cmd - sprintf "qemu-img convert -f qcow2 -O %s%s%s %s %s"
- (Filename.quote output_format)
- (if compress then " -c" else "")
- (match option with
- | None -> ""
- | Some option -> " -o " ^ Filename.quote option)
- (Filename.quote overlaydisk) (Filename.quote outdisk) in
- if verbose then
- printf "%s\n%!" cmd;
- if Sys.command cmd <> 0 then
- error (f_"external command failed: %s") cmd;
-
- (* Finished. *)
- if not quiet then (
- print_newline ();
- wrap (s_"Sparsify operation completed with no errors. Before deleting
the old disk, carefully check that the target disk boots and works
correctly.\n");
- )
-
let () try main ()
with
--
1.8.5.3
Richard W.M. Jones
2014-Mar-11 23:14 UTC
[Libguestfs] [PATCH v2 17/18] sparsify: Add virt-sparsify --in-place mode.
---
po/POTFILES-ml | 1 +
sparsify/Makefile.am | 2 +
sparsify/cmdline.ml | 77 +++++++++++++++++-------
sparsify/in_place.ml | 143 +++++++++++++++++++++++++++++++++++++++++++++
sparsify/sparsify.ml | 3 +
sparsify/virt-sparsify.pod | 63 +++++++++++++++++---
6 files changed, 259 insertions(+), 30 deletions(-)
create mode 100644 sparsify/in_place.ml
diff --git a/po/POTFILES-ml b/po/POTFILES-ml
index fd2e886..2c37d91 100644
--- a/po/POTFILES-ml
+++ b/po/POTFILES-ml
@@ -33,6 +33,7 @@ mllib/urandom.ml
resize/resize.ml
sparsify/cmdline.ml
sparsify/copying.ml
+sparsify/in_place.ml
sparsify/sparsify.ml
sysprep/main.ml
sysprep/sysprep_operation.ml
diff --git a/sparsify/Makefile.am b/sparsify/Makefile.am
index 2f32093..811c131 100644
--- a/sparsify/Makefile.am
+++ b/sparsify/Makefile.am
@@ -28,6 +28,7 @@ CLEANFILES = *~ *.cmi *.cmo *.cmx *.cmxa *.o virt-sparsify
SOURCES = \
cmdline.ml \
copying.ml \
+ in_place.ml \
sparsify.ml \
statvfs-c.c
@@ -46,6 +47,7 @@ deps = \
statvfs-c.o \
cmdline.cmx \
copying.cmx \
+ in_place.cmx \
sparsify.cmx
if HAVE_OCAMLOPT
diff --git a/sparsify/cmdline.ml b/sparsify/cmdline.ml
index b714a9e..d25e9e9 100644
--- a/sparsify/cmdline.ml
+++ b/sparsify/cmdline.ml
@@ -27,7 +27,8 @@ let prog = Filename.basename Sys.executable_name
let error fs = error ~prog fs
type mode_t - Mode_copying of string * check_t * bool * string option * string
option
+| Mode_copying of string * check_t * bool * string option * string option
+| Mode_in_place
and check_t = [`Ignore|`Continue|`Warn|`Fail]
let parse_cmdline () @@ -54,6 +55,7 @@ let parse_cmdline () let debug_gc =
ref false in
let format = ref "" in
let ignores = ref [] in
+ let in_place = ref false in
let machine_readable = ref false in
let option = ref "" in
let quiet = ref false in
@@ -69,6 +71,8 @@ let parse_cmdline () "--debug-gc", Arg.Set
debug_gc, " " ^ s_"Debug GC and memory allocations";
"--format", Arg.Set_string format, s_"format" ^
" " ^ s_"Format of input disk";
"--ignore", Arg.String (add ignores), s_"fs" ^ "
" ^ s_"Ignore filesystem";
+ "--in-place", Arg.Set in_place, " " ^
s_"Modify the disk image in-place";
+ "--inplace", Arg.Set in_place, ditto;
"--long-options", Arg.Unit display_long_options, " " ^
s_"List long options";
"--machine-readable", Arg.Set machine_readable, " " ^
s_"Make output machine readable";
"-o", Arg.Set_string option, s_"option" ^
" " ^ s_"Add qemu-img options";
@@ -90,6 +94,8 @@ let parse_cmdline ()
virt-sparsify [--options] indisk outdisk
+ virt-sparsify [--options] --in-place disk
+
A short summary of the options is given below. For detailed help please
read the man page virt-sparsify(1).
")
@@ -103,6 +109,7 @@ read the man page virt-sparsify(1).
let debug_gc = !debug_gc in
let format = match !format with "" -> None | str -> Some str
in
let ignores = List.rev !ignores in
+ let in_place = !in_place in
let machine_readable = !machine_readable in
let option = match !option with "" -> None | str -> Some str
in
let quiet = !quiet in
@@ -118,6 +125,7 @@ read the man page virt-sparsify(1).
printf "linux-swap\n";
printf "zero\n";
printf "check-tmpdir\n";
+ printf "in-place\n";
let g = new G.guestfs () in
g#add_drive "/dev/null";
g#launch ();
@@ -128,12 +136,14 @@ read the man page virt-sparsify(1).
exit 0
);
- (* Verify we got exactly 2 disks. *)
+ (* Verify we got exactly 1 or 2 disks, depending on the mode. *)
let indisk, outdisk - match List.rev !disks with
- | [indisk; outdisk] -> indisk, outdisk
+ match in_place, List.rev !disks with
+ | false, [indisk; outdisk] -> indisk, outdisk
+ | true, [disk] -> disk, ""
| _ ->
- error "usage is: %s [--options] indisk outdisk" prog in
+ error "usage is: %s [--options] indisk outdisk OR %s --in-place
disk"
+ prog prog in
(* Simple-minded check that the user isn't trying to use the
* same disk for input and output.
@@ -141,24 +151,49 @@ read the man page virt-sparsify(1).
if indisk = outdisk then
error (f_"you cannot use the same disk image for input and
output");
- (* The input disk must be an absolute path, so we can store the name
- * in the overlay disk.
- *)
let indisk - if not (Filename.is_relative indisk) then
+ if not in_place then (
+ (* The input disk must be an absolute path, so we can store the name
+ * in the overlay disk.
+ *)
+ let indisk + if not (Filename.is_relative indisk) then
+ indisk
+ else
+ Sys.getcwd () // indisk in
+
+ (* Check the output is not a block or char special (RHBZ#1056290). *)
+ if is_block_device outdisk then
+ error (f_"output '%s' cannot be a block device, it must be
a regular file")
+ outdisk;
+
+ if is_char_device outdisk then
+ error (f_"output '%s' cannot be a character device, it
must be a regular file")
+ outdisk;
+
indisk
+ )
+ else ( (* --in-place checks *)
+ if check_tmpdir <> `Warn then
+ error (f_"you cannot use --in-place and --check-tmpdir options
together");
+
+ if compress then
+ error (f_"you cannot use --in-place and --compress options
together");
+
+ if convert <> None then
+ error (f_"you cannot use --in-place and --convert options
together");
+
+ if option <> None then
+ error (f_"you cannot use --in-place and -o options
together");
+
+ indisk
+ ) in
+
+ let mode + if not in_place then
+ Mode_copying (outdisk, check_tmpdir, compress, convert, option)
else
- Sys.getcwd () // indisk in
-
- (* Check the output is not a block or char special (RHBZ#1056290). *)
- if is_block_device outdisk then
- error (f_"output '%s' cannot be a block device, it must be a
regular file")
- outdisk;
-
- if is_char_device outdisk then
- error (f_"output '%s' cannot be a character device, it must be
a regular file")
- outdisk;
+ Mode_in_place in
indisk, debug_gc, format, ignores, machine_readable,
- quiet, verbose, trace, zeroes,
- Mode_copying (outdisk, check_tmpdir, compress, convert, option)
+ quiet, verbose, trace, zeroes, mode
diff --git a/sparsify/in_place.ml b/sparsify/in_place.ml
new file mode 100644
index 0000000..9f46ad3
--- /dev/null
+++ b/sparsify/in_place.ml
@@ -0,0 +1,143 @@
+(* virt-sparsify
+ * Copyright (C) 2011-2014 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *)
+
+(* This is the virt-sparsify --in-place mode. *)
+
+open Unix
+open Printf
+
+open Common_gettext.Gettext
+
+module G = Guestfs
+
+open Common_utils
+open Cmdline
+
+let run disk format ignores machine_readable quiet verbose trace zeroes + (*
Connect to libguestfs. *)
+ let g = new G.guestfs () in
+ if trace then g#set_trace true;
+ if verbose then g#set_verbose true;
+
+ (* XXX Current limitation of the API. Can remove this hunk in future. *)
+ let format + match format with
+ | Some _ -> format
+ | None -> Some (g#disk_format disk) in
+
+ g#add_drive ?format ~discard:"enable" disk;
+
+ if not quiet then Progress.set_up_progress_bar ~machine_readable g;
+ g#launch ();
+
+ (* Discard non-ignored filesystems that we are able to mount, and
+ * selected swap partitions.
+ *)
+ let filesystems = g#list_filesystems () in
+ let filesystems = List.map fst filesystems in
+ let filesystems = List.sort compare filesystems in
+
+ let is_ignored fs + let fs = g#canonical_device_name fs in
+ List.exists (fun fs' -> fs = g#canonical_device_name fs')
ignores
+ in
+
+ List.iter (
+ fun fs ->
+ if not (is_ignored fs) then (
+ if List.mem fs zeroes then (
+ if not quiet then
+ printf (f_"Zeroing %s ...\n%!") fs;
+
+ if not (g#blkdiscardzeroes fs) then
+ g#zero_device fs;
+ g#blkdiscard fs
+ ) else (
+ let mounted + try g#mount_options "discard" fs
"/"; true
+ with _ -> false in
+
+ if mounted then (
+ if not quiet then
+ printf (f_"Trimming %s ...\n%!") fs;
+
+ g#fstrim "/"
+ ) else (
+ let is_linux_x86_swap + (* Look for the signature for
Linux swap on i386.
+ * Location depends on page size, so it definitely won't
+ * work on non-x86 architectures (eg. on PPC, page size is
+ * 64K). Also this avoids hibernated swap space: in those,
+ * the signature is moved to a different location.
+ *)
+ try g#pread_device fs 10 4086L = "SWAPSPACE2"
+ with _ -> false in
+
+ if is_linux_x86_swap then (
+ if not quiet then
+ printf (f_"Clearing Linux swap on %s ...\n%!") fs;
+
+ (* Don't use mkswap. Just preserve the header containing
+ * the label, UUID and swap format version (libguestfs
+ * mkswap may differ from guest's own).
+ *)
+ let header = g#pread_device fs 4096 0L in
+ g#blkdiscard fs;
+ if g#pwrite_device fs header 0L <> 4096 then
+ error (f_"pwrite: short write restoring swap partition
header")
+ )
+ )
+ );
+
+ g#umount_all ()
+ )
+ ) filesystems;
+
+ (* Discard unused space in volume groups. *)
+ let vgs = g#vgs () in
+ let vgs = Array.to_list vgs in
+ let vgs = List.sort compare vgs in
+ List.iter (
+ fun vg ->
+ if not (List.mem vg ignores) then (
+ let lvname = string_random8 () in
+ let lvdev = "/dev/" ^ vg ^ "/" ^ lvname in
+
+ let created + try g#lvcreate_free lvname vg 100; true
+ with _ -> false in
+
+ if created then (
+ if not quiet then
+ printf (f_"Discard space in volgroup %s ...\n%!") vg;
+
+ g#blkdiscard lvdev;
+ g#sync ();
+ g#lvremove lvdev
+ )
+ )
+ ) vgs;
+
+ g#shutdown ();
+ g#close ();
+
+ (* Finished. *)
+ if not quiet then (
+ print_newline ();
+ wrap (s_"Sparsify in-place operation completed with no
errors.\n");
+ )
diff --git a/sparsify/sparsify.ml b/sparsify/sparsify.ml
index faefb23..f148296 100644
--- a/sparsify/sparsify.ml
+++ b/sparsify/sparsify.ml
@@ -37,6 +37,9 @@ let rec main () | Mode_copying (outdisk, check_tmpdir,
compress, convert, option) ->
Copying.run indisk outdisk check_tmpdir compress convert
format ignores machine_readable option quiet verbose trace zeroes
+ | Mode_in_place ->
+ In_place.run indisk format ignores machine_readable
+ quiet verbose trace zeroes
);
if debug_gc then
diff --git a/sparsify/virt-sparsify.pod b/sparsify/virt-sparsify.pod
index 84e94c5..c12a15f 100644
--- a/sparsify/virt-sparsify.pod
+++ b/sparsify/virt-sparsify.pod
@@ -8,6 +8,8 @@ virt-sparsify - Make a virtual machine disk sparse
virt-sparsify [--options] indisk outdisk
+ virt-sparsify [--options] --in-place disk
+
=head1 DESCRIPTION
Virt-sparsify is a tool which can make a virtual machine disk (or any
@@ -45,13 +47,6 @@ such as C<du -sh>. It can make a huge difference:
=item *
-Virt-sparsify does not do in-place modifications. It copies from a
-source image to a destination image, leaving the source unchanged.
-I<Check that the sparsification was successful before deleting the
-source image>.
-
-=item *
-
The virtual machine I<must be shut down> before using this tool.
=item *
@@ -60,6 +55,9 @@ Virt-sparsify may require up to 2x the virtual size of the
source disk
image (1 temporary copy + 1 destination image). This is in the worst
case and usually much less space is required.
+If you are using the I<--in-place> option, then large amounts of
+temporary space are B<not> required.
+
=item *
Virt-sparsify cannot resize disk images. To do that, use
@@ -105,6 +103,11 @@ to ignore (don't zero free space on) certain
filesystems by doing:
See L<virt-filesystems(1)> to get a list of filesystems within a disk
image.
+Since virt-sparsify E<ge> 1.26, you can now sparsify a disk image
+in place by doing:
+
+ virt-sparsify --in-place disk.img
+
=head1 OPTIONS
=over 4
@@ -147,11 +150,15 @@ B<fail> and exit.
=back
+You cannot use this option and I<--in-place> together.
+
=item B<--compress>
Compress the output file. This I<only> works if the output format is
C<qcow2>.
+You cannot use this option and I<--in-place> together.
+
=item B<--convert> raw
=item B<--convert> qcow2
@@ -171,6 +178,8 @@ then virt-sparsify doesn't need to try to guess the
input format.
For fine-tuning the output format, see: I<--compress>, I<-o>.
+You cannot use this option and I<--in-place> together.
+
=item B<--debug-gc>
Debug garbage collection and memory allocation. This is only useful
@@ -191,14 +200,23 @@ ensure the format is always specified.
=item B<--ignore> volgroup
-Ignore the named filesystem. Free space on the filesystem will not be
+Ignore the named filesystem.
+
+When not using I<--in-place>: Free space on the filesystem will not be
zeroed, but existing blocks of zeroes will still be sparsified.
+When using I<--in-place>, the filesystem is ignored completely.
+
In the second form, this ignores the named volume group. Use the
volume group name without the C</dev/> prefix, eg. I<--ignore
vg_foo>
You can give this option multiple times.
+=item B<--in-place>
+
+Do in-place sparsification instead of copying sparsification.
+See L</IN-PLACE SPARSIFICATION> below.
+
=item B<--machine-readable>
This option is used to make the output more machine friendly
@@ -217,6 +235,8 @@ them with commas, eg:
virt-sparsify --convert qcow2 \
-o cluster_size=512,preallocation=metadata ...
+You cannot use this option and I<--in-place> together.
+
=item B<-q>
=item B<--quiet>
@@ -249,6 +269,28 @@ excellent! You can give this option multiple times.
=back
+=head1 IN-PLACE SPARSIFICATION
+
+Since virt-sparsify E<ge> 1.26, the tool is able to do in-place
+sparsification (instead of copying from an input disk to an output
+disk). This is more efficient. However it requires special support
+in libguestfs, the kernel and qemu, and it is not able to recover
+quite as much space as copying sparsification. So in-place
+sparsification is considered to be experimental at this time.
+
+To use this mode, specify a disk image which will be modified in
+place:
+
+ virt-sparsify --in-place disk.img
+
+Some options are not compatible with this mode: I<--convert>,
+I<--compress> and I<-o> because they require wholesale disk format
+changes; I<--check-tmpdir> because large amounts of temporary space
+are not required.
+
+In-place sparsification works using discard (a.k.a trim or unmap)
+support.
+
=head1 MACHINE READABLE OUTPUT
The I<--machine-readable> option can be used to make the output more
@@ -332,6 +374,9 @@ size of the tmpfs mountpoint, eg:
mount -o remount,size=10G /tmp
+If you are using the I<--in-place> option, then large amounts of
+temporary space are B<not> required.
+
=back
For other environment variables, see L<guestfs(3)/ENVIRONMENT VARIABLES>.
@@ -355,4 +400,4 @@ Richard W.M. Jones
L<http://people.redhat.com/~rjones/>
=head1 COPYRIGHT
-Copyright (C) 2011-2012 Red Hat Inc.
+Copyright (C) 2011-2014 Red Hat Inc.
--
1.8.5.3
Richard W.M. Jones
2014-Mar-11 23:14 UTC
[Libguestfs] [PATCH v2 18/18] sparsify: Add a test of the virt-sparsify --in-place option.
---
sparsify/Makefile.am | 7 +++-
sparsify/test-virt-sparsify-in-place.sh | 69 +++++++++++++++++++++++++++++++++
2 files changed, 74 insertions(+), 2 deletions(-)
create mode 100755 sparsify/test-virt-sparsify-in-place.sh
diff --git a/sparsify/Makefile.am b/sparsify/Makefile.am
index 811c131..5e1b382 100644
--- a/sparsify/Makefile.am
+++ b/sparsify/Makefile.am
@@ -20,7 +20,8 @@ include $(top_srcdir)/subdir-rules.mk
EXTRA_DIST = \
$(SOURCES) \
virt-sparsify.pod \
- test-virt-sparsify.sh
+ test-virt-sparsify.sh \
+ test-virt-sparsify-in-place.sh
CLEANFILES = *~ *.cmi *.cmo *.cmx *.cmxa *.o virt-sparsify
@@ -133,7 +134,9 @@ CLEANFILES += stamp-virt-sparsify.pod
TESTS_ENVIRONMENT = $(top_builddir)/run --test
if ENABLE_APPLIANCE
-TESTS = test-virt-sparsify.sh
+TESTS = \
+ test-virt-sparsify.sh \
+ test-virt-sparsify-in-place.sh
endif ENABLE_APPLIANCE
check-valgrind:
diff --git a/sparsify/test-virt-sparsify-in-place.sh
b/sparsify/test-virt-sparsify-in-place.sh
new file mode 100755
index 0000000..56311a0
--- /dev/null
+++ b/sparsify/test-virt-sparsify-in-place.sh
@@ -0,0 +1,69 @@
+#!/bin/bash -
+# libguestfs virt-sparsify --in-place test script
+# Copyright (C) 2011-2014 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+export LANG=C
+set -e
+
+if [ -n "$SKIP_TEST_VIRT_SPARSIFY_IN_PLACE_SH" ]; then
+ echo "$0: skipping test (environment variable set)"
+ exit 77
+fi
+
+if [ "$(../fish/guestfish get-backend)" = "uml" ]; then
+ echo "$0: skipping test because uml backend does not support
discard"
+ exit 77
+fi
+
+rm -f test-virt-sparsify-in-place.img
+
+# Create a filesystem, fill it with data, then delete the data. Then
+# prove that sparsifying it reduces the size of the final filesystem.
+
+$VG ../fish/guestfish \
+ -N
test-virt-sparsify-in-place.img=bootrootlv:/dev/VG/LV:ext4:ext4:400M:32M:gpt
<<EOF
+mount /dev/VG/LV /
+mkdir /boot
+mount /dev/sda1 /boot
+fill 1 300M /big
+fill 1 10M /boot/big
+sync
+rm /big
+rm /boot/big
+umount-all
+EOF
+
+size_before=$(du -s test-virt-sparsify-in-place.img | awk '{print $1}')
+
+$VG ./virt-sparsify --debug-gc --in-place test-virt-sparsify-in-place.img
+
+size_after=$(du -s test-virt-sparsify-in-place.img | awk '{print $1}')
+
+echo "test virt-sparsify: $size_before K -> $size_after K"
+
+if [ $size_before -lt 310000 ]; then
+ echo "test virt-sparsify --in-place: size_before ($size_before) too
small"
+ exit 1
+fi
+
+if [ $size_after -gt 15000 ]; then
+ echo "test virt-sparsify --in-place: size_after ($size_after) too
large"
+ echo "sparsification failed"
+ exit 1
+fi
+
+rm test-virt-sparsify-in-place.img
--
1.8.5.3
Pino Toscano
2014-Mar-12 09:45 UTC
Re: [Libguestfs] [PATCH v2 03/18] New API parameter: Add discard parameter to guestfs_add_drive_opts.
On Tuesday 11 March 2014 23:13:46 Richard W.M. Jones wrote:> diff --git a/src/drives.c b/src/drives.c > index 2c85b52..68e37f7 100644 > --- a/src/drives.c > +++ b/src/drives.c > @@ -115,7 +115,8 @@ static struct drive * > create_drive_file (guestfs_h *g, const char *path, > bool readonly, const char *format, > const char *iface, const char *name, > - const char *disk_label, const char *cachemode) > + const char *disk_label, const char *cachemode, > + enum discard discard) > { > struct drive *drv = safe_calloc (g, 1, sizeof *drv); >(and others) It seems like these functions, albeit internal and private to this file, could need some help in avoid the over-long list of parameters, which all need to be changed every time there's an argument change (just like now). I'll do a small refactor.> --- a/src/guestfs-internal.h > +++ b/src/guestfs-internal.h > @@ -231,6 +231,12 @@ struct drive_source { > char *secret; > }; > > +enum discard { > + discard_disable = 0, > + discard_enable = 1, > + discard_besteffort = 2, > +};I guess the manual numbering is not needed.> > - if (construct_libvirt_xml_disk_driver_qemu (g, xo, "qcow2", "unsafe") > + if (construct_libvirt_xml_disk_driver_qemu (g, NULL, NULL, > + xo, "qcow2", "unsafe", > + discard_disable) == -1) > return -1; > }... and ...> @@ -1499,7 +1544,9 @@ construct_libvirt_xml_appliance (guestfs_h *g, > attribute ("bus", "scsi"); > } end_element (); > > - if (construct_libvirt_xml_disk_driver_qemu (g, xo, "qcow2", "unsafe") == -1) > + if (construct_libvirt_xml_disk_driver_qemu (g, NULL, NULL, xo, > + "qcow2", "unsafe", > + discard_disable) == -1) return -1; > > if (construct_libvirt_xml_disk_address (g, xo,Even if passing discard_disable makes sure that now "data" and "drv" are not used within construct_libvirt_xml_disk_driver_qemu, wouldn't be better to pass them anyway if possible, so further code might use them (at least "data") safely? -- Pino Toscano
On Tuesday 11 March 2014 23:13:43 Richard W.M. Jones wrote:> This still isn't working at the moment. See: > http://marc.info/?t=139457409300003&r=1&w=2 > > This set of patches: > > - Adds new APIs to support discard in libguestfs. > > - Adds discard support to virt-format. > > - Adds discard support to virt-sysprep. > > - Implements virt-sparsify --in-place.Do you plan to wait to solve the issues (wrt the linux-ext4 discussion) before pushing the series? There are few commits (01, 02, 11, 12, 13, 14, 15, 16) which IMHO could go even right now (so I ACK them). -- Pino Toscano
Richard W.M. Jones
2014-Mar-12 13:44 UTC
Re: [Libguestfs] [PATCH v2 00/18] Add discard support.
On Wed, Mar 12, 2014 at 10:53:06AM +0100, Pino Toscano wrote:> On Tuesday 11 March 2014 23:13:43 Richard W.M. Jones wrote: > > This still isn't working at the moment. See: > > http://marc.info/?t=139457409300003&r=1&w=2 > > > > This set of patches: > > > > - Adds new APIs to support discard in libguestfs. > > > > - Adds discard support to virt-format. > > > > - Adds discard support to virt-sysprep. > > > > - Implements virt-sparsify --in-place. > > Do you plan to wait to solve the issues (wrt the linux-ext4 discussion) > before pushing the series?I just fixed it. There is a stupid mistake in daemon/fstrim.c:>From edee98c2e44838bf4cd997886fbabd308d5e379f Mon Sep 17 00:00:00 2001From: "Richard W.M. Jones" <rjones@redhat.com> Date: Wed, 12 Mar 2014 13:41:39 +0000 Subject: [PATCH] daemon: fstrim: Fix fstrim so it trims the correct filesystem. We didn't call sysroot_path, so it was trimming the appliance instead of the guest filesystem. --- daemon/fstrim.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/daemon/fstrim.c b/daemon/fstrim.c index bf9dad8..2aad155 100644 --- a/daemon/fstrim.c +++ b/daemon/fstrim.c @@ -46,6 +46,7 @@ do_fstrim (const char *path, const char *argv[MAX_ARGS]; size_t i = 0; char offset_s[64], length_s[64], mfe_s[64]; + CLEANUP_FREE char *buf = NULL; CLEANUP_FREE char *out = NULL, *err = NULL; int r; @@ -88,7 +89,13 @@ do_fstrim (const char *path, if (verbose) ADD_ARG (argv, i, "-v"); - ADD_ARG (argv, i, path); + buf = sysroot_path (path); + if (!buf) { + reply_with_error ("malloc"); + return -1; + } + + ADD_ARG (argv, i, buf); ADD_ARG (argv, i, NULL); r = commandv (&out, &err, argv); -- 1.8.5.3> There are few commits (01, 02, 11, 12, 13, 14, 15, 16) which IMHO could > go even right now (so I ACK them).I'm going to read your other comments, and I'll post a v3 of this series. Thanks, Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming blog: http://rwmj.wordpress.com Fedora now supports 80 OCaml packages (the OPEN alternative to F#)
Apparently Analagous Threads
- [PATCH v3 00/10] Add discard support.
- [PATCH 1/2] mlstdutils/mltools: factorize the machine-readable option
- [PATCH 0/2] sparsify: Add --tmp option to allow specifying temporary directory or block device.
- [PATCH] OCaml tools: use open_guestfs everywhere
- [PATCH 0/2] RFC: --key option for tools