iotivity 0.9.0
[platform/upstream/iotivity.git] / service / protocol-plugin / lib / cpluff / libcpluff / pcontrol.c
1 /*-------------------------------------------------------------------------
2  * C-Pluff, a plug-in framework for C
3  * Copyright 2007 Johannes Lehtinen
4  * 
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:
11  *
12  * The above copyright notice and this permission notice shall be included
13  * in all copies or substantial portions of the Software.
14  *
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  *-----------------------------------------------------------------------*/
23
24 /** @file
25  * Core plug-in management functions
26  */
27
28 #ifdef HAVE_CONFIG_H
29 #include "config.h"
30 #endif
31
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <assert.h>
35 #include <string.h>
36 #include <stddef.h>
37 #include "../kazlib/list.h"
38 #include "../kazlib/hash.h"
39 #include "cpluff.h"
40 #include "defines.h"
41 #include "util.h"
42 #include "internal.h"
43
44
45 /* ------------------------------------------------------------------------
46  * Function definitions
47  * ----------------------------------------------------------------------*/
48
49 // Plug-in control
50
51 #ifndef NDEBUG
52 static void assert_processed_zero(cp_context_t *context) {
53         hscan_t scan;
54         hnode_t *node;
55         
56         hash_scan_begin(&scan, context->env->plugins);
57         while ((node = hash_scan_next(&scan)) != NULL) {
58                 cp_plugin_t *plugin = hnode_get(node);
59                 assert(plugin->processed == 0);
60         }
61 }
62 #else
63 #define assert_processed_zero(c) assert(1)
64 #endif
65
66 static void unregister_extensions(cp_context_t *context, cp_plugin_info_t *plugin) {
67         int i;
68         
69         for (i = 0; i < plugin->num_ext_points; i++) {
70                 cp_ext_point_t *ep = plugin->ext_points + i;
71                 hnode_t *hnode;
72                 
73                 if ((hnode = hash_lookup(context->env->ext_points, ep->identifier)) != NULL
74                         && hnode_get(hnode) == ep) {
75                         hash_delete_free(context->env->ext_points, hnode);
76                 }
77         }
78         for (i = 0; i < plugin->num_extensions; i++) {
79                 cp_extension_t *e = plugin->extensions + i;
80                 hnode_t *hnode;
81                 
82                 if ((hnode = hash_lookup(context->env->extensions, e->ext_point_id)) != NULL) {
83                         list_t *el = hnode_get(hnode);
84                         lnode_t *lnode = list_first(el);
85                         
86                         while (lnode != NULL) {
87                                 lnode_t *nn = list_next(el, lnode);
88                                 if (lnode_get(lnode) == e) {
89                                         list_delete(el, lnode);
90                                         lnode_destroy(lnode);
91                                         break;
92                                 }
93                                 lnode = nn;
94                         }
95                         if (list_isempty(el)) {
96                                 char *epid = (char *) hnode_getkey(hnode);                              
97                                 hash_delete_free(context->env->extensions, hnode);
98                                 free(epid);
99                                 list_destroy(el);
100                         }
101                 }
102         }
103 }
104
105 CP_C_API cp_status_t cp_install_plugin(cp_context_t *context, cp_plugin_info_t *plugin) {
106         cp_plugin_t *rp = NULL;
107         cp_status_t status = CP_OK;
108         cpi_plugin_event_t event;
109         int i;
110
111         CHECK_NOT_NULL(context);
112         CHECK_NOT_NULL(plugin);
113         
114         cpi_lock_context(context);
115         cpi_check_invocation(context, CPI_CF_ANY, __func__);
116         do {
117                 
118                 // Check that there is no conflicting plug-in already loaded 
119                 if (hash_lookup(context->env->plugins, plugin->identifier) != NULL) {
120                         cpi_errorf(context,
121                                 N_("Plug-in %s could not be installed because a plug-in with the same identifier is already installed."), 
122                                 plugin->identifier);
123                         status = CP_ERR_CONFLICT;
124                         break;
125                 }
126
127                 // Increase usage count for the plug-in descriptor
128                 cpi_use_info(context, plugin);
129
130                 // Allocate space for the plug-in state 
131                 if ((rp = malloc(sizeof(cp_plugin_t))) == NULL) {
132                         status = CP_ERR_RESOURCE;
133                         break;
134                 }
135         
136                 // Initialize plug-in state 
137                 memset(rp, 0, sizeof(cp_plugin_t));
138                 rp->context = NULL;
139                 rp->plugin = plugin;
140                 rp->state = CP_PLUGIN_INSTALLED;
141                 rp->imported = NULL;
142                 rp->runtime_lib = NULL;
143                 rp->runtime_funcs = NULL;
144                 rp->plugin_data = NULL;
145                 rp->importing = list_create(LISTCOUNT_T_MAX);
146                 if (rp->importing == NULL) {
147                         status = CP_ERR_RESOURCE;
148                         break;
149                 }
150                 if (!hash_alloc_insert(context->env->plugins, plugin->identifier, rp)) {
151                         status = CP_ERR_RESOURCE;
152                         break;
153                 }
154                 
155                 // Register extension points
156                 for (i = 0; status == CP_OK && i < plugin->num_ext_points; i++) {
157                         cp_ext_point_t *ep = plugin->ext_points + i;
158                         hnode_t *hnode;
159                         
160                         if ((hnode = hash_lookup(context->env->ext_points, ep->identifier)) != NULL) {
161                                 cpi_errorf(context, N_("Plug-in %s could not be installed because extension point %s conflicts with an already installed extension point."), plugin->identifier, ep->identifier);
162                                 status = CP_ERR_CONFLICT;
163                         } else if (!hash_alloc_insert(context->env->ext_points, ep->identifier, ep)) {
164                                 status = CP_ERR_RESOURCE;
165                         }
166                 }
167                 
168                 // Register extensions
169                 for (i = 0; status == CP_OK && i < plugin->num_extensions; i++) {
170                         cp_extension_t *e = plugin->extensions + i;
171                         hnode_t *hnode;
172                         lnode_t *lnode;
173                         list_t *el;
174                         
175                         if ((hnode = hash_lookup(context->env->extensions, e->ext_point_id)) == NULL) {
176                                 char *epid;
177                                 if ((el = list_create(LISTCOUNT_T_MAX)) != NULL
178                                         && (epid = strdup(e->ext_point_id)) != NULL) {
179                                         if (!hash_alloc_insert(context->env->extensions, epid, el)) {
180                                                 list_destroy(el);
181                                                 status = CP_ERR_RESOURCE;
182                                                 break;
183                                         }
184                                 } else {
185                                         if (el != NULL) {
186                                                 list_destroy(el);
187                                         }
188                                         status = CP_ERR_RESOURCE;
189                                         break;
190                                 }
191                         } else {
192                                 el = hnode_get(hnode);
193                         }
194                         if ((lnode = lnode_create(e)) != NULL) {
195                                 list_append(el, lnode);
196                         } else {
197                                 status = CP_ERR_RESOURCE;
198                                 break;
199                         }
200                 }
201
202                 // Break if previous loops failed
203                 if (status != CP_OK) {
204                         break;
205                 }
206                 
207                 // Plug-in installed 
208                 event.plugin_id = plugin->identifier;
209                 event.old_state = CP_PLUGIN_UNINSTALLED;
210                 event.new_state = rp->state;
211                 cpi_deliver_event(context, &event);
212
213         } while (0);
214
215         // Release resources on failure
216         if (status != CP_OK) {
217                 if (rp != NULL) {
218                         if (rp->importing != NULL) {
219                                 list_destroy(rp->importing);
220                         }
221                         free(rp);
222                 }
223                 unregister_extensions(context, plugin);
224         }
225
226         // Report possible resource error
227         if (status == CP_ERR_RESOURCE) {
228                 cpi_errorf(context,
229                         N_("Plug-in %s could not be installed due to insufficient system resources."), plugin->identifier);
230         }
231         cpi_unlock_context(context);
232
233         return status;
234 }
235
236 /**
237  * Unresolves the plug-in runtime information.
238  * 
239  * @param plugin the plug-in to unresolve
240  */
241 static void unresolve_plugin_runtime(cp_plugin_t *plugin) {
242
243         // Destroy the plug-in instance, if necessary
244         if (plugin->context != NULL) {
245                 plugin->context->env->in_destroy_func_invocation++;
246                 plugin->runtime_funcs->destroy(plugin->plugin_data);
247                 plugin->context->env->in_destroy_func_invocation--;
248                 plugin->plugin_data = NULL;
249                 cpi_free_context(plugin->context);
250                 plugin->context = NULL;
251         }
252
253         // Close plug-in runtime library        
254         plugin->runtime_funcs = NULL;
255         if (plugin->runtime_lib != NULL) {
256                 DLCLOSE(plugin->runtime_lib);
257                 plugin->runtime_lib = NULL;
258         }       
259 }
260
261 /**
262  * Loads and resolves the plug-in runtime library and initialization functions.
263  * 
264  * @param context the plug-in context
265  * @param plugin the plugin
266  * @return CP_OK (zero) on success or error code on failure
267  */
268 static int resolve_plugin_runtime(cp_context_t *context, cp_plugin_t *plugin) {
269         char *rlpath = NULL;
270         int rlpath_len;
271         cp_status_t status = CP_OK;
272         
273         assert(plugin->runtime_lib == NULL);
274         if (plugin->plugin->runtime_lib_name == NULL) {
275                 return CP_OK;
276         }
277         
278         do {
279                 int ppath_len, lname_len;
280                 int cpluff_compatibility = 1;
281         
282                 // Check C-Pluff compatibility
283                 if (plugin->plugin->req_cpluff_version != NULL) {
284 #ifdef CP_ABI_COMPATIBILITY
285                         cpluff_compatibility = (
286                                 cpi_vercmp(plugin->plugin->req_cpluff_version, CP_VERSION) <= 0
287                                 && cpi_vercmp(plugin->plugin->req_cpluff_version, CP_ABI_COMPATIBILITY) >= 0);
288 #else
289                         cpluff_compatibility = (cpi_vercmp(plugin->plugin->req_cpluff_version, CP_VERSION) == 0);
290 #endif
291                 }
292                 if (!cpluff_compatibility) {
293                         cpi_errorf(context, N_("Plug-in %s could not be resolved due to version incompatibility with C-Pluff."), plugin->plugin->identifier);
294                         status = CP_ERR_DEPENDENCY;
295                         break;
296                 }
297
298                 // Construct a path to plug-in runtime library.
299                 /// @todo Add platform specific prefix (for example, "lib")
300                 ppath_len = strlen(plugin->plugin->plugin_path);
301                 lname_len = strlen(plugin->plugin->runtime_lib_name);
302                 rlpath_len = ppath_len + lname_len + strlen(CP_SHREXT) + 2;
303                 if ((rlpath = malloc(rlpath_len * sizeof(char))) == NULL) {
304                         cpi_errorf(context, N_("Plug-in %s runtime library could not be loaded due to insufficient memory."), plugin->plugin->identifier);
305                         status = CP_ERR_RESOURCE;
306                         break;
307                 }
308                 memset(rlpath, 0, rlpath_len * sizeof(char));
309                 strcpy(rlpath, plugin->plugin->plugin_path);
310                 rlpath[ppath_len] = CP_FNAMESEP_CHAR;
311                 strcpy(rlpath + ppath_len + 1, plugin->plugin->runtime_lib_name);
312                 strcpy(rlpath + ppath_len + 1 + lname_len, CP_SHREXT);
313                 
314                 // Open the plug-in runtime library 
315                 plugin->runtime_lib = DLOPEN(rlpath);
316                 if (plugin->runtime_lib == NULL) {
317                         const char *error = DLERROR();
318                         if (error == NULL) {
319                                 error = _("Unspecified error.");
320                         }
321                         cpi_errorf(context, N_("Plug-in %s runtime library %s could not be opened: %s"), plugin->plugin->identifier, rlpath, error);
322                         status = CP_ERR_RUNTIME;
323                         break;
324                 }
325                 
326                 // Resolve plug-in functions
327                 if (plugin->plugin->runtime_funcs_symbol != NULL) {
328                         plugin->runtime_funcs = (cp_plugin_runtime_t *) DLSYM(plugin->runtime_lib, plugin->plugin->runtime_funcs_symbol);
329                         if (plugin->runtime_funcs == NULL) {
330                                 const char *error = DLERROR();
331                                 if (error == NULL) {
332                                         error = _("Unspecified error.");
333                                 }
334                                 cpi_errorf(context, N_("Plug-in %s symbol %s containing plug-in runtime information could not be resolved: %s"), plugin->plugin->identifier, plugin->plugin->runtime_funcs_symbol, error);
335                                 status = CP_ERR_RUNTIME;
336                                 break;
337                         }
338                         if (plugin->runtime_funcs->create == NULL
339                                 || plugin->runtime_funcs->destroy == NULL) {
340                                 cpi_errorf(context, N_("Plug-in %s is missing a constructor or destructor function."), plugin->plugin->identifier);
341                                 status = CP_ERR_RUNTIME;
342                                 break;
343                         }
344                 }
345
346         } while (0);
347         
348         // Release resources 
349         free(rlpath);
350         if (status != CP_OK) {
351                 unresolve_plugin_runtime(plugin);
352         }
353         
354         return status;
355 }
356
357 /**
358  * Resolves the specified plug-in import into a plug-in pointer. Does not
359  * try to resolve the imported plug-in.
360  * 
361  * @param context the plug-in context
362  * @param plugin the plug-in being resolved
363  * @param import the plug-in import to resolve
364  * @param ipptr filled with pointer to the resolved plug-in or NULL
365  * @return CP_OK on success or error code on failure
366  */
367 static int resolve_plugin_import(cp_context_t *context, cp_plugin_t *plugin, cp_plugin_import_t *import, cp_plugin_t **ipptr) {
368         cp_plugin_t *ip = NULL;
369         hnode_t *node;
370
371         // Lookup the plug-in 
372         node = hash_lookup(context->env->plugins, import->plugin_id);
373         if (node != NULL) {
374                 ip = hnode_get(node);
375         }
376                         
377         // Check plug-in version
378         if (ip != NULL
379                 && import->version != NULL
380                 && (ip->plugin->version == NULL
381                         || (ip->plugin->abi_bw_compatibility == NULL
382                                 && cpi_vercmp(import->version, ip->plugin->version) != 0)
383                         || (ip->plugin->abi_bw_compatibility != NULL
384                                 && (cpi_vercmp(import->version, ip->plugin->version) > 0
385                                         || cpi_vercmp(import->version, ip->plugin->abi_bw_compatibility) < 0)))) {
386                 cpi_errorf(context,
387                         N_("Plug-in %s could not be resolved due to version incompatibility with plug-in %s."),
388                         plugin->plugin->identifier,
389                         import->plugin_id);
390                 *ipptr = NULL;
391                 return CP_ERR_DEPENDENCY;
392         }
393         
394         // Check if missing mandatory plug-in
395         if (ip == NULL && !import->optional) {
396                 cpi_errorf(context,
397                         N_("Plug-in %s could not be resolved because it depends on plug-in %s which is not installed."),
398                         plugin->plugin->identifier,
399                         import->plugin_id);
400                 *ipptr = NULL;
401                 return CP_ERR_DEPENDENCY;
402         }
403
404         // Return imported plug-in
405         *ipptr = ip;
406         return CP_OK;
407 }
408
409 /**
410  * Resolves the specified plug-in and its dependencies while leaving plug-ins
411  * with circular dependencies in a preliminarily resolved state.
412  * 
413  * @param context the plug-in context
414  * @param plugin the plug-in
415  * @return CP_OK (zero) or CP_OK_PRELIMINARY or an error code
416  */
417 static int resolve_plugin_prel_rec(cp_context_t *context, cp_plugin_t *plugin) {
418         cp_status_t status = CP_OK;
419         int error_reported = 0;
420         lnode_t *node = NULL;
421         int i;
422
423         // Check if already resolved
424         if (plugin->state >= CP_PLUGIN_RESOLVED) {
425                 return CP_OK;
426         }
427         
428         // Check for dependency loops
429         if (plugin->processed) {
430                 return CP_OK_PRELIMINARY;
431         }
432         plugin->processed = 1;
433
434         do {
435
436                 // Recursively resolve the imported plug-ins
437                 assert(plugin->imported == NULL);
438                 if ((plugin->imported = list_create(LISTCOUNT_T_MAX)) == NULL) {
439                         status = CP_ERR_RESOURCE;
440                         break;
441                 }
442                 for (i = 0; i < plugin->plugin->num_imports; i++) {
443                         cp_plugin_t *ip;
444                         int s;
445                                 
446                         if ((node = lnode_create(NULL)) == NULL) {
447                                 status = CP_ERR_RESOURCE;
448                                 break;
449                         }
450                         if ((s = resolve_plugin_import(context, plugin, plugin->plugin->imports + i, &ip)) != CP_OK) {
451                                 error_reported = 1;
452                                 status = s;
453                                 break;
454                         }
455                         if (ip != NULL) {
456                                 lnode_put(node, ip);
457                                 list_append(plugin->imported, node);
458                                 node = NULL;
459                                 if (!cpi_ptrset_add(ip->importing, plugin)) {
460                                         status = CP_ERR_RESOURCE;
461                                         break;
462                                 } else if ((s = resolve_plugin_prel_rec(context, ip)) != CP_OK && s != CP_OK_PRELIMINARY) {
463                                         cpi_errorf(context, N_("Plug-in %s could not be resolved because it depends on plug-in %s which could not be resolved."), plugin->plugin->identifier, ip->plugin->identifier);
464                                         error_reported = 1;
465                                         status = s;
466                                         break;
467                                 }
468                         } else {
469                                 lnode_destroy(node);
470                                 node = NULL;
471                         }
472                 }
473                 if (status != CP_OK) {
474                         break;
475                 }
476                 
477                 // Resolve this plug-in
478                 assert(plugin->state == CP_PLUGIN_INSTALLED);
479                 if ((i = resolve_plugin_runtime(context, plugin)) != CP_OK) {
480                         status = i;
481                         error_reported = 1;
482                         break;
483                 }
484                 
485                 // Notify event listeners and update state if completely resolved
486                 if (status == CP_OK) {
487                         cpi_plugin_event_t event;
488                         
489                         plugin->processed = 0;
490                         event.plugin_id = plugin->plugin->identifier;
491                         event.old_state = plugin->state;
492                         event.new_state = plugin->state = CP_PLUGIN_RESOLVED;
493                         cpi_deliver_event(context, &event);
494                 }
495
496         } while (0);
497
498         // Clean up
499         if (node != NULL) {
500                 lnode_destroy(node);
501         }
502
503         // Handle errors
504         if (status == CP_ERR_RESOURCE && !error_reported) {
505                 cpi_errorf(context, N_("Plug-in %s could not be resolved because of insufficient memory."), plugin->plugin->identifier);
506         }
507         
508         return status;
509 }
510
511 /**
512  * Recursively commits the resolving process for the specified plug-in and
513  * its dependencies.
514  * 
515  * @param context the plug-in context
516  * @param plugin the plug-in
517  */
518 static void resolve_plugin_commit_rec(cp_context_t *context, cp_plugin_t *plugin) {
519         
520         // Check if already committed
521         if (!plugin->processed) {
522                 return;
523         }
524         plugin->processed = 0;
525         
526         // Commit if only preliminarily resolved
527         if (plugin->state < CP_PLUGIN_RESOLVED) {
528                         cpi_plugin_event_t event;
529                         lnode_t *node;
530
531                         // Recursively commit dependencies
532                         node = list_first(plugin->imported);
533                         while (node != NULL) {
534                                 resolve_plugin_commit_rec(context, (cp_plugin_t *) lnode_get(node));
535                                 node = list_next(plugin->imported, node);
536                         }
537                         
538                         // Notify event listeners and update state
539                         event.plugin_id = plugin->plugin->identifier;
540                         event.old_state = plugin->state;
541                         event.new_state = plugin->state = CP_PLUGIN_RESOLVED;
542                         cpi_deliver_event(context, &event);             
543         }
544 }
545
546 /**
547  * Recursively cleans up the specified plug-in and its dependencies after
548  * a failed resolving attempt.
549  * 
550  * @param plugin the plug-in
551  */
552 static void resolve_plugin_failed_rec(cp_plugin_t *plugin) {
553         
554         // Check if already cleaned up
555         if (!plugin->processed) {
556                 return;
557         }
558         plugin->processed = 0;
559         
560         // Clean up if only preliminarily resolved
561         if (plugin->state < CP_PLUGIN_RESOLVED) {
562                 lnode_t *node;
563
564                 // Recursively clean up depedencies
565                 while ((node = list_first(plugin->imported)) != NULL) {
566                         cp_plugin_t *ip = lnode_get(node);
567                         
568                         resolve_plugin_failed_rec(ip);
569                         cpi_ptrset_remove(ip->importing, plugin);
570                         list_delete(plugin->imported, node);
571                         lnode_destroy(node);
572                 }
573                 list_destroy(plugin->imported);
574                 plugin->imported = NULL;
575         }
576 }
577
578 /**
579  * Resolves the specified plug-in and its dependencies.
580  * 
581  * @param context the plug-in context
582  * @param plugin the plug-in to be resolved
583  * @return CP_OK (zero) on success or an error code on failure
584  */
585 static int resolve_plugin(cp_context_t *context, cp_plugin_t *plugin) {
586         cp_status_t status;
587         
588         if ((status = resolve_plugin_prel_rec(context, plugin)) == CP_OK || status == CP_OK_PRELIMINARY) {
589                 status = CP_OK;
590                 resolve_plugin_commit_rec(context, plugin);
591         } else {
592                 resolve_plugin_failed_rec(plugin);
593         }
594         assert_processed_zero(context);
595         return status;
596 }
597
598 /**
599  * Starts the plug-in runtime of the specified plug-in. This function does
600  * not consider dependencies and assumes that the plug-in is resolved but
601  * not yet started.
602  * 
603  * @param context the plug-in context
604  * @param plugin the plug-in
605  * @return CP_OK (zero) on success or an error code on failure
606  */
607 static int start_plugin_runtime(cp_context_t *context, cp_plugin_t *plugin) {
608         cp_status_t status = CP_OK;
609         cpi_plugin_event_t event;
610         lnode_t *node = NULL;
611
612         event.plugin_id = plugin->plugin->identifier;
613         do {
614
615                 // Allocate space for the list node 
616                 node = lnode_create(plugin);
617                 if (node == NULL) {
618                         status = CP_ERR_RESOURCE;
619                         break;
620                 }
621                 
622                 // Set up plug-in instance
623                 if (plugin->runtime_funcs != NULL) {
624
625                         // Create plug-in instance if necessary
626                         if (plugin->context == NULL) {
627                                 if ((plugin->context = cpi_new_context(plugin, context->env, &status)) == NULL) {
628                                         break;
629                                 }
630                                 context->env->in_create_func_invocation++;
631                                 plugin->plugin_data = plugin->runtime_funcs->create(plugin->context);
632                                 context->env->in_create_func_invocation--;
633                                 if (plugin->plugin_data == NULL) {
634                                         status = CP_ERR_RUNTIME;
635                                         break;
636                                 }
637                         }
638                         
639                         // Start plug-in
640                         if (plugin->runtime_funcs->start != NULL) {
641                                 int s;
642                         
643                                 // About to start the plug-in 
644                                 event.old_state = plugin->state;
645                                 event.new_state = plugin->state = CP_PLUGIN_STARTING;
646                                 cpi_deliver_event(context, &event);
647                 
648                                 // Start the plug-in
649                                 context->env->in_start_func_invocation++;
650                                 s = plugin->runtime_funcs->start(plugin->plugin_data);
651                                 context->env->in_start_func_invocation--;
652
653                                 if (s != CP_OK) {
654                         
655                                         // Roll back plug-in state 
656                                         if (plugin->runtime_funcs->stop != NULL) {
657
658                                                 // Update state                                 
659                                                 event.old_state = plugin->state;
660                                                 event.new_state = plugin->state = CP_PLUGIN_STOPPING;
661                                                 cpi_deliver_event(context, &event);
662                                         
663                                                 // Call stop function
664                                                 context->env->in_stop_func_invocation++;
665                                                 plugin->runtime_funcs->stop(plugin->plugin_data);
666                                                 context->env->in_stop_func_invocation--;
667                                         }
668                                 
669                                         // Destroy plug-in object
670                                         context->env->in_destroy_func_invocation++;
671                                         plugin->runtime_funcs->destroy(plugin->plugin_data);
672                                         context->env->in_destroy_func_invocation--;
673                         
674                                         status = CP_ERR_RUNTIME;
675                                         break;
676                                 }
677                         }
678                 }
679                 
680                 // Plug-in active 
681                 list_append(context->env->started_plugins, node);
682                 event.old_state = plugin->state;
683                 event.new_state = plugin->state = CP_PLUGIN_ACTIVE;
684                 cpi_deliver_event(context, &event);
685                 
686         } while (0);
687
688         // Release resources and roll back plug-in state on failure
689         if (status != CP_OK) {
690                 if (node != NULL) {
691                         lnode_destroy(node);
692                 }
693                 if (plugin->context != NULL) {
694                         cpi_free_context(plugin->context);
695                         plugin->context = NULL;
696                 }
697                 if (plugin->state != CP_PLUGIN_RESOLVED) {
698                         event.old_state = plugin->state;
699                         event.new_state = plugin->state = CP_PLUGIN_RESOLVED;
700                         cpi_deliver_event(context, &event);
701                 }
702                 plugin->plugin_data = NULL;
703         }
704
705         // Report error on failure
706         switch (status) {
707                 case CP_ERR_RESOURCE:
708                         cpi_errorf(context,
709                                 N_("Plug-in %s could not be started due to insufficient memory."),
710                                 plugin->plugin->identifier);
711                         break;
712                 case CP_ERR_RUNTIME:
713                         cpi_errorf(context,
714                                 N_("Plug-in %s failed to start due to plug-in runtime error."),
715                                 plugin->plugin->identifier);
716                         break;
717                 default:
718                         break;
719         }       
720         
721         return status;
722 }
723
724 static void warn_dependency_loop(cp_context_t *context, cp_plugin_t *plugin, list_t *importing, int dynamic) {
725         char *msgbase;
726         char *msg;
727         int msgsize;
728         lnode_t *node;
729         
730         // Take the message base
731         if (dynamic) {
732                 msgbase = N_("Detected a runtime plug-in dependency loop: %s");
733         } else {
734                 msgbase = N_("Detected a static plug-in dependency loop: %s");
735         }
736         
737         // Calculate the required message space
738         msgsize = 0;
739         msgsize += strlen(plugin->plugin->identifier);
740         msgsize += 2;
741         node = list_last(importing);
742         while (node != NULL) {
743                 cp_plugin_t *p = lnode_get(node);
744                 if (p == plugin) {
745                         break;
746                 }
747                 msgsize += strlen(p->plugin->identifier);
748                 msgsize += 2;
749                 node = list_prev(importing, node);
750         }
751         msg = malloc(sizeof(char) * msgsize);
752         if (msg != NULL) {
753                 strcpy(msg, plugin->plugin->identifier);
754                 node = list_last(importing);
755                 while (node != NULL) {
756                         cp_plugin_t *p = lnode_get(node);
757                         if (p == plugin) {
758                                 break;
759                         }
760                         strcat(msg, ", ");
761                         strcat(msg, p->plugin->identifier);
762                         node = list_prev(importing, node);
763                 }
764                 strcat(msg, ".");
765                 cpi_infof(context, msgbase, msg);
766                 free(msg);
767         } else {
768                 cpi_infof(context, msgbase, plugin->plugin->identifier);
769         }
770 }
771
772 /**
773  * Starts the specified plug-in and its dependencies.
774  * 
775  * @param context the plug-in context
776  * @param plugin the plug-in
777  * @param importing stack of importing plug-ins
778  * @return CP_OK (zero) on success or an error code on failure
779  */
780 static int start_plugin_rec(cp_context_t *context, cp_plugin_t *plugin, list_t *importing) {
781         cp_status_t status = CP_OK;
782         lnode_t *node;
783         
784         // Check if already started or starting
785         if (plugin->state == CP_PLUGIN_ACTIVE) {
786                 return CP_OK;
787         } else if (plugin->state == CP_PLUGIN_STARTING) {
788                 warn_dependency_loop(context, plugin, importing, 1);
789                 return CP_OK;
790         }
791         assert(plugin->state == CP_PLUGIN_RESOLVED);
792         
793         // Check for dependency loops
794         if (cpi_ptrset_contains(importing, plugin)) {
795                 warn_dependency_loop(context, plugin, importing, 0);
796                 return CP_OK;
797         }
798         if (!cpi_ptrset_add(importing, plugin)) {
799                 cpi_errorf(context,
800                         N_("Plug-in %s could not be started due to insufficient memory."),
801                         plugin->plugin->identifier);
802                 return CP_ERR_RESOURCE;
803         }
804
805         // Start up dependencies
806         node = list_first(plugin->imported);
807         while (node != NULL) {
808                 cp_plugin_t *ip = lnode_get(node);
809                 
810                 if ((status = start_plugin_rec(context, ip, importing)) != CP_OK) {
811                         break;
812                 }
813                 node = list_next(plugin->imported, node);
814         }
815         cpi_ptrset_remove(importing, plugin);
816         
817         // Start up this plug-in
818         if (status == CP_OK) {
819                 status = start_plugin_runtime(context, plugin);
820         }
821
822         return status;
823 }
824
825 CP_HIDDEN cp_status_t cpi_start_plugin(cp_context_t *context, cp_plugin_t *plugin) {
826         cp_status_t status;
827         
828         if ((status = resolve_plugin(context, plugin)) == CP_OK) {
829                 list_t *importing = list_create(LISTCOUNT_T_MAX);
830                 if (importing != NULL) {
831                         status = start_plugin_rec(context, plugin, importing);
832                         assert(list_isempty(importing));
833                         list_destroy(importing);
834                 } else {
835                         cpi_errorf(context,
836                                 N_("Plug-in %s could not be started due to insufficient memory."),
837                                 plugin->plugin->identifier);
838                         status = CP_ERR_RESOURCE;
839                 }
840         }
841         return status;
842 }
843
844 CP_C_API cp_status_t cp_start_plugin(cp_context_t *context, const char *id) {
845         hnode_t *node;
846         cp_status_t status = CP_OK;
847
848         CHECK_NOT_NULL(context);
849         CHECK_NOT_NULL(id);
850
851         // Look up and start the plug-in 
852         cpi_lock_context(context);
853         cpi_check_invocation(context, CPI_CF_ANY, __func__);
854         node = hash_lookup(context->env->plugins, id);
855         if (node != NULL) {
856                 status = cpi_start_plugin(context, hnode_get(node));
857         } else {
858                 cpi_warnf(context, N_("Unknown plug-in %s could not be started."), id);
859                 status = CP_ERR_UNKNOWN;
860         }
861         cpi_unlock_context(context);
862
863         return status;
864 }
865
866 /**
867  * Stops the plug-in runtime of the specified plug-in. This function does
868  * not consider dependencies and assumes that the plug-in is active.
869  * 
870  * @param context the plug-in context
871  * @param plugin the plug-in
872  */
873 static void stop_plugin_runtime(cp_context_t *context, cp_plugin_t *plugin) {
874         cpi_plugin_event_t event;
875         
876         // Destroy plug-in instance
877         event.plugin_id = plugin->plugin->identifier;
878         if (plugin->context != NULL) {
879         
880                 // Wait until possible run functions have stopped
881                 cpi_stop_plugin_run(plugin);
882
883                 // Stop the plug-in
884                 if (plugin->runtime_funcs->stop != NULL) {
885
886                         // About to stop the plug-in 
887                         event.old_state = plugin->state;
888                         event.new_state = plugin->state = CP_PLUGIN_STOPPING;
889                         cpi_deliver_event(context, &event);
890         
891                         // Invoke stop function 
892                         context->env->in_stop_func_invocation++;
893                         plugin->runtime_funcs->stop(plugin->plugin_data);
894                         context->env->in_stop_func_invocation--;
895
896                 }
897
898                 // Unregister all logger functions
899                 cpi_unregister_loggers(plugin->context->env->loggers, plugin);
900
901                 // Unregister all plug-in listeners
902                 cpi_unregister_plisteners(plugin->context->env->plugin_listeners, plugin);      
903
904                 // Release resolved symbols
905                 if (plugin->context->resolved_symbols != NULL) {
906                         while (!hash_isempty(plugin->context->resolved_symbols)) {
907                                 hscan_t scan;
908                                 hnode_t *node;
909                                 const void *ptr;
910                         
911                                 hash_scan_begin(&scan, plugin->context->resolved_symbols);
912                                 node = hash_scan_next(&scan);
913                                 ptr = hnode_getkey(node);
914                                 cp_release_symbol(context, ptr);
915                         }
916                         assert(hash_isempty(plugin->context->resolved_symbols));
917                 }
918                 if (plugin->context->symbol_providers != NULL) {
919                         assert(hash_isempty(plugin->context->symbol_providers));
920                 }
921
922                 // Release defined symbols
923                 if (plugin->defined_symbols != NULL) {
924                         hscan_t scan;
925                         hnode_t *node;
926                         
927                         hash_scan_begin(&scan, plugin->defined_symbols);
928                         while ((node = hash_scan_next(&scan)) != NULL) {
929                                 char *n = (char *) hnode_getkey(node);
930                                 hash_scan_delfree(plugin->defined_symbols, node);
931                                 free(n);
932                         }
933                         hash_destroy(plugin->defined_symbols);
934                         plugin->defined_symbols = NULL;
935                 }
936                 
937         }
938         
939         // Plug-in stopped 
940         cpi_ptrset_remove(context->env->started_plugins, plugin);
941         event.old_state = plugin->state;
942         event.new_state = plugin->state = CP_PLUGIN_RESOLVED;
943         cpi_deliver_event(context, &event);
944 }
945
946 /**
947  * Stops the plug-in and all plug-ins depending on it.
948  * 
949  * @param context the plug-in context
950  * @param plugin the plug-in
951  */
952 static void stop_plugin_rec(cp_context_t *context, cp_plugin_t *plugin) {
953         lnode_t *node;
954         
955         // Check if already stopped
956         if (plugin->state < CP_PLUGIN_ACTIVE) {
957                 return;
958         }
959         
960         // Check for dependency loops
961         if (plugin->processed) {
962                 return;
963         }
964         plugin->processed = 1;
965         
966         // Stop the depending plug-ins
967         node = list_first(plugin->importing);
968         while (node != NULL) {
969                 stop_plugin_rec(context, lnode_get(node));
970                 node = list_next(plugin->importing, node);
971         }
972
973         // Stop this plug-in
974         assert(plugin->state == CP_PLUGIN_ACTIVE);
975         stop_plugin_runtime(context, plugin);
976         assert(plugin->state < CP_PLUGIN_ACTIVE);
977         
978         // Clear processed flag
979         plugin->processed = 0;
980 }
981
982 static void stop_plugin(cp_context_t *context, cp_plugin_t *plugin) {
983         stop_plugin_rec(context, plugin);
984         assert_processed_zero(context);
985 }
986
987 CP_C_API cp_status_t cp_stop_plugin(cp_context_t *context, const char *id) {
988         hnode_t *node;
989         cp_plugin_t *plugin;
990         cp_status_t status = CP_OK;
991
992         CHECK_NOT_NULL(context);
993         CHECK_NOT_NULL(id);
994
995         // Look up and stop the plug-in 
996         cpi_lock_context(context);
997         cpi_check_invocation(context, CPI_CF_ANY, __func__);
998         node = hash_lookup(context->env->plugins, id);
999         if (node != NULL) {
1000                 plugin = hnode_get(node);
1001                 stop_plugin(context, plugin);
1002         } else {
1003                 cpi_warnf(context, N_("Unknown plug-in %s could not be stopped."), id);
1004                 status = CP_ERR_UNKNOWN;
1005         }
1006         cpi_unlock_context(context);
1007
1008         return status;
1009 }
1010
1011 CP_C_API void cp_stop_plugins(cp_context_t *context) {
1012         lnode_t *node;
1013         
1014         CHECK_NOT_NULL(context);
1015         
1016         // Stop the active plug-ins in the reverse order they were started 
1017         cpi_lock_context(context);
1018         cpi_check_invocation(context, CPI_CF_ANY, __func__);
1019         while ((node = list_last(context->env->started_plugins)) != NULL) {
1020                 stop_plugin(context, lnode_get(node));
1021         }
1022         cpi_unlock_context(context);
1023 }
1024
1025 static void unresolve_plugin_rec(cp_context_t *context, cp_plugin_t *plugin) {
1026         lnode_t *node;
1027         cpi_plugin_event_t event;
1028         
1029         // Check if already unresolved
1030         if (plugin->state < CP_PLUGIN_RESOLVED) {
1031                 return;
1032         }
1033         assert(plugin->state == CP_PLUGIN_RESOLVED);
1034         
1035         // Clear the list of imported plug-ins (also breaks dependency loops)
1036         while ((node = list_first(plugin->imported)) != NULL) {
1037                 cp_plugin_t *ip = lnode_get(node);
1038                 
1039                 cpi_ptrset_remove(ip->importing, plugin);
1040                 list_delete(plugin->imported, node);
1041                 lnode_destroy(node);
1042         }
1043         assert(list_isempty(plugin->imported));
1044         list_destroy(plugin->imported);
1045         plugin->imported = NULL;
1046
1047         // Unresolve depending plugins
1048         while ((node = list_first(plugin->importing)) != NULL) {
1049                 unresolve_plugin_rec(context, lnode_get(node));
1050         }
1051         
1052         // Unresolve this plug-in
1053         unresolve_plugin_runtime(plugin);
1054         event.plugin_id = plugin->plugin->identifier;
1055         event.old_state = plugin->state;
1056         event.new_state = plugin->state = CP_PLUGIN_INSTALLED;
1057         cpi_deliver_event(context, &event);
1058 }
1059
1060 /**
1061  * Unresolves a plug-in.
1062  * 
1063  * @param context the plug-in context
1064  * @param plug-in the plug-in to be unresolved
1065  */
1066 static void unresolve_plugin(cp_context_t *context, cp_plugin_t *plugin) {
1067         stop_plugin(context, plugin);
1068         unresolve_plugin_rec(context, plugin);
1069 }
1070
1071 static void free_plugin_import_content(cp_plugin_import_t *import) {
1072         assert(import != NULL);
1073         free(import->plugin_id);
1074         free(import->version);
1075 }
1076
1077 static void free_ext_point_content(cp_ext_point_t *ext_point) {
1078         free(ext_point->name);
1079         free(ext_point->local_id);
1080         free(ext_point->identifier);
1081         free(ext_point->schema_path);
1082 }
1083
1084 static void free_extension_content(cp_extension_t *extension) {
1085         free(extension->name);
1086         free(extension->local_id);
1087         free(extension->identifier);
1088         free(extension->ext_point_id);
1089 }
1090
1091 static void free_cfg_element_content(cp_cfg_element_t *ce) {
1092         int i;
1093
1094         assert(ce != NULL);
1095         free(ce->name);
1096         if (ce->atts != NULL) {
1097                 free(ce->atts[0]);
1098                 free(ce->atts);
1099         }
1100         free(ce->value);
1101         for (i = 0; i < ce->num_children; i++) {
1102                 free_cfg_element_content(ce->children + i);
1103         }
1104         free(ce->children);
1105 }
1106
1107 CP_HIDDEN void cpi_free_plugin(cp_plugin_info_t *plugin) {
1108         int i;
1109         
1110         assert(plugin != NULL);
1111         free(plugin->name);
1112         free(plugin->identifier);
1113         free(plugin->version);
1114         free(plugin->provider_name);
1115         free(plugin->plugin_path);
1116         free(plugin->abi_bw_compatibility);
1117         free(plugin->api_bw_compatibility);
1118         free(plugin->req_cpluff_version);
1119         for (i = 0; i < plugin->num_imports; i++) {
1120                 free_plugin_import_content(plugin->imports + i);
1121         }
1122         free(plugin->imports);
1123         free(plugin->runtime_lib_name);
1124         free(plugin->runtime_funcs_symbol);
1125         for (i = 0; i < plugin->num_ext_points; i++) {
1126                 free_ext_point_content(plugin->ext_points + i);
1127         }
1128         free(plugin->ext_points);
1129         for (i = 0; i < plugin->num_extensions; i++) {
1130                 free_extension_content(plugin->extensions + i);
1131                 if (plugin->extensions[i].configuration != NULL) {
1132                         free_cfg_element_content(plugin->extensions[i].configuration);
1133                         free(plugin->extensions[i].configuration);
1134                 }
1135         }
1136         free(plugin->extensions);
1137         free(plugin);
1138 }
1139
1140 /**
1141  * Frees any memory allocated for a registered plug-in.
1142  * 
1143  * @param context the plug-in context
1144  * @param plugin the plug-in to be freed
1145  */
1146 static void free_registered_plugin(cp_context_t *context, cp_plugin_t *plugin) {
1147         assert(context != NULL);
1148         assert(plugin != NULL);
1149
1150         // Release plug-in information
1151         cpi_release_info(context, plugin->plugin);
1152
1153         // Release data structures 
1154         if (plugin->importing != NULL) {
1155                 assert(list_isempty(plugin->importing));
1156                 list_destroy(plugin->importing);
1157         }
1158         assert(plugin->imported == NULL);
1159
1160         free(plugin);
1161 }
1162
1163 /**
1164  * Uninstalls a plug-in associated with the specified hash node.
1165  * 
1166  * @param context the plug-in context
1167  * @param node the hash node of the plug-in to be uninstalled
1168  */
1169 static void uninstall_plugin(cp_context_t *context, hnode_t *node) {
1170         cp_plugin_t *plugin;
1171         cpi_plugin_event_t event;
1172         
1173         // Check if already uninstalled 
1174         plugin = (cp_plugin_t *) hnode_get(node);
1175         if (plugin->state <= CP_PLUGIN_UNINSTALLED) {
1176                 // TODO: Is this possible state?
1177                 return;
1178         }
1179         
1180         // Make sure the plug-in is not in resolved state 
1181         unresolve_plugin(context, plugin);
1182         assert(plugin->state == CP_PLUGIN_INSTALLED);
1183
1184         // Plug-in uninstalled 
1185         event.plugin_id = plugin->plugin->identifier;
1186         event.old_state = plugin->state;
1187         event.new_state = plugin->state = CP_PLUGIN_UNINSTALLED;
1188         cpi_deliver_event(context, &event);
1189         
1190         // Unregister extension objects
1191         unregister_extensions(context, plugin->plugin);
1192
1193         // Unregister the plug-in 
1194         hash_delete_free(context->env->plugins, node);
1195
1196         // Free the plug-in data structures
1197         free_registered_plugin(context, plugin);
1198 }
1199
1200 CP_C_API cp_status_t cp_uninstall_plugin(cp_context_t *context, const char *id) {
1201         hnode_t *node;
1202         cp_status_t status = CP_OK;
1203
1204         CHECK_NOT_NULL(context);
1205         CHECK_NOT_NULL(id);
1206
1207         // Look up and unload the plug-in 
1208         cpi_lock_context(context);
1209         cpi_check_invocation(context, CPI_CF_ANY, __func__);
1210         node = hash_lookup(context->env->plugins, id);
1211         if (node != NULL) {
1212                 uninstall_plugin(context, node);
1213         } else {
1214                 cpi_warnf(context, N_("Unknown plug-in %s could not be uninstalled."), id);
1215                 status = CP_ERR_UNKNOWN;
1216         }
1217         cpi_unlock_context(context);
1218
1219         return status;
1220 }
1221
1222 CP_C_API void cp_uninstall_plugins(cp_context_t *context) {
1223         hscan_t scan;
1224         hnode_t *node;
1225         
1226         CHECK_NOT_NULL(context);
1227         
1228         cpi_lock_context(context);
1229         cpi_check_invocation(context, CPI_CF_ANY, __func__);
1230         cp_stop_plugins(context);
1231         while (1) {
1232                 hash_scan_begin(&scan, context->env->plugins);
1233                 if ((node = hash_scan_next(&scan)) != NULL) {
1234                         uninstall_plugin(context, node);
1235                 } else {
1236                         break;
1237                 }
1238         }
1239         cpi_unlock_context(context);
1240 }