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

Hans de Goede hdegoede at redhat.com
Sat Jun 8 07:32:39 PDT 2013


Hi,

Looks good, ack.

I notice that you're using templates for the enums stuff, would be
nice to convert our existing uses to templates too....

Regards,

Hans


On 06/05/2013 05:39 PM, Marc-André Lureau wrote:
> 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          | 423 +++++++++++++++++++++
>   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, 1829 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 8ab5b6b..fc2cab4 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -666,6 +666,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 d31a396..5d29018 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..115380f
> --- /dev/null
> +++ b/gtk/nbd/nbd-export.c
> @@ -0,0 +1,423 @@
> +/* -*- 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->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_ */
>


More information about the Spice-devel mailing list