inspector: Rewrite virt-inspector in C.
[libguestfs.git] / inspector / virt-inspector.c
diff --git a/inspector/virt-inspector.c b/inspector/virt-inspector.c
new file mode 100644 (file)
index 0000000..a3c0a04
--- /dev/null
@@ -0,0 +1,657 @@
+/* virt-inspector
+ * 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 <inttypes.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <assert.h>
+
+#include <libxml/xmlIO.h>
+#include <libxml/xmlwriter.h>
+
+#include "progname.h"
+#include "c-ctype.h"
+
+#include "guestfs.h"
+#include "options.h"
+
+/* Currently open libguestfs handle. */
+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 = 1;
+
+static void output (char **roots);
+static void output_roots (xmlTextWriterPtr xo, char **roots);
+static void output_root (xmlTextWriterPtr xo, char *root);
+static void output_mountpoints (xmlTextWriterPtr xo, char *root);
+static void output_filesystems (xmlTextWriterPtr xo, char *root);
+static void output_applications (xmlTextWriterPtr xo, char *root);
+static void canonicalize (char *dev);
+static void free_strings (char **argv);
+static int count_strings (char *const*argv);
+
+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 information about a virtual machine\n"
+             "Copyright (C) 2010 Red Hat Inc.\n"
+             "Usage:\n"
+             "  %s [--options] -d domname file [file ...]\n"
+             "  %s [--options] -a disk.img [-a disk.img ...] file [file ...]\n"
+             "Options:\n"
+             "  -a|--add image       Add image\n"
+             "  -c|--connect uri     Specify libvirt URI for -d option\n"
+             "  -d|--domain guest    Add disks from libvirt guest\n"
+             "  --echo-keys          Don't turn off echo for passphrases\n"
+             "  --format[=raw|..]    Force disk format for -a option\n"
+             "  --help               Display brief help\n"
+             "  --keys-from-stdin    Read passphrases from stdin\n"
+             "  -v|--verbose         Verbose messages\n"
+             "  -V|--version         Display version and exit\n"
+             "  -x                   Echo each command before executing it\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:vVx";
+  static const struct option long_options[] = {
+    { "add", 1, 0, 'a' },
+    { "connect", 1, 0, 'c' },
+    { "domain", 1, 0, 'd' },
+    { "echo-keys", 0, 0, 0 },
+    { "format", 2, 0, 0 },
+    { "help", 0, 0, HELP_OPTION },
+    { "keys-from-stdin", 0, 0, 0 },
+    { "verbose", 0, 0, 'v' },
+    { "version", 0, 0, 'V' },
+    { 0, 0, 0, 0 }
+  };
+  struct drv *drvs = NULL;
+  struct drv *drv;
+  char *p, *file = NULL;
+  const char *format = NULL;
+  int c;
+  int option_index;
+  int next_prepared_drive = 1;
+
+  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, "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, "format")) {
+        if (!optarg || STREQ (optarg, ""))
+          format = NULL;
+        else
+          format = optarg;
+      } 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':
+      usage (EXIT_SUCCESS);
+
+    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-inspector 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 == 1);
+
+  /* Must be no extra arguments on the command line. */
+  if (optind != argc)
+    usage (EXIT_FAILURE);
+
+  /* User must have specified some drives. */
+  if (drvs == NULL)
+    usage (EXIT_FAILURE);
+
+  /* Add drives, inspect and mount.  Note that inspector is always true,
+   * and there is no -m option.
+   */
+  add_drives (drvs, 'a');
+
+  if (guestfs_launch (g) == -1)
+    exit (EXIT_FAILURE);
+
+  /* Free up data structures, no longer needed after this point. */
+  free_drives (drvs);
+
+  /* NB. Can't call inspect_mount () here (ie. normal processing of
+   * the -i option) because it can only handle a single root.  So we
+   * use low-level APIs.
+   */
+  inspect_do_decrypt ();
+
+  char **roots = guestfs_inspect_os (g);
+  if (roots == NULL) {
+    fprintf (stderr, _("%s: no operating system could be detected inside this disk image.\n\nThis may be because the file is not a disk image, or is not a virtual machine\nimage, or because the OS type is not understood by libguestfs.\n\nNOTE for Red Hat Enterprise Linux 6 users: for Windows guest support you must\ninstall the separate libguestfs-winsupport package.\n\nIf you feel this is an error, please file a bug report including as much\ninformation about the disk image as possible.\n"),
+             program_name);
+    exit (EXIT_FAILURE);
+  }
+
+  output (roots);
+
+  free_strings (roots);
+
+  guestfs_close (g);
+
+  exit (EXIT_SUCCESS);
+}
+
+#define DISABLE_GUESTFS_ERRORS_FOR(stmt) do {                           \
+    guestfs_error_handler_cb old_error_cb;                              \
+    void *old_error_data;                                               \
+    old_error_cb = guestfs_get_error_handler (g, &old_error_data);      \
+    guestfs_set_error_handler (g, NULL, NULL);                          \
+    stmt;                                                               \
+    guestfs_set_error_handler (g, old_error_cb, old_error_data);        \
+  } while (0)
+
+#define XMLERROR(code,e) do {                                           \
+    if ((e) == (code)) {                                                \
+      fprintf (stderr, _("%s: XML write error at \"%s\": %m\n"),        \
+               #e, program_name);                                       \
+      exit (EXIT_FAILURE);                                              \
+    }                                                                   \
+  } while (0)
+
+static void
+output (char **roots)
+{
+  xmlOutputBufferPtr ob = xmlOutputBufferCreateFd (1, NULL);
+  if (ob == NULL) {
+    fprintf (stderr,
+             _("%s: xmlOutputBufferCreateFd: failed to open stdout\n"),
+             program_name);
+    exit (EXIT_FAILURE);
+  }
+
+  xmlTextWriterPtr xo = xmlNewTextWriter (ob);
+  if (xo == NULL) {
+    fprintf (stderr,
+             _("%s: xmlNewTextWriter: failed to create libxml2 writer\n"),
+             program_name);
+    exit (EXIT_FAILURE);
+  }
+
+  /* Pretty-print the output. */
+  XMLERROR (-1, xmlTextWriterSetIndent (xo, 1));
+  XMLERROR (-1, xmlTextWriterSetIndentString (xo, BAD_CAST "  "));
+
+  XMLERROR (-1, xmlTextWriterStartDocument (xo, NULL, NULL, NULL));
+  output_roots (xo, roots);
+  XMLERROR (-1, xmlTextWriterEndDocument (xo));
+
+  /* 'ob' is freed by this too. */
+  xmlFreeTextWriter (xo);
+}
+
+static void
+output_roots (xmlTextWriterPtr xo, char **roots)
+{
+  size_t i;
+
+  XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "operatingsystems"));
+  for (i = 0; roots[i] != NULL; ++i)
+    output_root (xo, roots[i]);
+  XMLERROR (-1, xmlTextWriterEndElement (xo));
+}
+
+static void
+output_root (xmlTextWriterPtr xo, char *root)
+{
+  char *str;
+  int i;
+  char buf[32];
+  char canonical_root[strlen (root) + 1];
+
+  XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "operatingsystem"));
+
+  strcpy (canonical_root, root);
+  canonicalize (canonical_root);
+  XMLERROR (-1,
+    xmlTextWriterWriteElement (xo, BAD_CAST "root", BAD_CAST canonical_root));
+
+  str = guestfs_inspect_get_type (g, root);
+  if (!str) exit (EXIT_FAILURE);
+  if (STRNEQ (str, "unknown"))
+    XMLERROR (-1,
+      xmlTextWriterWriteElement (xo, BAD_CAST "name", BAD_CAST str));
+  free (str);
+
+  str = guestfs_inspect_get_arch (g, root);
+  if (!str) exit (EXIT_FAILURE);
+  if (STRNEQ (str, "unknown"))
+    XMLERROR (-1,
+      xmlTextWriterWriteElement (xo, BAD_CAST "arch", BAD_CAST str));
+  free (str);
+
+  str = guestfs_inspect_get_distro (g, root);
+  if (!str) exit (EXIT_FAILURE);
+  if (STRNEQ (str, "unknown"))
+    XMLERROR (-1,
+      xmlTextWriterWriteElement (xo, BAD_CAST "distro", BAD_CAST str));
+  free (str);
+
+  str = guestfs_inspect_get_product_name (g, root);
+  if (!str) exit (EXIT_FAILURE);
+  if (STRNEQ (str, "unknown"))
+    XMLERROR (-1,
+      xmlTextWriterWriteElement (xo, BAD_CAST "product_name", BAD_CAST str));
+  free (str);
+
+  i = guestfs_inspect_get_major_version (g, root);
+  snprintf (buf, sizeof buf, "%d", i);
+  XMLERROR (-1,
+    xmlTextWriterWriteElement (xo, BAD_CAST "major_version", BAD_CAST buf));
+  i = guestfs_inspect_get_minor_version (g, root);
+  snprintf (buf, sizeof buf, "%d", i);
+  XMLERROR (-1,
+    xmlTextWriterWriteElement (xo, BAD_CAST "minor_version", BAD_CAST buf));
+
+  str = guestfs_inspect_get_package_format (g, root);
+  if (!str) exit (EXIT_FAILURE);
+  if (STRNEQ (str, "unknown"))
+    XMLERROR (-1,
+      xmlTextWriterWriteElement (xo, BAD_CAST "package_format", BAD_CAST str));
+  free (str);
+
+  str = guestfs_inspect_get_package_management (g, root);
+  if (!str) exit (EXIT_FAILURE);
+  if (STRNEQ (str, "unknown"))
+    XMLERROR (-1,
+      xmlTextWriterWriteElement (xo, BAD_CAST "package_management",
+                                 BAD_CAST str));
+  free (str);
+
+  /* inspect-get-windows-systemroot will fail with non-windows guests,
+   * or if the systemroot could not be determined for a windows guest.
+   * Disable error output around this call.
+   */
+  DISABLE_GUESTFS_ERRORS_FOR (
+    str = guestfs_inspect_get_windows_systemroot (g, root);
+    if (str)
+      XMLERROR (-1,
+                xmlTextWriterWriteElement (xo, BAD_CAST "windows_systemroot",
+                                           BAD_CAST str));
+    free (str);
+  );
+
+  output_mountpoints (xo, root);
+
+  output_filesystems (xo, root);
+
+  output_applications (xo, root);
+
+  XMLERROR (-1, xmlTextWriterEndElement (xo));
+}
+
+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
+output_mountpoints (xmlTextWriterPtr xo, char *root)
+{
+  char **mountpoints;
+  size_t i;
+
+  mountpoints = guestfs_inspect_get_mountpoints (g, root);
+  if (mountpoints == NULL)
+    exit (EXIT_FAILURE);
+
+  /* Sort by key length, shortest key first, and then name, so the
+   * output is stable.
+   */
+  qsort (mountpoints, count_strings (mountpoints) / 2, 2 * sizeof (char *),
+         compare_keys_len);
+
+  XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "mountpoints"));
+
+  for (i = 0; mountpoints[i] != NULL; i += 2) {
+    canonicalize (mountpoints[i+1]);
+
+    XMLERROR (-1,
+              xmlTextWriterStartElement (xo, BAD_CAST "mountpoint"));
+    XMLERROR (-1,
+              xmlTextWriterWriteAttribute (xo, BAD_CAST "dev",
+                                           BAD_CAST mountpoints[i+1]));
+    XMLERROR (-1,
+              xmlTextWriterWriteString (xo, BAD_CAST mountpoints[i]));
+    XMLERROR (-1, xmlTextWriterEndElement (xo));
+  }
+
+  XMLERROR (-1, xmlTextWriterEndElement (xo));
+
+  free_strings (mountpoints);
+}
+
+static void
+output_filesystems (xmlTextWriterPtr xo, char *root)
+{
+  char **filesystems;
+  char *str;
+  size_t i;
+
+  filesystems = guestfs_inspect_get_filesystems (g, root);
+  if (filesystems == NULL)
+    exit (EXIT_FAILURE);
+
+  /* Sort by name so the output is stable. */
+  qsort (filesystems, count_strings (filesystems), sizeof (char *),
+         compare_keys);
+
+  XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "filesystems"));
+
+  for (i = 0; filesystems[i] != NULL; ++i) {
+    canonicalize (filesystems[i]);
+
+    XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "filesystem"));
+    XMLERROR (-1,
+              xmlTextWriterWriteAttribute (xo, BAD_CAST "dev",
+                                           BAD_CAST filesystems[i]));
+
+    DISABLE_GUESTFS_ERRORS_FOR (
+      str = guestfs_vfs_type (g, filesystems[i]);
+      if (str && str[0])
+        XMLERROR (-1,
+                  xmlTextWriterWriteElement (xo, BAD_CAST "type",
+                                             BAD_CAST str));
+      free (str);
+    );
+
+    DISABLE_GUESTFS_ERRORS_FOR (
+      str = guestfs_vfs_label (g, filesystems[i]);
+      if (str && str[0])
+        XMLERROR (-1,
+                  xmlTextWriterWriteElement (xo, BAD_CAST "label",
+                                             BAD_CAST str));
+      free (str);
+    );
+
+    DISABLE_GUESTFS_ERRORS_FOR (
+      str = guestfs_vfs_uuid (g, filesystems[i]);
+      if (str && str[0])
+        XMLERROR (-1,
+                  xmlTextWriterWriteElement (xo, BAD_CAST "uuid",
+                                             BAD_CAST str));
+      free (str);
+    );
+
+    XMLERROR (-1, xmlTextWriterEndElement (xo));
+  }
+
+  XMLERROR (-1, xmlTextWriterEndElement (xo));
+
+  free_strings (filesystems);
+}
+
+static void
+output_applications (xmlTextWriterPtr xo, char *root)
+{
+  struct guestfs_application_list *apps;
+  size_t i;
+
+  /* We need to mount everything up in order to read out the list of
+   * applications.
+   */
+  inspect_mount_root (root);
+
+  /* This returns an empty list if we simply couldn't determine the
+   * applications, so if it returns NULL then it's a real error.
+   */
+  apps = guestfs_inspect_list_applications (g, root);
+  if (apps == NULL)
+    exit (EXIT_FAILURE);
+  if (guestfs_umount_all (g) == -1)
+    exit (EXIT_FAILURE);
+
+  XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "applications"));
+
+  for (i = 0; i < apps->len; ++i) {
+    XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "application"));
+
+    assert (apps->val[i].app_name && apps->val[i].app_name[0]);
+    XMLERROR (-1,
+              xmlTextWriterWriteElement (xo, BAD_CAST "name",
+                                         BAD_CAST apps->val[i].app_name));
+
+    if (apps->val[i].app_display_name && apps->val[i].app_display_name[0])
+      XMLERROR (-1,
+        xmlTextWriterWriteElement (xo, BAD_CAST "display_name",
+                                   BAD_CAST apps->val[i].app_display_name));
+
+    if (apps->val[i].app_epoch != 0) {
+      char buf[32];
+
+      snprintf (buf, sizeof buf, "%d", apps->val[i].app_epoch);
+
+      XMLERROR (-1,
+        xmlTextWriterWriteElement (xo, BAD_CAST "epoch", BAD_CAST buf));
+    }
+
+    if (apps->val[i].app_version && apps->val[i].app_version[0])
+      XMLERROR (-1,
+        xmlTextWriterWriteElement (xo, BAD_CAST "version",
+                                   BAD_CAST apps->val[i].app_version));
+    if (apps->val[i].app_release && apps->val[i].app_release[0])
+      XMLERROR (-1,
+        xmlTextWriterWriteElement (xo, BAD_CAST "release",
+                                   BAD_CAST apps->val[i].app_release));
+    if (apps->val[i].app_install_path && apps->val[i].app_install_path[0])
+      XMLERROR (-1,
+        xmlTextWriterWriteElement (xo, BAD_CAST "install_path",
+                                   BAD_CAST apps->val[i].app_install_path));
+    if (apps->val[i].app_publisher && apps->val[i].app_publisher[0])
+      XMLERROR (-1,
+        xmlTextWriterWriteElement (xo, BAD_CAST "publisher",
+                                   BAD_CAST apps->val[i].app_publisher));
+    if (apps->val[i].app_url && apps->val[i].app_url[0])
+      XMLERROR (-1,
+        xmlTextWriterWriteElement (xo, BAD_CAST "url",
+                                   BAD_CAST apps->val[i].app_url));
+    if (apps->val[i].app_source_package && apps->val[i].app_source_package[0])
+      XMLERROR (-1,
+        xmlTextWriterWriteElement (xo, BAD_CAST "source_package",
+                                   BAD_CAST apps->val[i].app_source_package));
+    if (apps->val[i].app_summary && apps->val[i].app_summary[0])
+      XMLERROR (-1,
+        xmlTextWriterWriteElement (xo, BAD_CAST "summary",
+                                   BAD_CAST apps->val[i].app_summary));
+    if (apps->val[i].app_description && apps->val[i].app_description[0])
+      XMLERROR (-1,
+        xmlTextWriterWriteElement (xo, BAD_CAST "description",
+                                   BAD_CAST apps->val[i].app_description));
+
+    XMLERROR (-1, xmlTextWriterEndElement (xo));
+  }
+
+  XMLERROR (-1, xmlTextWriterEndElement (xo));
+
+  guestfs_free_application_list (apps);
+}
+
+/* "/dev/vda1" -> "/dev/sda1"
+ * See BLOCK DEVICE NAMING in guestfs(3).
+ */
+static void
+canonicalize (char *dev)
+{
+  if (STRPREFIX (dev, "/dev/") &&
+      (dev[5] == 'h' || dev[5] == 'v') &&
+      dev[6] == 'd' &&
+      c_isalpha (dev[7]) &&
+      c_isdigit (dev[8]))
+    dev[5] = 's';
+}
+
+static void
+free_strings (char **argv)
+{
+  int argc;
+
+  for (argc = 0; argv[argc] != NULL; ++argc)
+    free (argv[argc]);
+  free (argv);
+}
+
+static int
+count_strings (char *const *argv)
+{
+  int c;
+
+  for (c = 0; argv[c]; ++c)
+    ;
+  return c;
+}