/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* gck-call.c - the GObject PKCS#11 wrapper library
Copyright (C) 2008, Stefan Walter
The Gnome Keyring Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
The Gnome Keyring 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with the Gnome Library; see the file COPYING.LIB. If not,
see <http://www.gnu.org/licenses/>.
Author: Stef Walter <nielsen@memberwebs.com>
*/
#include "config.h"
#include "gck-private.h"
#include <string.h>
typedef struct _GckCallSource GckCallSource;
static gpointer _gck_call_parent_class = NULL;
struct _GckCall {
GObject parent;
GckModule *module;
/* For making the call */
GckPerformFunc perform;
GckCompleteFunc complete;
GckArguments *args;
GCancellable *cancellable;
GDestroyNotify destroy;
CK_RV rv;
/* For result callback only */
gpointer object;
GAsyncReadyCallback callback;
gpointer user_data;
};
struct _GckCallClass {
GObjectClass parent;
GThreadPool *thread_pool;
GAsyncQueue *completed_queue;
guint completed_id;
};
struct _GckCallSource {
GSource source;
GckCallClass *klass;
};
/* ----------------------------------------------------------------------------
* HELPER FUNCTIONS
*/
static CK_RV
perform_call (GckPerformFunc func, GCancellable *cancellable, GckArguments *args)
{
CK_RV rv;
/* Double check a few things */
g_assert (func);
g_assert (args);
if (cancellable) {
if (g_cancellable_is_cancelled (cancellable)) {
return CKR_FUNCTION_CANCELED;
}
/* Push for the notify callback */
g_object_ref (cancellable);
g_cancellable_push_current (cancellable);
}
rv = (func) (args);
if (cancellable) {
g_cancellable_pop_current (cancellable);
g_object_unref (cancellable);
}
return rv;
}
static gboolean
complete_call (GckCompleteFunc func, GckArguments *args, CK_RV result)
{
/* Double check a few things */
g_assert (args);
/* If no complete function, then just ignore */
if (!func)
return TRUE;
return (func) (args, result);
}
static void
process_async_call (gpointer data, GckCallClass *klass)
{
GckCall *call = GCK_CALL (data);
g_assert (GCK_IS_CALL (call));
call->rv = perform_call (call->perform, call->cancellable, call->args);
g_async_queue_push (klass->completed_queue, call);
/* Wakeup main thread if on a separate thread */
g_main_context_wakeup (NULL);
}
static void
process_result (GckCall *call, gpointer unused)
{
gboolean stop = FALSE;
/* Double check a few things */
g_assert (GCK_IS_CALL (call));
if (call->cancellable) {
/* Don't call the callback when cancelled */
if (g_cancellable_is_cancelled (call->cancellable)) {
call->rv = CKR_FUNCTION_CANCELED;
stop = TRUE;
}
}
/*
* Hmmm, does the function want to actually be done?
* If not, then queue this call again.
*/
if (!stop && !complete_call (call->complete, call->args, call->rv)) {
g_object_ref (call);
g_thread_pool_push (GCK_CALL_GET_CLASS (call)->thread_pool, call, NULL);
/* All done, finish processing */
} else if (call->callback) {
(call->callback) (call->object, G_ASYNC_RESULT (call),
call->user_data);
}
}
static gboolean
process_completed (GckCallClass *klass)
{
gpointer call;
g_assert (klass->completed_queue);
call = g_async_queue_try_pop (klass->completed_queue);
if (call) {
process_result (call, NULL);
g_object_unref (call);
return TRUE;
}
return FALSE;
}
static gboolean
completed_prepare(GSource* base, gint *timeout)
{
GckCallSource *source = (GckCallSource*)base;
gboolean have;
g_assert (source->klass->completed_queue);
have = g_async_queue_length (source->klass->completed_queue) > 0;
*timeout = have ? 0 : -1;
return have;
}
static gboolean
completed_check(GSource* base)
{
GckCallSource *source = (GckCallSource*)base;
g_assert (source->klass->completed_queue);
return g_async_queue_length (source->klass->completed_queue) > 0;
}
static gboolean
completed_dispatch(GSource* base, GSourceFunc callback, gpointer user_data)
{
GckCallSource *source = (GckCallSource*)base;
process_completed (source->klass);
return TRUE;
}
static void
completed_finalize(GSource* base)
{
}
static GSourceFuncs completed_functions = {
completed_prepare,
completed_check,
completed_dispatch,
completed_finalize
};
/* ----------------------------------------------------------------------------
* OBJECT
*/
static void
_gck_call_init (GckCall *call)
{
call->rv = CKR_OK;
}
static void
_gck_call_finalize (GObject *obj)
{
GckCall *call = GCK_CALL (obj);
if (call->module)
g_object_unref (call->module);
call->module = NULL;
if (call->object)
g_object_unref (call->object);
call->object = NULL;
if (call->cancellable)
g_object_unref (call->cancellable);
call->cancellable = NULL;
if (call->destroy)
(call->destroy) (call->args);
call->destroy = NULL;
call->args = NULL;
G_OBJECT_CLASS (_gck_call_parent_class)->finalize (obj);
}
static gpointer
_gck_call_get_user_data (GAsyncResult *async_result)
{
g_return_val_if_fail (GCK_IS_CALL (async_result), NULL);
return GCK_CALL (async_result)->user_data;
}
static GObject*
_gck_call_get_source_object (GAsyncResult *async_result)
{
GObject *source;
g_return_val_if_fail (GCK_IS_CALL (async_result), NULL);
source = GCK_CALL (async_result)->object;
return source ? g_object_ref (source): NULL;
}
static void
_gck_call_implement_async_result (GAsyncResultIface *iface)
{
iface->get_user_data = _gck_call_get_user_data;
iface->get_source_object = _gck_call_get_source_object;
}
static void
_gck_call_class_init (GckCallClass *klass)
{
GObjectClass *gobject_class = (GObjectClass*)klass;
_gck_call_parent_class = g_type_class_peek_parent (klass);
gobject_class->finalize = _gck_call_finalize;
}
static void
_gck_call_base_init (GckCallClass *klass)
{
GckCallSource *source;
GMainContext *context;
GError *err = NULL;
klass->thread_pool = g_thread_pool_new ((GFunc)process_async_call, klass, 16, FALSE, &err);
if (!klass->thread_pool) {
g_critical ("couldn't create thread pool: %s",
err && err->message ? err->message : "");
return;
}
klass->completed_queue = g_async_queue_new_full (g_object_unref);
g_assert (klass->completed_queue);
context = g_main_context_default ();
g_assert (context);
/* Add our idle handler which processes other tasks */
source = (GckCallSource*)g_source_new (&completed_functions, sizeof (GckCallSource));
source->klass = klass;
klass->completed_id = g_source_attach ((GSource*)source, context);
g_source_set_callback ((GSource*)source, NULL, NULL, NULL);
g_source_unref ((GSource*)source);
}
static void
_gck_call_base_finalize (GckCallClass *klass)
{
GMainContext *context;
GSource *src;
if (klass->thread_pool) {
g_assert (g_thread_pool_unprocessed (klass->thread_pool) == 0);
g_thread_pool_free (klass->thread_pool, FALSE, TRUE);
klass->thread_pool = NULL;
}
if (klass->completed_id) {
context = g_main_context_default ();
g_return_if_fail (context);
src = g_main_context_find_source_by_id (context, klass->completed_id);
g_assert (src);
g_source_destroy (src);
klass->completed_id = 0;
}
if (klass->completed_queue) {
g_assert (g_async_queue_length (klass->completed_queue));
g_async_queue_unref (klass->completed_queue);
klass->completed_queue = NULL;
}
}
GType
_gck_call_get_type (void)
{
static volatile gsize type_id__volatile = 0;
if (g_once_init_enter (&type_id__volatile)) {
static const GTypeInfo type_info = {
sizeof (GckCallClass),
(GBaseInitFunc)_gck_call_base_init,
(GBaseFinalizeFunc)_gck_call_base_finalize,
(GClassInitFunc)_gck_call_class_init,
(GClassFinalizeFunc)NULL,
NULL, // class_data
sizeof (GckCall),
0, // n_preallocs
(GInstanceInitFunc)_gck_call_init,
};
static const GInterfaceInfo interface_info = {
(GInterfaceInitFunc)_gck_call_implement_async_result
};
GType type_id = g_type_register_static (G_TYPE_OBJECT, "_GckCall", &type_info, 0);
g_type_add_interface_static (type_id, G_TYPE_ASYNC_RESULT, &interface_info);
g_once_init_leave (&type_id__volatile, type_id);
}
return type_id__volatile;
}
/* ----------------------------------------------------------------------------
* PUBLIC
*/
void
_gck_call_uninitialize (void)
{
}
gboolean
_gck_call_sync (gpointer object, gpointer perform, gpointer complete,
gpointer data, GCancellable *cancellable, GError **err)
{
GckArguments *args = (GckArguments*)data;
GckModule *module = NULL;
CK_RV rv;
g_assert (!object || G_IS_OBJECT (object));
g_assert (perform);
g_assert (args);
if (object) {
g_object_get (object, "module", &module, "handle", &args->handle, NULL);
g_assert (GCK_IS_MODULE (module));
/* We now hold a reference to module until below */
args->pkcs11 = gck_module_get_functions (module);
g_assert (args->pkcs11);
}
do {
rv = perform_call (perform, cancellable, args);
if (rv == CKR_FUNCTION_CANCELED)
break;
} while (!complete_call (complete, args, rv));
if (module)
g_object_unref (module);
if (rv == CKR_OK)
return TRUE;
g_set_error (err, GCK_ERROR, rv, "%s", gck_message_from_rv (rv));
return FALSE;
}
gpointer
_gck_call_async_prep (gpointer object, gpointer cb_object, gpointer perform,
gpointer complete, gsize args_size, gpointer destroy)
{
GckArguments *args;
GckCall *call;
g_assert (!object || G_IS_OBJECT (object));
g_assert (!cb_object || G_IS_OBJECT (cb_object));
g_assert (perform);
if (!destroy)
destroy = g_free;
if (args_size == 0)
args_size = sizeof (GckArguments);
g_assert (args_size >= sizeof (GckArguments));
args = g_malloc0 (args_size);
call = g_object_new (GCK_TYPE_CALL, NULL);
call->destroy = (GDestroyNotify)destroy;
call->perform = (GckPerformFunc)perform;
call->complete = (GckCompleteFunc)complete;
call->object = cb_object ? g_object_ref (cb_object) : NULL;
/* Hook the two together */
call->args = args;
call->args->call = call;
/* Setup call object if available */
if (object != NULL)
_gck_call_async_object (call, object);
return args;
}
void
_gck_call_async_object (GckCall *call, gpointer object)
{
g_assert (GCK_IS_CALL (call));
g_assert (call->args);
if (call->module)
g_object_unref (call->module);
call->module = NULL;
g_object_get (object, "module", &call->module, "handle", &call->args->handle, NULL);
g_assert (GCK_IS_MODULE (call->module));
call->args->pkcs11 = gck_module_get_functions (call->module);
/* We now hold a reference on module until finalize */
}
GckCall*
_gck_call_async_ready (gpointer data, GCancellable *cancellable,
GAsyncReadyCallback callback, gpointer user_data)
{
GckArguments *args = (GckArguments*)data;
g_assert (GCK_IS_CALL (args->call));
args->call->cancellable = cancellable;
if (cancellable) {
g_assert (G_IS_CANCELLABLE (cancellable));
g_object_ref (cancellable);
}
args->call->callback = callback;
args->call->user_data = user_data;
return args->call;
}
void
_gck_call_async_go (GckCall *call)
{
g_assert (GCK_IS_CALL (call));
/* To keep things balanced, process at one completed event */
process_completed(GCK_CALL_GET_CLASS (call));
g_assert (GCK_CALL_GET_CLASS (call)->thread_pool);
g_thread_pool_push (GCK_CALL_GET_CLASS (call)->thread_pool, call, NULL);
}
void
_gck_call_async_ready_go (gpointer data, GCancellable *cancellable,
GAsyncReadyCallback callback, gpointer user_data)
{
GckCall *call = _gck_call_async_ready (data, cancellable, callback, user_data);
_gck_call_async_go (call);
}
gboolean
_gck_call_basic_finish (GAsyncResult *result, GError **err)
{
CK_RV rv;
g_return_val_if_fail (GCK_IS_CALL (result), FALSE);
rv = GCK_CALL (result)->rv;
if (rv == CKR_OK)
return TRUE;
g_set_error (err, GCK_ERROR, rv, "%s", gck_message_from_rv (rv));
return FALSE;
}
void
_gck_call_async_short (GckCall *call, CK_RV rv)
{
g_assert (GCK_IS_CALL (call));
call->rv = rv;
/* Already complete, so just push it for processing in main loop */
g_assert (GCK_CALL_GET_CLASS (call)->completed_queue);
g_async_queue_push (GCK_CALL_GET_CLASS (call)->completed_queue, call);
g_main_context_wakeup (NULL);
}
gpointer
_gck_call_get_arguments (GckCall *call)
{
g_assert (GCK_IS_CALL (call));
return call->args;
}