/* -*- 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 Copyright (C) 2020, Marco Trevisan 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 . Author: Stef Walter */ #include "config.h" #include "gck-private.h" #include struct _GckCall { GObject parent; GTask *task; GckModule *module; /* For making the call */ GckPerformFunc perform; GckCompleteFunc complete; GckArguments *args; GDestroyNotify destroy; }; G_DEFINE_TYPE (GckCall, _gck_call, G_TYPE_OBJECT) /* ---------------------------------------------------------------------------- * 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 CK_RV perform_call_chain (GckPerformFunc perform, GckCompleteFunc complete, GCancellable *cancellable, GckArguments *args) { CK_RV rv; do { rv = perform_call (perform, cancellable, args); if (rv == CKR_FUNCTION_CANCELED) break; } while (!complete_call (complete, args, rv)); return rv; } static void _gck_task_return (GTask *task, CK_RV rv) { if (rv == CKR_OK) { g_task_return_boolean (task, TRUE); } else if (rv == CKR_FUNCTION_CANCELED) { g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Gck function call cancelled"); } else { g_task_return_new_error (task, GCK_ERROR, rv, "%s", gck_message_from_rv (rv)); } } static void _gck_call_thread_func (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { GckCall *call = task_data; CK_RV rv; /* Double check a few things */ g_assert (GCK_IS_CALL (call)); rv = perform_call_chain (call->perform, call->complete, cancellable, call->args); _gck_task_return (task, rv); } /* ---------------------------------------------------------------------------- * OBJECT */ static void _gck_call_init (GckCall *call) { } static void _gck_call_finalize (GObject *obj) { GckCall *call = GCK_CALL (obj); if (call->module) g_object_unref (call->module); call->module = NULL; g_clear_object (&call->task); if (call->destroy) (call->destroy) (call->args); call->destroy = NULL; call->args = NULL; G_OBJECT_CLASS (_gck_call_parent_class)->finalize (obj); } static void _gck_call_class_init (GckCallClass *klass) { GObjectClass *gobject_class = (GObjectClass*)klass; gobject_class->finalize = _gck_call_finalize; } /* ---------------------------------------------------------------------------- * 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); } rv = perform_call_chain (perform, complete, cancellable, args); 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; } GckCall* _gck_call_async_prep (gpointer object, gpointer perform, gpointer complete, gsize args_size, gpointer destroy) { GckArguments *args; GckCall *call; g_assert (!object || G_IS_OBJECT (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; /* Hook the two together */ call->args = args; /* Setup call object if available */ if (object != NULL) _gck_call_async_object (call, object); return call; } 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 (GckCall *call, gpointer cb_object, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask* task; g_assert (GCK_IS_CALL (call)); g_assert (call->args && "GckCall not prepared"); g_assert (!cb_object || G_IS_OBJECT (cb_object)); g_object_ref (call); task = g_task_new (cb_object, cancellable, callback, user_data); g_task_set_task_data (task, call, g_object_unref); g_set_object (&call->task, task); g_object_unref (task); g_object_unref (call); return call; } void _gck_call_async_go (GckCall *call) { g_assert (GCK_IS_CALL (call)); g_assert (G_IS_TASK (call->task)); g_task_run_in_thread (call->task, _gck_call_thread_func); g_clear_object (&call->task); } void _gck_call_async_ready_go (GckCall *call, gpointer cb_object, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { _gck_call_async_ready (call, cb_object, cancellable, callback, user_data); _gck_call_async_go (call); } gboolean _gck_call_basic_finish (GAsyncResult *result, GError **err) { g_return_val_if_fail (G_IS_TASK (result), FALSE); return g_task_propagate_boolean (G_TASK (result), err); } void _gck_call_async_short (GckCall *call, CK_RV rv) { g_assert (GCK_IS_CALL (call)); /* Already complete, so just push it for processing in main loop */ _gck_task_return (call->task, rv); g_clear_object (&call->task); g_main_context_wakeup (NULL); } gpointer _gck_call_get_arguments (GckCall *call) { g_assert (GCK_IS_CALL (call)); return call->args; }