This is a wrapper around either 'qemu-img create' or calls to open,
truncate and posix_fallocate which litter and complicate existing
code.
---
Makefile.am | 1 +
configure.ac | 1 +
generator/actions.ml | 52 ++++++-
gobject/Makefile.inc | 2 +
po/POTFILES | 2 +
src/Makefile.am | 1 +
src/create.c | 319 +++++++++++++++++++++++++++++++++++++++
tests/create/Makefile.am | 27 ++++
tests/create/test-disk-create.sh | 48 ++++++
9 files changed, 451 insertions(+), 2 deletions(-)
create mode 100644 src/create.c
create mode 100644 tests/create/Makefile.am
create mode 100755 tests/create/test-disk-create.sh
diff --git a/Makefile.am b/Makefile.am
index e39d11f..5b8f109 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -39,6 +39,7 @@ SUBDIRS += tests/tmpdirs
SUBDIRS += tests/protocol
SUBDIRS += tests/events
SUBDIRS += tests/parallel
+SUBDIRS += tests/create
SUBDIRS += tests/disks
SUBDIRS += tests/mountable
SUBDIRS += tests/network
diff --git a/configure.ac b/configure.ac
index c07462e..df8e962 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1745,6 +1745,7 @@ AC_CONFIG_FILES([Makefile
tests/btrfs/Makefile
tests/c-api/Makefile
tests/charsets/Makefile
+ tests/create/Makefile
tests/data/Makefile
tests/disks/Makefile
tests/disks/test-qemu-drive-libvirt.xml
diff --git a/generator/actions.ml b/generator/actions.ml
index fa1a2c5..3637da0 100644
--- a/generator/actions.ml
+++ b/generator/actions.ml
@@ -3093,6 +3093,54 @@ Return the current backend settings.
See L<guestfs(3)/BACKEND>, L<guestfs(3)/BACKEND SETTINGS>." };
+ { defaults with
+ name = "disk_create";
+ style = RErr, [String "filename"; String "format"; Int64
"size"], [OString "backingfile"; OString "backingformat";
OString "preallocation"; OString "compat"; OInt
"clustersize"];
+ test_excuse = "tests in tests/create subdirectory";
+ shortdesc = "create a blank disk image";
+ longdesc = "\
+Create a blank disk image called C<filename> (a host file or device)
+with format C<format> (usually C<raw> or C<qcow2>, but other formats
+are possible). The size is C<size> bytes.
+
+If used with the optional C<backingfile> parameter, then a snapshot
+is created on top of the backing file. In this case, C<size> must
+be passed as C<-1>. The size of the snapshot is the same as the
+size of the backing file, which is discovered automatically. You
+are encouraged to also pass C<backingformat> to describe the format
+of C<backingfile>.
+
+The other optional parameters are:
+
+=over 4
+
+=item C<preallocation>
+
+If format is C<raw>, then this can be either C<sparse> or C<full>
+to create a sparse or fully allocated file respectively. The default
+is C<sparse>.
+
+If format is C<qcow2>, then this can be either C<off> or
+C<metadata>. Preallocating metadata can be faster when doing lots
+of writes, but uses more space.
+
+=item C<compat>
+
+C<qcow2> only:
+Pass the string C<1.1> to use the advanced qcow2 format supported
+by qemu E<ge> 1.1.
+
+=item C<clustersize>
+
+C<qcow2> only:
+Change the qcow2 cluster size. The default is 65536 (bytes) and
+this settig may be any power of two between 512 and 2097152.
+
+=back
+
+Note that this call does not add the new disk to the handle. You
+may need to call C<guestfs_add_drive_opts> separately." };
+
]
(* daemon_functions are any functions which cause some action
@@ -11739,7 +11787,7 @@ let fish_commands = [
This creates an empty (zeroed) file of the given size, and then adds
so it can be further examined.
-For more advanced image creation, see L<qemu-img(1)> utility.
+For more advanced image creation, see L</disk-create>.
Size can be specified using standard suffixes, eg. C<1M>.
@@ -11983,7 +12031,7 @@ not assigned to the file until they are needed. Sparse disk
files
only use space when written to, but they are slower and there is a
danger you could run out of real disk space during a write operation.
-For more advanced image creation, see L<qemu-img(1)> utility.
+For more advanced image creation, see L</disk-create>.
Size can be specified using standard suffixes, eg. C<1M>.
diff --git a/gobject/Makefile.inc b/gobject/Makefile.inc
index 7024754..7a8493a 100644
--- a/gobject/Makefile.inc
+++ b/gobject/Makefile.inc
@@ -52,6 +52,7 @@ guestfs_gobject_headers= \
include/guestfs-gobject/optargs-mount_local.h \
include/guestfs-gobject/optargs-umount_local.h \
include/guestfs-gobject/optargs-add_drive_scratch.h \
+ include/guestfs-gobject/optargs-disk_create.h \
include/guestfs-gobject/optargs-is_file.h \
include/guestfs-gobject/optargs-is_dir.h \
include/guestfs-gobject/optargs-umount.h \
@@ -127,6 +128,7 @@ guestfs_gobject_sources= \
src/optargs-mount_local.c \
src/optargs-umount_local.c \
src/optargs-add_drive_scratch.c \
+ src/optargs-disk_create.c \
src/optargs-is_file.c \
src/optargs-is_dir.c \
src/optargs-umount.c \
diff --git a/po/POTFILES b/po/POTFILES
index c6277e9..76d1b1d 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -170,6 +170,7 @@ gobject/src/optargs-copy_device_to_device.c
gobject/src/optargs-copy_device_to_file.c
gobject/src/optargs-copy_file_to_device.c
gobject/src/optargs-copy_file_to_file.c
+gobject/src/optargs-disk_create.c
gobject/src/optargs-e2fsck.c
gobject/src/optargs-fstrim.c
gobject/src/optargs-grep.c
@@ -267,6 +268,7 @@ src/canonical-name.c
src/cleanup.c
src/command.c
src/conn-socket.c
+src/create.c
src/dbdump.c
src/drives.c
src/errnostring-gperf.c
diff --git a/src/Makefile.am b/src/Makefile.am
index ba02061..4475281 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -90,6 +90,7 @@ libguestfs_la_SOURCES = \
canonical-name.c \
command.c \
conn-socket.c \
+ create.c \
dbdump.c \
drives.c \
errors.c \
diff --git a/src/create.c b/src/create.c
new file mode 100644
index 0000000..18c4402
--- /dev/null
+++ b/src/create.c
@@ -0,0 +1,319 @@
+/* libguestfs
+ * Copyright (C) 2012 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; 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 <stdint.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <errno.h>
+
+#include "guestfs.h"
+#include "guestfs-internal.h"
+#include "guestfs-internal-actions.h"
+#include "guestfs_protocol.h"
+
+static int disk_create_raw (guestfs_h *g, const char *filename, int64_t size, const
struct guestfs_disk_create_argv *optargs);
+static int disk_create_qcow2 (guestfs_h *g, const char *filename, int64_t size, const
char *backingfile, const struct guestfs_disk_create_argv *optargs);
+static char *qemu_escape_param (guestfs_h *g, const char *param);
+
+int
+guestfs__disk_create (guestfs_h *g, const char *filename,
+ const char *format, int64_t size,
+ const struct guestfs_disk_create_argv *optargs)
+{
+ const char *backingfile;
+
+ backingfile = optargs->bitmask & GUESTFS_DISK_CREATE_BACKINGFILE_BITMASK ?
+ optargs->backingfile : NULL;
+
+ /* Ensure size is valid. */
+ if (backingfile) {
+ if (size != -1) {
+ error (g, _("if using a backing file, size must be passed as -1"));
+ return -1;
+ }
+ } else {
+ /* XXX Actually size == 0 could be valid, although not useful and
+ * it causes qemu to break.
+ */
+ if (size <= 0) {
+ error (g, _("invalid size: %" PRIi64), size);
+ return -1;
+ }
+ }
+
+ /* Now the format-specific code. */
+ if (STREQ (format, "raw")) {
+ if (backingfile) {
+ error (g, _("backingfile cannot be used for raw format disks"));
+ return -1;
+ }
+ if (disk_create_raw (g, filename, size, optargs) == -1)
+ return -1;
+ }
+ else if (STREQ (format, "qcow2")) {
+ if (disk_create_qcow2 (g, filename, size, backingfile, optargs) == -1)
+ return -1;
+ }
+ else {
+ /* Be conservative about what formats we support, since we don't
+ * want to make unlimited promises through the API. We can always
+ * add more later.
+ */
+ error (g, _("unsupported format '%s'"), format);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+disk_create_raw (guestfs_h *g, const char *filename, int64_t size,
+ const struct guestfs_disk_create_argv *optargs)
+{
+ int allocated = 0;
+ int fd;
+ struct stat statbuf;
+
+ /* backingfile parameter not present checked above */
+
+ if (optargs->bitmask & GUESTFS_DISK_CREATE_BACKINGFORMAT_BITMASK) {
+ error (g, _("backingformat parameter cannot be used with raw format"));
+ return -1;
+ }
+ if (optargs->bitmask & GUESTFS_DISK_CREATE_PREALLOCATION_BITMASK) {
+ if (STREQ (optargs->preallocation, "sparse"))
+ allocated = 0;
+ else if (STREQ (optargs->preallocation, "full"))
+ allocated = 1;
+ else {
+ error (g, _("invalid value for preallocation parameter '%s'"),
+ optargs->preallocation);
+ return -1;
+ }
+ }
+ if (optargs->bitmask & GUESTFS_DISK_CREATE_COMPAT_BITMASK) {
+ error (g, _("compat parameter cannot be used with raw format"));
+ return -1;
+ }
+ if (optargs->bitmask & GUESTFS_DISK_CREATE_CLUSTERSIZE_BITMASK) {
+ error (g, _("clustersize parameter cannot be used with raw format"));
+ return -1;
+ }
+
+ /* This version refuses the overwrite block devices or char devices.
+ * XXX It would be possible to make it work with block devices.
+ */
+ if (stat (filename, &statbuf) == 0) {
+ if (S_ISBLK (statbuf.st_mode)) {
+ error (g, _("refusing to overwrite block device '%s'"),
filename);
+ return -1;
+ }
+ if (S_ISCHR (statbuf.st_mode)) {
+ error (g, _("refusing to overwrite char device '%s'"),
filename);
+ return -1;
+ }
+ }
+
+ fd = open (filename, O_WRONLY|O_CREAT|O_NOCTTY|O_TRUNC|O_CLOEXEC, 0666);
+ if (fd == -1) {
+ perrorf (g, _("cannot create raw file: %s"), filename);
+ return -1;
+ }
+
+ if (!allocated) { /* Sparse file. */
+ if (ftruncate (fd, size) == -1) {
+ perrorf (g, _("%s: truncate"), filename);
+ close (fd);
+ unlink (filename);
+ return -1;
+ }
+ }
+ else { /* Allocated file. */
+#ifdef HAVE_POSIX_FALLOCATE
+ int err;
+
+ err = posix_fallocate (fd, 0, size);
+ if (err != 0) {
+ errno = err;
+ perrorf (g, _("%s: fallocate"), filename);
+ close (fd);
+ unlink (filename);
+ return -1;
+ }
+#else
+ /* Slow emulation of posix_fallocate on platforms which don't have it. */
+ char buffer[BUFSIZ];
+ size_t remaining = size;
+ size_t n;
+ ssize_t r;
+
+ memset (buffer, 0, sizeof buffer);
+
+ while (remaining > 0) {
+ n = remaining > sizeof buffer ? sizeof buffer : remaining;
+ r = write (fd, buffer, n);
+ if (r == -1) {
+ perrorf (g, _("%s: write"), filename);
+ close (fd);
+ unlink (filename);
+ return -1;
+ }
+ remaining -= r;
+ }
+#endif
+ }
+
+ if (close (fd) == -1) {
+ perrorf (g, _("%s: close"), filename);
+ unlink (filename);
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
http://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2 */
+static int
+is_power_of_2 (unsigned v)
+{
+ return v && ((v & (v - 1)) == 0);
+}
+
+static int
+disk_create_qcow2 (guestfs_h *g, const char *orig_filename, int64_t size,
+ const char *backingfile,
+ const struct guestfs_disk_create_argv *optargs)
+{
+ CLEANUP_FREE char *filename = NULL;
+ const char *backingformat = NULL;
+ const char *preallocation = NULL;
+ const char *compat = NULL;
+ int clustersize = -1;
+ CLEANUP_FREE_STRINGSBUF DECLARE_STRINGSBUF (optionsv);
+ CLEANUP_CMD_CLOSE struct command *cmd = guestfs___new_command (g);
+ int r;
+
+ /* If the filename is something like "file:foo" then qemu-img will
+ * try to interpret that as "foo" in the file:/// protocol. To
+ * avoid that, if the path is relative prefix it with "./" since
+ * qemu-img won't try to interpret such a path.
+ */
+ if (orig_filename[0] != '/')
+ filename = safe_asprintf (g, "./%s", orig_filename);
+ else
+ filename = safe_strdup (g, orig_filename);
+
+ if (optargs->bitmask & GUESTFS_DISK_CREATE_BACKINGFORMAT_BITMASK) {
+ backingformat = optargs->backingformat;
+ if (STRNEQ (backingformat, "raw") && STRNEQ (backingformat,
"qcow2")) {
+ error (g, _("invalid value for backingformat parameter '%s'"),
+ backingformat);
+ return -1;
+ }
+ }
+ if (optargs->bitmask & GUESTFS_DISK_CREATE_PREALLOCATION_BITMASK) {
+ preallocation = optargs->preallocation;
+ if (STRNEQ (preallocation, "off") && STRNEQ (preallocation,
"metadata")) {
+ error (g, _("invalid value for preallocation parameter '%s'"),
+ preallocation);
+ return -1;
+ }
+ }
+ if (optargs->bitmask & GUESTFS_DISK_CREATE_COMPAT_BITMASK) {
+ compat = optargs->compat;
+ if (STRNEQ (compat, "0.10") && STRNEQ (compat, "1.1")) {
+ error (g, _("invalid value for compat parameter '%s'"), compat);
+ return -1;
+ }
+ }
+ if (optargs->bitmask & GUESTFS_DISK_CREATE_CLUSTERSIZE_BITMASK) {
+ clustersize = optargs->clustersize;
+ if (clustersize < 512 || clustersize > 2097152 ||
+ !is_power_of_2 ((unsigned) clustersize)) {
+ error (g, _("invalid value for clustersize parameter '%d'"),
+ clustersize);
+ return -1;
+ }
+ }
+
+ /* Assemble the qemu-img command line. */
+ guestfs___cmd_add_arg (cmd, "qemu-img");
+ guestfs___cmd_add_arg (cmd, "create");
+ guestfs___cmd_add_arg (cmd, "-f");
+ guestfs___cmd_add_arg (cmd, "qcow2");
+
+ /* -o parameter. */
+ if (backingfile) {
+ CLEANUP_FREE char *p = qemu_escape_param (g, backingfile);
+ guestfs___add_sprintf (g, &optionsv, "backing_file=%s", p);
+ }
+ if (backingformat)
+ guestfs___add_sprintf (g, &optionsv, "backing_fmt=%s", backingformat);
+ if (preallocation)
+ guestfs___add_sprintf (g, &optionsv, "preallocation=%s",
preallocation);
+ if (compat)
+ guestfs___add_sprintf (g, &optionsv, "compat=%s", compat);
+ if (clustersize >= 0)
+ guestfs___add_sprintf (g, &optionsv, "cluster_size=%d", clustersize);
+ guestfs___end_stringsbuf (g, &optionsv);
+
+ if (optionsv.size > 1) {
+ CLEANUP_FREE char *options = guestfs___join_strings (",", optionsv.argv);
+ guestfs___cmd_add_arg (cmd, "-o");
+ guestfs___cmd_add_arg (cmd, options);
+ }
+
+ /* Complete the command line. */
+ guestfs___cmd_add_arg (cmd, filename);
+ if (size >= 0)
+ guestfs___cmd_add_arg_format (cmd, "%" PRIi64, size);
+
+ r = guestfs___cmd_run (cmd);
+ if (!WIFEXITED (r) || WEXITSTATUS (r) != 0) {
+ guestfs___external_command_failed (g, r, "qemu-img", orig_filename);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* XXX Duplicated in launch-direct.c. */
+static char *
+qemu_escape_param (guestfs_h *g, const char *param)
+{
+ size_t i, len = strlen (param);
+ char *p, *ret;
+
+ ret = p = safe_malloc (g, len*2 + 1); /* max length of escaped name*/
+ for (i = 0; i < len; ++i) {
+ *p++ = param[i];
+ if (param[i] == ',')
+ *p++ = ',';
+ }
+ *p = '\0';
+
+ return ret;
+}
diff --git a/tests/create/Makefile.am b/tests/create/Makefile.am
new file mode 100644
index 0000000..cdcecd2
--- /dev/null
+++ b/tests/create/Makefile.am
@@ -0,0 +1,27 @@
+# libguestfs
+# Copyright (C) 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
+
+TESTS = \
+ test-disk-create.sh
+
+TESTS_ENVIRONMENT = \
+ $(top_builddir)/run --test
+
+EXTRA_DIST = \
+ $(TESTS)
diff --git a/tests/create/test-disk-create.sh b/tests/create/test-disk-create.sh
new file mode 100755
index 0000000..b287233
--- /dev/null
+++ b/tests/create/test-disk-create.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+# Copyright (C) 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.
+
+# Test the disk-create API.
+
+export LANG=C
+
+set -e
+
+rm -f disk*.img file:*.img
+
+# XXX We should also test failure paths.
+
+../../fish/guestfish <<EOF
+ disk-create disk1.img raw 256K
+ disk-create disk2.img raw 256K preallocation:sparse
+ disk-create disk3.img raw 256K preallocation:full
+ disk-create disk4.img qcow2 256K
+ disk-create disk5.img qcow2 256K preallocation:off
+ disk-create disk6.img qcow2 256K preallocation:metadata
+ disk-create disk7.img qcow2 256K compat:1.1
+ disk-create disk8.img qcow2 256K clustersize:128K
+ disk-create disk9.img qcow2 -1 backingfile:disk1.img compat:1.1
+ disk-create disk10.img qcow2 -1 backingfile:disk2.img backingformat:raw
+ disk-create disk11.img qcow2 -1 backingfile:disk4.img backingformat:qcow2
+
+ # Some annoying corner-cases in qemu-img.
+ disk-create disk:0.img qcow2 256K
+ disk-create file:0.img qcow2 256K
+ disk-create disk,0.img qcow2 256K
+ disk-create disk,,0.img qcow2 256K
+EOF
+
+rm disk*.img file:*.img
--
1.8.4.2