iotivity 0.9.0
[platform/upstream/iotivity.git] / service / protocol-plugin / lib / cpluff / libcpluff / pinfo.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  * Plug-in information functions
26  */
27
28 #include <string.h>
29 #include <stdlib.h>
30 #include <assert.h>
31 #include "../kazlib/hash.h"
32 #include "cpluff.h"
33 #include "defines.h"
34 #include "util.h"
35 #include "internal.h"
36
37
38 /* ------------------------------------------------------------------------
39  * Data types
40  * ----------------------------------------------------------------------*/
41
42 /// Registration of a dynamically allocated information object
43 typedef struct info_resource_t {
44
45         /// Pointer to the resource
46         void *resource; 
47
48         /// Usage count for the resource
49         int usage_count;
50         
51         /// Deallocation function
52         cpi_dealloc_func_t dealloc_func;
53         
54 } info_resource_t;
55
56 /// A plug-in listener registration
57 typedef struct el_holder_t {
58         
59         /// The plug-in listener
60         cp_plugin_listener_func_t plugin_listener;
61         
62         /// The registering plug-in or NULL for the client program
63         cp_plugin_t *plugin;
64         
65         /// Associated user data
66         void *user_data;
67         
68 } el_holder_t;
69
70
71
72 /* ------------------------------------------------------------------------
73  * Function definitions
74  * ----------------------------------------------------------------------*/
75
76 // General information object management
77
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;
81
82         assert(context != NULL);
83         assert(res != NULL);
84         assert(df != NULL);
85         assert(cpi_is_context_locked(context));
86         do {
87                 if ((ir = malloc(sizeof(info_resource_t))) == NULL) {
88                         status = CP_ERR_RESOURCE;
89                         break;
90                 }
91                 ir->resource = res;
92                 ir->usage_count = 1;
93                 ir->dealloc_func = df;
94                 if (!hash_alloc_insert(context->env->infos, res, ir)) {
95                         status = CP_ERR_RESOURCE;
96                         break;
97                 }
98         } while (0);
99         
100         // Report success
101         if (status == CP_OK) {
102                 cpi_debugf(context, _("An information object at address %p was registered."), res);
103         }               
104         
105         // Release resources on failure
106         if (status != CP_OK) {
107                 if (ir != NULL) {
108                         free(ir);
109                 }
110         }
111         
112         return status;
113 }
114
115 CP_HIDDEN void cpi_use_info(cp_context_t *context, void *res) {
116         hnode_t *node;
117         
118         assert(context != NULL);
119         assert(res != 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);
123                 ir->usage_count++;
124                 cpi_debugf(context, _("Reference count of the information object at address  %p increased to %d."), res, ir->usage_count);
125         } else {
126                 cpi_fatalf(_("Reference count of an unknown information object at address %p could not be increased."), res);
127         }
128 }
129
130 CP_HIDDEN void cpi_release_info(cp_context_t *context, void *info) {
131         hnode_t *node;
132         
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);
143                         free(ir);
144                 } else {
145                         cpi_debugf(context, _("Reference count of the information object at address %p decreased to %d."), info, ir->usage_count);
146                 }
147         } else {
148                 cpi_fatalf(_("Could not release an unknown information object at address %p."), info);
149         }
150 }
151
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);
159 }
160
161 CP_HIDDEN void cpi_release_infos(cp_context_t *context) {
162         hscan_t scan;
163         hnode_t *node;
164                 
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);
172                 free(ir);
173         }
174 }
175
176
177 // Information acquiring functions
178
179 CP_C_API cp_plugin_info_t * cp_get_plugin_info(cp_context_t *context, const char *id, cp_status_t *error) {
180         hnode_t *node;
181         cp_plugin_info_t *plugin = NULL;
182         cp_status_t status = CP_OK;
183
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."));
187         }
188
189         // Look up the plug-in and return information 
190         cpi_lock_context(context);
191         cpi_check_invocation(context, CPI_CF_LOGGER, __func__);
192         do {
193                 
194                 // Lookup plug-in information
195                 if (id != NULL) {
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;
199                                 break;
200                         }
201                         plugin = ((cp_plugin_t *) hnode_get(node))->plugin;
202                 } else {
203                         plugin = context->plugin->plugin;
204                         assert(plugin != NULL);
205                 }
206                 cpi_use_info(context, plugin);
207         } while (0);
208         cpi_unlock_context(context);
209
210         if (error != NULL) {
211                 *error = status;
212         }
213         return plugin;
214 }
215
216 static void dealloc_plugins_info(cp_context_t *context, cp_plugin_info_t **plugins) {
217         int i;
218         
219         assert(context != NULL);
220         assert(plugins != NULL);
221         for (i = 0; plugins[i] != NULL; i++) {
222                 cpi_release_info(context, plugins[i]);
223         }
224         free(plugins);
225 }
226
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;
229         int i, n;
230         cp_status_t status = CP_OK;
231         
232         CHECK_NOT_NULL(context);
233         
234         cpi_lock_context(context);
235         cpi_check_invocation(context, CPI_CF_LOGGER, __func__);
236         do {
237                 hscan_t scan;
238                 hnode_t *node;
239                 
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;
244                         break;
245                 }
246                 
247                 // Get plug-in information structures 
248                 hash_scan_begin(&scan, context->env->plugins);
249                 i = 0;
250                 while ((node = hash_scan_next(&scan)) != NULL) {
251                         cp_plugin_t *rp = hnode_get(node);
252                         
253                         assert(i < n);
254                         cpi_use_info(context, rp->plugin);
255                         plugins[i] = rp->plugin;
256                         i++;
257                 }
258                 plugins[i] = NULL;
259                 
260                 // Register the array
261                 status = cpi_register_info(context, plugins, (void (*)(cp_context_t *, void *)) dealloc_plugins_info);
262                 
263         } while (0);
264
265         // Report error
266         if (status != CP_OK) {
267                 cpi_error(context, N_("Plug-in information could not be returned due to insufficient memory."));
268         }
269         cpi_unlock_context(context);
270
271         // Release resources on error 
272         if (status != CP_OK) {
273                 if (plugins != NULL) {
274                         dealloc_plugins_info(context, plugins);
275                         plugins = NULL;
276                 }
277         }
278         
279         assert(status != CP_OK || n == 0 || plugins[n - 1] != NULL);
280         if (error != NULL) {
281                 *error = status;
282         }
283         if (num != NULL && status == CP_OK) {
284                 *num = n;
285         }
286         return plugins;
287 }
288
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;
291         hnode_t *hnode;
292         
293         CHECK_NOT_NULL(context);
294         CHECK_NOT_NULL(id);
295         
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);
301                 state = rp->state;
302         }
303         cpi_unlock_context(context);
304         return state;
305 }
306
307 static void dealloc_ext_points_info(cp_context_t *context, cp_ext_point_t **ext_points) {
308         int i;
309         
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);
314         }
315         free(ext_points);
316 }
317
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;
320         int i, n;
321         cp_status_t status = CP_OK;
322         
323         CHECK_NOT_NULL(context);
324         
325         cpi_lock_context(context);
326         cpi_check_invocation(context, CPI_CF_LOGGER, __func__);
327         do {
328                 hscan_t scan;
329                 hnode_t *node;
330                 
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;
335                         break;
336                 }
337                 
338                 // Get extension point information structures 
339                 hash_scan_begin(&scan, context->env->ext_points);
340                 i = 0;
341                 while ((node = hash_scan_next(&scan)) != NULL) {
342                         cp_ext_point_t *ep = hnode_get(node);
343                         
344                         assert(i < n);
345                         cpi_use_info(context, ep->plugin);
346                         ext_points[i] = ep;
347                         i++;
348                 }
349                 ext_points[i] = NULL;
350                 
351                 // Register the array
352                 status = cpi_register_info(context, ext_points, (void (*)(cp_context_t *, void *)) dealloc_ext_points_info);
353                 
354         } while (0);
355         
356         // Report error
357         if (status != CP_OK) {
358                 cpi_error(context, N_("Extension point information could not be returned due to insufficient memory."));
359         }
360         cpi_unlock_context(context);
361         
362         // Release resources on error 
363         if (status != CP_OK) {
364                 if (ext_points != NULL) {
365                         dealloc_ext_points_info(context, ext_points);
366                         ext_points = NULL;
367                 }
368         }
369         
370         assert(status != CP_OK || n == 0 || ext_points[n - 1] != NULL);
371         if (error != NULL) {
372                 *error = status;
373         }
374         if (num != NULL && status == CP_OK) {
375                 *num = n;
376         }
377         return ext_points;
378 }
379
380 static void dealloc_extensions_info(cp_context_t *context, cp_extension_t **extensions) {
381         int i;
382         
383         assert(context != NULL);
384         assert(extensions != NULL);
385         for (i = 0; extensions[i] != NULL; i++) {
386                 cpi_release_info(context, extensions[i]->plugin);
387         }
388         free(extensions);
389 }
390
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;
393         int i, n;
394         cp_status_t status = CP_OK;
395         
396         CHECK_NOT_NULL(context);
397         
398         cpi_lock_context(context);
399         cpi_check_invocation(context, CPI_CF_LOGGER, __func__);
400         do {
401                 hscan_t scan;
402                 hnode_t *hnode;
403
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));
408                         } else {
409                                 n = 0;
410                         }
411                 } else {
412                         hscan_t scan;
413                         
414                         n = 0;
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));
418                         }
419                 }
420                 
421                 // Allocate space for pointer array 
422                 if ((extensions = malloc(sizeof(cp_extension_t *) * (n + 1))) == NULL) {
423                         status = CP_ERR_RESOURCE;
424                         break;
425                 }
426                 
427                 // Get extension information structures
428                 if (extpt_id != NULL) {
429                         i = 0;
430                         if ((hnode = hash_lookup(context->env->extensions, extpt_id)) != NULL) {
431                                 list_t *el = hnode_get(hnode);
432                                 lnode_t *lnode;
433                                 
434                                 lnode = list_first(el);
435                                 while (lnode != NULL) {
436                                         cp_extension_t *e = lnode_get(lnode);
437                                 
438                                         assert(i < n);
439                                         cpi_use_info(context, e->plugin);
440                                         extensions[i] = e;
441                                         i++;
442                                         lnode = list_next(el, lnode);
443                                 }
444                         }
445                         extensions[i] = NULL;
446                 } else { 
447                         hash_scan_begin(&scan, context->env->extensions);
448                         i = 0;
449                         while ((hnode = hash_scan_next(&scan)) != NULL) {
450                                 list_t *el = hnode_get(hnode);
451                                 lnode_t *lnode;
452                         
453                                 lnode = list_first(el);
454                                 while (lnode != NULL) {
455                                         cp_extension_t *e = lnode_get(lnode);
456                                 
457                                         assert(i < n);
458                                         cpi_use_info(context, e->plugin);
459                                         extensions[i] = e;
460                                         i++;
461                                         lnode = list_next(el, lnode);
462                                 }
463                         }
464                 }
465                 extensions[i] = NULL;
466                 
467                 // Register the array
468                 status = cpi_register_info(context, extensions, (void (*)(cp_context_t *, void *)) dealloc_extensions_info);
469                 
470         } while (0);
471         
472         // Report error
473         if (status != CP_OK) {
474                 cpi_error(context, N_("Extension information could not be returned due to insufficient memory."));
475         }
476         cpi_unlock_context(context);
477         
478         // Release resources on error 
479         if (status != CP_OK) {
480                 if (extensions != NULL) {
481                         dealloc_extensions_info(context, extensions);
482                         extensions = NULL;
483                 }
484         }
485         
486         assert(status != CP_OK || n == 0 || extensions[n - 1] != NULL);
487         if (error != NULL) {
488                 *error = status;
489         }
490         if (num != NULL && status == CP_OK) {
491                 *num = n;
492         }
493         return extensions;
494 }
495
496
497 // Plug-in listeners 
498
499 /**
500  * Compares plug-in listener holders.
501  * 
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
505  */
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;
509         
510         return (plh1->plugin_listener != plh2->plugin_listener);
511 }
512
513 /**
514  * Processes a node by delivering the specified event to the associated
515  * plug-in listener.
516  * 
517  * @param list the list being processed
518  * @param node the node being processed
519  * @param event the event
520  */
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);
525 }
526
527 /**
528  * Processes a node by unregistering the associated plug-in listener.
529  * 
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
533  */
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);
538                 lnode_destroy(node);
539                 free(h);
540         }
541 }
542
543 CP_HIDDEN void cpi_unregister_plisteners(list_t *listeners, cp_plugin_t *plugin) {
544         list_process(listeners, plugin, process_unregister_plistener);
545 }
546
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;
549         el_holder_t *holder;
550         lnode_t *node;
551
552         CHECK_NOT_NULL(context);
553         CHECK_NOT_NULL(listener);
554         
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);
563                         status = CP_OK;
564                 } else {
565                         free(holder);
566                 }
567         }
568         
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)) {
573                 char owner[64];
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)));
576         }
577         cpi_unlock_context(context);
578         
579         return status;
580 }
581
582 CP_C_API void cp_unregister_plistener(cp_context_t *context, cp_plugin_listener_func_t listener) {
583         el_holder_t holder;
584         lnode_t *node;
585         
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);
591         if (node != NULL) {
592                 process_unregister_plistener(context->env->plugin_listeners, node, NULL);
593         }
594         if (cpi_is_logged(context, CP_LOG_DEBUG)) {
595                 char owner[64];
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)));
598         }
599         cpi_unlock_context(context);
600 }
601
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)) {
611                 char *str;
612                 switch (event->new_state) {
613                         case CP_PLUGIN_UNINSTALLED:
614                                 str = N_("Plug-in %s has been uninstalled.");
615                                 break;
616                         case CP_PLUGIN_INSTALLED:
617                                 if (event->old_state < CP_PLUGIN_INSTALLED) {
618                                         str = N_("Plug-in %s has been installed.");
619                                 } else {
620                                         str = N_("Plug-in %s runtime library has been unloaded.");
621                                 }
622                                 break;
623                         case CP_PLUGIN_RESOLVED:
624                                 if (event->old_state < CP_PLUGIN_RESOLVED) {
625                                         str = N_("Plug-in %s runtime library has been loaded.");
626                                 } else {
627                                         str = N_("Plug-in %s has been stopped.");
628                                 }
629                                 break;
630                         case CP_PLUGIN_STARTING:
631                                 str = N_("Plug-in %s is starting.");
632                                 break;
633                         case CP_PLUGIN_STOPPING:
634                                 str = N_("Plug-in %s is stopping.");
635                                 break;
636                         case CP_PLUGIN_ACTIVE:
637                                 str = N_("Plug-in %s has been started.");
638                                 break;
639                         default:
640                                 str = NULL;
641                                 break;
642                 }
643                 if (str != NULL) {
644                         cpi_infof(context, str, event->plugin_id);
645                 }
646         }
647 }
648
649
650 // Configuration element helpers
651
652 static cp_cfg_element_t * lookup_cfg_element(cp_cfg_element_t *base, const char *path, int len) {
653         int start = 0;
654         
655         CHECK_NOT_NULL(base);
656         CHECK_NOT_NULL(path);
657         
658         // Traverse the path
659         while (base != NULL && path[start] != '\0' && (len == -1 || start < len)) {
660                 int end = start;
661                 while (path[end] != '\0' && path[end] != '/' && (len == -1 || end < len))
662                         end++;
663                 if (end - start == 2 && !strncmp(path + start, "..", 2)) {
664                         base = base->parent;
665                 } else {
666                         int i;
667                         int found = 0;
668                         
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)) {
673                                         base = e;
674                                         found = 1;
675                                 }
676                         }
677                         if (!found) {
678                                 base = NULL;
679                         }
680                 }
681                 start = end;
682                 if (path[start] == '/') {
683                         start++;
684                 }
685         }
686         return base;
687 }
688
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);
691 }
692
693 CP_C_API char * cp_lookup_cfg_value(cp_cfg_element_t *base, const char *path) {
694         cp_cfg_element_t *e;
695         const char *attr;
696         
697         CHECK_NOT_NULL(base);
698         CHECK_NOT_NULL(path);
699         
700         if ((attr = strrchr(path, '@')) == NULL) {
701                 e = lookup_cfg_element(base, path, -1);
702         } else {
703                 e = lookup_cfg_element(base, path, attr - path);
704                 attr++;
705         }
706         if (e != NULL) {
707                 if (attr == NULL) {
708                         return e->value;
709                 } else {
710                         int i;
711                         
712                         for (i = 0; i < e->num_atts; i++) {
713                                 if (!strcmp(attr, e->atts[2*i])) {
714                                         return e->atts[2*i + 1];
715                                 }
716                         }
717                         return NULL;
718                 }
719         } else {
720                 return NULL;
721         }
722 }