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 = ();
-@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