[Libguestfs] [PATCH] Unify guestfish and guestmount options processing (RHBZ#642932).

Richard W.M. Jones
Wed, 27 Oct 2010 11:03:43 -0000

--
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
virt-p2v converts physical machines to virtual machines.  Boot with a
live CD or over the network (PXE) and turn machines into Xen guests.
http://et.redhat.com/~rjones/virt-p2v
-------------- next part --------------
>From 9f30ce6bf70242bf2f543fd74a07c6b6973d1a13 Mon Sep 17 00:00:00 2001
From: Richard W.M. Jones <rjones at redhat.com>
Date: Tue, 26 Oct 2010 15:28:28 +0100
Subject: [PATCH] Unify guestfish and guestmount options processing
(RHBZ#642932).

In guestfish, factor out the processing of the options -a, -c,
-d, -i, -m, -n, -r, -v, -V, -x into a separate set of files:
options.c, options.h, inspect.c, virt.c.

Change guestmount so that it uses these same files (from the
../fish directory) to process the same options.

This unifies the handling of these options between the two programs.
It also adds the useful inspection feature to guestmount, so you
can now do:

  guestmount -d Guest -i --ro mnt/
---
 fish/Makefile.am    |   15 ++-
 fish/fish.c         |  280 ++++++++-------------------------------------------
 fish/fish.h         |    9 +--
 fish/inspect.c      |   32 +++++-
 fish/options.c      |  160 +++++++++++++++++++++++++++++
 fish/options.h      |  186 ++++++++++++++++++++++++++++++++++
 fish/prep.c         |    3 +-
 fish/virt.c         |   39 ++++---
 fuse/Makefile.am    |   13 ++-
 fuse/guestmount.c   |  164 +++++++++---------------------
 fuse/guestmount.pod |   63 ++++++++----
 po/POTFILES.in      |    1 +
 12 files changed, 558 insertions(+), 407 deletions(-)
 create mode 100644 fish/options.c
 create mode 100644 fish/options.h

diff --git a/fish/Makefile.am b/fish/Makefile.am
index 91094df..e3221ca 100644
--- a/fish/Makefile.am
+++ b/fish/Makefile.am
@@ -38,8 +38,19 @@ EXTRA_DIST = \
 	guestfish.pod \
 	guestfish-bash-completion.sh

+# These source files (all related to option parsing) are shared
+# between guestfish and guestmount.  Keep a convenient list here just
+# so we know which ones are shared.  These files must not include
+# other guestfish files.
+SHARED_SOURCE_FILES = \
+	inspect.c \
+	options.h \
+	options.c \
+	virt.c
+
 guestfish_SOURCES = \
 	$(generator_built) \
+	$(SHARED_SOURCE_FILES) \
 	alloc.c \
 	copy.c \
 	destpaths.c \
@@ -49,7 +60,6 @@ guestfish_SOURCES = \
 	fish.h \
 	glob.c \
 	hexedit.c \
-	inspect.c \
 	lcd.c \
 	man.c \
 	more.c \
@@ -65,8 +75,7 @@ guestfish_SOURCES = \
 	rmsd.h \
 	supported.c \
 	tilde.c \
-	time.c \
-	virt.c
+	time.c

 # This convenience library is solely to avoid compiler warnings
 # in its generated sources.
diff --git a/fish/fish.c b/fish/fish.c
index 8da4ffd..559d609 100644
--- a/fish/fish.c
+++ b/fish/fish.c
@@ -40,42 +40,14 @@
 #include <guestfs.h>

 #include "fish.h"
+#include "options.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 */
-      const char *format;   /* format (NULL == autodetect) */
-    } 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;
-  char *device;
-  char *mountpoint;
-};
-
 static void set_up_terminal (void);
-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 free_drives (struct drv *drv);
-static void free_mps (struct mp *mp);
 static int launch (void);
 static void interactive (void);
 static void shell_script (void);
@@ -151,8 +123,8 @@ usage (int status)
              "  -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"
+             "  -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, program_name, program_name,
@@ -299,61 +271,15 @@ main (int argc, char *argv[])
       break;

     case 'a':
-      if (access (optarg, R_OK) != 0) {
-        perror (optarg);
-        exit (EXIT_FAILURE);
-      }
-      drv = malloc (sizeof (struct drv));
-      if (!drv) {
-        perror ("malloc");
-        exit (EXIT_FAILURE);
-      }
-      drv->type = drv_a;
-      drv->a.filename = optarg;
-      drv->a.format = format;
-      drv->next = drvs;
-      drvs = drv;
+      OPTION_a;
       break;

     case 'c':
-      libvirt_uri = optarg;
+      OPTION_c;
       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") ||
-          STRCASEEQ (optarg, "help") ||
-          STRCASEEQ (optarg, "h") ||
-          STRCASEEQ (optarg, "?")) {
-        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;
+      OPTION_d;
       break;

     case 'D':
@@ -383,48 +309,57 @@ main (int argc, char *argv[])
     }

     case 'i':
-      inspector = 1;
+      OPTION_i;
       break;

     case 'm':
-      mp = malloc (sizeof (struct mp));
-      if (!mp) {
-        perror ("malloc");
-        exit (EXIT_FAILURE);
-      }
-      p = strchr (optarg, ':');
-      if (p) {
-        *p = '\0';
-        mp->mountpoint = p+1;
-      } else
-        mp->mountpoint = bad_cast ("/");
-      mp->device = optarg;
-      mp->next = mps;
-      mps = mp;
+      OPTION_m;
       break;

     case 'n':
-      guestfs_set_autosync (g, 0);
+      OPTION_n;
+      break;
+
+    case 'N':
+      if (STRCASEEQ (optarg, "list") ||
+          STRCASEEQ (optarg, "help") ||
+          STRCASEEQ (optarg, "h") ||
+          STRCASEEQ (optarg, "?")) {
+        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.data_free = free_prep_data;
+      drv->N.device = NULL;     /* filled in by add_drives */
+      drv->next = drvs;
+      drvs = drv;
       break;

     case 'r':
-      read_only = 1;
+      OPTION_r;
       break;

     case 'v':
-      verbose++;
-      guestfs_set_verbose (g, verbose);
+      OPTION_v;
       break;

-    case 'V': {
-      struct guestfs_version *v = guestfs_version (g);
-      printf ("%s
%"PRIi64".%"PRIi64".%"PRIi64"%s\n",
program_name,
-              v->major, v->minor, v->release, v->extra);
-      exit (EXIT_SUCCESS);
-    }
+    case 'V':
+      OPTION_V;
+      break;

     case 'x':
-      guestfs_set_trace (g, 1);
+      OPTION_x;
       break;

     case HELP_OPTION:
@@ -604,107 +539,6 @@ pod2text (const char *name, const char *shortdesc, const
char *str)
   pclose (fp);
 }

-/* List is built in reverse order, so mount them in reverse order. */
-static void
-mount_mps (struct mp *mp)
-{
-  int r;
-
-  if (mp) {
-    mount_mps (mp->next);
-
-    /* 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) {
-      /* Display possible mountpoints before exiting. */
-      char **fses = guestfs_list_filesystems (g);
-      if (fses == NULL || fses[0] == NULL)
-        goto out;
-      fprintf (stderr,
-               _("guestfish: '%s' could not be mounted.  Did you
mean one of these?\n"),
-               mp->device);
-      size_t i;
-      for (i = 0; fses[i] != NULL; i += 2)
-        fprintf (stderr, "\t%s (%s)\n", fses[i], fses[i+1]);
-
-    out:
-      exit (EXIT_FAILURE);
-    }
-  }
-}
-
-static char
-add_drives (struct drv *drv, char next_drive)
-{
-  int r;
-  struct guestfs_add_drive_opts_argv ad_optargs;
-
-  if (next_drive > 'z') {
-    fprintf (stderr,
-             _("guestfish: too many drives added on the command
line\n"));
-    exit (EXIT_FAILURE);
-  }
-
-  if (drv) {
-    next_drive = add_drives (drv->next, next_drive);
-
-    switch (drv->type) {
-    case drv_a:
-      ad_optargs.bitmask = 0;
-      if (read_only) {
-        ad_optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK;
-        ad_optargs.readonly = 1;
-      }
-      if (drv->a.format) {
-        ad_optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_FORMAT_BITMASK;
-        ad_optargs.format = drv->a.format;
-      }
-      r = guestfs_add_drive_opts_argv (g, drv->a.filename, &ad_optargs);
-      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:
-      /* guestfs_add_drive (ie. autodetecting) should be safe here
-       * since we have just created the prepared disk.  At the moment
-       * it will always be "raw" but in a theoretical future we might
-       * create other formats.
-       */
-      /* -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)
 {
@@ -715,38 +549,6 @@ prepare_drives (struct drv *drv)
   }
 }

-static void
-free_drives (struct drv *drv)
-{
-  if (!drv) return;
-  free_drives (drv->next);
-
-  switch (drv->type) {
-  case drv_a: /* a.filename and a.format are optargs, don't free them */
break;
-  case drv_d: /* d.filename is optarg, don't free it */ break;
-  case drv_N:
-    free (drv->N.filename);
-    free (drv->N.device);
-    free_prep_data (drv->N.data);
-    break;
-  default: ;                    /* keep GCC happy */
-  }
-  free (drv);
-}
-
-static void
-free_mps (struct mp *mp)
-{
-  if (!mp) return;
-  free_mps (mp->next);
-
-  /* The drive and mountpoint fields are not allocated
-   * from the heap, so we should not free them here.
-   */
-
-  free (mp);
-}
-
 static int
 launch (void)
 {
diff --git a/fish/fish.h b/fish/fish.h
index b94277d..ac44bbf 100644
--- a/fish/fish.h
+++ b/fish/fish.h
@@ -112,10 +112,6 @@ extern int run_edit (const char *cmd, size_t argc, char
*argv[]);
 /* in hexedit.c */
 extern int run_hexedit (const char *cmd, size_t argc, char *argv[]);

-/* in inspect.c */
-extern void inspect_mount (void);
-extern void print_inspect_prompt (void);
-
 /* in lcd.c */
 extern int run_lcd (const char *cmd, size_t argc, char *argv[]);

@@ -141,7 +137,7 @@ extern prep_data *create_prepared_file (const char
*type_string,
 extern void prepare_drive (const char *filename, prep_data *data,
                            const char *device);
 extern void prep_error (prep_data *data, const char *filename, const char *fs,
...) __attribute__((noreturn, format (printf,3,4)));
-extern void free_prep_data (prep_data *data);
+extern void free_prep_data (void *data);

 /* in prep_lv.c */
 extern int vg_lv_parse (const char *device, char **vg, char **lv);
@@ -167,9 +163,6 @@ extern int run_time (const char *cmd, size_t argc, char
*argv[]);
 /* in tilde.c */
 extern char *try_tilde_expansion (char *path);

-/* in virt.c */
-extern int add_libvirt_drives (const char *guest);
-
 /* This should just list all the built-in commands so they can
  * be added to the generated auto-completion code.
  */
diff --git a/fish/inspect.c b/fish/inspect.c
index d17496f..8e56553 100644
--- a/fish/inspect.c
+++ b/fish/inspect.c
@@ -1,4 +1,4 @@
-/* guestfish - the filesystem interactive shell
+/* libguestfs - guestfish and guestmount shared option parsing
  * Copyright (C) 2010 Red Hat Inc.
  *
  * This program is free software; you can redistribute it and/or modify
@@ -22,13 +22,35 @@
 #include <stdlib.h>
 #include <string.h>

-#include "fish.h"
+#include "guestfs.h"
+
+#include "options.h"

 /* Global that saves the root device between inspect_mount and
  * print_inspect_prompt.
  */
 static char *root = NULL;

+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;
+}
+
 static int
 compare_keys_len (const void *p1, const void *p2)
 {
@@ -54,12 +76,14 @@ inspect_mount (void)
     exit (EXIT_FAILURE);

   if (roots[0] == NULL) {
-    fprintf (stderr, _("guestfish: no operating system was found on this
disk\n"));
+    fprintf (stderr, _("%s: no operating system was found on this
disk\n"),
+             program_name);
     exit (EXIT_FAILURE);
   }

   if (roots[1] != NULL) {
-    fprintf (stderr, _("guestfish: multi-boot operating systems are not
supported by the -i option\n"));
+    fprintf (stderr, _("%s: multi-boot operating systems are not supported
by the -i option\n"),
+             program_name);
     exit (EXIT_FAILURE);
   }

diff --git a/fish/options.c b/fish/options.c
new file mode 100644
index 0000000..55bcf68
--- /dev/null
+++ b/fish/options.c
@@ -0,0 +1,160 @@
+/* libguestfs - guestfish and guestmount shared option parsing
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "guestfs.h"
+
+#include "options.h"
+
+char
+add_drives (struct drv *drv, char next_drive)
+{
+  int r;
+  struct guestfs_add_drive_opts_argv ad_optargs;
+
+  if (next_drive > 'z') {
+    fprintf (stderr,
+             _("%s: too many drives added on the command line\n"),
+             program_name);
+    exit (EXIT_FAILURE);
+  }
+
+  if (drv) {
+    next_drive = add_drives (drv->next, next_drive);
+
+    switch (drv->type) {
+    case drv_a:
+      ad_optargs.bitmask = 0;
+      if (read_only) {
+        ad_optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK;
+        ad_optargs.readonly = 1;
+      }
+      if (drv->a.format) {
+        ad_optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_FORMAT_BITMASK;
+        ad_optargs.format = drv->a.format;
+      }
+      r = guestfs_add_drive_opts_argv (g, drv->a.filename, &ad_optargs);
+      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:
+      /* guestfs_add_drive (ie. autodetecting) should be safe here
+       * since we have just created the prepared disk.  At the moment
+       * it will always be "raw" but in a theoretical future we might
+       * create other formats.
+       */
+      /* -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;
+}
+
+/* List is built in reverse order, so mount them in reverse order. */
+void
+mount_mps (struct mp *mp)
+{
+  int r;
+
+  if (mp) {
+    mount_mps (mp->next);
+
+    /* 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) {
+      /* Display possible mountpoints before exiting. */
+      char **fses = guestfs_list_filesystems (g);
+      if (fses == NULL || fses[0] == NULL)
+        goto out;
+      fprintf (stderr,
+               _("%s: '%s' could not be mounted.  Did you mean one
of these?\n"),
+               program_name, mp->device);
+      size_t i;
+      for (i = 0; fses[i] != NULL; i += 2)
+        fprintf (stderr, "\t%s (%s)\n", fses[i], fses[i+1]);
+
+    out:
+      exit (EXIT_FAILURE);
+    }
+  }
+}
+
+void
+free_drives (struct drv *drv)
+{
+  if (!drv) return;
+  free_drives (drv->next);
+
+  switch (drv->type) {
+  case drv_a: /* a.filename and a.format are optargs, don't free them */
break;
+  case drv_d: /* d.filename is optarg, don't free it */ break;
+  case drv_N:
+    free (drv->N.filename);
+    free (drv->N.device);
+    drv->N.data_free (drv->N.data);
+    break;
+  default: ;                    /* keep GCC happy */
+  }
+  free (drv);
+}
+
+void
+free_mps (struct mp *mp)
+{
+  if (!mp) return;
+  free_mps (mp->next);
+
+  /* The drive and mountpoint fields are not allocated
+   * from the heap, so we should not free them here.
+   */
+
+  free (mp);
+}
diff --git a/fish/options.h b/fish/options.h
new file mode 100644
index 0000000..b0bbdeb
--- /dev/null
+++ b/fish/options.h
@@ -0,0 +1,186 @@
+/* libguestfs - guestfish and guestmount shared option parsing
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef OPTIONS_H
+#define OPTIONS_H
+
+#ifdef HAVE_GETTEXT
+#include "gettext.h"
+#ifndef _
+#define _(str) dgettext(PACKAGE, (str))
+#endif
+#ifndef N_
+#define N_(str) dgettext(PACKAGE, (str))
+#endif
+#else
+#ifndef _
+#define _(str) str
+#endif
+#ifndef _
+#define N_(str) str
+#endif
+#endif
+
+#ifndef STREQ
+#define STREQ(a,b) (strcmp((a),(b)) == 0)
+#endif
+#ifndef STRCASEEQ
+#define STRCASEEQ(a,b) (strcasecmp((a),(b)) == 0)
+#endif
+#ifndef STRNEQ
+#define STRNEQ(a,b) (strcmp((a),(b)) != 0)
+#endif
+#ifndef STRCASENEQ
+#define STRCASENEQ(a,b) (strcasecmp((a),(b)) != 0)
+#endif
+#ifndef STREQLEN
+#define STREQLEN(a,b,n) (strncmp((a),(b),(n)) == 0)
+#endif
+#ifndef STRCASEEQLEN
+#define STRCASEEQLEN(a,b,n) (strncasecmp((a),(b),(n)) == 0)
+#endif
+#ifndef STRNEQLEN
+#define STRNEQLEN(a,b,n) (strncmp((a),(b),(n)) != 0)
+#endif
+#ifndef STRCASENEQLEN
+#define STRCASENEQLEN(a,b,n) (strncasecmp((a),(b),(n)) != 0)
+#endif
+#ifndef STRPREFIX
+#define STRPREFIX(a,b) (strncmp((a),(b),strlen((b))) == 0)
+#endif
+
+/* Provided by guestfish or guestmount. */
+extern guestfs_h *g;
+extern int read_only;
+extern int verbose;
+extern int inspector;
+extern const char *libvirt_uri;
+extern const char *program_name;
+
+/* 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 */
+      const char *format;   /* format (NULL == autodetect) */
+    } a;
+    struct {
+      char *guest;          /* guest name */
+    } d;
+    struct {
+      char *filename;       /* disk filename (testX.img) */
+      void *data;           /* prepared type */
+      void (*data_free)(void*); /* function to free 'data' */
+      char *device;         /* device inside the appliance */
+    } N;
+  };
+};
+
+struct mp {
+  struct mp *next;
+  char *device;
+  char *mountpoint;
+};
+
+/* in inspect.c */
+extern void inspect_mount (void);
+extern void print_inspect_prompt (void);
+
+/* in options.c */
+extern char add_drives (struct drv *drv, char next_drive);
+extern void mount_mps (struct mp *mp);
+extern void free_drives (struct drv *drv);
+extern void free_mps (struct mp *mp);
+
+/* in virt.c */
+extern int add_libvirt_drives (const char *guest);
+
+#define OPTION_a                                \
+  if (access (optarg, R_OK) != 0) {             \
+    perror (optarg);                            \
+    exit (EXIT_FAILURE);                        \
+  }                                             \
+  drv = malloc (sizeof (struct drv));           \
+  if (!drv) {                                   \
+    perror ("malloc");                          \
+    exit (EXIT_FAILURE);                        \
+  }                                             \
+  drv->type = drv_a;                            \
+  drv->a.filename = optarg;                     \
+  drv->a.format = format;                       \
+  drv->next = drvs;                             \
+  drvs = drv
+
+#define OPTION_c                                \
+  libvirt_uri = optarg
+
+#define OPTION_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
+
+#define OPTION_i                                \
+  inspector = 1
+
+#define OPTION_m                                \
+  mp = malloc (sizeof (struct mp));             \
+  if (!mp) {                                    \
+    perror ("malloc");                          \
+    exit (EXIT_FAILURE);                        \
+  }                                             \
+  p = strchr (optarg, ':');                     \
+  if (p) {                                      \
+    *p = '\0';                                  \
+    mp->mountpoint = p+1;                       \
+  } else                                        \
+    mp->mountpoint = bad_cast ("/");            \
+  mp->device = optarg;                          \
+  mp->next = mps;                               \
+  mps = mp
+
+#define OPTION_n                                \
+  guestfs_set_autosync (g, 0)
+
+#define OPTION_r                                \
+  read_only = 1
+
+#define OPTION_v                                \
+  verbose++;                                    \
+  guestfs_set_verbose (g, verbose)
+
+#define OPTION_V                                                        \
+  {                                                                     \
+    struct guestfs_version *v = guestfs_version (g);                    \
+    printf ("%s
%"PRIi64".%"PRIi64".%"PRIi64"%s\n",
\
+            program_name,                                               \
+            v->major, v->minor, v->release, v->extra);
\
+    exit (EXIT_SUCCESS);                                                \
+  }
+
+#define OPTION_x                                \
+  guestfs_set_trace (g, 1)
+
+#endif /* OPTIONS_H */
diff --git a/fish/prep.c b/fish/prep.c
index 9a6b64e..8ac4b31 100644
--- a/fish/prep.c
+++ b/fish/prep.c
@@ -169,8 +169,9 @@ prep_error (prep_data *data, const char *filename, const
char *fs, ...)
 }

 void
-free_prep_data (prep_data *data)
+free_prep_data (void *vp)
 {
+  prep_data *data = vp;
   size_t i;

   for (i = 0; i < data->prep->nr_params; ++i)
diff --git a/fish/virt.c b/fish/virt.c
index d915d22..728f9c2 100644
--- a/fish/virt.c
+++ b/fish/virt.c
@@ -1,4 +1,4 @@
-/* guestfish - the filesystem interactive shell
+/* libguestfs - guestfish and guestmount shared option parsing
  * Copyright (C) 2010 Red Hat Inc.
  *
  * This program is free software; you can redistribute it and/or modify
@@ -30,7 +30,9 @@
 #include <libxml/parser.h>
 #include <libxml/tree.h>

-#include "fish.h"
+#include "guestfs.h"
+
+#include "options.h"

 /* Implements the guts of the '-d' option.
  *
@@ -67,29 +69,29 @@ add_libvirt_drives (const char *guest)
   conn = virConnectOpenReadOnly (libvirt_uri);
   if (!conn) {
     err = virGetLastError ();
-    fprintf (stderr, _("guestfish: could not connect to libvirt (code %d,
domain %d): %s\n"),
-             err->code, err->domain, err->message);
+    fprintf (stderr, _("%s: could not connect to libvirt (code %d, domain
%d): %s\n"),
+             program_name, err->code, err->domain, err->message);
     goto cleanup;
   }

   dom = virDomainLookupByName (conn, guest);
   if (!dom) {
     err = virConnGetLastError (conn);
-    fprintf (stderr, _("guestfish: no libvirt domain called '%s':
%s\n"),
-             guest, err->message);
+    fprintf (stderr, _("%s: no libvirt domain called '%s':
%s\n"),
+             program_name, guest, err->message);
     goto cleanup;
   }
   if (!read_only) {
     virDomainInfo info;
     if (virDomainGetInfo (dom, &info) == -1) {
       err = virConnGetLastError (conn);
-      fprintf (stderr, _("guestfish: error getting domain info about
'%s': %s\n"),
-               guest, err->message);
+      fprintf (stderr, _("%s: error getting domain info about
'%s': %s\n"),
+               program_name, guest, err->message);
       goto cleanup;
     }
     if (info.state != VIR_DOMAIN_SHUTOFF) {
-      fprintf (stderr, _("guestfish: error: '%s' is a live virtual
machine.\nYou must use '--ro' because write access to a running virtual
machine can\ncause disk corruption.\n"),
-               guest);
+      fprintf (stderr, _("%s: error: '%s' is a live virtual
machine.\nYou must use '--ro' because write access to a running virtual
machine can\ncause disk corruption.\n"),
+               program_name, guest);
       goto cleanup;
     }
   }
@@ -99,8 +101,8 @@ add_libvirt_drives (const char *guest)

   if (!xml) {
     err = virConnGetLastError (conn);
-    fprintf (stderr, _("guestfish: error reading libvirt XML information
about '%s': %s\n"),
-             guest, err->message);
+    fprintf (stderr, _("%s: error reading libvirt XML information about
'%s': %s\n"),
+             program_name, guest, err->message);
     goto cleanup;
   }

@@ -109,20 +111,23 @@ add_libvirt_drives (const char *guest)
    */
   doc = xmlParseMemory (xml, strlen (xml));
   if (doc == NULL) {
-    fprintf (stderr, _("guestfish: unable to parse XML information
returned by libvirt\n"));
+    fprintf (stderr, _("%s: unable to parse XML information returned by
libvirt\n"),
+             program_name);
     goto cleanup;
   }

   xpathCtx = xmlXPathNewContext (doc);
   if (xpathCtx == NULL) {
-    fprintf (stderr, _("guestfish: unable to create new XPath
context\n"));
+    fprintf (stderr, _("%s: unable to create new XPath context\n"),
+             program_name);
     goto cleanup;
   }

   /* This gives us a set of all the <disk> nodes. */
   xpathObj = xmlXPathEvalExpression (BAD_CAST "//devices/disk",
xpathCtx);
   if (xpathObj == NULL) {
-    fprintf (stderr, _("guestfish: unable to evaluate XPath
expression\n"));
+    fprintf (stderr, _("%s: unable to evaluate XPath expression\n"),
+             program_name);
     goto cleanup;
   }

@@ -196,8 +201,8 @@ add_libvirt_drives (const char *guest)
   }

   if (nr_added == 0) {
-    fprintf (stderr, _("guestfish: libvirt domain '%s' has no
disks\n"),
-             guest);
+    fprintf (stderr, _("%s: libvirt domain '%s' has no
disks\n"),
+             program_name, guest);
     goto cleanup;
   }

diff --git a/fuse/Makefile.am b/fuse/Makefile.am
index db0e418..a122082 100644
--- a/fuse/Makefile.am
+++ b/fuse/Makefile.am
@@ -23,7 +23,16 @@ if HAVE_FUSE

 bin_PROGRAMS = guestmount

+# These source files (all related to option parsing) are shared
+# between guestfish and guestmount.
+SHARED_SOURCE_FILES = \
+	../fish/inspect.c \
+	../fish/options.h \
+	../fish/options.c \
+	../fish/virt.c
+
 guestmount_SOURCES = \
+	$(SHARED_SOURCE_FILES) \
 	dircache.c \
 	dircache.h \
 	guestmount.c \
@@ -31,14 +40,16 @@ guestmount_SOURCES = \

 guestmount_CFLAGS = \
 	-I$(top_srcdir)/src -I$(top_builddir)/src \
+	-I$(top_srcdir)/fish \
 	-I$(srcdir)/../gnulib/lib -I../gnulib/lib \
 	-DGUESTFS_DEFAULT_PATH='"$(libdir)/guestfs"' \
 	-DLOCALEBASEDIR=\""$(datadir)/locale"\" \
-	$(FUSE_CFLAGS) \
+	$(FUSE_CFLAGS) $(LIBVIRT_CFLAGS) $(LIBXML2_CFLAGS) \
 	$(WARN_CFLAGS) $(WERROR_CFLAGS)

 guestmount_LDADD = \
 	$(FUSE_LIBS) -lulockmgr \
+	$(LIBVIRT_LIBS) $(LIBXML2_LIBS) \
 	$(top_builddir)/src/libguestfs.la \
 	../gnulib/lib/libgnu.la

diff --git a/fuse/guestmount.c b/fuse/guestmount.c
index d17982a..068f7de 100644
--- a/fuse/guestmount.c
+++ b/fuse/guestmount.c
@@ -1,5 +1,5 @@
 /* guestmount - mount guests using libguestfs and FUSE
- * 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
@@ -49,6 +49,7 @@
 #include "progname.h"

 #include "guestmount.h"
+#include "options.h"
 #include "dircache.h"

 /* See <attr/xattr.h> */
@@ -56,9 +57,11 @@
 #define ENOATTR ENODATA
 #endif

-static guestfs_h *g = NULL;
-static int read_only = 0;
+guestfs_h *g = NULL;
+int read_only = 0;
 int verbose = 0;
+int inspector = 0;
+const char *libvirt_uri;
 int dir_cache_timeout = 60;

 /* This is ugly: guestfs errors are strings, FUSE wants -errno.  We
@@ -851,21 +854,6 @@ static struct fuse_operations fg_operations = {
   .removexattr	= fg_removexattr,
 };

-struct drv {
-  struct drv *next;
-  char *filename;
-  const char *format;
-};
-
-struct mp {
-  struct mp *next;
-  char *device;
-  char *mountpoint;
-};
-
-static void add_drives (struct drv *);
-static void mount_mps (struct mp *);
-
 static void __attribute__((noreturn))
 fuse_help (void)
 {
@@ -884,23 +872,26 @@ usage (int status)
     fprintf (stdout,
            _("%s: FUSE module for libguestfs\n"
              "%s lets you mount a virtual machine filesystem\n"
-             "Copyright (C) 2009 Red Hat Inc.\n"
+             "Copyright (C) 2009-2010 Red Hat Inc.\n"
              "Usage:\n"
              "  %s [--options] [-- [--FUSE-options]] mountpoint\n"
              "Options:\n"
              "  -a|--add image       Add image\n"
+             "  -c|--connect uri     Specify libvirt URI for -d
option\n"
              "  --dir-cache-timeout  Set readdir cache timeout (default 5
sec)\n"
+             "  -d|--domain guest    Add disks from libvirt guest\n"
              "  --format[=raw|..]    Force disk format for -a
option\n"
              "  --fuse-help          Display extra FUSE options\n"
+             "  -i|--inspector       Automatically mount
filesystems\n"
              "  --help               Display help message and exit\n"
              "  -m|--mount dev[:mnt] Mount dev on mnt (if omitted,
/)\n"
              "  -n|--no-sync         Don't autosync\n"
              "  -o|--option opt      Pass extra option to FUSE\n"
              "  -r|--ro              Mount read-only\n"
              "  --selinux            Enable SELinux support\n"
-             "  --trace              Trace guestfs API calls (to
stderr)\n"
              "  -v|--verbose         Verbose messages\n"
              "  -V|--version         Display version and exit\n"
+             "  -x|--trace           Trace guestfs API calls\n"
              ),
              program_name, program_name, program_name);
   }
@@ -919,19 +910,22 @@ main (int argc, char *argv[])
   /* The command line arguments are broadly compatible with (a subset
    * of) guestfish.  Thus we have to deal mainly with -a, -m and --ro.
    */
-  static const char *options = "a:m:no:rv?V";
+  static const char *options = "a:c:d:im:no:rv?Vx";
   static const struct option long_options[] = {
     { "add", 1, 0, 'a' },
+    { "connect", 1, 0, 'c' },
     { "dir-cache-timeout", 1, 0, 0 },
+    { "domain", 1, 0, 'd' },
     { "format", 2, 0, 0 },
     { "fuse-help", 0, 0, 0 },
     { "help", 0, 0, HELP_OPTION },
+    { "inspector", 0, 0, 'i' },
     { "mount", 1, 0, 'm' },
     { "no-sync", 0, 0, 'n' },
     { "option", 1, 0, 'o' },
     { "ro", 0, 0, 'r' },
     { "selinux", 0, 0, 0 },
-    { "trace", 0, 0, 0 },
+    { "trace", 0, 0, 'x' },
     { "verbose", 0, 0, 'v' },
     { "version", 0, 0, 'V' },
     { 0, 0, 0, 0 }
@@ -1016,11 +1010,6 @@ main (int argc, char *argv[])
         fuse_help ();
       else if (STREQ (long_options[option_index].name, "selinux"))
         guestfs_set_selinux (g, 1);
-      else if (STREQ (long_options[option_index].name, "trace")) {
-        ADD_FUSE_ARG ("-f");
-        guestfs_set_trace (g, 1);
-        guestfs_set_recovery_proc (g, 1);
-      }
       else if (STREQ (long_options[option_index].name, "format")) {
         if (!optarg || STREQ (optarg, ""))
           format = NULL;
@@ -1035,40 +1024,27 @@ main (int argc, char *argv[])
       break;

     case 'a':
-      if (access (optarg, R_OK) != 0) {
-        perror (optarg);
-        exit (EXIT_FAILURE);
-      }
-      drv = malloc (sizeof (struct drv));
-      if (!drv) {
-        perror ("malloc");
-        exit (EXIT_FAILURE);
-      }
-      drv->filename = optarg;
-      drv->format = format;
-      drv->next = drvs;
-      drvs = drv;
+      OPTION_a;
+      break;
+
+    case 'c':
+      OPTION_c;
+      break;
+
+    case 'd':
+      OPTION_d;
+      break;
+
+    case 'i':
+      OPTION_i;
       break;

     case 'm':
-      mp = malloc (sizeof (struct mp));
-      if (!mp) {
-        perror ("malloc");
-        exit (EXIT_FAILURE);
-      }
-      p = strchr (optarg, ':');
-      if (p) {
-        *p = '\0';
-        mp->mountpoint = p+1;
-      } else
-        mp->mountpoint = bad_cast ("/");
-      mp->device = optarg;
-      mp->next = mps;
-      mps = mp;
+      OPTION_m;
       break;

     case 'n':
-      guestfs_set_autosync (g, 0);
+      OPTION_n;
       break;

     case 'o':
@@ -1077,20 +1053,22 @@ main (int argc, char *argv[])
       break;

     case 'r':
-      read_only = 1;
+      OPTION_r;
       break;

     case 'v':
-      verbose++;
-      guestfs_set_verbose (g, verbose);
+      OPTION_v;
       break;

-    case 'V': {
-      struct guestfs_version *v = guestfs_version (g);
-      printf ("%s
%"PRIi64".%"PRIi64".%"PRIi64"%s\n",
program_name,
-              v->major, v->minor, v->release, v->extra);
-      exit (EXIT_SUCCESS);
-    }
+    case 'V':
+      OPTION_V;
+      break;
+
+    case 'x':
+      OPTION_x;
+      ADD_FUSE_ARG ("-f");
+      guestfs_set_recovery_proc (g, 1);
+      break;

     case HELP_OPTION:
       usage (EXIT_SUCCESS);
@@ -1100,10 +1078,10 @@ main (int argc, char *argv[])
     }
   }

-  /* We must have at least one -a and at least one -m. */
-  if (!drvs || !mps) {
+  /* Check we have the right options. */
+  if (!drvs || !(mps || inspector)) {
     fprintf (stderr,
-             _("%s: must have at least one -a and at least one -m
option\n"),
+             _("%s: must have at least one -a/-d and at least one -m/-i
option\n"),
              program_name);
     exit (EXIT_FAILURE);
   }
@@ -1117,11 +1095,16 @@ main (int argc, char *argv[])
   }

   /* Do the guest drives and mountpoints. */
-  add_drives (drvs);
+  add_drives (drvs, 'a');
   if (guestfs_launch (g) == -1)
     exit (EXIT_FAILURE);
+  if (inspector)
+    inspect_mount ();
   mount_mps (mps);

+  free_drives (drvs);
+  free_mps (mps);
+
   /* FUSE example does this, not clear if it's necessary, but ... */
   if (guestfs_umask (g, 0) == -1)
     exit (EXIT_FAILURE);
@@ -1159,48 +1142,3 @@ main (int argc, char *argv[])

   exit (r == -1 ? 1 : 0);
 }
-
-/* List is built in reverse order, so add them in reverse order. */
-static void
-add_drives (struct drv *drv)
-{
-  int r;
-  struct guestfs_add_drive_opts_argv ad_optargs;
-
-  if (drv) {
-    add_drives (drv->next);
-
-    ad_optargs.bitmask = 0;
-    if (read_only) {
-      ad_optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK;
-      ad_optargs.readonly = 1;
-    }
-    if (drv->format) {
-      ad_optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_FORMAT_BITMASK;
-      ad_optargs.format = drv->format;
-    }
-    r = guestfs_add_drive_opts_argv (g, drv->filename, &ad_optargs);
-    if (r == -1)
-      exit (EXIT_FAILURE);
-  }
-}
-
-/* List is built in reverse order, so mount them in reverse order. */
-static void
-mount_mps (struct mp *mp)
-{
-  int r;
-
-  if (mp) {
-    mount_mps (mp->next);
-
-    /* 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)
-      exit (EXIT_FAILURE);
-  }
-}
diff --git a/fuse/guestmount.pod b/fuse/guestmount.pod
index fe19a07..afa1478 100644
--- a/fuse/guestmount.pod
+++ b/fuse/guestmount.pod
@@ -8,6 +8,10 @@ guestmount - Mount a guest filesystem on the host using FUSE
and libguestfs

  guestmount [--options] -a disk.img -m device [--ro] mountpoint

+ guestmount [--options] -a disk.img -i [--ro] mountpoint
+
+ guestmount [--options] -d Guest -i [--ro] mountpoint
+
 =head1 WARNING

 You must I<not> use C<guestmount> in read-write mode on live
virtual
@@ -21,10 +25,10 @@ access to the guest filesystem, and FUSE (the
"filesystem in
 userspace") to make it appear as a mountable device.

 Along with other options, you have to give at least one device (I<-a>
-option) and at least one mountpoint (I<-m> option).  How this works is
-better explained in the L<guestfish(1)> manual page, or you can use
-L<virt-inspector(1)> and/or the wrapper script
-C<guestmount-wrapper> to help you.
+option) or libvirt domain (I<-d> option), and at least one mountpoint
+(I<-m> option) or use the I<-i> inspection option.  How this works
is
+better explained in the L<guestfish(1)> manual page, or by looking at
+the examples below.

 FUSE lets you mount filesystems as non-root.  The mountpoint must be
 owned by you, and the filesystem will not be visible to any other
@@ -44,14 +48,13 @@ partition, and the root filesystem on a logical volume:

  guestmount -a linux.img -m /dev/VG/LV -m /dev/sda1:/boot --ro /mnt

-To get L<virt-inspector(1)> to do the hard work of detecting guest
-mountpoints for you:
+To get libguestfs to detect guest mountpoints for you:
+
+ guestmount -a guest.img -i --ro /mnt

- guestmount $(virt-inspector --ro-fish MyGuest) /mnt
+For a libvirt guest called "Guest" you could do:

-(or use --fish if you don't want it to be a read only mount).  The
-option is called I<--ro-fish> or I<--fish> because these parameters
-are compatible with L<guestfish(1)>.
+ guestmount -d Guest -i --ro /mnt

 If you don't know what filesystems are contained in a guest or
 disk image, use L<virt-list-filesystems(1)> first:
@@ -59,13 +62,13 @@ disk image, use L<virt-list-filesystems(1)> first:
  virt-list-filesystems MyGuest

 If you want to trace the libguestfs calls but without excessive
-debugging, we recommend:
+debugging information, we recommend:

- guestmount [-a ... -m ...] --trace /mnt
+ guestmount [...] --trace /mnt

 If you want to debug the program, we recommend:

- guestmount [-a ... -m ...] --trace --verbose /mnt
+ guestmount [...] --trace --verbose /mnt

 =head1 OPTIONS

@@ -78,6 +81,18 @@ Add a block device or virtual machine image.
 The format of the disk image is auto-detected.  To override this and
 force a particular format use the I<--format=..> option.

+=item B<-c URI> | B<--connect URI>
+
+When used in conjunction with the I<-d> option, this specifies
+the libvirt URI to use.  The default is to use the default libvirt
+connection.
+
+=item B<-d libvirt-domain> | B<--domain libvirt-domain>
+
+Add disks from the named libvirt domain.  If the I<--ro> option is
+also used, then any libvirt domain can be used.  However in write
+mode, only libvirt domains which are shut down can be named here.
+
 =item B<--dir-cache-timeout N>

 Set the readdir cache timeout to I<N> seconds, the default being 60
@@ -100,7 +115,7 @@ switches back to auto-detection for subsequent I<-a>
options.
 If you have untrusted raw-format guest disk images, you should use
 this option to specify the disk format.  This avoids a possible
 security problem with malicious guests (CVE-2010-3851).  See also
-L</add-drive-opts>.
+L<guestfs(3)/guestfs_add_drive_opts>.

 =item B<--fuse-help>

@@ -110,6 +125,12 @@ Display help on special FUSE options (see I<-o>
below).

 Display brief help and exit.

+=item B<-i> | B<--inspector>
+
+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.
+
 =item B<-m dev[:mnt]> | B<--mount dev[:mnt]>

 Mount the named partition or logical volume on the given mountpoint
@@ -174,12 +195,6 @@ to prevent this from happening, but it is not always
possible.

 Enable SELinux support for the guest.

-=item B<--trace>
-
-Trace libguestfs calls (to stderr).
-
-This also stops the daemon from forking into the background.
-
 =item B<-v> | B<--verbose>

 Enable verbose messages from underlying libguestfs.
@@ -188,6 +203,12 @@ Enable verbose messages from underlying libguestfs.

 Display the program version and exit.

+=item B<-x> | B<--trace>
+
+Trace libguestfs calls.
+
+This also stops the daemon from forking into the background.
+
 =back

 =head1 SEE ALSO
@@ -207,7 +228,7 @@ Richard W.M. Jones (C<rjones at redhat dot com>)

 =head1 COPYRIGHT

-Copyright (C) 2009 Red Hat Inc.
+Copyright (C) 2009-2010 Red Hat Inc.
 L<http://libguestfs.org/>

 This program is free software; you can redistribute it and/or modify
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 10b690c..970068d 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -83,6 +83,7 @@ fish/inspect.c
 fish/lcd.c
 fish/man.c
 fish/more.c
+fish/options.c
 fish/prep.c
 fish/prep_boot.c
 fish/prep_disk.c
--
1.7.3.1