1 /*-------------------------------------------------------------------------
2 * C-Pluff, a plug-in framework for C
3 * Copyright 2007 Johannes Lehtinen
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 *-----------------------------------------------------------------------*/
25 * Plug-in information functions
31 #include "../kazlib/hash.h"
38 /* ------------------------------------------------------------------------
40 * ----------------------------------------------------------------------*/
42 /// Registration of a dynamically allocated information object
43 typedef struct info_resource_t {
45 /// Pointer to the resource
48 /// Usage count for the resource
51 /// Deallocation function
52 cpi_dealloc_func_t dealloc_func;
56 /// A plug-in listener registration
57 typedef struct el_holder_t {
59 /// The plug-in listener
60 cp_plugin_listener_func_t plugin_listener;
62 /// The registering plug-in or NULL for the client program
65 /// Associated user data
72 /* ------------------------------------------------------------------------
73 * Function definitions
74 * ----------------------------------------------------------------------*/
76 // General information object management
78 CP_HIDDEN cp_status_t cpi_register_info(cp_context_t *context, void *res, cpi_dealloc_func_t df) {
79 cp_status_t status = CP_OK;
80 info_resource_t *ir = NULL;
82 assert(context != NULL);
85 assert(cpi_is_context_locked(context));
87 if ((ir = malloc(sizeof(info_resource_t))) == NULL) {
88 status = CP_ERR_RESOURCE;
93 ir->dealloc_func = df;
94 if (!hash_alloc_insert(context->env->infos, res, ir)) {
95 status = CP_ERR_RESOURCE;
101 if (status == CP_OK) {
102 cpi_debugf(context, _("An information object at address %p was registered."), res);
105 // Release resources on failure
106 if (status != CP_OK) {
115 CP_HIDDEN void cpi_use_info(cp_context_t *context, void *res) {
118 assert(context != NULL);
120 assert(cpi_is_context_locked(context));
121 if ((node = hash_lookup(context->env->infos, res)) != NULL) {
122 info_resource_t *ir = hnode_get(node);
124 cpi_debugf(context, _("Reference count of the information object at address %p increased to %d."), res, ir->usage_count);
126 cpi_fatalf(_("Reference count of an unknown information object at address %p could not be increased."), res);
130 CP_HIDDEN void cpi_release_info(cp_context_t *context, void *info) {
133 assert(context != NULL);
134 assert(info != NULL);
135 assert(cpi_is_context_locked(context));
136 if ((node = hash_lookup(context->env->infos, info)) != NULL) {
137 info_resource_t *ir = hnode_get(node);
138 assert(ir != NULL && info == ir->resource);
139 if (--ir->usage_count == 0) {
140 hash_delete_free(context->env->infos, node);
141 ir->dealloc_func(context, info);
142 cpi_debugf(context, _("The information object at address %p was unregistered."), info);
145 cpi_debugf(context, _("Reference count of the information object at address %p decreased to %d."), info, ir->usage_count);
148 cpi_fatalf(_("Could not release an unknown information object at address %p."), info);
152 CP_C_API void cp_release_info(cp_context_t *context, void *info) {
153 CHECK_NOT_NULL(context);
154 CHECK_NOT_NULL(info);
155 cpi_lock_context(context);
156 cpi_check_invocation(context, CPI_CF_LOGGER, __func__);
157 cpi_release_info(context, info);
158 cpi_unlock_context(context);
161 CP_HIDDEN void cpi_release_infos(cp_context_t *context) {
165 hash_scan_begin(&scan, context->env->infos);
166 while ((node = hash_scan_next(&scan)) != NULL) {
167 info_resource_t *ir = hnode_get(node);
168 cpi_lock_context(context);
169 cpi_errorf(context, _("An unreleased information object was encountered at address %p with reference count %d when destroying the associated plug-in context. Not releasing the object."), ir->resource, ir->usage_count);
170 cpi_unlock_context(context);
171 hash_scan_delfree(context->env->infos, node);
177 // Information acquiring functions
179 CP_C_API cp_plugin_info_t * cp_get_plugin_info(cp_context_t *context, const char *id, cp_status_t *error) {
181 cp_plugin_info_t *plugin = NULL;
182 cp_status_t status = CP_OK;
184 CHECK_NOT_NULL(context);
185 if (id == NULL && context->plugin == NULL) {
186 cpi_fatalf(_("The plug-in identifier argument to cp_get_plugin_info must not be NULL when the main program calls it."));
189 // Look up the plug-in and return information
190 cpi_lock_context(context);
191 cpi_check_invocation(context, CPI_CF_LOGGER, __func__);
194 // Lookup plug-in information
196 if ((node = hash_lookup(context->env->plugins, id)) == NULL) {
197 cpi_warnf(context, N_("Could not return information about unknown plug-in %s."), id);
198 status = CP_ERR_UNKNOWN;
201 plugin = ((cp_plugin_t *) hnode_get(node))->plugin;
203 plugin = context->plugin->plugin;
204 assert(plugin != NULL);
206 cpi_use_info(context, plugin);
208 cpi_unlock_context(context);
216 static void dealloc_plugins_info(cp_context_t *context, cp_plugin_info_t **plugins) {
219 assert(context != NULL);
220 assert(plugins != NULL);
221 for (i = 0; plugins[i] != NULL; i++) {
222 cpi_release_info(context, plugins[i]);
227 CP_C_API cp_plugin_info_t ** cp_get_plugins_info(cp_context_t *context, cp_status_t *error, int *num) {
228 cp_plugin_info_t **plugins = NULL;
230 cp_status_t status = CP_OK;
232 CHECK_NOT_NULL(context);
234 cpi_lock_context(context);
235 cpi_check_invocation(context, CPI_CF_LOGGER, __func__);
240 // Allocate space for pointer array
241 n = hash_count(context->env->plugins);
242 if ((plugins = malloc(sizeof(cp_plugin_info_t *) * (n + 1))) == NULL) {
243 status = CP_ERR_RESOURCE;
247 // Get plug-in information structures
248 hash_scan_begin(&scan, context->env->plugins);
250 while ((node = hash_scan_next(&scan)) != NULL) {
251 cp_plugin_t *rp = hnode_get(node);
254 cpi_use_info(context, rp->plugin);
255 plugins[i] = rp->plugin;
260 // Register the array
261 status = cpi_register_info(context, plugins, (void (*)(cp_context_t *, void *)) dealloc_plugins_info);
266 if (status != CP_OK) {
267 cpi_error(context, N_("Plug-in information could not be returned due to insufficient memory."));
269 cpi_unlock_context(context);
271 // Release resources on error
272 if (status != CP_OK) {
273 if (plugins != NULL) {
274 dealloc_plugins_info(context, plugins);
279 assert(status != CP_OK || n == 0 || plugins[n - 1] != NULL);
283 if (num != NULL && status == CP_OK) {
289 CP_C_API cp_plugin_state_t cp_get_plugin_state(cp_context_t *context, const char *id) {
290 cp_plugin_state_t state = CP_PLUGIN_UNINSTALLED;
293 CHECK_NOT_NULL(context);
296 // Look up the plug-in state
297 cpi_lock_context(context);
298 cpi_check_invocation(context, CPI_CF_LOGGER, __func__);
299 if ((hnode = hash_lookup(context->env->plugins, id)) != NULL) {
300 cp_plugin_t *rp = hnode_get(hnode);
303 cpi_unlock_context(context);
307 static void dealloc_ext_points_info(cp_context_t *context, cp_ext_point_t **ext_points) {
310 assert(context != NULL);
311 assert(ext_points != NULL);
312 for (i = 0; ext_points[i] != NULL; i++) {
313 cpi_release_info(context, ext_points[i]->plugin);
318 CP_C_API cp_ext_point_t ** cp_get_ext_points_info(cp_context_t *context, cp_status_t *error, int *num) {
319 cp_ext_point_t **ext_points = NULL;
321 cp_status_t status = CP_OK;
323 CHECK_NOT_NULL(context);
325 cpi_lock_context(context);
326 cpi_check_invocation(context, CPI_CF_LOGGER, __func__);
331 // Allocate space for pointer array
332 n = hash_count(context->env->ext_points);
333 if ((ext_points = malloc(sizeof(cp_ext_point_t *) * (n + 1))) == NULL) {
334 status = CP_ERR_RESOURCE;
338 // Get extension point information structures
339 hash_scan_begin(&scan, context->env->ext_points);
341 while ((node = hash_scan_next(&scan)) != NULL) {
342 cp_ext_point_t *ep = hnode_get(node);
345 cpi_use_info(context, ep->plugin);
349 ext_points[i] = NULL;
351 // Register the array
352 status = cpi_register_info(context, ext_points, (void (*)(cp_context_t *, void *)) dealloc_ext_points_info);
357 if (status != CP_OK) {
358 cpi_error(context, N_("Extension point information could not be returned due to insufficient memory."));
360 cpi_unlock_context(context);
362 // Release resources on error
363 if (status != CP_OK) {
364 if (ext_points != NULL) {
365 dealloc_ext_points_info(context, ext_points);
370 assert(status != CP_OK || n == 0 || ext_points[n - 1] != NULL);
374 if (num != NULL && status == CP_OK) {
380 static void dealloc_extensions_info(cp_context_t *context, cp_extension_t **extensions) {
383 assert(context != NULL);
384 assert(extensions != NULL);
385 for (i = 0; extensions[i] != NULL; i++) {
386 cpi_release_info(context, extensions[i]->plugin);
391 CP_C_API cp_extension_t ** cp_get_extensions_info(cp_context_t *context, const char *extpt_id, cp_status_t *error, int *num) {
392 cp_extension_t **extensions = NULL;
394 cp_status_t status = CP_OK;
396 CHECK_NOT_NULL(context);
398 cpi_lock_context(context);
399 cpi_check_invocation(context, CPI_CF_LOGGER, __func__);
404 // Count the number of extensions
405 if (extpt_id != NULL) {
406 if ((hnode = hash_lookup(context->env->extensions, extpt_id)) != NULL) {
407 n = list_count((list_t *) hnode_get(hnode));
415 hash_scan_begin(&scan, context->env->extensions);
416 while ((hnode = hash_scan_next(&scan)) != NULL) {
417 n += list_count((list_t *) hnode_get(hnode));
421 // Allocate space for pointer array
422 if ((extensions = malloc(sizeof(cp_extension_t *) * (n + 1))) == NULL) {
423 status = CP_ERR_RESOURCE;
427 // Get extension information structures
428 if (extpt_id != NULL) {
430 if ((hnode = hash_lookup(context->env->extensions, extpt_id)) != NULL) {
431 list_t *el = hnode_get(hnode);
434 lnode = list_first(el);
435 while (lnode != NULL) {
436 cp_extension_t *e = lnode_get(lnode);
439 cpi_use_info(context, e->plugin);
442 lnode = list_next(el, lnode);
445 extensions[i] = NULL;
447 hash_scan_begin(&scan, context->env->extensions);
449 while ((hnode = hash_scan_next(&scan)) != NULL) {
450 list_t *el = hnode_get(hnode);
453 lnode = list_first(el);
454 while (lnode != NULL) {
455 cp_extension_t *e = lnode_get(lnode);
458 cpi_use_info(context, e->plugin);
461 lnode = list_next(el, lnode);
465 extensions[i] = NULL;
467 // Register the array
468 status = cpi_register_info(context, extensions, (void (*)(cp_context_t *, void *)) dealloc_extensions_info);
473 if (status != CP_OK) {
474 cpi_error(context, N_("Extension information could not be returned due to insufficient memory."));
476 cpi_unlock_context(context);
478 // Release resources on error
479 if (status != CP_OK) {
480 if (extensions != NULL) {
481 dealloc_extensions_info(context, extensions);
486 assert(status != CP_OK || n == 0 || extensions[n - 1] != NULL);
490 if (num != NULL && status == CP_OK) {
500 * Compares plug-in listener holders.
502 * @param h1 the first holder to be compared
503 * @param h2 the second holder to be compared
504 * @return zero if the holders point to the same function, otherwise non-zero
506 static int comp_el_holder(const void *h1, const void *h2) {
507 const el_holder_t *plh1 = h1;
508 const el_holder_t *plh2 = h2;
510 return (plh1->plugin_listener != plh2->plugin_listener);
514 * Processes a node by delivering the specified event to the associated
517 * @param list the list being processed
518 * @param node the node being processed
519 * @param event the event
521 static void process_event(list_t *list, lnode_t *node, void *event) {
522 el_holder_t *h = lnode_get(node);
523 cpi_plugin_event_t *e = event;
524 h->plugin_listener(e->plugin_id, e->old_state, e->new_state, h->user_data);
528 * Processes a node by unregistering the associated plug-in listener.
530 * @param list the list being processed
531 * @param node the node being processed
532 * @param plugin plugin whose listeners are to be unregistered or NULL for all
534 static void process_unregister_plistener(list_t *list, lnode_t *node, void *plugin) {
535 el_holder_t *h = lnode_get(node);
536 if (plugin == NULL || h->plugin == plugin) {
537 list_delete(list, node);
543 CP_HIDDEN void cpi_unregister_plisteners(list_t *listeners, cp_plugin_t *plugin) {
544 list_process(listeners, plugin, process_unregister_plistener);
547 CP_C_API cp_status_t cp_register_plistener(cp_context_t *context, cp_plugin_listener_func_t listener, void *user_data) {
548 cp_status_t status = CP_ERR_RESOURCE;
552 CHECK_NOT_NULL(context);
553 CHECK_NOT_NULL(listener);
555 cpi_lock_context(context);
556 cpi_check_invocation(context, CPI_CF_LOGGER | CPI_CF_LISTENER, __func__);
557 if ((holder = malloc(sizeof(el_holder_t))) != NULL) {
558 holder->plugin_listener = listener;
559 holder->plugin = context->plugin;
560 holder->user_data = user_data;
561 if ((node = lnode_create(holder)) != NULL) {
562 list_append(context->env->plugin_listeners, node);
569 // Report error or success
570 if (status != CP_OK) {
571 cpi_error(context, _("A plug-in listener could not be registered due to insufficient memory."));
572 } else if (cpi_is_logged(context, CP_LOG_DEBUG)) {
574 /* TRANSLATORS: %s is the context owner */
575 cpi_debugf(context, N_("%s registered a plug-in listener."), cpi_context_owner(context, owner, sizeof(owner)));
577 cpi_unlock_context(context);
582 CP_C_API void cp_unregister_plistener(cp_context_t *context, cp_plugin_listener_func_t listener) {
586 CHECK_NOT_NULL(context);
587 holder.plugin_listener = listener;
588 cpi_lock_context(context);
589 cpi_check_invocation(context, CPI_CF_LOGGER | CPI_CF_LISTENER, __func__);
590 node = list_find(context->env->plugin_listeners, &holder, comp_el_holder);
592 process_unregister_plistener(context->env->plugin_listeners, node, NULL);
594 if (cpi_is_logged(context, CP_LOG_DEBUG)) {
596 /* TRANSLATORS: %s is the context owner */
597 cpi_debugf(context, N_("%s unregistered a plug-in listener."), cpi_context_owner(context, owner, sizeof(owner)));
599 cpi_unlock_context(context);
602 CP_HIDDEN void cpi_deliver_event(cp_context_t *context, const cpi_plugin_event_t *event) {
603 assert(event != NULL);
604 assert(event->plugin_id != NULL);
605 cpi_lock_context(context);
606 context->env->in_event_listener_invocation++;
607 list_process(context->env->plugin_listeners, (void *) event, process_event);
608 context->env->in_event_listener_invocation--;
609 cpi_unlock_context(context);
610 if (cpi_is_logged(context, CP_LOG_INFO)) {
612 switch (event->new_state) {
613 case CP_PLUGIN_UNINSTALLED:
614 str = N_("Plug-in %s has been uninstalled.");
616 case CP_PLUGIN_INSTALLED:
617 if (event->old_state < CP_PLUGIN_INSTALLED) {
618 str = N_("Plug-in %s has been installed.");
620 str = N_("Plug-in %s runtime library has been unloaded.");
623 case CP_PLUGIN_RESOLVED:
624 if (event->old_state < CP_PLUGIN_RESOLVED) {
625 str = N_("Plug-in %s runtime library has been loaded.");
627 str = N_("Plug-in %s has been stopped.");
630 case CP_PLUGIN_STARTING:
631 str = N_("Plug-in %s is starting.");
633 case CP_PLUGIN_STOPPING:
634 str = N_("Plug-in %s is stopping.");
636 case CP_PLUGIN_ACTIVE:
637 str = N_("Plug-in %s has been started.");
644 cpi_infof(context, str, event->plugin_id);
650 // Configuration element helpers
652 static cp_cfg_element_t * lookup_cfg_element(cp_cfg_element_t *base, const char *path, int len) {
655 CHECK_NOT_NULL(base);
656 CHECK_NOT_NULL(path);
659 while (base != NULL && path[start] != '\0' && (len == -1 || start < len)) {
661 while (path[end] != '\0' && path[end] != '/' && (len == -1 || end < len))
663 if (end - start == 2 && !strncmp(path + start, "..", 2)) {
669 for (i = 0; !found && i < base->num_children; i++) {
670 cp_cfg_element_t *e = base->children + i;
671 if (end - start == strlen(e->name)
672 && !strncmp(path + start, e->name, end - start)) {
682 if (path[start] == '/') {
689 CP_C_API cp_cfg_element_t * cp_lookup_cfg_element(cp_cfg_element_t *base, const char *path) {
690 return lookup_cfg_element(base, path, -1);
693 CP_C_API char * cp_lookup_cfg_value(cp_cfg_element_t *base, const char *path) {
697 CHECK_NOT_NULL(base);
698 CHECK_NOT_NULL(path);
700 if ((attr = strrchr(path, '@')) == NULL) {
701 e = lookup_cfg_element(base, path, -1);
703 e = lookup_cfg_element(base, path, attr - path);
712 for (i = 0; i < e->num_atts; i++) {
713 if (!strcmp(attr, e->atts[2*i])) {
714 return e->atts[2*i + 1];