Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 10:17:35

0001 
0002 // Copyright 2020, Jefferson Science Associates, LLC.
0003 // Subject to the terms in the LICENSE file found in the top-level directory.
0004 
0005 
0006 #include "JPluginLoader.h"
0007 #include "JComponentManager.h"
0008 #include "JParameterManager.h"
0009 #include <JANA/JVersion.h>
0010 
0011 #include <cstdlib>
0012 #include <dlfcn.h>
0013 #include <iostream>
0014 #include <sstream>
0015 #include <unistd.h>
0016 #include <set>
0017 #include <memory>
0018 
0019 class JApplication;
0020 
0021 void JPluginLoader::Init() {
0022 
0023     m_params->SetDefaultParameter("plugins", m_plugins_from_parameter, "Comma-separated list of plugins to load.");
0024     m_params->SetDefaultParameter("plugins_to_ignore", m_plugins_to_exclude, "Comma-separated list of plugins to NOT load, even if they are specified in 'plugins'.");
0025     m_params->SetDefaultParameter("jana:plugin_path", m_plugin_paths_str, "Colon-separated list of paths to search for plugins");
0026     m_params->SetDefaultParameter("jana:debug_plugin_loading", m_verbose, "Trace the plugin search path and display any loading errors");
0027 
0028     if (m_verbose) {
0029         // The jana:debug_plugin_loading parameter is kept around for backwards compatibility
0030         // at least for now
0031         GetLogger().level = JLogger::Level::TRACE;
0032     }
0033 }
0034 
0035 
0036 
0037 void JPluginLoader::add_plugin(std::string plugin_name) {
0038     /// Add the specified plugin to the list of plugins to be attached. This only records 
0039     /// the plugin name. The plugin is not actually attached until AttachPlugins() is called during
0040     /// JApplication::Initialize(). Note that any plugins specified by the 'plugins' parameter
0041     /// are loaded _after_ any plugins specified via 'add_plugin'. This lets the maintainers
0042     /// of a project specify a minimum set of plugins that always get loaded, and enforce that 
0043     /// their loading order. This also prevents users from clobbering the minimum plugin set, unless
0044     /// they explicitly intend to, in which case they should use the 'plugins_to_ignore' parameter.
0045     ///
0046     /// @param plugin_name name of the plugin. Do not include the ".so" or ".dylib" suffix in the name.
0047     ///                    The path to the plugin will be searched first from the 'jana:plugin_path' parameter,
0048     ///                    followed by the JANA_PLUGIN_PATH envar, followed by the JANA install prefix.
0049     ///
0050     m_all_plugins_requested.push(plugin_name);
0051 }
0052 
0053 void JPluginLoader::resolve_plugin_paths() {
0054     // Build our list of plugin search paths.
0055  
0056     // 1. First we look for plugins in locations specified via parameters. (Colon-separated)
0057     std::stringstream param_ss(m_plugin_paths_str);
0058     std::string path;
0059     while (getline(param_ss, path, ':')) add_plugin_path(path);
0060 
0061     // 2. Next we look for plugins in locations specified via environment variable. (Colon-separated)
0062     if (const char* jpp = getenv("JANA_PLUGIN_PATH")) {
0063         std::stringstream envvar_ss(jpp);
0064         while (getline(envvar_ss, path, ':')) add_plugin_path(path);
0065     }
0066 
0067     // 3. Finally we look in the JANA install directory.
0068     add_plugin_path(JVersion::GetInstallDir() + "/lib/JANA/plugins");
0069 }
0070 
0071 
0072 void JPluginLoader::add_plugin_path(std::string path) {
0073 
0074     /// Add a path to the directories searched for plugins. This
0075     /// should not include the plugin name itself. This only has
0076     /// an effect when called before AttachPlugins is called
0077     /// (i.e. before Run is called).
0078     /// n.b. if this is called with a path already in the list,
0079     /// then the call is silently ignored.
0080     ///
0081     /// Generally, users will set the path via the JANA_PLUGIN_PATH
0082     /// environment variable and won't need to call this method. This
0083     /// may be called if it needs to be done programmatically.
0084     ///
0085     /// @param path directory to search for plugins.
0086     ///
0087     for (std::string& n : m_plugin_paths) {
0088         if (n == path) {
0089             return;
0090         }
0091     }
0092     m_plugin_paths.push_back(path);
0093 }
0094 
0095 
0096 void JPluginLoader::attach_plugins(JComponentManager* jcm) {
0097     /// Loop over list of plugin names added via AddPlugin() and
0098     /// actually attach and initialize them. See AddPlugin method
0099     /// for more.
0100     
0101     // Figure out the search paths for plugins. For now, plugins _cannot_ add to the search paths
0102     resolve_plugin_paths();
0103 
0104     // Figure out the set of plugins to exclude. Note that this applies to plugins added
0105     // by other plugins as well.
0106     std::set<std::string> exclusions(m_plugins_to_exclude.begin(), m_plugins_to_exclude.end());
0107 
0108     // Loop over all requested plugins as per the `plugins` parameter. Note that this vector may grow as 
0109     // plugins themselves request additional plugins. These get appended to the back of `m_plugins_to_include`.
0110     
0111     // Add the contents of the "plugins" parameter to the back of the requested plugins queue.
0112     for (const auto& plugin_name : m_plugins_from_parameter) {
0113         m_all_plugins_requested.push(plugin_name);
0114     }
0115 
0116     while (!m_all_plugins_requested.empty()) {
0117 
0118         const std::string name = m_all_plugins_requested.front();
0119         m_all_plugins_requested.pop();
0120 
0121         std::ostringstream paths_checked;
0122 
0123         if (!is_valid_plugin_name(name)) {
0124             throw JException("Invalid plugin name: '%s'. Should not be a path or have an extension.", name.c_str());
0125         }
0126 
0127         if (exclusions.find(name) != exclusions.end()) {
0128             LOG_INFO(m_logger) << "Excluding plugin `" << name << "`" << LOG_END;
0129             continue;
0130         }
0131         if (m_plugin_index.find(name) != m_plugin_index.end()) {
0132             // Make sure each plugin is only loaded once (whether provided as a path or not)
0133             LOG_DEBUG(m_logger) << "Ignoring already-loaded plugin `" << name << "`" << LOG_END;
0134             continue;
0135         }
0136 
0137         std::string path = find_first_valid_path(name, paths_checked);
0138         // User didn't provide a path, so we have to search
0139         // If no valid paths found, `path` variable stays empty
0140 
0141         if (path.empty()) {
0142             LOG_ERROR(m_logger) 
0143                 << "Couldn't find plugin '" << name << "'\n" 
0144                 << "  Make sure that the plugin search path is correctly set using the 'jana:plugin_path' parameter or the JANA_PLUGIN_PATH environment variable.\n"
0145                 << "  Paths checked:\n" << paths_checked.str() << LOG_END;
0146             throw JException("Couldn't find plugin '%s'", name.c_str());
0147         }
0148 
0149         // At this point, the plugin has been found, and we are going to try to attach it. 
0150         // If the attachment fails for any reason, so does attach_plugins() and ultimately JApplication::Initialize().
0151         // We do not attempt to search for non-failing plugins by looking further down the search path, because this 
0152         // masks the "root cause" error and causes much greater confusion. A specific example is working in an environment
0153         // that has a read-only system install of JANA, e.g. Singularity or CVMFS. If the host application is built using
0154         // a newer version of JANA which is not binary-compatible with the system install, and both locations end up on the 
0155         // plugin search path, a legitimate error loading the correct plugin would be suppressed, and the user would instead
0156         // see an extremely difficult-to-debug error (usually a segfault) stemming from the binary incompatibility 
0157         // between the host application and the plugin.
0158 
0159         jcm->next_plugin(name);
0160         attach_plugin(name, path); // Throws JException on failure
0161     }
0162 }
0163 
0164 
0165 void JPluginLoader::attach_plugin(std::string name, std::string path) {
0166 
0167     /// Attach a plugin by opening the shared object file and running the
0168     /// InitPlugin_t(JApplication* app) global C-style routine in it.
0169     /// An exception will be thrown if the plugin is not successfully opened.
0170     /// Users will not need to call this directly since it is called automatically
0171     /// from Initialize().
0172 
0173     // Open shared object
0174     dlerror(); // Clear any earlier dlerrors
0175     void* handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_GLOBAL | RTLD_NODELETE);
0176     if (!handle) {
0177         std::string err = dlerror();
0178         LOG_ERROR(m_logger) << "Plugin \"" << name << "\" dlopen() failed: " << err << LOG_END;
0179         throw JException("Plugin '%s' dlopen() failed: %s", name.c_str(), err.c_str());
0180     }
0181 
0182     // Retrieve the InitPlugin symbol
0183     typedef void InitPlugin_t(JApplication* app);
0184     InitPlugin_t* initialize_proc = (InitPlugin_t*) dlsym(handle, "InitPlugin");
0185     if (!initialize_proc) {
0186         dlclose(handle);
0187         LOG_ERROR(m_logger) << "Plugin \"" << name << "\" is missing 'InitPlugin' symbol" << LOG_END;
0188         throw JException("Plugin '%s' is missing 'InitPlugin' symbol", name.c_str());
0189     }
0190 
0191     // Run InitPlugin() and wrap exceptions as needed
0192     LOG_WARN(m_logger) << "Loading plugin '" << name << "' from '" << path << "'" << LOG_END;
0193 
0194     try {
0195         (*initialize_proc)(GetApplication());
0196     }
0197     catch (JException& ex) {
0198         if (ex.function_name.empty()) ex.function_name = "attach_plugin";
0199         if (ex.type_name.empty()) ex.type_name = "JPluginLoader";
0200         if (ex.instance_name.empty()) ex.instance_name = m_prefix;
0201         if (ex.plugin_name.empty()) ex.plugin_name = name;
0202         throw ex;
0203     }
0204     catch (std::exception& e) {
0205         auto ex = JException(e.what());
0206         ex.exception_type = JTypeInfo::demangle_current_exception_type();
0207         ex.nested_exception = std::current_exception();
0208         ex.function_name = "attach_plugin";
0209         ex.type_name = "JPluginLoader";
0210         ex.instance_name = m_prefix;
0211         ex.plugin_name = name;
0212         throw ex;
0213     }
0214     catch (...) {
0215         auto ex = JException("Unknown exception");
0216         ex.exception_type = JTypeInfo::demangle_current_exception_type();
0217         ex.nested_exception = std::current_exception();
0218         ex.function_name = "attach_plugin";
0219         ex.type_name = "JPluginLoader";
0220         ex.instance_name = m_prefix;
0221         ex.plugin_name = name;
0222         throw ex;
0223     }
0224 
0225     // Do some bookkeeping
0226     auto plugin = std::make_unique<JPlugin>(name, path);
0227     plugin->m_app = GetApplication();
0228     plugin->m_logger = m_logger;
0229     plugin->m_handle = handle;
0230     m_plugin_index[name] = plugin.get();
0231     m_plugins.push_front(std::move(plugin)); 
0232 }
0233 
0234 
0235 JPlugin::~JPlugin() {
0236 
0237     // Call FinalizePlugin()
0238     typedef void FinalizePlugin_t(JApplication* app);
0239     FinalizePlugin_t* finalize_proc = (FinalizePlugin_t*) dlsym(m_handle, "FinalizePlugin");
0240     if (finalize_proc) {
0241         LOG_INFO(m_logger) << "Finalizing plugin \"" << m_name << "\"" << LOG_END;
0242         (*finalize_proc)(m_app);
0243     }
0244 
0245     // Close plugin handle
0246     dlclose(m_handle);
0247     LOG_DEBUG(m_logger) << "Unloaded plugin \"" << m_name << "\"" << LOG_END;
0248 }
0249 
0250 bool JPluginLoader::is_valid_plugin_name(const std::string& plugin_name) const {
0251     
0252     if (plugin_name.size() > 2 && plugin_name.substr(plugin_name.size() - 3) == ".so") return false;
0253     if (plugin_name.size() > 5 && plugin_name.substr(plugin_name.size() - 6) == ".dylib") return false;
0254     if (plugin_name.find('/') != std::string::npos) return false;
0255     return true;
0256 }
0257 
0258 bool ends_with(const std::string& s, char c) {
0259     // Ideally we would just do s.ends_with(c), but we want to be C++17 compatible
0260     if (s.empty()) return false;
0261     return s.back() == c;
0262 }
0263 
0264 std::string JPluginLoader::make_path_from_name(const std::string& name, const std::string& path_prefix) const {
0265     std::ostringstream oss;
0266     oss << path_prefix;
0267     if (!ends_with(path_prefix, '/')) {
0268         oss << "/";
0269     }
0270     oss << name;
0271     oss << ".so";
0272     return oss.str();
0273 }
0274 
0275 std::string JPluginLoader::find_first_valid_path(const std::string& name, std::ostringstream& debug_log) const {
0276 
0277     for (const std::string& path_prefix : m_plugin_paths) {
0278         auto path = make_path_from_name(name, path_prefix);
0279 
0280         if (is_valid_path(path)) {
0281             debug_log << "    " << path << " => Found" << std::endl;
0282             return path;
0283         }
0284         else {
0285             debug_log << "    " << path << " => Not found" << std::endl;
0286         }
0287     }
0288     return "";
0289 }
0290 
0291 
0292 bool JPluginLoader::is_valid_path(const std::string& path) const {
0293     return (access(path.c_str(), F_OK) != -1);
0294 }
0295 
0296