diff --git a/.gitignore b/.gitignore index d9bef995d..1ee7775a3 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 5b8f109dd..e34e10cf7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -74,7 +74,7 @@ SUBDIRS += test-tool SUBDIRS += fish # virt-tools in C. -SUBDIRS += align cat diff df edit format inspector rescue +SUBDIRS += align cat diff df edit format inspector make-fs rescue # bash-completion SUBDIRS += bash diff --git a/configure.ac b/configure.ac index df8e96275..6466813fc 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 000000000..d31846844 --- /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 000000000..4f9a8c5e3 --- /dev/null +++ b/make-fs/make-fs.c @@ -0,0 +1,854 @@ +/* virt-make-fs + * Copyright (C) 2010-2014 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "guestfs.h" +#include "guestfs-internal-frontend.h" + +#include "xstrtol.h" + +#include "options.h" + +guestfs_h *g; +const char *libvirt_uri; +int live; +int read_only; +int verbose; + +static const char *format = "raw", *label = NULL, + *partition = NULL, *size_str = NULL, *type = "ext2"; + +enum { HELP_OPTION = CHAR_MAX + 1 }; +static const char *options = "F:s:t:Vvx"; +static const struct option long_options[] = { + { "debug", 0, 0, 'v' }, /* for compat with Perl tool */ + { "floppy", 0, 0, 0 }, + { "format", 1, 0, 'F' }, + { "help", 0, 0, HELP_OPTION }, + { "label", 1, 0, 0 }, + { "long-options", 0, 0, 0 }, + { "partition", 2, 0, 0 }, + { "size", 1, 0, 's' }, + { "type", 1, 0, 't' }, + { "verbose", 0, 0, 'v' }, + { "version", 0, 0, 'V' }, + { 0, 0, 0, 0 } +}; + +static void __attribute__((noreturn)) +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else { + fprintf (stdout, + _("%s: make a filesystem from a tar archive or files\n" + "Copyright (C) 2010-2014 Red Hat Inc.\n" + "Usage:\n" + " %s [--options] input.tar output.img\n" + " %s [--options] input.tar.gz output.img\n" + " %s [--options] directory output.img\n" + "Options:\n" + " --floppy Make a virtual floppy disk\n" + " --format=raw|qcow2|.. Set output format\n" + " --help Display brief help\n" + " --label=label Filesystem label\n" + " --partition=mbr|gpt|.. Set partition type\n" + " --size=size|+size Set size of output disk\n" + " --type=ext4|.. Set filesystem type\n" + " -v|--verbose Verbose messages\n" + " -V|--version Display version and exit\n" + " -x Trace libguestfs API calls\n" + "For more information, see the manpage %s(1).\n"), + program_name, program_name, program_name, program_name, + program_name); + } + exit (status); +} + +static int do_make_fs (const char *input, const char *output_str); + +int +main (int argc, char *argv[]) +{ + int c; + int option_index; + + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEBASEDIR); + textdomain (PACKAGE); + + g = guestfs_create (); + if (g == NULL) { + fprintf (stderr, _("guestfs_create: failed to create handle\n")); + exit (EXIT_FAILURE); + } + + for (;;) { + c = getopt_long (argc, argv, options, long_options, &option_index); + if (c == -1) break; + + switch (c) { + case 0: /* options which are long only */ + if (STREQ (long_options[option_index].name, "long-options")) { + display_long_options (long_options); + } + else if (STREQ (long_options[option_index].name, "floppy")) { + size_str = "1440K"; + partition = "mbr"; + type = "vfat"; + } + else if (STREQ (long_options[option_index].name, "label")) { + label = optarg; + } + else if (STREQ (long_options[option_index].name, "partition")) { + partition = optarg; + } else { + fprintf (stderr, _("%s: unknown long option: %s (%d)\n"), + program_name, long_options[option_index].name, option_index); + exit (EXIT_FAILURE); + } + break; + + case 'F': + format = optarg; + break; + + case 's': + size_str = optarg; + break; + + case 't': + type = optarg; + break; + + case 'v': + OPTION_v; + break; + + case 'V': + OPTION_V; + break; + + case 'x': + OPTION_x; + break; + + case HELP_OPTION: + usage (EXIT_SUCCESS); + + default: + usage (EXIT_FAILURE); + } + } + + if (optind + 2 != argc) { + fprintf (stderr, _("%s: missing input and output arguments on the command line\n"), + program_name); + usage (EXIT_FAILURE); + } + + if (do_make_fs (argv[optind], argv[optind+1]) == -1) + exit (EXIT_FAILURE); + + exit (EXIT_SUCCESS); +} + +static int +check_ntfs_available (void) +{ + const char *ntfs_features[] = { "ntfs3g", "ntfsprogs", NULL }; + + if (STREQ (type, "ntfs") && + guestfs_feature_available (g, (char **) ntfs_features) == 0) { + fprintf (stderr, _("%s: NTFS support was disabled when libguestfs was compiled\n"), + program_name); + return -1; + } + + return 0; +} + +/* Execute a command, sending output to a file. */ +static int +exec_command (char **argv, const char *file) +{ + pid_t pid; + int status, fd; + + pid = fork (); + if (pid == -1) { + perror ("fork"); + return -1; + } + if (pid > 0) { + if (waitpid (pid, &status, 0) == -1) { + perror ("waitpid"); + return -1; + } + if (!WIFEXITED (status) || WEXITSTATUS (status) != 0) { + fprintf (stderr, _("%s: %s command failed\n"), program_name, argv[0]); + return -1; + } + return 0; + } + + /* Child process. */ + fd = open (file, O_WRONLY|O_NOCTTY); + if (fd == -1) { + perror (file); + _exit (EXIT_FAILURE); + } + dup2 (fd, 1); + close (fd); + + execvp (argv[0], argv); + perror ("execvp"); + _exit (EXIT_FAILURE); +} + +/* Execute a command, counting the amount of bytes output. */ +static int +exec_command_count_output (char **argv, uint64_t *bytes_rtn) +{ + pid_t pid; + int status; + int fd[2]; + char buffer[BUFSIZ]; + ssize_t r; + + if (pipe (fd) == -1) { + perror ("pipe"); + return -1; + } + pid = fork (); + if (pid == -1) { + perror ("fork"); + return -1; + } + if (pid > 0) { + close (fd[1]); + + /* Read output from the subprocess and count the length. */ + while ((r = read (fd[0], buffer, sizeof buffer)) > 0) { + *bytes_rtn += r; + } + if (r == -1) { + perror ("read"); + return -1; + } + close (fd[0]); + + if (waitpid (pid, &status, 0) == -1) { + perror ("waitpid"); + return -1; + } + if (!WIFEXITED (status) || WEXITSTATUS (status) != 0) { + fprintf (stderr, _("%s: %s command failed\n"), program_name, argv[0]); + return -1; + } + return 0; + } + + /* Child process. */ + close (fd[0]); + dup2 (fd[1], 1); + close (fd[1]); + + execvp (argv[0], argv); + perror ("execvp"); + _exit (EXIT_FAILURE); +} + +/* Execute a command in the background (don't wait) and send the output + * to a file. + */ +static int +bg_command (char **argv, const char *file) +{ + pid_t pid; + int fd; + + pid = fork (); + if (pid == -1) { + perror ("fork"); + return -1; + } + if (pid > 0) + /* Return immediately in the parent without waiting. */ + return 0; + + /* Child process. */ + fd = open (file, O_WRONLY|O_NOCTTY); + if (fd == -1) { + perror (file); + _exit (EXIT_FAILURE); + } + dup2 (fd, 1); + close (fd); + + execvp (argv[0], argv); + perror ("execvp"); + _exit (EXIT_FAILURE); +} + +static char * +create_pipe (void) +{ + char *tmppipe; + + if (asprintf (&tmppipe, "/tmp/makefsXXXXXX") == -1) { + perror ("asprintf"); + return NULL; + } + if (mkstemp (tmppipe) == -1) { + perror (tmppipe); + return NULL; + } + + /* Convert the temporary file into a pipe. */ + unlink (tmppipe); + if (mkfifo (tmppipe, 0600) == -1) { + perror ("mkfifo"); + return NULL; + } + return tmppipe; +} + +/* Estimate the size of the input. This returns the estimated size + * (in bytes) of the input. It also sets ifmt to the format of the + * input, either the string "directory" if the input is a directory, + * or the output of the "file" command on the input. + * + * Estimation is a Hard Problem. Some factors which make it hard: + * + * - Superblocks, block free bitmaps, FAT and other fixed overhead + * - Indirect blocks (ext2, ext3), and extents + * - Journal size + * - Internal fragmentation of files + * + * What we could also do is try shrinking the filesystem after + * creating and populating it, but that is complex given partitions. + */ +static int +estimate_input (const char *input, uint64_t *estimate_rtn, char **ifmt_rtn) +{ + struct stat statbuf; + const char *argv[6]; + CLEANUP_UNLINK_FREE char *tmpfile = NULL; + FILE *fp; + char line[256]; + size_t len; + + if (asprintf (&tmpfile, "/tmp/makefsXXXXXX") == -1) { + perror ("asprintf"); + return -1; + } + if (mkstemp (tmpfile) == -1) { + perror (tmpfile); + return -1; + } + + if (stat (input, &statbuf) == -1) { + perror (input); + return -1; + } + if (S_ISDIR (statbuf.st_mode)) { + *ifmt_rtn = strdup ("directory"); + if (*ifmt_rtn == NULL) { + perror ("strdup"); + return -1; + } + + argv[0] = "du"; + argv[1] = "--apparent-size"; + argv[2] = "-b"; + argv[3] = "-s"; + argv[4] = input; + argv[5] = NULL; + + if (exec_command ((char **) argv, tmpfile) == -1) + return -1; + + fp = fopen (tmpfile, "r"); + if (fp == NULL) { + perror (tmpfile); + return -1; + } + if (fgets (line, sizeof line, fp) == NULL) { + perror ("fgets"); + return -1; + } + fclose (fp); + + if (sscanf (line, "%" SCNu64, estimate_rtn) != 1) { + fprintf (stderr, _("%s: cannot parse the output of 'du' command: %s\n"), + program_name, line); + return -1; + } + } + else { + argv[0] = "file"; + argv[1] = "-bsLz"; + argv[2] = input; + argv[3] = NULL; + + if (exec_command ((char **) argv, tmpfile) == -1) + return -1; + + fp = fopen (tmpfile, "r"); + if (fp == NULL) { + perror (tmpfile); + return -1; + } + if (fgets (line, sizeof line, fp) == NULL) { + perror ("fgets"); + return -1; + } + fclose (fp); + + len = strlen (line); + if (len > 0 && line[len-1] == '\n') + line[len-1] = '\0'; + + *ifmt_rtn = strdup (line); + if (*ifmt_rtn == NULL) { + perror ("strdup"); + return -1; + } + + if (strstr (line, "tar archive") == NULL) { + fprintf (stderr, _("%s: %s: input is not a directory, tar archive or compressed tar achive\n"), + program_name, input); + return -1; + } + + if (strstr (line, "compress")) { + if (strstr (line, "compress'd")) { + argv[0] = "uncompress"; + argv[1] = "-c"; + argv[2] = input; + argv[3] = NULL; + } + else if (strstr (line, "gzip compressed")) { + argv[0] = "gzip"; + argv[1] = "-cd"; + argv[2] = input; + argv[3] = NULL; + } + else if (strstr (line, "bzip2 compressed")) { + argv[0] = "bzip2"; + argv[1] = "-cd"; + argv[2] = input; + argv[3] = NULL; + } + else if (strstr (line, "xz compressed")) { + argv[0] = "xz"; + argv[1] = "-cd"; + argv[2] = input; + argv[3] = NULL; + } + else { + fprintf (stderr, _("%s: %s: unknown compressed input format (%s)\n"), + program_name, input, line); + return -1; + } + + *estimate_rtn = 0; + if (exec_command_count_output ((char **) argv, estimate_rtn) == -1) + return -1; + } + else { + /* Plain tar file, just get the size directly. Tar files have + * a 512 byte block size (compared with typically 1K or 4K for + * filesystems) so this isn't very accurate. + */ + *estimate_rtn = statbuf.st_size; + } + } + + return 0; +} + +/* Prepare the input source. If the input is a regular tar file, this + * just sets ifile = input. However normally the input will be either + * a directory or a compressed tarball. In that case we set up an + * external command to do the tar/uncompression to a temporary pipe, + * and set ifile to the name of the pipe. + */ +static int +prepare_input (const char *input, const char *ifmt, + char **ifile_rtn, int *ifile_delete_on_exit) +{ + char *tmppipe; + const char *argv[7]; + + if (STREQ (ifmt, "directory")) { + tmppipe = create_pipe (); + if (tmppipe == NULL) + return -1; + argv[0] = "tar"; + argv[1] = "-C"; + argv[2] = input; + argv[3] = "-cf"; + argv[4] = "-"; + argv[5] = "."; + argv[6] = NULL; + + if (bg_command ((char **) argv, tmppipe) == -1) { + unlink (tmppipe); + free (tmppipe); + return -1; + } + + *ifile_rtn = tmppipe; + *ifile_delete_on_exit = 1; + } + else { + if (strstr (ifmt, "compress")) { + tmppipe = create_pipe (); + if (tmppipe == NULL) + return -1; + if (strstr (ifmt, "compress'd")) { + argv[0] = "uncompress"; + argv[1] = "-c"; + argv[2] = input; + argv[3] = NULL; + } + else if (strstr (ifmt, "gzip compressed")) { + argv[0] = "gzip"; + argv[1] = "-cd"; + argv[2] = input; + argv[3] = NULL; + } + else if (strstr (ifmt, "bzip2 compressed")) { + argv[0] = "bzip2"; + argv[1] = "-cd"; + argv[2] = input; + argv[3] = NULL; + } + else if (strstr (ifmt, "xz compressed")) { + argv[0] = "xz"; + argv[1] = "-cd"; + argv[2] = input; + argv[3] = NULL; + } + else + /* Shouldn't happen - see estimate_input above. */ + abort (); + + if (bg_command ((char **) argv, tmppipe) == -1) { + unlink (tmppipe); + free (tmppipe); + return -1; + } + + *ifile_rtn = tmppipe; + *ifile_delete_on_exit = 1; + } + else { + /* Plain tar file, read directly from the file. */ + *ifile_rtn = strdup (input); + if (*ifile_rtn == NULL) { + perror ("strdup"); + return -1; + } + *ifile_delete_on_exit = 0; + } + } + + return 0; +} + +/* Adapted from fish/alloc.c */ +static int +parse_size (const char *str, uint64_t estimate, uint64_t *size_rtn) +{ + unsigned long long size; + strtol_error xerr; + int plus = 0; + + assert (str); + + if (str[0] == '+') { + plus = 1; + str++; + } + + xerr = xstrtoull (str, NULL, 0, &size, "0kKMGTPEZY"); + if (xerr != LONGINT_OK) { + fprintf (stderr, + _("%s: %s: invalid size parameter '%s' (%s returned %d)\n"), + program_name, "parse_size", str, "xstrtoull", xerr); + return -1; + } + + if (plus) + *size_rtn = estimate + size; + else + *size_rtn = size; + + return 0; +} + +static int +do_make_fs (const char *input, const char *output_str) +{ + const char *dev, *options; + CLEANUP_UNLINK_FREE char *output = NULL; + uint64_t estimate, size; + struct guestfs_disk_create_argv optargs; + CLEANUP_FREE char *ifmt = NULL; + CLEANUP_FREE char *ifile = NULL; + int ifile_delete_on_exit, r; + + /* Use of CLEANUP_UNLINK_FREE *output ensures the output file is + * deleted unless we successfully reach the end of this function. + */ + output = strdup (output_str); + if (output == NULL) { + perror ("strdup"); + return -1; + } + + /* Input. What is it? Estimate how much space it will need. */ + if (estimate_input (input, &estimate, &ifmt) == -1) + return -1; + + if (verbose) { + fprintf (stderr, "input format = %s\n", ifmt); + fprintf (stderr, "estimate = %" PRIu64 " bytes " + "(%" PRIu64 " 1K blocks, %" PRIu64 " 4K blocks)\n", + estimate, estimate / 1024, estimate / 4096); + } + + estimate += 256 * 1024; /* For superblocks &c. */ + + if (STRPREFIX (type, "ext") && type[3] >= '3') { + /* For ext3+, add some more for the journal. */ + estimate += 1024 * 1024; + } + + else if (STREQ (type, "ntfs")) { + estimate += 4 * 1024 * 1024; /* NTFS journal. */ + } + + else if (STREQ (type, "btrfs")) { + /* For BTRFS, the minimum metadata allocation is 256MB, with data + * additional to that. Note that we disable data and metadata + * duplication below. + */ + estimate += 256 * 1024 * 1024; + } + + /* Add 10%, see above. */ + estimate *= 1.10; + + /* Calculate the output size. */ + if (size_str == NULL) + size = estimate; + else + if (parse_size (size_str, estimate, &size) == -1) + return -1; + + /* Create the output disk. */ + optargs.bitmask = 0; + if (STREQ (format, "qcow2")) { + optargs.bitmask |= GUESTFS_DISK_CREATE_PREALLOCATION_BITMASK; + optargs.preallocation = "metadata"; + } + if (guestfs_disk_create_argv (g, output, format, size, &optargs) == -1) + return -1; + + if (guestfs_add_drive_opts (g, output, + GUESTFS_ADD_DRIVE_OPTS_FORMAT, format, + -1) == -1) + return -1; + + if (guestfs_launch (g) == -1) + return -1; + + if (check_ntfs_available () == -1) + return -1; + + /* Partition the disk. */ + dev = "/dev/sda"; + if (partition) { + int mbr_id = 0; + + if (STREQ (partition, "")) + partition = "mbr"; + + if (guestfs_part_disk (g, dev, partition) == -1) + return -1; + + dev = "/dev/sda1"; + + /* Set the partition type byte if it's MBR and the filesystem type + * is one that we know about. + */ + if (STREQ (partition, "mbr") || STREQ (partition, "msdos")) { + if (STREQ (type, "msdos")) + /* According to Wikipedia. However I have not actually tried this. */ + mbr_id = 0x1; + else if (STREQ (type, "vfat") || STREQ (type, "fat")) + mbr_id = 0xb; + else if (STREQ (type, "ntfs")) + mbr_id = 0x7; + else if (STRPREFIX (type, "ext")) + mbr_id = 0x83; + else if (STREQ (type, "minix")) + mbr_id = 0x81; + } + if (mbr_id != 0) { + if (guestfs_part_set_mbr_id (g, "/dev/sda", 1, mbr_id) == -1) + return -1; + } + } + + if (verbose) + fprintf (stderr, "creating %s filesystem on %s ...\n", type, dev); + + /* Create the filesystem. */ + if (STRNEQ (type, "btrfs")) { + int r; + + guestfs_push_error_handler (g, NULL, NULL); + r = guestfs_mkfs (g, type, dev); + guestfs_pop_error_handler (g); + + if (r == -1) { + /* Provide more guidance in the error message (RHBZ#823883). */ + fprintf (stderr, "%s: 'mkfs' (create filesystem) operation failed.\n", + program_name); + if (STREQ (type, "fat")) + fprintf (stderr, "Instead of 'fat', try 'vfat' (long filenames) or 'msdos' (short filenames).\n"); + else + fprintf (stderr, "Is '%s' a correct filesystem type?\n", type); + + return -1; + } + } + else { + const char *devs[] = { dev, NULL }; + + if (guestfs_mkfs_btrfs (g, (char **) devs, + GUESTFS_MKFS_BTRFS_DATATYPE, "single", + GUESTFS_MKFS_BTRFS_METADATA, "single", + -1) == -1) + return -1; + } + + /* Set label. */ + if (label) { + if (guestfs_set_label (g, dev, label) == -1) + return -1; + } + + /* Mount it. */ + + /* For vfat, add the utf8 mount option because we want to be able to + * encode any non-ASCII characters into UCS2 which is what modern + * vfat uses on disk (RHBZ#823885). + */ + if (STREQ (type, "vfat")) + options = "utf8"; + else + options = ""; + + if (guestfs_mount_options (g, options, dev, "/") == -1) + return -1; + + /* For debugging, print statvfs before and after doing the tar-in. */ + if (verbose) { + CLEANUP_FREE_STATVFS struct guestfs_statvfs *stats = + guestfs_statvfs (g, "/"); + fprintf (stderr, "before uploading:\n"); + fprintf (stderr, " bsize = %" PRIi64 "\n", stats->bsize); + fprintf (stderr, " frsize = %" PRIi64 "\n", stats->frsize); + fprintf (stderr, " blocks = %" PRIi64 "\n", stats->blocks); + fprintf (stderr, " bfree = %" PRIi64 "\n", stats->bfree); + fprintf (stderr, " bavail = %" PRIi64 "\n", stats->bavail); + fprintf (stderr, " files = %" PRIi64 "\n", stats->files); + fprintf (stderr, " ffree = %" PRIi64 "\n", stats->ffree); + fprintf (stderr, " favail = %" PRIi64 "\n", stats->favail); + fprintf (stderr, " fsid = %" PRIi64 "\n", stats->fsid); + fprintf (stderr, " flag = %" PRIi64 "\n", stats->flag); + fprintf (stderr, " namemax = %" PRIi64 "\n", stats->namemax); + } + + /* Prepare the input to be copied in. */ + if (prepare_input (input, ifmt, &ifile, &ifile_delete_on_exit) == -1) + return -1; + + if (verbose) + fprintf (stderr, "uploading from %s to / ...\n", ifile); + r = guestfs_tar_in (g, ifile, "/"); + if (ifile_delete_on_exit) + unlink (ifile); + if (r == -1) + return -1; + + if (verbose) { + CLEANUP_FREE_STATVFS struct guestfs_statvfs *stats = + guestfs_statvfs (g, "/"); + fprintf (stderr, "after uploading:\n"); + fprintf (stderr, " bsize = %" PRIi64 "\n", stats->bsize); + fprintf (stderr, " frsize = %" PRIi64 "\n", stats->frsize); + fprintf (stderr, " blocks = %" PRIi64 "\n", stats->blocks); + fprintf (stderr, " bfree = %" PRIi64 "\n", stats->bfree); + fprintf (stderr, " bavail = %" PRIi64 "\n", stats->bavail); + fprintf (stderr, " files = %" PRIi64 "\n", stats->files); + fprintf (stderr, " ffree = %" PRIi64 "\n", stats->ffree); + fprintf (stderr, " favail = %" PRIi64 "\n", stats->favail); + fprintf (stderr, " fsid = %" PRIi64 "\n", stats->fsid); + fprintf (stderr, " flag = %" PRIi64 "\n", stats->flag); + fprintf (stderr, " namemax = %" PRIi64 "\n", stats->namemax); + } + + if (verbose) + fprintf (stderr, "finishing off\n"); + if (guestfs_shutdown (g) == -1) + return -1; + guestfs_close (g); + + /* Output was created OK, so save it from being deleted by + * CLEANUP_UNLINK_FREE. + */ + free (output); + output = NULL; + + return 0; +} diff --git a/tools/test-virt-make-fs.sh b/make-fs/test-virt-make-fs.sh similarity index 92% rename from tools/test-virt-make-fs.sh rename to make-fs/test-virt-make-fs.sh index 62881b16f..f276e4c27 100755 --- a/tools/test-virt-make-fs.sh +++ b/make-fs/test-virt-make-fs.sh @@ -1,6 +1,6 @@ #!/bin/bash - # libguestfs -# Copyright (C) 2010-2012 Red Hat Inc. +# Copyright (C) 2010-2014 Red Hat Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,7 +16,9 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# Engage in some montecarlo testing of virt-make-fs. +# Engage in some montecarlo testing of virt-make-fs. This test is +# copied from the original Perl tool virt-make-fs, on the basis that +# the new C tool should be able to pass the same tests. export LANG=C set -e diff --git a/make-fs/virt-make-fs.pod b/make-fs/virt-make-fs.pod new file mode 100644 index 000000000..8f0e57e93 --- /dev/null +++ b/make-fs/virt-make-fs.pod @@ -0,0 +1,249 @@ +=encoding utf8 + +=head1 NAME + +virt-make-fs - Make a filesystem from a tar archive or files + +=head1 SYNOPSIS + + virt-make-fs [--options] input.tar output.img + + virt-make-fs [--options] input.tar.gz output.img + + virt-make-fs [--options] directory output.img + +=head1 DESCRIPTION + +Virt-make-fs is a command line tool for creating a filesystem from a +tar archive or some files in a directory. It is similar to tools like +L, L and L. 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. To create complex +layouts, use L. + +Basic usage is: + + virt-make-fs input output.img + +where C is either a directory containing files that you want to +add, or a tar archive (either uncompressed tar or gzip-compressed +tar); and C 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 below). + +=head2 FILESYSTEM TYPE + +The default filesystem type is C. Just about any filesystem +type that libguestfs supports can be used (but I read-only +formats like ISO9660). Here are some of the more common choices: + +=over 4 + +=item I + +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 or I + +Useful if exporting data to a Windows guest. + +=item I + +Lower overhead than C, 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 directly on a partitioned disk image. (However libguestfs +tools such as L and L 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 and L 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=ENE> + +=item B<--size=+ENE> + +=item B<-s ENE> + +=item B<-s +ENE> + +Use the I<--size> (or I<-s>) option to choose the size of the output +image. + +If this option is I 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=EfmtE> + +=item B<-F EfmtE> + +Choose the output disk image format. + +The default is C (raw sparse disk image). + +=item B<--type=EfsE> + +=item B<-t EfsE> + +Choose the output filesystem type. + +The default is C. + +Any filesystem which is supported read-write by libguestfs can be used +here. + +=item B<--label=ELABELE> + +Set the filesystem label. + +=item B<--partition> + +=item B<--partition=EparttypeE> + +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 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, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L. + +=head1 AUTHOR + +Richard W.M. Jones L + +=head1 COPYRIGHT + +Copyright (C) 2010-2014 Red Hat Inc. diff --git a/po/POTFILES b/po/POTFILES index 76d1b1d65..7ded4c472 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -235,6 +235,7 @@ gobject/src/tristate.c inspector/inspector.c java/com_redhat_et_libguestfs_GuestFS.c lua/lua-guestfs.c +make-fs/make-fs.c mllib/crypt-c.c mllib/fsync-c.c mllib/progress-c.c diff --git a/po/POTFILES-pl b/po/POTFILES-pl index b2efffa46..9d7665aa3 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 93c50d2d8..f77db9549 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 df6044dcd..4d95a2fae 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 + +L command and documentation. + =item C Various libraries and common code used by L and diff --git a/tools/Makefile.am b/tools/Makefile.am index f975a28f7..e1797f272 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -20,7 +20,6 @@ include $(top_srcdir)/subdir-rules.mk tools = \ list-filesystems \ list-partitions \ - make-fs \ tar \ win-reg @@ -62,7 +61,6 @@ TESTS_ENVIRONMENT = $(top_builddir)/run --test if ENABLE_APPLIANCE TESTS = test-virt-list-filesystems.sh \ - test-virt-make-fs.sh \ test-virt-tar.sh endif ENABLE_APPLIANCE diff --git a/tools/virt-make-fs b/tools/virt-make-fs deleted file mode 100755 index f1cc09dc7..000000000 --- a/tools/virt-make-fs +++ /dev/null @@ -1,632 +0,0 @@ -#!/usr/bin/perl -w -# virt-make-fs -# Copyright (C) 2010-2012 Red Hat Inc. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -use warnings; -use strict; - -use Sys::Guestfs; - -use Pod::Usage; -use Getopt::Long; -use File::Temp qw(tempfile tempdir); -use POSIX qw(mkfifo floor); -use Data::Dumper; -use String::ShellQuote qw(shell_quote); -use Locale::TextDomain 'libguestfs'; -use Fcntl qw(SEEK_SET); - -=encoding utf8 - -=head1 NAME - -virt-make-fs - Make a filesystem from a tar archive or files - -=head1 SYNOPSIS - - virt-make-fs [--options] input.tar output.img - - virt-make-fs [--options] input.tar.gz output.img - - virt-make-fs [--options] directory output.img - -=head1 DESCRIPTION - -Virt-make-fs is a command line tool for creating a filesystem from a -tar archive or some files in a directory. It is similar to tools like -L, L and L. 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. To create complex -layouts, use L. - -Basic usage is: - - virt-make-fs input output.img - -where C is either a directory containing files that you want to -add, or a tar archive (either uncompressed tar or gzip-compressed -tar); and C 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 below). - -=head2 FILESYSTEM TYPE - -The default filesystem type is C. Just about any filesystem -type that libguestfs supports can be used (but I read-only -formats like ISO9660). Here are some of the more common choices: - -=over 4 - -=item I - -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 or I - -Useful if exporting data to a Windows guest. - -=item I - -Lower overhead than C, 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 directly on a partitioned disk image. (However libguestfs -tools such as L and L 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 and L 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=ENE> - -=item B<--size=+ENE> - -=item B<-s ENE> - -=item B<-s +ENE> - -Use the I<--size> (or I<-s>) option to choose the size of the output -image. - -If this option is I 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=EfmtE> - -=item B<-F EfmtE> - -Choose the output disk image format. - -The default is C (raw sparse disk image). - -=cut - -my $type = "ext2"; - -=item B<--type=EfsE> - -=item B<-t EfsE> - -Choose the output filesystem type. - -The default is C. - -Any filesystem which is supported read-write by libguestfs can be used -here. - -=cut - -my $label; - -=item B<--label=ELABELE> - -Set the filesystem label. - -=cut - -my $partition; - -=item B<--partition> - -=item B<--partition=EparttypeE> - -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 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: $!"; - - $_ = ; - 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 = ; - 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 ; - close PIPE or die "close: $!"; - } else { - # Plain tar file, just get the size directly. Tar files have - # a 512 byte block size (compared with typically 1K or 4K for - # filesystems) so this isn't very accurate. - $estimate = -s $input; - } -} - -if ($debug) { - printf STDERR "input format = %s\n", $ifmt; - printf STDERR "estimate = %s bytes (%s 1K blocks, %s 4K blocks)\n", - $estimate, $estimate / 1024, $estimate / 4096; -} - -$estimate += 256 * 1024; # For superblocks &c. - -if ($type =~ /^ext[3-9]/) { - $estimate += 1024 * 1024; # For ext3/4, add some more for the journal. -} - -if ($type eq "ntfs") { - $estimate += 4 * 1024 * 1024; # NTFS journal. -} - -if ($type eq "btrfs") { - # For BTRFS, the minimum metadata allocation is 256MB, with data - # additional to that. Note that we disable data and metadata - # duplication below. - $estimate += 256 * 1024 * 1024; -} - -$estimate *= 1.10; # Add 10%, see above. - -# Calculate the output size. - -if (!defined $size) { - $size = $estimate; -} else { - if ($size =~ /^\+([.\d]+)([bKMGTPE])$/) { - $size = $estimate + sizebytes ($1, $2); - } elsif ($size =~ /^([.\d]+)([bKMGTPE])$/) { - $size = sizebytes ($1, $2); - } else { - die __x("virt-make-fs: cannot parse size parameter: {sz}\n", - sz => $size); - } -} - -$size = int ($size); - -eval { - print STDERR "starting libguestfs ...\n" if $debug; - - # Run libguestfs. - my $g = Sys::Guestfs->new (); - - # Create the output disk. - my %options = (); - $options{preallocation} = "metadata" if $format eq "qcow2"; - $g->disk_create ($output, $format, $size, %options); - - $g->add_drive ($output, format => $format); - $g->launch (); - - if ($type eq "ntfs" && !$g->feature_available (["ntfs3g", "ntfsprogs"])) { - die __"virt-make-fs: NTFS support was disabled when libguestfs was compiled\n" - } - - # Partition the disk. - my $dev = "/dev/sda"; - if (defined $partition) { - $partition = "mbr" if $partition eq ""; - $g->part_disk ($dev, $partition); - $dev = "/dev/sda1"; - - # Set the partition type byte if it's MBR and the filesystem - # type is one that we know about. - my $mbr_id; - if ($partition eq "mbr" || $partition eq "msdos") { - if ($type eq "msdos") { - # According to Wikipedia. However I have not actually - # tried this. - $mbr_id = 0x1; - } elsif ($type =~ /^v?fat$/) { - $mbr_id = 0xb; - } elsif ($type eq "ntfs") { - $mbr_id = 0x7; - } elsif ($type =~ /^ext\d$/) { - $mbr_id = 0x83; - } elsif ($type eq "minix") { - $mbr_id = 0x81; - } - } - $g->part_set_mbr_id ("/dev/sda", 1, $mbr_id) if defined $mbr_id; - } - - print STDERR "creating $type filesystem on $dev ...\n" if $debug; - - # Create the filesystem. - if ($type ne "btrfs") { - eval { - $g->mkfs ($type, $dev); - }; - if ($@) { - # Provide more guidance in the error message (RHBZ#823883). - print STDERR "'mkfs' (create filesystem) operation failed.\n"; - if ($type eq "fat") { - print STDERR "Instead of 'fat', try 'vfat' (long filenames) or 'msdos' (short filenames).\n"; - } else { - print STDERR "Is '$type' a correct filesystem type?\n"; - } - die - } - } else { - $g->mkfs_btrfs ([$dev], datatype => "single", metadata => "single"); - } - - # Set label. - if (defined $label) { - $g->set_label ($dev, $label); - } - - # Mount it. - - # For vfat, add the utf8 mount option because we want to be able - # to encode any non-ASCII characters into UCS2 which is what - # modern vfat uses on disk (RHBZ#823885). - my $options = ""; - $options = "utf8" if $type eq "vfat"; - - $g->mount_options ($options, $dev, "/"); - - # Copy the data in. - my $ifile; - - if ($ifmt eq "directory") { - my $pfile = create_pipe (); - my $cmd = sprintf ("tar -C %s -cf - . > $pfile &", - shell_quote ($input)); - print STDERR "command: $cmd\n" if $debug; - system ($cmd) == 0 or die __"tar: failed, see earlier messages\n"; - $ifile = $pfile; - } else { - if ($ifmt =~ /compress.d/) { - my $pfile = create_pipe (); - my $cmd; - if ($ifmt =~ /compress'd/) { - $cmd = sprintf ("uncompress -c %s > $pfile", - shell_quote ($input)); - } elsif ($ifmt =~ /gzip compressed/) { - $cmd = sprintf ("gzip -cd %s", shell_quote ($input)); - } elsif ($ifmt =~ /bzip2 compressed/) { - $cmd = sprintf ("bzip2 -cd %s", shell_quote ($input)); - } elsif ($ifmt =~ /xz compressed/) { - $cmd = sprintf ("xz -cd %s", shell_quote ($input)); - } else { - die __x("{f}: unknown input format: {fmt}\n", - f => $input, fmt => $ifmt); - } - $cmd .= " > $pfile &"; - print STDERR "command: $cmd\n" if $debug; - system ($cmd) == 0 or - die __"uncompress command failed, see earlier messages\n"; - $ifile = $pfile; - } else { - print STDERR "reading directly from $input\n" if $debug; - $ifile = $input; - } - } - - if ($debug) { - # For debugging, print statvfs before and after doing - # the tar-in. - my %stat = $g->statvfs ("/"); - print STDERR "Before uploading ...\n"; - print STDERR Dumper(\%stat); - } - - print STDERR "Uploading from $ifile to / ...\n" if $debug; - $g->tar_in ($ifile, "/"); - - if ($debug) { - my %stat = $g->statvfs ("/"); - print STDERR "After uploading ...\n"; - print STDERR Dumper(\%stat); - } - - print STDERR "finishing off\n" if $debug; - $g->shutdown (); - $g->close () -}; -if ($@) { - # Error: delete the output before exiting. - my $err = $@; - unlink $output; - if ($err =~ /tar_in/) { - print STDERR __"virt-make-fs: error copying contents into filesystem\nAn error here usually means that the program did not estimate the\nfilesystem size correctly. Please read the BUGS section of the manpage.\n"; - } - print STDERR $err; - exit 1; -} - -exit 0; - -sub sizebytes -{ - local $_ = shift; - my $unit = shift; - - $_ *= 1024 if $unit =~ /[KMGTPE]/; - $_ *= 1024 if $unit =~ /[MGTPE]/; - $_ *= 1024 if $unit =~ /[GTPE]/; - $_ *= 1024 if $unit =~ /[TPE]/; - $_ *= 1024 if $unit =~ /[PE]/; - $_ *= 1024 if $unit =~ /[E]/; - - return floor($_); -} - -sub create_pipe -{ - local $_; - my $dir = tempdir (CLEANUP => 1); - my $pipe = "$dir/pipe"; - mkfifo ($pipe, 0600) or - die "mkfifo: $pipe: $!"; - return $pipe; -} - -=head1 SHELL QUOTING - -Libvirt guest names can contain arbitrary characters, some of which -have meaning to the shell such as C<#> and space. You may need to -quote or escape these characters on the command line. See the shell -manual page L for details. - -=head1 SEE ALSO - -L, -L, -L, -L, -L, -L, -L, -L, -L, -L, -L, -L. - -=head1 AUTHOR - -Richard W.M. Jones L - -=head1 COPYRIGHT - -Copyright (C) 2010-2012 Red Hat Inc.