fish: Add -N option for making prepared disk images.
authorRichard Jones <rjones@redhat.com>
Thu, 22 Apr 2010 09:42:58 +0000 (10:42 +0100)
committerRichard Jones <rjones@redhat.com>
Thu, 22 Apr 2010 17:07:11 +0000 (18:07 +0100)
Previously you might have typed:

$ guestfish
><fs> alloc test1.img 100M
><fs> run
><fs> part-disk /dev/sda mbr
><fs> mkfs ext4 /dev/sda1

now you can do the same with:

$ guestfish -N fs:ext4

Some tests have also been updated to use this new
functionality.

17 files changed:
TODO
fish/Makefile.am
fish/alloc.c
fish/fish.c
fish/fish.h
fish/guestfish.pod
fish/prep.c [new file with mode: 0644]
po/POTFILES.in
regressions/rhbz503169c10.sh
regressions/rhbz503169c13.sh
regressions/rhbz557655.sh
regressions/rhbz576879.sh
regressions/test-bootbootboot.sh
regressions/test-cancellation-upload-daemoncancels.sh
regressions/test-qemudie-killsub.sh
regressions/test-qemudie-midcommand.sh
regressions/test-qemudie-synch.sh

diff --git a/TODO b/TODO
index 56b429a..881067d 100644 (file)
--- a/TODO
+++ b/TODO
@@ -212,16 +212,6 @@ ntfsclone
 Useful imaging tool:
 http://man.linux-ntfs.org/ntfsclone.8.html
 
-Standard images
----------------
-
-Equip guestfish with some standard images that it can load
-quickly, eg:
-
-  load ext2
-
-Maybe it's better to create these on the fly?
-
 virt-rescue pty
 ---------------
 
index 43dce00..c25a602 100644 (file)
@@ -45,6 +45,7 @@ guestfish_SOURCES = \
        glob.c \
        lcd.c \
        more.c \
+       prep.c \
        rc.c \
        reopen.c \
        tilde.c \
index 93cd8af..f91c5bb 100644 (file)
 
 #include "fish.h"
 
-static int parse_size (const char *str, off_t *size_rtn);
-
 int
 do_alloc (const char *cmd, int argc, char *argv[])
 {
-  off_t size;
-  int fd;
-
   if (argc != 2) {
     fprintf (stderr, _("use 'alloc file size' to create an image\n"));
     return -1;
   }
 
-  if (parse_size (argv[1], &size) == -1)
-    return -1;
-
-  if (!guestfs_is_config (g)) {
-    fprintf (stderr, _("can't allocate or add disks after launching\n"));
-    return -1;
-  }
-
-  fd = open (argv[0], O_WRONLY|O_CREAT|O_NOCTTY|O_TRUNC, 0666);
-  if (fd == -1) {
-    perror (argv[0]);
+  if (alloc_disk (argv[0], argv[1], 1, 0) == -1)
     return -1;
-  }
 
-#ifdef HAVE_POSIX_FALLOCATE
-  int err = posix_fallocate (fd, 0, size);
-  if (err != 0) {
-    errno = err;
-    perror ("fallocate");
-    close (fd);
-    unlink (argv[0]);
-    return -1;
-  }
-#else
-  /* Slow emulation of posix_fallocate on platforms which don't have it. */
-  char buffer[BUFSIZ];
-  memset (buffer, 0, sizeof buffer);
-
-  size_t remaining = size;
-  while (remaining > 0) {
-    size_t n = remaining > sizeof buffer ? sizeof buffer : remaining;
-    ssize_t r = write (fd, buffer, n);
-    if (r == -1) {
-      perror ("write");
-      close (fd);
-      unlink (argv[0]);
-      return -1;
-    }
-    remaining -= r;
-  }
-#endif
+  return 0;
+}
 
-  if (close (fd) == -1) {
-    perror (argv[0]);
-    unlink (argv[0]);
+int
+do_sparse (const char *cmd, int argc, char *argv[])
+{
+  if (argc != 2) {
+    fprintf (stderr, _("use 'sparse file size' to create a sparse image\n"));
     return -1;
   }
 
-  if (guestfs_add_drive (g, argv[0]) == -1) {
-    unlink (argv[0]);
+  if (alloc_disk (argv[0], argv[1], 1, 1) == -1)
     return -1;
-  }
 
   return 0;
 }
 
+static int parse_size (const char *str, off_t *size_rtn);
+
+/* This is the underlying allocation function.  It's called from
+ * a few other places in guestfish.
+ */
 int
-do_sparse (const char *cmd, int argc, char *argv[])
+alloc_disk (const char *filename, const char *size_str, int add, int sparse)
 {
   off_t size;
   int fd;
   char c = 0;
 
-  if (argc != 2) {
-    fprintf (stderr, _("use 'sparse file size' to create a sparse image\n"));
-    return -1;
-  }
-
-  if (parse_size (argv[1], &size) == -1)
+  if (parse_size (size_str, &size) == -1)
     return -1;
 
   if (!guestfs_is_config (g)) {
@@ -117,35 +76,67 @@ do_sparse (const char *cmd, int argc, char *argv[])
     return -1;
   }
 
-  fd = open (argv[0], O_WRONLY|O_CREAT|O_NOCTTY|O_TRUNC, 0666);
+  fd = open (filename, O_WRONLY|O_CREAT|O_NOCTTY|O_TRUNC, 0666);
   if (fd == -1) {
-    perror (argv[0]);
+    perror (filename);
     return -1;
   }
 
-  if (lseek (fd, size-1, SEEK_SET) == (off_t) -1) {
-    perror ("lseek");
-    close (fd);
-    unlink (argv[0]);
-    return -1;
-  }
+  if (!sparse) {                /* Not sparse */
+#ifdef HAVE_POSIX_FALLOCATE
+    int err = posix_fallocate (fd, 0, size);
+    if (err != 0) {
+      errno = err;
+      perror ("fallocate");
+      close (fd);
+      unlink (filename);
+      return -1;
+    }
+#else
+    /* Slow emulation of posix_fallocate on platforms which don't have it. */
+    char buffer[BUFSIZ];
+    memset (buffer, 0, sizeof buffer);
+
+    size_t remaining = size;
+    while (remaining > 0) {
+      size_t n = remaining > sizeof buffer ? sizeof buffer : remaining;
+      ssize_t r = write (fd, buffer, n);
+      if (r == -1) {
+        perror ("write");
+        close (fd);
+        unlink (filename);
+        return -1;
+      }
+      remaining -= r;
+    }
+#endif
+  } else {                      /* Sparse */
+    if (lseek (fd, size-1, SEEK_SET) == (off_t) -1) {
+      perror ("lseek");
+      close (fd);
+      unlink (filename);
+      return -1;
+    }
 
-  if (write (fd, &c, 1) != 1) {
-    perror ("write");
-    close (fd);
-    unlink (argv[0]);
-    return -1;
+    if (write (fd, &c, 1) != 1) {
+      perror ("write");
+      close (fd);
+      unlink (filename);
+      return -1;
+    }
   }
 
   if (close (fd) == -1) {
-    perror (argv[0]);
-    unlink (argv[0]);
+    perror (filename);
+    unlink (filename);
     return -1;
   }
 
-  if (guestfs_add_drive (g, argv[0]) == -1) {
-    unlink (argv[0]);
-    return -1;
+  if (add) {
+    if (guestfs_add_drive (g, filename) == -1) {
+      unlink (filename);
+      return -1;
+    }
   }
 
   return 0;
index db3149e..4a7e70b 100644 (file)
 #include "closeout.h"
 #include "progname.h"
 
+struct drv {
+  struct drv *next;
+  char *filename;               /* disk filename (for -a or -N options) */
+  prep_data *data;              /* prepared type (for -N option only) */
+  char *device;                 /* device inside the appliance */
+};
+
 struct mp {
   struct mp *next;
   char *device;
   char *mountpoint;
 };
 
-struct drv {
-  struct drv *next;
-  char *filename;
-};
-
 static void add_drives (struct drv *drv);
+static void prepare_drives (struct drv *drv);
 static void mount_mps (struct mp *mp);
 static void interactive (void);
 static void shell_script (void);
@@ -119,6 +122,7 @@ usage (int status)
              "  --listen             Listen for remote commands\n"
              "  -m|--mount dev[:mnt] Mount dev on mnt (if omitted, /)\n"
              "  -n|--no-sync         Don't autosync\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"
@@ -147,7 +151,7 @@ main (int argc, char *argv[])
 
   enum { HELP_OPTION = CHAR_MAX + 1 };
 
-  static const char *options = "a:Df:h::im:nrv?Vx";
+  static const char *options = "a:Df:h::im:nN:rv?Vx";
   static const struct option long_options[] = {
     { "add", 1, 0, 'a' },
     { "cmd-help", 2, 0, 'h' },
@@ -156,6 +160,7 @@ main (int argc, char *argv[])
     { "inspector", 0, 0, 'i' },
     { "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 },
@@ -174,6 +179,8 @@ main (int argc, char *argv[])
   int inspector = 0;
   int option_index;
   struct sigaction sa;
+  char next_drive = 'a';
+  int next_prepared_drive = 1;
 
   initialize_readline ();
 
@@ -259,6 +266,36 @@ main (int argc, char *argv[])
         exit (EXIT_FAILURE);
       }
       drv->filename = optarg;
+      drv->data = NULL;
+      /* We could fill the device field in, but in fact we
+       * only use it for the -N option at present.
+       */
+      drv->device = NULL;
+      drv->next = drvs;
+      drvs = drv;
+      next_drive++;
+      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);
+      }
+      if (asprintf (&drv->filename, "test%d.img",
+                    next_prepared_drive++) == -1) {
+        perror ("asprintf");
+        exit (EXIT_FAILURE);
+      }
+      drv->data = create_prepared_file (optarg, drv->filename);
+      if (asprintf (&drv->device, "/dev/sd%c", next_drive++) == -1) {
+        perror ("asprintf");
+        exit (EXIT_FAILURE);
+      }
       drv->next = drvs;
       drvs = drv;
       break;
@@ -342,8 +379,8 @@ main (int argc, char *argv[])
 
     if (drvs || mps || remote_control_listen || remote_control ||
         guestfs_get_selinux (g)) {
-      fprintf (stderr, _("%s: cannot use -i option with -a, -m,"
-                         " --listen, --remote or --selinux\n"),
+      fprintf (stderr, _("%s: cannot use -i option with -a, -m, -N, "
+                         "--listen, --remote or --selinux\n"),
                program_name);
       exit (EXIT_FAILURE);
     }
@@ -396,9 +433,12 @@ main (int argc, char *argv[])
   /* If we've got drives to add, add them now. */
   add_drives (drvs);
 
-  /* If we've got mountpoints, we must launch the guest and mount them. */
-  if (mps != NULL) {
+  /* If we've got mountpoints or prepared drives, we must launch the
+   * guest and mount them.
+   */
+  if (next_prepared_drive > 1 || mps != NULL) {
     if (launch (g) == -1) exit (EXIT_FAILURE);
+    prepare_drives (drvs);
     mount_mps (mps);
   }
 
@@ -495,7 +535,8 @@ add_drives (struct drv *drv)
 
   if (drv) {
     add_drives (drv->next);
-    if (!read_only)
+
+    if (drv->data /* -N option is not affected by --ro */ || !read_only)
       r = guestfs_add_drive (g, drv->filename);
     else
       r = guestfs_add_drive_ro (g, drv->filename);
@@ -505,6 +546,15 @@ add_drives (struct drv *drv)
 }
 
 static void
+prepare_drives (struct drv *drv)
+{
+  if (drv) {
+    prepare_drives (drv->next);
+    prepare_drive (drv->filename, drv->data, drv->device);
+  }
+}
+
+static void
 interactive (void)
 {
   script (1);
index 05135fb..13efa9a 100644 (file)
@@ -85,6 +85,8 @@ extern char *complete_dest_paths_generator (const char *text, int state);
 /* in alloc.c */
 extern int do_alloc (const char *cmd, int argc, char *argv[]);
 extern int do_sparse (const char *cmd, int argc, char *argv[]);
+extern int alloc_disk (const char *filename, const char *size,
+                       int add, int sparse);
 
 /* in echo.c */
 extern int do_echo (const char *cmd, int argc, char *argv[]);
@@ -101,6 +103,14 @@ extern int do_glob (const char *cmd, int argc, char *argv[]);
 /* in more.c */
 extern int do_more (const char *cmd, int argc, char *argv[]);
 
+/* in prep.c */
+typedef struct prep_data prep_data;
+extern void list_prepared_drives (void);
+extern prep_data *create_prepared_file (const char *type_string,
+                                        const char *filename);
+extern void prepare_drive (const char *filename, prep_data *data,
+                           const char *device);
+
 /* in rc.c (remote control) */
 extern void rc_listen (void) __attribute__((noreturn));
 extern int rc_remote (int pid, const char *cmd, int argc, char *argv[],
index a0c3975..165cdf2 100644 (file)
@@ -75,14 +75,25 @@ in the virtual machine:
 
 =head2 As a script interpreter
 
-Create a 50MB disk containing an ext2-formatted partition:
+Create a 100MB disk containing an ext2-formatted partition:
 
  #!/usr/bin/guestfish -f
alloc /tmp/output.img 50M
sparse test1.img 100M
  run
  part-disk /dev/sda mbr
  mkfs ext2 /dev/sda1
 
+=head2 Start with a prepared disk
+
+An alternate way to create a 100MB disk called C<test1.img> containing
+a single ext2-formatted partition:
+
+ guestfish -N fs
+
+To list what is available do:
+
+ guestfish -N list | less
+
 =head2 Remote control
 
  eval `guestfish --listen --ro`
@@ -159,9 +170,9 @@ Typical usage is either:
 
  guestfish -i /dev/Guests/MyGuest
 
-You cannot use I<-a>, I<-m>, I<--listen>, I<--remote> or I<--selinux>
-in conjunction with this option, and options other than I<--ro> might
-not behave correctly.
+You cannot use I<-a>, I<-m>, I<-N>, I<--listen>, I<--remote> or
+I<--selinux> in conjunction with this option, and options other than
+I<--ro> might not behave correctly.
 
 See also: L<virt-inspector(1)>.
 
@@ -191,6 +202,13 @@ or you can use the L<virt-list-filesystems(1)> program.
 Disable autosync.  This is enabled by default.  See the discussion
 of autosync in the L<guestfs(3)> manpage.
 
+=item B<-N type> | B<--new type> | B<-N list>
+
+Prepare a fresh disk image formatted as "type".  This is an
+alternative to the I<-a> option: whereas I<-a> adds an existing disk,
+I<-N> creates a preformatted disk with a filesystem and adds it.
+See L</PREPARED DISK IMAGES> below.
+
 =item B<--remote[=pid]>
 
 Send remote commands to C<$GUESTFISH_PID> or C<pid>.  See section
@@ -205,6 +223,9 @@ The option must always be used if the disk image or virtual machine
 might be running, and is generally recommended in cases where you
 don't need write access to the disk.
 
+Note that prepared disk images created with I<-N> are not affected by
+the I<--ro> option.
+
 =item B<--selinux>
 
 Enable SELinux support for the guest.  See L<guestfs(3)/SELINUX>.
@@ -486,6 +507,51 @@ user ID of the process, and C<$PID> is the process ID of the server.
 
 Guestfish client and server versions must match exactly.
 
+=head1 PREPARED DISK IMAGES
+
+Use the I<-N type> or I<--new type> parameter to select one of a set
+of preformatted disk images that guestfish can make for you to save
+typing.  This is particularly useful for testing purposes.  This
+option is used instead of the I<-a> option, and like I<-a> can appear
+multiple times (and can be mixed with I<-a>).
+
+The new disk is called C<test1.img> for the first I<-N>, C<test2.img>
+for the second and so on.  Existing files in the current directory are
+not overwritten, so you may need to do C<rm -f test1.img>.
+
+The type briefly describes how the disk should be sized, partitioned,
+how filesystem(s) should be created, and how content should be added.
+Optionally the type can be followed by extra parameters, separated by
+C<:> (colon) characters.  For example, I<-N fs> creates a default
+100MB, sparsely-allocated disk, containing a single partition, with
+the partition formatted as ext2.  I<-N fs:ext4:1G> is the same, but
+for an ext4 filesystem on a 1GB disk instead.
+
+To list the available types and any extra parameters they take, run:
+
+ guestfish -N list | less
+
+Note that the prepared filesystem is not mounted.  You would usually
+have to use the C<mount /dev/sda1 /> command or add the
+I<-m /dev/sda1> option.
+
+If any I<-N> or I<--new> options are given, the guest is automatically
+launched.
+
+=head2 EXAMPLES
+
+Create a 100MB disk with an ext4-formatted partition:
+
+ guestfish -N fs:ext4
+
+Create a 32MB disk with a VFAT-formatted partition, and mount it:
+
+ guestfish -N fs:vfat:32M -m /dev/sda1
+
+Create a blank 200MB disk:
+
+ guestfish -N disk:200M
+
 =head1 UPLOADING AND DOWNLOADING FILES
 
 For commands such as C<upload>, C<download>, C<tar-in>, C<tar-out> and
diff --git a/fish/prep.c b/fish/prep.c
new file mode 100644 (file)
index 0000000..ab5b4bc
--- /dev/null
@@ -0,0 +1,305 @@
+/* guestfish - the filesystem interactive shell
+ * Copyright (C) 2010 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "fish.h"
+
+static prep_data *parse_type_string (const char *type_string);
+static void prep_error (prep_data *data, const char *filename, const char *fs, ...) __attribute__((noreturn, format (printf,3,4)));
+
+struct prep {
+  const char *name;             /* eg. "fs" */
+
+  size_t nr_params;             /* optional parameters */
+  struct param *params;
+
+  const char *shortdesc;        /* short description */
+  const char *longdesc;         /* long description */
+
+                                /* functions to implement it */
+  void (*prelaunch) (const char *filename, prep_data *);
+  void (*postlaunch) (const char *filename, prep_data *, const char *device);
+};
+
+struct param {
+  const char *pname;            /* parameter name */
+  const char *pdefault;         /* parameter default */
+  const char *pdesc;            /* parameter description */
+};
+
+static void prelaunch_disk (const char *filename, prep_data *data);
+static struct param disk_params[] = {
+  { "size", "100M", "the size of the disk image" },
+};
+
+static void prelaunch_part (const char *filename, prep_data *data);
+static void postlaunch_part (const char *filename, prep_data *data, const char *device);
+static struct param part_params[] = {
+  { "size", "100M", "the size of the disk image" },
+  { "partition", "mbr", "partition table type" },
+};
+
+static void prelaunch_fs (const char *filename, prep_data *data);
+static void postlaunch_fs (const char *filename, prep_data *data, const char *device);
+static struct param fs_params[] = {
+  { "filesystem", "ext2", "the type of filesystem to use" },
+  { "size", "100M", "the size of the disk image" },
+  { "partition", "mbr", "partition table type" },
+};
+
+static const struct prep preps[] = {
+  { "disk",
+    1, disk_params,
+    "create a blank disk",
+    "\
+  Create a blank disk, size 100MB (by default).\n\
+\n\
+  The default size can be changed by supplying an optional parameter.",
+    prelaunch_disk, NULL
+  },
+  { "part",
+    2, part_params,
+    "create a partitioned disk",
+    "\
+  Create a disk with a single partition.  By default the size of the disk\n\
+  is 100MB (the available space in the partition will be a tiny bit smaller)\n\
+  and the partition table will be MBR (old DOS-style).\n\
+\n\
+  These defaults can be changed by supplying optional parameters.",
+    prelaunch_part, postlaunch_part
+  },
+  { "fs",
+    3, fs_params,
+    "create a filesystem",
+    "\
+  Create a disk with a single partition, with the partition containing\n\
+  an empty filesystem.  This defaults to creating a 100MB disk (the available\n\
+  space in the filesystem will be a tiny bit smaller) with an MBR (old\n\
+  DOS-style) partition table and an ext2 filesystem.\n\
+\n\
+  These defaults can be changed by supplying optional parameters.",
+    prelaunch_fs, postlaunch_fs
+  },
+};
+
+void
+list_prepared_drives (void)
+{
+  size_t i, j;
+
+  printf (_("List of available prepared disk images:\n\n"));
+
+  for (i = 0; i < sizeof preps / sizeof preps[0]; ++i) {
+    printf (_("\
+guestfish -N %-16s %s\n\
+\n\
+%s\n"),
+            preps[i].name, preps[i].shortdesc, preps[i].longdesc);
+
+    if (preps[i].nr_params > 0) {
+      printf ("\n");
+      printf (_("  Optional parameters:\n"));
+      printf ("    -N %s", preps[i].name);
+      for (j = 0; j < preps[i].nr_params; ++j)
+        printf (":<%s>", preps[i].params[j].pname);
+      printf ("\n");
+      for (j = 0; j < preps[i].nr_params; ++j) {
+        printf ("      ");
+        printf (_("<%s> %s (default: %s)\n"),
+                preps[i].params[j].pname,
+                preps[i].params[j].pdesc,
+                preps[i].params[j].pdefault);
+      }
+    }
+
+    printf ("\n");
+  }
+
+  printf (_("\
+Prepared disk images are written to file \"test1.img\" in the local\n\
+directory.  (\"test2.img\" etc if -N option is given multiple times).\n\
+For more information see the guestfish(1) manual.\n"));
+}
+
+struct prep_data {
+  const struct prep *prep;
+  const char *orig_type_string;
+  const char **params;
+};
+
+/* Parse the type string (from the command line) and create the output
+ * file 'filename'.  This is called before launch.  Return the opaque
+ * prep_data which will be passed back to us in prepare_drive below.
+ */
+prep_data *
+create_prepared_file (const char *type_string, const char *filename)
+{
+  if (access (filename, F_OK) == 0) {
+    fprintf (stderr, _("guestfish: file '%s' exists and the '-N' option will not overwrite it\n"),
+             filename);
+    exit (EXIT_FAILURE);
+  }
+
+  prep_data *data = parse_type_string (type_string);
+  if (data->prep->prelaunch)
+    data->prep->prelaunch (filename, data);
+  return data;
+}
+
+static prep_data *
+parse_type_string (const char *type_string)
+{
+  size_t i;
+
+  /* Match on the type part (without parameters). */
+  size_t len = strcspn (type_string, ":");
+  for (i = 0; i < sizeof preps / sizeof preps[0]; ++i)
+    if (STRCASEEQLEN (type_string, preps[i].name, len))
+      break;
+
+  if (preps[i].name == NULL) {
+    fprintf (stderr, _("\
+guestfish: -N parameter '%s': no such prepared disk image known.\n\
+Use 'guestfish -N list' to list possible values for the -N parameter.\n"),
+             type_string);
+    exit (EXIT_FAILURE);
+  }
+
+  prep_data *data = malloc (sizeof *data);
+  if (data == NULL) {
+    perror ("malloc");
+    exit (EXIT_FAILURE);
+  }
+  data->prep = &preps[i];
+  data->orig_type_string = type_string;
+
+  /* Set up the optional parameters to all-defaults. */
+  data->params = malloc (data->prep->nr_params * sizeof (char *));
+  if (data->params == NULL) {
+    perror ("malloc");
+    exit (EXIT_FAILURE);
+  }
+
+  for (i = 0; i < data->prep->nr_params; ++i)
+    data->params[i] = data->prep->params[i].pdefault;
+
+  /* Parse the optional parameters. */
+  const char *p = type_string + len;
+  if (*p) p++; /* skip colon char */
+
+  i = 0;
+  while (*p) {
+    len = strcspn (p, ":");
+    data->params[i] = strndup (p, len);
+    if (data->params[i] == NULL) {
+      perror ("strndup");
+      exit (EXIT_FAILURE);
+    }
+
+    p += len;
+    if (*p) p++; /* skip colon char */
+    i++;
+  }
+
+  return data;
+}
+
+/* Prepare a drive.  The appliance has been launched, and 'device' is
+ * the libguestfs device.  'data' is the requested type.  'filename'
+ * is just used for error messages.
+ */
+void
+prepare_drive (const char *filename, prep_data *data,
+               const char *device)
+{
+  if (data->prep->postlaunch)
+    data->prep->postlaunch (filename, data, device);
+}
+
+static void
+prep_error (prep_data *data, const char *filename, const char *fs, ...)
+{
+  fprintf (stderr,
+           _("guestfish: error creating prepared disk image '%s' on '%s': "),
+           data->orig_type_string, filename);
+
+  va_list args;
+  va_start (args, fs);
+  vfprintf (stderr, fs, args);
+  va_end (args);
+
+  fprintf (stderr, "\n");
+
+  exit (EXIT_FAILURE);
+}
+
+static void
+prelaunch_disk (const char *filename, prep_data *data)
+{
+  if (alloc_disk (filename, data->params[0], 0, 1) == -1)
+    prep_error (data, filename, _("failed to allocate disk"));
+}
+
+static void
+prelaunch_part (const char *filename, prep_data *data)
+{
+  if (alloc_disk (filename, data->params[0], 0, 1) == -1)
+    prep_error (data, filename, _("failed to allocate disk"));
+}
+
+static void
+postlaunch_part (const char *filename, prep_data *data, const char *device)
+{
+  if (guestfs_part_disk (g, device, data->params[2]) == -1)
+    prep_error (data, filename, _("failed to partition disk: %s"),
+                guestfs_last_error (g));
+}
+
+static void
+prelaunch_fs (const char *filename, prep_data *data)
+{
+  if (alloc_disk (filename, data->params[1], 0, 1) == -1)
+    prep_error (data, filename, _("failed to allocate disk"));
+}
+
+static void
+postlaunch_fs (const char *filename, prep_data *data, const char *device)
+{
+  if (guestfs_part_disk (g, device, data->params[2]) == -1)
+    prep_error (data, filename, _("failed to partition disk: %s"),
+                guestfs_last_error (g));
+
+  char *part;
+  if (asprintf (&part, "%s1", device) == -1) {
+    perror ("asprintf");
+    exit (EXIT_FAILURE);
+  }
+
+  if (guestfs_mkfs (g, data->params[0], part) == -1)
+    prep_error (data, filename, _("failed to create filesystem (%s): %s"),
+                data->params[0], guestfs_last_error (g));
+
+  free (part);
+}
index aebe450..8cf6e16 100644 (file)
@@ -76,6 +76,7 @@ fish/fish.c
 fish/glob.c
 fish/lcd.c
 fish/more.c
+fish/prep.c
 fish/rc.c
 fish/reopen.c
 fish/tilde.c
index a975f69..a777b22 100755 (executable)
@@ -23,12 +23,7 @@ set -e
 
 rm -f test1.img
 
-../fish/guestfish <<EOF
-sparse test1.img 10M
-launch
-part-disk /dev/sda mbr
-mkfs ext2 /dev/sda1
-mount-options "" /dev/sda1 /
+../fish/guestfish -N fs -m /dev/sda1 <<EOF
 ll /../dev/console
 ll /../dev/full
 ll /../dev/mapper/
index 1b19cdf..a1a84c6 100755 (executable)
@@ -28,12 +28,7 @@ set -e
 
 rm -f test1.img
 
-../fish/guestfish <<EOF
-sparse test1.img 10M
-run
-part-disk /dev/sda mbr
-mkfs ext2 /dev/sda1
-mount-options "" /dev/sda1 /
+../fish/guestfish -N fs -m /dev/sda1 <<EOF
 mkdir /dev
 -command /ignore-this-error
 unmount-all
index 85cfb1e..228b498 100755 (executable)
@@ -49,13 +49,7 @@ get-memsize
 -set-memsize 123L
 EOF
 
-../fish/guestfish >> test.out 2>> test.err <<EOF
-alloc test1.img 10M
-run
-part-disk /dev/sda mbr
-mkfs ext2 /dev/sda1
-mount /dev/sda1 /
-
+../fish/guestfish -N fs -m /dev/sda1 >> test.out 2>> test.err <<EOF
 touch /test
 
 # truncate-size takes an Int64 argument
index 639dd7c..7453ac7 100755 (executable)
@@ -24,9 +24,7 @@ set -e
 
 rm -f test1.img
 
-../fish/guestfish <<EOF
-alloc test1.img 10M
-run
+../fish/guestfish -N disk <<EOF
 -upload $srcdir/rhbz576879.sh /test.sh
 # Shouldn't lose synchronization, so next command should work:
 ping-daemon
index 842e41f..d3a30a3 100755 (executable)
@@ -32,7 +32,7 @@ export LIBGUESTFS_DEBUG=1
 
 for i in $(seq 1 $n); do
   echo Test boot $i of $n ...
-  ../fish/guestfish sparse test1.img 500M : run
+  ../fish/guestfish -N disk </dev/null
 done
 
 rm test1.img
index 4962c25..8204530 100755 (executable)
 
 set -e
 
-rm -f test.img
-
-../fish/guestfish <<'EOF'
-alloc test.img 10M
-run
-
-part-disk /dev/sda mbr
-mkfs ext2 /dev/sda1
-mount-options "" /dev/sda1 /
+rm -f test1.img
 
+../fish/guestfish -N fs -m /dev/sda1 <<'EOF'
 # Upload image, daemon should cancel because the image is too large
 # to upload into itself.
 echo "Expect: write: /test: No space left on device"
@@ -40,4 +33,4 @@ echo "Expect: write: /test: No space left on device"
 ping-daemon
 EOF
 
-rm -f test.img
+rm -f test1.img
index 14b4688..3280975 100755 (executable)
 
 set -e
 
-rm -f test.img
-
-../fish/guestfish <<'EOF'
-alloc test.img 10M
-run
+rm -f test1.img
 
+../fish/guestfish -N disk <<'EOF'
 # Kill the subprocess.
 kill-subprocess
 
@@ -38,4 +35,4 @@ run
 ping-daemon
 EOF
 
-rm -f test.img
+rm -f test1.img
index 92a4c68..84e19b0 100755 (executable)
 
 set -e
 
-rm -f test.pid test.img
-
-../fish/guestfish <<'EOF'
-alloc test.img 10M
-run
+rm -f test.pid test1.img
 
+../fish/guestfish -N disk <<'EOF'
 # Kill the subprocess after a short wait.
 pid | cat > test.pid
 ! sleep 2 ; kill $(cat test.pid) &
@@ -38,4 +35,4 @@ run
 ping-daemon
 EOF
 
-rm -f test.pid test.img
+rm -f test.pid test1.img
index 5932fef..85aef69 100755 (executable)
 
 set -e
 
-rm -f test.pid test.img
-
-../fish/guestfish <<'EOF'
-alloc test.img 10M
-run
+rm -f test.pid test1.img
 
+../fish/guestfish -N disk <<'EOF'
 # Kill subprocess.
 pid | cat > test.pid
 ! kill $(cat test.pid) ; sleep 2
@@ -39,4 +36,4 @@ run
 ping-daemon
 EOF
 
-rm -f test.pid test.img
+rm -f test.pid test1.img