[Spice-devel] [PATCH spice-gtk 4/5] Add NBD channel

Marc-André Lureau marcandre.lureau at gmail.com
Fri Nov 15 13:15:10 PST 2013


From: Marc-André Lureau <marcandre.lureau at redhat.com>

This channel implements the NBD protocol and the port events defined by
Spice protocol.
---
 doc/reference/spice-gtk-docs.xml     |   1 +
 doc/reference/spice-gtk-sections.txt |  18 ++
 doc/reference/spice-gtk.types        |   3 +-
 gtk/Makefile.am                      |  16 +-
 gtk/channel-nbd.c                    | 449 +++++++++++++++++++++++++++++++++++
 gtk/channel-nbd.h                    |  88 +++++++
 gtk/map-file                         |   5 +
 gtk/spice-channel.c                  |   6 +
 gtk/spice-client.h                   |   1 +
 gtk/spice-glib-sym-file              |   4 +
 10 files changed, 588 insertions(+), 3 deletions(-)
 create mode 100644 gtk/channel-nbd.c
 create mode 100644 gtk/channel-nbd.h

diff --git a/doc/reference/spice-gtk-docs.xml b/doc/reference/spice-gtk-docs.xml
index 4a9a3cf..b7cea5a 100644
--- a/doc/reference/spice-gtk-docs.xml
+++ b/doc/reference/spice-gtk-docs.xml
@@ -37,6 +37,7 @@
       <xi:include href="xml/channel-smartcard.xml"/>
       <xi:include href="xml/channel-usbredir.xml"/>
       <xi:include href="xml/channel-port.xml"/>
+      <xi:include href="xml/channel-nbd.xml"/>
     </chapter>
 
     <chapter>
diff --git a/doc/reference/spice-gtk-sections.txt b/doc/reference/spice-gtk-sections.txt
index 8d61aa9..56cb00b 100644
--- a/doc/reference/spice-gtk-sections.txt
+++ b/doc/reference/spice-gtk-sections.txt
@@ -429,3 +429,21 @@ SPICE_PORT_CHANNEL_GET_CLASS
 SpicePortChannelPrivate
 </SECTION>
 
+<SECTION>
+<FILE>channel-nbd</FILE>
+<TITLE>SpiceNbdChannel</TITLE>
+SpiceNbdChannel
+SpiceNbdChannelClass
+<SUBSECTION>
+<SUBSECTION Standard>
+SPICE_NBD_CHANNEL
+SPICE_IS_NBD_CHANNEL
+SPICE_TYPE_NBD_CHANNEL
+spice_nbd_channel_get_type
+SPICE_NBD_CHANNEL_CLASS
+SPICE_IS_NBD_CHANNEL_CLASS
+SPICE_NBD_CHANNEL_GET_CLASS
+<SUBSECTION Private>
+SpiceNbdChannelPrivate
+</SECTION>
+
diff --git a/doc/reference/spice-gtk.types b/doc/reference/spice-gtk.types
index 2f52845..29741fe 100644
--- a/doc/reference/spice-gtk.types
+++ b/doc/reference/spice-gtk.types
@@ -42,4 +42,5 @@ spice_usbredir_channel_get_type
 spice_usb_device_get_type
 spice_usb_device_manager_get_type
 spice_usb_device_widget_get_type
-spice_port_channel_get_type
\ No newline at end of file
+spice_port_channel_get_type
+spice_nbd_channel_get_type
\ No newline at end of file
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 7b38d1c..6a800bb 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -76,6 +76,7 @@ SPICE_COMMON_CPPFLAGS =						\
 	-DUSB_IDS=\""$(USB_IDS)"\"				\
 	-DSPICE_DISABLE_ABORT					\
 	-I$(top_srcdir)						\
+	-I$(srcdir)/nbd						\
 	$(COMMON_CFLAGS)					\
 	$(PIXMAN_CFLAGS)					\
 	$(CELT051_CFLAGS)					\
@@ -181,6 +182,7 @@ libspice_client_glib_2_0_la_LDFLAGS =	\
 libspice_client_glib_2_0_la_LIBADD =					\
 	$(top_builddir)/spice-common/common/libspice-common.la		\
 	$(top_builddir)/spice-common/common/libspice-common-client.la	\
+	nbd/libnbd.la							\
 	$(GLIB2_LIBS)							\
 	$(GIO_LIBS)							\
 	$(GOBJECT2_LIBS)						\
@@ -231,6 +233,7 @@ libspice_client_glib_2_0_la_SOURCES =			\
 	gio-coroutine.h					\
 							\
 	channel-base.c					\
+	channel-nbd.c					\
 	channel-cursor.c				\
 	channel-display.c				\
 	channel-display-priv.h				\
@@ -449,11 +452,19 @@ spice-marshal.c: spice-marshal.txt
 spice-marshal.h: spice-marshal.txt
 	$(AM_V_GEN)glib-genmarshal --header $< > $@ || (rm -f $@ && exit 1)
 
-spice-glib-enums.c: spice-channel.h channel-inputs.h spice-session.h
+SPICE_GLIB_ENUMS_FILES =			\
+	channel-nbd.h				\
+	channel-inputs.h			\
+	spice-channel.h				\
+	spice-session.h				\
+	$(NULL)
+
+spice-glib-enums.c: $(SPICE_GLIB_ENUMS_FILES)
 	$(AM_V_GEN)glib-mkenums --fhead "#include <glib-object.h>\n" \
 			--fhead "#include \"spice-glib-enums.h\"\n\n" \
 			--fprod "\n#include \"spice-session.h\"\n" \
 			--fprod "\n#include \"spice-channel.h\"\n" \
+			--fprod "\n#include \"channel-nbd.h\"\n" \
 			--fprod "\n#include \"channel-inputs.h\"\n" \
 			--vhead "static const G at Type@Value _ at enum_name@_values[] = {" \
 			--vprod "  { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \
@@ -468,7 +479,7 @@ spice-glib-enums.c: spice-channel.h channel-inputs.h spice-session.h
 			--vtail "  return type;\n}\n\n" \
 		$^ > $@
 
-spice-glib-enums.h: spice-channel.h channel-inputs.h spice-session.h
+spice-glib-enums.h: $(SPICE_GLIB_ENUMS_FILES)
 	$(AM_V_GEN)glib-mkenums --fhead "#ifndef SPICE_GLIB_ENUMS_H\n" \
 			--fhead "#define SPICE_GLIB_ENUMS_H\n\n" \
 			--fhead "G_BEGIN_DECLS\n\n" \
@@ -591,6 +602,7 @@ glib_introspection_files =				\
 	spice-glib-enums.c				\
 	spice-option.c					\
 	spice-util.c					\
+	channel-nbd.c					\
 	channel-cursor.c				\
 	channel-display.c				\
 	channel-inputs.c				\
diff --git a/gtk/channel-nbd.c b/gtk/channel-nbd.c
new file mode 100644
index 0000000..786e9a2
--- /dev/null
+++ b/gtk/channel-nbd.c
@@ -0,0 +1,449 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2013 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.1 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, see <http://www.gnu.org/licenses/>.
+*/
+#include "spice-client.h"
+#include "spice-common.h"
+#include "spice-channel-priv.h"
+#include "spice-session-priv.h"
+#include "spice-marshal.h"
+#include "glib-compat.h"
+#include "nbd/nbd.h"
+#include "vmcstream.h"
+
+/**
+ * SECTION:channel-nbd
+ * @short_description: blockdevice communication channel
+ * @title: Block device Channel
+ * @section_id:
+ * @see_also: #SpiceChannel
+ * @stability: Stable
+ * @include: channel-nbd.h
+ *
+ * The "nbd" channel exports block device to a Spice server, using the
+ * Network Block Device protocol. This is mainly useful to redirect
+ * ISO images to a CD device for example.
+ *
+ * As of today, the implementation allows read-only operations.
+ *
+ * Since: 0.20
+ */
+
+#define SPICE_NBD_CHANNEL_GET_PRIVATE(obj)                                  \
+    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_NBD_CHANNEL, SpiceNbdChannelPrivate))
+
+struct _SpiceNbdChannelPrivate {
+    NbdServer *server;
+
+    gboolean pending_update;
+    gboolean pending_session;
+    NbdServerSession *session;
+
+    GCancellable *cancellable;
+    NbdExport *export;
+    SpiceVmcStream *stream;
+};
+
+G_DEFINE_TYPE(SpiceNbdChannel, spice_nbd_channel, SPICE_TYPE_PORT_CHANNEL)
+
+/* Properties */
+enum {
+    PROP_0,
+
+    PROP_NBD_FILE,
+    PROP_NBD_OPENED,
+};
+
+static void spice_nbd_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg);
+static void update_nbd_session(SpiceNbdChannel *self);
+
+static void
+session_closed(GObject *source_object,
+               GAsyncResult *res,
+               gpointer user_data)
+{
+    SpiceNbdChannel *self = user_data;
+    SpiceNbdChannelPrivate *c = self->priv;
+    GError *error = NULL;
+
+    CHANNEL_DEBUG(self, "session closed");
+
+    if (!nbd_server_session_close_finish(c->session, res, &error)) {
+        g_warning("%s", error->message);
+        g_clear_error(&error);
+    }
+
+    g_clear_object(&c->session);
+    g_object_notify(G_OBJECT(self), "nbd-opened");
+
+    c->pending_update = FALSE;
+    update_nbd_session(self);
+}
+
+static void port_event(SpiceNbdChannel *self, gint event)
+{
+    SpiceNbdChannelPrivate *c = self->priv;
+
+    CHANNEL_DEBUG(self, "port event:%d pending:%d", event, c->pending_update);
+    if (event != SPICE_PORT_EVENT_OPENED ||
+        !c->pending_update)
+        return;
+
+    if (c->session)
+        nbd_server_session_close_async(c->session,
+                                       NULL,
+                                       session_closed,
+                                       self);
+    else {
+        c->pending_update = FALSE;
+        update_nbd_session(self);
+    }
+
+}
+
+static void spice_nbd_channel_init(SpiceNbdChannel *channel)
+{
+    SpiceNbdChannelPrivate *c = SPICE_NBD_CHANNEL_GET_PRIVATE(channel);
+
+    channel->priv = c;
+
+    c->stream = spice_vmc_stream_new(SPICE_CHANNEL(channel));
+    c->server = nbd_server_new();
+    c->cancellable = g_cancellable_new();
+}
+
+/**
+ * spice_nbd_channel_get_file:
+ * @channel: a %SpiceNbdChannel
+ *
+ * Returns the %GFile that has been set with
+ * spice_nbd_channel_set_file_async().
+ *
+ * Returns: (transfer none): The associated %GFile or %NULL.
+ **/
+GFile* spice_nbd_channel_get_file(SpiceNbdChannel *self)
+{
+    GFile *file = NULL;
+
+    g_return_val_if_fail(SPICE_IS_NBD_CHANNEL(self), NULL);
+    SpiceNbdChannelPrivate *c = self->priv;
+
+    if (c->export)
+        file = nbd_export_get_file(c->export);
+
+    return file;
+}
+
+static void spice_nbd_get_property(GObject    *object,
+                                   guint       prop_id,
+                                   GValue     *value,
+                                   GParamSpec *pspec)
+{
+    g_return_if_fail(SPICE_IS_NBD_CHANNEL(object));
+    SpiceNbdChannel *self = SPICE_NBD_CHANNEL(object);
+    SpiceNbdChannelPrivate *c = self->priv;
+
+    switch (prop_id) {
+    case PROP_NBD_FILE:
+        g_value_set_object(value, spice_nbd_channel_get_file(self));
+        break;
+    case PROP_NBD_OPENED:
+        g_value_set_boolean(value, !!c->session);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void migration_state_cb(SpiceSession *session, GParamSpec *pspec,
+                               SpiceNbdChannel *self)
+{
+    SpiceSessionMigration state = spice_session_get_migration_state(session);
+
+    if (state != SPICE_SESSION_MIGRATION_NONE)
+        return;
+
+    CHANNEL_DEBUG(self, "migration finished, re-new the nbd session on dest");
+    update_nbd_session(self);
+}
+
+static void spice_nbd_channel_constructed(GObject *object)
+{
+    SpiceChannelPrivate *c = SPICE_CHANNEL(object)->priv;
+
+    spice_g_signal_connect_object(c->session, "notify::migration-state",
+                                  G_CALLBACK(migration_state_cb), object, 0);
+
+    if (G_OBJECT_CLASS(spice_nbd_channel_parent_class)->constructed)
+        G_OBJECT_CLASS(spice_nbd_channel_parent_class)->constructed(object);
+}
+
+static void spice_nbd_channel_finalize(GObject *object)
+{
+    SpiceNbdChannelPrivate *c = SPICE_NBD_CHANNEL(object)->priv;
+
+    g_clear_object(&c->export);
+    g_clear_object(&c->server);
+    g_clear_object(&c->session);
+    g_clear_object(&c->cancellable);
+
+    G_OBJECT_CLASS(spice_nbd_channel_parent_class)->finalize(object);
+}
+
+static void
+session_ready(GObject *source_object,
+              GAsyncResult *res,
+              gpointer user_data)
+{
+    SpiceNbdChannel *self = user_data;
+    SpiceNbdChannelPrivate *c = self->priv;
+    GError *error = NULL;
+
+    g_warn_if_fail(c->session == NULL);
+    c->session = nbd_server_session_new_finish(res, &error);
+    CHANNEL_DEBUG(self, "New NBD session %p", c->session);
+    c->pending_session = FALSE;
+
+    if (!c->session) {
+        if (error)
+            g_warning("%s", error->message);
+        g_clear_error(&error);
+    } else
+        g_object_notify(G_OBJECT(self), "nbd-opened");
+}
+
+static void update_nbd_session(SpiceNbdChannel *self)
+{
+    SpiceNbdChannelPrivate *c = self->priv;
+
+    if (spice_channel_get_state(SPICE_CHANNEL(self)) != SPICE_CHANNEL_STATE_READY ||
+        !c->export ||
+        c->pending_update ||
+        c->pending_session)
+        return;
+
+    if (c->session) {
+        spice_port_event(SPICE_PORT_CHANNEL(self), SPICE_PORT_EVENT_BREAK);
+        c->pending_update = TRUE;
+        return;
+    }
+
+    c->pending_session = TRUE;
+    nbd_server_session_new(c->server,
+                           G_IO_STREAM(c->stream), c->export,
+                           c->cancellable, session_ready, self);
+}
+
+static void spice_nbd_channel_up(SpiceChannel *channel)
+{
+    SpiceNbdChannel *self = SPICE_NBD_CHANNEL(channel);
+
+    update_nbd_session(self);
+}
+
+static void spice_nbd_channel_class_init(SpiceNbdChannelClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+    SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
+
+    gobject_class->constructed  = spice_nbd_channel_constructed;
+    gobject_class->finalize     = spice_nbd_channel_finalize;
+    gobject_class->get_property = spice_nbd_get_property;
+    channel_class->handle_msg   = spice_nbd_handle_msg;
+    channel_class->channel_up   = spice_nbd_channel_up;
+
+    g_signal_override_class_handler("port-event",
+                                    SPICE_TYPE_NBD_CHANNEL,
+                                    G_CALLBACK(port_event));
+
+    g_object_class_install_property
+        (gobject_class, PROP_NBD_FILE,
+         g_param_spec_string("nbd-file",
+                             "file",
+                             "Associated block device file",
+                             NULL,
+                             G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property
+        (gobject_class, PROP_NBD_OPENED,
+         g_param_spec_boolean("nbd-opened",
+                              "opened",
+                              "Session opened",
+                              FALSE,
+                              G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+    g_type_class_add_private(klass, sizeof(SpiceNbdChannelPrivate));
+}
+
+static void
+export_ready(GObject *source_object,
+             GAsyncResult *res,
+             gpointer user_data)
+{
+    GError *error = NULL;
+    GSimpleAsyncResult *simple = user_data;
+    SpiceNbdChannel *self = SPICE_NBD_CHANNEL(g_async_result_get_source_object(G_ASYNC_RESULT(simple)));
+    SpiceNbdChannelPrivate *c = self->priv;
+
+    NbdExport *export = nbd_export_new_finish(res, &error);
+    CHANNEL_DEBUG(self, "set export %p", export);
+    if (!export) {
+        g_simple_async_result_take_error(simple, error);
+        goto end;
+    }
+
+    if (c->export)
+        nbd_server_remove_export(c->server, c->export);
+
+    nbd_server_add_export(c->server, export);
+
+    g_clear_object(&c->export);
+    c->export = export;
+    g_object_notify(G_OBJECT(self), "nbd-file");
+
+    update_nbd_session(self);
+    g_simple_async_result_set_op_res_gboolean(simple, TRUE);
+
+end:
+    g_simple_async_result_complete(simple);
+    g_object_unref(simple);
+    g_object_unref(self);
+}
+
+/**
+ * spice_nbd_set_file_async:
+ * @channel: a #SpiceNbdChannel
+ * @file: the block device image file
+ * @flags: open options
+ * @cancellable: (allow-none): optional GCancellable object, NULL to ignore
+ * @callback: (scope async): callback to call when the request is satisfied
+ * @user_data: (closure): the data to pass to callback function
+ *
+ * Update the @file image associated with @channel.
+ *
+ * The caller is responsible to finish any previous call, no
+ * concurrent call will be permitted.
+ **/
+void
+spice_nbd_channel_set_file_async(SpiceNbdChannel *self,
+                                 GFile *file, SpiceNbdOpenFlags flags,
+                                 GCancellable *cancellable,
+                                 GAsyncReadyCallback callback,
+                                 gpointer user_data)
+{
+    GSimpleAsyncResult *simple;
+
+    g_return_if_fail(SPICE_IS_NBD_CHANNEL(self));
+    g_return_if_fail(!file || G_IS_FILE(file));
+
+    SpiceNbdChannelPrivate *c = self->priv;
+
+    if (c->pending_session) {
+        g_simple_async_report_error_in_idle(G_OBJECT (self),
+                                            callback,
+                                            user_data,
+                                            SPICE_CLIENT_ERROR,
+                                            SPICE_CLIENT_ERROR_FAILED,
+                                            "Channel has already a pending session");
+        return;
+    }
+
+    simple = g_simple_async_result_new(G_OBJECT(self),
+                                       callback,
+                                       user_data,
+                                       spice_nbd_channel_set_file_async);
+
+    CHANNEL_DEBUG(self, "set file %p 0x%X", file, flags);
+
+    if (!file) {
+        g_clear_object(&c->export);
+        g_object_notify(G_OBJECT(self), "nbd-file");
+
+        update_nbd_session(self);
+
+        g_simple_async_result_set_op_res_gboolean(simple, TRUE);
+        g_simple_async_result_complete_in_idle(simple);
+        g_object_unref(simple);
+    } else
+        nbd_export_new("", file, flags, cancellable, export_ready, simple);
+}
+
+/**
+ * spice_nbd_set_file_finish:
+ * @channel: a #SpiceNbdChannel
+ * @result: a #GAsyncResult
+ * @error: a #GError location to store the error occurring, or %NULL
+ * to ignore
+ *
+ * Finishes an asynchronous file read operation started with
+ * spice_nbd_set_file_async().
+ *
+ * Returns: %TRUE on success.
+ **/
+gboolean
+spice_nbd_channel_set_file_finish(SpiceNbdChannel *self,
+                                  GAsyncResult *result,
+                                  GError **error)
+{
+    GSimpleAsyncResult *simple;
+
+    g_return_val_if_fail(SPICE_IS_NBD_CHANNEL(self), FALSE);
+    g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
+    g_return_val_if_fail(g_simple_async_result_is_valid(result,
+                                                        G_OBJECT(self),
+                                                        spice_nbd_channel_set_file_async),
+                         FALSE);
+
+    simple = (GSimpleAsyncResult *)result;
+
+    if (g_simple_async_result_propagate_error(simple, error))
+        return FALSE;
+
+    return g_simple_async_result_get_op_res_gboolean(simple);
+}
+
+/* coroutine context */
+static void nbd_handle_msg(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceNbdChannel *self = SPICE_NBD_CHANNEL(channel);
+    SpiceNbdChannelPrivate *c = self->priv;
+    int size;
+    uint8_t *buf;
+
+    buf = spice_msg_in_raw(in, &size);
+
+    spice_vmc_input_stream_co_data(
+        SPICE_VMC_INPUT_STREAM(g_io_stream_get_input_stream(G_IO_STREAM(c->stream))),
+        buf, size);
+}
+
+
+/* coroutine context */
+static void spice_nbd_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg)
+{
+    int type = spice_msg_in_type(msg);
+    SpiceChannelClass *parent_class;
+
+    parent_class = SPICE_CHANNEL_CLASS(spice_nbd_channel_parent_class);
+
+    if (type == SPICE_MSG_SPICEVMC_DATA)
+        nbd_handle_msg(channel, msg);
+    else if (parent_class->handle_msg)
+        parent_class->handle_msg(channel, msg);
+    else
+        g_return_if_reached();
+}
diff --git a/gtk/channel-nbd.h b/gtk/channel-nbd.h
new file mode 100644
index 0000000..2cb2d7a
--- /dev/null
+++ b/gtk/channel-nbd.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2013 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.1 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, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef __SPICE_CLIENT_NBD_CHANNEL_H__
+#define __SPICE_CLIENT_NBD_CHANNEL_H__
+
+#include <gio/gio.h>
+#include "spice-client.h"
+#include "channel-port.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_NBD_CHANNEL            (spice_nbd_channel_get_type())
+#define SPICE_NBD_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_NBD_CHANNEL, SpiceNbdChannel))
+#define SPICE_NBD_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_NBD_CHANNEL, SpiceNbdChannelClass))
+#define SPICE_IS_NBD_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_NBD_CHANNEL))
+#define SPICE_IS_NBD_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_NBD_CHANNEL))
+#define SPICE_NBD_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_NBD_CHANNEL, SpiceNbdChannelClass))
+
+typedef struct _SpiceNbdChannel SpiceNbdChannel;
+typedef struct _SpiceNbdChannelClass SpiceNbdChannelClass;
+typedef struct _SpiceNbdChannelPrivate SpiceNbdChannelPrivate;
+
+/**
+ * SpiceNbdOpenFlags:
+ *
+ **/
+typedef enum
+{
+    SPICE_NBD_OPEN_NONE = 0,
+
+    SPICE_NBD_OPEN_READWRITE = 1 << 0,
+} SpiceNbdOpenFlags;
+
+/**
+ * SpiceNbdChannel:
+ *
+ * The #SpiceNbdChannel struct is opaque and should not be accessed directly.
+ */
+struct _SpiceNbdChannel {
+    SpicePortChannel parent;
+
+    /*< private >*/
+    SpiceNbdChannelPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+/**
+ * SpiceNbdChannelClass:
+ * @parent_class: Parent class.
+ *
+ * Class structure for #SpiceNbdChannel.
+ */
+struct _SpiceNbdChannelClass {
+    SpicePortChannelClass parent_class;
+
+    /*< private >*/
+    /* Do not add fields to this struct */
+};
+
+GType spice_nbd_channel_get_type(void);
+
+void spice_nbd_channel_set_file_async(SpiceNbdChannel *channel,
+                                      GFile *file, SpiceNbdOpenFlags flags,
+                                      GCancellable *cancellable,
+                                      GAsyncReadyCallback callback,
+                                      gpointer user_data);
+gboolean spice_nbd_channel_set_file_finish(SpiceNbdChannel *channel,
+                                           GAsyncResult *result, GError **error);
+GFile* spice_nbd_channel_get_file(SpiceNbdChannel *channel);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_NBD_CHANNEL_H__ */
diff --git a/gtk/map-file b/gtk/map-file
index 368b44f..43ed851 100644
--- a/gtk/map-file
+++ b/gtk/map-file
@@ -3,6 +3,11 @@ global:
 spice_audio_get;
 spice_audio_get_type;
 spice_audio_new;
+spice_nbd_channel_get_type;
+spice_nbd_open_flags_get_type;
+spice_nbd_channel_set_file_async;
+spice_nbd_channel_set_file_finish;
+spice_nbd_channel_get_file;
 spice_channel_connect;
 spice_channel_destroy;
 spice_channel_disconnect;
diff --git a/gtk/spice-channel.c b/gtk/spice-channel.c
index 901c193..b8acfb4 100644
--- a/gtk/spice-channel.c
+++ b/gtk/spice-channel.c
@@ -1859,6 +1859,7 @@ static const char *to_string[] = {
     [ SPICE_CHANNEL_SMARTCARD ] = "smartcard",
     [ SPICE_CHANNEL_USBREDIR ] = "usbredir",
     [ SPICE_CHANNEL_PORT ] = "port",
+    [ SPICE_CHANNEL_NBD ] = "nbd",
 };
 
 /**
@@ -1919,6 +1920,7 @@ gchar *spice_channel_supported_string(void)
 #ifdef USE_USBREDIR
                      spice_channel_type_to_string(SPICE_CHANNEL_USBREDIR),
 #endif
+                     spice_channel_type_to_string(SPICE_CHANNEL_NBD),
                      NULL);
 }
 
@@ -1983,6 +1985,10 @@ SpiceChannel *spice_channel_new(SpiceSession *s, int type, int id)
         break;
     }
 #endif
+    case SPICE_CHANNEL_NBD: {
+        gtype = SPICE_TYPE_NBD_CHANNEL;
+        break;
+    }
     case SPICE_CHANNEL_PORT:
         gtype = SPICE_TYPE_PORT_CHANNEL;
         break;
diff --git a/gtk/spice-client.h b/gtk/spice-client.h
index 730d11a..5b9b01b 100644
--- a/gtk/spice-client.h
+++ b/gtk/spice-client.h
@@ -41,6 +41,7 @@
 #include "channel-smartcard.h"
 #include "channel-usbredir.h"
 #include "channel-port.h"
+#include "channel-nbd.h"
 
 #include "smartcard-manager.h"
 #include "usb-device-manager.h"
diff --git a/gtk/spice-glib-sym-file b/gtk/spice-glib-sym-file
index 4fc8643..da7d14d 100644
--- a/gtk/spice-glib-sym-file
+++ b/gtk/spice-glib-sym-file
@@ -1,6 +1,10 @@
 spice_audio_get
 spice_audio_get_type
 spice_audio_new
+spice_nbd_channel_get_type
+spice_nbd_open_flags_get_type
+spice_nbd_set_file_async
+spice_nbd_set_file_finish
 spice_channel_connect
 spice_channel_destroy
 spice_channel_disconnect
-- 
1.8.3.1



More information about the Spice-devel mailing list