fish: Add -c/--connect and -d/--domain options.
[libguestfs.git] / fish / fish.c
index 987df59..bc7d96c 100644 (file)
@@ -1,5 +1,5 @@
 /* guestfish - the filesystem interactive shell
 /* guestfish - the filesystem interactive shell
- * Copyright (C) 2009 Red Hat Inc.
+ * Copyright (C) 2009-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
  *
  * 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
@@ -18,8 +18,6 @@
 
 #include <config.h>
 
 
 #include <config.h>
 
-#define _GNU_SOURCE // for strchrnul
-
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <getopt.h>
 #include <signal.h>
 #include <assert.h>
 #include <getopt.h>
 #include <signal.h>
 #include <assert.h>
-#include <ctype.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <sys/types.h>
 #include <sys/wait.h>
+#include <locale.h>
+#include <termios.h>
 
 #ifdef HAVE_LIBREADLINE
 #include <readline/readline.h>
 
 #ifdef HAVE_LIBREADLINE
 #include <readline/readline.h>
 #include <guestfs.h>
 
 #include "fish.h"
 #include <guestfs.h>
 
 #include "fish.h"
+#include "c-ctype.h"
+#include "closeout.h"
+#include "progname.h"
+
+/* List of drives added via -a, -d or -N options. */
+struct drv {
+  struct drv *next;
+  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 {
   struct mp *next;
 
 struct mp {
   struct mp *next;
@@ -47,20 +68,20 @@ struct mp {
   char *mountpoint;
 };
 
   char *mountpoint;
 };
 
-struct drv {
-  struct drv *next;
-  char *filename;
-};
-
-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 void mount_mps (struct mp *mp);
+static int launch (void);
 static void interactive (void);
 static void shell_script (void);
 static void script (int prompt);
 static void cmdline (char *argv[], int optind, int argc);
 static void initialize_readline (void);
 static void cleanup_readline (void);
 static void interactive (void);
 static void shell_script (void);
 static void script (int prompt);
 static void cmdline (char *argv[], int optind, int argc);
 static void initialize_readline (void);
 static void cleanup_readline (void);
+#ifdef HAVE_LIBREADLINE
 static void add_history_line (const char *);
 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;
 
 /* Currently open libguestfs handle. */
 guestfs_h *g;
@@ -68,40 +89,32 @@ guestfs_h *g;
 int read_only = 0;
 int quit = 0;
 int verbose = 0;
 int read_only = 0;
 int quit = 0;
 int verbose = 0;
-int echo_commands = 0;
 int remote_control_listen = 0;
 int remote_control = 0;
 int exit_on_error = 1;
 int remote_control_listen = 0;
 int remote_control = 0;
 int exit_on_error = 1;
+int command_num = 0;
+int keys_from_stdin = 0;
+const char *libvirt_uri = NULL;
 
 
-int
-launch (guestfs_h *_g)
-{
-  assert (_g == g);
-
-  if (guestfs_is_config (g)) {
-    if (guestfs_launch (g) == -1)
-      return -1;
-    if (guestfs_wait_ready (g) == -1)
-      return -1;
-  }
-  return 0;
-}
-
-static void
-usage (void)
+static void __attribute__((noreturn))
+usage (int status)
 {
 {
-  fprintf (stderr,
-           _("guestfish: guest filesystem shell\n"
-             "guestfish lets you edit virtual machine filesystems\n"
+  if (status != EXIT_SUCCESS)
+    fprintf (stderr, _("Try `%s --help' for more information.\n"),
+             program_name);
+  else {
+    fprintf (stdout,
+           _("%s: guest filesystem shell\n"
+             "%s lets you edit virtual machine filesystems\n"
              "Copyright (C) 2009 Red Hat Inc.\n"
              "Usage:\n"
              "Copyright (C) 2009 Red Hat Inc.\n"
              "Usage:\n"
-             "  guestfish [--options] cmd [: cmd : cmd ...]\n"
-             "  guestfish -i libvirt-domain\n"
-             "  guestfish -i disk-image(s)\n"
+             "  %s [--options] cmd [: cmd : cmd ...]\n"
+             "  %s -i libvirt-domain\n"
+             "  %s -i disk-image(s)\n"
              "or for interactive use:\n"
              "or for interactive use:\n"
-             "  guestfish\n"
+             "  %s\n"
              "or from a shell script:\n"
              "or from a shell script:\n"
-             "  guestfish <<EOF\n"
+             "  %s <<EOF\n"
              "  cmd\n"
              "  ...\n"
              "  EOF\n"
              "  cmd\n"
              "  ...\n"
              "  EOF\n"
@@ -109,33 +122,57 @@ usage (void)
              "  -h|--cmd-help        List available commands\n"
              "  -h|--cmd-help cmd    Display detailed help on 'cmd'\n"
              "  -a|--add image       Add image\n"
              "  -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"
              "  -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"
+             "  --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"
              "  -n|--no-sync         Don't autosync\n"
              "  --listen             Listen for remote commands\n"
              "  -m|--mount dev[:mnt] Mount dev on mnt (if omitted, /)\n"
              "  -n|--no-sync         Don't autosync\n"
-             "  --remote[=pid]       Send commands to remote guestfish\n"
+             "  -N|--new type        Create prepared disk (test1.img, ...)\n"
+             "  --remote[=pid]       Send commands to remote %s\n"
              "  -r|--ro              Mount read-only\n"
              "  --selinux            Enable SELinux support\n"
              "  -v|--verbose         Verbose messages\n"
              "  -x                   Echo each command before executing it\n"
              "  -V|--version         Display version and exit\n"
              "  -r|--ro              Mount read-only\n"
              "  --selinux            Enable SELinux support\n"
              "  -v|--verbose         Verbose messages\n"
              "  -x                   Echo each command before executing it\n"
              "  -V|--version         Display version and exit\n"
-             "For more information,  see the manpage guestfish(1).\n"));
+             "For more information,  see the manpage %s(1).\n"),
+             program_name, program_name, program_name,
+             program_name, program_name, program_name,
+             program_name, program_name, program_name);
+  }
+  exit (status);
 }
 
 int
 main (int argc, char *argv[])
 {
 }
 
 int
 main (int argc, char *argv[])
 {
-  static const char *options = "a:Df:h::im:nrv?Vx";
+  /* Set global program name that is not polluted with libtool artifacts.  */
+  set_program_name (argv[0]);
+
+  atexit (close_stdout);
+
+  setlocale (LC_ALL, "");
+  bindtextdomain (PACKAGE, LOCALEBASEDIR);
+  textdomain (PACKAGE);
+
+  enum { HELP_OPTION = CHAR_MAX + 1 };
+
+  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' },
   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' },
     { "file", 1, 0, 'f' },
-    { "help", 0, 0, '?' },
+    { "help", 0, 0, HELP_OPTION },
     { "inspector", 0, 0, 'i' },
     { "inspector", 0, 0, 'i' },
+    { "keys-from-stdin", 0, 0, 0 },
     { "listen", 0, 0, 0 },
     { "mount", 1, 0, 'm' },
     { "listen", 0, 0, 0 },
     { "mount", 1, 0, 'm' },
+    { "new", 1, 0, 'N' },
     { "no-dest-paths", 0, 0, 'D' },
     { "no-sync", 0, 0, 'n' },
     { "remote", 2, 0, 0 },
     { "no-dest-paths", 0, 0, 'D' },
     { "no-sync", 0, 0, 'n' },
     { "remote", 2, 0, 0 },
@@ -154,6 +191,7 @@ main (int argc, char *argv[])
   int inspector = 0;
   int option_index;
   struct sigaction sa;
   int inspector = 0;
   int option_index;
   struct sigaction sa;
+  int next_prepared_drive = 1;
 
   initialize_readline ();
 
 
   initialize_readline ();
 
@@ -168,7 +206,7 @@ main (int argc, char *argv[])
   g = guestfs_create ();
   if (g == NULL) {
     fprintf (stderr, _("guestfs_create: failed to create handle\n"));
   g = guestfs_create ();
   if (g == NULL) {
     fprintf (stderr, _("guestfs_create: failed to create handle\n"));
-    exit (1);
+    exit (EXIT_FAILURE);
   }
 
   guestfs_set_autosync (g, 1);
   }
 
   guestfs_set_autosync (g, 1);
@@ -186,47 +224,100 @@ main (int argc, char *argv[])
       (argv[0][0] != '/' || strstr (argv[0], "/.libs/lt-") != NULL))
     guestfs_set_path (g, "appliance:" GUESTFS_DEFAULT_PATH);
 
       (argv[0][0] != '/' || strstr (argv[0], "/.libs/lt-") != NULL))
     guestfs_set_path (g, "appliance:" GUESTFS_DEFAULT_PATH);
 
+  /* CAUTION: we are careful to modify argv[0] here, only after
+   * 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.
+   */
+  char *real_argv0 = argv[0];
+  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 */
   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 (strcmp (long_options[option_index].name, "listen") == 0)
+      if (STREQ (long_options[option_index].name, "listen"))
         remote_control_listen = 1;
         remote_control_listen = 1;
-      else if (strcmp (long_options[option_index].name, "remote") == 0) {
+      else if (STREQ (long_options[option_index].name, "remote")) {
         if (optarg) {
           if (sscanf (optarg, "%d", &remote_control) != 1) {
         if (optarg) {
           if (sscanf (optarg, "%d", &remote_control) != 1) {
-            fprintf (stderr, _("guestfish: --listen=PID: PID was not a number: %s\n"), optarg);
-            exit (1);
+            fprintf (stderr, _("%s: --listen=PID: PID was not a number: %s\n"),
+                     program_name, optarg);
+            exit (EXIT_FAILURE);
           }
         } else {
           p = getenv ("GUESTFISH_PID");
           if (!p || sscanf (p, "%d", &remote_control) != 1) {
           }
         } else {
           p = getenv ("GUESTFISH_PID");
           if (!p || sscanf (p, "%d", &remote_control) != 1) {
-            fprintf (stderr, _("guestfish: remote: $GUESTFISH_PID must be set to the PID of the remote process\n"));
-            exit (1);
+            fprintf (stderr, _("%s: remote: $GUESTFISH_PID must be set"
+                               " to the PID of the remote process\n"),
+                     program_name);
+            exit (EXIT_FAILURE);
           }
         }
           }
         }
-      } else if (strcmp (long_options[option_index].name, "selinux") == 0) {
+      } else if (STREQ (long_options[option_index].name, "selinux")) {
         guestfs_set_selinux (g, 1);
         guestfs_set_selinux (g, 1);
+      } else if (STREQ (long_options[option_index].name, "keys-from-stdin")) {
+        keys_from_stdin = 1;
       } else {
       } else {
-        fprintf (stderr, _("guestfish: unknown long option: %s (%d)\n"),
-                 long_options[option_index].name, option_index);
-        exit (1);
+        fprintf (stderr, _("%s: unknown long option: %s (%d)\n"),
+                 program_name, long_options[option_index].name, option_index);
+        exit (EXIT_FAILURE);
       }
       break;
 
     case 'a':
       if (access (optarg, R_OK) != 0) {
         perror (optarg);
       }
       break;
 
     case 'a':
       if (access (optarg, R_OK) != 0) {
         perror (optarg);
-        exit (1);
+        exit (EXIT_FAILURE);
       }
       drv = malloc (sizeof (struct drv));
       if (!drv) {
         perror ("malloc");
       }
       drv = malloc (sizeof (struct drv));
       if (!drv) {
         perror ("malloc");
-        exit (1);
+        exit (EXIT_FAILURE);
       }
       }
-      drv->filename = optarg;
+      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;
+      break;
+
+    case 'N':
+      if (STRCASEEQ (optarg, "list")) {
+        list_prepared_drives ();
+        exit (EXIT_SUCCESS);
+      }
+      drv = malloc (sizeof (struct drv));
+      if (!drv) {
+        perror ("malloc");
+        exit (EXIT_FAILURE);
+      }
+      drv->type = drv_N;
+      if (asprintf (&drv->N.filename, "test%d.img",
+                    next_prepared_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;
       drv->next = drvs;
       drvs = drv;
       break;
@@ -237,20 +328,25 @@ main (int argc, char *argv[])
 
     case 'f':
       if (file) {
 
     case 'f':
       if (file) {
-        fprintf (stderr, _("guestfish: only one -f parameter can be given\n"));
-        exit (1);
+        fprintf (stderr, _("%s: only one -f parameter can be given\n"),
+                 program_name);
+        exit (EXIT_FAILURE);
       }
       file = optarg;
       break;
 
       }
       file = optarg;
       break;
 
-    case 'h':
+    case 'h': {
+      int r = 0;
+
       if (optarg)
       if (optarg)
-        display_command (optarg);
+        r = display_command (optarg);
       else if (argv[optind] && argv[optind][0] != '-')
       else if (argv[optind] && argv[optind][0] != '-')
-        display_command (argv[optind++]);
+        r = display_command (argv[optind++]);
       else
         list_commands ();
       else
         list_commands ();
-      exit (0);
+
+      exit (r == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+    }
 
     case 'i':
       inspector = 1;
 
     case 'i':
       inspector = 1;
@@ -260,14 +356,14 @@ main (int argc, char *argv[])
       mp = malloc (sizeof (struct mp));
       if (!mp) {
         perror ("malloc");
       mp = malloc (sizeof (struct mp));
       if (!mp) {
         perror ("malloc");
-        exit (1);
+        exit (EXIT_FAILURE);
       }
       p = strchr (optarg, ':');
       if (p) {
         *p = '\0';
         mp->mountpoint = p+1;
       } else
       }
       p = strchr (optarg, ':');
       if (p) {
         *p = '\0';
         mp->mountpoint = p+1;
       } else
-        mp->mountpoint = "/";
+        mp->mountpoint = bad_cast ("/");
       mp->device = optarg;
       mp->next = mps;
       mps = mp;
       mp->device = optarg;
       mp->next = mps;
       mps = mp;
@@ -287,99 +383,166 @@ main (int argc, char *argv[])
       break;
 
     case 'V':
       break;
 
     case 'V':
-      printf ("guestfish %s\n", PACKAGE_VERSION);
-      exit (0);
+      printf ("%s %s\n", program_name, PACKAGE_VERSION);
+      exit (EXIT_SUCCESS);
 
     case 'x':
 
     case 'x':
-      echo_commands = 1;
+      guestfs_set_trace (g, 1);
       break;
 
       break;
 
-    case '?':
-      usage ();
-      exit (0);
+    case HELP_OPTION:
+      usage (EXIT_SUCCESS);
 
     default:
 
     default:
-      fprintf (stderr, _("guestfish: unexpected command line option 0x%x\n"),
-               c);
-      exit (1);
+      usage (EXIT_FAILURE);
     }
   }
 
   /* Inspector mode invalidates most of the other arguments. */
   if (inspector) {
     }
   }
 
   /* Inspector mode invalidates most of the other arguments. */
   if (inspector) {
-    char cmd[1024];
-    int r;
-
     if (drvs || mps || remote_control_listen || remote_control ||
         guestfs_get_selinux (g)) {
     if (drvs || mps || remote_control_listen || remote_control ||
         guestfs_get_selinux (g)) {
-      fprintf (stderr, _("guestfish: cannot use -i option with -a, -m, --listen, --remote or --selinux\n"));
-      exit (1);
+      fprintf (stderr, _("%s: cannot use -i option with -a, -m, -N, "
+                         "--listen, --remote or --selinux\n"),
+               program_name);
+      exit (EXIT_FAILURE);
     }
     if (optind >= argc) {
     }
     if (optind >= argc) {
-      fprintf (stderr, _("guestfish -i requires a libvirt domain or path(s) to disk image(s)\n"));
-      exit (1);
+      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);
     }
 
     }
 
-    strcpy (cmd, "a=`virt-inspector");
+    fprintf (fp, "virt-inspector");
     while (optind < argc) {
     while (optind < argc) {
-      if (strlen (cmd) + strlen (argv[optind]) + strlen (argv[0]) + 60
-          >= sizeof cmd) {
-        fprintf (stderr, _("guestfish: virt-inspector command too long for fixed-size buffer\n"));
-        exit (1);
-      }
-      strcat (cmd, " '");
-      strcat (cmd, argv[optind]);
-      strcat (cmd, "'");
+      fputc (' ', fp);
+      print_shell_quote (fp, argv[optind]);
       optind++;
     }
 
     if (read_only)
       optind++;
     }
 
     if (read_only)
-      strcat (cmd, " --ro-fish");
+      fprintf (fp, " --ro-fish");
     else
     else
-      strcat (cmd, " --fish");
+      fprintf (fp, " --fish");
+
+    if (fclose (fp) == -1) {
+      perror ("fclose");
+      exit (EXIT_FAILURE);
+    }
+
+    if (verbose)
+      fprintf (stderr,
+               "%s -i: running: %s\n", program_name, cmd);
 
 
-    sprintf (&cmd[strlen(cmd)], "` && %s $a", argv[0]);
+    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))
 
     if (guestfs_get_verbose (g))
-      strcat (cmd, " -v");
+      fprintf (fp, " -v");
     if (!guestfs_get_autosync (g))
     if (!guestfs_get_autosync (g))
-      strcat (cmd, " -n");
+      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,
 
     if (verbose)
       fprintf (stderr,
-               "guestfish -i: running virt-inspector command:\n%s\n", cmd);
+               "%s -i: running: %s\n", program_name, cmd2);
 
 
-    r = system (cmd);
+    int r = system (cmd2);
     if (r == -1) {
     if (r == -1) {
-      perror ("system");
-      exit (1);
+      perror (cmd2);
+      exit (EXIT_FAILURE);
     }
     }
+
+    free (cmd);
+    free (cmd2);
+    free (insp);
+
     exit (WEXITSTATUS (r));
   }
 
   /* If we've got drives to add, add them now. */
     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 (next_prepared_drive > 1 || mps != NULL) {
+    /* 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
+     * will think the main program has died and kill qemu.  Therefore
+     * don't use the recovery process for this case.  (A better
+     * solution would be to call launch () etc after the fork, but
+     * that greatly complicates the code here).
+     */
+    if (remote_control_listen)
+      guestfs_set_recovery_proc (g, 0);
 
 
-  /* If we've got mountpoints, we must launch the guest and mount them. */
-  if (mps != NULL) {
-    if (launch (g) == -1) exit (1);
+    if (launch () == -1) exit (EXIT_FAILURE);
+    prepare_drives (drvs);
     mount_mps (mps);
   }
 
   /* Remote control? */
   if (remote_control_listen && remote_control) {
     mount_mps (mps);
   }
 
   /* Remote control? */
   if (remote_control_listen && remote_control) {
-    fprintf (stderr, _("guestfish: cannot use --listen and --remote options at the same time\n"));
-    exit (1);
+    fprintf (stderr,
+             _("%s: cannot use --listen and --remote options at the same time\n"),
+             program_name);
+    exit (EXIT_FAILURE);
   }
 
   if (remote_control_listen) {
     if (optind < argc) {
   }
 
   if (remote_control_listen) {
     if (optind < argc) {
-      fprintf (stderr, _("guestfish: extra parameters on the command line with --listen flag\n"));
-      exit (1);
+      fprintf (stderr,
+               _("%s: extra parameters on the command line with --listen flag\n"),
+               program_name);
+      exit (EXIT_FAILURE);
     }
     if (file) {
     }
     if (file) {
-      fprintf (stderr, _("guestfish: cannot use --listen and --file options at the same time\n"));
-      exit (1);
+      fprintf (stderr,
+               _("%s: cannot use --listen and --file options at the same time\n"),
+               program_name);
+      exit (EXIT_FAILURE);
     }
     rc_listen ();
   }
     }
     rc_listen ();
   }
@@ -389,7 +552,7 @@ main (int argc, char *argv[])
     close (0);
     if (open (file, O_RDONLY) == -1) {
       perror (file);
     close (0);
     if (open (file, O_RDONLY) == -1) {
       perror (file);
-      exit (1);
+      exit (EXIT_FAILURE);
     }
   }
 
     }
   }
 
@@ -405,7 +568,7 @@ main (int argc, char *argv[])
 
   cleanup_readline ();
 
 
   cleanup_readline ();
 
-  exit (0);
+  exit (EXIT_SUCCESS);
 }
 
 void
 }
 
 void
@@ -421,7 +584,7 @@ pod2text (const char *name, const char *shortdesc, const char *str)
     printf ("%s - %s\n\n%s\n", name, shortdesc, str);
     return;
   }
     printf ("%s - %s\n\n%s\n", name, shortdesc, str);
     return;
   }
-  fprintf (fp, "=head1 %s - %s\n\n", name, shortdesc);
+  fprintf (fp, "=head1 NAME\n\n%s - %s\n\n", name, shortdesc);
   fputs (str, fp);
   pclose (fp);
 }
   fputs (str, fp);
   pclose (fp);
 }
@@ -434,29 +597,92 @@ mount_mps (struct mp *mp)
 
   if (mp) {
     mount_mps (mp->next);
 
   if (mp) {
     mount_mps (mp->next);
-    if (!read_only)
-      r = guestfs_mount (g, mp->device, mp->mountpoint);
-    else
-      r = guestfs_mount_ro (g, mp->device, mp->mountpoint);
+
+    /* Don't use guestfs_mount here because that will default to mount
+     * options -o sync,noatime.  For more information, see guestfs(3)
+     * section "LIBGUESTFS GOTCHAS".
+     */
+    const char *options = read_only ? "ro" : "";
+    r = guestfs_mount_options (g, options, mp->device, mp->mountpoint);
     if (r == -1)
     if (r == -1)
-      exit (1);
+      exit (EXIT_FAILURE);
   }
 }
 
   }
 }
 
-static void
-add_drives (struct drv *drv)
+static char
+add_drives (struct drv *drv, char next_drive)
 {
   int r;
 
 {
   int r;
 
+  if (next_drive > 'z') {
+    fprintf (stderr,
+             _("guestfish: too many drives added on the command line\n"));
+    exit (EXIT_FAILURE);
+  }
+
   if (drv) {
   if (drv) {
-    add_drives (drv->next);
-    if (!read_only)
-      r = guestfs_add_drive (g, drv->filename);
-    else
-      r = guestfs_add_drive_ro (g, drv->filename);
-    if (r == -1)
-      exit (1);
+    next_drive = add_drives (drv->next, next_drive);
+
+    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
+prepare_drives (struct drv *drv)
+{
+  if (drv) {
+    prepare_drives (drv->next);
+    if (drv->type == drv_N)
+      prepare_drive (drv->N.filename, drv->N.data, drv->N.device);
+  }
+}
+
+static int
+launch (void)
+{
+  if (guestfs_is_config (g)) {
+    if (guestfs_launch (g) == -1)
+      return -1;
+  }
+  return 0;
 }
 
 static void
 }
 
 static void
@@ -517,7 +743,7 @@ script (int prompt)
   char *cmd;
   char *p, *pend;
   char *argv[64];
   char *cmd;
   char *p, *pend;
   char *argv[64];
-  int i, len;
+  int len;
   int global_exit_on_error = !prompt;
   int tilde_candidate;
 
   int global_exit_on_error = !prompt;
   int tilde_candidate;
 
@@ -526,7 +752,8 @@ script (int prompt)
               "Welcome to guestfish, the libguestfs filesystem interactive shell for\n"
               "editing virtual machine filesystems.\n"
               "\n"
               "Welcome to guestfish, the libguestfs filesystem interactive shell for\n"
               "editing virtual machine filesystems.\n"
               "\n"
-              "Type: 'help' for help with commands\n"
+              "Type: 'help' for a list of commands\n"
+              "      'man' to read the manual\n"
               "      'quit' to quit the shell\n"
               "\n"));
 
               "      'quit' to quit the shell\n"
               "\n"));
 
@@ -543,7 +770,7 @@ script (int prompt)
 
     /* Skip any initial whitespace before the command. */
   again:
 
     /* Skip any initial whitespace before the command. */
   again:
-    while (*buf && isspace (*buf))
+    while (*buf && c_isspace (*buf))
       buf++;
 
     if (!*buf) continue;
       buf++;
 
     if (!*buf) continue;
@@ -561,7 +788,7 @@ script (int prompt)
             (WIFSIGNALED (r) &&
              (WTERMSIG (r) == SIGINT || WTERMSIG (r) == SIGQUIT)) ||
             WEXITSTATUS (r) != 0)
             (WIFSIGNALED (r) &&
              (WTERMSIG (r) == SIGINT || WTERMSIG (r) == SIGQUIT)) ||
             WEXITSTATUS (r) != 0)
-          exit (1);
+          exit (EXIT_FAILURE);
       }
       continue;
     }
       }
       continue;
     }
@@ -581,7 +808,7 @@ script (int prompt)
     if (len == 0) continue;
 
     cmd = buf;
     if (len == 0) continue;
 
     cmd = buf;
-    i = 0;
+    unsigned int i = 0;
     if (buf[len] == '\0') {
       argv[0] = NULL;
       goto got_command;
     if (buf[len] == '\0') {
       argv[0] = NULL;
       goto got_command;
@@ -602,13 +829,15 @@ script (int prompt)
         p++;
         len = strcspn (p, "\"");
         if (p[len] == '\0') {
         p++;
         len = strcspn (p, "\"");
         if (p[len] == '\0') {
-          fprintf (stderr, _("guestfish: unterminated double quote\n"));
-          if (exit_on_error) exit (1);
+          fprintf (stderr, _("%s: unterminated double quote\n"), program_name);
+          if (exit_on_error) exit (EXIT_FAILURE);
           goto next_command;
         }
         if (p[len+1] && (p[len+1] != ' ' && p[len+1] != '\t')) {
           goto next_command;
         }
         if (p[len+1] && (p[len+1] != ' ' && p[len+1] != '\t')) {
-          fprintf (stderr, _("guestfish: command arguments not separated by whitespace\n"));
-          if (exit_on_error) exit (1);
+          fprintf (stderr,
+                   _("%s: command arguments not separated by whitespace\n"),
+                   program_name);
+          if (exit_on_error) exit (EXIT_FAILURE);
           goto next_command;
         }
         p[len] = '\0';
           goto next_command;
         }
         p[len] = '\0';
@@ -617,13 +846,15 @@ script (int prompt)
         p++;
         len = strcspn (p, "'");
         if (p[len] == '\0') {
         p++;
         len = strcspn (p, "'");
         if (p[len] == '\0') {
-          fprintf (stderr, _("guestfish: unterminated single quote\n"));
-          if (exit_on_error) exit (1);
+          fprintf (stderr, _("%s: unterminated single quote\n"), program_name);
+          if (exit_on_error) exit (EXIT_FAILURE);
           goto next_command;
         }
         if (p[len+1] && (p[len+1] != ' ' && p[len+1] != '\t')) {
           goto next_command;
         }
         if (p[len+1] && (p[len+1] != ' ' && p[len+1] != '\t')) {
-          fprintf (stderr, _("guestfish: command arguments not separated by whitespace\n"));
-          if (exit_on_error) exit (1);
+          fprintf (stderr,
+                   _("%s: command arguments not separated by whitespace\n"),
+                   program_name);
+          if (exit_on_error) exit (EXIT_FAILURE);
           goto next_command;
         }
         p[len] = '\0';
           goto next_command;
         }
         p[len] = '\0';
@@ -643,13 +874,16 @@ script (int prompt)
           pend++;
         }
         if (c != 0) {
           pend++;
         }
         if (c != 0) {
-          fprintf (stderr, _("guestfish: unterminated \"[...]\" sequence\n"));
-          if (exit_on_error) exit (1);
+          fprintf (stderr,
+                   _("%s: unterminated \"[...]\" sequence\n"), program_name);
+          if (exit_on_error) exit (EXIT_FAILURE);
           goto next_command;
         }
         if (*pend && (*pend != ' ' && *pend != '\t')) {
           goto next_command;
         }
         if (*pend && (*pend != ' ' && *pend != '\t')) {
-          fprintf (stderr, _("guestfish: command arguments not separated by whitespace\n"));
-          if (exit_on_error) exit (1);
+          fprintf (stderr,
+                   _("%s: command arguments not separated by whitespace\n"),
+                   program_name);
+          if (exit_on_error) exit (EXIT_FAILURE);
           goto next_command;
         }
         *(pend-1) = '\0';
           goto next_command;
         }
         *(pend-1) = '\0';
@@ -667,8 +901,8 @@ script (int prompt)
         } else
           pend = &p[len];
       } else {
         } else
           pend = &p[len];
       } else {
-        fprintf (stderr, _("guestfish: internal error parsing string at '%s'\n"),
-                 p);
+        fprintf (stderr, _("%s: internal error parsing string at '%s'\n"),
+                 program_name, p);
         abort ();
       }
 
         abort ();
       }
 
@@ -684,8 +918,8 @@ script (int prompt)
     }
 
     if (i == sizeof argv / sizeof argv[0]) {
     }
 
     if (i == sizeof argv / sizeof argv[0]) {
-      fprintf (stderr, _("guestfish: too many arguments\n"));
-      if (exit_on_error) exit (1);
+      fprintf (stderr, _("%s: too many arguments\n"), program_name);
+      if (exit_on_error) exit (EXIT_FAILURE);
       goto next_command;
     }
 
       goto next_command;
     }
 
@@ -693,7 +927,7 @@ script (int prompt)
 
   got_command:
     if (issue_command (cmd, argv, pipe) == -1) {
 
   got_command:
     if (issue_command (cmd, argv, pipe) == -1) {
-      if (exit_on_error) exit (1);
+      if (exit_on_error) exit (EXIT_FAILURE);
     }
 
   next_command:;
     }
 
   next_command:;
@@ -712,21 +946,32 @@ cmdline (char *argv[], int optind, int argc)
   if (optind >= argc) return;
 
   cmd = argv[optind++];
   if (optind >= argc) return;
 
   cmd = argv[optind++];
-  if (strcmp (cmd, ":") == 0) {
-    fprintf (stderr, _("guestfish: empty command on command line\n"));
-    exit (1);
+  if (STREQ (cmd, ":")) {
+    fprintf (stderr, _("%s: empty command on command line\n"), program_name);
+    exit (EXIT_FAILURE);
+  }
+
+  /* Allow -cmd on the command line to mean (temporarily) override
+   * the normal exit on error (RHBZ#578407).
+   */
+  if (cmd[0] == '-') {
+    exit_on_error = 0;
+    cmd++;
   }
   }
+
   params = &argv[optind];
 
   /* Search for end of command list or ":" ... */
   params = &argv[optind];
 
   /* Search for end of command list or ":" ... */
-  while (optind < argc && strcmp (argv[optind], ":") != 0)
+  while (optind < argc && STRNEQ (argv[optind], ":"))
     optind++;
 
   if (optind == argc) {
     optind++;
 
   if (optind == argc) {
-    if (issue_command (cmd, params, NULL) == -1) exit (1);
+    if (issue_command (cmd, params, NULL) == -1 && exit_on_error)
+        exit (EXIT_FAILURE);
   } else {
     argv[optind] = NULL;
   } else {
     argv[optind] = NULL;
-    if (issue_command (cmd, params, NULL) == -1) exit (1);
+    if (issue_command (cmd, params, NULL) == -1 && exit_on_error)
+      exit (EXIT_FAILURE);
     cmdline (argv, optind+1, argc);
   }
 }
     cmdline (argv, optind+1, argc);
   }
 }
@@ -739,12 +984,8 @@ issue_command (const char *cmd, char *argv[], const char *pipecmd)
   int pid = 0;
   int i, r;
 
   int pid = 0;
   int i, r;
 
-  if (echo_commands) {
-    printf ("%s", cmd);
-    for (i = 0; argv[i] != NULL; ++i)
-      printf (" %s", argv[i]);
-    printf ("\n");
-  }
+  /* This counts the commands issued, starting at 1. */
+  command_num++;
 
   /* For | ... commands.  Annoyingly we can't use popen(3) here. */
   if (pipecmd) {
 
   /* For | ... commands.  Annoyingly we can't use popen(3) here. */
   if (pipecmd) {
@@ -800,38 +1041,45 @@ issue_command (const char *cmd, char *argv[], const char *pipecmd)
     r = rc_remote (remote_control, cmd, argc, argv, exit_on_error);
 
   /* Otherwise execute it locally. */
     r = rc_remote (remote_control, cmd, argc, argv, exit_on_error);
 
   /* Otherwise execute it locally. */
-  else if (strcasecmp (cmd, "help") == 0) {
-    if (argc == 0)
+  else if (STRCASEEQ (cmd, "help")) {
+    if (argc == 0) {
       list_commands ();
       list_commands ();
-    else
-      display_command (argv[0]);
-    r = 0;
+      r = 0;
+    } else
+      r = display_command (argv[0]);
   }
   }
-  else if (strcasecmp (cmd, "quit") == 0 ||
-           strcasecmp (cmd, "exit") == 0 ||
-           strcasecmp (cmd, "q") == 0) {
+  else if (STRCASEEQ (cmd, "quit") ||
+           STRCASEEQ (cmd, "exit") ||
+           STRCASEEQ (cmd, "q")) {
     quit = 1;
     r = 0;
   }
     quit = 1;
     r = 0;
   }
-  else if (strcasecmp (cmd, "alloc") == 0 ||
-           strcasecmp (cmd, "allocate") == 0)
+  else if (STRCASEEQ (cmd, "alloc") ||
+           STRCASEEQ (cmd, "allocate"))
     r = do_alloc (cmd, argc, argv);
     r = do_alloc (cmd, argc, argv);
-  else if (strcasecmp (cmd, "echo") == 0)
+  else if (STRCASEEQ (cmd, "echo"))
     r = do_echo (cmd, argc, argv);
     r = do_echo (cmd, argc, argv);
-  else if (strcasecmp (cmd, "edit") == 0 ||
-           strcasecmp (cmd, "vi") == 0 ||
-           strcasecmp (cmd, "emacs") == 0)
+  else if (STRCASEEQ (cmd, "edit") ||
+           STRCASEEQ (cmd, "vi") ||
+           STRCASEEQ (cmd, "emacs"))
     r = do_edit (cmd, argc, argv);
     r = do_edit (cmd, argc, argv);
-  else if (strcasecmp (cmd, "lcd") == 0)
+  else if (STRCASEEQ (cmd, "lcd"))
     r = do_lcd (cmd, argc, argv);
     r = do_lcd (cmd, argc, argv);
-  else if (strcasecmp (cmd, "glob") == 0)
+  else if (STRCASEEQ (cmd, "glob"))
     r = do_glob (cmd, argc, argv);
     r = do_glob (cmd, argc, argv);
-  else if (strcasecmp (cmd, "more") == 0 ||
-           strcasecmp (cmd, "less") == 0)
+  else if (STRCASEEQ (cmd, "man") ||
+           STRCASEEQ (cmd, "manual"))
+    r = do_man (cmd, argc, argv);
+  else if (STRCASEEQ (cmd, "more") ||
+           STRCASEEQ (cmd, "less"))
     r = do_more (cmd, argc, argv);
     r = do_more (cmd, argc, argv);
-  else if (strcasecmp (cmd, "reopen") == 0)
+  else if (STRCASEEQ (cmd, "reopen"))
     r = do_reopen (cmd, argc, argv);
     r = do_reopen (cmd, argc, argv);
-  else if (strcasecmp (cmd, "time") == 0)
+  else if (STRCASEEQ (cmd, "sparse"))
+    r = do_sparse (cmd, argc, argv);
+  else if (STRCASEEQ (cmd, "supported"))
+    r = do_supported (cmd, argc, argv);
+  else if (STRCASEEQ (cmd, "time"))
     r = do_time (cmd, argc, argv);
   else
     r = run_action (cmd, argc, argv);
     r = do_time (cmd, argc, argv);
   else
     r = run_action (cmd, argc, argv);
@@ -863,10 +1111,12 @@ issue_command (const char *cmd, char *argv[], const char *pipecmd)
 void
 list_builtin_commands (void)
 {
 void
 list_builtin_commands (void)
 {
-  /* help and quit should appear at the top */
+  /* help, man and quit should appear at the top */
   printf ("%-20s %s\n",
           "help", _("display a list of commands or help on a command"));
   printf ("%-20s %s\n",
   printf ("%-20s %s\n",
           "help", _("display a list of commands or help on a command"));
   printf ("%-20s %s\n",
+          "man", _("read the manual"));
+  printf ("%-20s %s\n",
           "quit", _("quit guestfish"));
 
   printf ("%-20s %s\n",
           "quit", _("quit guestfish"));
 
   printf ("%-20s %s\n",
@@ -884,18 +1134,22 @@ list_builtin_commands (void)
   printf ("%-20s %s\n",
           "reopen", _("close and reopen libguestfs handle"));
   printf ("%-20s %s\n",
   printf ("%-20s %s\n",
           "reopen", _("close and reopen libguestfs handle"));
   printf ("%-20s %s\n",
+          "sparse", _("allocate a sparse image file"));
+  printf ("%-20s %s\n",
+          "supported", _("list supported groups of commands"));
+  printf ("%-20s %s\n",
           "time", _("measure time taken to run command"));
 
   /* actions are printed after this (see list_commands) */
 }
 
           "time", _("measure time taken to run command"));
 
   /* actions are printed after this (see list_commands) */
 }
 
-void
+int
 display_builtin_command (const char *cmd)
 {
   /* help for actions is auto-generated, see display_command */
 
 display_builtin_command (const char *cmd)
 {
   /* help for actions is auto-generated, see display_command */
 
-  if (strcasecmp (cmd, "alloc") == 0 ||
-      strcasecmp (cmd, "allocate") == 0)
+  if (STRCASEEQ (cmd, "alloc") ||
+      STRCASEEQ (cmd, "allocate")) {
     printf (_("alloc - allocate an image\n"
               "     alloc <filename> <size>\n"
               "\n"
     printf (_("alloc - allocate an image\n"
               "     alloc <filename> <size>\n"
               "\n"
@@ -904,54 +1158,69 @@ display_builtin_command (const char *cmd)
               "\n"
               "    For more advanced image creation, see qemu-img utility.\n"
               "\n"
               "\n"
               "    For more advanced image creation, see qemu-img utility.\n"
               "\n"
-              "    Size can be specified (where <nn> means a number):\n"
-              "    <nn>             number of kilobytes\n"
-              "      eg: 1440       standard 3.5\" floppy\n"
-              "    <nn>K or <nn>KB  number of kilobytes\n"
-              "    <nn>M or <nn>MB  number of megabytes\n"
-              "    <nn>G or <nn>GB  number of gigabytes\n"
-              "    <nn>sects        number of 512 byte sectors\n"));
-  else if (strcasecmp (cmd, "echo") == 0)
+              "    Size can be specified using standard suffixes, eg. '1M'.\n"
+              ));
+    return 0;
+  }
+  else if (STRCASEEQ (cmd, "echo")) {
     printf (_("echo - display a line of text\n"
               "     echo [<params> ...]\n"
               "\n"
               "    This echos the parameters to the terminal.\n"));
     printf (_("echo - display a line of text\n"
               "     echo [<params> ...]\n"
               "\n"
               "    This echos the parameters to the terminal.\n"));
-  else if (strcasecmp (cmd, "edit") == 0 ||
-           strcasecmp (cmd, "vi") == 0 ||
-           strcasecmp (cmd, "emacs") == 0)
+    return 0;
+  }
+  else if (STRCASEEQ (cmd, "edit") ||
+           STRCASEEQ (cmd, "vi") ||
+           STRCASEEQ (cmd, "emacs")) {
     printf (_("edit - edit a file in the image\n"
               "     edit <filename>\n"
               "\n"
               "    This is used to edit a file.\n"
               "\n"
               "    It is the equivalent of (and is implemented by)\n"
     printf (_("edit - edit a file in the image\n"
               "     edit <filename>\n"
               "\n"
               "    This is used to edit a file.\n"
               "\n"
               "    It is the equivalent of (and is implemented by)\n"
-              "    running \"cat\", editing locally, and then \"write-file\".\n"
+              "    running \"cat\", editing locally, and then \"write\".\n"
               "\n"
               "    Normally it uses $EDITOR, but if you use the aliases\n"
               "    \"vi\" or \"emacs\" you will get those editors.\n"
               "\n"
               "    NOTE: This will not work reliably for large files\n"
               "    (> 2 MB) or binary files containing \\0 bytes.\n"));
               "\n"
               "    Normally it uses $EDITOR, but if you use the aliases\n"
               "    \"vi\" or \"emacs\" you will get those editors.\n"
               "\n"
               "    NOTE: This will not work reliably for large files\n"
               "    (> 2 MB) or binary files containing \\0 bytes.\n"));
-  else if (strcasecmp (cmd, "lcd") == 0)
+    return 0;
+  }
+  else if (STRCASEEQ (cmd, "lcd")) {
     printf (_("lcd - local change directory\n"
               "    lcd <directory>\n"
               "\n"
               "    Change guestfish's current directory. This command is\n"
               "    useful if you want to download files to a particular\n"
               "    place.\n"));
     printf (_("lcd - local change directory\n"
               "    lcd <directory>\n"
               "\n"
               "    Change guestfish's current directory. This command is\n"
               "    useful if you want to download files to a particular\n"
               "    place.\n"));
-  else if (strcasecmp (cmd, "glob") == 0)
+    return 0;
+  }
+  else if (STRCASEEQ (cmd, "glob")) {
     printf (_("glob - expand wildcards in command\n"
               "    glob <command> [<args> ...]\n"
               "\n"
               "    Glob runs <command> with wildcards expanded in any\n"
               "    command args.  Note that the command is run repeatedly\n"
               "    once for each expanded argument.\n"));
     printf (_("glob - expand wildcards in command\n"
               "    glob <command> [<args> ...]\n"
               "\n"
               "    Glob runs <command> with wildcards expanded in any\n"
               "    command args.  Note that the command is run repeatedly\n"
               "    once for each expanded argument.\n"));
-  else if (strcasecmp (cmd, "help") == 0)
+    return 0;
+  }
+  else if (STRCASEEQ (cmd, "man") ||
+           STRCASEEQ (cmd, "manual")) {
+    printf (_("man - read the manual\n"
+              "    man\n"
+              "\n"
+              "    Opens the manual page for guestfish.\n"));
+    return 0;
+  }
+  else if (STRCASEEQ (cmd, "help")) {
     printf (_("help - display a list of commands or help on a command\n"
               "     help cmd\n"
               "     help\n"));
     printf (_("help - display a list of commands or help on a command\n"
               "     help cmd\n"
               "     help\n"));
-  else if (strcasecmp (cmd, "more") == 0 ||
-           strcasecmp (cmd, "less") == 0)
+    return 0;
+  }
+  else if (STRCASEEQ (cmd, "more") ||
+           STRCASEEQ (cmd, "less")) {
     printf (_("more - view a file in the pager\n"
               "     more <filename>\n"
               "\n"
     printf (_("more - view a file in the pager\n"
               "     more <filename>\n"
               "\n"
@@ -965,27 +1234,85 @@ display_builtin_command (const char *cmd)
               "\n"
               "    NOTE: This will not work reliably for large files\n"
               "    (> 2 MB) or binary files containing \\0 bytes.\n"));
               "\n"
               "    NOTE: This will not work reliably for large files\n"
               "    (> 2 MB) or binary files containing \\0 bytes.\n"));
-  else if (strcasecmp (cmd, "quit") == 0 ||
-           strcasecmp (cmd, "exit") == 0 ||
-           strcasecmp (cmd, "q") == 0)
+    return 0;
+  }
+  else if (STRCASEEQ (cmd, "quit") ||
+           STRCASEEQ (cmd, "exit") ||
+           STRCASEEQ (cmd, "q")) {
     printf (_("quit - quit guestfish\n"
               "     quit\n"));
     printf (_("quit - quit guestfish\n"
               "     quit\n"));
-  else if (strcasecmp (cmd, "reopen") == 0)
+    return 0;
+  }
+  else if (STRCASEEQ (cmd, "reopen")) {
     printf (_("reopen - close and reopen the libguestfs handle\n"
               "     reopen\n"
               "\n"
               "Close and reopen the libguestfs handle.  It is not necessary to use\n"
               "this normally, because the handle is closed properly when guestfish\n"
               "exits.  However this is occasionally useful for testing.\n"));
     printf (_("reopen - close and reopen the libguestfs handle\n"
               "     reopen\n"
               "\n"
               "Close and reopen the libguestfs handle.  It is not necessary to use\n"
               "this normally, because the handle is closed properly when guestfish\n"
               "exits.  However this is occasionally useful for testing.\n"));
-  else if (strcasecmp (cmd, "time") == 0)
+    return 0;
+  }
+  else if (STRCASEEQ (cmd, "sparse")) {
+    printf (_("sparse - allocate a sparse image file\n"
+              "     sparse <filename> <size>\n"
+              "\n"
+              "    This creates an empty sparse file of the given size,\n"
+              "    and then adds so it can be further examined.\n"
+              "\n"
+              "    In all respects it works the same as the 'alloc'\n"
+              "    command, except that the image file is allocated\n"
+              "    sparsely, which means that disk blocks are not assigned\n"
+              "    to the file until they are needed.  Sparse disk files\n"
+              "    only use space when written to, but they are slower\n"
+              "    and there is a danger you could run out of real disk\n"
+              "    space during a write operation.\n"
+              "\n"
+              "    For more advanced image creation, see qemu-img utility.\n"
+              "\n"
+              "    Size can be specified using standard suffixes, eg. '1M'.\n"
+              ));
+    return 0;
+  }
+  else if (STRCASEEQ (cmd, "supported")) {
+    printf (_("supported - list supported groups of commands\n"
+              "     supported\n"
+              "\n"
+              "    This command returns a list of the optional groups\n"
+              "    known to the daemon, and indicates which ones are\n"
+              "    supported by this build of the libguestfs appliance.\n"
+              "\n"
+              "    See also guestfs(3) section AVAILABILITY.\n"
+              ));
+    return 0;
+  }
+  else if (STRCASEEQ (cmd, "time")) {
     printf (_("time - measure time taken to run command\n"
               "    time <command> [<args> ...]\n"
               "\n"
               "    This runs <command> as usual, and prints the elapsed\n"
               "    time afterwards.\n"));
     printf (_("time - measure time taken to run command\n"
               "    time <command> [<args> ...]\n"
               "\n"
               "    This runs <command> as usual, and prints the elapsed\n"
               "    time afterwards.\n"));
-  else
+    return 0;
+  }
+  else {
     fprintf (stderr, _("%s: command not known, use -h to list all commands\n"),
              cmd);
     fprintf (stderr, _("%s: command not known, use -h to list all commands\n"),
              cmd);
+    return -1;
+  }
+}
+
+/* This is printed when the user types in an unknown command for the
+ * first command issued.  A common case is the user doing:
+ *   guestfish disk.img
+ * expecting guestfish to open 'disk.img' (in fact, this tried to
+ * run a command 'disk.img').
+ */
+void
+extended_help_message (void)
+{
+  fprintf (stderr,
+           _("Did you mean to open a disk image?  guestfish -a disk.img\n"
+             "For a list of commands:             guestfish -h\n"
+             "For complete documentation:         man guestfish\n"));
 }
 
 void
 }
 
 void
@@ -1030,37 +1357,155 @@ int
 is_true (const char *str)
 {
   return
 is_true (const char *str)
 {
   return
-    strcasecmp (str, "0") != 0 &&
-    strcasecmp (str, "f") != 0 &&
-    strcasecmp (str, "false") != 0 &&
-    strcasecmp (str, "n") != 0 &&
-    strcasecmp (str, "no") != 0;
+    STRCASENEQ (str, "0") &&
+    STRCASENEQ (str, "f") &&
+    STRCASENEQ (str, "false") &&
+    STRCASENEQ (str, "n") &&
+    STRCASENEQ (str, "no");
+}
+
+/* Free strings from a non-NULL terminated char** */
+static void
+free_n_strings (char **str, size_t len)
+{
+  size_t i;
+
+  for (i = 0; i < len; i++) {
+    free (str[i]);
+  }
+  free (str);
 }
 
 }
 
-/* XXX We could improve list parsing. */
 char **
 parse_string_list (const char *str)
 {
 char **
 parse_string_list (const char *str)
 {
-  char **argv;
-  const char *p, *pend;
-  int argc, i;
+  char **argv = NULL;
+  size_t argv_len = 0;
+
+  /* Current position pointer */
+  const char *p = str;
+
+  /* Token might be simple:
+   *  Token
+   * or be quoted:
+   *  'This is a single token'
+   * or contain embedded single-quoted sections:
+   *  This' is a sing'l'e to'ken
+   *
+   * The latter may seem over-complicated, but it's what a normal shell does.
+   * Not doing it risks surprising somebody.
+   *
+   * This outer loop is over complete tokens.
+   */
+  while (*p) {
+    char *tok = NULL;
+    size_t tok_len = 0;
 
 
-  argc = 1;
-  for (i = 0; str[i]; ++i)
-    if (str[i] == ' ') argc++;
+    /* Skip leading whitespace */
+    p += strspn (p, " \t");
 
 
-  argv = malloc (sizeof (char *) * (argc+1));
-  if (argv == NULL) { perror ("malloc"); exit (1); }
+    char in_quote = 0;
 
 
-  p = str;
-  i = 0;
-  while (*p) {
-    pend = strchrnul (p, ' ');
-    argv[i] = strndup (p, pend-p);
-    i++;
-    p = *pend == ' ' ? pend+1 : pend;
+    /* This loop is over token 'fragments'. A token can be in multiple bits if
+     * it contains single quotes. We also treat both sides of an escaped quote
+     * as separate fragments because we can't just copy it: we have to remove
+     * the \.
+     */
+    while (*p && (!c_isblank (*p) || in_quote)) {
+      const char *end = p;
+
+      /* Check if the fragment starts with a quote */
+      if ('\'' == *p) {
+        /* Toggle in_quote */
+        in_quote = !in_quote;
+
+        /* Skip the quote */
+        p++; end++;
+      }
+
+      /* If we're in a quote, look for an end quote */
+      if (in_quote) {
+        end += strcspn (end, "'");
+      }
+
+      /* Otherwise, look for whitespace or a quote */
+      else {
+        end += strcspn (end, " \t'");
+      }
+
+      /* Grow the token to accommodate the fragment */
+      size_t tok_end = tok_len;
+      tok_len += end - p;
+      char *tok_new = realloc (tok, tok_len + 1);
+      if (NULL == tok_new) {
+        perror ("realloc");
+        free_n_strings (argv, argv_len);
+        free (tok);
+        exit (EXIT_FAILURE);
+      }
+      tok = tok_new;
+
+      /* Check if we stopped on an escaped quote */
+      if ('\'' == *end && end != p && *(end-1) == '\\') {
+        /* Add everything before \' to the token */
+        memcpy (&tok[tok_end], p, end - p - 1);
+
+        /* Add the quote */
+        tok[tok_len-1] = '\'';
+
+        /* Already processed the quote */
+        p = end + 1;
+      }
+
+      else {
+        /* Add the whole fragment */
+        memcpy (&tok[tok_end], p, end - p);
+
+        p = end;
+      }
+    }
+
+    /* We've reached the end of a token. We shouldn't still be in quotes. */
+    if (in_quote) {
+      fprintf (stderr, _("Runaway quote in string \"%s\"\n"), str);
+
+      free_n_strings (argv, argv_len);
+
+      return NULL;
+    }
+
+    /* Add this token if there is one. There might not be if there was
+     * whitespace at the end of the input string */
+    if (tok) {
+      /* Add the NULL terminator */
+      tok[tok_len] = '\0';
+
+      /* Add the argument to the argument list */
+      argv_len++;
+      char **argv_new = realloc (argv, sizeof (*argv) * argv_len);
+      if (NULL == argv_new) {
+        perror ("realloc");
+        free_n_strings (argv, argv_len-1);
+        free (tok);
+        exit (EXIT_FAILURE);
+      }
+      argv = argv_new;
+
+      argv[argv_len-1] = tok;
+    }
   }
   }
-  argv[i] = NULL;
+
+  /* NULL terminate the argument list */
+  argv_len++;
+  char **argv_new = realloc (argv, sizeof (*argv) * argv_len);
+  if (NULL == argv_new) {
+    perror ("realloc");
+    free_n_strings (argv, argv_len-1);
+    exit (EXIT_FAILURE);
+  }
+  argv = argv_new;
+
+  argv[argv_len-1] = NULL;
 
   return argv;
 }
 
   return argv;
 }
@@ -1085,6 +1530,13 @@ initialize_readline (void)
 
   rl_readline_name = "guestfish";
   rl_attempted_completion_function = do_completion;
 
   rl_readline_name = "guestfish";
   rl_attempted_completion_function = do_completion;
+
+  /* Note that .inputrc (or /etc/inputrc) is not read until the first
+   * call the readline(), which happens later.  Therefore, these
+   * provide default values which can be overridden by the user if
+   * they wish.
+   */
+  (void) rl_variable_bind ("completion-ignore-case", "on");
 #endif
 }
 
 #endif
 }
 
@@ -1102,24 +1554,29 @@ cleanup_readline (void)
     }
     close (fd);
 
     }
     close (fd);
 
+#ifdef HAVE_APPEND_HISTORY
     (void) append_history (nr_history_lines, histfile);
     (void) append_history (nr_history_lines, histfile);
+#else
+    (void) write_history (histfile);
+#endif
   }
 #endif
 }
 
   }
 #endif
 }
 
+#ifdef HAVE_LIBREADLINE
 static void
 add_history_line (const char *line)
 {
 static void
 add_history_line (const char *line)
 {
-#ifdef HAVE_LIBREADLINE
   add_history (line);
   nr_history_lines++;
   add_history (line);
   nr_history_lines++;
-#endif
 }
 }
+#endif
 
 int
 
 int
-xwrite (int fd, const void *buf, size_t len)
+xwrite (int fd, const void *v_buf, size_t len)
 {
   int r;
 {
   int r;
+  const char *buf = v_buf;
 
   while (len > 0) {
     r = write (fd, buf, len);
 
   while (len > 0) {
     r = write (fd, buf, len);
@@ -1133,3 +1590,267 @@ xwrite (int fd, const void *buf, size_t len)
 
   return 0;
 }
 
   return 0;
 }
+
+/* Resolve the special "win:..." form for Windows-specific paths.
+ * This always returns a newly allocated string which is freed by the
+ * caller function in "cmds.c".
+ */
+char *
+resolve_win_path (const char *path)
+{
+  char *ret;
+  size_t i;
+
+  if (STRCASENEQLEN (path, "win:", 4)) {
+    ret = strdup (path);
+    if (ret == NULL)
+      perror ("strdup");
+    return ret;
+  }
+
+  path += 4;
+
+  /* Drop drive letter, if it's "C:". */
+  if (STRCASEEQLEN (path, "c:", 2))
+    path += 2;
+
+  if (!*path) {
+    ret = strdup ("/");
+    if (ret == NULL)
+      perror ("strdup");
+    return ret;
+  }
+
+  ret = strdup (path);
+  if (ret == NULL) {
+    perror ("strdup");
+    return NULL;
+  }
+
+  /* Blindly convert any backslashes into forward slashes.  Is this good? */
+  for (i = 0; i < strlen (ret); ++i)
+    if (ret[i] == '\\')
+      ret[i] = '/';
+
+  char *t = guestfs_case_sensitive_path (g, ret);
+  free (ret);
+  ret = t;
+
+  return ret;
+}
+
+/* Resolve the special FileIn paths ("-" or "-<<END" or filename).
+ * The caller (cmds.c) will call free_file_in after the command has
+ * run which should clean up resources.
+ */
+static char *file_in_heredoc (const char *endmarker);
+static char *file_in_tmpfile = NULL;
+
+char *
+file_in (const char *arg)
+{
+  char *ret;
+
+  if (STREQ (arg, "-")) {
+    ret = strdup ("/dev/stdin");
+    if (!ret) {
+      perror ("strdup");
+      return NULL;
+    }
+  }
+  else if (STRPREFIX (arg, "-<<")) {
+    const char *endmarker = &arg[3];
+    if (*endmarker == '\0') {
+      fprintf (stderr, "%s: missing end marker in -<< expression\n",
+               program_name);
+      return NULL;
+    }
+    ret = file_in_heredoc (endmarker);
+    if (ret == NULL)
+      return NULL;
+  }
+  else {
+    ret = strdup (arg);
+    if (!ret) {
+      perror ("strdup");
+      return NULL;
+    }
+  }
+
+  return ret;
+}
+
+static char *
+file_in_heredoc (const char *endmarker)
+{
+  static const char template[] = "/tmp/heredocXXXXXX";
+  file_in_tmpfile = strdup (template);
+  if (file_in_tmpfile == NULL) {
+    perror ("strdup");
+    return NULL;
+  }
+
+  int fd = mkstemp (file_in_tmpfile);
+  if (fd == -1) {
+    perror ("mkstemp");
+    goto error1;
+  }
+
+  size_t markerlen = strlen (endmarker);
+
+  char buffer[BUFSIZ];
+  int write_error = 0;
+  while (fgets (buffer, sizeof buffer, stdin) != NULL) {
+    /* Look for "END"<EOF> or "END\n" in input. */
+    size_t blen = strlen (buffer);
+    if (STREQLEN (buffer, endmarker, markerlen) &&
+        (blen == markerlen ||
+         (blen == markerlen+1 && buffer[markerlen] == '\n')))
+      goto found_end;
+
+    if (xwrite (fd, buffer, blen) == -1) {
+      if (!write_error) perror ("write");
+      write_error = 1;
+      /* continue reading up to the end marker */
+    }
+  }
+
+  /* Reached EOF of stdin without finding the end marker, which
+   * is likely to be an error.
+   */
+  fprintf (stderr, "%s: end of input reached without finding '%s'\n",
+           program_name, endmarker);
+  goto error2;
+
+ found_end:
+  if (write_error) {
+    close (fd);
+    goto error2;
+  }
+
+  if (close (fd) == -1) {
+    perror ("close");
+    goto error2;
+  }
+
+  return file_in_tmpfile;
+
+ error2:
+  unlink (file_in_tmpfile);
+
+ error1:
+  free (file_in_tmpfile);
+  file_in_tmpfile = NULL;
+  return NULL;
+}
+
+void
+free_file_in (char *s)
+{
+  if (file_in_tmpfile) {
+    if (unlink (file_in_tmpfile) == -1)
+      perror (file_in_tmpfile);
+    file_in_tmpfile = NULL;
+  }
+
+  /* Free the device or file name which was strdup'd in file_in().
+   * Note it's not immediately clear, but for -<< heredocs,
+   * s == file_in_tmpfile, so this frees up that buffer.
+   */
+  free (s);
+}
+
+/* Resolve the special FileOut paths ("-" or filename).
+ * The caller (cmds.c) will call free (str) after the command has run.
+ */
+char *
+file_out (const char *arg)
+{
+  char *ret;
+
+  if (STREQ (arg, "-"))
+    ret = strdup ("/dev/stdout");
+  else
+    ret = strdup (arg);
+
+  if (!ret) {
+    perror ("strdup");
+    return NULL;
+  }
+  return ret;
+}
+
+/* Read a passphrase ('Key') from /dev/tty with echo off.
+ * The caller (cmds.c) will call free on the string afterwards.
+ * Based on the code in cryptsetup file lib/utils.c.
+ */
+char *
+read_key (const char *param)
+{
+  FILE *infp, *outfp;
+  struct termios orig, temp;
+  char *ret = NULL;
+
+  /* Read and write to /dev/tty if available. */
+  if (keys_from_stdin ||
+      (infp = outfp = fopen ("/dev/tty", "w+")) == NULL) {
+    infp = stdin;
+    outfp = stdout;
+  }
+
+  /* Print the prompt and set no echo. */
+  int tty = isatty (fileno (infp));
+  int tcset = 0;
+  if (tty) {
+    fprintf (outfp, _("Enter key or passphrase (\"%s\"): "), param);
+
+    if (tcgetattr (fileno (infp), &orig) == -1) {
+      perror ("tcgetattr");
+      goto error;
+    }
+    memcpy (&temp, &orig, sizeof temp);
+    temp.c_lflag &= ~ECHO;
+
+    tcsetattr (fileno (infp), TCSAFLUSH, &temp);
+    tcset = 1;
+  }
+
+  size_t n = 0;
+  ssize_t len;
+  len = getline (&ret, &n, infp);
+  if (len == -1) {
+    perror ("getline");
+    ret = NULL;
+    goto error;
+  }
+
+  /* Remove the terminating \n if there is one. */
+  if (len > 0 && ret[len-1] == '\n')
+    ret[len-1] = '\0';
+
+ error:
+  /* Restore echo, close file descriptor. */
+  if (tty && tcset) {
+    printf ("\n");
+    tcsetattr (fileno (infp), TCSAFLUSH, &orig);
+  }
+
+  if (infp != stdin)
+    fclose (infp); /* outfp == infp, so this is closed also */
+
+  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);
+  }
+}