diff options
Diffstat (limited to 'df')
-rw-r--r-- | df/Makefile.am | 84 | ||||
-rw-r--r-- | df/README | 17 | ||||
-rw-r--r-- | df/df.c | 158 | ||||
-rw-r--r-- | df/domains.c | 436 | ||||
-rw-r--r-- | df/main.c | 306 | ||||
-rw-r--r-- | df/output.c | 242 | ||||
-rwxr-xr-x | df/run-df-locally | 52 | ||||
-rwxr-xr-x | df/test-virt-df.sh | 72 | ||||
-rw-r--r-- | df/virt-df.h | 42 | ||||
-rwxr-xr-x | df/virt-df.pod | 229 |
10 files changed, 1638 insertions, 0 deletions
diff --git a/df/Makefile.am b/df/Makefile.am new file mode 100644 index 00000000..f148e6ce --- /dev/null +++ b/df/Makefile.am @@ -0,0 +1,84 @@ +# libguestfs virt-df +# Copyright (C) 2010 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +include $(top_srcdir)/subdir-rules.mk + +EXTRA_DIST = \ + README \ + run-df-locally \ + test-virt-df.sh \ + virt-df.pod + +CLEANFILES = stamp-virt-df.pod + +bin_PROGRAMS = virt-df + +SHARED_SOURCE_FILES = \ + ../fish/inspect.c \ + ../fish/keys.c \ + ../fish/options.h \ + ../fish/options.c \ + ../fish/virt.c + +virt_df_SOURCES = \ + $(SHARED_SOURCE_FILES) \ + virt-df.h \ + domains.c \ + df.c \ + main.c \ + output.c + +virt_df_CFLAGS = \ + -I$(top_srcdir)/src -I$(top_builddir)/src \ + -I$(top_srcdir)/fish \ + -I$(srcdir)/../gnulib/lib -I../gnulib/lib \ + -DLOCALEBASEDIR=\""$(datadir)/locale"\" \ + $(WARN_CFLAGS) $(WERROR_CFLAGS) + +virt_df_LDADD = \ + $(top_builddir)/src/libguestfs.la \ + ../gnulib/lib/libgnu.la -lm + +# Manual pages and HTML files for the website. +man_MANS = virt-df.1 +noinst_DATA = $(top_builddir)/html/virt-df.1.html + +virt-df.1 $(top_builddir)/html/virt-df.1.html: stamp-virt-df.pod + +stamp-virt-df.pod: virt-df.pod + $(top_srcdir)/podwrapper.sh \ + --man virt-df.1 \ + --html $(top_builddir)/html/virt-df.1.html \ + $< + touch $@ + +# Tests. + +random_val := $(shell awk 'BEGIN{srand(); print 1+int(255*rand())}' < /dev/null) + +TESTS_ENVIRONMENT = \ + MALLOC_PERTURB_=$(random_val) \ + LD_LIBRARY_PATH=$(top_builddir)/src/.libs \ + LIBGUESTFS_PATH=$(top_builddir)/appliance + +TESTS = test-virt-df.sh + +# Build a partly-static binary (for the binary distribution). + +virt-df.static$(EXEEXT): $(virt_df_OBJECTS) $(virt_df_DEPENDENCIES) + $(top_srcdir)/relink-static.sh \ + $(virt_df_LINK) $(virt_df_OBJECTS) -static $(virt_df_LDADD) $(virt_df_LIBS) $(LIBVIRT_LIBS) $(LIBXML2_LIBS) -lpcre -lhivex -lmagic -lz -lm diff --git a/df/README b/df/README new file mode 100644 index 00000000..f928f2d8 --- /dev/null +++ b/df/README @@ -0,0 +1,17 @@ +This is the third rewrite of the virt-df program. It very much +follows the outline of the Perl program which this replaced in +libguestfs 1.7.14. + +main.c - main program + +domains.c - dealing with libvirt, only used if libvirt is around + at compile time + +df.c - getting the stats from libguestfs + +output.c - writing the output, CSV output + +virt-df.h - header file + +Note this also uses the shared options parsing code in +'fish/options.[ch]'. diff --git a/df/df.c b/df/df.c new file mode 100644 index 00000000..c2db970f --- /dev/null +++ b/df/df.c @@ -0,0 +1,158 @@ +/* virt-df + * Copyright (C) 2010 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <inttypes.h> + +#ifdef HAVE_LIBVIRT +#include <libvirt/libvirt.h> +#include <libvirt/virterror.h> +#endif + +#include "progname.h" + +#include "guestfs.h" +#include "options.h" +#include "virt-df.h" + +static void try_df (const char *name, const char *uuid, const char *dev, int offset); +static int find_dev_in_devices (const char *dev, char **devices); + +/* Since we want this function to be robust against very bad failure + * cases (hello, https://bugzilla.kernel.org/show_bug.cgi?id=18792) it + * won't exit on guestfs failures. + */ +int +df_on_handle (const char *name, const char *uuid, char **devices, int offset) +{ + int ret = -1; + size_t i; + char **fses = NULL; + int free_devices = 0, is_lv; + + if (verbose) { + fprintf (stderr, "df_on_handle %s devices=", name); + if (devices) { + fputc ('[', stderr); + for (i = 0; devices[i] != NULL; ++i) { + if (i > 0) + fputc (' ', stderr); + fputs (devices[i], stderr); + } + fputc (']', stderr); + } + else + fprintf (stderr, "null"); + fputc ('\n', stderr); + } + + if (devices == NULL) { + devices = guestfs_list_devices (g); + if (devices == NULL) + goto cleanup; + free_devices = 1; + } else { + /* Mask LVM for just the devices in the set. */ + if (guestfs_lvm_set_filter (g, devices) == -1) + goto cleanup; + } + + /* list-filesystems will return filesystems on every device ... */ + fses = guestfs_list_filesystems (g); + if (fses == NULL) + goto cleanup; + + /* ... so we need to filter out only the devices we are interested in. */ + for (i = 0; fses[i] != NULL; i += 2) { + if (STRNEQ (fses[i+1], "") && + STRNEQ (fses[i+1], "swap") && + STRNEQ (fses[i+1], "unknown")) { + is_lv = guestfs_is_lv (g, fses[i]); + if (is_lv > 0) /* LVs are OK because of the LVM filter */ + try_df (name, uuid, fses[i], -1); + else if (is_lv == 0) { + if (find_dev_in_devices (fses[i], devices)) + try_df (name, uuid, fses[i], offset); + } + } + } + + ret = 0; + + cleanup: + if (fses) { + for (i = 0; fses[i] != NULL; ++i) + free (fses[i]); + free (fses); + } + + if (free_devices) { + for (i = 0; devices[i] != NULL; ++i) + free (devices[i]); + free (devices); + } + + return ret; +} + +static int +find_dev_in_devices (const char *dev, char **devices) +{ + size_t i; + + for (i = 0; devices[i] != NULL; ++i) { + if (STRPREFIX (dev, devices[i])) + return 1; + } + + return 0; +} + +static void +try_df (const char *name, const char *uuid, + const char *dev, int offset) +{ + struct guestfs_statvfs *stat = NULL; + guestfs_error_handler_cb old_error_cb; + void *old_error_data; + + if (verbose) + fprintf (stderr, "try_df %s %s %d\n", name, dev, offset); + + /* Try mounting and stating the device. This might reasonably fail, + * so don't show errors. + */ + old_error_cb = guestfs_get_error_handler (g, &old_error_data); + guestfs_set_error_handler (g, NULL, NULL); + + if (guestfs_mount_ro (g, dev, "/") == 0) { + stat = guestfs_statvfs (g, "/"); + guestfs_umount_all (g); + } + + guestfs_set_error_handler (g, old_error_cb, old_error_data); + + if (stat) { + print_stat (name, uuid, dev, offset, stat); + guestfs_free_statvfs (stat); + } +} diff --git a/df/domains.c b/df/domains.c new file mode 100644 index 00000000..5bc7c42a --- /dev/null +++ b/df/domains.c @@ -0,0 +1,436 @@ +/* virt-df + * Copyright (C) 2010 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#ifdef HAVE_LIBVIRT +#include <libvirt/libvirt.h> +#include <libvirt/virterror.h> +#endif + +#include "progname.h" + +#define GUESTFS_PRIVATE_FOR_EACH_DISK 1 + +#include "guestfs.h" +#include "options.h" +#include "virt-df.h" + +#ifdef HAVE_LIBVIRT + +/* Limit the number of devices we will ever add to the appliance. The + * overall limit in current libguestfs is 25: 26 = number of letters + * in the English alphabet since we are only confident that + * /dev/sd[a-z] will work because of various limits, minus 1 because + * that may be used by the ext2 initial filesystem. (RHBZ#635373). + */ +#define MAX_DISKS 25 + +/* The list of domains and disks that we build up in + * get_domains_from_libvirt. + */ +struct disk { + struct disk *next; + char *filename; + char *format; /* could be NULL */ +}; + +struct domain { + char *name; + char *uuid; + struct disk *disks; + size_t nr_disks; +}; + +struct domain *domains = NULL; +size_t nr_domains; + +static int +compare_domain_names (const void *p1, const void *p2) +{ + const struct domain *d1 = p1; + const struct domain *d2 = p2; + + return strcmp (d1->name, d2->name); +} + +static void +free_domain (struct domain *domain) +{ + struct disk *disk, *next; + + for (disk = domain->disks; disk; disk = next) { + next = disk->next; + free (disk->filename); + free (disk->format); + free (disk); + } + + free (domain->name); + free (domain->uuid); +} + +static void add_domains_by_id (virConnectPtr conn, int *ids, size_t n); +static void add_domains_by_name (virConnectPtr conn, char **names, size_t n); +static void add_domain (virDomainPtr dom); +static int add_disk (guestfs_h *g, const char *filename, const char *format, void *domain_vp); +static void multi_df (struct domain *, size_t n); + +void +get_domains_from_libvirt (void) +{ + virErrorPtr err; + virConnectPtr conn; + int n; + size_t i, j, nr_disks_added; + + nr_domains = 0; + domains = NULL; + + /* Get the list of all domains. */ + conn = virConnectOpenReadOnly (libvirt_uri); + if (!conn) { + err = virGetLastError (); + fprintf (stderr, + _("%s: could not connect to libvirt (code %d, domain %d): %s"), + program_name, err->code, err->domain, err->message); + exit (EXIT_FAILURE); + } + + n = virConnectNumOfDomains (conn); + if (n == -1) { + err = virGetLastError (); + fprintf (stderr, + _("%s: could not get number of running domains (code %d, domain %d): %s"), + program_name, err->code, err->domain, err->message); + exit (EXIT_FAILURE); + } + + int ids[n]; + n = virConnectListDomains (conn, ids, n); + if (n == -1) { + err = virGetLastError (); + fprintf (stderr, + _("%s: could not list running domains (code %d, domain %d): %s"), + program_name, err->code, err->domain, err->message); + exit (EXIT_FAILURE); + } + + add_domains_by_id (conn, ids, n); + + n = virConnectNumOfDefinedDomains (conn); + if (n == -1) { + err = virGetLastError (); + fprintf (stderr, + _("%s: could not get number of inactive domains (code %d, domain %d): %s"), + program_name, err->code, err->domain, err->message); + exit (EXIT_FAILURE); + } + + char *names[n]; + n = virConnectListDefinedDomains (conn, names, n); + if (n == -1) { + err = virGetLastError (); + fprintf (stderr, + _("%s: could not list inactive domains (code %d, domain %d): %s"), + program_name, err->code, err->domain, err->message); + exit (EXIT_FAILURE); + } + + add_domains_by_name (conn, names, n); + + /* You must free these even though the libvirt documentation doesn't + * mention it. + */ + for (i = 0; i < (size_t) n; ++i) + free (names[i]); + + virConnectClose (conn); + + /* No domains? */ + if (nr_domains == 0) + return; + + /* Sort the domains alphabetically by name for display. */ + qsort (domains, nr_domains, sizeof (struct domain), compare_domain_names); + + print_title (); + + /* To minimize the number of times we have to launch the appliance, + * shuffle as many domains together as we can, but not exceeding + * MAX_DISKS per request. If --one-per-guest was requested then only + * request disks from a single guest each time. + * Interesting application for NP-complete knapsack problem here. + */ + if (one_per_guest) { + for (i = 0; i < nr_domains; ++i) + multi_df (&domains[i], 1); + } else { + for (i = 0; i < nr_domains; /**/) { + nr_disks_added = 0; + + /* Make a request with domains [i..j-1]. */ + for (j = i; j < nr_domains; ++j) { + if (nr_disks_added + domains[j].nr_disks > MAX_DISKS) + break; + nr_disks_added += domains[j].nr_disks; + } + multi_df (&domains[i], j-i); + + i = j; + } + } + + /* Free up domains structure. */ + for (i = 0; i < nr_domains; ++i) + free_domain (&domains[i]); + free (domains); +} + +static void +add_domains_by_id (virConnectPtr conn, int *ids, size_t n) +{ + size_t i; + virDomainPtr dom; + + for (i = 0; i < n; ++i) { + if (ids[i] != 0) { /* RHBZ#538041 */ + dom = virDomainLookupByID (conn, ids[i]); + if (dom) { /* transient errors are possible here, ignore them */ + add_domain (dom); + virDomainFree (dom); + } + } + } +} + +static void +add_domains_by_name (virConnectPtr conn, char **names, size_t n) +{ + size_t i; + virDomainPtr dom; + + for (i = 0; i < n; ++i) { + dom = virDomainLookupByName (conn, names[i]); + if (dom) { /* transient errors are possible here, ignore them */ + add_domain (dom); + virDomainFree (dom); + } + } +} + +static void +add_domain (virDomainPtr dom) +{ + struct domain *domain; + + domains = realloc (domains, (nr_domains + 1) * sizeof (struct domain)); + if (domains == NULL) { + perror ("realloc"); + exit (EXIT_FAILURE); + } + + domain = &domains[nr_domains]; + nr_domains++; + + domain->name = strdup (virDomainGetName (dom)); + if (domain->name == NULL) { + perror ("strdup"); + exit (EXIT_FAILURE); + } + + char uuid[VIR_UUID_STRING_BUFLEN]; + if (virDomainGetUUIDString (dom, uuid) == 0) { + domain->uuid = strdup (uuid); + if (domain->uuid == NULL) { + perror ("strdup"); + exit (EXIT_FAILURE); + } + } + else + domain->uuid = NULL; + + domain->disks = NULL; + int n = guestfs___for_each_disk (g, dom, add_disk, domain); + if (n == -1) + exit (EXIT_FAILURE); + domain->nr_disks = n; + + if (domain->nr_disks > MAX_DISKS) { + fprintf (stderr, + _("%s: ignoring %s, it has too many disks (%zu > %d)"), + program_name, domain->name, domain->nr_disks, MAX_DISKS); + free_domain (domain); + nr_domains--; + return; + } +} + +static int +add_disk (guestfs_h *g, const char *filename, const char *format, + void *domain_vp) +{ + struct domain *domain = domain_vp; + struct disk *disk; + + disk = malloc (sizeof *disk); + if (disk == NULL) { + perror ("malloc"); + return -1; + } + + disk->next = domain->disks; + domain->disks = disk; + + disk->filename = strdup (filename); + if (disk->filename == NULL) { + perror ("malloc"); + return -1; + } + if (format) { + disk->format = strdup (format); + if (disk->format == NULL) { + perror ("malloc"); + return -1; + } + } + else + disk->format = NULL; + + return 0; +} + +static size_t +count_strings (char **argv) +{ + size_t i; + + for (i = 0; argv[i] != NULL; ++i) + ; + return i; +} + +static void reset_guestfs_handle (void); +static void add_disks_to_handle_reverse (struct disk *disk); + +/* Perform 'df' operation on the domain(s) given in the list. */ +static void +multi_df (struct domain *domains, size_t n) +{ + size_t i; + size_t nd; + int r; + char **devices; + + /* Add all the disks to the handle (since they were added in reverse + * order, we must add them here in reverse too). + */ + for (i = 0; i < n; ++i) + add_disks_to_handle_reverse (domains[i].disks); + + /* Launch the handle. */ + if (guestfs_launch (g) == -1) + exit (EXIT_FAILURE); + + devices = guestfs_list_devices (g); + if (devices == NULL) + exit (EXIT_FAILURE); + + /* Check the number of disks we think we added is the same as the + * number of devices returned by libguestfs. + */ + nd = 0; + for (i = 0; i < n; ++i) + nd += domains[i].nr_disks; + assert (nd == count_strings (devices)); + + nd = 0; + for (i = 0; i < n; ++i) { + /* So that &devices[nd] is a NULL-terminated list of strings. */ + char *p = devices[nd + domains[i].nr_disks]; + devices[nd + domains[i].nr_disks] = NULL; + + r = df_on_handle (domains[i].name, domains[i].uuid, &devices[nd], nd); + + /* Restore devices to original. */ + devices[nd + domains[i].nr_disks] = p; + nd += domains[i].nr_disks; + + /* Something broke in df_on_handle. Give up on the remaining + * devices for this handle, but keep going on the next handle. + */ + if (r == -1) + break; + } + + for (i = 0; devices[i] != NULL; ++i) + free (devices[i]); + free (devices); + + /* Reset the handle. */ + reset_guestfs_handle (); +} + +static void +add_disks_to_handle_reverse (struct disk *disk) +{ + if (disk == NULL) + return; + + add_disks_to_handle_reverse (disk->next); + + struct guestfs_add_drive_opts_argv optargs = { .bitmask = 0 }; + + optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK; + optargs.readonly = 1; + + if (disk->format) { + optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_FORMAT_BITMASK; + optargs.format = disk->format; + } + + if (guestfs_add_drive_opts_argv (g, disk->filename, &optargs) == -1) + exit (EXIT_FAILURE); +} + +/* Close and reopen the libguestfs handle. */ +static void +reset_guestfs_handle (void) +{ + /* Copy the settings from the old handle. */ + int verbose = guestfs_get_verbose (g); + int trace = guestfs_get_trace (g); + + guestfs_close (g); + + g = guestfs_create (); + if (g == NULL) { + fprintf (stderr, _("guestfs_create: failed to create handle\n")); + exit (EXIT_FAILURE); + } + + guestfs_set_verbose (g, verbose); + guestfs_set_trace (g, trace); +} + +#endif diff --git a/df/main.c b/df/main.c new file mode 100644 index 00000000..9565464b --- /dev/null +++ b/df/main.c @@ -0,0 +1,306 @@ +/* virt-df + * Copyright (C) 2010 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <inttypes.h> +#include <unistd.h> +#include <getopt.h> +#include <assert.h> + +#ifdef HAVE_LIBVIRT +#include <libvirt/libvirt.h> +#include <libvirt/virterror.h> +#endif + +#include "progname.h" + +#include "guestfs.h" +#include "options.h" +#include "virt-df.h" + +/* These globals are shared with options.c. */ +guestfs_h *g; + +int read_only = 1; +int verbose = 0; +int keys_from_stdin = 0; +int echo_keys = 0; +const char *libvirt_uri = NULL; +int inspector = 0; + +int csv = 0; /* --csv */ +int human = 0; /* --human-readable|-h */ +int inodes = 0; /* --inodes */ +int one_per_guest = 0; /* --one-per-guest */ +int uuid = 0; /* --uuid */ + +static inline char * +bad_cast (char const *s) +{ + return (char *) s; +} + +static void __attribute__((noreturn)) +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else { + fprintf (stdout, + _("%s: display free space on virtual filesystems\n" + "Copyright (C) 2010 Red Hat Inc.\n" + "Usage:\n" + " %s [--options] -d domname\n" + " %s [--options] -a disk.img [-a disk.img ...]\n" + "Options:\n" + " -a|--add image Add image\n" + " -c|--connect uri Specify libvirt URI for -d option\n" + " --csv Output as Comma-Separated Values\n" + " -d|--domain guest Add disks from libvirt guest\n" + " --format[=raw|..] Force disk format for -a option\n" + " -h|--human-readable Human-readable sizes in --long output\n" + " --help Display brief help\n" + " -i|--inodes Display inodes\n" + " --one-per-guest Separate appliance per guest\n" + " --uuid Add UUIDs to --long output\n" + " -v|--verbose Verbose messages\n" + " -V|--version Display version and exit\n" + " -x Trace libguestfs API calls\n" + "For more information, see the manpage %s(1).\n"), + program_name, program_name, program_name, + program_name); + } + exit (status); +} + +int +main (int argc, char *argv[]) +{ + /* Set global program name that is not polluted with libtool artifacts. */ + set_program_name (argv[0]); + + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEBASEDIR); + textdomain (PACKAGE); + + enum { HELP_OPTION = CHAR_MAX + 1 }; + + static const char *options = "a:c:d:hivVx"; + static const struct option long_options[] = { + { "add", 1, 0, 'a' }, + { "connect", 1, 0, 'c' }, + { "csv", 0, 0, 0 }, + { "domain", 1, 0, 'd' }, + { "format", 2, 0, 0 }, + { "help", 0, 0, HELP_OPTION }, + { "human-readable", 0, 0, 'h' }, + { "inodes", 0, 0, 'i' }, + { "one-per-guest", 0, 0, 0 }, + { "uuid", 0, 0, 0 }, + { "verbose", 0, 0, 'v' }, + { "version", 0, 0, 'V' }, + { 0, 0, 0, 0 } + }; + struct drv *drvs = NULL; + struct drv *drv; + const char *format = NULL; + int c; + int option_index; + + g = guestfs_create (); + if (g == NULL) { + fprintf (stderr, _("guestfs_create: failed to create handle\n")); + exit (EXIT_FAILURE); + } + + argv[0] = bad_cast (program_name); + + for (;;) { + c = getopt_long (argc, argv, options, long_options, &option_index); + if (c == -1) break; + + switch (c) { + case 0: /* options which are long only */ + if (STREQ (long_options[option_index].name, "format")) { + if (!optarg || STREQ (optarg, "")) + format = NULL; + else + format = optarg; + } else if (STREQ (long_options[option_index].name, "csv")) { + csv = 1; + } else if (STREQ (long_options[option_index].name, "one-per-guest")) { + one_per_guest = 1; + } else if (STREQ (long_options[option_index].name, "uuid")) { + uuid = 1; + } else { + fprintf (stderr, _("%s: unknown long option: %s (%d)\n"), + program_name, long_options[option_index].name, option_index); + exit (EXIT_FAILURE); + } + break; + + case 'a': + OPTION_a; + break; + + case 'c': + OPTION_c; + break; + + case 'd': + OPTION_d; + break; + + case 'h': + human = 1; + break; + + case 'i': + inodes = 1; + break; + + case 'v': + OPTION_v; + break; + + case 'V': + OPTION_V; + break; + + case 'x': + OPTION_x; + break; + + case HELP_OPTION: + usage (EXIT_SUCCESS); + + default: + usage (EXIT_FAILURE); + } + } + + /* Old-style syntax? There were no -a or -d options in the old + * virt-df which is how we detect this. + */ + if (drvs == NULL) { + while (optind < argc) { + if (strchr (argv[optind], '/') || + access (argv[optind], F_OK) == 0) { /* simulate -a option */ + drv = malloc (sizeof (struct drv)); + if (!drv) { + perror ("malloc"); + exit (EXIT_FAILURE); + } + drv->type = drv_a; + drv->a.filename = argv[optind]; + drv->a.format = NULL; + drv->next = drvs; + drvs = drv; + } else { /* simulate -d option */ + drv = malloc (sizeof (struct drv)); + if (!drv) { + perror ("malloc"); + exit (EXIT_FAILURE); + } + drv->type = drv_d; + drv->d.guest = argv[optind]; + drv->next = drvs; + drvs = drv; + } + + optind++; + } + } + + /* These are really constants, but they have to be variables for the + * options parsing code. Assert here that they have known-good + * values. + */ + assert (read_only == 1); + assert (inspector == 0); + + /* Must be no extra arguments on the command line. */ + if (optind != argc) + usage (EXIT_FAILURE); + + /* -h and --csv doesn't make sense. Spreadsheets will corrupt these + * fields. (RHBZ#600977). + */ + if (human && csv) { + fprintf (stderr, _("%s: you cannot use -h and --csv options together.\n"), + program_name); + exit (EXIT_FAILURE); + } + + /* If the user didn't specify any drives, then we ask libvirt for + * the full list of guests and drives, which we add in batches. + */ + if (drvs == NULL) { +#ifdef HAVE_LIBVIRT + get_domains_from_libvirt (); +#else + fprintf (stderr, _("%s: compiled without support for libvirt.\n"), + program_name); + exit (EXIT_FAILURE); +#endif + } + else { + const char *name; + + /* Add domains/drives from the command line (for a single guest). */ + add_drives (drvs, 'a'); + + if (guestfs_launch (g) == -1) + exit (EXIT_FAILURE); + + print_title (); + + /* Synthesize a display name. */ + switch (drvs->type) { + case drv_a: + name = strrchr (drvs->a.filename, '/'); + break; + case drv_d: + name = drvs->d.guest; + break; + case drv_N: + default: + abort (); + } + + /* XXX regression: in the Perl version we cached the UUID from the + * libvirt domain handle so it was available to us here. In this + * version the libvirt domain handle is hidden inside + * guestfs_add_domain so the UUID is not available easily for + * single '-d' command-line options. + */ + (void) df_on_handle (name, NULL, NULL, 0); + + /* Free up data structures, no longer needed after this point. */ + free_drives (drvs); + } + + guestfs_close (g); + + exit (EXIT_SUCCESS); +} diff --git a/df/output.c b/df/output.c new file mode 100644 index 00000000..52ce0631 --- /dev/null +++ b/df/output.c @@ -0,0 +1,242 @@ +/* virt-df + * Copyright (C) 2010 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <inttypes.h> +#include <xvasprintf.h> +#include <math.h> +#include <assert.h> + +#ifdef HAVE_LIBVIRT +#include <libvirt/libvirt.h> +#include <libvirt/virterror.h> +#endif + +#include "c-ctype.h" +#include "human.h" +#include "progname.h" + +#include "guestfs.h" +#include "options.h" +#include "virt-df.h" + +static void write_csv_field (const char *field); + +void +print_title (void) +{ + const char *cols[6]; + size_t i; + + cols[0] = _("VirtualMachine"); + cols[1] = _("Filesystem"); + if (!inodes) { + if (!human) + cols[2] = _("1K-blocks"); + else + cols[2] = _("Size"); + cols[3] = _("Used"); + cols[4] = _("Available"); + cols[5] = _("Use%"); + } else { + cols[2] = _("Inodes"); + cols[3] = _("IUsed"); + cols[4] = _("IFree"); + cols[5] = _("IUse%"); + } + + if (!csv) { + /* ignore cols[0] in this mode */ + printf ("%-36s%10s %10s %10s %5s\n", + cols[1], cols[2], cols[3], cols[4], cols[5]); + } + else { + size_t i; + + for (i = 0; i < 6; ++i) { + if (i > 0) + putchar (','); + write_csv_field (cols[i]); + } + putchar ('\n'); + } +} + +static void canonical_device (char *dev, int offset); + +void +print_stat (const char *name, const char *uuid_param, + const char *dev_param, int offset, + const struct guestfs_statvfs *stat) +{ + /* First two columns are always 'name' and 'dev', followed by four + * other data columns. In text mode the 'name' and 'dev' are + * combined into a single 'name:dev' column. In CSV mode they are + * kept as two separate columns. In UUID mode the name might be + * replaced by 'uuid', if available. + */ +#define MAX_LEN (LONGEST_HUMAN_READABLE > 128 ? LONGEST_HUMAN_READABLE : 128) + char buf[4][MAX_LEN]; + const char *cols[4]; + int64_t factor, v; + float percent; + int hopts = human_round_to_nearest|human_autoscale|human_base_1024|human_SI; + size_t i, len; + + /* Make the device canonical. */ + len = strlen (dev_param) + 1; + char dev[len]; + strcpy (dev, dev_param); + if (offset >= 0) + canonical_device (dev, offset); + + if (!inodes) { /* 1K blocks */ + if (!human) { + factor = stat->bsize / 1024; + + v = stat->blocks * factor; + snprintf (buf[0], MAX_LEN, "%" PRIi64, v); + cols[0] = buf[0]; + v = (stat->blocks - stat->bfree) * factor; + snprintf (buf[1], MAX_LEN, "%" PRIi64, v); + cols[1] = buf[1]; + v = stat->bavail * factor; + snprintf (buf[2], MAX_LEN, "%" PRIi64, v); + cols[2] = buf[2]; + } else { + cols[0] = + human_readable ((uintmax_t) stat->blocks, buf[0], + hopts, stat->bsize, 1); + v = stat->blocks - stat->bfree; + cols[1] = + human_readable ((uintmax_t) v, buf[1], hopts, stat->bsize, 1); + cols[2] = + human_readable ((uintmax_t) stat->bavail, buf[2], + hopts, stat->bsize, 1); + } + + if (stat->blocks != 0) + percent = 100. - 100. * stat->bfree / stat->blocks; + else + percent = 0; + } + else { /* inodes */ + snprintf (buf[0], MAX_LEN, "%" PRIi64, stat->files); + cols[0] = buf[0]; + snprintf (buf[1], MAX_LEN, "%" PRIi64, stat->files - stat->ffree); + cols[1] = buf[1]; + snprintf (buf[2], MAX_LEN, "%" PRIi64, stat->ffree); + cols[2] = buf[2]; + + if (stat->files != 0) + percent = 100. - 100. * stat->ffree / stat->files; + else + percent = 0; + } + + if (!csv) + /* Use 'ceil' on the percentage in order to emulate what df itself does. */ + snprintf (buf[3], MAX_LEN, "%3.0f%%", ceil (percent)); + else + snprintf (buf[3], MAX_LEN, "%.1f", percent); + cols[3] = buf[3]; + +#undef MAX_LEN + + if (uuid && uuid_param) + name = uuid_param; + + if (!csv) { + len = strlen (name) + strlen (dev) + 1; + printf ("%s:%s", name, dev); + if (len <= 36) { + for (i = len; i < 36; ++i) + putchar (' '); + } else { + printf ("\n "); + } + + printf ("%10s %10s %10s %5s\n", cols[0], cols[1], cols[2], cols[3]); + } + else { + write_csv_field (name); + putchar (','); + write_csv_field (dev); + + for (i = 0; i < 4; ++i) { + putchar (','); + write_csv_field (cols[i]); + } + + putchar ('\n'); + } +} + +/* /dev/vda1 -> /dev/sda, adjusting the device offset. */ +static void +canonical_device (char *dev, int offset) +{ + if (STRPREFIX (dev, "/dev/") && + (dev[5] == 'h' || dev[5] == 'v') && + dev[6] == 'd' && + c_isalpha (dev[7]) && + (c_isdigit (dev[8]) || dev[8] == '\0')) { + dev[5] = 's'; + dev[7] -= offset; + } +} + +/* Function to quote CSV fields on output without requiring an + * external module. + */ +static void +write_csv_field (const char *field) +{ + size_t i, len; + int needs_quoting = 0; + + len = strlen (field); + + for (i = 0; i < len; ++i) { + if (field[i] == ' ' || field[i] == '"' || + field[i] == '\n' || field[i] == ',') { + needs_quoting = 1; + break; + } + } + + if (!needs_quoting) { + printf ("%s", field); + return; + } + + /* Quoting for CSV fields. */ + putchar ('"'); + for (i = 0; i < len; ++i) { + if (field[i] == '"') { + putchar ('"'); + putchar ('"'); + } else + putchar (field[i]); + } + putchar ('"'); +} diff --git a/df/run-df-locally b/df/run-df-locally new file mode 100755 index 00000000..2456ab52 --- /dev/null +++ b/df/run-df-locally @@ -0,0 +1,52 @@ +#!/usr/bin/perl +# Copyright (C) 2009 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +# This script sets up the environment so you can run virt-* tools in +# place without needing to do 'make install' first. You can also run +# the tools by creating a symlink to this script and putting it in +# your path. +# +# Use it like this: +# ./run-df-locally [usual virt-df args ...] + +use strict; +use warnings; + +use File::Basename qw(dirname); +use File::Spec; +use Cwd qw(abs_path); + +my $path = $0; + +# Follow symlinks until we get to the real file +while(-l $path) { + my $link = readlink($path) or die "readlink: $path: $!"; + if(File::Spec->file_name_is_absolute($link)) { + $path = $link; + } else { + $path = File::Spec->catfile(dirname($path), $link); + } +} + +# Get the absolute path of the parent directory +$path = abs_path(dirname($path).'/..'); + +$ENV{LD_LIBRARY_PATH} = $path.'/src/.libs'; +$ENV{LIBGUESTFS_PATH} = $path.'/appliance'; + +#print (join " ", ("$path/df/virt-df", @ARGV), "\n"); +exec("$path/df/virt-df", @ARGV); diff --git a/df/test-virt-df.sh b/df/test-virt-df.sh new file mode 100755 index 00000000..6878327d --- /dev/null +++ b/df/test-virt-df.sh @@ -0,0 +1,72 @@ +#!/bin/bash - + +export LANG=C +set -e + +# Run virt-df. +output=$(./virt-df ../images/fedora.img) + +# Check title is the first line. +if [[ ! $output =~ ^Filesystem.* ]]; then + echo "$0: error: no title line" + exit 1 +fi + +# Check 6 lines (title line + 5 * filesystems). +if [ $(echo "$output" | wc -l) -ne 6 ]; then + echo "$0: error: not all filesystems were found" + exit 1 +fi + +# Check /dev/VG/LV[1-3] and /dev/VG/Root were found. +if [[ ! $output =~ fedora.img:/dev/VG/LV1 ]]; then + echo "$0: error: filesystem /dev/VG/LV1 was not found" + exit 1 +fi +if [[ ! $output =~ fedora.img:/dev/VG/LV2 ]]; then + echo "$0: error: filesystem /dev/VG/LV2 was not found" + exit 1 +fi +if [[ ! $output =~ fedora.img:/dev/VG/LV3 ]]; then + echo "$0: error: filesystem /dev/VG/LV3 was not found" + exit 1 +fi +if [[ ! $output =~ fedora.img:/dev/VG/Root ]]; then + echo "$0: error: filesystem /dev/VG/Root was not found" + exit 1 +fi + +# Check /dev/sda1 was found. Might be called /dev/vda1. +if [[ ! $output =~ fedora.img:/dev/[hsv]da1 ]]; then + echo "$0: error: filesystem /dev/VG/sda1 was not found" + exit 1 +fi + +# This is what df itself prints for these filesystems (determined +# by running the test image under virt-rescue): +# +# ><rescue> df -h +# Filesystem Size Used Avail Use% Mounted on +# /dev/dm-1 31M 28K 30M 1% /sysroot/lv1 +# /dev/dm-2 31M 395K 29M 2% /sysroot/lv2 +# /dev/dm-3 62M 144K 59M 1% /sysroot/lv3 +# ><rescue> df -i +# Filesystem Inodes IUsed IFree IUse% Mounted on +# /dev/dm-1 8192 11 8181 1% /sysroot/lv1 +# /dev/dm-2 8192 11 8181 1% /sysroot/lv2 +# /dev/dm-3 16384 11 16373 1% /sysroot/lv3 +# ><rescue> df +# Filesystem 1K-blocks Used Available Use% Mounted on +# /dev/dm-1 31728 28 30064 1% /sysroot/lv1 +# /dev/dm-2 31729 395 29696 2% /sysroot/lv2 +# /dev/dm-3 63472 144 60052 1% /sysroot/lv3 +# +# Only test plain 'df' output at the moment (XXX). + +if [ "$(echo "$output" | sort | awk '/VG.LV[123]/ { print $2 " " $3 " " $4 " " $5 }')" != \ +"31728 28 30064 1% +31729 395 29696 2% +63472 144 60052 1%" ]; then + echo "$0: error: output of virt-df did not match expected (df) output" + exit 1 +fi diff --git a/df/virt-df.h b/df/virt-df.h new file mode 100644 index 00000000..4a3179ef --- /dev/null +++ b/df/virt-df.h @@ -0,0 +1,42 @@ +/* virt-df + * Copyright (C) 2010 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef GUESTFS_VIRT_DF_ +#define GUESTFS_VIRT_DF_ + +extern guestfs_h *g; +extern const char *libvirt_uri; /* --connect */ +extern int csv; /* --csv */ +extern int human; /* --human-readable|-h */ +extern int inodes; /* --inodes */ +extern int one_per_guest; /* --one-per-guest */ +extern int uuid; /* --uuid */ + +/* df.c */ +extern int df_on_handle (const char *name, const char *uuid, char **devices, int offset); + +/* domains.c */ +#ifdef HAVE_LIBVIRT +extern void get_domains_from_libvirt (void); +#endif + +/* output.c */ +extern void print_title (void); +extern void print_stat (const char *name, const char *uuid, const char *dev, int offset, const struct guestfs_statvfs *stat); + +#endif /* GUESTFS_VIRT_DF_ */ diff --git a/df/virt-df.pod b/df/virt-df.pod new file mode 100755 index 00000000..5d06f9b0 --- /dev/null +++ b/df/virt-df.pod @@ -0,0 +1,229 @@ +=encoding utf8 + +=head1 NAME + +virt-df - Display free space on virtual filesystems + +=head1 SYNOPSIS + + virt-df [--options] + + virt-df [--options] -d domname + + virt-df [--options] -a disk.img [-a disk.img ...] + +Old style: + + virt-df [--options] domname + + virt-df [--options] disk.img [disk.img ...] + +=head1 DESCRIPTION + +C<virt-df> is a command line tool to display free space on virtual +machine filesystems. Unlike other tools, it doesn't just display the +amount of space allocated to a virtual machine, but can look inside +the virtual machine to see how much space is really being used. + +It is like the L<df(1)> command, but for virtual machines, except that +it also works for Windows virtual machines. + +If used without any arguments, C<virt-df> checks with libvirt to get a +list of all active and inactive guests, and performs a C<df>-type +operation on each one in turn, printing out the results. + +If used with any argument(s), C<virt-df> performs a C<df>-type +operation on either the single named libvirt domain, or on the disk +image(s) listed on the command line (which must all belong to a single +VM). In this mode (with arguments), C<virt-df> will I<only work for a +single guest>. If you want to run on multiple guests, then you have +to invoke C<virt-df> multiple times. + +Use the C<--csv> option to get a format which can be easily parsed by +other programs. Other options are mostly similar to standard C<df> +options. See below for the complete list. + +=head1 OPTIONS + +=over 4 + +=item B<--help> + +Display brief help. + +=item B<-a> file + +=item B<--add> file + +Add I<file> which should be a disk image from a virtual machine. If +the virtual machine has multiple block devices, you must supply all of +them with separate I<-a> options. + +The format of the disk image is auto-detected. To override this and +force a particular format use the I<--format=..> option. + +=item B<-c> URI + +=item B<--connect> URI + +If using libvirt, connect to the given I<URI>. If omitted, then we +connect to the default libvirt hypervisor. + +If you specify guest block devices directly (I<-a>), then libvirt is +not used at all. + +=item B<-d> guest + +=item B<--domain> guest + +Add all the disks from the named libvirt guest. + +=item B<--format=raw|qcow2|..> + +=item B<--format> + +The default for the I<-a> option is to auto-detect the format of the +disk image. Using this forces the disk format for I<-a> options which +follow on the command line. Using I<--format> with no argument +switches back to auto-detection for subsequent I<-a> options. + +For example: + + virt-df --format=raw -a disk.img + +forces raw format (no auto-detection) for C<disk.img>. + + virt-df --format=raw -a disk.img --format -a another.img + +forces raw format (no auto-detection) for C<disk.img> and reverts to +auto-detection for C<another.img>. + +If you have untrusted raw-format guest disk images, you should use +this option to specify the disk format. This avoids a possible +security problem with malicious guests (CVE-2010-3851). See also +L</add-drive-opts>. + +=item B<-h> + +=item B<--human-readable> + +Print sizes in human-readable format. + +You are not allowed to use I<-h> and I<--csv> at the same time. + +=item B<--inodes> | B<-i> + +Print inodes instead of blocks. + +=item B<--one-per-guest> + +Run one libguestfs appliance per guest. Normally C<virt-df> will +add the disks from several guests to a single libguestfs appliance. + +You might use this option in the following circumstances: + +=over 4 + +=item * + +If you think an untrusted guest might actively try to exploit the +libguestfs appliance kernel, then this prevents one guest from +interfering with the stats printed for another guest. + +=item * + +If the kernel has a bug which stops it from accessing a +filesystem in one guest (see for example RHBZ#635373) then +this allows libguestfs to continue and report stats for further +guests. + +=back + +=item B<--uuid> + +Print UUIDs instead of names. This is useful for following +a guest even when the guest is migrated or renamed, or when +two guests happen to have the same name. + +Note that only domains that we fetch from libvirt come with UUIDs. +For disk images, we still print the disk image name even when +this option is specified. + +=item B<-v> + +=item B<--verbose> + +Enable verbose messages for debugging. + +=item B<-V> + +=item B<--version> + +Display version number and exit. + +=item B<-x> + +Enable tracing of libguestfs API calls. + +=back + +=head1 NOTE ABOUT CSV FORMAT + +Comma-separated values (CSV) is a deceptive format. It I<seems> like +it should be easy to parse, but it is definitely not easy to parse. + +Myth: Just split fields at commas. Reality: This does I<not> work +reliably. This example has two columns: + + "foo,bar",baz + +Myth: Read the file one line at a time. Reality: This does I<not> +work reliably. This example has one row: + + "foo + bar",baz + +For shell scripts, use C<csvtool> (L<http://merjis.com/developers/csv> +also packaged in major Linux distributions). + +For other languages, use a CSV processing library (eg. C<Text::CSV> +for Perl or Python's built-in csv library). + +Most spreadsheets and databases can import CSV directly. + +=head1 SHELL QUOTING + +Libvirt guest names can contain arbitrary characters, some of which +have meaning to the shell such as C<#> and space. You may need to +quote or escape these characters on the command line. See the shell +manual page L<sh(1)> for details. + +=head1 SEE ALSO + +L<guestfs(3)>, +L<guestfish(1)>, +L<virt-filesystems(1)>, +L<Sys::Virt(3)>, +L<http://libguestfs.org/>. + +=head1 AUTHOR + +Richard W.M. Jones L<http://people.redhat.com/~rjones/> + +=head1 COPYRIGHT + +Copyright (C) 2009-2010 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., 675 Mass Ave, Cambridge, MA 02139, USA. |