From: Marc-André Lureau <marcandre.lureau@xxxxxxxxxx> 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@xxxxxxxxxx> + * + * 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 +@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@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_@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@xxxxxxxxxx> + * + * 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@_get_type (void) G_GNUC_CONST; +#define NBD_TYPE_@ENUMSHORT@ (@enum_name@_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 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/spice-devel