Annotation of embedaddon/strongswan/src/libstrongswan/plugins/plugin_loader.c, revision 1.1
1.1 ! misho 1: /*
! 2: * Copyright (C) 2010-2014 Tobias Brunner
! 3: * Copyright (C) 2007 Martin Willi
! 4: * HSR Hochschule fuer Technik Rapperswil
! 5: *
! 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>.
! 10: *
! 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
! 14: * for more details.
! 15: */
! 16:
! 17: #define _GNU_SOURCE
! 18: #include "plugin_loader.h"
! 19:
! 20: #include <sys/types.h>
! 21: #include <sys/stat.h>
! 22: #include <unistd.h>
! 23: #include <string.h>
! 24: #ifdef HAVE_DLADDR
! 25: #include <dlfcn.h>
! 26: #endif
! 27: #include <limits.h>
! 28: #include <stdio.h>
! 29:
! 30: #include <utils/debug.h>
! 31: #include <library.h>
! 32: #include <collections/hashtable.h>
! 33: #include <collections/array.h>
! 34: #include <collections/linked_list.h>
! 35: #include <plugins/plugin.h>
! 36: #include <utils/integrity_checker.h>
! 37:
! 38: typedef struct private_plugin_loader_t private_plugin_loader_t;
! 39: typedef struct registered_feature_t registered_feature_t;
! 40: typedef struct provided_feature_t provided_feature_t;
! 41: typedef struct plugin_entry_t plugin_entry_t;
! 42:
! 43: #ifdef STATIC_PLUGIN_CONSTRUCTORS
! 44: /**
! 45: * Statically registered constructors
! 46: */
! 47: static hashtable_t *plugin_constructors = NULL;
! 48: #endif
! 49:
! 50: /**
! 51: * private data of plugin_loader
! 52: */
! 53: struct private_plugin_loader_t {
! 54:
! 55: /**
! 56: * public functions
! 57: */
! 58: plugin_loader_t public;
! 59:
! 60: /**
! 61: * List of plugins, as plugin_entry_t
! 62: */
! 63: linked_list_t *plugins;
! 64:
! 65: /**
! 66: * Hashtable for registered features, as registered_feature_t
! 67: */
! 68: hashtable_t *features;
! 69:
! 70: /**
! 71: * Loaded features (stored in reverse order), as provided_feature_t
! 72: */
! 73: linked_list_t *loaded;
! 74:
! 75: /**
! 76: * List of paths to search for plugins
! 77: */
! 78: linked_list_t *paths;
! 79:
! 80: /**
! 81: * List of names of loaded plugins
! 82: */
! 83: char *loaded_plugins;
! 84:
! 85: /**
! 86: * Statistics collected while loading features
! 87: */
! 88: struct {
! 89: /** Number of features that failed to load */
! 90: int failed;
! 91: /** Number of features that failed because of unmet dependencies */
! 92: int depends;
! 93: /** Number of features in critical plugins that failed to load */
! 94: int critical;
! 95: } stats;
! 96: };
! 97:
! 98: /**
! 99: * Registered plugin feature
! 100: */
! 101: struct registered_feature_t {
! 102:
! 103: /**
! 104: * The registered feature
! 105: */
! 106: plugin_feature_t *feature;
! 107:
! 108: /**
! 109: * List of plugins providing this feature, as provided_feature_t
! 110: */
! 111: linked_list_t *plugins;
! 112: };
! 113:
! 114: /**
! 115: * Hash a registered feature
! 116: */
! 117: static u_int registered_feature_hash(registered_feature_t *this)
! 118: {
! 119: return plugin_feature_hash(this->feature);
! 120: }
! 121:
! 122: /**
! 123: * Compare two registered features
! 124: */
! 125: static bool registered_feature_equals(registered_feature_t *a,
! 126: registered_feature_t *b)
! 127: {
! 128: return plugin_feature_equals(a->feature, b->feature);
! 129: }
! 130:
! 131: /**
! 132: * Feature as provided by a plugin
! 133: */
! 134: struct provided_feature_t {
! 135:
! 136: /**
! 137: * Plugin providing the feature
! 138: */
! 139: plugin_entry_t *entry;
! 140:
! 141: /**
! 142: * FEATURE_REGISTER or FEATURE_CALLBACK entry
! 143: */
! 144: plugin_feature_t *reg;
! 145:
! 146: /**
! 147: * The provided feature (followed by dependencies)
! 148: */
! 149: plugin_feature_t *feature;
! 150:
! 151: /**
! 152: * Maximum number of dependencies (following feature)
! 153: */
! 154: int dependencies;
! 155:
! 156: /**
! 157: * TRUE if currently loading this feature (to prevent loops)
! 158: */
! 159: bool loading;
! 160:
! 161: /**
! 162: * TRUE if feature loaded
! 163: */
! 164: bool loaded;
! 165:
! 166: /**
! 167: * TRUE if feature failed to load
! 168: */
! 169: bool failed;
! 170: };
! 171:
! 172: /**
! 173: * Entry for a plugin
! 174: */
! 175: struct plugin_entry_t {
! 176:
! 177: /**
! 178: * Plugin instance
! 179: */
! 180: plugin_t *plugin;
! 181:
! 182: /**
! 183: * TRUE, if the plugin is marked as critical
! 184: */
! 185: bool critical;
! 186:
! 187: /**
! 188: * dlopen handle, if in separate lib
! 189: */
! 190: void *handle;
! 191:
! 192: /**
! 193: * List of features, as provided_feature_t
! 194: */
! 195: linked_list_t *features;
! 196: };
! 197:
! 198: /**
! 199: * Destroy a plugin entry
! 200: */
! 201: static void plugin_entry_destroy(plugin_entry_t *entry)
! 202: {
! 203: DESTROY_IF(entry->plugin);
! 204: if (entry->handle)
! 205: {
! 206: dlclose(entry->handle);
! 207: }
! 208: entry->features->destroy(entry->features);
! 209: free(entry);
! 210: }
! 211:
! 212: /**
! 213: * Wrapper for static plugin features
! 214: */
! 215: typedef struct {
! 216:
! 217: /**
! 218: * Implements plugin_t interface
! 219: */
! 220: plugin_t public;
! 221:
! 222: /**
! 223: * Name of the module registering these features
! 224: */
! 225: char *name;
! 226:
! 227: /**
! 228: * Optional reload function for features
! 229: */
! 230: bool (*reload)(void *data);
! 231:
! 232: /**
! 233: * User data to pass to reload function
! 234: */
! 235: void *reload_data;
! 236:
! 237: /**
! 238: * Static plugin features
! 239: */
! 240: plugin_feature_t *features;
! 241:
! 242: /**
! 243: * Number of plugin features
! 244: */
! 245: int count;
! 246:
! 247: } static_features_t;
! 248:
! 249: METHOD(plugin_t, get_static_name, char*,
! 250: static_features_t *this)
! 251: {
! 252: return this->name;
! 253: }
! 254:
! 255: METHOD(plugin_t, get_static_features, int,
! 256: static_features_t *this, plugin_feature_t *features[])
! 257: {
! 258: *features = this->features;
! 259: return this->count;
! 260: }
! 261:
! 262: METHOD(plugin_t, static_reload, bool,
! 263: static_features_t *this)
! 264: {
! 265: if (this->reload)
! 266: {
! 267: return this->reload(this->reload_data);
! 268: }
! 269: return FALSE;
! 270: }
! 271:
! 272: METHOD(plugin_t, static_destroy, void,
! 273: static_features_t *this)
! 274: {
! 275: free(this->features);
! 276: free(this->name);
! 277: free(this);
! 278: }
! 279:
! 280: /**
! 281: * Create a wrapper around static plugin features.
! 282: */
! 283: static plugin_t *static_features_create(const char *name,
! 284: plugin_feature_t features[], int count,
! 285: bool (*reload)(void*), void *reload_data)
! 286: {
! 287: static_features_t *this;
! 288:
! 289: INIT(this,
! 290: .public = {
! 291: .get_name = _get_static_name,
! 292: .get_features = _get_static_features,
! 293: .reload = _static_reload,
! 294: .destroy = _static_destroy,
! 295: },
! 296: .name = strdup(name),
! 297: .reload = reload,
! 298: .reload_data = reload_data,
! 299: .features = calloc(count, sizeof(plugin_feature_t)),
! 300: .count = count,
! 301: );
! 302:
! 303: memcpy(this->features, features, sizeof(plugin_feature_t) * count);
! 304:
! 305: return &this->public;
! 306: }
! 307:
! 308: #ifdef STATIC_PLUGIN_CONSTRUCTORS
! 309: /*
! 310: * Described in header.
! 311: */
! 312: void plugin_constructor_register(char *name, void *constructor)
! 313: {
! 314: bool old = FALSE;
! 315:
! 316: if (lib && lib->leak_detective)
! 317: {
! 318: old = lib->leak_detective->set_state(lib->leak_detective, FALSE);
! 319: }
! 320:
! 321: if (!plugin_constructors)
! 322: {
! 323: chunk_hash_seed();
! 324: plugin_constructors = hashtable_create(hashtable_hash_str,
! 325: hashtable_equals_str, 32);
! 326: }
! 327: if (constructor)
! 328: {
! 329: plugin_constructors->put(plugin_constructors, name, constructor);
! 330: }
! 331: else
! 332: {
! 333: plugin_constructors->remove(plugin_constructors, name);
! 334: if (!plugin_constructors->get_count(plugin_constructors))
! 335: {
! 336: plugin_constructors->destroy(plugin_constructors);
! 337: plugin_constructors = NULL;
! 338: }
! 339: }
! 340:
! 341: if (lib && lib->leak_detective)
! 342: {
! 343: lib->leak_detective->set_state(lib->leak_detective, old);
! 344: }
! 345: }
! 346: #endif
! 347:
! 348: /**
! 349: * create a plugin
! 350: * returns: NOT_FOUND, if the constructor was not found
! 351: * FAILED, if the plugin could not be constructed
! 352: */
! 353: static status_t create_plugin(private_plugin_loader_t *this, void *handle,
! 354: char *name, bool integrity, bool critical,
! 355: plugin_entry_t **entry)
! 356: {
! 357: char create[128];
! 358: plugin_t *plugin;
! 359: plugin_constructor_t constructor = NULL;
! 360:
! 361: if (snprintf(create, sizeof(create), "%s_plugin_create",
! 362: name) >= sizeof(create))
! 363: {
! 364: return FAILED;
! 365: }
! 366: translate(create, "-", "_");
! 367: #ifdef STATIC_PLUGIN_CONSTRUCTORS
! 368: if (plugin_constructors)
! 369: {
! 370: constructor = plugin_constructors->get(plugin_constructors, name);
! 371: }
! 372: if (!constructor)
! 373: #endif
! 374: {
! 375: constructor = dlsym(handle, create);
! 376: }
! 377: if (!constructor)
! 378: {
! 379: return NOT_FOUND;
! 380: }
! 381: if (integrity && lib->integrity)
! 382: {
! 383: if (!lib->integrity->check_segment(lib->integrity, name, constructor))
! 384: {
! 385: DBG1(DBG_LIB, "plugin '%s': failed segment integrity test", name);
! 386: return FAILED;
! 387: }
! 388: DBG1(DBG_LIB, "plugin '%s': passed file and segment integrity tests",
! 389: name);
! 390: }
! 391: plugin = constructor();
! 392: if (plugin == NULL)
! 393: {
! 394: DBG1(DBG_LIB, "plugin '%s': failed to load - %s returned NULL", name,
! 395: create);
! 396: return FAILED;
! 397: }
! 398: INIT(*entry,
! 399: .plugin = plugin,
! 400: .critical = critical,
! 401: .features = linked_list_create(),
! 402: );
! 403: DBG2(DBG_LIB, "plugin '%s': loaded successfully", name);
! 404: return SUCCESS;
! 405: }
! 406:
! 407: /**
! 408: * load a single plugin
! 409: */
! 410: static plugin_entry_t *load_plugin(private_plugin_loader_t *this, char *name,
! 411: char *file, bool critical)
! 412: {
! 413: plugin_entry_t *entry;
! 414: void *handle;
! 415: int flag = RTLD_LAZY;
! 416:
! 417: switch (create_plugin(this, RTLD_DEFAULT, name, FALSE, critical, &entry))
! 418: {
! 419: case SUCCESS:
! 420: this->plugins->insert_last(this->plugins, entry);
! 421: return entry;
! 422: case NOT_FOUND:
! 423: if (file)
! 424: { /* try to load the plugin from a file */
! 425: break;
! 426: }
! 427: /* fall-through */
! 428: default:
! 429: return NULL;
! 430: }
! 431: if (lib->integrity)
! 432: {
! 433: if (!lib->integrity->check_file(lib->integrity, name, file))
! 434: {
! 435: DBG1(DBG_LIB, "plugin '%s': failed file integrity test of '%s'",
! 436: name, file);
! 437: return NULL;
! 438: }
! 439: }
! 440: if (lib->settings->get_bool(lib->settings, "%s.dlopen_use_rtld_now",
! 441: FALSE, lib->ns))
! 442: {
! 443: flag = RTLD_NOW;
! 444: }
! 445: #ifdef RTLD_NODELETE
! 446: /* If supported, do not unload the library when unloading a plugin. It
! 447: * really doesn't matter in productive systems, but causes many (dependency)
! 448: * library reloads during unit tests. Some libraries can't handle that, e.g.
! 449: * GnuTLS leaks file descriptors in its library load/unload functions. */
! 450: flag |= RTLD_NODELETE;
! 451: #endif
! 452: handle = dlopen(file, flag);
! 453: if (handle == NULL)
! 454: {
! 455: DBG1(DBG_LIB, "plugin '%s' failed to load: %s", name, dlerror());
! 456: return NULL;
! 457: }
! 458: if (create_plugin(this, handle, name, TRUE, critical, &entry) != SUCCESS)
! 459: {
! 460: dlclose(handle);
! 461: return NULL;
! 462: }
! 463: entry->handle = handle;
! 464: this->plugins->insert_last(this->plugins, entry);
! 465: return entry;
! 466: }
! 467:
! 468: CALLBACK(feature_filter, bool,
! 469: void *null, enumerator_t *orig, va_list args)
! 470: {
! 471: provided_feature_t *provided;
! 472: plugin_feature_t **feature;
! 473:
! 474: VA_ARGS_VGET(args, feature);
! 475:
! 476: while (orig->enumerate(orig, &provided))
! 477: {
! 478: if (provided->loaded)
! 479: {
! 480: *feature = provided->feature;
! 481: return TRUE;
! 482: }
! 483: }
! 484: return FALSE;
! 485: }
! 486:
! 487: CALLBACK(plugin_filter, bool,
! 488: void *null, enumerator_t *orig, va_list args)
! 489: {
! 490: plugin_entry_t *entry;
! 491: linked_list_t **list;
! 492: plugin_t **plugin;
! 493:
! 494: VA_ARGS_VGET(args, plugin, list);
! 495:
! 496: if (orig->enumerate(orig, &entry))
! 497: {
! 498: *plugin = entry->plugin;
! 499: if (list)
! 500: {
! 501: enumerator_t *features;
! 502: features = enumerator_create_filter(
! 503: entry->features->create_enumerator(entry->features),
! 504: feature_filter, NULL, NULL);
! 505: *list = linked_list_create_from_enumerator(features);
! 506: }
! 507: return TRUE;
! 508: }
! 509: return FALSE;
! 510: }
! 511:
! 512: METHOD(plugin_loader_t, create_plugin_enumerator, enumerator_t*,
! 513: private_plugin_loader_t *this)
! 514: {
! 515: return enumerator_create_filter(
! 516: this->plugins->create_enumerator(this->plugins),
! 517: plugin_filter, NULL, NULL);
! 518: }
! 519:
! 520: METHOD(plugin_loader_t, has_feature, bool,
! 521: private_plugin_loader_t *this, plugin_feature_t feature)
! 522: {
! 523: enumerator_t *plugins, *features;
! 524: plugin_t *plugin;
! 525: linked_list_t *list;
! 526: plugin_feature_t *current;
! 527: bool found = FALSE;
! 528:
! 529: plugins = create_plugin_enumerator(this);
! 530: while (plugins->enumerate(plugins, &plugin, &list))
! 531: {
! 532: features = list->create_enumerator(list);
! 533: while (features->enumerate(features, ¤t))
! 534: {
! 535: if (plugin_feature_matches(&feature, current))
! 536: {
! 537: found = TRUE;
! 538: break;
! 539: }
! 540: }
! 541: features->destroy(features);
! 542: list->destroy(list);
! 543: }
! 544: plugins->destroy(plugins);
! 545:
! 546: return found;
! 547: }
! 548:
! 549: /**
! 550: * Create a list of the names of all loaded plugins
! 551: */
! 552: static char* loaded_plugins_list(private_plugin_loader_t *this)
! 553: {
! 554: int buf_len = 128, len = 0;
! 555: char *buf, *name;
! 556: enumerator_t *enumerator;
! 557: plugin_t *plugin;
! 558:
! 559: buf = malloc(buf_len);
! 560: buf[0] = '\0';
! 561: enumerator = create_plugin_enumerator(this);
! 562: while (enumerator->enumerate(enumerator, &plugin, NULL))
! 563: {
! 564: name = plugin->get_name(plugin);
! 565: if (len + (strlen(name) + 1) >= buf_len)
! 566: {
! 567: buf_len <<= 1;
! 568: buf = realloc(buf, buf_len);
! 569: }
! 570: len += snprintf(&buf[len], buf_len - len, "%s ", name);
! 571: }
! 572: enumerator->destroy(enumerator);
! 573: if (len > 0 && buf[len - 1] == ' ')
! 574: {
! 575: buf[len - 1] = '\0';
! 576: }
! 577: return buf;
! 578: }
! 579:
! 580: /**
! 581: * Check if a plugin is already loaded
! 582: */
! 583: static bool plugin_loaded(private_plugin_loader_t *this, char *name)
! 584: {
! 585: enumerator_t *enumerator;
! 586: bool found = FALSE;
! 587: plugin_t *plugin;
! 588:
! 589: enumerator = create_plugin_enumerator(this);
! 590: while (enumerator->enumerate(enumerator, &plugin, NULL))
! 591: {
! 592: if (streq(plugin->get_name(plugin), name))
! 593: {
! 594: found = TRUE;
! 595: break;
! 596: }
! 597: }
! 598: enumerator->destroy(enumerator);
! 599: return found;
! 600: }
! 601:
! 602: /**
! 603: * Forward declaration
! 604: */
! 605: static void load_provided(private_plugin_loader_t *this,
! 606: provided_feature_t *provided,
! 607: int level);
! 608:
! 609: CALLBACK(is_feature_loaded, bool,
! 610: provided_feature_t *item, va_list args)
! 611: {
! 612: return item->loaded;
! 613: }
! 614:
! 615: CALLBACK(is_feature_loadable, bool,
! 616: provided_feature_t *item, va_list args)
! 617: {
! 618: return !item->loading && !item->loaded && !item->failed;
! 619: }
! 620:
! 621: /**
! 622: * Find a loaded and matching feature
! 623: */
! 624: static bool loaded_feature_matches(registered_feature_t *a,
! 625: registered_feature_t *b)
! 626: {
! 627: if (plugin_feature_matches(a->feature, b->feature))
! 628: {
! 629: return b->plugins->find_first(b->plugins, is_feature_loaded, NULL);
! 630: }
! 631: return FALSE;
! 632: }
! 633:
! 634: /**
! 635: * Find a loadable module that equals the requested feature
! 636: */
! 637: static bool loadable_feature_equals(registered_feature_t *a,
! 638: registered_feature_t *b)
! 639: {
! 640: if (plugin_feature_equals(a->feature, b->feature))
! 641: {
! 642: return b->plugins->find_first(b->plugins, is_feature_loadable, NULL);
! 643: }
! 644: return FALSE;
! 645: }
! 646:
! 647: /**
! 648: * Find a loadable module that matches the requested feature
! 649: */
! 650: static bool loadable_feature_matches(registered_feature_t *a,
! 651: registered_feature_t *b)
! 652: {
! 653: if (plugin_feature_matches(a->feature, b->feature))
! 654: {
! 655: return b->plugins->find_first(b->plugins, is_feature_loadable, NULL);
! 656: }
! 657: return FALSE;
! 658: }
! 659:
! 660: /**
! 661: * Returns a compatible plugin feature for the given dependency
! 662: */
! 663: static bool find_compatible_feature(private_plugin_loader_t *this,
! 664: plugin_feature_t *dependency)
! 665: {
! 666: registered_feature_t *feature, lookup = {
! 667: .feature = dependency,
! 668: };
! 669:
! 670: feature = this->features->get_match(this->features, &lookup,
! 671: (void*)loaded_feature_matches);
! 672: return feature != NULL;
! 673: }
! 674:
! 675: /**
! 676: * Load a registered plugin feature
! 677: */
! 678: static void load_registered(private_plugin_loader_t *this,
! 679: registered_feature_t *registered,
! 680: int level)
! 681: {
! 682: enumerator_t *enumerator;
! 683: provided_feature_t *provided;
! 684:
! 685: enumerator = registered->plugins->create_enumerator(registered->plugins);
! 686: while (enumerator->enumerate(enumerator, &provided))
! 687: {
! 688: load_provided(this, provided, level);
! 689: }
! 690: enumerator->destroy(enumerator);
! 691: }
! 692:
! 693: /**
! 694: * Try to load dependencies of the given feature
! 695: */
! 696: static bool load_dependencies(private_plugin_loader_t *this,
! 697: provided_feature_t *provided,
! 698: int level)
! 699: {
! 700: registered_feature_t *registered, lookup;
! 701: int i;
! 702:
! 703: /* first entry is provided feature, followed by dependencies */
! 704: for (i = 1; i < provided->dependencies; i++)
! 705: {
! 706: if (provided->feature[i].kind != FEATURE_DEPENDS &&
! 707: provided->feature[i].kind != FEATURE_SDEPEND)
! 708: { /* end of dependencies */
! 709: break;
! 710: }
! 711:
! 712: /* we load the feature even if a compatible one is already loaded,
! 713: * otherwise e.g. a specific database implementation loaded before
! 714: * another might cause a plugin feature loaded in-between to fail */
! 715: lookup.feature = &provided->feature[i];
! 716: do
! 717: { /* prefer an exactly matching feature, could be omitted but
! 718: * results in a more predictable behavior */
! 719: registered = this->features->get_match(this->features,
! 720: &lookup,
! 721: (void*)loadable_feature_equals);
! 722: if (!registered)
! 723: { /* try fuzzy matching */
! 724: registered = this->features->get_match(this->features,
! 725: &lookup,
! 726: (void*)loadable_feature_matches);
! 727: }
! 728: if (registered)
! 729: {
! 730: load_registered(this, registered, level);
! 731: }
! 732: /* we could stop after finding one but for dependencies like
! 733: * DB_ANY it might be needed to load all matching features */
! 734: }
! 735: while (registered);
! 736:
! 737: if (!find_compatible_feature(this, &provided->feature[i]))
! 738: {
! 739: bool soft = provided->feature[i].kind == FEATURE_SDEPEND;
! 740:
! 741: #ifndef USE_FUZZING
! 742: char *name, *provide, *depend;
! 743: int indent = level * 2;
! 744:
! 745: name = provided->entry->plugin->get_name(provided->entry->plugin);
! 746: provide = plugin_feature_get_string(&provided->feature[0]);
! 747: depend = plugin_feature_get_string(&provided->feature[i]);
! 748: if (soft)
! 749: {
! 750: DBG3(DBG_LIB, "%*sfeature %s in plugin '%s' has unmet soft "
! 751: "dependency: %s", indent, "", provide, name, depend);
! 752: }
! 753: else if (provided->entry->critical)
! 754: {
! 755: DBG1(DBG_LIB, "feature %s in critical plugin '%s' has unmet "
! 756: "dependency: %s", provide, name, depend);
! 757: }
! 758: else
! 759: {
! 760: DBG2(DBG_LIB, "feature %s in plugin '%s' has unmet dependency: "
! 761: "%s", provide, name, depend);
! 762: }
! 763: free(provide);
! 764: free(depend);
! 765: #endif /* !USE_FUZZING */
! 766:
! 767: if (soft)
! 768: { /* it's ok if we can't resolve soft dependencies */
! 769: continue;
! 770: }
! 771: return FALSE;
! 772: }
! 773: }
! 774: return TRUE;
! 775: }
! 776:
! 777: /**
! 778: * Load registered plugin features
! 779: */
! 780: static void load_feature(private_plugin_loader_t *this,
! 781: provided_feature_t *provided,
! 782: int level)
! 783: {
! 784: if (load_dependencies(this, provided, level))
! 785: {
! 786: if (plugin_feature_load(provided->entry->plugin, provided->feature,
! 787: provided->reg))
! 788: {
! 789: provided->loaded = TRUE;
! 790: /* insert first so we can unload the features in reverse order */
! 791: this->loaded->insert_first(this->loaded, provided);
! 792: return;
! 793: }
! 794:
! 795: #ifndef USE_FUZZING
! 796: char *name, *provide;
! 797:
! 798: name = provided->entry->plugin->get_name(provided->entry->plugin);
! 799: provide = plugin_feature_get_string(&provided->feature[0]);
! 800: if (provided->entry->critical)
! 801: {
! 802: DBG1(DBG_LIB, "feature %s in critical plugin '%s' failed to load",
! 803: provide, name);
! 804: }
! 805: else
! 806: {
! 807: DBG2(DBG_LIB, "feature %s in plugin '%s' failed to load",
! 808: provide, name);
! 809: }
! 810: free(provide);
! 811: #endif /* !USE_FUZZING */
! 812: }
! 813: else
! 814: { /* TODO: we could check the current level and set a different flag when
! 815: * being loaded as dependency. If there are loops there is a chance the
! 816: * feature can be loaded later when loading the feature directly. */
! 817: this->stats.depends++;
! 818: }
! 819: provided->failed = TRUE;
! 820: this->stats.critical += provided->entry->critical ? 1 : 0;
! 821: this->stats.failed++;
! 822: }
! 823:
! 824: /**
! 825: * Load a provided feature
! 826: */
! 827: static void load_provided(private_plugin_loader_t *this,
! 828: provided_feature_t *provided,
! 829: int level)
! 830: {
! 831:
! 832: if (provided->loaded || provided->failed)
! 833: {
! 834: return;
! 835: }
! 836:
! 837: #ifndef USE_FUZZING
! 838: char *name, *provide;
! 839: int indent = level * 2;
! 840:
! 841: name = provided->entry->plugin->get_name(provided->entry->plugin);
! 842: provide = plugin_feature_get_string(provided->feature);
! 843: if (provided->loading)
! 844: { /* prevent loop */
! 845: DBG3(DBG_LIB, "%*sloop detected while loading %s in plugin '%s'",
! 846: indent, "", provide, name);
! 847: free(provide);
! 848: return;
! 849: }
! 850: DBG3(DBG_LIB, "%*sloading feature %s in plugin '%s'",
! 851: indent, "", provide, name);
! 852: free(provide);
! 853: #else
! 854: if (provided->loading)
! 855: {
! 856: return;
! 857: }
! 858: #endif /* USE_FUZZING */
! 859:
! 860: provided->loading = TRUE;
! 861: load_feature(this, provided, level + 1);
! 862: provided->loading = FALSE;
! 863: }
! 864:
! 865: /**
! 866: * Load registered plugin features
! 867: */
! 868: static void load_features(private_plugin_loader_t *this)
! 869: {
! 870: enumerator_t *enumerator, *inner;
! 871: plugin_entry_t *plugin;
! 872: provided_feature_t *provided;
! 873:
! 874: /* we do this in plugin order to allow implicit dependencies to be resolved
! 875: * by reordering plugins */
! 876: enumerator = this->plugins->create_enumerator(this->plugins);
! 877: while (enumerator->enumerate(enumerator, &plugin))
! 878: {
! 879: inner = plugin->features->create_enumerator(plugin->features);
! 880: while (inner->enumerate(inner, &provided))
! 881: {
! 882: load_provided(this, provided, 0);
! 883: }
! 884: inner->destroy(inner);
! 885: }
! 886: enumerator->destroy(enumerator);
! 887: }
! 888:
! 889: /**
! 890: * Register plugin features provided by the given plugin
! 891: */
! 892: static void register_features(private_plugin_loader_t *this,
! 893: plugin_entry_t *entry)
! 894: {
! 895: plugin_feature_t *feature, *reg;
! 896: registered_feature_t *registered, lookup;
! 897: provided_feature_t *provided;
! 898: int count, i;
! 899:
! 900: if (!entry->plugin->get_features)
! 901: { /* feature interface not supported */
! 902: DBG1(DBG_LIB, "plugin '%s' does not provide features, deprecated",
! 903: entry->plugin->get_name(entry->plugin));
! 904: return;
! 905: }
! 906: reg = NULL;
! 907: count = entry->plugin->get_features(entry->plugin, &feature);
! 908: for (i = 0; i < count; i++)
! 909: {
! 910: switch (feature->kind)
! 911: {
! 912: case FEATURE_PROVIDE:
! 913: lookup.feature = feature;
! 914: registered = this->features->get(this->features, &lookup);
! 915: if (!registered)
! 916: {
! 917: INIT(registered,
! 918: .feature = feature,
! 919: .plugins = linked_list_create(),
! 920: );
! 921: this->features->put(this->features, registered, registered);
! 922: }
! 923: INIT(provided,
! 924: .entry = entry,
! 925: .feature = feature,
! 926: .reg = reg,
! 927: .dependencies = count - i,
! 928: );
! 929: registered->plugins->insert_last(registered->plugins,
! 930: provided);
! 931: entry->features->insert_last(entry->features, provided);
! 932: break;
! 933: case FEATURE_REGISTER:
! 934: case FEATURE_CALLBACK:
! 935: reg = feature;
! 936: break;
! 937: default:
! 938: break;
! 939: }
! 940: feature++;
! 941: }
! 942: }
! 943:
! 944: /**
! 945: * Unregister a plugin feature
! 946: */
! 947: static void unregister_feature(private_plugin_loader_t *this,
! 948: provided_feature_t *provided)
! 949: {
! 950: registered_feature_t *registered, lookup;
! 951:
! 952: lookup.feature = provided->feature;
! 953: registered = this->features->get(this->features, &lookup);
! 954: if (registered)
! 955: {
! 956: registered->plugins->remove(registered->plugins, provided, NULL);
! 957: if (registered->plugins->get_count(registered->plugins) == 0)
! 958: {
! 959: this->features->remove(this->features, &lookup);
! 960: registered->plugins->destroy(registered->plugins);
! 961: free(registered);
! 962: }
! 963: else if (registered->feature == provided->feature)
! 964: { /* update feature in case the providing plugin gets unloaded */
! 965: provided_feature_t *first;
! 966:
! 967: registered->plugins->get_first(registered->plugins, (void**)&first);
! 968: registered->feature = first->feature;
! 969: }
! 970: }
! 971: free(provided);
! 972: }
! 973:
! 974: /**
! 975: * Unregister plugin features
! 976: */
! 977: static void unregister_features(private_plugin_loader_t *this,
! 978: plugin_entry_t *entry)
! 979: {
! 980: provided_feature_t *provided;
! 981: enumerator_t *enumerator;
! 982:
! 983: enumerator = entry->features->create_enumerator(entry->features);
! 984: while (enumerator->enumerate(enumerator, &provided))
! 985: {
! 986: entry->features->remove_at(entry->features, enumerator);
! 987: unregister_feature(this, provided);
! 988: }
! 989: enumerator->destroy(enumerator);
! 990: }
! 991:
! 992: /**
! 993: * Remove plugins we were not able to load any plugin features from.
! 994: */
! 995: static void purge_plugins(private_plugin_loader_t *this)
! 996: {
! 997: enumerator_t *enumerator;
! 998: plugin_entry_t *entry;
! 999:
! 1000: enumerator = this->plugins->create_enumerator(this->plugins);
! 1001: while (enumerator->enumerate(enumerator, &entry))
! 1002: {
! 1003: if (!entry->plugin->get_features)
! 1004: { /* feature interface not supported */
! 1005: continue;
! 1006: }
! 1007: if (!entry->features->find_first(entry->features, is_feature_loaded,
! 1008: NULL))
! 1009: {
! 1010: DBG2(DBG_LIB, "unloading plugin '%s' without loaded features",
! 1011: entry->plugin->get_name(entry->plugin));
! 1012: this->plugins->remove_at(this->plugins, enumerator);
! 1013: unregister_features(this, entry);
! 1014: plugin_entry_destroy(entry);
! 1015: }
! 1016: }
! 1017: enumerator->destroy(enumerator);
! 1018: }
! 1019:
! 1020: METHOD(plugin_loader_t, add_static_features, void,
! 1021: private_plugin_loader_t *this, const char *name,
! 1022: plugin_feature_t features[], int count, bool critical,
! 1023: bool (*reload)(void*), void *reload_data)
! 1024: {
! 1025: plugin_entry_t *entry;
! 1026: plugin_t *plugin;
! 1027:
! 1028: plugin = static_features_create(name, features, count, reload, reload_data);
! 1029:
! 1030: INIT(entry,
! 1031: .plugin = plugin,
! 1032: .critical = critical,
! 1033: .features = linked_list_create(),
! 1034: );
! 1035: this->plugins->insert_last(this->plugins, entry);
! 1036: register_features(this, entry);
! 1037: }
! 1038:
! 1039: /**
! 1040: * Tries to find the plugin with the given name in the given path.
! 1041: */
! 1042: static bool find_plugin(char *path, char *name, char *buf, char **file)
! 1043: {
! 1044: struct stat stb;
! 1045:
! 1046: if (path && snprintf(buf, PATH_MAX, "%s/libstrongswan-%s.so",
! 1047: path, name) < PATH_MAX)
! 1048: {
! 1049: if (stat(buf, &stb) == 0)
! 1050: {
! 1051: *file = buf;
! 1052: return TRUE;
! 1053: }
! 1054: }
! 1055: return FALSE;
! 1056: }
! 1057:
! 1058: CALLBACK(find_plugin_cb, bool,
! 1059: char *path, va_list args)
! 1060: {
! 1061: char *name, *buf, **file;
! 1062:
! 1063: VA_ARGS_VGET(args, name, buf, file);
! 1064: return find_plugin(path, name, buf, file);
! 1065: }
! 1066:
! 1067: /**
! 1068: * Used to sort plugins by priority
! 1069: */
! 1070: typedef struct {
! 1071: /* name of the plugin */
! 1072: char *name;
! 1073: /* the plugins priority */
! 1074: int prio;
! 1075: /* default priority */
! 1076: int def;
! 1077: } plugin_priority_t;
! 1078:
! 1079: static void plugin_priority_free(const plugin_priority_t *this, int idx,
! 1080: void *user)
! 1081: {
! 1082: free(this->name);
! 1083: }
! 1084:
! 1085: /**
! 1086: * Sort plugins and their priority by name
! 1087: */
! 1088: static int plugin_priority_cmp_name(const plugin_priority_t *a,
! 1089: const plugin_priority_t *b)
! 1090: {
! 1091: return strcmp(a->name, b->name);
! 1092: }
! 1093:
! 1094: /**
! 1095: * Sort plugins by decreasing priority or default priority then by name
! 1096: */
! 1097: static int plugin_priority_cmp(const plugin_priority_t *a,
! 1098: const plugin_priority_t *b, void *user)
! 1099: {
! 1100: int diff;
! 1101:
! 1102: diff = b->prio - a->prio;
! 1103: if (!diff)
! 1104: { /* the same priority, use default order */
! 1105: diff = b->def - a->def;
! 1106: if (!diff)
! 1107: { /* same default priority (i.e. both were not found in that list) */
! 1108: return strcmp(a->name, b->name);
! 1109: }
! 1110: }
! 1111: return diff;
! 1112: }
! 1113:
! 1114: CALLBACK(plugin_priority_filter, bool,
! 1115: void *null, enumerator_t *orig, va_list args)
! 1116: {
! 1117: plugin_priority_t *prio;
! 1118: char **name;
! 1119:
! 1120: VA_ARGS_VGET(args, name);
! 1121:
! 1122: if (orig->enumerate(orig, &prio))
! 1123: {
! 1124: *name = prio->name;
! 1125: return TRUE;
! 1126: }
! 1127: return FALSE;
! 1128: }
! 1129:
! 1130: /**
! 1131: * Determine the list of plugins to load via load option in each plugin's
! 1132: * config section.
! 1133: */
! 1134: static char *modular_pluginlist(char *list)
! 1135: {
! 1136: enumerator_t *enumerator;
! 1137: array_t *given, *final;
! 1138: plugin_priority_t item, *current, found;
! 1139: char *plugin, *plugins = NULL;
! 1140: int i = 0, max_prio;
! 1141: bool load_def = FALSE;
! 1142:
! 1143: given = array_create(sizeof(plugin_priority_t), 0);
! 1144: final = array_create(sizeof(plugin_priority_t), 0);
! 1145:
! 1146: enumerator = enumerator_create_token(list, " ", " ");
! 1147: while (enumerator->enumerate(enumerator, &plugin))
! 1148: {
! 1149: item.name = strdup(plugin);
! 1150: item.prio = i++;
! 1151: array_insert(given, ARRAY_TAIL, &item);
! 1152: }
! 1153: enumerator->destroy(enumerator);
! 1154: array_sort(given, (void*)plugin_priority_cmp_name, NULL);
! 1155: /* the maximum priority used for plugins not found in this list */
! 1156: max_prio = i + 1;
! 1157:
! 1158: if (lib->settings->get_bool(lib->settings, "%s.load_modular", FALSE,
! 1159: lib->ns))
! 1160: {
! 1161: enumerator = lib->settings->create_section_enumerator(lib->settings,
! 1162: "%s.plugins", lib->ns);
! 1163: }
! 1164: else
! 1165: {
! 1166: enumerator = enumerator_create_filter(array_create_enumerator(given),
! 1167: plugin_priority_filter, NULL, NULL);
! 1168: load_def = TRUE;
! 1169: }
! 1170: while (enumerator->enumerate(enumerator, &plugin))
! 1171: {
! 1172: item.prio = lib->settings->get_int(lib->settings,
! 1173: "%s.plugins.%s.load", 0, lib->ns, plugin);
! 1174: if (!item.prio)
! 1175: {
! 1176: if (!lib->settings->get_bool(lib->settings,
! 1177: "%s.plugins.%s.load", load_def, lib->ns, plugin))
! 1178: {
! 1179: continue;
! 1180: }
! 1181: item.prio = 1;
! 1182: }
! 1183: item.name = plugin;
! 1184: item.def = max_prio;
! 1185: if (array_bsearch(given, &item, (void*)plugin_priority_cmp_name,
! 1186: &found) != -1)
! 1187: {
! 1188: item.def = max_prio - found.prio;
! 1189: }
! 1190: array_insert(final, ARRAY_TAIL, &item);
! 1191: }
! 1192: enumerator->destroy(enumerator);
! 1193:
! 1194: array_sort(final, (void*)plugin_priority_cmp, NULL);
! 1195:
! 1196: plugins = strdup("");
! 1197: enumerator = array_create_enumerator(final);
! 1198: while (enumerator->enumerate(enumerator, ¤t))
! 1199: {
! 1200: char *prev = plugins;
! 1201: if (asprintf(&plugins, "%s %s", plugins ?: "", current->name) < 0)
! 1202: {
! 1203: plugins = prev;
! 1204: break;
! 1205: }
! 1206: free(prev);
! 1207: }
! 1208: enumerator->destroy(enumerator);
! 1209: array_destroy_function(given, (void*)plugin_priority_free, NULL);
! 1210: array_destroy(final);
! 1211: return plugins;
! 1212: }
! 1213:
! 1214: METHOD(plugin_loader_t, load_plugins, bool,
! 1215: private_plugin_loader_t *this, char *list)
! 1216: {
! 1217: enumerator_t *enumerator;
! 1218: char *default_path = NULL, *plugins, *token;
! 1219: bool critical_failed = FALSE;
! 1220:
! 1221: #ifdef PLUGINDIR
! 1222: default_path = PLUGINDIR;
! 1223: #endif /* PLUGINDIR */
! 1224:
! 1225: plugins = modular_pluginlist(list);
! 1226:
! 1227: enumerator = enumerator_create_token(plugins, " ", " ");
! 1228: while (!critical_failed && enumerator->enumerate(enumerator, &token))
! 1229: {
! 1230: plugin_entry_t *entry;
! 1231: bool critical = FALSE;
! 1232: char buf[PATH_MAX], *file = NULL;
! 1233: int len;
! 1234:
! 1235: token = strdup(token);
! 1236: len = strlen(token);
! 1237: if (token[len-1] == '!')
! 1238: {
! 1239: critical = TRUE;
! 1240: token[len-1] = '\0';
! 1241: }
! 1242: if (plugin_loaded(this, token))
! 1243: {
! 1244: free(token);
! 1245: continue;
! 1246: }
! 1247: if (this->paths)
! 1248: {
! 1249: this->paths->find_first(this->paths, find_plugin_cb, NULL, token,
! 1250: buf, &file);
! 1251: }
! 1252: if (!file)
! 1253: {
! 1254: find_plugin(default_path, token, buf, &file);
! 1255: }
! 1256: entry = load_plugin(this, token, file, critical);
! 1257: if (entry)
! 1258: {
! 1259: register_features(this, entry);
! 1260: }
! 1261: else if (critical)
! 1262: {
! 1263: critical_failed = TRUE;
! 1264: DBG1(DBG_LIB, "loading critical plugin '%s' failed", token);
! 1265: }
! 1266: free(token);
! 1267: }
! 1268: enumerator->destroy(enumerator);
! 1269: if (!critical_failed)
! 1270: {
! 1271: load_features(this);
! 1272: if (this->stats.critical > 0)
! 1273: {
! 1274: critical_failed = TRUE;
! 1275: DBG1(DBG_LIB, "failed to load %d critical plugin feature%s",
! 1276: this->stats.critical, this->stats.critical == 1 ? "" : "s");
! 1277: }
! 1278: /* unload plugins that we were not able to load any features for */
! 1279: purge_plugins(this);
! 1280: }
! 1281: if (!critical_failed)
! 1282: {
! 1283: free(this->loaded_plugins);
! 1284: this->loaded_plugins = loaded_plugins_list(this);
! 1285: }
! 1286: if (plugins != list)
! 1287: {
! 1288: free(plugins);
! 1289: }
! 1290: return !critical_failed;
! 1291: }
! 1292:
! 1293: /**
! 1294: * Unload plugin features, they are registered in reverse order
! 1295: */
! 1296: static void unload_features(private_plugin_loader_t *this)
! 1297: {
! 1298: enumerator_t *enumerator;
! 1299: provided_feature_t *provided;
! 1300: plugin_entry_t *entry;
! 1301:
! 1302: enumerator = this->loaded->create_enumerator(this->loaded);
! 1303: while (enumerator->enumerate(enumerator, &provided))
! 1304: {
! 1305: entry = provided->entry;
! 1306: plugin_feature_unload(entry->plugin, provided->feature, provided->reg);
! 1307: this->loaded->remove_at(this->loaded, enumerator);
! 1308: entry->features->remove(entry->features, provided, NULL);
! 1309: unregister_feature(this, provided);
! 1310: }
! 1311: enumerator->destroy(enumerator);
! 1312: }
! 1313:
! 1314: METHOD(plugin_loader_t, unload, void,
! 1315: private_plugin_loader_t *this)
! 1316: {
! 1317: plugin_entry_t *entry;
! 1318:
! 1319: /* unload features followed by plugins, in reverse order */
! 1320: unload_features(this);
! 1321: while (this->plugins->remove_last(this->plugins, (void**)&entry) == SUCCESS)
! 1322: {
! 1323: if (lib->leak_detective)
! 1324: { /* keep handle to report leaks properly */
! 1325: entry->handle = NULL;
! 1326: }
! 1327: unregister_features(this, entry);
! 1328: plugin_entry_destroy(entry);
! 1329: }
! 1330: free(this->loaded_plugins);
! 1331: this->loaded_plugins = NULL;
! 1332: memset(&this->stats, 0, sizeof(this->stats));
! 1333: }
! 1334:
! 1335: METHOD(plugin_loader_t, add_path, void,
! 1336: private_plugin_loader_t *this, char *path)
! 1337: {
! 1338: if (!this->paths)
! 1339: {
! 1340: this->paths = linked_list_create();
! 1341: }
! 1342: this->paths->insert_last(this->paths, strdupnull(path));
! 1343: }
! 1344:
! 1345: /**
! 1346: * Reload a plugin by name, NULL for all
! 1347: */
! 1348: static u_int reload_by_name(private_plugin_loader_t *this, char *name)
! 1349: {
! 1350: u_int reloaded = 0;
! 1351: enumerator_t *enumerator;
! 1352: plugin_t *plugin;
! 1353:
! 1354: enumerator = create_plugin_enumerator(this);
! 1355: while (enumerator->enumerate(enumerator, &plugin, NULL))
! 1356: {
! 1357: if (name == NULL || streq(name, plugin->get_name(plugin)))
! 1358: {
! 1359: if (plugin->reload && plugin->reload(plugin))
! 1360: {
! 1361: DBG2(DBG_LIB, "reloaded configuration of '%s' plugin",
! 1362: plugin->get_name(plugin));
! 1363: reloaded++;
! 1364: }
! 1365: }
! 1366: }
! 1367: enumerator->destroy(enumerator);
! 1368: return reloaded;
! 1369: }
! 1370:
! 1371: METHOD(plugin_loader_t, reload, u_int,
! 1372: private_plugin_loader_t *this, char *list)
! 1373: {
! 1374: u_int reloaded = 0;
! 1375: enumerator_t *enumerator;
! 1376: char *name;
! 1377:
! 1378: if (list == NULL)
! 1379: {
! 1380: return reload_by_name(this, NULL);
! 1381: }
! 1382: enumerator = enumerator_create_token(list, " ", "");
! 1383: while (enumerator->enumerate(enumerator, &name))
! 1384: {
! 1385: reloaded += reload_by_name(this, name);
! 1386: }
! 1387: enumerator->destroy(enumerator);
! 1388: return reloaded;
! 1389: }
! 1390:
! 1391: METHOD(plugin_loader_t, loaded_plugins, char*,
! 1392: private_plugin_loader_t *this)
! 1393: {
! 1394: return this->loaded_plugins ?: "";
! 1395: }
! 1396:
! 1397: METHOD(plugin_loader_t, status, void,
! 1398: private_plugin_loader_t *this, level_t level)
! 1399: {
! 1400: if (this->loaded_plugins)
! 1401: {
! 1402: dbg(DBG_LIB, level, "loaded plugins: %s", this->loaded_plugins);
! 1403:
! 1404: if (this->stats.failed)
! 1405: {
! 1406: DBG2(DBG_LIB, "unable to load %d plugin feature%s (%d due to unmet "
! 1407: "dependencies)", this->stats.failed,
! 1408: this->stats.failed == 1 ? "" : "s", this->stats.depends);
! 1409: }
! 1410: }
! 1411: }
! 1412:
! 1413: METHOD(plugin_loader_t, destroy, void,
! 1414: private_plugin_loader_t *this)
! 1415: {
! 1416: unload(this);
! 1417: this->features->destroy(this->features);
! 1418: this->loaded->destroy(this->loaded);
! 1419: this->plugins->destroy(this->plugins);
! 1420: DESTROY_FUNCTION_IF(this->paths, free);
! 1421: free(this->loaded_plugins);
! 1422: free(this);
! 1423: }
! 1424:
! 1425: /*
! 1426: * see header file
! 1427: */
! 1428: plugin_loader_t *plugin_loader_create()
! 1429: {
! 1430: private_plugin_loader_t *this;
! 1431:
! 1432: INIT(this,
! 1433: .public = {
! 1434: .add_static_features = _add_static_features,
! 1435: .load = _load_plugins,
! 1436: .add_path = _add_path,
! 1437: .reload = _reload,
! 1438: .unload = _unload,
! 1439: .create_plugin_enumerator = _create_plugin_enumerator,
! 1440: .has_feature = _has_feature,
! 1441: .loaded_plugins = _loaded_plugins,
! 1442: .status = _status,
! 1443: .destroy = _destroy,
! 1444: },
! 1445: .plugins = linked_list_create(),
! 1446: .loaded = linked_list_create(),
! 1447: .features = hashtable_create(
! 1448: (hashtable_hash_t)registered_feature_hash,
! 1449: (hashtable_equals_t)registered_feature_equals, 64),
! 1450: );
! 1451:
! 1452: return &this->public;
! 1453: }
! 1454:
! 1455: /*
! 1456: * See header
! 1457: */
! 1458: void plugin_loader_add_plugindirs(char *basedir, char *plugins)
! 1459: {
! 1460: enumerator_t *enumerator;
! 1461: char *name, path[PATH_MAX], dir[64];
! 1462:
! 1463: enumerator = enumerator_create_token(plugins, " ", "!");
! 1464: while (enumerator->enumerate(enumerator, &name))
! 1465: {
! 1466: snprintf(dir, sizeof(dir), "%s", name);
! 1467: translate(dir, "-", "_");
! 1468: snprintf(path, sizeof(path), "%s/%s/.libs", basedir, dir);
! 1469: lib->plugins->add_path(lib->plugins, path);
! 1470: }
! 1471: enumerator->destroy(enumerator);
! 1472: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>