[Spice-devel] [PATCH spice-gtk 2/5] Add simple NBD server library

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


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

This library is not a complete NBD client/server, although it has been
designed with this goal in mind. It currently only supports the
read-only operations (adding write operations shouldn't be difficult),
which is enough to redirect a block device for a cdrom drive or a
read-only device.

It uses GIO as its core, to provide a consistant and modern API, that
could be easiliy binded. However, GIO currently doesn't offer concurrent
IO operations on stream. If this library becomes a seperate project, it
might be worthwile to define only a simple interface for the NbdExport
object to let various backend handled concurrent operations.
---
 configure.ac                  |   1 +
 gtk/Makefile.am               |   2 +-
 gtk/nbd/Makefile.am           |  46 +++
 gtk/nbd/nbd-enums.c.etemplate |  55 +++
 gtk/nbd/nbd-enums.h.etemplate |  36 ++
 gtk/nbd/nbd-export.c          | 425 +++++++++++++++++++++
 gtk/nbd/nbd-export.h          |  65 ++++
 gtk/nbd/nbd-priv.h            |  84 +++++
 gtk/nbd/nbd-server-session.c  | 837 ++++++++++++++++++++++++++++++++++++++++++
 gtk/nbd/nbd-server-session.h  |  56 +++
 gtk/nbd/nbd-server.c          | 108 ++++++
 gtk/nbd/nbd-server.h          |  45 +++
 gtk/nbd/nbd.c                 |  25 ++
 gtk/nbd/nbd.h                 |  47 +++
 14 files changed, 1831 insertions(+), 1 deletion(-)
 create mode 100644 gtk/nbd/Makefile.am
 create mode 100644 gtk/nbd/nbd-enums.c.etemplate
 create mode 100644 gtk/nbd/nbd-enums.h.etemplate
 create mode 100644 gtk/nbd/nbd-export.c
 create mode 100644 gtk/nbd/nbd-export.h
 create mode 100644 gtk/nbd/nbd-priv.h
 create mode 100644 gtk/nbd/nbd-server-session.c
 create mode 100644 gtk/nbd/nbd-server-session.h
 create mode 100644 gtk/nbd/nbd-server.c
 create mode 100644 gtk/nbd/nbd-server.h
 create mode 100644 gtk/nbd/nbd.c
 create mode 100644 gtk/nbd/nbd.h

diff --git a/configure.ac b/configure.ac
index c42066f..d3f1bed 100644
--- a/configure.ac
+++ b/configure.ac
@@ -675,6 +675,7 @@ data/spicy.desktop.in
 data/spicy.nsis
 po/Makefile.in
 gtk/Makefile
+gtk/nbd/Makefile
 gtk/controller/Makefile
 doc/Makefile
 doc/reference/Makefile
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 5af6642..f82e614 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -1,6 +1,6 @@
 NULL =
 
-SUBDIRS =
+SUBDIRS = nbd
 
 if WITH_CONTROLLER
 SUBDIRS += controller
diff --git a/gtk/nbd/Makefile.am b/gtk/nbd/Makefile.am
new file mode 100644
index 0000000..50f105d
--- /dev/null
+++ b/gtk/nbd/Makefile.am
@@ -0,0 +1,46 @@
+NULL =
+
+noinst_LTLIBRARIES = libnbd.la
+
+libnbd_la_LIBADD = $(GIO_LIBS)
+# FIXME: -I.. for glib-compat atm
+libnbd_la_CPPFLAGS =				\
+	-DG_LOG_DOMAIN=\"nbd\"			\
+	$(GIO_CFLAGS)				\
+	-I..					\
+	$(NULL)
+
+ENUMS =						\
+	nbd-enums.c				\
+	nbd-enums.h				\
+	$(NULL)
+
+libnbd_la_SOURCES =				\
+	$(ENUMS)				\
+	nbd-export.c				\
+	nbd-export.h				\
+	nbd-priv.h				\
+	nbd-server-session.c			\
+	nbd-server-session.h			\
+	nbd-server.c				\
+	nbd-server.h				\
+	nbd.c					\
+	nbd.h					\
+	$(NULL)
+
+ENUMS_FILES =					\
+	nbd-export.h				\
+	$(NULL);
+
+BUILT_SOURCES = $(ENUMS)
+
+$(ENUMS): %: %.etemplate $(ENUMS_FILES)
+	$(AM_V_GEN)glib-mkenums --template $^ > $@
+
+EXTRA_DIST =					\
+	$(BUILT_SOURCES)			\
+	nbd-enums.c.etemplate			\
+	nbd-enums.h.etemplate			\
+	$(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/gtk/nbd/nbd-enums.c.etemplate b/gtk/nbd/nbd-enums.c.etemplate
new file mode 100644
index 0000000..d0564a3
--- /dev/null
+++ b/gtk/nbd/nbd-enums.c.etemplate
@@ -0,0 +1,55 @@
+/*** BEGIN file-header ***/
+/*
+ * Copyright (C) 2013 Marc-André Lureau <marcandre.lureau at redhat.com>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "nbd-enums.h"
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+#include "@filename@"
+/*** END file-production ***/
+
+
+/*** BEGIN value-header ***/
+
+GType
+ at enum_name@_get_type (void)
+{
+  static volatile gsize g_define_type_id__volatile = 0;
+
+  if (g_once_init_enter (&g_define_type_id__volatile))
+    {
+      static const G at Type@Value values[] = {
+/*** END value-header ***/
+
+/*** BEGIN value-production ***/
+        { @VALUENAME@, "@VALUENAME@", "@valuenick@" },
+/*** END value-production ***/
+
+/*** BEGIN value-tail ***/
+        { 0, NULL, NULL }
+      };
+      GType g_define_type_id =
+        g_ at type@_register_static (g_intern_static_string ("@EnumName@"), values);
+      g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+    }
+
+  return g_define_type_id__volatile;
+}
+
+/*** END value-tail ***/
diff --git a/gtk/nbd/nbd-enums.h.etemplate b/gtk/nbd/nbd-enums.h.etemplate
new file mode 100644
index 0000000..ba74a14
--- /dev/null
+++ b/gtk/nbd/nbd-enums.h.etemplate
@@ -0,0 +1,36 @@
+/*** BEGIN file-header ***/
+/*
+ * Copyright (C) 2013 Marc-André Lureau <marcandre.lureau at redhat.com>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NBD_ENUMS_H
+#define NBD_ENUMS_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+/*** END file-header ***/
+
+/*** BEGIN value-header ***/
+GType @enum_name at _get_type (void) G_GNUC_CONST;
+#define NBD_TYPE_ at ENUMSHORT@ (@enum_name at _get_type ())
+/*** END value-header ***/
+
+/*** BEGIN file-tail ***/
+G_END_DECLS
+
+#endif
+/*** END file-tail ***/
diff --git a/gtk/nbd/nbd-export.c b/gtk/nbd/nbd-export.c
new file mode 100644
index 0000000..29efb8c
--- /dev/null
+++ b/gtk/nbd/nbd-export.c
@@ -0,0 +1,425 @@
+/* -*- 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 <errno.h>
+
+#include "nbd-export.h"
+#include "nbd-enums.h"
+#include "nbd-priv.h"
+
+struct _NbdExportClass
+{
+    GObjectClass parent_class;
+};
+
+struct _NbdExport
+{
+    GObject parent_instance;
+
+    gchar *name;
+    GFile *file;
+    GInputStream *input;
+    GOutputStream *output;
+    guint64 size;
+    NbdExportFlags flags;
+};
+
+enum {
+    PROP_0,
+
+    PROP_NAME,
+    PROP_FILE,
+    PROP_FLAGS,
+};
+static void initable_iface_init       (GInitableIface      *initable_iface);
+static void async_initable_iface_init (GAsyncInitableIface *async_initable_iface);
+
+G_DEFINE_TYPE_WITH_CODE(NbdExport, nbd_export, G_TYPE_OBJECT,
+                        G_IMPLEMENT_INTERFACE(G_TYPE_INITABLE, initable_iface_init)
+                        G_IMPLEMENT_INTERFACE(G_TYPE_ASYNC_INITABLE, async_initable_iface_init)
+                        );
+
+static void
+nbd_export_init(NbdExport *self)
+{
+}
+
+static void
+nbd_export_finalize(GObject *object)
+{
+    NbdExport *self = NBD_EXPORT(object);
+
+    g_clear_object(&self->input);
+    g_clear_object(&self->output);
+    g_clear_object(&self->file);
+    g_free(self->name);
+
+    G_OBJECT_CLASS(nbd_export_parent_class)->finalize(object);
+}
+
+static void
+nbd_export_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    g_return_if_fail(NBD_IS_EXPORT(object));
+    NbdExport *self = NBD_EXPORT(object);
+
+    switch (prop_id) {
+    case PROP_NAME:
+        self->name = g_value_dup_string(value);
+        break;
+    case PROP_FILE:
+        self->file = g_value_dup_object(value);
+        break;
+    case PROP_FLAGS:
+        self->flags = g_value_get_flags(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nbd_export_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    g_return_if_fail(NBD_IS_EXPORT(object));
+    NbdExport *self = NBD_EXPORT(object);
+
+    switch (prop_id) {
+    case PROP_NAME:
+        g_value_set_string(value, nbd_export_get_name(self));
+        break;
+    case PROP_FILE:
+        g_value_set_object(value, self->file);
+        break;
+    case PROP_FLAGS:
+        g_value_set_flags(value, nbd_export_get_flags(self));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nbd_export_class_init(NbdExportClass *klass)
+{
+    GObjectClass* object_class = G_OBJECT_CLASS (klass);
+
+    object_class->finalize = nbd_export_finalize;
+    object_class->set_property = nbd_export_set_property;
+    object_class->get_property = nbd_export_get_property;
+
+    g_object_class_install_property(object_class, PROP_NAME,
+        g_param_spec_string("name", "name", "name", NULL,
+                            G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+                            G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property(object_class, PROP_FILE,
+        g_param_spec_object("file", "file", "file", G_TYPE_FILE,
+                            G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+                            G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property(object_class, PROP_FLAGS,
+        g_param_spec_flags("flags", "flags", "flags", NBD_TYPE_EXPORT_FLAGS, 0,
+                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+                           G_PARAM_STATIC_STRINGS));
+}
+
+static gboolean
+initable_init(GInitable     *initable,
+              GCancellable  *cancellable,
+              GError       **error)
+{
+    NbdExport *self = NBD_EXPORT(initable);
+    GFileInfo *info;
+
+    /* the asyncinitable will call this in a thread */
+    /* finish by initable is called if init fails */
+
+    g_debug("nbd export init %d", self->flags);
+
+    if (self->flags & NBD_EXPORT_FLAGS_READWRITE) {
+        GIOStream *file;
+
+        file = G_IO_STREAM(g_file_open_readwrite(self->file, cancellable, error));
+        if (!file)
+            return FALSE;
+
+        self->output = G_OUTPUT_STREAM(g_object_ref(g_io_stream_get_output_stream(file)));
+        self->input = G_INPUT_STREAM(g_object_ref(g_io_stream_get_input_stream(file)));
+        g_object_unref(file);
+
+        if (!self->output || !self->input)
+            return FALSE;
+    } else {
+        self->input = G_INPUT_STREAM(g_file_read(self->file, cancellable, error));
+        if (!self->input)
+            return FALSE;
+    }
+
+    info = g_file_input_stream_query_info(G_FILE_INPUT_STREAM(self->input),
+                                          G_FILE_ATTRIBUTE_STANDARD_SIZE,
+                                          cancellable, error);
+    if (!info)
+        return FALSE;
+
+    self->size = g_file_info_get_attribute_uint64(info,
+                                                  G_FILE_ATTRIBUTE_STANDARD_SIZE);
+    g_object_unref(info);
+
+    return TRUE;
+}
+
+static void
+initable_iface_init(GInitableIface *initable_iface)
+{
+    initable_iface->init = initable_init;
+}
+
+static void
+async_initable_iface_init(GAsyncInitableIface *async_initable_iface)
+{
+    /* Use default, in thread */
+}
+
+
+GFile *
+nbd_export_get_file(NbdExport *self)
+{
+    g_return_val_if_fail(NBD_IS_EXPORT(self), NULL);
+
+    return self->file;
+}
+
+const gchar *
+nbd_export_get_name(NbdExport *self)
+{
+    g_return_val_if_fail(NBD_IS_EXPORT(self), NULL);
+
+    return self->name;
+}
+
+guint64
+nbd_export_get_size(NbdExport *self)
+{
+    g_return_val_if_fail(NBD_IS_EXPORT(self), 0);
+
+    return self->size;
+}
+
+guint
+nbd_export_get_flags(NbdExport *self)
+{
+    g_return_val_if_fail(NBD_IS_EXPORT(self), 0);
+
+    return self->flags;
+}
+
+void
+nbd_export_new(gchar *name,
+               GFile *file,
+               NbdExportFlags flags,
+               GCancellable *cancellable,
+               GAsyncReadyCallback callback,
+               gpointer user_data)
+{
+    g_return_if_fail(name != NULL);
+    g_return_if_fail(G_IS_FILE(file));
+    g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+    g_async_initable_new_async(NBD_TYPE_EXPORT,
+                               G_PRIORITY_DEFAULT,
+                               cancellable,
+                               callback,
+                               user_data,
+                               "name", name,
+                               "file", file,
+                               "flags", flags,
+                               NULL);
+}
+
+NbdExport *
+nbd_export_new_finish(GAsyncResult *res,
+                      GError **error)
+{
+    GObject *object;
+    GObject *source_object;
+
+    g_return_val_if_fail(G_IS_ASYNC_RESULT(res), NULL);
+    g_return_val_if_fail(error == NULL || *error == NULL, NULL);
+
+    source_object = g_async_result_get_source_object(res);
+    g_return_val_if_fail(source_object != NULL, NULL);
+    object = g_async_initable_new_finish(G_ASYNC_INITABLE(source_object), res, error);
+    g_object_unref(source_object);
+
+    if (object != NULL)
+        return NBD_EXPORT(object);
+
+    return NULL;
+}
+
+static gboolean
+job_ended(GIOSchedulerJob *job,
+          GSimpleAsyncResult *simple,
+          NbdRequest *req,
+          GError *error)
+{
+    g_debug("job ended %p", job);
+
+    if (error) {
+        if (req->data) {
+            g_slice_free1(req->len, req->data);
+            req->data = NULL;
+        }
+        req->error = EIO;
+        req->len = 0;
+        g_simple_async_result_take_error(simple, error);
+    } else {
+        g_simple_async_result_set_op_res_gboolean(simple, TRUE);
+        req->error = 0;
+    }
+
+    return FALSE;
+}
+
+static gboolean
+flush_job(GIOSchedulerJob *job,
+          GCancellable *cancellable,
+          gpointer user_data)
+{
+    GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT(user_data);
+    NbdExport *self = NBD_EXPORT(g_async_result_get_source_object(G_ASYNC_RESULT(simple)));
+    NbdRequest *req = g_async_result_get_user_data(G_ASYNC_RESULT(simple));
+    GError *error = NULL;
+
+    g_debug("flush job %p", job);
+    g_output_stream_flush(self->output, cancellable, &error);
+    g_object_unref(self);
+
+    return job_ended(job, simple, req, error);
+}
+
+static gboolean
+read_job(GIOSchedulerJob *job,
+         GCancellable *cancellable,
+         gpointer user_data)
+{
+    GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT(user_data);
+    NbdExport *self = NBD_EXPORT(g_async_result_get_source_object(G_ASYNC_RESULT(simple)));
+    NbdRequest *req = g_async_result_get_user_data(G_ASYNC_RESULT(simple));
+    GError *error = NULL;
+    gssize bytes_read;
+
+    g_debug("read job %p", job);
+
+    if (!g_seekable_seek(G_SEEKABLE(self->input), req->from, G_SEEK_SET, cancellable, &error))
+        goto end;
+
+    g_warn_if_fail(req->data == NULL);
+    req->data = g_slice_alloc(req->len);
+
+    if (!g_input_stream_read_all(G_INPUT_STREAM(self->input),
+                                 req->data, req->len, &bytes_read, cancellable, &error))
+        goto end;
+
+end:
+    g_object_unref(self);
+
+    return job_ended(job, simple, req, error);
+}
+
+static void
+job_notify(gpointer data)
+{
+    GSimpleAsyncResult *simple = data;
+
+    g_simple_async_result_complete_in_idle(simple);
+    g_object_unref(simple);
+}
+
+
+G_GNUC_INTERNAL void
+nbd_export_request(NbdExport          *self,
+                   NbdRequest         *request,
+                   GCancellable       *cancellable,
+                   GAsyncReadyCallback callback,
+                   gpointer            data)
+{
+    GSimpleAsyncResult *simple;
+
+    g_return_if_fail(NBD_IS_EXPORT(self));
+    g_return_if_fail(G_IS_FILE_INPUT_STREAM(self->input));
+    g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+    guint32 command = request->type & NBD_CMD_MASK_COMMAND;
+    g_debug("NBD export request %u", command);
+
+    simple = g_simple_async_result_new(G_OBJECT(self),
+                                       callback,
+                                       request,
+                                       nbd_export_request);
+
+    if (!(self->flags & NBD_EXPORT_FLAGS_READWRITE) &&
+        command != NBD_CMD_READ)
+        goto unhandled;
+
+    switch (command) {
+    case NBD_CMD_READ:
+        g_io_scheduler_push_job(read_job, simple, job_notify,
+                                G_PRIORITY_DEFAULT, cancellable);
+        break;
+    case NBD_CMD_FLUSH:
+        g_io_scheduler_push_job(flush_job, simple, job_notify,
+                                G_PRIORITY_DEFAULT, cancellable);
+        break;
+    case NBD_CMD_WRITE:
+    case NBD_CMD_TRIM:
+        g_debug("Write commands not yet supported");
+    default:
+    unhandled:
+        request->error = EINVAL;
+        g_simple_async_result_set_error(simple,
+                                        NBD_ERROR,
+                                        NBD_ERROR_FAILED,
+                                        "unhandled NBD request %u", command);
+        job_notify(simple);
+    }
+}
+
+G_GNUC_INTERNAL gboolean
+nbd_export_request_finish(NbdExport          *self,
+                          GAsyncResult       *result,
+                          GError            **error)
+{
+    GSimpleAsyncResult *simple;
+
+    g_return_val_if_fail(NBD_IS_EXPORT(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),
+                                                        nbd_export_request),
+                         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);
+}
diff --git a/gtk/nbd/nbd-export.h b/gtk/nbd/nbd-export.h
new file mode 100644
index 0000000..f1b4c35
--- /dev/null
+++ b/gtk/nbd/nbd-export.h
@@ -0,0 +1,65 @@
+/* -*- 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 _NBD_EXPORT_H_
+# define _NBD_EXPORT_H_
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+/**
+ * NbdExportFlags:
+ *
+ **/
+typedef enum
+{
+    NBD_EXPORT_FLAGS_NONE = 0,
+
+    NBD_EXPORT_FLAGS_READWRITE = 1 << 0,
+} NbdExportFlags;
+
+#define NBD_TYPE_EXPORT                        (nbd_export_get_type ())
+#define NBD_EXPORT(export)                     (G_TYPE_CHECK_INSTANCE_CAST ((export), NBD_TYPE_EXPORT, NbdExport))
+#define NBD_EXPORT_CLASS(klass)                (G_TYPE_CHECK_CLASS_CAST ((klass), NBD_TYPE_EXPORT, NbdExportClass))
+#define NBD_IS_EXPORT(export)                  (G_TYPE_CHECK_INSTANCE_TYPE ((export), NBD_TYPE_EXPORT))
+#define NBD_IS_EXPORT_CLASS(klass)             (G_TYPE_CHECK_CLASS_TYPE ((klass), NBD_TYPE_EXPORT))
+#define NBD_EXPORT_GET_CLASS(export)           (G_TYPE_INSTANCE_GET_CLASS ((export), NBD_TYPE_EXPORT, NbdExportClass))
+
+/* TODO: we could have a base class with overriable op */
+
+typedef struct _NbdExportClass NbdExportClass;
+typedef struct _NbdExport NbdExport;
+
+GType             nbd_export_get_type                     (void) G_GNUC_CONST;
+void              nbd_export_new                          (gchar *name,
+                                                           GFile *file,
+                                                           NbdExportFlags flags,
+                                                           GCancellable *cancellable,
+                                                           GAsyncReadyCallback callback,
+                                                           gpointer user_data);
+NbdExport*        nbd_export_new_finish                   (GAsyncResult *res,
+                                                           GError **error);
+GFile*            nbd_export_get_file                     (NbdExport *export);
+const gchar*      nbd_export_get_name                     (NbdExport *export);
+guint64           nbd_export_get_size                     (NbdExport *export);
+guint             nbd_export_get_flags                    (NbdExport *export);
+
+G_END_DECLS
+
+#endif /* _NBD_EXPORT_H_ */
diff --git a/gtk/nbd/nbd-priv.h b/gtk/nbd/nbd-priv.h
new file mode 100644
index 0000000..8c4712e
--- /dev/null
+++ b/gtk/nbd/nbd-priv.h
@@ -0,0 +1,84 @@
+/* -*- 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 _NBD_PRIV_H_
+# define _NBD_PRIV_H_
+
+#include "nbd.h"
+#include "glib-compat.h"
+
+/* This is all part of the "official" NBD API */
+
+#define NBD_REQUEST_SIZE        (4 + 4 + 8 + 8 + 4)
+#define NBD_REPLY_SIZE          (4 + 4 + 8)
+#define NBD_REQUEST_MAGIC       0x25609513
+#define NBD_REPLY_MAGIC         0x67446698
+#define NBD_OPTS_MAGIC          0x49484156454F5054LL
+#define NBD_CLIENT_MAGIC        0x0000420281861253LL
+
+#define NBD_OPT_EXPORT_NAME     (1 << 0)
+
+#define NBD_FLAG_HAS_FLAGS      (1 << 0)        /* Flags are there */
+#define NBD_FLAG_READ_ONLY      (1 << 1)        /* Device is read-only */
+#define NBD_FLAG_SEND_FLUSH     (1 << 2)        /* Send FLUSH */
+#define NBD_FLAG_SEND_FUA       (1 << 3)        /* Send FUA (Force Unit Access) */
+#define NBD_FLAG_ROTATIONAL     (1 << 4)        /* Use elevator algorithm - rotational media */
+#define NBD_FLAG_SEND_TRIM      (1 << 5)        /* Send TRIM (discard) */
+
+#define NBD_CMD_MASK_COMMAND	0x0000ffff
+#define NBD_CMD_FLAG_FUA	(1 << 16)
+
+enum {
+    NBD_CMD_READ = 0,
+    NBD_CMD_WRITE = 1,
+    NBD_CMD_DISC = 2,
+    NBD_CMD_FLUSH = 3,
+    NBD_CMD_TRIM = 4
+};
+
+#define NBD_DEFAULT_PORT	10809
+
+/* Maximum size of a single READ/WRITE data buffer */
+#define NBD_MAX_BUFFER_SIZE (32 * 1024 * 1024)
+
+/* FIXME: the export backend might support more but currently GIO
+ * doesn't allow to do multiple outstanding IO */
+#define NBD_MAX_REQUESTS 1
+
+typedef struct NbdRequest
+{
+    guint32 type;
+    guint64 handle;
+    guint64 from;
+    guint32 len;
+    guint8 *data;
+    NbdServerSession *session;
+    guint32 error;
+} NbdRequest;
+
+void     nbd_export_request                (NbdExport          *export,
+                                            NbdRequest         *request,
+                                            GCancellable       *cancellable,
+                                            GAsyncReadyCallback cb,
+                                            gpointer data);
+
+gboolean nbd_export_request_finish         (NbdExport          *export,
+                                            GAsyncResult       *res,
+                                            GError            **error);
+
+#endif /* _NBD_PRIV_H_ */
diff --git a/gtk/nbd/nbd-server-session.c b/gtk/nbd/nbd-server-session.c
new file mode 100644
index 0000000..0b5b175
--- /dev/null
+++ b/gtk/nbd/nbd-server-session.c
@@ -0,0 +1,837 @@
+/* -*- 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 "nbd-server-session.h"
+#include "nbd-priv.h"
+
+struct _NbdServerSessionClass
+{
+    GObjectClass parent_class;
+};
+
+struct _NbdServerSession
+{
+    GObject parent_instance;
+    GQueue *requests;
+
+    NbdServer *server;
+    NbdExport *export;
+    GIOStream *stream;
+
+    GDataOutputStream *dout;
+    GDataInputStream *din;
+
+    NbdRequest *flushing; /* weak */
+    gboolean pending;
+    GSimpleAsyncResult *closing;
+    gulong closing_id;
+    GCancellable *cancellable;
+    GCancellable *closing_cancellable;
+
+    gsize name_len;
+    gchar name[256];
+};
+
+enum {
+    PROP_0,
+    PROP_SERVER,
+    PROP_EXPORT,
+    PROP_STREAM,
+    PROP_CLOSED,
+};
+
+enum {
+    SIGNAL_LAST,
+};
+
+static guint signals[SIGNAL_LAST];
+
+static void async_initable_iface_init(GAsyncInitableIface *iface);
+static void export_request_finished(GObject *source_object,
+                                    GAsyncResult *res,
+                                    gpointer user_data);
+
+G_DEFINE_TYPE_WITH_CODE(NbdServerSession, nbd_server_session, G_TYPE_OBJECT,
+                        G_IMPLEMENT_INTERFACE(G_TYPE_ASYNC_INITABLE, async_initable_iface_init)
+                        );
+
+static void
+nbd_server_session_init(NbdServerSession *self)
+{
+    self->requests = g_queue_new();
+}
+
+static void
+nbd_server_session_finalize(GObject *object)
+{
+    NbdServerSession *self = NBD_SERVER_SESSION(object);
+
+    g_debug("NBD session finalize: %d", self->pending);
+
+    g_queue_free_full(self->requests, NULL); /* FIXME */
+
+    /* the async must be finished before, or it will hold a ref */
+    g_warn_if_fail(self->closing == NULL);
+
+    g_clear_object(&self->server);
+    g_clear_object(&self->export);
+    g_clear_object(&self->stream);
+    g_clear_object(&self->dout);
+    g_clear_object(&self->din);
+    g_clear_object(&self->cancellable);
+
+    G_OBJECT_CLASS(nbd_server_session_parent_class)->finalize(object);
+}
+
+static void
+nbd_server_session_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    g_return_if_fail(NBD_IS_SERVER_SESSION(object));
+    NbdServerSession *self = NBD_SERVER_SESSION(object);
+
+    switch (prop_id) {
+    case PROP_SERVER:
+        self->server = g_value_dup_object(value);
+        break;
+    case PROP_EXPORT:
+        self->export = g_value_dup_object(value);
+        break;
+    case PROP_STREAM:
+        self->stream = g_value_dup_object(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static gboolean
+nbd_server_session_get_closed(NbdServerSession *self)
+{
+    g_return_val_if_fail(NBD_IS_SERVER_SESSION(self), FALSE);
+
+    return !self->stream || g_io_stream_is_closed(self->stream);
+}
+
+
+
+static void
+nbd_server_session_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    g_return_if_fail(NBD_IS_SERVER_SESSION(object));
+    NbdServerSession *self = NBD_SERVER_SESSION(object);
+
+    switch (prop_id) {
+    case PROP_SERVER:
+        g_value_set_object(value, self->server);
+        break;
+    case PROP_EXPORT:
+        g_value_set_object(value, self->export);
+        break;
+    case PROP_STREAM:
+        g_value_set_object(value, self->stream);
+        break;
+    case PROP_CLOSED:
+        g_value_set_boolean(value, nbd_server_session_get_closed(self));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nbd_server_session_class_init(NbdServerSessionClass *klass)
+{
+    GObjectClass* object_class = G_OBJECT_CLASS (klass);
+
+    object_class->finalize = nbd_server_session_finalize;
+    object_class->set_property = nbd_server_session_set_property;
+    object_class->get_property = nbd_server_session_get_property;
+
+    g_object_class_install_property(object_class, PROP_SERVER,
+        g_param_spec_object("server", "server", "server", NBD_TYPE_SERVER,
+                            G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+                            G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property(object_class, PROP_EXPORT,
+        g_param_spec_object("export", "export", "export", NBD_TYPE_EXPORT,
+                            G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+                            G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property(object_class, PROP_STREAM,
+        g_param_spec_object("stream", "stream", "stream", G_TYPE_IO_STREAM,
+                            G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+                            G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property(object_class, PROP_CLOSED,
+        g_param_spec_boolean("closed", "closed", "closed", FALSE,
+                            G_PARAM_READABLE |
+                            G_PARAM_STATIC_STRINGS));
+}
+
+void
+nbd_server_session_new(NbdServer *server,
+                       GIOStream *stream,
+                       NbdExport *export,
+                       GCancellable *cancellable,
+                       GAsyncReadyCallback callback,
+                       gpointer user_data)
+{
+    g_return_if_fail(NBD_IS_SERVER(server));
+    g_return_if_fail(G_IS_IO_STREAM(stream));
+    g_return_if_fail(!export || NBD_IS_EXPORT(export));
+    g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+    g_async_initable_new_async(NBD_TYPE_SERVER_SESSION,
+                               G_PRIORITY_DEFAULT,
+                               cancellable,
+                               callback,
+                               user_data,
+                               "server", server,
+                               "stream", stream,
+                               "export", export,
+                               NULL);
+}
+
+NbdServerSession*
+nbd_server_session_new_finish(GAsyncResult *res,
+                              GError **error)
+{
+    GObject *object;
+    GObject *source_object;
+
+    g_return_val_if_fail(G_IS_ASYNC_RESULT(res), NULL);
+    g_return_val_if_fail(error == NULL || *error == NULL, NULL);
+
+    source_object = g_async_result_get_source_object(res);
+    g_return_val_if_fail(source_object != NULL, NULL);
+    object = g_async_initable_new_finish(G_ASYNC_INITABLE(source_object), res, error);
+    g_object_unref(source_object);
+
+    if (object != NULL)
+        return NBD_SERVER_SESSION(object);
+
+    return NULL;
+}
+
+static gboolean
+nbd_server_session_has_pending(NbdServerSession *self)
+{
+    /* the 1st request is waiting */
+    return self->pending || g_queue_get_length(self->requests) > 1;
+}
+
+#define RETURN_ERROR(simple, error) G_STMT_START{               \
+        g_simple_async_result_take_error(simple, error);        \
+        g_simple_async_result_complete(simple);                 \
+        g_object_unref(simple);                                 \
+        return;                                                 \
+    }G_STMT_END
+
+static void
+session_close(NbdServerSession *self, GError *error)
+{
+    g_debug("session close, pending:%d",
+            nbd_server_session_has_pending(self));
+
+    if (error) {
+        g_warning("NBD error: %s", error->message);
+        g_clear_error(&error);
+    }
+
+    /* we don't close the stream ourself,
+       leave that to the last unref handler */
+    if (self->stream) {
+        g_clear_object(&self->stream);
+        g_object_notify(G_OBJECT(self), "closed");
+    }
+
+    /* this will cancel the waiting request, if any */
+    if (!nbd_server_session_has_pending(self))
+        g_cancellable_cancel(self->cancellable);
+
+    /* finish the async, if any */
+    if (self->closing &&
+        g_queue_get_length(self->requests) == 0) {
+
+        g_simple_async_result_set_op_res_gboolean(self->closing, TRUE);
+        g_simple_async_result_complete_in_idle(self->closing);
+        g_clear_object(&self->closing);
+
+        g_cancellable_disconnect(self->closing_cancellable, self->closing_id);
+        self->closing_id = 0;
+    }
+}
+
+static void
+close_cancelled_handler(GCancellable *cancellable,
+                        gpointer      user_data)
+{
+    NbdServerSession *self = user_data;
+
+    /* if closing is forced, cancel all pending requests */
+    g_cancellable_cancel(self->cancellable);
+}
+
+void
+nbd_server_session_close_async(NbdServerSession    *self,
+                               GCancellable        *cancellable,
+                               GAsyncReadyCallback  callback,
+                               gpointer             user_data)
+
+{
+    g_return_if_fail(NBD_IS_SERVER_SESSION(self));
+    g_return_if_fail(!self->closing);
+
+    if (cancellable) {
+        self->closing_cancellable = cancellable;
+        self->closing_id = g_cancellable_connect(cancellable,
+                                                 G_CALLBACK(close_cancelled_handler),
+                                                 self, NULL);
+    }
+
+    self->closing =
+        g_simple_async_result_new(G_OBJECT(self),
+                                  callback,
+                                  user_data,
+                                  nbd_server_session_close_async);
+
+    session_close(self, NULL);
+}
+
+gboolean
+nbd_server_session_close_finish(NbdServerSession   *self,
+                                GAsyncResult       *result,
+                                GError            **error)
+{
+    GSimpleAsyncResult *simple;
+
+    g_return_val_if_fail(NBD_IS_SERVER_SESSION(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),
+                                                        nbd_server_session_close_async),
+                         FALSE);
+
+    simple = (GSimpleAsyncResult *)result;
+    self->closing = NULL;
+
+    if (g_simple_async_result_propagate_error(simple, error))
+        return FALSE;
+
+    return g_simple_async_result_get_op_res_gboolean(simple);
+}
+
+static void receive_request(NbdServerSession *self);
+
+static NbdRequest *
+nbd_request_get(NbdServerSession *self)
+{
+    NbdRequest *req;
+
+    g_debug("Get NBD request, nb: %d  flushing: %p",
+            g_queue_get_length(self->requests), self->flushing);
+
+    if (!self->stream ||
+        g_queue_get_length(self->requests) >= NBD_MAX_REQUESTS ||
+        self->flushing)
+        return NULL;
+
+    req = g_slice_new0(NbdRequest);
+    req->session = self;
+    g_queue_push_head(self->requests, req);
+
+    return req;
+}
+
+static void
+nbd_request_put(NbdServerSession *self, NbdRequest *req)
+{
+    g_warn_if_fail(g_queue_remove(self->requests, req));
+
+    if (self->flushing &&
+        g_queue_get_length(self->requests) == 1) {
+        NbdRequest *req = self->flushing;
+
+        g_debug("flushed all pending requests");
+        self->flushing = NULL;
+        nbd_export_request(self->export, req,
+                           self->cancellable, export_request_finished,
+                           req);
+    }
+
+    /* that could be useful? */
+    /* if (g_queue_get_length(self->requests) == 0) */
+    /*     g_object_notify(G_OBJECT(self), "pending"); */
+
+    /* loop can be interrupted by request_get() == NULL */
+    receive_request(self);
+}
+
+static void
+reply_flushed(GObject *source_object,
+              GAsyncResult *res,
+              gpointer user_data)
+{
+    GError *error = NULL;
+    NbdRequest *req = user_data;
+    NbdServerSession *self = req->session;
+
+    g_debug("NBD reply flushed");
+    if (!g_output_stream_flush_finish(G_OUTPUT_STREAM(source_object), res, &error))
+        session_close(self, error);
+
+    nbd_request_put(self, req);
+}
+
+static void
+nbd_send_reply(NbdRequest *req)
+{
+    NbdServerSession *self = req->session;
+    GError *error = NULL;
+
+    g_warn_if_fail(!req->len || req->data);
+
+    g_debug("Send reply: "
+            "{ .error = %u, .len = %u }", req->error, req->len);
+
+    if (!g_data_output_stream_put_uint32(self->dout,
+                                         NBD_REPLY_MAGIC, self->cancellable, &error) ||
+        !g_data_output_stream_put_uint32(self->dout,
+                                         req->error, self->cancellable, &error) ||
+        !g_data_output_stream_put_uint64(self->dout,
+                                         req->handle, self->cancellable, &error))
+        goto err;
+
+    if (req->len && req->data &&
+        g_output_stream_write(G_OUTPUT_STREAM(self->dout),
+                              req->data, req->len, self->cancellable, &error) < 0)
+        goto err;
+
+    g_output_stream_flush_async(G_OUTPUT_STREAM(self->dout),
+                                G_PRIORITY_DEFAULT, self->cancellable,
+                                reply_flushed, req);
+    return;
+
+ err:
+    session_close(self, error);
+    nbd_request_put(self, req);
+}
+
+static void
+export_request_finished(GObject *source_object,
+                        GAsyncResult *res,
+                        gpointer user_data)
+{
+    GError *error = NULL;
+    NbdRequest *req = user_data;
+    NbdServerSession *self = req->session;
+
+    if (!nbd_export_request_finish(self->export, res, &error)) {
+        g_warning("request error: %s", error->message);
+        g_clear_error(&error);
+    }
+
+    nbd_send_reply(req);
+}
+
+static void
+handle_request(NbdServerSession *self, NbdRequest *req)
+{
+    guint command = req->type & NBD_CMD_MASK_COMMAND;
+
+    switch (command) {
+    case NBD_CMD_DISC:
+        session_close(self, NULL);
+        break;
+    case NBD_CMD_FLUSH:
+        /* wait until all previous requests are done */
+        g_warn_if_fail(!self->flushing);
+        self->flushing = req;
+        break;
+    default:
+        nbd_export_request(self->export, req,
+                           self->cancellable,
+                           export_request_finished,
+                           req);
+    }
+}
+
+static void
+write_filled(GObject *source_object,
+             GAsyncResult *res,
+             gpointer user_data)
+{
+    GError *error = NULL;
+    NbdRequest *req = user_data;
+    NbdServerSession *self = req->session;
+
+    if (g_input_stream_read_finish(G_INPUT_STREAM(self->din), res, &error) < req->len) {
+        session_close(self, error);
+        nbd_request_put(self, req);
+        return;
+    }
+
+    handle_request(self, req);
+}
+
+static void
+request_filled(GObject *source_object,
+               GAsyncResult *res,
+               gpointer user_data)
+{
+    NbdRequest *req = user_data;
+    NbdServerSession *self = req->session;
+    GError *error = NULL;
+    guint32 magic, command;
+
+    if (g_buffered_input_stream_fill_finish(G_BUFFERED_INPUT_STREAM(self->din), res, &error) != NBD_REQUEST_SIZE)
+        goto end;
+
+    g_return_if_fail(g_buffered_input_stream_get_available(G_BUFFERED_INPUT_STREAM(self->din)) >= NBD_REQUEST_SIZE);
+
+    magic = g_data_input_stream_read_uint32(self->din, self->cancellable, &error);
+    req->type = g_data_input_stream_read_uint32(self->din, self->cancellable, &error);
+    req->handle = g_data_input_stream_read_uint64(self->din, self->cancellable, &error);
+    req->from = g_data_input_stream_read_uint64(self->din, self->cancellable, &error);
+    req->len = g_data_input_stream_read_uint32(self->din, self->cancellable, &error);
+
+    g_debug("Got request: "
+            "{ magic = 0x%x, .type = %u, from = %" G_GINT64_FORMAT " , len = %u }",
+            magic, req->type, req->from, req->len);
+
+    if (magic != NBD_REQUEST_MAGIC)
+        error = g_error_new(NBD_ERROR, NBD_ERROR_FAILED, "Invalid request magic");
+    else if (req->len > NBD_MAX_BUFFER_SIZE)
+        error = g_error_new(NBD_ERROR, NBD_ERROR_FAILED, "len (%u) is larger than max len (%u)", req->len, NBD_MAX_BUFFER_SIZE);
+    else if ((req->from + req->len) < req->from)
+        error = g_error_new(NBD_ERROR, NBD_ERROR_FAILED, "Integer overflow");
+    else if ((req->from + req->len) > nbd_export_get_size(self->export))
+        error = g_error_new(NBD_ERROR, NBD_ERROR_FAILED, "Operation past EOF");
+
+    if (error)
+        goto end;
+
+    command = req->type & NBD_CMD_MASK_COMMAND;
+    if (command == NBD_CMD_WRITE) {
+        req->data = g_slice_alloc(req->len);
+        g_input_stream_read_async(G_INPUT_STREAM(self->din),
+                                  req->data, req->len, G_PRIORITY_DEFAULT,
+                                  self->cancellable, write_filled, req);
+    } else
+        handle_request(self, req);
+
+ end:
+    if (error) {
+        if (error->code == G_IO_ERROR_CANCELLED &&
+            self->closing) {
+            g_debug("request cancelled");
+            g_clear_error(&error);
+        }
+        nbd_request_put(self, req);
+        session_close(self, error);
+        return;
+    }
+
+    /* will receive concurrent requests up to MAX REQUEST */
+    receive_request(self);
+}
+
+static void
+receive_request(NbdServerSession *self)
+{
+    g_return_if_fail(NBD_IS_SERVER_SESSION(self));
+
+    NbdRequest *req = nbd_request_get(self);
+    if (!req) {
+        g_debug("Request processing is interrupted");
+        return;
+    }
+
+    g_return_if_fail(!g_input_stream_has_pending(G_INPUT_STREAM(self->din)));
+    g_buffered_input_stream_fill_async(G_BUFFERED_INPUT_STREAM(self->din),
+                                       NBD_REQUEST_SIZE, G_PRIORITY_DEFAULT,
+                                       self->cancellable, request_filled, req);
+}
+
+static void
+negotiate_ended(GObject *source_object,
+                GAsyncResult *res,
+                gpointer user_data)
+{
+    GSimpleAsyncResult *simple = user_data;
+
+    g_simple_async_result_set_op_res_gboolean(simple, TRUE);
+    g_simple_async_result_complete(simple);
+}
+
+static void
+size_and_flags(NbdServerSession *self,
+               GSimpleAsyncResult *simple,
+               gboolean with_server_flags)
+{
+    GError *error = NULL;
+    gchar reserved[124] = { 0, };
+    const int myflags = (NBD_FLAG_HAS_FLAGS | NBD_FLAG_SEND_TRIM |
+                         NBD_FLAG_SEND_FLUSH | NBD_FLAG_SEND_FUA);
+
+    if (!g_data_output_stream_put_uint64(self->dout,
+                                         nbd_export_get_size(self->export), self->cancellable, &error) ||
+        (with_server_flags &&
+         !g_data_output_stream_put_uint16(self->dout,
+                                          0, self->cancellable, &error)) ||
+        !g_data_output_stream_put_uint16(self->dout,
+                                         nbd_export_get_flags(self->export) | myflags, self->cancellable, &error) ||
+        g_output_stream_write(G_OUTPUT_STREAM(self->dout),
+                              reserved, sizeof(reserved), self->cancellable, &error) == -1)
+        RETURN_ERROR(simple, error);
+
+    g_output_stream_flush_async(G_OUTPUT_STREAM(self->dout),
+                                G_PRIORITY_DEFAULT, self->cancellable,
+                                negotiate_ended, simple);
+}
+
+static void
+header_named(GObject *source_object,
+             GAsyncResult *res,
+             gpointer user_data)
+{
+    GError *error = NULL;
+    NbdExport *export;
+    GSimpleAsyncResult *simple = user_data;
+    NbdServerSession *self = NBD_SERVER_SESSION(g_async_result_get_source_object(G_ASYNC_RESULT(simple)));
+
+    g_return_if_fail(g_buffered_input_stream_get_available(G_BUFFERED_INPUT_STREAM(self->din)) >= self->name_len);
+
+    if (g_input_stream_read(G_INPUT_STREAM(self->din), self->name, self->name_len, self->cancellable, &error) < self->name_len)
+        goto err;
+
+    self->name[self->name_len] = '\0';
+    g_debug("Request export name: %s", self->name);
+
+    export = nbd_server_get_export(self->server, self->name);
+    if (!export) {
+        g_simple_async_result_set_error(simple,
+                                        NBD_ERROR,
+                                        NBD_ERROR_FAILED,
+                                        "Couldn't find export");
+        goto complete;
+    }
+
+    g_warn_if_fail(self->export == NULL);
+    self->export = g_object_ref(export);
+
+    size_and_flags(self, simple, FALSE);
+    return;
+
+err:
+    if (error != NULL)
+        g_simple_async_result_take_error(simple, error);
+    else
+        g_simple_async_result_set_error(simple,
+                                        NBD_ERROR,
+                                        NBD_ERROR_FAILED,
+                                        "Invalid header");
+complete:
+    g_object_unref(self);
+    g_simple_async_result_complete(simple);
+    g_object_unref(simple);
+}
+
+#define HEADER_SIZE (3 * sizeof(guint32) + sizeof(guint64))
+
+static void
+header_filled(GObject *source_object,
+              GAsyncResult *res,
+              gpointer user_data)
+{
+    GError *error = NULL;
+    GSimpleAsyncResult *simple = user_data;
+    NbdServerSession *self = NBD_SERVER_SESSION(g_async_result_get_source_object(G_ASYNC_RESULT(simple)));
+
+    if (g_buffered_input_stream_fill_finish(G_BUFFERED_INPUT_STREAM(self->din), res, &error) != HEADER_SIZE)
+        goto err;
+
+    g_return_if_fail(g_buffered_input_stream_get_available(G_BUFFERED_INPUT_STREAM(self->din)) >= HEADER_SIZE);
+
+    if (g_data_input_stream_read_uint32(self->din, self->cancellable, &error) != 0 ||
+        g_data_input_stream_read_uint64(self->din, self->cancellable, &error) != NBD_OPTS_MAGIC ||
+        g_data_input_stream_read_uint32(self->din, self->cancellable, &error) != NBD_OPT_EXPORT_NAME)
+        goto err;
+
+    self->name_len = g_data_input_stream_read_uint32(self->din, self->cancellable, &error);
+    g_debug("getting name len: %" G_GSIZE_FORMAT, self->name_len);
+    if (error || self->name_len >= sizeof(self->name))
+        goto err;
+
+    g_buffered_input_stream_fill_async(G_BUFFERED_INPUT_STREAM(self->din),
+                                       self->name_len, G_PRIORITY_DEFAULT,
+                                       self->cancellable, header_named, simple);
+    return;
+
+ err:
+    if (error != NULL)
+        g_simple_async_result_take_error(simple, error);
+    else
+        g_simple_async_result_set_error(simple,
+                                        NBD_ERROR,
+                                        NBD_ERROR_FAILED,
+                                        "Invalid header");
+
+    g_object_unref(self);
+    g_simple_async_result_complete(simple);
+    g_object_unref(simple);
+}
+
+static void
+negotiate_flushed(GObject *source_object,
+                  GAsyncResult *res,
+                  gpointer user_data)
+{
+    GError *error = NULL;
+    GSimpleAsyncResult *simple = user_data;
+
+    if (!g_output_stream_flush_finish(G_OUTPUT_STREAM(source_object), res, &error))
+        RETURN_ERROR(simple, error);
+
+    NbdServerSession *self = NBD_SERVER_SESSION(g_async_result_get_source_object(G_ASYNC_RESULT(simple)));
+
+    g_buffered_input_stream_fill_async(G_BUFFERED_INPUT_STREAM(self->din),
+                                       HEADER_SIZE, G_PRIORITY_DEFAULT,
+                                       self->cancellable, header_filled, simple);
+
+    g_object_unref(self);
+}
+
+static void
+nbd_server_session_negotiate_async(NbdServerSession *self,
+                                   GCancellable *cancellable,
+                                   GAsyncReadyCallback callback,
+                                   gpointer user_data)
+{
+    GError *error = NULL;
+    GSimpleAsyncResult *simple;
+
+    g_return_if_fail(NBD_IS_SERVER_SESSION(self));
+    g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+    g_return_if_fail(!nbd_server_session_has_pending(self));
+
+    g_debug("NBD negotiate %p", self);
+
+    self->pending = TRUE;
+    self->cancellable = g_object_ref(cancellable);
+    simple = g_simple_async_result_new(G_OBJECT(self),
+                                       callback,
+                                       user_data,
+                                       nbd_server_session_negotiate_async);
+
+    if (!g_data_output_stream_put_string(self->dout,
+                                         "NBDMAGIC", cancellable, &error))
+        RETURN_ERROR(simple, error);
+
+    if (self->export) {
+        if (!g_data_output_stream_put_uint64(self->dout,
+                                             NBD_CLIENT_MAGIC, cancellable, &error))
+            RETURN_ERROR(simple, error);
+
+        size_and_flags(self, simple, TRUE);
+    } else {
+        if (!g_data_output_stream_put_uint64(self->dout,
+                                             NBD_OPTS_MAGIC, cancellable, &error) ||
+            !g_data_output_stream_put_uint16(self->dout,
+                                             /* server flags */
+                                             0, cancellable, &error))
+            RETURN_ERROR(simple, error);
+
+        g_output_stream_flush_async(G_OUTPUT_STREAM(self->dout),
+                                    G_PRIORITY_DEFAULT, cancellable,
+                                    negotiate_flushed, simple);
+    }
+
+}
+
+static gboolean
+nbd_server_session_negotiate_finish(NbdServerSession *self,
+                                    GAsyncResult *result,
+                                    GError **error)
+{
+    GSimpleAsyncResult *simple;
+
+    g_return_val_if_fail(NBD_IS_SERVER_SESSION(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),
+                                                        nbd_server_session_negotiate_async),
+                         FALSE);
+
+    self->pending = FALSE;
+    g_clear_object(&self->cancellable);
+
+    simple = (GSimpleAsyncResult *)result;
+
+    if (g_simple_async_result_propagate_error(simple, error))
+        return FALSE;
+
+    self->cancellable = g_cancellable_new();
+    receive_request(self);
+
+    return g_simple_async_result_get_op_res_gboolean(simple);
+}
+
+static void
+init_async(GAsyncInitable      *initable,
+           int                  io_priority,
+           GCancellable        *cancellable,
+           GAsyncReadyCallback  callback,
+           gpointer             user_data)
+{
+    NbdServerSession *self = NBD_SERVER_SESSION(initable);
+
+    GInputStream *input = g_io_stream_get_input_stream(self->stream);
+    self->din = g_data_input_stream_new(input);
+    g_data_input_stream_set_byte_order(self->din,
+                                       G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN);
+
+    GOutputStream *output = g_io_stream_get_output_stream(self->stream);
+    GOutputStream *buffered = g_buffered_output_stream_new(output);
+    g_buffered_output_stream_set_auto_grow(G_BUFFERED_OUTPUT_STREAM(buffered), TRUE);
+    self->dout = g_data_output_stream_new(buffered);
+    g_object_unref(buffered);
+    g_data_output_stream_set_byte_order(self->dout,
+                                        G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN);
+
+    nbd_server_session_negotiate_async(self, cancellable, callback, user_data);
+}
+
+static gboolean
+init_finish(GAsyncInitable      *initable,
+            GAsyncResult        *res,
+            GError             **error)
+{
+    NbdServerSession *self = NBD_SERVER_SESSION(initable);
+
+    return nbd_server_session_negotiate_finish(self, res, error);
+}
+
+static void
+async_initable_iface_init(GAsyncInitableIface *iface)
+{
+    iface->init_async = init_async;
+    iface->init_finish = init_finish;
+}
diff --git a/gtk/nbd/nbd-server-session.h b/gtk/nbd/nbd-server-session.h
new file mode 100644
index 0000000..bcd316f
--- /dev/null
+++ b/gtk/nbd/nbd-server-session.h
@@ -0,0 +1,56 @@
+/* -*- 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 _NBD_SERVER_SESSION_H_
+# define _NBD_SERVER_SESSION_H_
+
+#include <gio/gio.h>
+#include "nbd-server.h"
+
+G_BEGIN_DECLS
+
+#define NBD_TYPE_SERVER_SESSION                (nbd_server_session_get_type ())
+#define NBD_SERVER_SESSION(session)            (G_TYPE_CHECK_INSTANCE_CAST ((session), NBD_TYPE_SERVER_SESSION, NbdServerSession))
+#define NBD_SERVER_SESSION_CLASS(klass)        (G_TYPE_CHECK_CLASS_CAST ((klass), NBD_TYPE_SERVER_SESSION, NbdServerSessionClass))
+#define NBD_IS_SERVER_SESSION(session)         (G_TYPE_CHECK_INSTANCE_TYPE ((session), NBD_TYPE_SERVER_SESSION))
+#define NBD_IS_SERVER_SESSION_CLASS(klass)     (G_TYPE_CHECK_CLASS_TYPE ((klass), NBD_TYPE_SERVER_SESSION))
+#define NBD_SERVER_SESSION_GET_CLASS(session)  (G_TYPE_INSTANCE_GET_CLASS ((session), NBD_TYPE_SERVER_SESSION, NbdServerSessionClass))
+
+typedef struct _NbdServerSessionClass NbdServerSessionClass;
+typedef struct _NbdServerSession NbdServerSession;
+
+GType             nbd_server_session_get_type       (void) G_GNUC_CONST;
+void              nbd_server_session_new            (NbdServer          *server,
+                                                     GIOStream          *stream,
+                                                     NbdExport          *export,
+                                                     GCancellable       *cancellable,
+                                                     GAsyncReadyCallback callback,
+                                                     gpointer            user_data);
+NbdServerSession* nbd_server_session_new_finish     (GAsyncResult       *result,
+                                                     GError            **error);
+void              nbd_server_session_close_async    (NbdServerSession   *session,
+                                                     GCancellable       *cancellable,
+                                                     GAsyncReadyCallback callback,
+                                                     gpointer            user_data);
+gboolean          nbd_server_session_close_finish   (NbdServerSession   *session,
+                                                     GAsyncResult       *result,
+                                                     GError            **error);
+
+G_END_DECLS
+
+#endif /* _NBD_SERVER_SESSION_H_ */
diff --git a/gtk/nbd/nbd-server.c b/gtk/nbd/nbd-server.c
new file mode 100644
index 0000000..45ab274
--- /dev/null
+++ b/gtk/nbd/nbd-server.c
@@ -0,0 +1,108 @@
+/* -*- 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 "nbd-server.h"
+#include "nbd-priv.h"
+
+struct _NbdServerClass
+{
+    GObjectClass parent_class;
+};
+
+struct _NbdServer
+{
+    GObject parent_instance;
+
+    GHashTable *exports;
+};
+
+enum {
+    PROP_0,
+};
+
+G_DEFINE_TYPE(NbdServer, nbd_server, G_TYPE_OBJECT);
+
+static void
+nbd_server_init(NbdServer *self)
+{
+    self->exports = g_hash_table_new_full(g_str_hash,
+                                          g_str_equal,
+                                          NULL,
+                                          g_object_unref);
+}
+
+static void
+nbd_server_dispose(GObject *object)
+{
+    NbdServer *self = NBD_SERVER(object);
+
+    g_clear_pointer(&self->exports, g_hash_table_unref);
+
+    G_OBJECT_CLASS(nbd_server_parent_class)->dispose(object);
+}
+
+static void
+nbd_server_finalize(GObject *object)
+{
+    NbdServer *self = NBD_SERVER(object);
+
+    G_OBJECT_CLASS(nbd_server_parent_class)->finalize(object);
+}
+
+static void
+nbd_server_class_init(NbdServerClass *klass)
+{
+    GObjectClass* object_class = G_OBJECT_CLASS (klass);
+
+    object_class->finalize = nbd_server_finalize;
+    object_class->dispose = nbd_server_dispose;
+}
+
+NbdServer *
+nbd_server_new(void)
+{
+    return g_object_new(NBD_TYPE_SERVER, NULL);
+}
+
+void
+nbd_server_add_export(NbdServer *self, NbdExport *export)
+{
+    g_return_if_fail(NBD_IS_SERVER(self));
+    g_return_if_fail(NBD_IS_EXPORT(export));
+
+    g_hash_table_replace(self->exports,
+                         (gpointer)nbd_export_get_name(export), g_object_ref(export));
+}
+
+gboolean
+nbd_server_remove_export(NbdServer *self, NbdExport *export)
+{
+    g_return_val_if_fail(NBD_IS_SERVER(self), FALSE);
+    g_return_val_if_fail(NBD_IS_EXPORT(export), FALSE);
+
+    return g_hash_table_remove(self->exports, nbd_export_get_name(export));
+}
+
+NbdExport *
+nbd_server_get_export(NbdServer *self, gchar *name)
+{
+    g_return_val_if_fail(NBD_IS_SERVER(self), NULL);
+    g_return_val_if_fail(name != NULL, NULL);
+
+    return g_hash_table_lookup(self->exports, name);
+}
diff --git a/gtk/nbd/nbd-server.h b/gtk/nbd/nbd-server.h
new file mode 100644
index 0000000..026347a
--- /dev/null
+++ b/gtk/nbd/nbd-server.h
@@ -0,0 +1,45 @@
+/* -*- 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 _NBD_SERVER_H_
+# define _NBD_SERVER_H_
+
+#include <gio/gio.h>
+#include "nbd-export.h"
+
+G_BEGIN_DECLS
+
+#define NBD_TYPE_SERVER                        (nbd_server_get_type ())
+#define NBD_SERVER(server)                     (G_TYPE_CHECK_INSTANCE_CAST ((server), NBD_TYPE_SERVER, NbdServer))
+#define NBD_SERVER_CLASS(klass)                (G_TYPE_CHECK_CLASS_CAST ((klass), NBD_TYPE_SERVER, NbdServerClass))
+#define NBD_IS_SERVER(server)                  (G_TYPE_CHECK_INSTANCE_TYPE ((server), NBD_TYPE_SERVER))
+#define NBD_IS_SERVER_CLASS(klass)             (G_TYPE_CHECK_CLASS_TYPE ((klass), NBD_TYPE_SERVER))
+#define NBD_SERVER_GET_CLASS(server)           (G_TYPE_INSTANCE_GET_CLASS ((server), NBD_TYPE_SERVER, NbdServerClass))
+
+typedef struct _NbdServerClass NbdServerClass;
+typedef struct _NbdServer NbdServer;
+
+GType             nbd_server_get_type                     (void) G_GNUC_CONST;
+NbdServer*        nbd_server_new                          (void);
+void              nbd_server_add_export                   (NbdServer *server, NbdExport *export);
+gboolean          nbd_server_remove_export                (NbdServer *server, NbdExport *export);
+NbdExport*        nbd_server_get_export                   (NbdServer *server, gchar *name);
+
+G_END_DECLS
+
+#endif /* _NBD_SERVER_H_ */
diff --git a/gtk/nbd/nbd.c b/gtk/nbd/nbd.c
new file mode 100644
index 0000000..866da2f
--- /dev/null
+++ b/gtk/nbd/nbd.c
@@ -0,0 +1,25 @@
+/* -*- 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 "nbd.h"
+
+GQuark
+nbd_error_quark(void)
+{
+    return g_quark_from_static_string("nbd-error-quark");
+}
diff --git a/gtk/nbd/nbd.h b/gtk/nbd/nbd.h
new file mode 100644
index 0000000..4b173ff
--- /dev/null
+++ b/gtk/nbd/nbd.h
@@ -0,0 +1,47 @@
+/* -*- 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 _NBD_H_
+# define _NBD_H_
+
+#include <gio/gio.h>
+
+#include "nbd-enums.h"
+#include "nbd-export.h"
+#include "nbd-server-session.h"
+#include "nbd-server.h"
+
+G_BEGIN_DECLS
+
+#define NBD_ERROR nbd_error_quark ()
+GQuark nbd_error_quark(void);
+
+/**
+ * NbdError:
+ * @NBD_ERROR_FAILED: generic error code
+ *
+ * Error codes returned by NBD API.
+ */
+enum
+{
+    NBD_ERROR_FAILED = 1,
+};
+
+G_END_DECLS
+
+#endif /* _NBD_H_ */
-- 
1.8.3.1



More information about the Spice-devel mailing list