fish: Reimplement -i option using new C-based inspection.
authorRichard Jones <rjones@redhat.com>
Mon, 2 Aug 2010 16:43:23 +0000 (17:43 +0100)
committerRichard Jones <rjones@redhat.com>
Tue, 17 Aug 2010 13:09:25 +0000 (14:09 +0100)
Don't shell out to virt-inspector.  Instead, use the new C-based
inspection APIs.

This is much faster.

The new syntax is slightly different:

  guestfish -a disk.img -i
  guestfish -d guest -i

However, the old syntax still works.

fish/Makefile.am
fish/fish.c
fish/fish.h
fish/guestfish.pod
fish/inspect.c [new file with mode: 0644]
po/POTFILES.in

index cd16733..9bc5b73 100644 (file)
@@ -44,6 +44,7 @@ guestfish_SOURCES = \
        fish.c \
        fish.h \
        glob.c \
+       inspect.c \
        lcd.c \
        man.c \
        more.c \
index bc7d96c..a896a92 100644 (file)
@@ -81,7 +81,6 @@ static void cleanup_readline (void);
 #ifdef HAVE_LIBREADLINE
 static void add_history_line (const char *);
 #endif
-static void print_shell_quote (FILE *stream, const char *str);
 
 /* Currently open libguestfs handle. */
 guestfs_h *g;
@@ -95,6 +94,7 @@ int exit_on_error = 1;
 int command_num = 0;
 int keys_from_stdin = 0;
 const char *libvirt_uri = NULL;
+int inspector = 0;
 
 static void __attribute__((noreturn))
 usage (int status)
@@ -126,7 +126,7 @@ usage (int status)
              "  -d|--domain guest    Add disks from libvirt guest\n"
              "  -D|--no-dest-paths   Don't tab-complete paths from guest fs\n"
              "  -f|--file file       Read commands from file\n"
-             "  -i|--inspector       Run virt-inspector to get disk mountpoints\n"
+             "  -i|--inspector       Automatically mount filesystems\n"
              "  --keys-from-stdin    Read passphrases from stdin\n"
              "  --listen             Listen for remote commands\n"
              "  -m|--mount dev[:mnt] Mount dev on mnt (if omitted, /)\n"
@@ -188,7 +188,6 @@ main (int argc, char *argv[])
   struct mp *mp;
   char *p, *file = NULL;
   int c;
-  int inspector = 0;
   int option_index;
   struct sigaction sa;
   int next_prepared_drive = 1;
@@ -228,7 +227,7 @@ main (int argc, char *argv[])
    * using it just above.
    *
    * getopt_long uses argv[0], so give it the sanitized name.  Save a copy
-   * of the original, in case it's needed in virt-inspector mode, below.
+   * of the original, in case it's needed below.
    */
   char *real_argv0 = argv[0];
   argv[0] = bad_cast (program_name);
@@ -398,115 +397,46 @@ main (int argc, char *argv[])
     }
   }
 
-  /* Inspector mode invalidates most of the other arguments. */
-  if (inspector) {
-    if (drvs || mps || remote_control_listen || remote_control ||
-        guestfs_get_selinux (g)) {
-      fprintf (stderr, _("%s: cannot use -i option with -a, -m, -N, "
-                         "--listen, --remote or --selinux\n"),
-               program_name);
-      exit (EXIT_FAILURE);
-    }
-    if (optind >= argc) {
-      fprintf (stderr,
-           _("%s: -i requires a libvirt domain or path(s) to disk image(s)\n"),
-               program_name);
-      exit (EXIT_FAILURE);
-    }
-
-    char *cmd;
-    size_t cmdlen;
-    FILE *fp = open_memstream (&cmd, &cmdlen);
-    if (fp == NULL) {
-      perror ("open_memstream");
-      exit (EXIT_FAILURE);
-    }
-
-    fprintf (fp, "virt-inspector");
+  /* Old-style -i syntax?  Since -a/-d/-N and -i was disallowed
+   * previously, if we have -i without any drives but with something
+   * on the command line, it must be old-style syntax.
+   */
+  if (inspector && drvs == NULL && optind < argc) {
     while (optind < argc) {
-      fputc (' ', fp);
-      print_shell_quote (fp, argv[optind]);
-      optind++;
-    }
-
-    if (read_only)
-      fprintf (fp, " --ro-fish");
-    else
-      fprintf (fp, " --fish");
-
-    if (fclose (fp) == -1) {
-      perror ("fclose");
-      exit (EXIT_FAILURE);
-    }
-
-    if (verbose)
-      fprintf (stderr,
-               "%s -i: running: %s\n", program_name, cmd);
-
-    FILE *pp = popen (cmd, "r");
-    if (pp == NULL) {
-      perror (cmd);
-      exit (EXIT_FAILURE);
-    }
-
-    char *cmd2;
-    fp = open_memstream (&cmd2, &cmdlen);
-    if (fp == NULL) {
-      perror ("open_memstream");
-      exit (EXIT_FAILURE);
-    }
-
-    fprintf (fp, "%s", real_argv0);
-
-    if (guestfs_get_verbose (g))
-      fprintf (fp, " -v");
-    if (!guestfs_get_autosync (g))
-      fprintf (fp, " -n");
-    if (guestfs_get_trace (g))
-      fprintf (fp, " -x");
-
-    char *insp = NULL;
-    size_t insplen;
-    if (getline (&insp, &insplen, pp) == -1) {
-      perror (cmd);
-      exit (EXIT_FAILURE);
-    }
-    fprintf (fp, " %s", insp);
-
-    if (pclose (pp) == -1) {
-      perror (cmd);
-      exit (EXIT_FAILURE);
-    }
-
-    if (fclose (fp) == -1) {
-      perror ("fclose");
-      exit (EXIT_FAILURE);
-    }
-
-    if (verbose)
-      fprintf (stderr,
-               "%s -i: running: %s\n", program_name, cmd2);
+      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->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;
+      }
 
-    int r = system (cmd2);
-    if (r == -1) {
-      perror (cmd2);
-      exit (EXIT_FAILURE);
+      optind++;
     }
-
-    free (cmd);
-    free (cmd2);
-    free (insp);
-
-    exit (WEXITSTATUS (r));
   }
 
   /* If we've got drives to add, add them now. */
   add_drives (drvs, 'a');
 
-  /* If we've got mountpoints or prepared drives, we must launch the
-   * guest and mount them.
+  /* If we've got mountpoints or prepared drives or -i option, we must
+   * launch the guest and mount them.
    */
-  if (next_prepared_drive > 1 || mps != NULL) {
+  if (next_prepared_drive > 1 || mps != NULL || inspector) {
     /* RHBZ#612178: If --listen flag is given, then we will fork into
      * the background in rc_listen().  However you can't do this while
      * holding a libguestfs handle open because the recovery process
@@ -519,6 +449,10 @@ main (int argc, char *argv[])
       guestfs_set_recovery_proc (g, 0);
 
     if (launch () == -1) exit (EXIT_FAILURE);
+
+    if (inspector)
+      inspect_mount ();
+
     prepare_drives (drvs);
     mount_mps (mps);
   }
@@ -747,7 +681,7 @@ script (int prompt)
   int global_exit_on_error = !prompt;
   int tilde_candidate;
 
-  if (prompt)
+  if (prompt) {
     printf (_("\n"
               "Welcome to guestfish, the libguestfs filesystem interactive shell for\n"
               "editing virtual machine filesystems.\n"
@@ -757,6 +691,12 @@ script (int prompt)
               "      'quit' to quit the shell\n"
               "\n"));
 
+    if (inspector) {
+      print_inspect_prompt ();
+      printf ("\n");
+    }
+  }
+
   while (!quit) {
     char *pipe = NULL;
 
@@ -1840,17 +1780,3 @@ read_key (const char *param)
 
   return ret;
 }
-
-static void
-print_shell_quote (FILE *stream, const char *str)
-{
-#define SAFE(c) (c_isalnum((c)) ||                                     \
-                 (c) == '/' || (c) == '-' || (c) == '_' || (c) == '.')
-  int i;
-
-  for (i = 0; str[i]; ++i) {
-    if (!SAFE(str[i]))
-      putc ('\\', stream);
-    putc (str[i], stream);
-  }
-}
index bf1f81c..660b8ee 100644 (file)
@@ -96,6 +96,10 @@ extern int do_echo (const char *cmd, int argc, char *argv[]);
 /* in edit.c */
 extern int do_edit (const char *cmd, int argc, char *argv[]);
 
+/* in inspect.c */
+extern void inspect_mount (void);
+extern void print_inspect_prompt (void);
+
 /* in lcd.c */
 extern int do_lcd (const char *cmd, int argc, char *argv[]);
 
index 8daebc8..cf1140a 100644 (file)
@@ -16,9 +16,9 @@ guestfish - the libguestfs Filesystem Interactive SHell
 
  guestfish -d libvirt-domain
 
- guestfish -i libvirt-domain
+ guestfish -a disk.img -i
 
- guestfish -i disk.img [disk.img ...]
+ guestfish -d libvirt-domain -i
 
 =head1 WARNING
 
@@ -75,13 +75,14 @@ Edit C</boot/grub/grub.conf> interactively:
    --mount /dev/sda1:/boot \
    edit /boot/grub/grub.conf
 
-=head2 Using virt-inspector
+=head2 Mount disks automatically
 
-Use the I<-i> option to get virt-inspector to mount
-the filesystems automatically as they would be mounted
-in the virtual machine:
+Use the I<-i> option to automatically mount the
+disks from a virtual machine:
 
- guestfish --ro -i disk.img cat /etc/group
+ guestfish --ro -a disk.img -i cat /etc/group
+
+ guestfish --ro -d libvirt-domain -i cat /etc/group
 
 =head2 As a script interpreter
 
@@ -170,28 +171,28 @@ scripts, use:
 
 =item B<-i> | B<--inspector>
 
-Run virt-inspector on the named libvirt domain or list of disk
-images.  If virt-inspector is available and if it can identify
-the domain or disk images, then partitions will be mounted
-correctly at start-up.
+Using L<virt-inspector(1)> code, inspect the disks looking for
+an operating system and mount filesystems as they would be
+mounted on the real virtual machine.
 
 Typical usage is either:
 
- guestfish -i myguest
+ guestfish -d myguest -i
 
 (for an inactive libvirt domain called I<myguest>), or:
 
- guestfish --ro -i myguest
+ guestfish --ro -d myguest -i
 
 (for active domains, readonly), or specify the block device directly:
 
- guestfish -i /dev/Guests/MyGuest
+ guestfish -a /dev/Guests/MyGuest -i
+
+Note that the command line syntax changed slightly over older
+versions of guestfish.  You can still use the old syntax:
 
-You cannot use I<-a>, I<-m>, I<-N>, I<--listen>, I<--remote> or
-I<--selinux> in conjunction with this option, and options other than
-I<--ro> might not behave correctly.
+ guestfish [--ro] -i disk.img
 
-See also: L<virt-inspector(1)>.
+ guestfish [--ro] -i libvirt-domain
 
 =item B<--keys-from-stdin>
 
diff --git a/fish/inspect.c b/fish/inspect.c
new file mode 100644 (file)
index 0000000..d17496f
--- /dev/null
@@ -0,0 +1,117 @@
+/* guestfish - the filesystem interactive shell
+ * 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 "fish.h"
+
+/* Global that saves the root device between inspect_mount and
+ * print_inspect_prompt.
+ */
+static char *root = NULL;
+
+static int
+compare_keys_len (const void *p1, const void *p2)
+{
+  const char *key1 = * (char * const *) p1;
+  const char *key2 = * (char * const *) p2;
+  return strlen (key1) - strlen (key2);
+}
+
+static int
+compare_keys (const void *p1, const void *p2)
+{
+  const char *key1 = * (char * const *) p1;
+  const char *key2 = * (char * const *) p2;
+  return strcasecmp (key1, key2);
+}
+
+/* This function implements the -i option. */
+void
+inspect_mount (void)
+{
+  char **roots = guestfs_inspect_os (g);
+  if (roots == NULL)
+    exit (EXIT_FAILURE);
+
+  if (roots[0] == NULL) {
+    fprintf (stderr, _("guestfish: no operating system was found on this disk\n"));
+    exit (EXIT_FAILURE);
+  }
+
+  if (roots[1] != NULL) {
+    fprintf (stderr, _("guestfish: multi-boot operating systems are not supported by the -i option\n"));
+    exit (EXIT_FAILURE);
+  }
+
+  root = roots[0];
+  free (roots);
+
+  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;
+  for (i = 0; mountpoints[i] != NULL; i += 2) {
+    int r;
+    if (!read_only)
+      r = guestfs_mount_options (g, "", mountpoints[i+1], mountpoints[i]);
+    else
+      r = guestfs_mount_ro (g, mountpoints[i+1], mountpoints[i]);
+    if (r == -1)
+      exit (EXIT_FAILURE);
+  }
+
+  free_strings (mountpoints);
+}
+
+/* This function is called only if the above function was called,
+ * and only after we've printed the prompt in interactive mode.
+ */
+void
+print_inspect_prompt (void)
+{
+  char *name = guestfs_inspect_get_product_name (g, root);
+  if (STRNEQ (name, "unknown"))
+    printf (_("Operating system: %s\n"), name);
+  free (name);
+
+  char **mountpoints = guestfs_inspect_get_mountpoints (g, root);
+  if (mountpoints == NULL)
+    return;
+
+  /* Sort by key. */
+  qsort (mountpoints, count_strings (mountpoints) / 2, 2 * sizeof (char *),
+         compare_keys);
+
+  size_t i;
+  for (i = 0; mountpoints[i] != NULL; i += 2)
+    printf (_("%s mounted on %s\n"), mountpoints[i+1], mountpoints[i]);
+
+  free_strings (mountpoints);
+}
index e463bbb..843e8e0 100644 (file)
@@ -76,6 +76,7 @@ fish/echo.c
 fish/edit.c
 fish/fish.c
 fish/glob.c
+fish/inspect.c
 fish/lcd.c
 fish/man.c
 fish/more.c