enable build for ocaml bytecode
by Olaf Hering
This is a first attempt to build libguestfs with just a ocaml bytecode
compiler. The three tools written in ocaml will be build only when an
ocamlopt compiler is available.
Olaf
---
Makefile.am | 5 ++++-
configure.ac | 2 ++
ocaml/Makefile.am | 20 +++++++++++++++++---
resize/Makefile.am | 2 +-
sparsify/Makefile.am | 2 +-
sysprep/Makefile.am | 2 +-
6 files changed, 26 insertions(+), 7 deletions(-)
Index: libguestfs-1.20.1/Makefile.am
===================================================================
--- libguestfs-1.20.1.orig/Makefile.am
+++ libguestfs-1.20.1/Makefile.am
@@ -74,7 +74,10 @@ if HAVE_PERL
SUBDIRS += perl perl/examples
endif
if HAVE_OCAML
-SUBDIRS += ocaml ocaml/examples
+SUBDIRS += ocaml
+endif
+if HAVE_OCAMLOPT
+SUBDIRS += ocaml/examples
endif
if HAVE_PYTHON
SUBDIRS += python python/examples
Index: libguestfs-1.20.1/configure.ac
===================================================================
--- libguestfs-1.20.1.orig/configure.ac
+++ libguestfs-1.20.1/configure.ac
@@ -865,6 +865,8 @@ AS_IF([test "x$enable_ocaml" != "xno"],
])
AM_CONDITIONAL([HAVE_OCAML],
[test "x$OCAMLC" != "xno" && test "x$OCAMLFIND" != "xno"])
+AM_CONDITIONAL([HAVE_OCAMLOPT],
+ [test "x$OCAMLOPT" != "xno" && test "x$OCAMLC" != "xno" && test "x$OCAMLFIND" != "xno"])
AM_CONDITIONAL([HAVE_OCAMLDOC],
[test "x$OCAMLDOC" != "xno"])
Index: libguestfs-1.20.1/ocaml/Makefile.am
===================================================================
--- libguestfs-1.20.1.orig/ocaml/Makefile.am
+++ libguestfs-1.20.1/ocaml/Makefile.am
@@ -43,10 +43,20 @@ CLEANFILES += t/*.cmi t/*.cmo t/*.cmx t/
if HAVE_OCAML
+DATA_HOOK_FILES = META *.so *.a *.cma \
+ *.cmi $(srcdir)/*.mli
+if HAVE_OCAMLOPT
+DATA_HOOK_FILES += *.cmx *.cmxa
+endif
+
OCAMLCFLAGS = -g -warn-error CDEFLMPSUVYZX
OCAMLOPTFLAGS = $(OCAMLCFLAGS)
-noinst_DATA = mlguestfs.cma mlguestfs.cmxa META
+noinst_DATA = mlguestfs.cma
+if HAVE_OCAMLOPT
+noinst_DATA += mlguestfs.cmxa
+endif
+noinst_DATA += META
# Build the C part into a library, so that automake handles the C
# compilation step for us. Note that we don't directly use this
@@ -101,9 +111,13 @@ TESTS = run-bindtests \
$(patsubst %,%.opt,$(test_progs)))
noinst_DATA += \
- bindtests.bc bindtests.opt \
+ bindtests.bc
+if HAVE_OCAMLOPT
+noinst_DATA += \
+ bindtests.opt \
$(test_progs:%=%.bc) \
$(test_progs:%=%.opt)
+endif
bindtests.bc: bindtests.cmo mlguestfs.cma
mkdir -p t
@@ -217,7 +231,7 @@ install-data-hook:
$(OCAMLFIND) install \
-ldconf ignore -destdir $(DESTDIR)$(OCAMLLIB) \
guestfs \
- META *.so *.a *.cma *.cmx *.cmxa *.cmi $(srcdir)/*.mli
+ $(DATA_HOOK_FILES)
rm $(DESTDIR)$(OCAMLLIB)/guestfs/bindtests.*
rm $(DESTDIR)$(OCAMLLIB)/guestfs/libguestfsocaml.a
Index: libguestfs-1.20.1/resize/Makefile.am
===================================================================
--- libguestfs-1.20.1.orig/resize/Makefile.am
+++ libguestfs-1.20.1/resize/Makefile.am
@@ -40,7 +40,7 @@ SOURCES = \
resize_utils.ml \
resize_utils_tests.ml
-if HAVE_OCAML
+if HAVE_OCAMLOPT
# Note this list must be in dependency order.
OBJECTS = \
Index: libguestfs-1.20.1/sparsify/Makefile.am
===================================================================
--- libguestfs-1.20.1.orig/sparsify/Makefile.am
+++ libguestfs-1.20.1/sparsify/Makefile.am
@@ -36,7 +36,7 @@ SOURCES = \
sparsify_gettext.ml \
sparsify_utils.ml
-if HAVE_OCAML
+if HAVE_OCAMLOPT
# Note this list must be in dependency order.
OBJECTS = \
Index: libguestfs-1.20.1/sysprep/Makefile.am
===================================================================
--- libguestfs-1.20.1.orig/sysprep/Makefile.am
+++ libguestfs-1.20.1/sysprep/Makefile.am
@@ -86,7 +86,7 @@ SOURCES = \
utils.ml \
utils.mli
-if HAVE_OCAML
+if HAVE_OCAMLOPT
# Note this list must be in dependency order.
OBJECTS = \
10 years, 9 months
[PATCH] sysprep: handle distro specific sysv scripts
by Olaf Hering
Currently firstboot would only work on redhat-based images.
Handle redhat-based, suse-based and debian guests, error out in case of an
unknown distro.
Update firstboot.sh:
- make sure scripts exists and can be executed
- add LSB header to avoid insserv warnings later on
- run script only if called with "start"
Update functions, pass only required options.
Signed-off-by: Olaf Hering <olaf(a)aepfle.de>
diff --git a/sysprep/firstboot.ml b/sysprep/firstboot.ml
index 97cd8a9..c5296a1 100644
--- a/sysprep/firstboot.ml
+++ b/sysprep/firstboot.ml
@@ -28,14 +28,35 @@ let firstboot_dir = "/usr/lib/virt-sysprep"
let firstboot_sh = sprintf "\
#!/bin/sh -
+### BEGIN INIT INFO
+# Provides: virt-sysprep
+# Required-Start: $null
+# Should-Start: $all
+# Required-Stop: $null
+# Should-Stop: $all
+# Default-Start: 2 3 5
+# Default-Stop: 0 1 6
+# Short-Description: Start scripts to run once at next boot
+# Description: Start scripts to run once at next boot
+# These scripts run the first time the guest boots,
+# and then are deleted. Output or errors from the scripts
+# are written to ~root/virt-sysprep-firstboot.log.
+### END INIT INFO
+
d=%s/scripts
logfile=~root/virt-sysprep-firstboot.log
-for f in $d/* ; do
- echo '=== Running' $f '===' >>$logfile
- $f >>$logfile 2>&1
- rm $f
-done
+if test \"$1\" = \"start\"
+then
+ for f in $d/* ; do
+ if test -x \"$f\"
+ then
+ echo '=== Running' $f '===' >>$logfile
+ $f >>$logfile 2>&1
+ rm -f $f
+ fi
+ done
+fi
" firstboot_dir
let firstboot_service = sprintf "\
@@ -56,7 +77,7 @@ WantedBy=default.target
let failed fs =
ksprintf (fun msg -> failwith (s_"firstboot: failed: " ^ msg)) fs
-let rec install_service g root =
+let rec install_service g distro =
g#mkdir_p firstboot_dir;
g#mkdir_p (sprintf "%s/scripts" firstboot_dir);
g#write (sprintf "%s/firstboot.sh" firstboot_dir) firstboot_sh;
@@ -64,18 +85,18 @@ let rec install_service g root =
(* systemd, else assume sysvinit *)
if g#is_dir "/etc/systemd" then
- install_systemd_service g root
+ install_systemd_service g
else
- install_sysvinit_service g root
+ install_sysvinit_service g distro
(* Install the systemd firstboot service, if not installed already. *)
-and install_systemd_service g root =
+and install_systemd_service g =
g#write (sprintf "%s/firstboot.service" firstboot_dir) firstboot_service;
g#mkdir_p "/etc/systemd/system/default.target.wants";
g#ln_sf (sprintf "%s/firstboot.service" firstboot_dir)
"/etc/systemd/system/default.target.wants"
-and install_sysvinit_service g root =
+and install_sysvinit_redhat g =
g#mkdir_p "/etc/rc.d/rc2.d";
g#mkdir_p "/etc/rc.d/rc3.d";
g#mkdir_p "/etc/rc.d/rc5.d";
@@ -86,12 +107,51 @@ and install_sysvinit_service g root =
g#ln_sf (sprintf "%s/firstboot.sh" firstboot_dir)
"/etc/rc.d/rc5.d/99virt-sysprep-firstboot"
+(* Make firstboot.sh look like a runlevel script to avoid insserv warnings. *)
+and install_sysvinit_suse g =
+ g#mkdir_p "/etc/init.d/rc2.d";
+ g#mkdir_p "/etc/init.d/rc3.d";
+ g#mkdir_p "/etc/init.d/rc5.d";
+ g#ln_sf (sprintf "%s/firstboot.sh" firstboot_dir)
+ "/etc/init.d/virt-sysprep-firstboot";
+ g#ln_sf "../virt-sysprep-firstboot"
+ "/etc/init.d/rc2.d/S99virt-sysprep-firstboot";
+ g#ln_sf "../virt-sysprep-firstboot"
+ "/etc/init.d/rc3.d/S99virt-sysprep-firstboot";
+ g#ln_sf "../virt-sysprep-firstboot"
+ "/etc/init.d/rc5.d/S99virt-sysprep-firstboot"
+
+and install_sysvinit_debian g =
+ g#mkdir_p "/etc/init.d";
+ g#mkdir_p "/etc/rc2.d";
+ g#mkdir_p "/etc/rc3.d";
+ g#mkdir_p "/etc/rc5.d";
+ g#ln_sf (sprintf "%s/firstboot.sh" firstboot_dir)
+ "/etc/init.d/virt-sysprep-firstboot";
+ g#ln_sf "/etc/init.d/virt-sysprep-firstboot"
+ "/etc/rc2.d/S99virt-sysprep-firstboot";
+ g#ln_sf "/etc/init.d/virt-sysprep-firstboot"
+ "/etc/rc3.d/S99virt-sysprep-firstboot";
+ g#ln_sf "/etc/init.d/virt-sysprep-firstboot"
+ "/etc/rc5.d/S99virt-sysprep-firstboot"
+
+and install_sysvinit_service g distro =
+ match distro with
+ | ("fedora"|"rhel"|"centos"|"scientificlinux"|"redhat-based") ->
+ install_sysvinit_redhat g
+ | ("opensuse"|"sles"|"suse-based") ->
+ install_sysvinit_suse g
+ | "debian" ->
+ install_sysvinit_debian g
+ | _ ->
+ failed "guest type %s is not supported" distro
+
let add_firstboot_script g root id content =
let typ = g#inspect_get_type root in
let distro = g#inspect_get_distro root in
match typ, distro with
| "linux", _ ->
- install_service g root;
+ install_service g distro;
let t = Int64.of_float (Unix.time ()) in
let r = string_random8 () in
let filename = sprintf "%s/scripts/%Ld-%s-%s" firstboot_dir t r id in
--
1.7.12
10 years, 11 months
Re: [Libguestfs] failed to compile 1.18.11 on fedora18
by Richard W.M. Jones
On Sat, Mar 23, 2013 at 09:53:16PM +0800, javaon wrote:
> hi richard,
>
> I'm trying compiling libguestfs 1.18.11 on fedora 18. But I had the following error during make:
>
> dbdump.c:56:25: error: unknown type name 'guestfs___db_dump_callback'
[..]
> Do you know what I can do to fix it? Thanks!
Install 'hivex-devel' and re-run ./configure.
Rich.
--
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
virt-top is 'top' for virtual machines. Tiny program with many
powerful monitoring features, net stats, disk stats, logging, etc.
http://people.redhat.com/~rjones/virt-top
11 years, 8 months
GSoC project to deal with bootloaders
by Kashyap Chamarthy
Hi Matt,
I was discussing with Rich on #libguestfs about using the supermin appliance to 'package'
all the 3 outputs (kernel, initrd, root) the fedora appliance [1] into a raw/qcow2 image.
He then suggested, it's not easy,but do-able:
- put the kernel and initrd onto the root disk, and then install a bootloader;
- and then installing 'syslinux' on disk images
And said it'd be a nice GSoC project to deal with bootloaders.
If you have time to mentor, you might want to add some details under qemu/libvirt GSoC
umbrella - http://wiki.qemu.org/Google_Summer_of_Code_2013
Thank You.
[1] http://git.annexia.org/?p=a-fedora-appliance.git;a=summary
--
/kashyap
11 years, 8 months
[PATCH] lib: Add direct support for the NBD (Network Block Device) protocol.
by Richard W.M. Jones
From: "Richard W.M. Jones" <rjones(a)redhat.com>
You can now add remote NBD drives using:
><fs> add-drive "" format:raw protocol:nbd server:localhost
(Note that you also need to add port:NNNN if the server is running on
a non-standard port).
The corresponding qemu-nbd service can be started by doing:
qemu-nbd disk.img -t
This commit also adds a test.
---
Makefile.am | 1 +
README | 2 +
configure.ac | 1 +
generator/actions.ml | 36 +++++-
src/drives.c | 302 ++++++++++++++++++++++++++++++++++++-------------
src/guestfs-internal.h | 19 +++-
src/guestfs.pod | 35 ++++++
src/launch-appliance.c | 81 +++++++------
src/launch-libvirt.c | 259 +++++++++++++++++++++++++++++-------------
tests/nbd/Makefile.am | 26 +++++
tests/nbd/test-nbd.pl | 78 +++++++++++++
11 files changed, 644 insertions(+), 196 deletions(-)
create mode 100644 tests/nbd/Makefile.am
create mode 100755 tests/nbd/test-nbd.pl
diff --git a/Makefile.am b/Makefile.am
index 1bb5ce0..9c649a9 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -56,6 +56,7 @@ SUBDIRS += tests/rsync
SUBDIRS += tests/bigdirs
SUBDIRS += tests/disk-labels
SUBDIRS += tests/hotplug
+SUBDIRS += tests/nbd
SUBDIRS += tests/regressions
endif
diff --git a/README b/README
index 4f2fb7f..5284d58 100644
--- a/README
+++ b/README
@@ -155,6 +155,8 @@ The full requirements are described below.
+--------------+-------------+---+-----------------------------------------+
| static glibc | | O | Used for testing only. |
+--------------+-------------+---+-----------------------------------------+
+| qemu-nbd | | O | Used for testing only. |
++--------------+-------------+---+-----------------------------------------+
| findlib | | O | For the OCaml bindings. |
+--------------+-------------+---+-----------------------------------------+
| ocaml-gettext| | O | For localizing OCaml virt-* tools. |
diff --git a/configure.ac b/configure.ac
index 7059867..64b4513 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1574,6 +1574,7 @@ AC_CONFIG_FILES([Makefile
tests/md/Makefile
tests/mount-local/Makefile
tests/mountable/Makefile
+ tests/nbd/Makefile
tests/ntfsclone/Makefile
tests/parallel/Makefile
tests/protocol/Makefile
diff --git a/generator/actions.ml b/generator/actions.ml
index 958c606..30e2482 100644
--- a/generator/actions.ml
+++ b/generator/actions.ml
@@ -1247,7 +1247,7 @@ not all belong to a single logical operating system
{ defaults with
name = "add_drive";
- style = RErr, [String "filename"], [OBool "readonly"; OString "format"; OString "iface"; OString "name"; OString "label"];
+ style = RErr, [String "filename"], [OBool "readonly"; OString "format"; OString "iface"; OString "name"; OString "label"; OString "protocol"; OString "server"; OInt "port"];
once_had_no_optargs = true;
blocking = false;
fish_alias = ["add"];
@@ -1319,6 +1319,40 @@ the drive will also be named C</dev/disk/guestfs/I<label>>.
See L<guestfs(3)/DISK LABELS>.
+=item C<protocol>
+
+The optional protocol argument can be used to select an alternate
+source protocol:
+
+=over 4
+
+=item C<protocol = \"file\">
+
+C<filename> is interpreted as a local file or device.
+This is the default if the optional protocol parameter
+is omitted.
+
+=item C<protocol = \"nbd\">
+
+Connect to the Network Block Device server at C<server:port>.
+
+See: L<guestfs(3)/NETWORK BLOCK DEVICES>.
+
+=back
+
+=item C<server>
+
+For protocols which require access to a remote server, this
+is the name of the server.
+
+=item C<port>
+
+For protocols which require access to a remote server, this
+is the port number of the service.
+
+If not specified, this defaults to the standard port for
+the protocol, eg. 10809 when C<protocol> is C<\"nbd\">.
+
=back" };
{ defaults with
diff --git a/src/drives.c b/src/drives.c
index d082193..c3199e8 100644
--- a/src/drives.c
+++ b/src/drives.c
@@ -30,6 +30,8 @@
#include <inttypes.h>
#include <unistd.h>
#include <fcntl.h>
+#include <netdb.h>
+#include <arpa/inet.h>
#include <assert.h>
#include "c-ctype.h"
@@ -41,21 +43,51 @@
/* Create and free the 'drive' struct. */
static struct drive *
-create_drive_struct (guestfs_h *g, const char *path,
- bool readonly, const char *format,
- const char *iface, const char *name,
- const char *disk_label,
- bool use_cache_none)
+create_drive_file (guestfs_h *g, const char *path,
+ bool readonly, const char *format,
+ const char *iface, const char *name,
+ const char *disk_label,
+ bool use_cache_none)
{
struct drive *drv = safe_malloc (g, sizeof (struct drive));
- drv->path = safe_strdup (g, path);
+ drv->protocol = drive_protocol_file;
+ drv->u.path = safe_strdup (g, path);
+
drv->readonly = readonly;
drv->format = format ? safe_strdup (g, format) : NULL;
drv->iface = iface ? safe_strdup (g, iface) : NULL;
drv->name = name ? safe_strdup (g, name) : NULL;
drv->disk_label = disk_label ? safe_strdup (g, disk_label) : NULL;
drv->use_cache_none = use_cache_none;
+
+ drv->priv = drv->free_priv = NULL;
+
+ return drv;
+}
+
+static struct drive *
+create_drive_nbd (guestfs_h *g,
+ const char *server, int port, const char *exportname,
+ bool readonly, const char *format,
+ const char *iface, const char *name,
+ const char *disk_label,
+ bool use_cache_none)
+{
+ struct drive *drv = safe_malloc (g, sizeof (struct drive));
+
+ drv->protocol = drive_protocol_nbd;
+ drv->u.nbd.server = safe_strdup (g, server);
+ drv->u.nbd.port = port;
+ drv->u.nbd.exportname = safe_strdup (g, exportname);
+
+ drv->readonly = readonly;
+ drv->format = format ? safe_strdup (g, format) : NULL;
+ drv->iface = iface ? safe_strdup (g, iface) : NULL;
+ drv->name = name ? safe_strdup (g, name) : NULL;
+ drv->disk_label = disk_label ? safe_strdup (g, disk_label) : NULL;
+ drv->use_cache_none = use_cache_none;
+
drv->priv = drv->free_priv = NULL;
return drv;
@@ -70,9 +102,9 @@ create_drive_struct (guestfs_h *g, const char *path,
* a null drive.
*/
static struct drive *
-create_null_drive_struct (guestfs_h *g, bool readonly, const char *format,
- const char *iface, const char *name,
- const char *disk_label)
+create_drive_dev_null (guestfs_h *g, bool readonly, const char *format,
+ const char *iface, const char *name,
+ const char *disk_label)
{
CLEANUP_FREE char *tmpfile = NULL;
int fd = -1;
@@ -107,30 +139,82 @@ create_null_drive_struct (guestfs_h *g, bool readonly, const char *format,
return NULL;
}
- return create_drive_struct (g, tmpfile, readonly, format, iface, name,
- disk_label, 0);
+ return create_drive_file (g, tmpfile, readonly, format, iface, name,
+ disk_label, 0);
}
static struct drive *
-create_dummy_drive_struct (guestfs_h *g)
+create_drive_dummy (guestfs_h *g)
{
/* A special drive struct that is used as a dummy slot for the appliance. */
- return create_drive_struct (g, "", 0, NULL, NULL, NULL, NULL, 0);
+ return create_drive_file (g, "", 0, NULL, NULL, NULL, NULL, 0);
}
static void
free_drive_struct (struct drive *drv)
{
- free (drv->path);
+ switch (drv->protocol) {
+ case drive_protocol_file:
+ free (drv->u.path);
+ break;
+ case drive_protocol_nbd:
+ free (drv->u.nbd.server);
+ free (drv->u.nbd.exportname);
+ break;
+ default:
+ abort ();
+ }
+
free (drv->format);
free (drv->iface);
free (drv->name);
free (drv->disk_label);
+
if (drv->priv && drv->free_priv)
drv->free_priv (drv->priv);
+
free (drv);
}
+/* Convert a struct drive to a string for debugging. The caller
+ * must free this string.
+ */
+static char *
+drive_to_string (guestfs_h *g, const struct drive *drv)
+{
+ CLEANUP_FREE char *p = NULL;
+
+ switch (drv->protocol) {
+ case drive_protocol_file:
+ p = safe_asprintf (g, "path=%s", drv->u.path);
+ break;
+ case drive_protocol_nbd:
+ if (STREQ (drv->u.nbd.exportname, ""))
+ p = safe_asprintf (g, "nbd=%s:%d", drv->u.nbd.server, drv->u.nbd.port);
+ else
+ p = safe_asprintf (g, "nbd=%s:%d:exportname=%s",
+ drv->u.nbd.server, drv->u.nbd.port,
+ drv->u.nbd.exportname);
+ break;
+ default:
+ abort ();
+ }
+
+ return safe_asprintf
+ (g, "%s%s%s%s%s%s%s%s%s%s%s",
+ p,
+ drv->readonly ? " readonly" : "",
+ drv->format ? " format=" : "",
+ drv->format ? : "",
+ drv->iface ? " iface=" : "",
+ drv->iface ? : "",
+ drv->name ? " name=" : "",
+ drv->name ? : "",
+ drv->disk_label ? " label=" : "",
+ drv->disk_label ? : "",
+ drv->use_cache_none ? " cache=none" : "");
+}
+
/* Add struct drive to the g->drives vector at the given index. */
static void
add_drive_to_handle_at (guestfs_h *g, struct drive *d, size_t drv_index)
@@ -162,7 +246,7 @@ guestfs___add_dummy_appliance_drive (guestfs_h *g)
{
struct drive *drv;
- drv = create_dummy_drive_struct (g);
+ drv = create_drive_dummy (g);
add_drive_to_handle (g, drv);
}
@@ -183,6 +267,48 @@ guestfs___free_drives (guestfs_h *g)
g->nr_drives = 0;
}
+/* The low-level function that adds a drive. */
+static int
+add_drive (guestfs_h *g, struct drive *drv)
+{
+ size_t i, drv_index;
+
+ if (g->state == CONFIG) {
+ /* Not hotplugging, so just add it to the handle. */
+ add_drive_to_handle (g, drv);
+ return 0;
+ }
+
+ /* ... else, hotplugging case. */
+ if (!g->attach_ops || !g->attach_ops->hot_add_drive) {
+ error (g, _("the current attach-method does not support hotplugging drives"));
+ return -1;
+ }
+
+ if (!drv->disk_label) {
+ error (g, _("'label' is required when hotplugging drives"));
+ return -1;
+ }
+
+ /* Get the first free index, or add it at the end. */
+ drv_index = g->nr_drives;
+ for (i = 0; i < g->nr_drives; ++i)
+ if (g->drives[i] == NULL)
+ drv_index = i;
+
+ /* Hot-add the drive. */
+ if (g->attach_ops->hot_add_drive (g, drv, drv_index) == -1)
+ return -1;
+
+ add_drive_to_handle_at (g, drv, drv_index);
+
+ /* Call into the appliance to wait for the new drive to appear. */
+ if (guestfs_internal_hot_add_drive (g, drv->disk_label) == -1)
+ return -1;
+
+ return 0;
+}
+
/* cache=none improves reliability in the event of a host crash.
*
* However this option causes qemu to try to open the file with
@@ -263,46 +389,35 @@ valid_disk_label (const char *str)
return 1;
}
-/* The low-level function that adds a drive. */
+/* Check the server (hostname) is reasonable. */
static int
-add_drive (guestfs_h *g, struct drive *drv)
+valid_server (const char *str)
{
- size_t i, drv_index;
+ size_t len = strlen (str);
- if (g->state == CONFIG) {
- /* Not hotplugging, so just add it to the handle. */
- add_drive_to_handle (g, drv);
+ if (len == 0 || len > 255)
return 0;
- }
-
- /* ... else, hotplugging case. */
- if (!g->attach_ops || !g->attach_ops->hot_add_drive) {
- error (g, _("the current attach-method does not support hotplugging drives"));
- return -1;
- }
- if (!drv->disk_label) {
- error (g, _("'label' is required when hotplugging drives"));
- return -1;
+ while (len > 0) {
+ char c = *str++;
+ len--;
+ if (!c_isalnum (c) &&
+ c != '-' && c != '.' && c != ':' && c != '[' && c != ']')
+ return 0;
}
+ return 1;
+}
- /* Get the first free index, or add it at the end. */
- drv_index = g->nr_drives;
- for (i = 0; i < g->nr_drives; ++i)
- if (g->drives[i] == NULL)
- drv_index = i;
-
- /* Hot-add the drive. */
- if (g->attach_ops->hot_add_drive (g, drv, drv_index) == -1)
- return -1;
-
- add_drive_to_handle_at (g, drv, drv_index);
-
- /* Call into the appliance to wait for the new drive to appear. */
- if (guestfs_internal_hot_add_drive (g, drv->disk_label) == -1)
- return -1;
+static int
+nbd_port (void)
+{
+ struct servent *servent;
- return 0;
+ servent = getservbyname ("nbd", "tcp");
+ if (servent)
+ return ntohs (servent->s_port);
+ else
+ return 10809;
}
int
@@ -314,6 +429,9 @@ guestfs__add_drive_opts (guestfs_h *g, const char *filename,
const char *iface;
const char *name;
const char *disk_label;
+ const char *protocol;
+ const char *server;
+ int port;
int use_cache_none;
struct drive *drv;
@@ -324,15 +442,27 @@ guestfs__add_drive_opts (guestfs_h *g, const char *filename,
}
readonly = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK
- ? optargs->readonly : false;
+ ? optargs->readonly : false;
format = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_FORMAT_BITMASK
- ? optargs->format : NULL;
+ ? optargs->format : NULL;
iface = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_IFACE_BITMASK
- ? optargs->iface : NULL;
+ ? optargs->iface : NULL;
name = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_NAME_BITMASK
- ? optargs->name : NULL;
+ ? optargs->name : NULL;
disk_label = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_LABEL_BITMASK
- ? optargs->label : NULL;
+ ? optargs->label : NULL;
+ protocol = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_PROTOCOL_BITMASK
+ ? optargs->protocol : "file";
+ server = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_SERVER_BITMASK
+ ? optargs->server : NULL;
+ if (optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_PORT_BITMASK) {
+ port = optargs->port;
+ if (port <= 0 || port > 65535) {
+ error (g, _("invalid port number"));
+ return -1;
+ }
+ } else
+ port = 0;
if (format && !valid_format_iface (format)) {
error (g, _("%s parameter is empty or contains disallowed characters"),
@@ -348,28 +478,50 @@ guestfs__add_drive_opts (guestfs_h *g, const char *filename,
error (g, _("label parameter is empty, too long, or contains disallowed characters"));
return -1;
}
+ if (server && !valid_server (server)) {
+ error (g, _("server parameter is empty, too long, or contains disallowed characters"));
+ return -1;
+ }
- if (STREQ (filename, "/dev/null"))
- drv = create_null_drive_struct (g, readonly, format, iface, name,
- disk_label);
- else {
- /* For writable files, see if we can use cache=none. This also
- * checks for the existence of the file. For readonly we have
- * to do the check explicitly.
- */
- use_cache_none = readonly ? false : test_cache_none (g, filename);
- if (use_cache_none == -1)
- return -1;
-
- if (readonly) {
- if (access (filename, R_OK) == -1) {
- perrorf (g, "%s", filename);
+ if (STREQ (protocol, "file")) {
+ if (STREQ (filename, "/dev/null"))
+ drv = create_drive_dev_null (g, readonly, format, iface, name,
+ disk_label);
+ else {
+ /* For writable files, see if we can use cache=none. This also
+ * checks for the existence of the file. For readonly we have
+ * to do the check explicitly.
+ */
+ use_cache_none = readonly ? false : test_cache_none (g, filename);
+ if (use_cache_none == -1)
return -1;
+
+ if (readonly) {
+ if (access (filename, R_OK) == -1) {
+ perrorf (g, "%s", filename);
+ return -1;
+ }
}
- }
- drv = create_drive_struct (g, filename, readonly, format, iface, name,
+ drv = create_drive_file (g, filename, readonly, format, iface, name,
disk_label, use_cache_none);
+ }
+ }
+ else if (STREQ (protocol, "nbd")) {
+ if (!server) {
+ error (g, _("protocol nbd: missing server name"));
+ return -1;
+ }
+ if (port == 0)
+ port = nbd_port ();
+
+ drv = create_drive_nbd (g, server, port, filename,
+ readonly, format, iface, name,
+ disk_label, false);
+ }
+ else {
+ error (g, _("unknown protocol '%s'"), protocol);
+ return -1;
}
if (drv == NULL)
@@ -530,17 +682,7 @@ guestfs__debug_drives (guestfs_h *g)
count = 0;
ITER_DRIVES (g, i, drv) {
- ret[count++] =
- safe_asprintf (g, "path=%s%s%s%s%s%s%s%s%s",
- drv->path,
- drv->readonly ? " readonly" : "",
- drv->format ? " format=" : "",
- drv->format ? : "",
- drv->iface ? " iface=" : "",
- drv->iface ? : "",
- drv->name ? " name=" : "",
- drv->name ? : "",
- drv->use_cache_none ? " cache=none" : "");
+ ret[count++] = drive_to_string (g, drv);
}
ret[count] = NULL;
diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h
index f5db4cf..71f18a3 100644
--- a/src/guestfs-internal.h
+++ b/src/guestfs-internal.h
@@ -113,8 +113,22 @@ struct event {
};
/* Drives added to the handle. */
+enum drive_protocol {
+ drive_protocol_file,
+ drive_protocol_nbd,
+};
+union drive_source {
+ char *path; /* protocol = "file" */
+ struct { /* protocol = "nbd" */
+ char *server;
+ int port;
+ char *exportname;
+ } nbd;
+};
+
struct drive {
- char *path;
+ enum drive_protocol protocol;
+ union drive_source u;
bool readonly;
char *format;
@@ -123,7 +137,8 @@ struct drive {
char *disk_label;
bool use_cache_none;
- void *priv; /* Data used by attach method. */
+ /* Data used by the attach method. */
+ void *priv;
void (*free_priv) (void *);
};
diff --git a/src/guestfs.pod b/src/guestfs.pod
index af7f276..6736f07 100644
--- a/src/guestfs.pod
+++ b/src/guestfs.pod
@@ -637,6 +637,41 @@ Backends that support hotplugging do not require that you add
E<ge> 1 disk before calling launch. When hotplugging is supported
you don't need to add any disks.
+=head2 NETWORK BLOCK DEVICES
+
+Libguestfs can access Network Block Device (NBD) disks remotely.
+
+To do this, set the optional C<protocol> and C<server> parameters of
+L</guestfs_add_drive_opts> like this:
+
+ guestfs_add_drive_opts (g, "" /* export name - see below */,
+ GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw",
+ GUESTFS_ADD_DRIVE_OPTS_PROTOCOL, "nbd",
+ GUESTFS_ADD_DRIVE_OPTS_SERVER, "localhost",
+ -1);
+
+You can also specify the optional C<port> number if the NBD server is
+running on a non-default port.
+
+Notes:
+
+=over 4
+
+=item *
+
+The C<filename> parameter is the NBD export name. Use an empty string
+to mean the default export.
+
+Not all NBD servers support this feature, and the libvirt
+attach-method also does not support it.
+
+=item *
+
+The libvirt backend requires that you set the C<format> parameter of
+L</guestfs_add_drive_opts> accurately when you use writable NBD disks.
+
+=back
+
=head2 INSPECTION
Libguestfs has APIs for inspecting an unknown disk image to find out
diff --git a/src/launch-appliance.c b/src/launch-appliance.c
index 122f143..4b70cc1 100644
--- a/src/launch-appliance.c
+++ b/src/launch-appliance.c
@@ -931,36 +931,46 @@ qemu_supports_virtio_scsi (guestfs_h *g)
return g->app.virtio_scsi == 1;
}
+/* Convert a struct drive into a qemu -drive parameter. Note that if
+ * using virtio-scsi, then the code above adds a second -device
+ * parameter to connect this drive to the SCSI HBA, as is required by
+ * virtio-scsi.
+ */
static char *
qemu_drive_param (guestfs_h *g, const struct drive *drv, size_t index)
{
- size_t i;
- size_t len = 128;
- const char *p;
- char *r;
+ CLEANUP_FREE char *file = NULL, *escaped_file = NULL;
+ size_t i, len;
const char *iface;
+ char *p, *ret;
+
+ /* Make the file= parameter. */
+ switch (drv->protocol) {
+ case drive_protocol_file:
+ file = safe_strdup (g, drv->u.path);
+ break;
+ case drive_protocol_nbd:
+ if (STREQ (drv->u.nbd.exportname, ""))
+ file = safe_asprintf (g, "nbd:%s:%d", drv->u.nbd.server, drv->u.nbd.port);
+ else
+ file = safe_asprintf (g, "nbd:%s:%d:exportname=%s",
+ drv->u.nbd.server, drv->u.nbd.port,
+ drv->u.nbd.exportname);
+ break;
+ default:
+ abort ();
+ }
- len += strlen (drv->path) * 2; /* every "," could become ",," */
- if (drv->iface)
- len += strlen (drv->iface);
- if (drv->format)
- len += strlen (drv->format);
- if (drv->disk_label)
- len += strlen (drv->disk_label);
-
- r = safe_malloc (g, len);
-
- strcpy (r, "file=");
- i = 5;
-
- /* Copy the path in, escaping any "," as ",,". */
- for (p = drv->path; *p; p++) {
- if (*p == ',') {
- r[i++] = ',';
- r[i++] = ',';
- } else
- r[i++] = *p;
+ /* Escape the file= parameter. Every ',' becomes ',,'. */
+ len = strlen (file);
+ len = len*2 + 1; /* Maximum length of escaped filename + \0 */
+ p = escaped_file = safe_malloc (g, len);
+ for (i = 0; i < len; ++i) {
+ *p++ = file[i];
+ if (file[i] == ',')
+ *p++ = ',';
}
+ *p = '\0';
if (drv->iface)
iface = drv->iface;
@@ -969,17 +979,18 @@ qemu_drive_param (guestfs_h *g, const struct drive *drv, size_t index)
else
iface = "virtio";
- snprintf (&r[i], len-i, "%s%s%s%s%s%s,id=hd%zu,if=%s",
- drv->readonly ? ",snapshot=on" : "",
- drv->use_cache_none ? ",cache=none" : "",
- drv->format ? ",format=" : "",
- drv->format ? drv->format : "",
- drv->disk_label ? ",serial=" : "",
- drv->disk_label ? drv->disk_label : "",
- index,
- iface);
-
- return r; /* caller frees */
+ ret = safe_asprintf (g, "file=%s%s%s%s%s%s%s,id=hd%zu,if=%s",
+ escaped_file,
+ drv->readonly ? ",snapshot=on" : "",
+ drv->use_cache_none ? ",cache=none" : "",
+ drv->format ? ",format=" : "",
+ drv->format ? drv->format : "",
+ drv->disk_label ? ",serial=" : "",
+ drv->disk_label ? drv->disk_label : "",
+ index,
+ iface);
+
+ return ret;
}
/* https://rwmj.wordpress.com/2011/01/09/how-are-linux-drives-named-beyond-d... */
diff --git a/src/launch-libvirt.c b/src/launch-libvirt.c
index 58ccb9e..bef3910 100644
--- a/src/launch-libvirt.c
+++ b/src/launch-libvirt.c
@@ -101,12 +101,13 @@ xmlBufferDetach (xmlBufferPtr buf)
/* Pointed to by 'struct drive *' -> priv field. */
struct drive_libvirt {
- /* This is either the original path, made absolute. Or for readonly
- * drives, it is an (absolute path to) the overlay file that we
- * create. This is always non-NULL.
+ /* The drive that we actually add. If using an overlay, then this
+ * might be different from drive->protocol.
*/
- char *path;
- /* The format of priv->path. */
+ enum drive_protocol protocol;
+ union drive_source u;
+
+ /* The format of the drive we add. */
char *format;
};
@@ -135,8 +136,8 @@ static int is_custom_qemu (guestfs_h *g);
static int is_blk (const char *path);
static int random_chars (char *ret, size_t len);
static void ignore_errors (void *ignore, virErrorPtr ignore2);
-static char *make_qcow2_overlay (guestfs_h *g, const char *path, const char *format, const char *selinux_imagelabel);
-static int make_qcow2_overlay_for_drive (guestfs_h *g, struct drive *drv, const char *selinux_imagelabel);
+static char *make_qcow2_overlay (guestfs_h *g, const char *backing_device, const char *format, const char *selinux_imagelabel);
+static int make_drive_priv (guestfs_h *g, struct drive *drv, const char *selinux_imagelabel);
static void drive_free_priv (void *);
static void set_socket_create_context (guestfs_h *g);
static void clear_socket_create_context (guestfs_h *g);
@@ -235,18 +236,18 @@ launch_libvirt (guestfs_h *g, const char *libvirt_uri)
guestfs___launch_send_progress (g, 3);
TRACE0 (launch_build_libvirt_appliance_end);
- /* Create overlays for read-only drives and the appliance. This
- * works around lack of support for <transient/> disks in libvirt.
- * Note that appliance can be NULL if using the old-style appliance.
- */
+ /* Note that appliance can be NULL if using the old-style appliance. */
if (appliance) {
params.appliance_overlay = make_qcow2_overlay (g, appliance, "raw", NULL);
if (!params.appliance_overlay)
goto cleanup;
}
+ /* Set up the drv->priv part of the struct. A side-effect of this
+ * may be that we create qcow2 overlays for drives.
+ */
ITER_DRIVES (g, i, drv) {
- if (make_qcow2_overlay_for_drive (g, drv, g->virt_selinux_imagelabel) == -1)
+ if (make_drive_priv (g, drv, g->virt_selinux_imagelabel) == -1)
goto cleanup;
}
@@ -1044,7 +1045,8 @@ construct_libvirt_xml_disk (guestfs_h *g,
{
char drive_name[64] = "sd";
char scsi_target[64];
- struct drive_libvirt *drv_priv;
+ char port_str[64];
+ struct drive_libvirt *drv_priv = (struct drive_libvirt *) drv->priv;
CLEANUP_FREE char *format = NULL;
int is_host_device;
@@ -1057,47 +1059,86 @@ construct_libvirt_xml_disk (guestfs_h *g,
guestfs___drive_name (drv_index, &drive_name[2]);
snprintf (scsi_target, sizeof scsi_target, "%zu", drv_index);
- drv_priv = (struct drive_libvirt *) drv->priv;
-
- /* Change the libvirt XML according to whether the host path is
- * a device or a file. For devices, use:
- * <disk type=block device=disk>
- * <source dev=[path]>
- * For files, use:
- * <disk type=file device=disk>
- * <source file=[path]>
- */
- is_host_device = is_blk (drv_priv->path);
-
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "disk"));
XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "device",
BAD_CAST "disk"));
- if (!is_host_device) {
- XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
- BAD_CAST "file"));
- XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "source"));
- XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "file",
- BAD_CAST drv_priv->path));
- if (construct_libvirt_xml_disk_source_seclabel (g, xo) == -1)
+ switch (drv_priv->protocol) {
+ case drive_protocol_file:
+ /* Change the libvirt XML according to whether the host path is
+ * a device or a file. For devices, use:
+ * <disk type=block device=disk>
+ * <source dev=[path]>
+ * For files, use:
+ * <disk type=file device=disk>
+ * <source file=[path]>
+ */
+ is_host_device = is_blk (drv_priv->u.path);
+
+ if (!is_host_device) {
+ XMLERROR (-1,
+ xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
+ BAD_CAST "file"));
+
+ XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "source"));
+ XMLERROR (-1,
+ xmlTextWriterWriteAttribute (xo, BAD_CAST "file",
+ BAD_CAST drv_priv->u.path));
+ if (construct_libvirt_xml_disk_source_seclabel (g, xo) == -1)
+ return -1;
+ XMLERROR (-1, xmlTextWriterEndElement (xo));
+ }
+ else {
+ XMLERROR (-1,
+ xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
+ BAD_CAST "block"));
+
+ XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "source"));
+ XMLERROR (-1,
+ xmlTextWriterWriteAttribute (xo, BAD_CAST "dev",
+ BAD_CAST drv_priv->u.path));
+ if (construct_libvirt_xml_disk_source_seclabel (g, xo) == -1)
+ return -1;
+ XMLERROR (-1, xmlTextWriterEndElement (xo));
+ }
+ break;
+
+ case drive_protocol_nbd:
+ /* For NBD:
+ * <disk type=network device=disk>
+ * <source protocol=nbd>
+ * <host name='example.com' port='10809'/>
+ */
+ if (STRNEQ (drv_priv->u.nbd.exportname, "")) {
+ error (g, _("libvirt does not support nbd 'exportname' feature"));
return -1;
- XMLERROR (-1, xmlTextWriterEndElement (xo));
- }
- else {
+ }
+
XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
- BAD_CAST "block"));
+ BAD_CAST "network"));
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "source"));
XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "dev",
- BAD_CAST drv_priv->path));
+ xmlTextWriterWriteAttribute (xo, BAD_CAST "protocol",
+ BAD_CAST "nbd"));
+ XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "host"));
+ XMLERROR (-1,
+ xmlTextWriterWriteAttribute (xo, BAD_CAST "name",
+ BAD_CAST drv_priv->u.nbd.server));
+ snprintf (port_str, sizeof port_str, "%d", drv_priv->u.nbd.port);
+ XMLERROR (-1,
+ xmlTextWriterWriteAttribute (xo, BAD_CAST "port",
+ BAD_CAST port_str));
+ XMLERROR (-1, xmlTextWriterEndElement (xo));
if (construct_libvirt_xml_disk_source_seclabel (g, xo) == -1)
return -1;
XMLERROR (-1, xmlTextWriterEndElement (xo));
+ break;
+
+ default:
+ abort ();
}
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "target"));
@@ -1118,7 +1159,7 @@ construct_libvirt_xml_disk (guestfs_h *g,
xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
BAD_CAST drv_priv->format));
}
- else {
+ else if (drv_priv->protocol == drive_protocol_file) {
/* libvirt has disabled the feature of detecting the disk format,
* unless the administrator sets allow_disk_format_probing=1 in
* qemu.conf. There is no way to detect if this option is set, so we
@@ -1129,15 +1170,14 @@ construct_libvirt_xml_disk (guestfs_h *g,
* the users pass the format to libguestfs which will faithfully pass
* that to libvirt and this function won't be used.
*/
- format = guestfs_disk_format (g, drv_priv->path);
+ format = guestfs_disk_format (g, drv_priv->u.path);
if (!format)
return -1;
if (STREQ (format, "unknown")) {
- error (g, _("could not auto-detect the format of '%s'\n"
+ error (g, _("could not auto-detect the format.\n"
"If the format is known, pass the format to libguestfs, eg. using the\n"
- "'--format' option, or via the optional 'format' argument to 'add-drive'."),
- drv->path);
+ "'--format' option, or via the optional 'format' argument to 'add-drive'."));
return -1;
}
@@ -1145,6 +1185,13 @@ construct_libvirt_xml_disk (guestfs_h *g,
xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
BAD_CAST format));
}
+ else {
+ error (g, _("could not auto-detect the format when using a non-file protocol.\n"
+ "If the format is known, pass the format to libguestfs, eg. using the\n"
+ "'--format' option, or via the optional 'format' argument to 'add-drive'."));
+ return -1;
+ }
+
if (drv->use_cache_none) {
XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "cache",
@@ -1265,7 +1312,7 @@ construct_libvirt_xml_appliance (guestfs_h *g,
XMLERROR (-1, xmlTextWriterEndElement (xo));
/* We'd like to do this, but it's not supported by libvirt.
- * See construct_libvirt_xml_qemu_cmdline for the workaround.
+ * See make_drive_priv for the workaround.
*
* XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "transient"));
* XMLERROR (-1, xmlTextWriterEndElement (xo));
@@ -1397,18 +1444,22 @@ ignore_errors (void *ignore, virErrorPtr ignore2)
/* empty */
}
-/* Create a temporary qcow2 overlay on top of 'path'. */
+/* Create a temporary qcow2 overlay on top of 'backing_device', which is
+ * either an absolute path or a qemu device name.
+ */
static char *
-make_qcow2_overlay (guestfs_h *g, const char *path, const char *format,
- const char *selinux_imagelabel)
+make_qcow2_overlay (guestfs_h *g, const char *backing_device,
+ const char *format, const char *selinux_imagelabel)
{
char *tmpfile = NULL;
CLEANUP_CMD_CLOSE struct command *cmd = guestfs___new_command (g);
int r;
- /* Path must be absolute. */
- assert (path);
- assert (path[0] == '/');
+ /* Assert that backing_device is an absolute path, or it's an
+ * nbd device name.
+ */
+ assert (backing_device);
+ assert (backing_device[0] == '/' || STRPREFIX (backing_device, "nbd:"));
tmpfile = safe_asprintf (g, "%s/snapshot%d", g->tmpdir, ++g->unique);
@@ -1417,7 +1468,7 @@ make_qcow2_overlay (guestfs_h *g, const char *path, const char *format,
guestfs___cmd_add_arg (cmd, "-f");
guestfs___cmd_add_arg (cmd, "qcow2");
guestfs___cmd_add_arg (cmd, "-b");
- guestfs___cmd_add_arg (cmd, path);
+ guestfs___cmd_add_arg (cmd, backing_device);
if (format) {
guestfs___cmd_add_arg (cmd, "-o");
guestfs___cmd_add_arg_format (cmd, "backing_fmt=%s", format);
@@ -1427,7 +1478,7 @@ make_qcow2_overlay (guestfs_h *g, const char *path, const char *format,
if (r == -1)
goto error;
if (!WIFEXITED (r) || WEXITSTATUS (r) != 0) {
- guestfs___external_command_failed (g, r, "qemu-img create", path);
+ guestfs___external_command_failed (g, r, "qemu-img create", backing_device);
goto error;
}
@@ -1448,9 +1499,14 @@ make_qcow2_overlay (guestfs_h *g, const char *path, const char *format,
return NULL;
}
+/* This sets up the drv->priv structure, which contains the drive
+ * that we're actually going to add. If asked to make a drive readonly
+ * then because libvirt doesn't support <transient/> we have to add
+ * a qcow2 overlay here.
+ */
static int
-make_qcow2_overlay_for_drive (guestfs_h *g, struct drive *drv,
- const char *selinux_imagelabel)
+make_drive_priv (guestfs_h *g, struct drive *drv,
+ const char *selinux_imagelabel)
{
char *path;
struct drive_libvirt *drv_priv;
@@ -1461,25 +1517,64 @@ make_qcow2_overlay_for_drive (guestfs_h *g, struct drive *drv,
drv->priv = drv_priv = safe_calloc (g, 1, sizeof (struct drive_libvirt));
drv->free_priv = drive_free_priv;
- /* Even for non-readonly paths, we need to make the paths absolute here. */
- path = realpath (drv->path, NULL);
- if (path == NULL) {
- perrorf (g, _("realpath: could not convert '%s' to absolute path"),
- drv->path);
- return -1;
- }
+ switch (drv->protocol) {
+ case drive_protocol_file:
- if (!drv->readonly) {
- drv_priv->path = path;
- drv_priv->format = drv->format ? safe_strdup (g, drv->format) : NULL;
- }
- else {
- drv_priv->path = make_qcow2_overlay (g, path, drv->format,
- selinux_imagelabel);
- free (path);
- if (!drv_priv->path)
+ /* Even for non-readonly paths, we need to make the paths absolute here. */
+ path = realpath (drv->u.path, NULL);
+ if (path == NULL) {
+ perrorf (g, _("realpath: could not convert '%s' to absolute path"),
+ drv->u.path);
return -1;
- drv_priv->format = safe_strdup (g, "qcow2");
+ }
+
+ drv_priv->protocol = drive_protocol_file;
+
+ if (!drv->readonly) {
+ drv_priv->u.path = path;
+ drv_priv->format = drv->format ? safe_strdup (g, drv->format) : NULL;
+ }
+ else {
+ drv_priv->u.path = make_qcow2_overlay (g, path, drv->format,
+ selinux_imagelabel);
+ free (path);
+ if (!drv_priv->u.path)
+ return -1;
+ drv_priv->format = safe_strdup (g, "qcow2");
+ }
+ break;
+
+ case drive_protocol_nbd:
+ if (!drv->readonly) {
+ drv_priv->protocol = drive_protocol_nbd;
+ drv_priv->u.nbd.server = safe_strdup (g, drv->u.nbd.server);
+ drv_priv->u.nbd.port = drv->u.nbd.port;
+ drv_priv->u.nbd.exportname = safe_strdup (g, drv->u.nbd.exportname);
+ drv_priv->format = drv->format ? safe_strdup (g, drv->format) : NULL;
+ }
+ else {
+ CLEANUP_FREE char *nbd_device;
+
+ if (STREQ (drv->u.nbd.exportname, ""))
+ nbd_device =
+ safe_asprintf (g, "nbd:%s:%d", drv->u.nbd.server, drv->u.nbd.port);
+ else
+ nbd_device =
+ safe_asprintf (g, "nbd:%s:%d:exportname=%s",
+ drv->u.nbd.server, drv->u.nbd.port,
+ drv->u.nbd.exportname);
+
+ drv_priv->protocol = drive_protocol_file;
+ drv_priv->u.path = make_qcow2_overlay (g, nbd_device, drv->format,
+ selinux_imagelabel);
+ if (!drv_priv->u.path)
+ return -1;
+ drv_priv->format = safe_strdup (g, "qcow2");
+ }
+ break;
+
+ default:
+ abort ();
}
return 0;
@@ -1490,7 +1585,18 @@ drive_free_priv (void *priv)
{
struct drive_libvirt *drv_priv = priv;
- free (drv_priv->path);
+ switch (drv_priv->protocol) {
+ case drive_protocol_file:
+ free (drv_priv->u.path);
+ break;
+ case drive_protocol_nbd:
+ free (drv_priv->u.nbd.server);
+ free (drv_priv->u.nbd.exportname);
+ break;
+ default:
+ abort ();
+ }
+
free (drv_priv->format);
free (drv_priv);
}
@@ -1589,10 +1695,7 @@ hot_add_drive_libvirt (guestfs_h *g, struct drive *drv, size_t drv_index)
return -1;
}
- /* Create overlay for read-only drive. This works around lack of
- * support for <transient/> disks in libvirt.
- */
- if (make_qcow2_overlay_for_drive (g, drv, g->virt_selinux_imagelabel) == -1)
+ if (make_drive_priv (g, drv, g->virt_selinux_imagelabel) == -1)
return -1;
/* Create the XML for the new disk. */
diff --git a/tests/nbd/Makefile.am b/tests/nbd/Makefile.am
new file mode 100644
index 0000000..09d79c8
--- /dev/null
+++ b/tests/nbd/Makefile.am
@@ -0,0 +1,26 @@
+# libguestfs
+# Copyright (C) 2013 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-nbd.pl
+
+TESTS_ENVIRONMENT = $(top_builddir)/run --test
+
+EXTRA_DIST = \
+ $(TESTS)
diff --git a/tests/nbd/test-nbd.pl b/tests/nbd/test-nbd.pl
new file mode 100755
index 0000000..4c14caf
--- /dev/null
+++ b/tests/nbd/test-nbd.pl
@@ -0,0 +1,78 @@
+#!/usr/bin/perl
+# Copyright (C) 2013 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 strict;
+use warnings;
+
+use Sys::Guestfs;
+
+my $disk = "../guests/fedora.img";
+
+exit 77 if $ENV{SKIP_TEST_NBD_PL};
+
+# Check we have qemu-nbd.
+if (system ("qemu-nbd --help >/dev/null 2>&1") != 0) {
+ print "$0: test skipped because qemu-nbd program not found\n";
+ exit 77
+}
+
+if (! -r $disk || -z $disk) {
+ print "$0: test skipped because $disk is not found\n";
+ exit 77
+}
+
+# Since read-only and read-write paths are quite different, we have to
+# test both separately.
+my $readonly;
+for $readonly (1, 0) {
+ # Choose a random port number. XXX Should check it is not in use.
+ my $port = int (60000 + rand (5000));
+
+ # Run the NBD server.
+ print "Starting qemu-nbd server on port $port ...\n";
+ my $pid = fork ();
+ if ($pid == 0) {
+ exec ("qemu-nbd", $disk, "-p", $port, "-t");
+ die "qemu-nbd: $!";
+ }
+
+ my $g = Sys::Guestfs->new ();
+
+ # Add an NBD drive.
+ $g->add_drive ("", readonly => $readonly, format => "raw",
+ protocol => "nbd", server => "localhost", port => $port);
+
+ # XXX qemu-nbd lacks any way to tell if it is awake and listening
+ # for connections. It could write a pid file or something. Could
+ # we check that the socket has been opened by looking in netstat?
+ sleep (2);
+
+ # This dies if qemu cannot connect to the NBD server.
+ $g->launch ();
+
+ # Inspection is quite a thorough test:
+ my $root = $g->inspect_os ();
+ die "$root != /dev/VG/Root" unless $root eq "/dev/VG/Root";
+
+ # Note we have to close the handle (hence killing qemu), and we
+ # have to kill qemu-nbd.
+ $g->close ();
+ kill 15, $pid;
+ waitpid ($pid, 0) or die "waitpid: $pid: $!";
+}
+
+exit 0
--
1.8.1.4
11 years, 8 months
guestfs_list_partitions with vdi
by Skippy VonDrake
I'm using a Windows raw disk image that is "converted" to the VDI
format by custom code that prepends a header to the image.
I use 'vdi' for GUESTFS_ADD_DRIVE_OPTS_FORMAT of
guestfs_add_drive_opts().
After launching the appliance, the call to guestfs_list_partitions()
results in an empty list.
Is there a way to add the drive but have an offset into the image
file as a "starting point"?
So that it could skip the header which apparently is causing the
issue.
If I use the image without the vdi customization (and use 'raw' for
add_drive_opts) then list_partitions is successful.
11 years, 8 months
Re: [Libguestfs] [PATCH] launch: appliance: Add custom parameters last.
by infernix
> From: "Richard W.M. Jones" <rjones redhat com>
> This allows custom parameters to modify parameters added by
> libguestfs, eg. by doing:
> -set drive.hd0.file=rbd:foo/bar
This works for recent versions of QEMU. The way to use this is:
guestfish
<fs> add-drive /dev/null
<fs> config -set drive.hd0.file=rbd:pool/volume
<fs> run
This will probably work for more types (sheepdog, iscsi et al), but with
the caveat that the libguestfs layers are bypassed so you'll *always*
end up directly accessing the disk in rw mode.
11 years, 8 months
[PATCH] launch: appliance: Add custom parameters last.
by Richard W.M. Jones
From: "Richard W.M. Jones" <rjones(a)redhat.com>
This allows custom parameters to modify parameters added by
libguestfs, eg. by doing:
-set drive.hd0.file=rbd:foo/bar
Thanks: infernix @ #libguestfs
---
src/launch-appliance.c | 31 +++++++++++++++++--------------
1 file changed, 17 insertions(+), 14 deletions(-)
diff --git a/src/launch-appliance.c b/src/launch-appliance.c
index d8d8e03..adaf498 100644
--- a/src/launch-appliance.c
+++ b/src/launch-appliance.c
@@ -261,13 +261,6 @@ launch_appliance (guestfs_h *g, const char *arg)
*/
alloc_cmdline (g);
- /* Add any qemu parameters. */
- for (qp = g->qemu_params; qp; qp = qp->next) {
- add_cmdline (g, qp->qemu_param);
- if (qp->qemu_value)
- add_cmdline (g, qp->qemu_value);
- }
-
/* CVE-2011-4127 mitigation: Disable SCSI ioctls on virtio-blk
* devices. The -global option must exist, but you can pass any
* strings to it so we don't need to check for the specific virtio
@@ -347,13 +340,6 @@ launch_appliance (guestfs_h *g, const char *arg)
guestfs___drive_name (g->nr_drives, &appliance_dev[7]);
}
- if (STRNEQ (QEMU_OPTIONS, "")) {
- /* Add the extra options for the qemu command line specified
- * at configure time.
- */
- add_cmdline_shell_unquoted (g, QEMU_OPTIONS);
- }
-
/* The qemu -machine option (added 2010-12) is a bit more sane
* since it falls back through various different acceleration
* modes, so try that first (thanks Markus Armbruster).
@@ -471,6 +457,23 @@ launch_appliance (guestfs_h *g, const char *arg)
guestfs___appliance_command_line (g, appliance_dev, 0);
add_cmdline (g, cmdline);
+ /* Note: custom command line parameters must come last so that
+ * qemu -set parameters can modify previously added options.
+ */
+
+ /* Add the extra options for the qemu command line specified
+ * at configure time.
+ */
+ if (STRNEQ (QEMU_OPTIONS, ""))
+ add_cmdline_shell_unquoted (g, QEMU_OPTIONS);
+
+ /* Add any qemu parameters. */
+ for (qp = g->qemu_params; qp; qp = qp->next) {
+ add_cmdline (g, qp->qemu_param);
+ if (qp->qemu_value)
+ add_cmdline (g, qp->qemu_value);
+ }
+
/* Finish off the command line. */
incr_cmdline_size (g);
g->app.cmdline[g->app.cmdline_size-1] = NULL;
--
1.8.1.4
11 years, 8 months
[PATCH 0/3] protocol: Abstract out socket operations.
by Richard W.M. Jones
I've been taking a long hard look at the protocol layer. It has
evolved over a long time without any particular direction, and the
result is, to say the least, not very organized.
These patches take a first step at cleaning up the mess by abstracting
out socket operations from the rest of the code. The purpose of this
is to allow us to slot in a different connection layer under the
library-side protocol (ie. one based on virStreamPtr for remote
libvirt connections).
But the refactoring is also useful both because it simplifies the code
and because it would make it easier to add multiple daemon
connections.
These changes pass all the tests with both libvirt & appliance
attach-methods, and with valgrind; *except* for tests/mount-local --
but that test was failing intermittently before this change for
reasons which I still don't quite understand.
Rich.
11 years, 8 months