[Libguestfs] [PATCH V3] virt-diff: add new virt-diff tool

Wanlong Gao gaowanlong at cn.fujitsu.com
Mon Aug 20 08:22:23 UTC 2012


add new virt-diff tool

Signed-off-by: Wanlong Gao <gaowanlong at cn.fujitsu.com>
---
 cat/Makefile.am |  20 ++-
 cat/virt-diff.c | 525 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 po/POTFILES     |   1 +
 3 files changed, 545 insertions(+), 1 deletion(-)
 create mode 100644 cat/virt-diff.c

diff --git a/cat/Makefile.am b/cat/Makefile.am
index f7c763a..5f6a986 100644
--- a/cat/Makefile.am
+++ b/cat/Makefile.am
@@ -27,7 +27,7 @@ EXTRA_DIST = \
 
 CLEANFILES = stamp-virt-cat.pod stamp-virt-ls.pod stamp-virt-filesystems.pod
 
-bin_PROGRAMS = virt-cat virt-filesystems virt-ls
+bin_PROGRAMS = virt-cat virt-filesystems virt-ls virt-diff
 
 SHARED_SOURCE_FILES = \
 	../fish/config.c \
@@ -91,6 +91,24 @@ virt_ls_LDADD = \
 	$(top_builddir)/src/libguestfs.la \
 	../gnulib/lib/libgnu.la
 
+virt_diff_SOURCES = \
+	../fish/keys.c \
+	virt-diff.c
+
+virt_diff_CFLAGS = \
+	-DGUESTFS_WARN_DEPRECATED=1 \
+	-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) \
+	$(LIBCONFIG_CFLAGS)
+
+virt_diff_LDADD = \
+	$(LIBCONFIG_LIBS) \
+	$(top_builddir)/src/libguestfs.la \
+	../gnulib/lib/libgnu.la
+
 # Manual pages and HTML files for the website.
 man_MANS = virt-cat.1 virt-filesystems.1 virt-ls.1
 
diff --git a/cat/virt-diff.c b/cat/virt-diff.c
new file mode 100644
index 0000000..c2c59a6
--- /dev/null
+++ b/cat/virt-diff.c
@@ -0,0 +1,525 @@
+/* virt-diff
+ * Copyright (C) 2012 Fujitsu Limited.
+ * Copyright (C) 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 <termios.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 <sys/wait.h>
+
+#include "c-ctype.h"
+
+#include "human.h"
+#include "progname.h"
+
+#include "guestfs.h"
+#include "options.h"
+
+#ifndef O_CLOEXEC
+#define O_CLOEXEC 0
+#endif
+
+int keys_from_stdin = 0;
+int echo_keys = 0;
+/* libguestfs handle for seed. */
+static guestfs_h *sg;
+
+/* libguestfs handle for temp. */
+static guestfs_h *dg;
+
+const char *libvirt_uri = NULL;
+
+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) 2012 Red Hat Inc.\n"
+             "Usage:\n"
+             "  %s [--options] -s domname -d domname path\n"
+             "Options:\n"
+             "  -c|--connect uri     Specify libvirt URI for -s and -d options\n"
+             "  -s|--seed guest      Add seed disk from libvirt guest\n"
+             "  -d|--domain guest    Add target disk from libvirt guest\n"
+             "  --keys-from-stdin    Read passphrases from stdin\n"
+             "  --echo-keys          Don't turn off echo for passphrases\n"
+             "  --help               Display brief help\n"),
+             program_name, program_name);
+  }
+  exit (status);
+}
+
+/* Make a LUKS map name from the partition name,
+ * eg "/dev/vda2" => "luksvda2"
+ */
+static void
+diff_make_mapname (const char *device, char *mapname, size_t len)
+{
+  size_t i = 0;
+
+  if (len < 5)
+    abort ();
+  strcpy (mapname, "luks");
+  mapname += 4;
+  len -= 4;
+
+  if (STRPREFIX (device, "/dev/"))
+    i = 5;
+
+  for (; device[i] != '\0' && len >= 1; ++i) {
+    if (c_isalnum (device[i])) {
+      *mapname++ = device[i];
+      len--;
+    }
+  }
+
+  *mapname = '\0';
+}
+
+static void
+free_strings (char **strings)
+{
+  size_t i;
+
+  for (i = 0; strings[i] != NULL; ++i)
+    free (strings[i]);
+  free (strings);
+}
+
+static size_t
+count_strings (char **strings)
+{
+  size_t i;
+
+  for (i = 0; strings[i] != NULL; ++i)
+    ;
+  return i;
+}
+
+/* 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];
+      diff_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 int
+compare_keys (const void *p1, const void *p2)
+{
+  const char *key1 = * (char * const *) p1;
+  const char *key2 = * (char * const *) p2;
+
+  return strcmp (key1, key2);
+}
+
+static int
+compare_keys_len (const void *p1, const void *p2)
+{
+  const char *key1 = * (char * const *) p1;
+  const char *key2 = * (char * const *) p2;
+  int c;
+
+  c = strlen (key1) - strlen (key2);
+  if (c != 0)
+    return c;
+
+  return compare_keys (p1, p2);
+}
+
+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)
+{
+  const char *root = NULL;
+  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 (g, root);
+}
+
+static void
+free_drive (struct drv *drv)
+{
+  if (!drv) return;
+
+  free (drv->device);
+  free (drv);
+}
+
+int
+main (int argc, char *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'},
+    {"help", 0, 0, HELP_OPTION},
+    {0, 0, 0, 0}
+  };
+
+  struct drv *sdrv = NULL;
+  struct drv *ddrv = NULL;
+  int c, nr;
+  int option_index;
+  int spid, dpid;
+
+  sg = guestfs_create ();
+  if (sg == NULL) {
+    fprintf (stderr, _("guestfs_create: failed to create seed handle\n"));
+    exit (EXIT_FAILURE);
+  }
+
+  dg = guestfs_create ();
+  if (dg == NULL) {
+    fprintf (stderr, _("guestfs_create: failed to create comparison handle\n"));
+    exit (EXIT_FAILURE);
+  }
+
+  argv[0] = diff_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, "keys-from-stdin")) {
+        keys_from_stdin = 1;
+      } else if (STREQ (long_options[option_index].name, "echo-keys")) {
+        echo_keys = 1;
+      } else if (STREQ (long_options[option_index].name, "seed")) {
+        if (sdrv) {
+          fprintf(stderr, _("Only one seed device"));
+          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")) {
+        if (ddrv) {
+          fprintf (stderr, _("Only one diff device"));
+          exit (EXIT_FAILURE);
+        }
+        ddrv = calloc (1, sizeof (struct drv));
+        if (!ddrv) {
+          perror ("malloc");
+          exit (EXIT_FAILURE);
+        }
+        ddrv->type = drv_d;
+        ddrv->nr_drives = -1;
+        ddrv->d.guest = optarg;
+        ddrv->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 'c':
+      libvirt_uri = optarg;
+
+    case 's':
+      if (sdrv) {
+        fprintf(stderr, _("Only one seed device"));
+        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 'd':
+      if (ddrv) {
+        fprintf (stderr, _("Only one diff device"));
+        exit (EXIT_FAILURE);
+      }
+      ddrv = calloc (1, sizeof (struct drv));
+      if (!ddrv) {
+        perror ("malloc");
+        exit (EXIT_FAILURE);
+      }
+      ddrv->type = drv_d;
+      ddrv->nr_drives = -1;
+      ddrv->d.guest = optarg;
+      ddrv->next = NULL;
+      break;
+
+    case HELP_OPTION:
+      diff_usage (EXIT_SUCCESS);
+
+    default:
+      diff_usage (EXIT_FAILURE);
+    }
+  }
+
+  if (sdrv == NULL)
+    diff_usage (EXIT_FAILURE);
+  if (ddrv == NULL)
+    diff_usage (EXIT_FAILURE);
+
+  struct guestfs_add_domain_argv optargs = {
+    .bitmask = 0,
+    .libvirturi = libvirt_uri,
+    .readonly = 1,
+    .allowuuid = 1,
+    .readonlydisk = "read",
+  };
+  nr = guestfs_add_domain_argv (sg, sdrv->d.guest, &optargs);
+  if (nr == -1)
+    exit (EXIT_FAILURE);
+  sdrv->nr_drives = nr;
+  nr = guestfs_add_domain_argv (dg, ddrv->d.guest, &optargs);
+  if (nr == -1)
+    exit (EXIT_FAILURE);
+  ddrv->nr_drives = nr;
+
+  if (guestfs_launch (sg) == -1)
+    exit (EXIT_FAILURE);
+  if (guestfs_launch (dg) == -1)
+    exit (EXIT_FAILURE);
+
+  diff_inspect_mount (sg);
+  diff_inspect_mount (dg);
+
+  char stempdir[] = "/tmp/sGuestXXXXXX";
+  char dtempdir[] = "/tmp/dGuestXXXXXX";
+
+  if (mkdtemp (stempdir) == NULL) {
+    perror ("mkdtemp");
+    exit (EXIT_FAILURE);
+  }
+  if (mkdtemp (dtempdir) == NULL) {
+    perror ("mkdtemp");
+    exit (EXIT_FAILURE);
+  }
+
+  if (guestfs_mount_local (sg, stempdir, -1) == -1)
+    exit (EXIT_FAILURE);
+  if (guestfs_mount_local (dg, dtempdir, -1) == -1)
+    exit (EXIT_FAILURE);
+
+  spid = fork ();
+  if (spid == -1) {
+    perror ("fork");
+    exit (EXIT_FAILURE);
+  }
+  if (spid == 0) {
+    if (guestfs_mount_local_run (sg) == -1)
+      exit (EXIT_FAILURE);
+    _exit (EXIT_SUCCESS);
+  }
+
+  dpid = fork();
+  if (dpid == -1) {
+    perror ("fork");
+    exit (EXIT_FAILURE);
+  }
+  if (dpid == 0) {
+    if (guestfs_mount_local_run (dg) == -1)
+      exit (EXIT_FAILURE);
+    _exit (EXIT_SUCCESS);
+  }
+
+  const char *dir = argv[optind];
+  char system_arg[BUFSIZ];
+  sprintf (system_arg, "diff -urN %s%s %s%s", stempdir, dir,
+                                              dtempdir, dir);
+  sleep (5);
+  if (system (system_arg) == -1)
+    exit (EXIT_FAILURE);
+
+  guestfs_umount_local (sg, GUESTFS_UMOUNT_LOCAL_RETRY, 1, -1);
+  waitpid (spid, NULL, 0);
+  guestfs_umount_local (dg, GUESTFS_UMOUNT_LOCAL_RETRY, 1, -1);
+  waitpid (dpid, NULL, 0);
+
+  if (guestfs_shutdown (sg) == -1)
+    exit (EXIT_FAILURE);
+  if (guestfs_shutdown (dg) == -1)
+    exit (EXIT_FAILURE);
+
+  free_drive (sdrv);
+  free_drive (ddrv);
+
+  guestfs_close (sg);
+  guestfs_close (dg);
+
+  exit (EXIT_SUCCESS);
+}
diff --git a/po/POTFILES b/po/POTFILES
index 60887dc..ad58555 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -1,6 +1,7 @@
 align/domains.c
 align/scan.c
 cat/virt-cat.c
+cat/virt-diff.c
 cat/virt-filesystems.c
 cat/virt-ls.c
 daemon/9p.c
-- 
1.7.12




More information about the Libguestfs mailing list