/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* gck-enumerator.c - the GObject PKCS#11 wrapper library Copyright (C) 2010, 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 . Author: Stef Walter */ #include "config.h" #include "gck.h" #include "gck-private.h" #include /** * GckEnumerator: * * Can be used to enumerate through PKCS#11 objects. It will automatically * create sessions as necessary. * * Use [func@modules_enumerate_objects] or [func@modules_enumerate_uri] to * create an enumerator. To get the objects, use [method@Enumerator.next] or * [method@Enumerator.next_async] functions. */ enum { PROP_0, PROP_INTERACTION, PROP_OBJECT_TYPE, PROP_CHAINED }; typedef struct _GckEnumeratorResult { gulong handle; GckSession *session; GckAttributes *attrs; } GckEnumeratorResult; typedef struct _GckEnumeratorState GckEnumeratorState; typedef gpointer (*GckEnumeratorFunc) (GckEnumeratorState *args, gboolean forward); struct _GckEnumeratorState { gpointer enumerator; GckEnumeratorState *chained; /* For the current call */ gint want_objects; /* The state we're currently in */ GckEnumeratorFunc handler; /* Input to enumerator */ GList *modules; GckUriData *match; GckSessionOptions session_options; GTlsInteraction *interaction; /* The type of objects to create */ GType object_type; gpointer object_class; const gulong *attr_types; gint attr_count; /* state_slots */ GList *slots; /* state_slot */ GckSlot *slot; GckTokenInfo *token_info; CK_FUNCTION_LIST_PTR funcs; /* state_session */ GckSession *session; /* state_find */ GQueue *found; /* state_results */ GQueue *results; }; struct _GckEnumerator { GObject parent; GMutex mutex; GckEnumeratorState *the_state; GTlsInteraction *interaction; GType object_type; GckObjectClass *object_class; gulong *attr_types; gint attr_count; GckEnumerator *chained; }; G_DEFINE_TYPE (GckEnumerator, gck_enumerator, G_TYPE_OBJECT); static gpointer state_modules (GckEnumeratorState *args, gboolean forward); static gpointer state_slots (GckEnumeratorState *args, gboolean forward); static gpointer state_slot (GckEnumeratorState *args, gboolean forward); static gpointer state_session (GckEnumeratorState *args, gboolean forward); static gpointer state_find (GckEnumeratorState *args, gboolean forward); static gpointer state_results (GckEnumeratorState *args, gboolean forward); static void _gck_enumerator_result_free (gpointer data) { GckEnumeratorResult *result = data; g_object_unref (result->session); if (result->attrs) gck_attributes_unref (result->attrs); g_free (result); } static gpointer rewind_state (GckEnumeratorState *args, GckEnumeratorFunc handler) { g_assert (args); g_assert (handler); g_assert (args->handler); while (handler != args->handler) { args->handler = (args->handler) (args, FALSE); g_assert (args->handler); } return handler; } static void cleanup_state (GckEnumeratorState *args) { g_assert (args); /* Have each state cleanup */ rewind_state (args, state_modules); /* state_slots */ g_assert (!args->slots); /* state_slot */ g_assert (!args->slot); g_assert (!args->token_info); g_assert (!args->funcs); /* state_session */ g_assert (!args->session); /* state_find */ if (args->found) { g_queue_foreach (args->found, (GFunc) _gck_enumerator_result_free, NULL); g_queue_free (args->found); args->found = NULL; } /* state_results */ if (args->results) { g_queue_foreach (args->results, (GFunc) _gck_enumerator_result_free, NULL); g_queue_free (args->results); args->results = NULL; } g_clear_list (&args->modules, g_object_unref); g_clear_object (&args->interaction); if (args->object_class) g_type_class_unref (args->object_class); args->object_class = NULL; args->object_type = 0; if (args->match) { gck_uri_data_free (args->match); args->match = NULL; } } static gpointer state_modules (GckEnumeratorState *args, gboolean forward) { GckModule *module; g_assert (args->slots == NULL); if (forward) { /* There are no more modules? */ if (!args->modules) { g_debug ("no more modules, stopping enumerator"); return NULL; } /* Pop off the current module */ module = args->modules->data; g_assert (GCK_IS_MODULE (module)); args->modules = g_list_delete_link (args->modules, args->modules); args->slots = gck_module_get_slots (module, TRUE); GckModuleInfo *info = gck_module_get_info (module); g_debug ("enumerating into module: %s", info->library_description); gck_module_info_free (info); g_object_unref (module); return state_slots; } /* Should never be asked to go backward from start state */ g_assert_not_reached (); } static gpointer state_slots (GckEnumeratorState *args, gboolean forward) { GckSlot *slot; GckModule *module; GckTokenInfo *token_info; gboolean matched; g_assert (args->slot == NULL); /* slots to slot state */ if (forward) { /* If there are no more slots go back to start state */ if (!args->slots) { g_debug ("no more slots, want next module"); return rewind_state (args, state_modules); } /* Pop the next slot off the stack */ slot = args->slots->data; args->slots = g_list_delete_link (args->slots, args->slots); token_info = gck_slot_get_token_info (slot); if (!token_info) { g_message ("couldn't get token info for slot while enumerating"); g_object_unref (slot); /* Skip over this slot to the next slot */ return state_slots; } /* Do we have unrecognized matches? */ if (args->match->any_unrecognized) { g_debug ("token uri had unrecognized, not matching any tokens"); matched = FALSE; /* Are we trying to match the slot? */ } else if (args->match->token_info) { /* No match? Go to next slot */ matched = _gck_token_info_match (args->match->token_info, token_info); g_debug ("%s token: %s", matched ? "matched" : "did not match", token_info->label); } else { g_debug ("matching all tokens: %s", token_info->label); matched = TRUE; } if (!matched) { g_object_unref (slot); gck_token_info_free (token_info); return state_slots; } module = gck_slot_get_module (slot); args->funcs = gck_module_get_functions (module); g_assert (args->funcs); g_object_unref (module); /* We have a slot */ args->slot = slot; args->token_info = token_info; return state_slot; /* slots state to modules state */ } else { g_clear_list (&args->slots, g_object_unref); return state_modules; } } static gpointer state_slot (GckEnumeratorState *args, gboolean forward) { CK_SESSION_HANDLE session; CK_FLAGS flags; CK_RV rv; g_assert (args->slot); g_assert (args->funcs); g_assert (args->session == NULL); /* slot to session state */ if (forward) { flags = CKF_SERIAL_SESSION; if ((args->session_options & GCK_SESSION_READ_WRITE) == GCK_SESSION_READ_WRITE) flags |= CKF_RW_SESSION; rv = (args->funcs->C_OpenSession) (gck_slot_get_handle (args->slot), flags, NULL, NULL, &session); if (rv != CKR_OK) { g_message ("couldn't open session on module while enumerating objects: %s", gck_message_from_rv (rv)); return rewind_state (args, state_slots); } g_debug ("opened %s session", flags & CKF_RW_SESSION ? "read-write" : "read-only"); args->session = gck_session_from_handle (args->slot, session, args->session_options); return state_session; /* slot to slots state */ } else { g_object_unref (args->slot); args->slot = NULL; args->funcs = NULL; gck_token_info_free (args->token_info); args->token_info = NULL; return state_slots; } } static gpointer state_session (GckEnumeratorState *args, gboolean forward) { CK_RV rv; g_assert (args->funcs); g_assert (args->session); g_assert (args->token_info); /* session to authenticated state */ if (forward) { /* Don't want to authenticate? */ if ((args->session_options & GCK_SESSION_LOGIN_USER) == 0) { g_debug ("no authentication necessary, skipping"); return state_find; } rv = _gck_session_authenticate_token (args->funcs, gck_session_get_handle (args->session), args->slot, args->interaction, NULL); if (rv != CKR_OK) g_message ("couldn't authenticate when enumerating: %s", gck_message_from_rv (rv)); /* We try to proceed anyway with the enumeration */ return state_find; /* Session to slot state */ } else { g_clear_object (&args->session); return state_slot; } } static gpointer state_find (GckEnumeratorState *args, gboolean forward) { CK_OBJECT_HANDLE objects[128]; CK_SESSION_HANDLE session; CK_ATTRIBUTE_PTR attrs; CK_ULONG n_attrs, i,count; GckEnumeratorResult *result; CK_RV rv; /* Just go back, no logout */ if (!forward) return state_session; /* This is where we do the actual searching */ g_assert (args->session != NULL); g_assert (args->want_objects > 0); g_assert (args->funcs != NULL); if (!args->found) args->found = g_queue_new (); if (args->match->attributes) { attrs = _gck_attributes_commit_out (args->match->attributes, &n_attrs); gchar *string = gck_attributes_to_string (args->match->attributes); g_debug ("finding objects matching: %s", string); g_free (string); } else { attrs = NULL; n_attrs = 0; g_debug ("finding all objects"); } session = gck_session_get_handle (args->session); g_return_val_if_fail (session, NULL); /* Get all the objects */ rv = (args->funcs->C_FindObjectsInit) (session, attrs, n_attrs); if (rv == CKR_OK) { for(;;) { rv = (args->funcs->C_FindObjects) (session, objects, G_N_ELEMENTS (objects), &count); if (rv != CKR_OK || count == 0) break; g_debug ("matched %lu objects", count); for (i = 0; i < count; i++) { result = g_new0 (GckEnumeratorResult, 1); result->handle = objects[i]; result->session = g_object_ref (args->session); g_queue_push_tail (args->found, result); } } (args->funcs->C_FindObjectsFinal) (session); } g_debug ("finding objects completed with: %s", _gck_stringize_rv (rv)); return state_results; } static gpointer state_results (GckEnumeratorState *args, gboolean forward) { GckEnumeratorResult *result; GckBuilder builder; GckAttributes *attrs; CK_ATTRIBUTE_PTR template; CK_ULONG n_template; CK_SESSION_HANDLE session; gint count; CK_RV rv; gint i; g_assert (args->funcs != NULL); g_assert (args->object_class != NULL); g_assert (args->found != NULL); /* No cleanup, just unwind */ if (!forward) return state_find; if (!args->results) args->results = g_queue_new (); session = gck_session_get_handle (args->session); g_return_val_if_fail (session, NULL); /* Get the attributes for want_objects */ for (count = 0; count < args->want_objects; count++) { result = g_queue_pop_head (args->found); if (result == NULL) { g_debug ("wanted %d objects, have %d, looking for more", args->want_objects, g_queue_get_length (args->results)); return rewind_state (args, state_slots); } /* If no request for attributes, just go forward */ if (args->attr_count == 0) { g_queue_push_tail (args->results, result); continue; } gck_builder_init (&builder); for (i = 0; i < args->attr_count; ++i) gck_builder_add_empty (&builder, args->attr_types[i]); /* Ask for attribute sizes */ template = _gck_builder_prepare_in (&builder, &n_template); rv = (args->funcs->C_GetAttributeValue) (session, result->handle, template, n_template); if (GCK_IS_GET_ATTRIBUTE_RV_OK (rv)) { /* Allocate memory for each value */ template = _gck_builder_commit_in (&builder, &n_template); /* Now get the actual values */ rv = (args->funcs->C_GetAttributeValue) (session, result->handle, template, n_template); } attrs = gck_builder_end (&builder); if (GCK_IS_GET_ATTRIBUTE_RV_OK (rv)) { gchar *string = gck_attributes_to_string (attrs); g_debug ("retrieved attributes for object %lu: %s", result->handle, string); g_free (string); result->attrs = attrs; g_queue_push_tail (args->results, result); } else { g_message ("couldn't retrieve attributes when enumerating: %s", gck_message_from_rv (rv)); gck_attributes_unref (attrs); _gck_enumerator_result_free (result); } } g_debug ("wanted %d objects, returned %d objects", args->want_objects, g_queue_get_length (args->results)); /* We got all the results we wanted */ return NULL; } static void gck_enumerator_init (GckEnumerator *self) { g_mutex_init (&self->mutex); self->the_state = g_new0 (GckEnumeratorState, 1); self->object_type = GCK_TYPE_OBJECT; self->object_class = g_type_class_ref (self->object_type); g_assert (self->object_class); } static void gck_enumerator_get_property (GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec) { GckEnumerator *self = GCK_ENUMERATOR (obj); switch (prop_id) { case PROP_INTERACTION: g_value_take_object (value, gck_enumerator_get_interaction (self)); break; case PROP_OBJECT_TYPE: g_value_set_gtype (value, gck_enumerator_get_object_type (self)); break; case PROP_CHAINED: g_value_set_object (value, gck_enumerator_get_chained (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); break; } } static void gck_enumerator_set_property (GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec) { GckEnumerator *self = GCK_ENUMERATOR (obj); switch (prop_id) { case PROP_INTERACTION: gck_enumerator_set_interaction (self, g_value_get_object (value)); break; case PROP_OBJECT_TYPE: gck_enumerator_set_object_type (self, g_value_get_gtype (value)); break; case PROP_CHAINED: gck_enumerator_set_chained (self, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); break; } } static void gck_enumerator_dispose (GObject *obj) { GckEnumerator *self = GCK_ENUMERATOR (obj); gck_enumerator_set_interaction (self, NULL); gck_enumerator_set_chained (self, NULL); G_OBJECT_CLASS (gck_enumerator_parent_class)->dispose (obj); } static void gck_enumerator_finalize (GObject *obj) { GckEnumerator *self = GCK_ENUMERATOR (obj); g_assert (self->interaction == NULL); g_assert (self->the_state != NULL); cleanup_state (self->the_state); g_free (self->the_state); g_mutex_clear (&self->mutex); g_type_class_unref (self->object_class); g_free (self->attr_types); G_OBJECT_CLASS (gck_enumerator_parent_class)->finalize (obj); } static void gck_enumerator_class_init (GckEnumeratorClass *klass) { GObjectClass *gobject_class = (GObjectClass*)klass; gobject_class->get_property = gck_enumerator_get_property; gobject_class->set_property = gck_enumerator_set_property; gobject_class->dispose = gck_enumerator_dispose; gobject_class->finalize = gck_enumerator_finalize; /** * GckEnumerator:interaction: * * Interaction object used to ask the user for pins when opening * sessions. Used if the session_options of the enumerator have * %GCK_SESSION_LOGIN_USER */ g_object_class_install_property (gobject_class, PROP_INTERACTION, g_param_spec_object ("interaction", "Interaction", "Interaction asking for pins", G_TYPE_TLS_INTERACTION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GckEnumerator:object-type: (skip) * * The type of objects that are created by the enumerator. Must be * GckObject or derived from it. */ g_object_class_install_property (gobject_class, PROP_OBJECT_TYPE, g_param_spec_gtype ("object-type", "Object Type", "Type of objects created", GCK_TYPE_OBJECT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GckEnumerator:chained: * * Chained enumerator, which will be enumerated when this enumerator * has enumerated all its objects. */ g_object_class_install_property (gobject_class, PROP_CHAINED, g_param_spec_object ("chained", "Chained", "Chained enumerator", GCK_TYPE_ENUMERATOR, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); } static void created_enumerator (GckUriData *uri_data, const gchar *type) { gchar *attrs, *uri; attrs = uri_data->attributes ? gck_attributes_to_string (uri_data->attributes) : NULL; uri = uri_data ? gck_uri_data_build (uri_data, GCK_URI_FOR_TOKEN | GCK_URI_FOR_MODULE) : NULL; g_debug ("for = %s, tokens = %s, objects = %s", type, uri, attrs); g_free (attrs); g_free (uri); } GckEnumerator * _gck_enumerator_new_for_modules (GList *modules, GckSessionOptions session_options, GckUriData *uri_data) { GckEnumerator *self; GckEnumeratorState *state; self = g_object_new (GCK_TYPE_ENUMERATOR, NULL); state = self->the_state; state->session_options = session_options; state->modules = g_list_copy_deep (modules, (GCopyFunc) g_object_ref, NULL); state->slots = NULL; state->handler = state_modules; state->match = uri_data; created_enumerator (uri_data, "modules"); return self; } GckEnumerator * _gck_enumerator_new_for_slots (GList *slots, GckSessionOptions session_options, GckUriData *uri_data) { GckEnumerator *self; GckEnumeratorState *state; self = g_object_new (GCK_TYPE_ENUMERATOR, NULL); state = self->the_state; state->session_options = session_options; state->slots = g_list_copy_deep (slots, (GCopyFunc) g_object_ref, NULL); state->modules = NULL; state->handler = state_slots; state->match = uri_data; created_enumerator (uri_data, "slots"); return self; } GckEnumerator * _gck_enumerator_new_for_session (GckSession *session, GckUriData *uri_data) { GckEnumerator *self; GckEnumeratorState *state; GckModule *module; self = g_object_new (GCK_TYPE_ENUMERATOR, NULL); state = self->the_state; state->session = g_object_ref (session); state->modules = NULL; state->slots = NULL; state->handler = state_session; state->match = uri_data; state->slot = gck_session_get_slot (session); state->token_info = gck_slot_get_token_info (state->slot); module = gck_session_get_module (session); state->funcs = gck_module_get_functions (module); g_object_unref (module); created_enumerator (uri_data, "session"); return self; } typedef struct _EnumerateNext { GckArguments base; GckEnumeratorState *state; gint want_objects; } EnumerateNext; static CK_RV perform_enumerate_next (EnumerateNext *args) { GckEnumeratorFunc handler; GckEnumeratorState *state; gint count = 0; g_assert (args->state); for (state = args->state; state != NULL; state = state->chained) { g_assert (state->handler); state->want_objects = args->want_objects - count; for (;;) { handler = (state->handler) (state, TRUE); if (!handler) break; state->handler = handler; } count += state->results ? g_queue_get_length (state->results) : 0; if (count >= args->want_objects) break; } /* TODO: In some modes, errors */ return CKR_OK; } static void free_enumerate_next (EnumerateNext *args) { /* Should have been assigned back to enumerator */ g_assert (!args->state); g_free (args); } /** * gck_enumerator_get_object_type: * @self: an enumerator * * Get the type of objects created by this enumerator. The type will always * either be #GckObject or derived from it. * * Returns: the type of objects created */ GType gck_enumerator_get_object_type (GckEnumerator *self) { GType result; g_return_val_if_fail (GCK_IS_ENUMERATOR (self), 0); g_mutex_lock (&self->mutex); result = self->object_type; g_mutex_unlock (&self->mutex); return result; } /** * gck_enumerator_set_object_type: (skip) * @self: an enumerator * @object_type: the type of objects to create * * Set the type of objects to be created by this enumerator. The type must * always be either #GckObject or derived from it. * * If the #GckObjectCache interface is implemented on the derived class * and the default_types class field is set, then the enumerator will retrieve * attributes for each object. */ void gck_enumerator_set_object_type (GckEnumerator *self, GType object_type) { gck_enumerator_set_object_type_full (self, object_type, NULL, 0); } /** * gck_enumerator_set_object_type_full: (rename-to gck_enumerator_set_object_type) * @self: an enumerator * @object_type: the type of objects to create * @attr_types: (array length=attr_count): types of attributes to retrieve for objects * @attr_count: the number of attributes to retrieve * * Set the type of objects to be created by this enumerator. The type must * always be either #GckObject or derived from it. * * If @attr_types and @attr_count are non-NULL and non-zero respectively, * then the #GckObjectCache interface is expected to be implemented on the * derived class, then the enumerator will retrieve attributes for each object. */ void gck_enumerator_set_object_type_full (GckEnumerator *self, GType object_type, const gulong *attr_types, gint attr_count) { gpointer klass; g_return_if_fail (GCK_IS_ENUMERATOR (self)); if (!g_type_is_a (object_type, GCK_TYPE_OBJECT)) { g_warning ("the object_type '%s' is not a derived type of GckObject", g_type_name (object_type)); return; } klass = g_type_class_ref (object_type); g_mutex_lock (&self->mutex); if (self->object_type) g_type_class_unref (self->object_class); self->object_type = object_type; self->object_class = klass; g_free (self->attr_types); self->attr_types = NULL; self->attr_count = 0; if (attr_types) { self->attr_types = g_memdup2 (attr_types, sizeof (gulong) * attr_count); self->attr_count = attr_count; } g_mutex_unlock (&self->mutex); } /** * gck_enumerator_get_chained: * @self: the enumerator * * Get the enumerator that will be run after all objects from this one * are seen. * * Returns: (transfer full) (nullable): the chained enumerator or %NULL */ GckEnumerator * gck_enumerator_get_chained (GckEnumerator *self) { GckEnumerator *chained = NULL; g_return_val_if_fail (GCK_IS_ENUMERATOR (self), NULL); g_mutex_lock (&self->mutex); if (self->chained) chained = g_object_ref (self->chained); g_mutex_unlock (&self->mutex); return chained; } /** * gck_enumerator_set_chained: * @self: the enumerator * @chained: (nullable): the chained enumerator or %NULL * * Set a chained enumerator that will be run after all objects from this one * are seen. */ void gck_enumerator_set_chained (GckEnumerator *self, GckEnumerator *chained) { GckEnumerator *old_chained = NULL; g_return_if_fail (GCK_IS_ENUMERATOR (self)); g_return_if_fail (chained == NULL || GCK_IS_ENUMERATOR (chained)); g_mutex_lock (&self->mutex); old_chained = self->chained; if (chained) g_object_ref (chained); self->chained = chained; g_mutex_unlock (&self->mutex); if (old_chained) g_object_unref (old_chained); g_object_notify (G_OBJECT (self), "chained"); } /** * gck_enumerator_get_interaction: * @self: the enumerator * * Get the interaction used when a pin is needed * * Returns: (transfer full) (nullable): the interaction or %NULL */ GTlsInteraction * gck_enumerator_get_interaction (GckEnumerator *self) { GTlsInteraction *result = NULL; g_return_val_if_fail (GCK_IS_ENUMERATOR (self), NULL); g_mutex_lock (&self->mutex); if (self->interaction) result = g_object_ref (self->interaction); g_mutex_unlock (&self->mutex); return result; } /** * gck_enumerator_set_interaction: * @self: the enumerator * @interaction: (nullable): the interaction or %NULL * * Set the interaction used when a pin is needed */ void gck_enumerator_set_interaction (GckEnumerator *self, GTlsInteraction *interaction) { GTlsInteraction *previous = NULL; g_return_if_fail (GCK_IS_ENUMERATOR (self)); g_return_if_fail (interaction == NULL || G_IS_TLS_INTERACTION (interaction)); g_mutex_lock (&self->mutex); if (interaction != self->interaction) { previous = self->interaction; self->interaction = interaction; if (interaction) g_object_ref (interaction); } g_mutex_unlock (&self->mutex); g_clear_object (&previous); g_object_notify (G_OBJECT (self), "interaction"); } static GckEnumeratorState * check_out_enumerator_state (GckEnumerator *self) { GckEnumeratorState *state = NULL; GTlsInteraction *old_interaction = NULL; gpointer old_object_class = NULL; GckEnumeratorState *chained_state = NULL; GckObjectCacheInterface *object_iface; GckEnumerator *chained; chained = gck_enumerator_get_chained (self); if (chained) { chained_state = check_out_enumerator_state (chained); g_object_unref (chained); } g_mutex_lock (&self->mutex); if (self->the_state) { state = self->the_state; self->the_state = NULL; state->enumerator = g_object_ref (self); g_assert (state->chained == NULL); state->chained = chained_state; old_interaction = state->interaction; if (self->interaction) state->interaction = g_object_ref (self->interaction); else state->interaction = NULL; old_object_class = state->object_class; /* Must already be holding a reference, state also holds a ref */ state->object_type = self->object_type; state->object_class = g_type_class_peek (state->object_type); g_assert (state->object_class == self->object_class); object_iface = g_type_interface_peek (state->object_class, GCK_TYPE_OBJECT_CACHE); if (self->attr_types) { state->attr_types = self->attr_types; state->attr_count = self->attr_count; } else if (object_iface && object_iface->default_types) { state->attr_types = object_iface->default_types; state->attr_count = object_iface->n_default_types; } g_type_class_ref (state->object_type); } g_mutex_unlock (&self->mutex); if (state == NULL) g_warning ("this enumerator is already running a next operation"); /* Free these outside the lock */ if (old_interaction) g_object_unref (old_interaction); if (old_object_class) g_type_class_unref (old_object_class); return state; } static void check_in_enumerator_state (GckEnumeratorState *state) { GckEnumeratorState *chained = NULL; GckEnumerator *self; g_assert (GCK_IS_ENUMERATOR (state->enumerator)); self = state->enumerator; g_mutex_lock (&self->mutex); state->enumerator = NULL; g_assert (self->the_state == NULL); self->the_state = state; chained = state->chained; state->chained = NULL; g_mutex_unlock (&self->mutex); /* matches ref in check_in */ g_object_unref (self); if (chained) check_in_enumerator_state (chained); } static GckObject * extract_result (GckEnumeratorState *state) { GckEnumeratorResult *result = NULL; GckModule *module; GckObject *object; g_assert (state != NULL); if (state->results != NULL) result = g_queue_pop_head (state->results); if (result == NULL) { if (state->chained) return extract_result (state->chained); return NULL; } module = gck_session_get_module (result->session); object = g_object_new (state->object_type, "module", module, "handle", result->handle, "session", result->session, result->attrs ? "attributes" : NULL, result->attrs, NULL); g_object_unref (module); _gck_enumerator_result_free (result); return object; } static GList * extract_results (GckEnumeratorState *state, gint *want_objects) { GList *objects = NULL; GckObject *object; gint i; g_assert (state != NULL); g_assert (want_objects != NULL); for (i = 0; i < *want_objects; i++) { object = extract_result (state); if (object == NULL) break; objects = g_list_prepend (objects, object); } *want_objects -= i; return g_list_reverse (objects); } /** * gck_enumerator_next: * @self: The enumerator * @cancellable: A #GCancellable or %NULL * @error: A location to store an error on failure * * Get the next object in the enumerator, or %NULL if there are no more objects. * * %NULL is also returned if the function fails. Use the @error to determine * whether a failure occurred or not. * * Returns: (transfer full) (nullable): The next object, which must be released * using g_object_unref, or %NULL. */ GckObject * gck_enumerator_next (GckEnumerator *self, GCancellable *cancellable, GError **error) { EnumerateNext args = { GCK_ARGUMENTS_INIT, NULL, 0, }; GckObject *result = NULL; g_return_val_if_fail (GCK_IS_ENUMERATOR (self), NULL); g_return_val_if_fail (!error || !*error, NULL); args.state = check_out_enumerator_state (self); g_return_val_if_fail (args.state != NULL, NULL); /* A result from a previous run? */ result = extract_result (args.state); if (result == NULL) { args.want_objects = 1; /* Run the operation and steal away the results */ if (_gck_call_sync (NULL, perform_enumerate_next, NULL, &args, cancellable, error)) result = extract_result (args.state); args.want_objects = 0; } /* Put the state back */ check_in_enumerator_state (args.state); return result; } /** * gck_enumerator_next_n: * @self: An enumerator * @max_objects: The maximum amount of objects to enumerate * @cancellable: A #GCancellable or %NULL * @error: A location to store an error on failure * * Get the next set of objects from the enumerator. The maximum number of * objects can be specified with @max_objects. If -1 is specified, then all * the remaining objects will be returned. * * %NULL is also returned if the function fails. Use the @error to determine * whether a failure occurred or not. * * Returns: (transfer full) (element-type Gck.Object): A list of `Gck.Object`s */ GList * gck_enumerator_next_n (GckEnumerator *self, gint max_objects, GCancellable *cancellable, GError **error) { EnumerateNext args = { GCK_ARGUMENTS_INIT, NULL, 0, }; GList *results = NULL; gint want_objects; g_return_val_if_fail (GCK_IS_ENUMERATOR (self), NULL); g_return_val_if_fail (max_objects == -1 || max_objects > 0, NULL); g_return_val_if_fail (!error || !*error, NULL); /* Remove the state and own it ourselves */ args.state = check_out_enumerator_state (self); g_return_val_if_fail (args.state != NULL, NULL); want_objects = max_objects <= 0 ? G_MAXINT : max_objects; /* A result from a previous run? */ results = extract_results (args.state, &want_objects); if (want_objects > 0) { args.want_objects = want_objects; /* Run the operation and steal away the results */ if (_gck_call_sync (NULL, perform_enumerate_next, NULL, &args, cancellable, error)) results = g_list_concat (results, extract_results (args.state, &want_objects)); args.want_objects = 0; } /* Put the state back */ check_in_enumerator_state (args.state); if (results) g_clear_error (error); return results; } /** * gck_enumerator_next_async: * @self: An enumerator * @max_objects: The maximum number of objects to get * @cancellable: A #GCancellable or %NULL * @callback: Called when the result is ready * @user_data: Data to pass to the callback * * Get the next set of objects from the enumerator. This operation completes * asynchronously.The maximum number of objects can be specified with * @max_objects. If -1 is specified, then all the remaining objects will be * enumerated. */ void gck_enumerator_next_async (GckEnumerator *self, gint max_objects, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GckEnumeratorState *state; EnumerateNext *args; GckCall *call; g_return_if_fail (GCK_IS_ENUMERATOR (self)); g_return_if_fail (max_objects == -1 || max_objects > 0); g_object_ref (self); /* Remove the state and own it ourselves */ state = check_out_enumerator_state (self); g_return_if_fail (state != NULL); call = _gck_call_async_prep (NULL, perform_enumerate_next, NULL, sizeof (*args), free_enumerate_next); args = _gck_call_get_arguments (call); args->want_objects = max_objects <= 0 ? G_MAXINT : max_objects; args->state = state; _gck_call_async_ready_go (call, self, cancellable, callback, user_data); g_object_unref (self); } /** * gck_enumerator_next_finish: * @self: An enumerator * @result: The result passed to the callback * @error: A location to raise an error on failure. * * Complete an operation to enumerate next objects. * * %NULL is also returned if the function fails. Use the @error to determine * whether a failure occurred or not. * * Returns: (transfer full) (element-type Gck.Object): A list of `Gck.Object`s */ GList* gck_enumerator_next_finish (GckEnumerator *self, GAsyncResult *result, GError **error) { EnumerateNext *args; GckEnumeratorState *state; GList *results = NULL; gint want_objects; g_object_ref (self); args = _gck_call_async_result_arguments (result, EnumerateNext); state = args->state; args->state = NULL; want_objects = args->want_objects; args->want_objects = 0; if (_gck_call_basic_finish (result, error)) results = extract_results (state, &want_objects); /* Put the state back */ check_in_enumerator_state (state); g_object_unref (self); return results; }