On Tue, Jun 19, 2012 at 05:24:26PM +0800, Wanlong Gao wrote:
add new virt-diff tool
Signed-off-by: Wanlong Gao <gaowanlong(a)cn.fujitsu.com>
---
Hi Rich,
This just a thought of virt-diff tool, I send it out ASAP
to ask your opinions.
Now, I just implement two guest, one SEED guest and one
DIFF guest, and the patch is wholly untested.
Please take patient to review.
And it's hard for me to start two guest handler, if you
have time, please help or just handle this work.
PS, I attended the LinuxCon Japan this month, so the plan
delayed.
The outline of the code seems to be:
* create two handles
* add both disks
* inspect both disks
* mount-local both disks on separate temporary directories
* run regular 'diff' command across the temporary directories
These is a reasonable approach for code simplicity, but I guess it'll
be very slow in practice.
cat/virt-diff.c | 423
++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 423 insertions(+)
create mode 100644 cat/virt-diff.c
diff --git a/cat/virt-diff.c b/cat/virt-diff.c
new file mode 100644
index 0000000..4bd8730
--- /dev/null
+++ b/cat/virt-diff.c
@@ -0,0 +1,423 @@
+/* virt-diff
+ * Copyright (C) 2012 FUJITSU LIMITED.
+ * Copyright (C) 2010-2012 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 <string.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <assert.h>
+#include <time.h>
+#include <libintl.h>
+
+#include "human.h"
+#include "progname.h"
+
+#include "guestfs.h"
+#include "options.h"
+
+#ifndef O_CLOEXEC
+#define O_CLOEXEC 0
+#endif
+
+/* libguestfs handle for seed. */
+static guestfs_h *sg;
+
+/* libguestfs handle for temp. */
+static guestfs_h *g;
+
+static int live = 0;
+static int verbose = 0;
+static int keys_from_stdin = 0;
+static int echo_keys = 0;
+static const char *libvirt_uri = NULL;
+static int inspector = 1;
+
+static doing_diff = 1;
+
+static inline char *
+diff_bad_case (char const *s)
+{
+ return (char *) s;
+}
+
+static void __attribute__((noreturn))
+diff_usage (int status)
+{
+ if (status != EXIT_SUCCESS) {
+ fprintf (stderr, _("Try `%s --help' for more information.\n"),
+ program_name);
+ } else {
+ fprintf (stdout,
+ _("%s: Show the differences between seed Guest and the others\n"
+ "Copyright (C) 2012 Fujitsu Limited.\n"
+ "Copyright (C) 2010-2012 Red Hat Inc.\n"
+ "Usage:\n"
+ "\n"
+ "\n"
+ "Options:\n"
+ " -a|--add image Add image\n"
+ " --checksum[=...] Display file checksums\n"),
+ program_name);
+ }
+ exit (status);
+}
+
+
+/* Simple implementation of decryption: look for any crypto_LUKS
+ * partitions and decrypt them, then rescan for VGs. This only works
+ * for Fedora whole-disk encryption. WIP to make this work for other
+ * encryption schemes.
+ */
+static void
+diff_inspect_do_decrypt (guestfs_h *g)
+{
+ char **partitions = guestfs_list_partitions (g);
+ if (partitions == NULL)
+ exit (EXIT_FAILURE);
+
+ int need_rescan = 0;
+ size_t i;
+ for (i = 0; partitions[i] != NULL; ++i) {
+ char *type = guestfs_vfs_type (g, partitions[i]);
+ if (type && STREQ (type, "crypto_LUKS")) {
+ char mapname[32];
+ make_mapname (partitions[i], mapname, sizeof mapname);
+
+ char *key = read_key (partitions[i]);
+ /* XXX Should we call guestfs_luks_open_ro if readonly flag
+ * is set? This might break 'mount_ro'.
+ */
+ if (guestfs_luks_open (g, partitions[i], key, mapname) == -1)
+ exit (EXIT_FAILURE);
+
+ free (key);
+
+ need_rescan = 1;
+ }
+ free (type);
+ }
+
+ free_strings (partitions);
+
+ if (need_rescan) {
+ if (guestfs_vgscan (g) == -1)
+ exit (EXIT_FAILURE);
+ if (guestfs_vg_activate_all (g, 1) == -1)
+ exit (EXIT_FAILURE);
+ }
+}
+
+static void
+diff_inspect_mount_root (guestfs_h *g, const char *root)
+{
+ char **mountpoints = guestfs_inspect_get_mountpoints (g, root);
+ if (mountpoints == NULL)
+ exit (EXIT_FAILURE);
+
+ /* Sort by key length, shortest key first, so that we end up
+ * mounting the filesystems in the correct order.
+ */
+ qsort (mountpoints, count_strings (mountpoints) / 2, 2 * sizeof (char *),
+ compare_keys_len);
+
+ size_t i;
+ size_t mount_errors = 0;
+ for (i = 0; mountpoints[i] != NULL; i += 2) {
+ int r;
+ r = guestfs_mount_ro (g, mountpoints[i+1], mountpoints[i]);
+ if (r == -1) {
+ /* If the "/" filesystem could not be mounted, give up, else
+ * just count the errors and print a warning.
+ */
+ if (STREQ (mountpoints[i], "/"))
+ exit (EXIT_FAILURE);
+ mount_errors++;
+ }
+ }
+
+ free_strings (mountpoints);
+
+ if (mount_errors)
+ fprintf (stderr, _("%s: some filesystems could not be mounted
(ignored)\n"),
+ program_name);
+}
+
+/* This function implements the -i option. */
+static void
+diff_inspect_mount (guestfs_h *g)
+{
+ diff_inspect_do_decrypt (g);
+
+ char **roots = guestfs_inspect_os (g);
+ if (roots == NULL)
+ exit (EXIT_FAILURE);
+
+ if (roots[0] == NULL) {
+ fprintf (stderr,
+ _("%s: no operating system was found on this disk\n"
+ "\n"
+ "If using guestfish '-i' option, remove this option and
instead\n"
+ "use the commands 'run' followed by
'list-filesystems'.\n"
+ "You can then mount filesystems you want by hand using the\n"
+ "'mount' or 'mount-ro' command.\n"
+ "\n"
+ "If using guestmount '-i', remove this option and choose
the\n"
+ "filesystem(s) you want to see by manually adding '-m'
option(s).\n"
+ "Use 'virt-filesystems' to see what filesystems are
available.\n"
+ "\n"
+ "If using other virt tools, this disk image won't work\n"
+ "with these tools. Use the guestfish equivalent commands\n"
+ "(see the virt tool manual page).\n"),
+ program_name);
+ free_strings (roots);
+ exit (EXIT_FAILURE);
+ }
+
+ if (roots[1] != NULL) {
+ fprintf (stderr,
+ _("%s: multi-boot operating systems are not supported\n"
+ "\n"
+ "If using guestfish '-i' option, remove this option and
instead\n"
+ "use the commands 'run' followed by
'list-filesystems'.\n"
+ "You can then mount filesystems you want by hand using the\n"
+ "'mount' or 'mount-ro' command.\n"
+ "\n"
+ "If using guestmount '-i', remove this option and choose
the\n"
+ "filesystem(s) you want to see by manually adding '-m'
option(s).\n"
+ "Use 'virt-filesystems' to see what filesystems are
available.\n"
+ "\n"
+ "If using other virt tools, multi-boot operating systems won't
work\n"
+ "with these tools. Use the guestfish equivalent commands\n"
+ "(see the virt tool manual page).\n"),
+ program_name);
+ free_strings (roots);
+ exit (EXIT_FAILURE);
+ }
+
+ root = roots[0];
+ free (roots);
+
+ diff_inspect_mount_root (root);
+}
+
+int
+main (int argc, int *argv[])
+{
+ /* set global program name that is not polluted with libtool artifacts. */
+ set_program_name (argv[0]);
+ bindtextdomain (PACKAGE, LOCALEBASEDIR);
+ textdomain (PACKAGE);
+
+ enum { HELP_OPTION = CHAR_MAX + 1 };
+
+ static const char *options = "s:d:";
+ static const struct option long_options[] = {
+ {"seed", 1, 0, 's'},
+ {"domain", 1, 0, 'd'},
+ {0, 0, 0, 0}
+ };
Eventually you'll need to support all the other options too, or
at least --help.
+ struct drv *sdrv = NULL;
+ struct drv *drv = NULL;
+ int c;
+
+ sg = guestfs_create ();
+ if (sg == NULL) {
+ fprintf (stderr, _("guestfs_create: failed to create seed handle\n"));
+ exit (EXIT_FAILURE);
+ }
+
+ g = guestfs_create ();
+ if (g == NULL) {
+ fprintf (stderr, _("guestfs_create: failed to create comparison
handle\n"));
+ exit (EXIT_FAILURE);
+ }
+
+ argv[0] = bad_case (program_name);
+
+ for (;;) {
+ c = getopt_long (argc, argv, options, long_options, &option_index);
+ if (c == -1) break;
+
+ switch (c) {
+ case 0:
+ if (STREQ (long_options[option_index].name, "seed")) {
+ if (sdrv) {
+ fprintf(stderr, _("...."));
+ exit (EXIT_FAILURE);
+ }
+ sdrv = calloc (1, sizeof (struct drv));
+ if (!sdrv) {
+ perror ("malloc");
+ exit (EXIT_FAILURE);
+ }
+ sdrv->type = drv_d;
+ sdrv->nr_drives = -1;
+ sdrv->d.guest = optarg;
+ sdrv->next = NULL;
+ } else if (STREQ (long_options[option_index].name, "domain")) {
+ drv = calloc ("malloc");
+ if (!drv) {
+ perror ("malloc");
+ exit (EXIT_FAILURE);
+ }
+ drv->type = drv_d;
+ drv->nr_drives = -1;
+ drv->d.guest = optarg;
+ drv->next = NULL;
+ } else {
+ fprintf (stderr, _("%s: unknown long option: %s (%d)\n"),
+ program_name, long_options[option_index].name, option_index);
+ exit (EXIT_FAILURE);
+ }
+ break;
+
+ case 'd':
+ OPTION_d;
+ break;
+
+ case 's':
+ if (sdrv) {
+ fprintf(stderr, _("...."));
+ exit (EXIT_FAILURE);
+ }
+ sdrv = calloc (1, sizeof (struct drv));
+ if (!sdrv) {
+ perror ("malloc");
+ exit (EXIT_FAILURE);
+ }
+ sdrv->type = drv_d;
+ sdrv->nr_drives = -1;
+ sdrv->d.guest = optarg;
+ sdrv->next = NULL;
+ break;
+
+ case HELP_OPTION:
+ usage (EXIT_SUCCESS);
+
+ default:
+ usage (EXIT_FAILURE);
+ }
+ }
+
+ if (sdrv == NULL)
+ usage (EXIT_FAILURE);
+ if (drv == NULL)
+ usage (EXIT_FAILURE);
+
+ struct guestfs_add_domain_argv optargs = {
+ .bitmask = 0;
+ .libvirturi = NULL;
+ .readonly = 1;
+ .allowuuid = 1;
+ .readonlydisk = "read";
+ };
+ nr = guestfs_add_domain_argv (sg, sdrv->d.guest, &optargs);
+ if (r == -1)
+ exit (EXIT_FAILURE);
+ sdrv->nr_drives = nr;
+ nr = guestfs_add_domain_argv (g, drv->d.guest, &optargs);
+ if (r == -1)
+ exit (EXIT_FAILURE);
+ drv->nr_drives = nr;
+
+ if (guestfs_launch (sg) == -1)
+ exit (EXIT_FAILURE);
+ if (guestfs_launch (g) == -1)
+ exit (EXIT_FAILURE);
+
+ diff_inspect_mount (sg);
+ diff_inspect_mount (g);
+
+ char stempdir = "/tmp/sGuestXXXXXX";
+ char tempdir = "/tmp/GuestXXXXXX";
+ char sdir[256];
+ char dir[256];
+
+ if (mkdtemp (stempdir) == NULL) {
+ perror ("mkdtemp");
+ exit (EXIT_FAILURE);
+ }
+ if (mkdtemp (tempdir) == NULL) {
+ perror ("mkdtemp");
+ exit (EXIT_FAILURE);
+ }
+
+ if (guestfs_mount_local (sg, stempdir, -1) == -1)
+ exit (EXIT_FAILURE);
+ if (guestfs_mount_local (g, tempdir, -1) == -1)
+ exit (EXIT_FAILURE);
+
+ spid = fork ();
+ if (spid == -1) {
+ perror ("fork");
+ exit (EXIT_FAILURE);
+ }
+ if (spid == 0) {
+ while (doing_diff)
+ sleep (1);
+ guestfs_umount_local (sg, GUESTFS_UMOUNT_LOCAL_RETRY, 1, -1);
+ _exit (EXIT_SUCCESS);
+ }
+
+ pid = fork();
+ if (pid == -1) {
+ perror ("fork");
+ exit (EXIT_FAILURE);
+ }
+ if (pid == 0) {
+ while (doing_diff)
+ sleep (1);
+ guestfs_umount_local (g, GUESTFS_UMOUNT_LOCAL_RETRY, 1, -1);
+ _exit (EXIT_SUCCESS);
+ }
The doing_diff variable isn't shared between forked processes, so the
child processes will simply busy-wait forever. In any case the main
program never even sets doing_diff.
This code is going to be far more complex in the final version.
+ if (guestfs_mount_local_run (sg) == -1)
+ exit (EXIT_FAILURE);
+ if (guestfs_mount_local_run (g) == -1)
+ exit (EXIT_FAILURE);
+
+ const char *dir = argv[optind];
+ char system_arg[BUFSIZ];
+ sprintf (system_arg, "diff -urN %s%s %s%s", stempdir, dir,
+ tempdir, dir);
+ system(system_arg);
+
+ waitpid (spid, NULL, 0);
+ waitpid (pid, NULL, 0);
+
+ if (guestfs_umount (sg, "/") == -1)
+ exit (EXIT_FAILURE);
+ if (guestfs_umount (g, "/") == -1)
+ exit (EXIT_FAILURE);
+
+ free_drives (sdrvs);
+ free_drives (drv);
+
+ guestfs_close (sg);
+ guestfs_close (g);
+
+ exit (EXIT_SUCCESS);
+}
--
1.7.11.rc0
Rich.
--
Richard Jones, Virtualization Group, Red Hat
http://people.redhat.com/~rjones
virt-top is 'top' for virtual machines. Tiny program with many
powerful monitoring features, net stats, disk stats, logging, etc.
http://et.redhat.com/~rjones/virt-top