Richard W.M. Jones
2014-Jan-27 13:29 UTC
[Libguestfs] [PATCH INCOMPLETE] Rewrite virt-make-fs in C (originally Perl).
I thought it would be easy to rewrite virt-make-fs in C. Two days later ... The Perl program uses a lot of external commands, which makes it pretty tedious to implement in C. Rich.
Richard W.M. Jones
2014-Jan-27 13:29 UTC
[Libguestfs] [PATCH INCOMPLETE] 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 | 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
Pino Toscano
2014-Jan-27 14:09 UTC
Re: [Libguestfs] [PATCH INCOMPLETE] Rewrite virt-make-fs in C (originally Perl).
On Monday 27 January 2014 13:29:54 Richard W.M. Jones wrote:> +/* 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) > +{Reading this and other similar implementations, for example: - fish/events.c, do_event_handler - fish/fish.c, execute_and_inline and issue_command (which has a comment regarding pipe commands) - src/command.c, run_command - src/launch-direct.c, launch_direct - src/launch-uml.c, launch_uml - daemon/guestfsd.c, commandrf (maybe, since it is in the appliance); other popen usages in daemon/* what about using libpipeline [1] to handle them? While it adds the overhead of a new shared library (although using just libc, and ~55kb in my f19/x86_64 installation), should help a bit in spawning commands and handling piping of them if needed. Given you are starting a new tool in C [2], what about making use of it to check how it works? [1] http://libpipeline.nongnu.org/ [2] as in, was not in C before -- Pino Toscano
Apparently Analagous Threads
- [PATCH INCOMPLETE] Rewrite virt-make-fs in C (originally Perl).
- [PATCH v2] Add tune2fs command.
- Re: [PATCH nbdkit] server: public: Add nbdkit_parse_* functions for safely parsing integers.
- [PATCH libnbd 1/2] lib: Avoid killing subprocess twice.
- [PATCH nbdkit] server: public: Add nbdkit_parse_* functions for safely parsing integers.