[Libguestfs] [PATCH INCOMPLETE] Rewrite virt-make-fs in C (originally Perl).

Richard W.M. Jones rjones at redhat.com
Mon Jan 27 13:29:54 UTC 2014


It should be very compatible with the Perl version.
---
 .gitignore                   |   3 +
 Makefile.am                  |   2 +-
 configure.ac                 |   1 +
 make-fs/Makefile.am          |  82 +++++
 make-fs/make-fs.c            | 736 +++++++++++++++++++++++++++++++++++++++++++
 make-fs/test-virt-make-fs.sh |  96 ++++++
 make-fs/virt-make-fs.pod     | 252 +++++++++++++++
 po/POTFILES                  |   1 +
 po/POTFILES-pl               |   1 -
 run.in                       |   2 +-
 src/guestfs.pod              |   4 +
 tools/Makefile.am            |   2 -
 tools/test-virt-make-fs.sh   |  94 ------
 tools/virt-make-fs           | 664 --------------------------------------
 14 files changed, 1177 insertions(+), 763 deletions(-)
 create mode 100644 make-fs/Makefile.am
 create mode 100644 make-fs/make-fs.c
 create mode 100755 make-fs/test-virt-make-fs.sh
 create mode 100755 make-fs/virt-make-fs.pod
 delete mode 100755 tools/test-virt-make-fs.sh
 delete mode 100755 tools/virt-make-fs

diff --git a/.gitignore b/.gitignore
index f8e6c71..55012fd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -282,6 +282,9 @@ Makefile.in
 /m4/ltsugar.m4
 /m4/ltversion.m4
 /maint.mk
+/make-fs/stamp-virt-make-fs.pod
+/make-fs/virt-make-fs
+/make-fs/virt-make-fs.1
 /missing
 /mllib/.depend
 /mllib/common_gettext.ml
diff --git a/Makefile.am b/Makefile.am
index e39d11f..2fc4f19 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -73,7 +73,7 @@ SUBDIRS += test-tool
 SUBDIRS += fish
 
 # virt-tools in C.
-SUBDIRS += align cat diff df edit format inspector rescue
+SUBDIRS += align cat diff df edit format inspector make-fs rescue
 
 # bash-completion
 SUBDIRS += bash
diff --git a/configure.ac b/configure.ac
index c07462e..ea54c12 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1714,6 +1714,7 @@ AC_CONFIG_FILES([Makefile
                  java/examples/Makefile
                  lua/Makefile
                  lua/examples/Makefile
+                 make-fs/Makefile
                  mllib/Makefile
                  mllib/config.ml
                  ocaml/META
diff --git a/make-fs/Makefile.am b/make-fs/Makefile.am
new file mode 100644
index 0000000..d318468
--- /dev/null
+++ b/make-fs/Makefile.am
@@ -0,0 +1,82 @@
+# libguestfs virt-diff
+# Copyright (C) 2010-2014 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 $(top_srcdir)/subdir-rules.mk
+
+EXTRA_DIST = \
+	test-virt-make-fs.sh \
+	virt-make-fs.pod
+
+CLEANFILES = stamp-virt-make-fs.pod
+
+bin_PROGRAMS = virt-make-fs
+
+SHARED_SOURCE_FILES = \
+	../fish/options.h \
+	../fish/options.c \
+	../fish/domain.c \
+	../fish/uri.c
+
+virt_make_fs_SOURCES = \
+	$(SHARED_SOURCE_FILES) \
+	make-fs.c
+
+virt_make_fs_CPPFLAGS = \
+	-DGUESTFS_WARN_DEPRECATED=1 \
+	-DLOCALEBASEDIR=\""$(datadir)/locale"\" \
+	-I$(top_srcdir)/src -I$(top_builddir)/src \
+	-I$(top_srcdir)/fish \
+	-I$(srcdir)/../gnulib/lib -I../gnulib/lib
+
+virt_make_fs_CFLAGS = \
+	$(WARN_CFLAGS) $(WERROR_CFLAGS) \
+	$(GPROF_CFLAGS) $(GCOV_CFLAGS) \
+	$(LIBXML2_CFLAGS)
+
+virt_make_fs_LDADD = \
+	$(top_builddir)/src/libutils.la \
+	$(top_builddir)/src/libguestfs.la \
+	$(LIBXML2_LIBS) \
+	../gnulib/lib/libgnu.la
+
+# Manual pages and HTML files for the website.
+man_MANS = virt-make-fs.1
+
+noinst_DATA = \
+	$(top_builddir)/html/virt-make-fs.1.html
+
+virt-make-fs.1 $(top_builddir)/html/virt-make-fs.1.html: stamp-virt-make-fs.pod
+
+stamp-virt-make-fs.pod: virt-make-fs.pod
+	$(PODWRAPPER) \
+	  --man virt-make-fs.1 \
+	  --html $(top_builddir)/html/virt-make-fs.1.html \
+	  --license GPLv2+ \
+	  $<
+	touch $@
+
+# Tests.
+
+TESTS_ENVIRONMENT = $(top_builddir)/run --test
+
+if ENABLE_APPLIANCE
+TESTS = \
+	test-virt-make-fs.sh
+endif ENABLE_APPLIANCE
+
+check-valgrind:
+	$(MAKE) VG="$(top_builddir)/run @VG@" check
diff --git a/make-fs/make-fs.c b/make-fs/make-fs.c
new file mode 100644
index 0000000..22b770e
--- /dev/null
+++ b/make-fs/make-fs.c
@@ -0,0 +1,736 @@
+/* virt-make-fs
+ * Copyright (C) 2010-2014 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 <string.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <locale.h>
+#include <assert.h>
+#include <libintl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include "guestfs.h"
+#include "guestfs-internal-frontend.h"
+
+#include "xstrtol.h"
+
+#include "options.h"
+
+guestfs_h *g;
+const char *libvirt_uri;
+int live;
+int read_only;
+int verbose;
+
+static const char *format = "raw", *label = NULL,
+  *partition = NULL, *size_str = NULL, *type = "ext2";
+
+enum { HELP_OPTION = CHAR_MAX + 1 };
+static const char *options = "F:s:t:Vvx";
+static const struct option long_options[] = {
+  { "debug", 0, 0, 'v' }, /* for compat with Perl tool */
+  { "floppy", 0, 0, 0 },
+  { "format", 0, 0, 'F' },
+  { "help", 0, 0, HELP_OPTION },
+  { "label", 1, 0, 0 },
+  { "long-options", 0, 0, 0 },
+  { "partition", 0, 0, 0 },
+  { "size", 1, 0, 's' },
+  { "type", 1, 0, 't' },
+  { "verbose", 0, 0, 'v' },
+  { "version", 0, 0, 'V' },
+  { 0, 0, 0, 0 }
+};
+
+static void __attribute__((noreturn))
+usage (int status)
+{
+  if (status != EXIT_SUCCESS)
+    fprintf (stderr, _("Try `%s --help' for more information.\n"),
+             program_name);
+  else {
+    fprintf (stdout,
+           _("%s: make a filesystem from a tar archive or files\n"
+             "Copyright (C) 2010-2014 Red Hat Inc.\n"
+             "Usage:\n"
+             "  %s [--options] input.tar output.img\n"
+             "  %s [--options] input.tar.gz output.img\n"
+             "  %s [--options] directory output.img\n"
+             "Options:\n"
+             "  --floppy               Make a virtual floppy disk\n"
+             "  --format=raw|qcow2|..  Set output format\n"
+             "  --help                 Display brief help\n"
+             "  --label=label          Filesystem label\n"
+             "  --partition=mbr|gpt|.. Set partition type\n"
+             "  --size=size|+size      Set size of output disk\n"
+             "  --type=ext4|..         Set filesystem type\n"
+             "  -v|--verbose           Verbose messages\n"
+             "  -V|--version           Display version and exit\n"
+             "  -x                     Trace libguestfs API calls\n"
+             "For more information, see the manpage %s(1).\n"),
+             program_name, program_name, program_name, program_name,
+             program_name);
+  }
+  exit (status);
+}
+
+static int do_make_fs (const char *input, const char *output_str);
+
+int
+main (int argc, char *argv[])
+{
+  int c;
+  int option_index;
+
+  setlocale (LC_ALL, "");
+  bindtextdomain (PACKAGE, LOCALEBASEDIR);
+  textdomain (PACKAGE);
+
+  g = guestfs_create ();
+  if (g == NULL) {
+    fprintf (stderr, _("guestfs_create: failed to create handle\n"));
+    exit (EXIT_FAILURE);
+  }
+
+  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 (STREQ (long_options[option_index].name, "long-options")) {
+        display_long_options (long_options);
+      }
+      else if (STREQ (long_options[option_index].name, "floppy")) {
+        size_str = "1440K";
+        partition = "mbr";
+        type = "vfat";
+      }
+      else if (STREQ (long_options[option_index].name, "label")) {
+        label = optarg;
+      } else {
+        fprintf (stderr, _("%s: unknown long option: %s (%d)\n"),
+                 program_name, long_options[option_index].name, option_index);
+        exit (EXIT_FAILURE);
+      }
+      break;
+
+    case 'F':
+      format = optarg;
+      break;
+
+    case 's':
+      size_str = optarg;
+      break;
+
+    case 't':
+      type = optarg;
+      break;
+
+    case 'v':
+      OPTION_v;
+      break;
+
+    case 'V':
+      OPTION_V;
+      break;
+
+    case 'x':
+      OPTION_x;
+      break;
+
+    case HELP_OPTION:
+      usage (EXIT_SUCCESS);
+
+    default:
+      usage (EXIT_FAILURE);
+    }
+  }
+
+  if (optind + 2 != argc) {
+    fprintf (stderr, _("%s: missing input and output arguments on the command line\n"),
+             program_name);
+    usage (EXIT_FAILURE);
+  }
+
+  if (do_make_fs (argv[optind], argv[optind+1]) == -1)
+    exit (EXIT_FAILURE);
+
+  exit (EXIT_SUCCESS);
+}
+
+static int
+check_ntfs_available (void)
+{
+  const char *ntfs_features[] = { "ntfs3g", "ntfsprogs", NULL };
+
+  if (STREQ (type, "ntfs") &&
+      guestfs_feature_available (g, (char **) ntfs_features) == 0) {
+    fprintf (stderr, _("%s: NTFS support was disabled when libguestfs was compiled\n"),
+             program_name);
+    return -1;
+  }
+
+  return 0;
+}
+
+/* Execute a command, sending output to a file. */
+static int
+exec_command (char **argv, const char *file, int stderr_to_file)
+{
+  pid_t pid;
+  int status, fd;
+  FILE *fp;
+  char line[256];
+
+  pid = fork ();
+  if (pid == -1) {
+    perror ("fork");
+    return -1;
+  }
+  if (pid > 0) {
+    if (waitpid (pid, &status, 0) == -1) {
+      perror ("waitpid");
+      return -1;
+    }
+    if (!WIFEXITED (status) || WEXITSTATUS (status) != 0) {
+      /* If the command failed, dump out the contents of tmpfile which
+       * contains the exact error messages from qemu-img.
+       */
+      fprintf (stderr, _("%s: %s command failed\n"), program_name, argv[0]);
+
+      if (stderr_to_file) {
+        fp = fopen (tmpfile, "r");
+        if (fp != NULL) {
+          while (fgets (line, sizeof line, fp) != NULL)
+            fprintf (stderr, "%s", line);
+          fclose (fp);
+        }
+      }
+
+      return -1;
+    }
+    return 0;
+  }
+
+  /* Child process. */
+  fd = open (tmpfile, O_WRONLY|O_NOCTTY);
+  if (fd == -1) {
+    perror (tmpfile);
+    _exit (EXIT_FAILURE);
+  }
+  dup2 (fd, 1);
+  if (stderr_to_file)
+    dup2 (fd, 2);
+  close (fd);
+
+  execvp (argv[0], argv);
+  perror ("execvp");
+  _exit (EXIT_FAILURE);
+}
+
+/* Execute a command in the background, sending output to a pipe. */
+static int
+bg_command (char **argv, char **pipef)
+{
+  pid_t pid;
+  int status, fd;
+  FILE *fp;
+  char line[256];
+
+  pid = fork ();
+  if (pid == -1) {
+    perror ("fork");
+    return -1;
+  }
+  if (pid > 0) {
+    if (waitpid (pid, &status, 0) == -1) {
+      perror ("waitpid");
+      return -1;
+    }
+    if (!WIFEXITED (status) || WEXITSTATUS (status) != 0) {
+      /* If the command failed, dump out the contents of tmpfile which
+       * contains the exact error messages from qemu-img.
+       */
+      fprintf (stderr, _("%s: %s command failed\n"), program_name, argv[0]);
+
+      if (stderr_to_file) {
+        fp = fopen (tmpfile, "r");
+        if (fp != NULL) {
+          while (fgets (line, sizeof line, fp) != NULL)
+            fprintf (stderr, "%s", line);
+          fclose (fp);
+        }
+      }
+
+      return -1;
+    }
+    return 0;
+  }
+
+  /* Child process. */
+  fd = open (tmpfile, O_WRONLY|O_NOCTTY);
+  if (fd == -1) {
+    perror (tmpfile);
+    _exit (EXIT_FAILURE);
+  }
+  dup2 (fd, 1);
+  if (stderr_to_file)
+    dup2 (fd, 2);
+  close (fd);
+
+  execvp (argv[0], argv);
+  perror ("execvp");
+  _exit (EXIT_FAILURE);
+}
+
+/* Estimate the size of the input.  This returns the estimated size
+ * (in bytes) of the input.  It also sets ifmt to the format of the
+ * input, either the string "directory" if the input is a directory,
+ * or the output of the "file" command on the input.
+ *
+ * Estimation is a Hard Problem.  Some factors which make it hard:
+ *
+ *   - Superblocks, block free bitmaps, FAT and other fixed overhead
+ *   - Indirect blocks (ext2, ext3), and extents
+ *   - Journal size
+ *   - Internal fragmentation of files
+ *
+ * What we could also do is try shrinking the filesystem after
+ * creating and populating it, but that is complex given partitions.
+ */
+static int
+estimate_input (const char *input, uint64_t *estimate_rtn, char **ifmt_rtn)
+{
+  struct stat statbuf;
+  const char *argv[6];
+  CLEANUP_UNLINK_FREE char *tmpfile = NULL;
+  FILE *fp;
+  char line[256];
+
+  if (asprintf (&tmpfile, "/tmp/makefsXXXXXX") == -1) {
+    perror ("asprintf");
+    return -1;
+  }
+  if (mkstemp (tmpfile) == -1) {
+    perror (tmpfile);
+    return -1;
+  }
+
+  if (stat (input, &statbuf) == -1) {
+    perror (input);
+    return -1;
+  }
+  if (S_ISDIR (statbuf.st_mode)) {
+    *ifmt = strdup ("directory");
+    if (*ifmt == NULL) {
+      perror ("strdup");
+      return -1;
+    }
+
+    argv[0] = "du";
+    argv[1] = "--apparent-size";
+    argv[2] = "-b";
+    argv[3] = "-s";
+    argv[4] = input;
+    argv[5] = NULL;
+
+    if (exec_command ((char **) argv, tmpfile, 0) == -1)
+      return -1;
+
+    fp = fopen (tmpfile, "r");
+    if (fp == NULL) {
+      perror (tmpfile);
+      return -1;
+    }
+    if (fgets (line, sizeof line, fp) == NULL) {
+      perror ("fgets");
+      return -1;
+    }
+    fclose (fp);
+
+    if (sscanf (line, "%" SCNu64, estimate_rtn) != 1) {
+      fprintf (stderr, _("%s: cannot parse the output of 'du' command: %s\n"),
+               program_name, line);
+      return -1;
+    }
+  }
+  else {
+    argv[0] = "file";
+    argv[1] = "-bsLz";
+    argv[2] = input;
+    argv[3] = NULL;
+
+    if (exec_command ((char **) argv, tmpfile, 0) == -1)
+      return -1;
+
+    fp = fopen (tmpfile, "r");
+    if (fp == NULL) {
+      perror (tmpfile);
+      return -1;
+    }
+    if (fgets (line, sizeof line, fp) == NULL) {
+      perror ("fgets");
+      return -1;
+    }
+    fclose (fp);
+
+    *ifmt = strdup (line);
+    if (*ifmt == NULL) {
+      perror ("strdup");
+      return -1;
+    }
+
+    if (strstr (line, "tar archive") == NULL) {
+      fprintf (stderr, _("%s: %s: input is not a directory, tar archive or compressed tar achive\n"),
+               program_name, input);
+      return -1;
+    }
+
+    if (strstr (line, "compress")) {
+      if (strstr (line, "compress'd")) {
+        
+
+
+
+}
+
+/* Prepare the input source.  If the input is a regular tar file, this
+ * just sets ifile = input.  However normally the input will be either
+ * a directory or a compressed tarball.  In that case we set up an
+ * external command to do the tar/uncompression to a temporary pipe,
+ * and set ifile to the name of the pipe.
+ */
+static int
+prepare_input (const char *input, const char *ifmt, char **ifile_rtn,
+               int *ifile_delete_on_exit)
+{
+  abort (); /* XXX */
+}
+
+/* Adapted from fish/alloc.c */
+static int
+parse_size (const char *str, uint64_t estimate, uint64_t *size_rtn)
+{
+  unsigned long long size;
+  strtol_error xerr;
+  int plus = 0;
+
+  assert (str);
+
+  if (str[0] == '+') {
+    plus = 1;
+    str++;
+  }
+
+  xerr = xstrtoull (str, NULL, 0, &size, "0kKMGTPEZY");
+  if (xerr != LONGINT_OK) {
+    fprintf (stderr,
+             _("%s: %s: invalid size parameter '%s' (%s returned %d)\n"),
+             program_name, "parse_size", str, "xstrtoull", xerr);
+    return -1;
+  }
+
+  if (plus)
+    *size_rtn = estimate + size;
+  else
+    *size_rtn = size;
+
+  return 0;
+}
+
+/* Run qemu-img to create the output disk with the correct format and
+ * size.  Capture any output temporarily so we can display it in case
+ * qemu-img fails.
+ */
+static int
+create_output_disk (const char *output, uint64_t size)
+{
+  CLEANUP_UNLINK_FREE char *tmpfile = NULL;
+  CLEANUP_FREE char *cmd = NULL;
+  const char *argv[9];
+  char size_str[64];
+
+  snprintf (size_str, sizeof size_str, "%" PRIu64, size);
+
+  if (asprintf (&tmpfile, "/tmp/makefsXXXXXX") == -1) {
+    perror ("asprintf");
+    return -1;
+  }
+  if (mkstemp (tmpfile) == -1) {
+    perror (tmpfile);
+    return -1;
+  }
+
+  assert (format);
+
+  argv[0] = "qemu-img";
+  argv[1] = "create";
+  argv[2] = "-f";
+  argv[3] = format;
+  if (STREQ (format, "qcow2")) {
+    argv[4] = "-o";
+    argv[5] = "preallocation=metadata";
+    argv[6] = output;
+    argv[7] = size_str;
+    argv[8] = NULL;
+  }
+  else {
+    argv[4] = output;
+    argv[5] = size_str;
+    argv[6] = NULL;
+  }
+
+  if (exec_command ((char **) argv, tmpfile, 1) == -1)
+    return -1;
+
+  return 0;
+}
+
+static int
+do_make_fs (const char *input, const char *output_str)
+{
+  const char *dev, *options;
+  CLEANUP_UNLINK_FREE char *output = NULL;
+  uint64_t estimate, size;
+  CLEANUP_FREE char *ifmt = NULL;
+  CLEANUP_FREE char *ifile = NULL;
+  int ifile_delete_on_exit, r;
+
+  /* Use of CLEANUP_UNLINK_FREE *output ensures the output file is
+   * deleted unless we successfully reach the end of this function.
+   */
+  output = strdup (output_str);
+  if (output == NULL) {
+    perror ("strdup");
+    return -1;
+  }
+
+  /* Input.  What is it?  Estimate how much space it will need. */
+  if (estimate_input (input, &estimate, &ifmt) == -1)
+    return -1;
+
+  if (verbose) {
+    fprintf (stderr, "input format = %s\n", ifmt);
+    fprintf (stderr, "estimate = %" PRIu64 " bytes "
+             "(%" PRIu64 " 1K blocks, %" PRIu64 " 4K blocks)\n",
+             estimate, estimate / 1024, estimate / 4096);
+  }
+
+  estimate += 256 * 1024;       /* For superblocks &c. */
+
+  if (STRPREFIX (type, "ext") && type[3] >= '3') {
+    /* For ext3+, add some more for the journal. */
+    estimate += 1024 * 1024;
+  }
+
+  else if (STREQ (type, "ntfs")) {
+    estimate += 4 * 1024 * 1024; /* NTFS journal. */
+  }
+
+  else if (STREQ (type, "btrfs")) {
+    /* For BTRFS, the minimum metadata allocation is 256MB, with data
+     * additional to that.  Note that we disable data and metadata
+     * duplication below.
+     */
+    estimate += 256 * 1024 * 1024;
+  }
+
+  /* Add 10%, see above. */
+  estimate *= 1.10;
+
+  /* Calculate the output size. */
+  if (size_str == NULL)
+    size = estimate;
+  else
+    if (parse_size (size_str, estimate, &size) == -1)
+      return -1;
+
+  /* Create the output disk. */
+  if (create_output_disk (output, size) == -1)
+    return -1;
+
+  if (guestfs_add_drive_opts (g, output,
+                              GUESTFS_ADD_DRIVE_OPTS_FORMAT, format,
+                              -1) == -1)
+    return -1;
+
+  if (guestfs_launch (g) == -1)
+    return -1;
+
+  if (check_ntfs_available () == -1)
+    return -1;
+
+  /* Partition the disk. */
+  dev = "/dev/sda";
+  if (partition) {
+    int mbr_id = 0;
+
+    if (STREQ (partition, ""))
+      partition = "mbr";
+
+    if (guestfs_part_disk (g, dev, partition) == -1)
+      exit (EXIT_FAILURE);
+
+    dev = "/dev/sda1";
+
+    /* Set the partition type byte if it's MBR and the filesystem type
+     * is one that we know about.
+     */
+    if (STREQ (partition, "mbr") || STREQ (partition, "msdos")) {
+      if (STREQ (type, "msdos"))
+        /* According to Wikipedia.  However I have not actually tried this. */
+        mbr_id = 0x1;
+      else if (STREQ (type, "vfat") || STREQ (type, "fat"))
+        mbr_id = 0xb;
+      else if (STREQ (type, "ntfs"))
+        mbr_id = 0x7;
+      else if (STRPREFIX (type, "ext"))
+        mbr_id = 0x83;
+      else if (STREQ (type, "minix"))
+        mbr_id = 0x81;
+    }
+    if (mbr_id != 0) {
+      if (guestfs_part_set_mbr_id (g, "/dev/sda", 1, mbr_id) == -1)
+        return -1;
+    }
+  }
+
+  if (verbose)
+    fprintf (stderr, "creating %s filesystem on %s ...\n", type, dev);
+
+  /* Create the filesystem. */
+  if (STRNEQ (type, "btrfs")) {
+    int r;
+
+    guestfs_push_error_handler (g, NULL, NULL);
+    r = guestfs_mkfs (g, type, dev);
+    guestfs_pop_error_handler (g);
+
+    if (r == -1) {
+      /* Provide more guidance in the error message (RHBZ#823883). */
+      fprintf (stderr, "%s: 'mkfs' (create filesystem) operation failed.\n",
+               program_name);
+      if (STREQ (type, "fat"))
+        fprintf (stderr, "Instead of 'fat', try 'vfat' (long filenames) or 'msdos' (short filenames).\n");
+      else
+        fprintf (stderr, "Is '%s' a correct filesystem type?\n", type);
+
+      return -1;
+    }
+  }
+  else {
+    const char *devs[] = { dev, NULL };
+
+    if (guestfs_mkfs_btrfs (g, (char **) devs,
+                            GUESTFS_MKFS_BTRFS_DATATYPE, "single",
+                            GUESTFS_MKFS_BTRFS_METADATA, "single",
+                            -1) == -1)
+      return -1;
+  }
+
+  /* Set label. */
+  if (label) {
+    if (guestfs_set_label (g, dev, label) == -1)
+      return -1;
+  }
+
+  /* Mount it. */
+
+  /* For vfat, add the utf8 mount option because we want to be able to
+   * encode any non-ASCII characters into UCS2 which is what modern
+   * vfat uses on disk (RHBZ#823885).
+   */
+  if (STREQ (type, "vfat"))
+    options = "utf8";
+  else
+    options = "";
+
+  if (guestfs_mount_options (g, options, dev, "/") == -1)
+    return -1;
+
+  /* For debugging, print statvfs before and after doing the tar-in. */
+  if (verbose) {
+    CLEANUP_FREE_STATVFS struct guestfs_statvfs *stats =
+      guestfs_statvfs (g, "/");
+    fprintf (stderr, "before uploading:\n");
+    fprintf (stderr, "  bsize = %" PRIi64 "\n", stats->bsize);
+    fprintf (stderr, "  frsize = %" PRIi64 "\n", stats->frsize);
+    fprintf (stderr, "  blocks = %" PRIi64 "\n", stats->blocks);
+    fprintf (stderr, "  bfree = %" PRIi64 "\n", stats->bfree);
+    fprintf (stderr, "  bavail = %" PRIi64 "\n", stats->bavail);
+    fprintf (stderr, "  files = %" PRIi64 "\n", stats->files);
+    fprintf (stderr, "  ffree = %" PRIi64 "\n", stats->ffree);
+    fprintf (stderr, "  favail = %" PRIi64 "\n", stats->favail);
+    fprintf (stderr, "  fsid = %" PRIi64 "\n", stats->fsid);
+    fprintf (stderr, "  flag = %" PRIi64 "\n", stats->flag);
+    fprintf (stderr, "  namemax = %" PRIi64 "\n", stats->namemax);
+  }
+
+  /* Prepare the input to be copied in. */
+  if (prepare_input (input, ifmt, &ifile, &ifile_delete_on_exit) == -1)
+    return -1;
+
+  if (verbose)
+    fprintf (stderr, "uploading from %s to / ...\n", ifile);
+  r = guestfs_tar_in (g, ifile, "/");
+  if (ifile_delete_on_exit)
+    unlink (ifile);
+  if (r == -1)
+    return -1;
+
+  if (verbose) {
+    CLEANUP_FREE_STATVFS struct guestfs_statvfs *stats =
+      guestfs_statvfs (g, "/");
+    fprintf (stderr, "after uploading:\n");
+    fprintf (stderr, "  bsize = %" PRIi64 "\n", stats->bsize);
+    fprintf (stderr, "  frsize = %" PRIi64 "\n", stats->frsize);
+    fprintf (stderr, "  blocks = %" PRIi64 "\n", stats->blocks);
+    fprintf (stderr, "  bfree = %" PRIi64 "\n", stats->bfree);
+    fprintf (stderr, "  bavail = %" PRIi64 "\n", stats->bavail);
+    fprintf (stderr, "  files = %" PRIi64 "\n", stats->files);
+    fprintf (stderr, "  ffree = %" PRIi64 "\n", stats->ffree);
+    fprintf (stderr, "  favail = %" PRIi64 "\n", stats->favail);
+    fprintf (stderr, "  fsid = %" PRIi64 "\n", stats->fsid);
+    fprintf (stderr, "  flag = %" PRIi64 "\n", stats->flag);
+    fprintf (stderr, "  namemax = %" PRIi64 "\n", stats->namemax);
+  }
+
+  if (verbose)
+    fprintf (stderr, "finishing off\n");
+  if (guestfs_shutdown (g) == -1)
+    return -1;
+  guestfs_close (g);
+
+  /* Output was created OK, so save it from being deleted by
+   * CLEANUP_UNLINK_FREE.
+   */
+  free (output);
+  output = NULL;
+
+  return 0;
+}
diff --git a/make-fs/test-virt-make-fs.sh b/make-fs/test-virt-make-fs.sh
new file mode 100755
index 0000000..f276e4c
--- /dev/null
+++ b/make-fs/test-virt-make-fs.sh
@@ -0,0 +1,96 @@
+#!/bin/bash -
+# libguestfs
+# Copyright (C) 2010-2014 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.
+
+# Engage in some montecarlo testing of virt-make-fs.  This test is
+# copied from the original Perl tool virt-make-fs, on the basis that
+# the new C tool should be able to pass the same tests.
+
+export LANG=C
+set -e
+
+# Check which filesystems are supported by the appliance.
+eval $(
+perl -MSys::Guestfs -e '
+  $g = Sys::Guestfs->new();
+  $g->add_drive ("/dev/null");
+  $g->launch ();
+  $g->feature_available (["ntfs3g"]) and print "ntfs3g_available=yes\n";
+  $g->feature_available (["ntfsprogs"]) and print "ntfsprogs_available=yes\n";
+  $g->feature_available (["btrfs"]) and print "btrfs_available=yes\n";
+')
+
+# Allow btrfs to be disabled when btrfs is broken (eg. RHBZ#863978).
+if [ -n "$SKIP_TEST_VIRT_MAKE_FS_BTRFS" ]; then
+    btrfs_available=
+fi
+
+# UML backend doesn't support qcow2.
+if [ "$(../fish/guestfish get-backend)" != "uml" ]; then
+    qcow2_supported=yes
+fi
+
+declare -a choices
+
+# Return a random element from the array 'choices'.
+function random_choice
+{
+    echo "${choices[$((RANDOM % ${#choices[*]}))]}"
+}
+
+# Can't test vfat because we cannot create a tar archive
+# where files are owned by UID:GID 0:0.  As a result, tar
+# in the appliance fails when trying to change the UID of
+# the files to some non-zero value (not supported by FAT).
+choices=(--type=ext2 --type=ext3 --type=ext4)
+if [ "$ntfs3g_available" = "yes" -a "$ntfsprogs_available" = "yes" ]; then
+    choices[${#choices[*]}]="--type=ntfs"
+fi
+if [ "$btrfs_available" = "yes" ]; then
+    choices[${#choices[*]}]="--type=btrfs"
+fi
+type=`random_choice`
+
+if [ "$qcow2_supported" = "yes" ]; then
+    choices=(--format=raw --format=qcow2)
+    format=`random_choice`
+else
+    format="--format=raw"
+fi
+
+choices=(--partition --partition=gpt)
+partition=`random_choice`
+
+choices=("" --size=+1M)
+size=`random_choice`
+
+if [ -n "$LIBGUESTFS_DEBUG" ]; then debug=--debug; fi
+
+params="$type $format $partition $size $debug"
+echo "test-virt-make-fs: parameters: $params"
+
+rm -f test.file test.tar output.img
+
+tarsize=$((RANDOM & 8191))
+echo "test-virt-make-fs: size of test file: $tarsize KB"
+dd if=/dev/zero of=test.file bs=1024 count=$tarsize
+tar -c -f test.tar test.file
+rm test.file
+
+$srcdir/virt-make-fs $params -- test.tar output.img
+
+rm test.tar output.img
diff --git a/make-fs/virt-make-fs.pod b/make-fs/virt-make-fs.pod
new file mode 100755
index 0000000..5f70668
--- /dev/null
+++ b/make-fs/virt-make-fs.pod
@@ -0,0 +1,252 @@
+=encoding utf8
+
+=head1 NAME
+
+virt-make-fs - Make a filesystem from a tar archive or files
+
+=head1 SYNOPSIS
+
+ virt-make-fs [--options] input.tar output.img
+
+ virt-make-fs [--options] input.tar.gz output.img
+
+ virt-make-fs [--options] directory output.img
+
+=head1 DESCRIPTION
+
+Virt-make-fs is a command line tool for creating a filesystem from a
+tar archive or some files in a directory.  It is similar to tools like
+L<mkisofs(1)>, L<genisoimage(1)> and L<mksquashfs(1)>.  Unlike those
+tools, it can create common filesystem types like ext2/3 or NTFS,
+which can be useful if you want to attach these filesystems to
+existing virtual machines (eg. to import large amounts of read-only
+data to a VM).
+
+To create blank disks, use L<virt-format(1)>.  To create complex
+layouts, use L<guestfish(1)>.
+
+Basic usage is:
+
+ virt-make-fs input output.img
+
+where C<input> is either a directory containing files that you want to
+add, or a tar archive (either uncompressed tar or gzip-compressed
+tar); and C<output.img> is a disk image.  The input type is detected
+automatically.  The output disk image defaults to a raw ext2 sparse
+image unless you specify extra flags (see L</OPTIONS> below).
+
+=head2 FILESYSTEM TYPE
+
+The default filesystem type is C<ext2>.  Just about any filesystem
+type that libguestfs supports can be used (but I<not> read-only
+formats like ISO9660).  Here are some of the more common choices:
+
+=over 4
+
+=item I<ext3>
+
+Note that ext3 filesystems contain a journal, typically 1-32 MB in size.
+If you are not going to use the filesystem in a way that requires the
+journal, then this is just wasted overhead.
+
+=item I<ntfs> or I<vfat>
+
+Useful if exporting data to a Windows guest.
+
+=item I<minix>
+
+Lower overhead than C<ext2>, but certain limitations on filename
+length and total filesystem size.
+
+=back
+
+=head3 EXAMPLE
+
+ virt-make-fs --type=minix input minixfs.img
+
+=head2 TO PARTITION OR NOT TO PARTITION
+
+Optionally virt-make-fs can add a partition table to the output disk.
+
+Adding a partition can make the disk image more compatible with
+certain virtualized operating systems which don't expect to see a
+filesystem directly located on a block device (Linux doesn't care and
+will happily handle both types).
+
+On the other hand, if you have a partition table then the output image
+is no longer a straight filesystem.  For example you cannot run
+L<fsck(8)> directly on a partitioned disk image.  (However libguestfs
+tools such as L<guestfish(1)> and L<virt-resize(1)> can still be
+used).
+
+=head3 EXAMPLE
+
+Add an MBR partition:
+
+ virt-make-fs --partition -- input disk.img
+
+If the output disk image could be terabyte-sized or larger, it's
+better to use an EFI/GPT-compatible partition table:
+
+ virt-make-fs --partition=gpt --size=+4T --format=qcow2 input disk.img
+
+=head2 EXTRA SPACE
+
+Unlike formats such as tar and squashfs, a filesystem does not "just
+fit" the files that it contains, but might have extra space.
+Depending on how you are going to use the output, you might think this
+extra space is wasted and want to minimize it, or you might want to
+leave space so that more files can be added later.  Virt-make-fs
+defaults to minimizing the extra space, but you can use the I<--size>
+flag to leave space in the filesystem if you want it.
+
+An alternative way to leave extra space but not make the output image
+any bigger is to use an alternative disk image format (instead of the
+default "raw" format).  Using I<--format=qcow2> will use the native
+QEmu/KVM qcow2 image format (check your hypervisor supports this
+before using it).  This allows you to choose a large I<--size> but the
+extra space won't actually be allocated in the image until you try to
+store something in it.
+
+Don't forget that you can also use local commands including
+L<resize2fs(8)> and L<virt-resize(1)> to resize existing filesystems,
+or rerun virt-make-fs to build another image from scratch.
+
+=head3 EXAMPLE
+
+ virt-make-fs --format=qcow2 --size=+200M input output.img
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--help>
+
+Display brief help.
+
+=item B<--floppy>
+
+Create a virtual floppy disk.
+
+Currently this preselects the size (1440K), partition type (MBR) and
+filesystem type (VFAT).  In future it may also choose the geometry.
+
+=item B<--size=E<lt>NE<gt>>
+
+=item B<--size=+E<lt>NE<gt>>
+
+=item B<-s E<lt>NE<gt>>
+
+=item B<-s +E<lt>NE<gt>>
+
+Use the I<--size> (or I<-s>) option to choose the size of the output
+image.
+
+If this option is I<not> given, then the output image will be just
+large enough to contain all the files, with not much wasted space.
+
+To choose a fixed size output disk, specify an absolute number
+followed by b/K/M/G/T/P/E to mean bytes, Kilobytes, Megabytes,
+Gigabytes, Terabytes, Petabytes or Exabytes.  This must be large
+enough to contain all the input files, else you will get an error.
+
+To leave extra space, specify C<+> (plus sign) and a number followed
+by b/K/M/G/T/P/E to mean bytes, Kilobytes, Megabytes, Gigabytes,
+Terabytes, Petabytes or Exabytes.  For example: I<--size=+200M> means
+enough space for the input files, and (approximately) an extra 200 MB
+free space.
+
+Note that virt-make-fs estimates free space, and therefore will not
+produce filesystems containing precisely the free space requested.
+(It is much more expensive and time-consuming to produce a filesystem
+which has precisely the desired free space).
+
+=item B<--format=E<lt>fmtE<gt>>
+
+=item B<-F E<lt>fmtE<gt>>
+
+Choose the output disk image format.
+
+The default is C<raw> (raw sparse disk image).
+
+For other choices, see the L<qemu-img(1)> manpage.  The only other
+choice that would really make sense here is C<qcow2>.
+
+=item B<--type=E<lt>fsE<gt>>
+
+=item B<-t E<lt>fsE<gt>>
+
+Choose the output filesystem type.
+
+The default is C<ext2>.
+
+Any filesystem which is supported read-write by libguestfs can be used
+here.
+
+=item B<--label=E<lt>LABELE<gt>>
+
+Set the filesystem label.
+
+=item B<--partition>
+
+=item B<--partition=E<lt>parttypeE<gt>>
+
+If specified, this flag adds an MBR partition table to the output disk
+image.
+
+You can change the partition table type, eg. I<--partition=gpt> for
+large disks.
+
+Note that if you just use a lonesome I<--partition>, the option parser
+might consider the next parameter to be the partition type.  For
+example:
+
+ virt-make-fs --partition input.tar output.img
+
+would cause virt-make-fs to think you wanted to use a partition type
+of C<input.tar> which is completely wrong.  To avoid this, use I<-->
+(a double dash) between options and the input and output arguments:
+
+ virt-make-fs --partition -- input.tar output.img
+
+For MBR, virt-make-fs sets the partition type byte automatically.
+
+=item B<-v>
+
+=item B<--verbose>
+
+Enable debugging information.
+
+=item B<-V>
+
+=item B<--version>
+
+Display version number and exit.
+
+=item B<-x>
+
+Enable libguestfs trace.
+
+=back
+
+=head1 SEE ALSO
+
+L<guestfish(1)>,
+L<virt-format(1)>,
+L<virt-resize(1)>,
+L<virt-tar-in(1)>,
+L<mkisofs(1)>,
+L<genisoimage(1)>,
+L<mksquashfs(1)>,
+L<mke2fs(8)>,
+L<resize2fs(8)>,
+L<guestfs(3)>,
+L<http://libguestfs.org/>.
+
+=head1 AUTHOR
+
+Richard W.M. Jones L<http://people.redhat.com/~rjones/>
+
+=head1 COPYRIGHT
+
+Copyright (C) 2010-2014 Red Hat Inc.
diff --git a/po/POTFILES b/po/POTFILES
index 4df7515..471801a 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -234,6 +234,7 @@ gobject/src/tristate.c
 inspector/inspector.c
 java/com_redhat_et_libguestfs_GuestFS.c
 lua/lua-guestfs.c
+make-fs/make-fs.c
 mllib/crypt-c.c
 mllib/fsync-c.c
 mllib/progress-c.c
diff --git a/po/POTFILES-pl b/po/POTFILES-pl
index b2efffa..9d7665a 100644
--- a/po/POTFILES-pl
+++ b/po/POTFILES-pl
@@ -1,5 +1,4 @@
 tools/virt-list-filesystems
 tools/virt-list-partitions
-tools/virt-make-fs
 tools/virt-tar
 tools/virt-win-reg
diff --git a/run.in b/run.in
index d4b13fe..08c3fbc 100755
--- a/run.in
+++ b/run.in
@@ -74,7 +74,7 @@ fi
 
 # Set the PATH to contain all the libguestfs binaries.  There are a
 # lot of binaries, so a lot of path entries.
-PATH="$b/align:$b/builder:$b/cat:$b/df:$b/diff:$b/edit:$b/erlang:$b/fish:$b/format:$b/fuse:$b/rescue:$b/resize:$b/sparsify:$b/sysprep:$b/test-tool:$b/tools:$PATH"
+PATH="$b/align:$b/builder:$b/cat:$b/df:$b/diff:$b/edit:$b/erlang:$b/fish:$b/format:$b/fuse:$b/make-fs:$b/rescue:$b/resize:$b/sparsify:$b/sysprep:$b/test-tool:$b/tools:$PATH"
 export PATH
 
 # Set LD_LIBRARY_PATH to contain library.
diff --git a/src/guestfs.pod b/src/guestfs.pod
index df6044d..4d95a2f 100644
--- a/src/guestfs.pod
+++ b/src/guestfs.pod
@@ -4335,6 +4335,10 @@ Logo used on the website.  The fish is called Arthur by the way.
 
 M4 macros used by autoconf.
 
+=item C<make-fs>
+
+L<virt-make-fs(1)> command and documentation.
+
 =item C<mllib>
 
 Various libraries and common code used by L<virt-resize(1)> and
diff --git a/tools/Makefile.am b/tools/Makefile.am
index f975a28..e1797f2 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -20,7 +20,6 @@ include $(top_srcdir)/subdir-rules.mk
 tools = \
 	list-filesystems \
 	list-partitions \
-	make-fs \
 	tar \
 	win-reg
 
@@ -62,7 +61,6 @@ TESTS_ENVIRONMENT = $(top_builddir)/run --test
 
 if ENABLE_APPLIANCE
 TESTS = test-virt-list-filesystems.sh \
-	test-virt-make-fs.sh \
 	test-virt-tar.sh
 endif ENABLE_APPLIANCE
 
diff --git a/tools/test-virt-make-fs.sh b/tools/test-virt-make-fs.sh
deleted file mode 100755
index 62881b1..0000000
--- a/tools/test-virt-make-fs.sh
+++ /dev/null
@@ -1,94 +0,0 @@
-#!/bin/bash -
-# libguestfs
-# Copyright (C) 2010-2012 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.
-
-# Engage in some montecarlo testing of virt-make-fs.
-
-export LANG=C
-set -e
-
-# Check which filesystems are supported by the appliance.
-eval $(
-perl -MSys::Guestfs -e '
-  $g = Sys::Guestfs->new();
-  $g->add_drive ("/dev/null");
-  $g->launch ();
-  $g->feature_available (["ntfs3g"]) and print "ntfs3g_available=yes\n";
-  $g->feature_available (["ntfsprogs"]) and print "ntfsprogs_available=yes\n";
-  $g->feature_available (["btrfs"]) and print "btrfs_available=yes\n";
-')
-
-# Allow btrfs to be disabled when btrfs is broken (eg. RHBZ#863978).
-if [ -n "$SKIP_TEST_VIRT_MAKE_FS_BTRFS" ]; then
-    btrfs_available=
-fi
-
-# UML backend doesn't support qcow2.
-if [ "$(../fish/guestfish get-backend)" != "uml" ]; then
-    qcow2_supported=yes
-fi
-
-declare -a choices
-
-# Return a random element from the array 'choices'.
-function random_choice
-{
-    echo "${choices[$((RANDOM % ${#choices[*]}))]}"
-}
-
-# Can't test vfat because we cannot create a tar archive
-# where files are owned by UID:GID 0:0.  As a result, tar
-# in the appliance fails when trying to change the UID of
-# the files to some non-zero value (not supported by FAT).
-choices=(--type=ext2 --type=ext3 --type=ext4)
-if [ "$ntfs3g_available" = "yes" -a "$ntfsprogs_available" = "yes" ]; then
-    choices[${#choices[*]}]="--type=ntfs"
-fi
-if [ "$btrfs_available" = "yes" ]; then
-    choices[${#choices[*]}]="--type=btrfs"
-fi
-type=`random_choice`
-
-if [ "$qcow2_supported" = "yes" ]; then
-    choices=(--format=raw --format=qcow2)
-    format=`random_choice`
-else
-    format="--format=raw"
-fi
-
-choices=(--partition --partition=gpt)
-partition=`random_choice`
-
-choices=("" --size=+1M)
-size=`random_choice`
-
-if [ -n "$LIBGUESTFS_DEBUG" ]; then debug=--debug; fi
-
-params="$type $format $partition $size $debug"
-echo "test-virt-make-fs: parameters: $params"
-
-rm -f test.file test.tar output.img
-
-tarsize=$((RANDOM & 8191))
-echo "test-virt-make-fs: size of test file: $tarsize KB"
-dd if=/dev/zero of=test.file bs=1024 count=$tarsize
-tar -c -f test.tar test.file
-rm test.file
-
-$srcdir/virt-make-fs $params -- test.tar output.img
-
-rm test.tar output.img
diff --git a/tools/virt-make-fs b/tools/virt-make-fs
deleted file mode 100755
index 605d067..0000000
--- a/tools/virt-make-fs
+++ /dev/null
@@ -1,664 +0,0 @@
-#!/usr/bin/perl -w
-# virt-make-fs
-# Copyright (C) 2010-2012 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.
-
-use warnings;
-use strict;
-
-use Sys::Guestfs;
-
-use Pod::Usage;
-use Getopt::Long;
-use File::Temp qw(tempfile tempdir);
-use POSIX qw(mkfifo floor);
-use Data::Dumper;
-use String::ShellQuote qw(shell_quote);
-use Locale::TextDomain 'libguestfs';
-use Fcntl qw(SEEK_SET);
-
-=encoding utf8
-
-=head1 NAME
-
-virt-make-fs - Make a filesystem from a tar archive or files
-
-=head1 SYNOPSIS
-
- virt-make-fs [--options] input.tar output.img
-
- virt-make-fs [--options] input.tar.gz output.img
-
- virt-make-fs [--options] directory output.img
-
-=head1 DESCRIPTION
-
-Virt-make-fs is a command line tool for creating a filesystem from a
-tar archive or some files in a directory.  It is similar to tools like
-L<mkisofs(1)>, L<genisoimage(1)> and L<mksquashfs(1)>.  Unlike those
-tools, it can create common filesystem types like ext2/3 or NTFS,
-which can be useful if you want to attach these filesystems to
-existing virtual machines (eg. to import large amounts of read-only
-data to a VM).
-
-To create blank disks, use L<virt-format(1)>.  To create complex
-layouts, use L<guestfish(1)>.
-
-Basic usage is:
-
- virt-make-fs input output.img
-
-where C<input> is either a directory containing files that you want to
-add, or a tar archive (either uncompressed tar or gzip-compressed
-tar); and C<output.img> is a disk image.  The input type is detected
-automatically.  The output disk image defaults to a raw ext2 sparse
-image unless you specify extra flags (see L</OPTIONS> below).
-
-=head2 FILESYSTEM TYPE
-
-The default filesystem type is C<ext2>.  Just about any filesystem
-type that libguestfs supports can be used (but I<not> read-only
-formats like ISO9660).  Here are some of the more common choices:
-
-=over 4
-
-=item I<ext3>
-
-Note that ext3 filesystems contain a journal, typically 1-32 MB in size.
-If you are not going to use the filesystem in a way that requires the
-journal, then this is just wasted overhead.
-
-=item I<ntfs> or I<vfat>
-
-Useful if exporting data to a Windows guest.
-
-=item I<minix>
-
-Lower overhead than C<ext2>, but certain limitations on filename
-length and total filesystem size.
-
-=back
-
-=head3 EXAMPLE
-
- virt-make-fs --type=minix input minixfs.img
-
-=head2 TO PARTITION OR NOT TO PARTITION
-
-Optionally virt-make-fs can add a partition table to the output disk.
-
-Adding a partition can make the disk image more compatible with
-certain virtualized operating systems which don't expect to see a
-filesystem directly located on a block device (Linux doesn't care and
-will happily handle both types).
-
-On the other hand, if you have a partition table then the output image
-is no longer a straight filesystem.  For example you cannot run
-L<fsck(8)> directly on a partitioned disk image.  (However libguestfs
-tools such as L<guestfish(1)> and L<virt-resize(1)> can still be
-used).
-
-=head3 EXAMPLE
-
-Add an MBR partition:
-
- virt-make-fs --partition -- input disk.img
-
-If the output disk image could be terabyte-sized or larger, it's
-better to use an EFI/GPT-compatible partition table:
-
- virt-make-fs --partition=gpt --size=+4T --format=qcow2 input disk.img
-
-=head2 EXTRA SPACE
-
-Unlike formats such as tar and squashfs, a filesystem does not "just
-fit" the files that it contains, but might have extra space.
-Depending on how you are going to use the output, you might think this
-extra space is wasted and want to minimize it, or you might want to
-leave space so that more files can be added later.  Virt-make-fs
-defaults to minimizing the extra space, but you can use the I<--size>
-flag to leave space in the filesystem if you want it.
-
-An alternative way to leave extra space but not make the output image
-any bigger is to use an alternative disk image format (instead of the
-default "raw" format).  Using I<--format=qcow2> will use the native
-QEmu/KVM qcow2 image format (check your hypervisor supports this
-before using it).  This allows you to choose a large I<--size> but the
-extra space won't actually be allocated in the image until you try to
-store something in it.
-
-Don't forget that you can also use local commands including
-L<resize2fs(8)> and L<virt-resize(1)> to resize existing filesystems,
-or rerun virt-make-fs to build another image from scratch.
-
-=head3 EXAMPLE
-
- virt-make-fs --format=qcow2 --size=+200M input output.img
-
-=head1 OPTIONS
-
-=over 4
-
-=cut
-
-my $help;
-
-=item B<--help>
-
-Display brief help.
-
-=cut
-
-my $version;
-
-=item B<--version>
-
-Display version number and exit.
-
-=cut
-
-my $debug;
-
-=item B<--debug>
-
-Enable debugging information.
-
-=cut
-
-=item B<--floppy>
-
-Create a virtual floppy disk.
-
-Currently this preselects the size (1440K), partition type (MBR) and
-filesystem type (VFAT).  In future it may also choose the geometry.
-
-=cut
-
-my $size;
-
-=item B<--size=E<lt>NE<gt>>
-
-=item B<--size=+E<lt>NE<gt>>
-
-=item B<-s E<lt>NE<gt>>
-
-=item B<-s +E<lt>NE<gt>>
-
-Use the I<--size> (or I<-s>) option to choose the size of the output
-image.
-
-If this option is I<not> given, then the output image will be just
-large enough to contain all the files, with not much wasted space.
-
-To choose a fixed size output disk, specify an absolute number
-followed by b/K/M/G/T/P/E to mean bytes, Kilobytes, Megabytes,
-Gigabytes, Terabytes, Petabytes or Exabytes.  This must be large
-enough to contain all the input files, else you will get an error.
-
-To leave extra space, specify C<+> (plus sign) and a number followed
-by b/K/M/G/T/P/E to mean bytes, Kilobytes, Megabytes, Gigabytes,
-Terabytes, Petabytes or Exabytes.  For example: I<--size=+200M> means
-enough space for the input files, and (approximately) an extra 200 MB
-free space.
-
-Note that virt-make-fs estimates free space, and therefore will not
-produce filesystems containing precisely the free space requested.
-(It is much more expensive and time-consuming to produce a filesystem
-which has precisely the desired free space).
-
-=cut
-
-my $format = "raw";
-
-=item B<--format=E<lt>fmtE<gt>>
-
-=item B<-F E<lt>fmtE<gt>>
-
-Choose the output disk image format.
-
-The default is C<raw> (raw sparse disk image).
-
-For other choices, see the L<qemu-img(1)> manpage.  The only other
-choice that would really make sense here is C<qcow2>.
-
-=cut
-
-my $type = "ext2";
-
-=item B<--type=E<lt>fsE<gt>>
-
-=item B<-t E<lt>fsE<gt>>
-
-Choose the output filesystem type.
-
-The default is C<ext2>.
-
-Any filesystem which is supported read-write by libguestfs can be used
-here.
-
-=cut
-
-my $label;
-
-=item B<--label=E<lt>LABELE<gt>>
-
-Set the filesystem label.
-
-=cut
-
-my $partition;
-
-=item B<--partition>
-
-=item B<--partition=E<lt>parttypeE<gt>>
-
-If specified, this flag adds an MBR partition table to the output disk
-image.
-
-You can change the partition table type, eg. I<--partition=gpt> for
-large disks.
-
-Note that if you just use a lonesome I<--partition>, the Perl option
-parser might consider the next parameter to be the partition type.
-For example:
-
- virt-make-fs --partition input.tar output.img
-
-would cause virt-make-fs to think you wanted to use a partition type
-of C<input.tar> which is completely wrong.  To avoid this, use I<-->
-(a double dash) between options and the input and output arguments:
-
- virt-make-fs --partition -- input.tar output.img
-
-For MBR, virt-make-fs sets the partition type byte automatically.
-
-=back
-
-=cut
-
-GetOptions ("help|?" => \$help,
-            "version" => \$version,
-            "debug" => \$debug,
-            "floppy" => sub {
-                $size = "1440K";
-                $partition = "mbr";
-                $type = "vfat";
-            },
-            "s|size=s" => \$size,
-            "F|format=s" => \$format,
-            "t|type=s" => \$type,
-            "label=s" => \$label,
-            "partition:s" => \$partition,
-    ) or pod2usage (2);
-pod2usage (1) if $help;
-if ($version) {
-    my $g = Sys::Guestfs->new ();
-    my %h = $g->version ();
-    print "$h{major}.$h{minor}.$h{release}$h{extra}\n";
-    exit
-}
-
-die __"virt-make-fs input output\n" if @ARGV != 2;
-
-my $input = $ARGV[0];
-my $output = $ARGV[1];
-
-# Input.  What is it?  Estimate how much space it will need.
-#
-# Estimation is a Hard Problem.  Some factors which make it hard:
-#
-#   - Superblocks, block free bitmaps, FAT and other fixed overhead
-#   - Indirect blocks (ext2, ext3), and extents
-#   - Journal size
-#   - Internal fragmentation of files
-#
-# What we could also do is try shrinking the filesystem after creating
-# and populating it, but that is complex given partitions.
-
-my $estimate;     # Estimated size required (in bytes).
-my $ifmt;         # Input format.
-
-if (-d $input) {
-    $ifmt = "directory";
-
-    my @cmd = ("du", "--apparent-size", "-b", "-s", $input);
-    open PIPE, "-|", @cmd or die "du $input: $!";
-
-    $_ = <PIPE>;
-    if (/^(\d+)/) {
-        $estimate = $1;
-    } else {
-        die __"unexpected output from 'du' command";
-    }
-} else {
-    local $ENV{LANG} = "C";
-    my @cmd = ("file", "-bsLz", $input);
-    open PIPE, "-|", @cmd or die "file $input: $!";
-
-    $ifmt = <PIPE>;
-    chomp $ifmt;
-    close PIPE;
-
-    if ($ifmt !~ /tar archive/) {
-        die __x("{f}: unknown input format: {fmt}\n",
-                f => $input, fmt => $ifmt);
-    }
-
-    if ($ifmt =~ /compress.d/) {
-        if ($ifmt =~ /compress'd/) {
-            @cmd = ("uncompress", "-c", $input);
-        } elsif ($ifmt =~ /gzip compressed/) {
-            @cmd = ("gzip", "-cd", $input);
-        } elsif ($ifmt =~ /bzip2 compressed/) {
-            @cmd = ("bzip2", "-cd", $input);
-        } elsif ($ifmt =~ /xz compressed/) {
-            @cmd = ("xz", "-cd", $input);
-        } else {
-            die __x("{f}: unknown input format: {fmt}\n",
-                    f => $input, fmt => $ifmt);
-        }
-
-        open PIPE, "-|", @cmd or die "uncompress $input: $!";
-        $estimate = 0;
-        $estimate += length while <PIPE>;
-        close PIPE or die "close: $!";
-    } else {
-        # Plain tar file, just get the size directly.  Tar files have
-        # a 512 byte block size (compared with typically 1K or 4K for
-        # filesystems) so this isn't very accurate.
-        $estimate = -s $input;
-    }
-}
-
-if ($debug) {
-    printf STDERR "input format = %s\n", $ifmt;
-    printf STDERR "estimate = %s bytes (%s 1K blocks, %s 4K blocks)\n",
-      $estimate, $estimate / 1024, $estimate / 4096;
-}
-
-$estimate += 256 * 1024;        # For superblocks &c.
-
-if ($type =~ /^ext[3-9]/) {
-    $estimate += 1024 * 1024;   # For ext3/4, add some more for the journal.
-}
-
-if ($type eq "ntfs") {
-    $estimate += 4 * 1024 * 1024; # NTFS journal.
-}
-
-if ($type eq "btrfs") {
-    # For BTRFS, the minimum metadata allocation is 256MB, with data
-    # additional to that.  Note that we disable data and metadata
-    # duplication below.
-    $estimate += 256 * 1024 * 1024;
-}
-
-$estimate *= 1.10;              # Add 10%, see above.
-
-# Calculate the output size.
-
-if (!defined $size) {
-    $size = $estimate;
-} else {
-    if ($size =~ /^\+([.\d]+)([bKMGTPE])$/) {
-        $size = $estimate + sizebytes ($1, $2);
-    } elsif ($size =~ /^([.\d]+)([bKMGTPE])$/) {
-        $size = sizebytes ($1, $2);
-    } else {
-        die __x("virt-make-fs: cannot parse size parameter: {sz}\n",
-                sz => $size);
-    }
-}
-
-$size = int ($size);
-
-# Create the output disk.
-#
-# Use qemu-img so we can control the output format, but capture any
-# output temporarily and only display it if the command fails.
-
-my @options = ();
- at options = ("-o", "preallocation=metadata") if $format eq "qcow2";
-
-my @cmd = ("qemu-img", "create", "-f", $format, @options, $output, $size);
-if ($debug) {
-    print STDERR ("running: ", join (" ", @cmd), "\n");
-}
-
-{
-    my $tmpfh = tempfile ();
-    my ($r, $oldout, $olderr);
-
-    open $oldout, ">&STDOUT" or die __"cannot dup STDOUT";
-    open $olderr, ">&STDERR" or die __"cannot dup STDERR";
-    close STDOUT;
-    close STDERR;
-    open STDOUT, ">&", \$tmpfh or die __"cannot redirect STDOUT";
-    open STDERR, ">&", \$tmpfh or die __"cannot redirect STDERR";
-    $r = system (@cmd);
-    open STDOUT, ">&", $oldout or die __"cannot restore STDOUT";
-    open STDERR, ">&", $olderr or die __"cannot restore STDERR";
-
-    unless ($r == 0) {
-        print STDERR __"qemu-img create: failed to create disk image:\n";
-        seek $tmpfh, 0, SEEK_SET;
-        print STDERR $_ while <$tmpfh>;
-        die "\n";
-    }
-}
-
-eval {
-    print STDERR "starting libguestfs ...\n" if $debug;
-
-    # Run libguestfs.
-    my $g = Sys::Guestfs->new ();
-    $g->add_drive ($output, format => $format);
-    $g->launch ();
-
-    if ($type eq "ntfs" && !$g->feature_available (["ntfs3g", "ntfsprogs"])) {
-        die __"virt-make-fs: NTFS support was disabled when libguestfs was compiled\n"
-    }
-
-    # Partition the disk.
-    my $dev = "/dev/sda";
-    if (defined $partition) {
-        $partition = "mbr" if $partition eq "";
-        $g->part_disk ($dev, $partition);
-        $dev = "/dev/sda1";
-
-        # Set the partition type byte if it's MBR and the filesystem
-        # type is one that we know about.
-        my $mbr_id;
-        if ($partition eq "mbr" || $partition eq "msdos") {
-            if ($type eq "msdos") {
-                # According to Wikipedia.  However I have not actually
-                # tried this.
-                $mbr_id = 0x1;
-            } elsif ($type =~ /^v?fat$/) {
-                $mbr_id = 0xb;
-            } elsif ($type eq "ntfs") {
-                $mbr_id = 0x7;
-            } elsif ($type =~ /^ext\d$/) {
-                $mbr_id = 0x83;
-            } elsif ($type eq "minix") {
-                $mbr_id = 0x81;
-            }
-        }
-        $g->part_set_mbr_id ("/dev/sda", 1, $mbr_id) if defined $mbr_id;
-    }
-
-    print STDERR "creating $type filesystem on $dev ...\n" if $debug;
-
-    # Create the filesystem.
-    if ($type ne "btrfs") {
-        eval {
-            $g->mkfs ($type, $dev);
-        };
-        if ($@) {
-            # Provide more guidance in the error message (RHBZ#823883).
-            print STDERR "'mkfs' (create filesystem) operation failed.\n";
-            if ($type eq "fat") {
-                print STDERR "Instead of 'fat', try 'vfat' (long filenames) or 'msdos' (short filenames).\n";
-            } else {
-                print STDERR "Is '$type' a correct filesystem type?\n";
-            }
-            die
-        }
-    } else {
-        $g->mkfs_btrfs ([$dev], datatype => "single", metadata => "single");
-    }
-
-    # Set label.
-    if (defined $label) {
-        $g->set_label ($dev, $label);
-    }
-
-    # Mount it.
-
-    # For vfat, add the utf8 mount option because we want to be able
-    # to encode any non-ASCII characters into UCS2 which is what
-    # modern vfat uses on disk (RHBZ#823885).
-    my $options = "";
-    $options = "utf8" if $type eq "vfat";
-
-    $g->mount_options ($options, $dev, "/");
-
-    # Copy the data in.
-    my $ifile;
-
-    if ($ifmt eq "directory") {
-        my $pfile = create_pipe ();
-        my $cmd = sprintf ("tar -C %s -cf - . > $pfile &",
-                           shell_quote ($input));
-        print STDERR "command: $cmd\n" if $debug;
-        system ($cmd) == 0 or die __"tar: failed, see earlier messages\n";
-        $ifile = $pfile;
-    } else {
-        if ($ifmt =~ /compress.d/) {
-            my $pfile = create_pipe ();
-            my $cmd;
-            if ($ifmt =~ /compress'd/) {
-                $cmd = sprintf ("uncompress -c %s > $pfile",
-                                shell_quote ($input));
-            } elsif ($ifmt =~ /gzip compressed/) {
-                $cmd = sprintf ("gzip -cd %s", shell_quote ($input));
-            } elsif ($ifmt =~ /bzip2 compressed/) {
-                $cmd = sprintf ("bzip2 -cd %s", shell_quote ($input));
-            } elsif ($ifmt =~ /xz compressed/) {
-                $cmd = sprintf ("xz -cd %s", shell_quote ($input));
-            } else {
-                die __x("{f}: unknown input format: {fmt}\n",
-                        f => $input, fmt => $ifmt);
-            }
-            $cmd .= " > $pfile &";
-            print STDERR "command: $cmd\n" if $debug;
-            system ($cmd) == 0 or
-                die __"uncompress command failed, see earlier messages\n";
-            $ifile = $pfile;
-        } else {
-            print STDERR "reading directly from $input\n" if $debug;
-            $ifile = $input;
-        }
-    }
-
-    if ($debug) {
-        # For debugging, print statvfs before and after doing
-        # the tar-in.
-        my %stat = $g->statvfs ("/");
-        print STDERR "Before uploading ...\n";
-        print STDERR Dumper(\%stat);
-    }
-
-    print STDERR "Uploading from $ifile to / ...\n" if $debug;
-    $g->tar_in ($ifile, "/");
-
-    if ($debug) {
-        my %stat = $g->statvfs ("/");
-        print STDERR "After uploading ...\n";
-        print STDERR Dumper(\%stat);
-    }
-
-    print STDERR "finishing off\n" if $debug;
-    $g->shutdown ();
-    $g->close ()
-};
-if ($@) {
-    # Error: delete the output before exiting.
-    my $err = $@;
-    unlink $output;
-    if ($err =~ /tar_in/) {
-        print STDERR __"virt-make-fs: error copying contents into filesystem\nAn error here usually means that the program did not estimate the\nfilesystem size correctly.  Please read the BUGS section of the manpage.\n";
-    }
-    print STDERR $err;
-    exit 1;
-}
-
-exit 0;
-
-sub sizebytes
-{
-    local $_ = shift;
-    my $unit = shift;
-
-    $_ *= 1024 if $unit =~ /[KMGTPE]/;
-    $_ *= 1024 if $unit =~ /[MGTPE]/;
-    $_ *= 1024 if $unit =~ /[GTPE]/;
-    $_ *= 1024 if $unit =~ /[TPE]/;
-    $_ *= 1024 if $unit =~ /[PE]/;
-    $_ *= 1024 if $unit =~ /[E]/;
-
-    return floor($_);
-}
-
-sub create_pipe
-{
-    local $_;
-    my $dir = tempdir (CLEANUP => 1);
-    my $pipe = "$dir/pipe";
-    mkfifo ($pipe, 0600) or
-        die "mkfifo: $pipe: $!";
-    return $pipe;
-}
-
-=head1 SHELL QUOTING
-
-Libvirt guest names can contain arbitrary characters, some of which
-have meaning to the shell such as C<#> and space.  You may need to
-quote or escape these characters on the command line.  See the shell
-manual page L<sh(1)> for details.
-
-=head1 SEE ALSO
-
-L<guestfish(1)>,
-L<virt-format(1)>,
-L<virt-resize(1)>,
-L<virt-tar-in(1)>,
-L<mkisofs(1)>,
-L<genisoimage(1)>,
-L<mksquashfs(1)>,
-L<mke2fs(8)>,
-L<resize2fs(8)>,
-L<guestfs(3)>,
-L<Sys::Guestfs(3)>,
-L<http://libguestfs.org/>.
-
-=head1 AUTHOR
-
-Richard W.M. Jones L<http://people.redhat.com/~rjones/>
-
-=head1 COPYRIGHT
-
-Copyright (C) 2010-2012 Red Hat Inc.
-- 
1.8.4.2




More information about the Libguestfs mailing list