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