[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