fish: Reimplement -i option using new C-based inspection.
[libguestfs.git] / fish / fish.c
index 68f26ed..a896a92 100644 (file)
 #include "closeout.h"
 #include "progname.h"
 
+/* List of drives added via -a, -d or -N options. */
 struct drv {
   struct drv *next;
-  char *filename;               /* disk filename (for -a or -N options) */
-  prep_data *data;              /* prepared type (for -N option only) */
-  char *device;                 /* device inside the appliance */
+  enum { drv_a, drv_d, drv_N } type;
+  union {
+    struct {
+      char *filename;       /* disk filename */
+    } a;
+    struct {
+      char *guest;          /* guest name */
+    } d;
+    struct {
+      char *filename;       /* disk filename (testX.img) */
+      prep_data *data;      /* prepared type */
+      char *device;         /* device inside the appliance */
+    } N;
+  };
 };
 
 struct mp {
@@ -56,7 +68,7 @@ struct mp {
   char *mountpoint;
 };
 
-static void add_drives (struct drv *drv);
+static char add_drives (struct drv *drv, char next_drive);
 static void prepare_drives (struct drv *drv);
 static void mount_mps (struct mp *mp);
 static int launch (void);
@@ -69,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;
@@ -82,6 +93,8 @@ int remote_control = 0;
 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)
@@ -109,9 +122,11 @@ usage (int status)
              "  -h|--cmd-help        List available commands\n"
              "  -h|--cmd-help cmd    Display detailed help on 'cmd'\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"
              "  -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"
@@ -145,10 +160,12 @@ main (int argc, char *argv[])
 
   enum { HELP_OPTION = CHAR_MAX + 1 };
 
-  static const char *options = "a:Df:h::im:nN:rv?Vx";
+  static const char *options = "a:c:d:Df:h::im:nN:rv?Vx";
   static const struct option long_options[] = {
     { "add", 1, 0, 'a' },
     { "cmd-help", 2, 0, 'h' },
+    { "connect", 1, 0, 'c' },
+    { "domain", 1, 0, 'd' },
     { "file", 1, 0, 'f' },
     { "help", 0, 0, HELP_OPTION },
     { "inspector", 0, 0, 'i' },
@@ -171,10 +188,8 @@ main (int argc, char *argv[])
   struct mp *mp;
   char *p, *file = NULL;
   int c;
-  int inspector = 0;
   int option_index;
   struct sigaction sa;
-  char next_drive = 'a';
   int next_prepared_drive = 1;
 
   initialize_readline ();
@@ -212,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);
@@ -262,15 +277,26 @@ main (int argc, char *argv[])
         perror ("malloc");
         exit (EXIT_FAILURE);
       }
-      drv->filename = optarg;
-      drv->data = NULL;
-      /* We could fill the device field in, but in fact we
-       * only use it for the -N option at present.
-       */
-      drv->device = NULL;
+      drv->type = drv_a;
+      drv->a.filename = optarg;
+      drv->next = drvs;
+      drvs = drv;
+      break;
+
+    case 'c':
+      libvirt_uri = optarg;
+      break;
+
+    case 'd':
+      drv = malloc (sizeof (struct drv));
+      if (!drv) {
+        perror ("malloc");
+        exit (EXIT_FAILURE);
+      }
+      drv->type = drv_d;
+      drv->d.guest = optarg;
       drv->next = drvs;
       drvs = drv;
-      next_drive++;
       break;
 
     case 'N':
@@ -283,16 +309,14 @@ main (int argc, char *argv[])
         perror ("malloc");
         exit (EXIT_FAILURE);
       }
-      if (asprintf (&drv->filename, "test%d.img",
+      drv->type = drv_N;
+      if (asprintf (&drv->N.filename, "test%d.img",
                     next_prepared_drive++) == -1) {
         perror ("asprintf");
         exit (EXIT_FAILURE);
       }
-      drv->data = create_prepared_file (optarg, drv->filename);
-      if (asprintf (&drv->device, "/dev/sd%c", next_drive++) == -1) {
-        perror ("asprintf");
-        exit (EXIT_FAILURE);
-      }
+      drv->N.data = create_prepared_file (optarg, drv->N.filename);
+      drv->N.device = NULL;     /* filled in by add_drives */
       drv->next = drvs;
       drvs = drv;
       break;
@@ -373,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);
+  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
@@ -494,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);
   }
@@ -584,21 +543,60 @@ mount_mps (struct mp *mp)
   }
 }
 
-static void
-add_drives (struct drv *drv)
+static char
+add_drives (struct drv *drv, char next_drive)
 {
   int r;
 
+  if (next_drive > 'z') {
+    fprintf (stderr,
+             _("guestfish: too many drives added on the command line\n"));
+    exit (EXIT_FAILURE);
+  }
+
   if (drv) {
-    add_drives (drv->next);
+    next_drive = add_drives (drv->next, next_drive);
 
-    if (drv->data /* -N option is not affected by --ro */ || !read_only)
-      r = guestfs_add_drive (g, drv->filename);
-    else
-      r = guestfs_add_drive_ro (g, drv->filename);
-    if (r == -1)
-      exit (EXIT_FAILURE);
+    switch (drv->type) {
+    case drv_a:
+      if (!read_only)
+        r = guestfs_add_drive (g, drv->a.filename);
+      else
+        r = guestfs_add_drive_ro (g, drv->a.filename);
+      if (r == -1)
+        exit (EXIT_FAILURE);
+
+      next_drive++;
+      break;
+
+    case drv_d:
+      r = add_libvirt_drives (drv->d.guest);
+      if (r == -1)
+        exit (EXIT_FAILURE);
+
+      next_drive += r;
+      break;
+
+    case drv_N:
+      /* -N option is not affected by --ro */
+      r = guestfs_add_drive (g, drv->N.filename);
+      if (r == -1)
+        exit (EXIT_FAILURE);
+
+      if (asprintf (&drv->N.device, "/dev/sd%c", next_drive) == -1) {
+        perror ("asprintf");
+        exit (EXIT_FAILURE);
+      }
+
+      next_drive++;
+      break;
+
+    default: /* keep GCC happy */
+      abort ();
+    }
   }
+
+  return next_drive;
 }
 
 static void
@@ -606,8 +604,8 @@ prepare_drives (struct drv *drv)
 {
   if (drv) {
     prepare_drives (drv->next);
-    if (drv->data)
-      prepare_drive (drv->filename, drv->data, drv->device);
+    if (drv->type == drv_N)
+      prepare_drive (drv->N.filename, drv->N.data, drv->N.device);
   }
 }
 
@@ -683,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"
@@ -693,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;
 
@@ -1776,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);
-  }
-}