2 * Copyright (C) 2010-2013 Tobias Brunner
3 * Copyright (C) 2007 Martin Willi
4 * Hochschule fuer Technik Rapperswil
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2 of the License, or (at your
9 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
18 #include "plugin_loader.h"
25 #include <utils/debug.h>
27 #include <collections/hashtable.h>
28 #include <collections/linked_list.h>
29 #include <plugins/plugin.h>
30 #include <utils/integrity_checker.h>
32 typedef struct private_plugin_loader_t private_plugin_loader_t
;
33 typedef struct registered_feature_t registered_feature_t
;
34 typedef struct provided_feature_t provided_feature_t
;
35 typedef struct plugin_entry_t plugin_entry_t
;
38 * private data of plugin_loader
40 struct private_plugin_loader_t
{
45 plugin_loader_t
public;
48 * List of plugins, as plugin_entry_t
50 linked_list_t
*plugins
;
53 * Hashtable for registered features, as registered_feature_t
55 hashtable_t
*features
;
58 * Loaded features (stored in reverse order), as provided_feature_t
60 linked_list_t
*loaded
;
63 * List of names of loaded plugins
69 * Registered plugin feature
71 struct registered_feature_t
{
74 * The registered feature
76 plugin_feature_t
*feature
;
79 * List of plugins providing this feature, as provided_feature_t
81 linked_list_t
*plugins
;
85 * Hash a registered feature
87 static bool registered_feature_hash(registered_feature_t
*this)
89 return plugin_feature_hash(this->feature
);
93 * Compare two registered features
95 static bool registered_feature_equals(registered_feature_t
*a
,
96 registered_feature_t
*b
)
98 return plugin_feature_equals(a
->feature
, b
->feature
);
102 * Feature as provided by a plugin
104 struct provided_feature_t
{
107 * Plugin providing the feature
109 plugin_entry_t
*entry
;
112 * FEATURE_REGISTER or FEATURE_CALLBACK entry
114 plugin_feature_t
*reg
;
117 * The provided feature (followed by dependencies)
119 plugin_feature_t
*feature
;
122 * Maximum number of dependencies (following feature)
127 * TRUE if currently loading this feature (to prevent loops)
132 * TRUE if feature loaded
137 * TRUE if feature failed to load
145 struct plugin_entry_t
{
153 * TRUE, if the plugin is marked as critical
158 * dlopen handle, if in separate lib
163 * List of features, as provided_feature_t
165 linked_list_t
*features
;
169 * Destroy a plugin entry
171 static void plugin_entry_destroy(plugin_entry_t
*entry
)
173 DESTROY_IF(entry
->plugin
);
176 dlclose(entry
->handle
);
178 entry
->features
->destroy(entry
->features
);
183 * Wrapper for static plugin features
188 * Implements plugin_t interface
193 * Name of the module registering these features
198 * Static plugin features
200 plugin_feature_t
*features
;
203 * Number of plugin features
209 METHOD(plugin_t
, get_static_name
, char*,
210 static_features_t
*this)
215 METHOD(plugin_t
, get_static_features
, int,
216 static_features_t
*this, plugin_feature_t
*features
[])
218 *features
= this->features
;
222 METHOD(plugin_t
, static_destroy
, void,
223 static_features_t
*this)
225 free(this->features
);
231 * Create a wrapper around static plugin features.
233 static plugin_t
*static_features_create(const char *name
,
234 plugin_feature_t features
[], int count
)
236 static_features_t
*this;
240 .get_name
= _get_static_name
,
241 .get_features
= _get_static_features
,
242 .destroy
= _static_destroy
,
244 .name
= strdup(name
),
245 .features
= calloc(count
, sizeof(plugin_feature_t
)),
249 memcpy(this->features
, features
, sizeof(plugin_feature_t
) * count
);
251 return &this->public;
256 * returns: NOT_FOUND, if the constructor was not found
257 * FAILED, if the plugin could not be constructed
259 static status_t
create_plugin(private_plugin_loader_t
*this, void *handle
,
260 char *name
, bool integrity
, bool critical
,
261 plugin_entry_t
**entry
)
265 plugin_constructor_t constructor
;
267 if (snprintf(create
, sizeof(create
), "%s_plugin_create",
268 name
) >= sizeof(create
))
272 translate(create
, "-", "_");
273 constructor
= dlsym(handle
, create
);
274 if (constructor
== NULL
)
278 if (integrity
&& lib
->integrity
)
280 if (!lib
->integrity
->check_segment(lib
->integrity
, name
, constructor
))
282 DBG1(DBG_LIB
, "plugin '%s': failed segment integrity test", name
);
285 DBG1(DBG_LIB
, "plugin '%s': passed file and segment integrity tests",
288 plugin
= constructor();
291 DBG1(DBG_LIB
, "plugin '%s': failed to load - %s returned NULL", name
,
297 .critical
= critical
,
298 .features
= linked_list_create(),
300 DBG2(DBG_LIB
, "plugin '%s': loaded successfully", name
);
305 * load a single plugin
307 static plugin_entry_t
*load_plugin(private_plugin_loader_t
*this, char *name
,
308 char *file
, bool critical
)
310 plugin_entry_t
*entry
;
313 switch (create_plugin(this, RTLD_DEFAULT
, name
, FALSE
, critical
, &entry
))
316 this->plugins
->insert_last(this->plugins
, entry
);
320 { /* try to load the plugin from a file */
329 if (!lib
->integrity
->check_file(lib
->integrity
, name
, file
))
331 DBG1(DBG_LIB
, "plugin '%s': failed file integrity test of '%s'",
336 handle
= dlopen(file
, RTLD_LAZY
);
339 DBG1(DBG_LIB
, "plugin '%s' failed to load: %s", name
, dlerror());
342 if (create_plugin(this, handle
, name
, TRUE
, critical
, &entry
) != SUCCESS
)
347 entry
->handle
= handle
;
348 this->plugins
->insert_last(this->plugins
, entry
);
353 * Convert enumerated provided_feature_t to plugin_feature_t
355 static bool feature_filter(void *null
, provided_feature_t
**provided
,
356 plugin_feature_t
**feature
)
358 *feature
= (*provided
)->feature
;
359 return (*provided
)->loaded
;
363 * Convert enumerated entries to plugin_t
365 static bool plugin_filter(void *null
, plugin_entry_t
**entry
, plugin_t
**plugin
,
366 void *in
, linked_list_t
**list
)
368 plugin_entry_t
*this = *entry
;
370 *plugin
= this->plugin
;
373 enumerator_t
*features
;
374 features
= enumerator_create_filter(
375 this->features
->create_enumerator(this->features
),
376 (void*)feature_filter
, NULL
, NULL
);
377 *list
= linked_list_create_from_enumerator(features
);
382 METHOD(plugin_loader_t
, create_plugin_enumerator
, enumerator_t
*,
383 private_plugin_loader_t
*this)
385 return enumerator_create_filter(
386 this->plugins
->create_enumerator(this->plugins
),
387 (void*)plugin_filter
, NULL
, NULL
);
391 * Create a list of the names of all loaded plugins
393 static char* loaded_plugins_list(private_plugin_loader_t
*this)
395 int buf_len
= 128, len
= 0;
397 enumerator_t
*enumerator
;
400 buf
= malloc(buf_len
);
402 enumerator
= create_plugin_enumerator(this);
403 while (enumerator
->enumerate(enumerator
, &plugin
, NULL
))
405 name
= plugin
->get_name(plugin
);
406 if (len
+ (strlen(name
) + 1) >= buf_len
)
409 buf
= realloc(buf
, buf_len
);
411 len
+= snprintf(&buf
[len
], buf_len
- len
, "%s ", name
);
413 enumerator
->destroy(enumerator
);
414 if (len
> 0 && buf
[len
- 1] == ' ')
422 * Check if a plugin is already loaded
424 static bool plugin_loaded(private_plugin_loader_t
*this, char *name
)
426 enumerator_t
*enumerator
;
430 enumerator
= create_plugin_enumerator(this);
431 while (enumerator
->enumerate(enumerator
, &plugin
, NULL
))
433 if (streq(plugin
->get_name(plugin
), name
))
439 enumerator
->destroy(enumerator
);
444 * Forward declaration
446 static void load_provided(private_plugin_loader_t
*this,
447 provided_feature_t
*provided
,
451 * Used to find a loaded feature
453 static bool is_feature_loaded(provided_feature_t
*item
)
459 * Used to find a loadable feature
461 static bool is_feature_loadable(provided_feature_t
*item
)
463 return !item
->loading
&& !item
->loaded
&& !item
->failed
;
467 * Find a loaded and matching feature
469 static bool loaded_feature_matches(registered_feature_t
*a
,
470 registered_feature_t
*b
)
472 if (plugin_feature_matches(a
->feature
, b
->feature
))
474 return b
->plugins
->find_first(b
->plugins
, (void*)is_feature_loaded
,
481 * Find a loadable module that equals the requested feature
483 static bool loadable_feature_equals(registered_feature_t
*a
,
484 registered_feature_t
*b
)
486 if (plugin_feature_equals(a
->feature
, b
->feature
))
488 return b
->plugins
->find_first(b
->plugins
, (void*)is_feature_loadable
,
495 * Find a loadable module that matches the requested feature
497 static bool loadable_feature_matches(registered_feature_t
*a
,
498 registered_feature_t
*b
)
500 if (plugin_feature_matches(a
->feature
, b
->feature
))
502 return b
->plugins
->find_first(b
->plugins
, (void*)is_feature_loadable
,
509 * Returns a compatible plugin feature for the given depencency
511 static bool find_compatible_feature(private_plugin_loader_t
*this,
512 plugin_feature_t
*dependency
)
514 registered_feature_t
*feature
, lookup
= {
515 .feature
= dependency
,
518 feature
= this->features
->get_match(this->features
, &lookup
,
519 (void*)loaded_feature_matches
);
520 return feature
!= NULL
;
524 * Load a registered plugin feature
526 static void load_registered(private_plugin_loader_t
*this,
527 registered_feature_t
*registered
,
530 enumerator_t
*enumerator
;
531 provided_feature_t
*provided
;
533 enumerator
= registered
->plugins
->create_enumerator(registered
->plugins
);
534 while (enumerator
->enumerate(enumerator
, &provided
))
536 load_provided(this, provided
, level
);
538 enumerator
->destroy(enumerator
);
542 * Try to load dependencies of the given feature
544 static bool load_dependencies(private_plugin_loader_t
*this,
545 provided_feature_t
*provided
,
548 registered_feature_t
*registered
, lookup
;
549 int indent
= level
* 2;
552 /* first entry is provided feature, followed by dependencies */
553 for (i
= 1; i
< provided
->dependencies
; i
++)
555 if (provided
->feature
[i
].kind
!= FEATURE_DEPENDS
&&
556 provided
->feature
[i
].kind
!= FEATURE_SDEPEND
)
557 { /* end of dependencies */
561 /* we load the feature even if a compatible one is already loaded,
562 * otherwise e.g. a specific database implementation loaded before
563 * another might cause a plugin feature loaded in-between to fail */
564 lookup
.feature
= &provided
->feature
[i
];
566 { /* prefer an exactly matching feature, could be omitted but
567 * results in a more predictable behavior */
568 registered
= this->features
->get_match(this->features
,
570 (void*)loadable_feature_equals
);
572 { /* try fuzzy matching */
573 registered
= this->features
->get_match(this->features
,
575 (void*)loadable_feature_matches
);
579 load_registered(this, registered
, level
);
581 /* we could stop after finding one but for dependencies like
582 * DB_ANY it might be needed to load all matching features */
586 if (!find_compatible_feature(this, &provided
->feature
[i
]))
588 char *name
, *provide
, *depend
;
589 bool soft
= provided
->feature
[i
].kind
== FEATURE_SDEPEND
;
591 name
= provided
->entry
->plugin
->get_name(provided
->entry
->plugin
);
592 provide
= plugin_feature_get_string(&provided
->feature
[0]);
593 depend
= plugin_feature_get_string(&provided
->feature
[i
]);
596 DBG2(DBG_LIB
, "%*sfeature %s in plugin '%s' has unmet soft "
597 "dependency: %s", indent
, "", provide
, name
, depend
);
601 DBG1(DBG_LIB
, "feature %s in plugin '%s' has unmet dependency: "
602 "%s", provide
, name
, depend
);
607 { /* it's ok if we can't resolve soft dependencies */
617 * Load registered plugin features
619 static void load_feature(private_plugin_loader_t
*this,
620 provided_feature_t
*provided
,
623 if (load_dependencies(this, provided
, level
))
625 if (plugin_feature_load(provided
->entry
->plugin
, provided
->feature
,
628 provided
->loaded
= TRUE
;
629 /* insert first so we can unload the features in reverse order */
630 this->loaded
->insert_first(this->loaded
, provided
);
634 provided
->failed
= TRUE
;
638 { /* TODO: we could check the current level and set a different flag when
639 * being loaded as dependency. If there are loops there is a chance the
640 * feature can be loaded later when loading the feature directly. */
641 provided
->failed
= TRUE
;
646 * Load a provided feature
648 static void load_provided(private_plugin_loader_t
*this,
649 provided_feature_t
*provided
,
652 char *name
, *provide
;
653 int indent
= level
* 2;
655 if (provided
->loaded
|| provided
->failed
)
659 name
= provided
->entry
->plugin
->get_name(provided
->entry
->plugin
);
660 provide
= plugin_feature_get_string(provided
->feature
);
661 if (provided
->loading
)
663 DBG2(DBG_LIB
, "%*sloop detected while loading %s in plugin '%s'",
664 indent
, "", provide
, name
);
668 DBG2(DBG_LIB
, "%*sloading feature %s in plugin '%s'",
669 indent
, "", provide
, name
);
672 provided
->loading
= TRUE
;
673 load_feature(this, provided
, level
+ 1);
674 provided
->loading
= FALSE
;
678 * Load registered plugin features
680 static void load_features(private_plugin_loader_t
*this)
682 enumerator_t
*enumerator
, *inner
;
683 plugin_entry_t
*plugin
;
684 provided_feature_t
*provided
;
686 /* we do this in plugin order to allow implicit dependencies to be resolved
687 * by reordering plugins */
688 enumerator
= this->plugins
->create_enumerator(this->plugins
);
689 while (enumerator
->enumerate(enumerator
, &plugin
))
691 inner
= plugin
->features
->create_enumerator(plugin
->features
);
692 while (inner
->enumerate(inner
, &provided
))
694 load_provided(this, provided
, 0);
696 inner
->destroy(inner
);
698 enumerator
->destroy(enumerator
);
702 * Register plugin features provided by the given plugin
704 static void register_features(private_plugin_loader_t
*this,
705 plugin_entry_t
*entry
)
707 plugin_feature_t
*feature
, *reg
;
708 registered_feature_t
*registered
, lookup
;
709 provided_feature_t
*provided
;
712 if (!entry
->plugin
->get_features
)
713 { /* feature interface not supported */
714 DBG1(DBG_LIB
, "plugin '%s' does not provide features, deprecated",
715 entry
->plugin
->get_name(entry
->plugin
));
719 count
= entry
->plugin
->get_features(entry
->plugin
, &feature
);
720 for (i
= 0; i
< count
; i
++)
722 switch (feature
->kind
)
724 case FEATURE_PROVIDE
:
725 lookup
.feature
= feature
;
726 registered
= this->features
->get(this->features
, &lookup
);
731 .plugins
= linked_list_create(),
733 this->features
->put(this->features
, registered
, registered
);
739 .dependencies
= count
- i
,
741 registered
->plugins
->insert_last(registered
->plugins
,
743 entry
->features
->insert_last(entry
->features
, provided
);
745 case FEATURE_REGISTER
:
746 case FEATURE_CALLBACK
:
757 * Unregister a plugin feature
759 static void unregister_feature(private_plugin_loader_t
*this,
760 provided_feature_t
*provided
)
762 registered_feature_t
*registered
, lookup
;
764 lookup
.feature
= provided
->feature
;
765 registered
= this->features
->get(this->features
, &lookup
);
768 registered
->plugins
->remove(registered
->plugins
, provided
, NULL
);
769 if (registered
->plugins
->get_count(registered
->plugins
) == 0)
771 this->features
->remove(this->features
, &lookup
);
772 registered
->plugins
->destroy(registered
->plugins
);
775 else if (registered
->feature
== provided
->feature
)
776 { /* update feature in case the providing plugin gets unloaded */
777 provided_feature_t
*first
;
779 registered
->plugins
->get_first(registered
->plugins
, (void**)&first
);
780 registered
->feature
= first
->feature
;
787 * Unregister plugin features
789 static void unregister_features(private_plugin_loader_t
*this,
790 plugin_entry_t
*entry
)
792 provided_feature_t
*provided
;
793 enumerator_t
*enumerator
;
795 enumerator
= entry
->features
->create_enumerator(entry
->features
);
796 while (enumerator
->enumerate(enumerator
, &provided
))
798 entry
->features
->remove_at(entry
->features
, enumerator
);
799 unregister_feature(this, provided
);
801 enumerator
->destroy(enumerator
);
805 * Check that we have all features loaded for critical plugins
807 static bool missing_critical_features(private_plugin_loader_t
*this)
809 enumerator_t
*enumerator
, *features
;
810 plugin_entry_t
*entry
;
811 bool critical_failed
= FALSE
;
813 enumerator
= this->plugins
->create_enumerator(this->plugins
);
814 while (enumerator
->enumerate(enumerator
, &entry
))
816 provided_feature_t
*provided
;
817 char *name
, *provide
;
820 if (!entry
->plugin
->get_features
||
822 { /* feature interface not supported, or not critical */
826 name
= entry
->plugin
->get_name(entry
->plugin
);
827 features
= entry
->features
->create_enumerator(entry
->features
);
828 while (features
->enumerate(features
, &provided
))
830 if (!provided
->loaded
)
832 provide
= plugin_feature_get_string(provided
->feature
);
833 DBG2(DBG_LIB
, " failed to load %s in critical plugin '%s'",
839 features
->destroy(features
);
842 DBG1(DBG_LIB
, "failed to load %d feature%s in critical plugin '%s'",
843 failed
, failed
> 1 ?
"s" : "", name
);
844 critical_failed
= TRUE
;
847 enumerator
->destroy(enumerator
);
849 return critical_failed
;
853 * Remove plugins we were not able to load any plugin features from.
855 static void purge_plugins(private_plugin_loader_t
*this)
857 enumerator_t
*enumerator
;
858 plugin_entry_t
*entry
;
860 enumerator
= this->plugins
->create_enumerator(this->plugins
);
861 while (enumerator
->enumerate(enumerator
, &entry
))
863 if (!entry
->plugin
->get_features
)
864 { /* feature interface not supported */
867 if (entry
->features
->find_first(entry
->features
,
868 (void*)is_feature_loaded
, NULL
) != SUCCESS
)
870 DBG2(DBG_LIB
, "unloading plugin '%s' without loaded features",
871 entry
->plugin
->get_name(entry
->plugin
));
872 this->plugins
->remove_at(this->plugins
, enumerator
);
873 unregister_features(this, entry
);
874 plugin_entry_destroy(entry
);
877 enumerator
->destroy(enumerator
);
880 METHOD(plugin_loader_t
, add_static_features
, void,
881 private_plugin_loader_t
*this, const char *name
,
882 plugin_feature_t features
[], int count
, bool critical
)
884 plugin_entry_t
*entry
;
887 plugin
= static_features_create(name
, features
, count
);
891 .critical
= critical
,
892 .features
= linked_list_create(),
894 this->plugins
->insert_last(this->plugins
, entry
);
895 register_features(this, entry
);
898 METHOD(plugin_loader_t
, load_plugins
, bool,
899 private_plugin_loader_t
*this, char *path
, char *list
)
901 enumerator_t
*enumerator
;
903 bool critical_failed
= FALSE
;
910 #endif /* PLUGINDIR */
912 enumerator
= enumerator_create_token(list
, " ", " ");
913 while (!critical_failed
&& enumerator
->enumerate(enumerator
, &token
))
915 plugin_entry_t
*entry
;
916 bool critical
= FALSE
;
917 char buf
[PATH_MAX
], *file
= NULL
;
920 token
= strdup(token
);
922 if (token
[len
-1] == '!')
927 if (plugin_loaded(this, token
))
934 if (snprintf(buf
, sizeof(buf
), "%s/libstrongswan-%s.so",
935 path
, token
) >= sizeof(buf
))
941 entry
= load_plugin(this, token
, file
, critical
);
944 register_features(this, entry
);
948 critical_failed
= TRUE
;
949 DBG1(DBG_LIB
, "loading critical plugin '%s' failed", token
);
953 enumerator
->destroy(enumerator
);
954 if (!critical_failed
)
957 /* check for unloaded features provided by critical plugins */
958 critical_failed
= missing_critical_features(this);
959 /* unload plugins that we were not able to load any features for */
962 if (!critical_failed
)
964 free(this->loaded_plugins
);
965 this->loaded_plugins
= loaded_plugins_list(this);
967 return !critical_failed
;
971 * Unload plugin features, they are registered in reverse order
973 static void unload_features(private_plugin_loader_t
*this)
975 enumerator_t
*enumerator
;
976 provided_feature_t
*provided
;
977 plugin_entry_t
*entry
;
979 enumerator
= this->loaded
->create_enumerator(this->loaded
);
980 while (enumerator
->enumerate(enumerator
, &provided
))
982 entry
= provided
->entry
;
983 plugin_feature_unload(entry
->plugin
, provided
->feature
, provided
->reg
);
984 this->loaded
->remove_at(this->loaded
, enumerator
);
985 entry
->features
->remove(entry
->features
, provided
, NULL
);
986 unregister_feature(this, provided
);
988 enumerator
->destroy(enumerator
);
991 METHOD(plugin_loader_t
, unload
, void,
992 private_plugin_loader_t
*this)
994 plugin_entry_t
*entry
;
996 /* unload features followed by plugins, in reverse order */
997 unload_features(this);
998 while (this->plugins
->remove_last(this->plugins
, (void**)&entry
) == SUCCESS
)
1000 if (lib
->leak_detective
)
1001 { /* keep handle to report leaks properly */
1002 entry
->handle
= NULL
;
1004 unregister_features(this, entry
);
1005 plugin_entry_destroy(entry
);
1007 free(this->loaded_plugins
);
1008 this->loaded_plugins
= NULL
;
1012 * Reload a plugin by name, NULL for all
1014 static u_int
reload_by_name(private_plugin_loader_t
*this, char *name
)
1017 enumerator_t
*enumerator
;
1020 enumerator
= create_plugin_enumerator(this);
1021 while (enumerator
->enumerate(enumerator
, &plugin
, NULL
))
1023 if (name
== NULL
|| streq(name
, plugin
->get_name(plugin
)))
1025 if (plugin
->reload
&& plugin
->reload(plugin
))
1027 DBG2(DBG_LIB
, "reloaded configuration of '%s' plugin",
1028 plugin
->get_name(plugin
));
1033 enumerator
->destroy(enumerator
);
1037 METHOD(plugin_loader_t
, reload
, u_int
,
1038 private_plugin_loader_t
*this, char *list
)
1041 enumerator_t
*enumerator
;
1046 return reload_by_name(this, NULL
);
1048 enumerator
= enumerator_create_token(list
, " ", "");
1049 while (enumerator
->enumerate(enumerator
, &name
))
1051 reloaded
+= reload_by_name(this, name
);
1053 enumerator
->destroy(enumerator
);
1057 METHOD(plugin_loader_t
, loaded_plugins
, char*,
1058 private_plugin_loader_t
*this)
1060 return this->loaded_plugins ?
: "";
1063 METHOD(plugin_loader_t
, destroy
, void,
1064 private_plugin_loader_t
*this)
1067 this->features
->destroy(this->features
);
1068 this->loaded
->destroy(this->loaded
);
1069 this->plugins
->destroy(this->plugins
);
1070 free(this->loaded_plugins
);
1077 plugin_loader_t
*plugin_loader_create()
1079 private_plugin_loader_t
*this;
1083 .add_static_features
= _add_static_features
,
1084 .load
= _load_plugins
,
1087 .create_plugin_enumerator
= _create_plugin_enumerator
,
1088 .loaded_plugins
= _loaded_plugins
,
1089 .destroy
= _destroy
,
1091 .plugins
= linked_list_create(),
1092 .loaded
= linked_list_create(),
1093 .features
= hashtable_create(
1094 (hashtable_hash_t
)registered_feature_hash
,
1095 (hashtable_equals_t
)registered_feature_equals
, 64),
1098 return &this->public;