[Pkg-libvirt-commits] [libguestfs] 154/233: Rewrite virt-make-fs in C (originally Perl).

Hilko Bengen bengen at moszumanska.debian.org
Wed Feb 19 21:11:52 UTC 2014


This is an automated email from the git hooks/post-receive script.

bengen pushed a commit to branch experimental
in repository libguestfs.

commit d3512deb6728dffbb3a958c7168b81dbde56d748
Author: Richard W.M. Jones <rjones at redhat.com>
Date:   Mon Jan 27 13:29:54 2014 +0000

    Rewrite virt-make-fs in C (originally Perl).
    
    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                       | 854 ++++++++++++++++++++++++++++++++
 {tools => make-fs}/test-virt-make-fs.sh |   6 +-
 make-fs/virt-make-fs.pod                | 249 ++++++++++
 po/POTFILES                             |   1 +
 po/POTFILES-pl                          |   1 -
 run.in                                  |   2 +-
 src/guestfs.pod                         |   4 +
 tools/Makefile.am                       |   2 -
 tools/virt-make-fs                      | 632 -----------------------
 13 files changed, 1200 insertions(+), 639 deletions(-)

diff --git a/.gitignore b/.gitignore
index d9bef99..1ee7775 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 5b8f109..e34e10c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -74,7 +74,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 df8e962..6466813 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..4f9a8c5
--- /dev/null
+++ b/make-fs/make-fs.c
@@ -0,0 +1,854 @@
+/* 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", 1, 0, 'F' },
+  { "help", 0, 0, HELP_OPTION },
+  { "label", 1, 0, 0 },
+  { "long-options", 0, 0, 0 },
+  { "partition", 2, 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 if (STREQ (long_options[option_index].name, "partition")) {
+        partition = 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)
+{
+  pid_t pid;
+  int status, fd;
+
+  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) {
+      fprintf (stderr, _("%s: %s command failed\n"), program_name, argv[0]);
+      return -1;
+    }
+    return 0;
+  }
+
+  /* Child process. */
+  fd = open (file, O_WRONLY|O_NOCTTY);
+  if (fd == -1) {
+    perror (file);
+    _exit (EXIT_FAILURE);
+  }
+  dup2 (fd, 1);
+  close (fd);
+
+  execvp (argv[0], argv);
+  perror ("execvp");
+  _exit (EXIT_FAILURE);
+}
+
+/* Execute a command, counting the amount of bytes output. */
+static int
+exec_command_count_output (char **argv, uint64_t *bytes_rtn)
+{
+  pid_t pid;
+  int status;
+  int fd[2];
+  char buffer[BUFSIZ];
+  ssize_t r;
+
+  if (pipe (fd) == -1) {
+    perror ("pipe");
+    return -1;
+  }
+  pid = fork ();
+  if (pid == -1) {
+    perror ("fork");
+    return -1;
+  }
+  if (pid > 0) {
+    close (fd[1]);
+
+    /* Read output from the subprocess and count the length. */
+    while ((r = read (fd[0], buffer, sizeof buffer)) > 0) {
+      *bytes_rtn += r;
+    }
+    if (r == -1) {
+      perror ("read");
+      return -1;
+    }
+    close (fd[0]);
+
+    if (waitpid (pid, &status, 0) == -1) {
+      perror ("waitpid");
+      return -1;
+    }
+    if (!WIFEXITED (status) || WEXITSTATUS (status) != 0) {
+      fprintf (stderr, _("%s: %s command failed\n"), program_name, argv[0]);
+      return -1;
+    }
+    return 0;
+  }
+
+  /* Child process. */
+  close (fd[0]);
+  dup2 (fd[1], 1);
+  close (fd[1]);
+
+  execvp (argv[0], argv);
+  perror ("execvp");
+  _exit (EXIT_FAILURE);
+}
+
+/* Execute a command in the background (don't wait) and send the output
+ * to a file.
+ */
+static int
+bg_command (char **argv, const char *file)
+{
+  pid_t pid;
+  int fd;
+
+  pid = fork ();
+  if (pid == -1) {
+    perror ("fork");
+    return -1;
+  }
+  if (pid > 0)
+    /* Return immediately in the parent without waiting. */
+    return 0;
+
+  /* Child process. */
+  fd = open (file, O_WRONLY|O_NOCTTY);
+  if (fd == -1) {
+    perror (file);
+    _exit (EXIT_FAILURE);
+  }
+  dup2 (fd, 1);
+  close (fd);
+
+  execvp (argv[0], argv);
+  perror ("execvp");
+  _exit (EXIT_FAILURE);
+}
+
+static char *
+create_pipe (void)
+{
+  char *tmppipe;
+
+  if (asprintf (&tmppipe, "/tmp/makefsXXXXXX") == -1) {
+    perror ("asprintf");
+    return NULL;
+  }
+  if (mkstemp (tmppipe) == -1) {
+    perror (tmppipe);
+    return NULL;
+  }
+
+  /* Convert the temporary file into a pipe. */
+  unlink (tmppipe);
+  if (mkfifo (tmppipe, 0600) == -1) {
+    perror ("mkfifo");
+    return NULL;
+  }
+  return tmppipe;
+}
+
+/* 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];
+  size_t len;
+
+  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_rtn = strdup ("directory");
+    if (*ifmt_rtn == 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) == -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) == -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);
+
+    len = strlen (line);
+    if (len > 0 && line[len-1] == '\n')
+      line[len-1] = '\0';
+
+    *ifmt_rtn = strdup (line);
+    if (*ifmt_rtn == 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")) {
+        argv[0] = "uncompress";
+        argv[1] = "-c";
+        argv[2] = input;
+        argv[3] = NULL;
+      }
+      else if (strstr (line, "gzip compressed")) {
+        argv[0] = "gzip";
+        argv[1] = "-cd";
+        argv[2] = input;
+        argv[3] = NULL;
+      }
+      else if (strstr (line, "bzip2 compressed")) {
+        argv[0] = "bzip2";
+        argv[1] = "-cd";
+        argv[2] = input;
+        argv[3] = NULL;
+      }
+      else if (strstr (line, "xz compressed")) {
+        argv[0] = "xz";
+        argv[1] = "-cd";
+        argv[2] = input;
+        argv[3] = NULL;
+      }
+      else {
+        fprintf (stderr, _("%s: %s: unknown compressed input format (%s)\n"),
+                 program_name, input, line);
+        return -1;
+      }
+
+      *estimate_rtn = 0;
+      if (exec_command_count_output ((char **) argv, estimate_rtn) == -1)
+        return -1;
+    }
+    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_rtn = statbuf.st_size;
+    }
+  }
+
+  return 0;
+}
+
+/* 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)
+{
+  char *tmppipe;
+  const char *argv[7];
+
+  if (STREQ (ifmt, "directory")) {
+    tmppipe = create_pipe ();
+    if (tmppipe == NULL)
+      return -1;
+    argv[0] = "tar";
+    argv[1] = "-C";
+    argv[2] = input;
+    argv[3] = "-cf";
+    argv[4] = "-";
+    argv[5] = ".";
+    argv[6] = NULL;
+
+    if (bg_command ((char **) argv, tmppipe) == -1) {
+      unlink (tmppipe);
+      free (tmppipe);
+      return -1;
+    }
+
+    *ifile_rtn = tmppipe;
+    *ifile_delete_on_exit = 1;
+  }
+  else {
+    if (strstr (ifmt, "compress")) {
+      tmppipe = create_pipe ();
+      if (tmppipe == NULL)
+        return -1;
+      if (strstr (ifmt, "compress'd")) {
+        argv[0] = "uncompress";
+        argv[1] = "-c";
+        argv[2] = input;
+        argv[3] = NULL;
+      }
+      else if (strstr (ifmt, "gzip compressed")) {
+        argv[0] = "gzip";
+        argv[1] = "-cd";
+        argv[2] = input;
+        argv[3] = NULL;
+      }
+      else if (strstr (ifmt, "bzip2 compressed")) {
+        argv[0] = "bzip2";
+        argv[1] = "-cd";
+        argv[2] = input;
+        argv[3] = NULL;
+      }
+      else if (strstr (ifmt, "xz compressed")) {
+        argv[0] = "xz";
+        argv[1] = "-cd";
+        argv[2] = input;
+        argv[3] = NULL;
+      }
+      else
+        /* Shouldn't happen - see estimate_input above. */
+        abort ();
+
+      if (bg_command ((char **) argv, tmppipe) == -1) {
+        unlink (tmppipe);
+        free (tmppipe);
+        return -1;
+      }
+
+      *ifile_rtn = tmppipe;
+      *ifile_delete_on_exit = 1;
+    }
+    else {
+      /* Plain tar file, read directly from the file. */
+      *ifile_rtn = strdup (input);
+      if (*ifile_rtn == NULL) {
+        perror ("strdup");
+        return -1;
+      }
+      *ifile_delete_on_exit = 0;
+    }
+  }
+
+  return 0;
+}
+
+/* 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;
+}
+
+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;
+  struct guestfs_disk_create_argv optargs;
+  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. */
+  optargs.bitmask = 0;
+  if (STREQ (format, "qcow2")) {
+    optargs.bitmask |= GUESTFS_DISK_CREATE_PREALLOCATION_BITMASK;
+    optargs.preallocation = "metadata";
+  }
+  if (guestfs_disk_create_argv (g, output, format, size, &optargs) == -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)
+      return -1;
+
+    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/tools/test-virt-make-fs.sh b/make-fs/test-virt-make-fs.sh
similarity index 92%
rename from tools/test-virt-make-fs.sh
rename to make-fs/test-virt-make-fs.sh
index 62881b1..f276e4c 100755
--- a/tools/test-virt-make-fs.sh
+++ b/make-fs/test-virt-make-fs.sh
@@ -1,6 +1,6 @@
 #!/bin/bash -
 # libguestfs
-# Copyright (C) 2010-2012 Red Hat Inc.
+# 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
@@ -16,7 +16,9 @@
 # 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.
+# 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
diff --git a/make-fs/virt-make-fs.pod b/make-fs/virt-make-fs.pod
new file mode 100644
index 0000000..8f0e57e
--- /dev/null
+++ b/make-fs/virt-make-fs.pod
@@ -0,0 +1,249 @@
+=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).
+
+=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 76d1b1d..7ded4c4 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -235,6 +235,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 93c50d2..f77db95 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/virt-make-fs b/tools/virt-make-fs
deleted file mode 100755
index f1cc09d..0000000
--- a/tools/virt-make-fs
+++ /dev/null
@@ -1,632 +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).
-
-=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);
-
-eval {
-    print STDERR "starting libguestfs ...\n" if $debug;
-
-    # Run libguestfs.
-    my $g = Sys::Guestfs->new ();
-
-    # Create the output disk.
-    my %options = ();
-    $options{preallocation} = "metadata" if $format eq "qcow2";
-    $g->disk_create ($output, $format, $size, %options);
-
-    $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.

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-libvirt/libguestfs.git



More information about the Pkg-libvirt-commits mailing list