From 751e239d58f7382799561190369aa7b480dc6db3 Mon Sep 17 00:00:00 2001 From: Philip Rauwolf Date: Mon, 8 Jul 2013 16:55:56 +0200 Subject: Introduced dynamic loading of middleware bindings and other generic libraries. Dynamic loading of middleware libraries including possibilities to configure the loading process now is available. Also added utility functions on CommonAPI level to support loading of libraries of generated code / other generic libraries. Configuration now has its own source module. Added several unit tests to confirm correctness. gitignore, README and inline documentation updated accordingly. Change-Id: Ia11d91a5f8de5b8bbb2ae9844324f050a926579e --- src/CommonAPI/CommonAPI.h | 1 + src/CommonAPI/Configuration.cpp | 166 ++++++++++++++++++++ src/CommonAPI/Configuration.h | 121 ++++++++++++++ src/CommonAPI/Factory.h | 1 + src/CommonAPI/Factory.hpp | 10 +- src/CommonAPI/MiddlewareInfo.h | 17 +- src/CommonAPI/Runtime.cpp | 334 ++++++++++++++++++++++++++++++++++++--- src/CommonAPI/Runtime.h | 98 +++++++++--- src/CommonAPI/utils.h | 340 ++++++++++++++++++++++++++++++++++++++++ 9 files changed, 1033 insertions(+), 55 deletions(-) create mode 100644 src/CommonAPI/Configuration.cpp create mode 100644 src/CommonAPI/Configuration.h create mode 100644 src/CommonAPI/utils.h (limited to 'src') diff --git a/src/CommonAPI/CommonAPI.h b/src/CommonAPI/CommonAPI.h index f72d1d2..e9beb77 100644 --- a/src/CommonAPI/CommonAPI.h +++ b/src/CommonAPI/CommonAPI.h @@ -14,6 +14,7 @@ #include "Runtime.h" #include "Factory.h" #include "AttributeExtension.h" +#include "ByteBuffer.h" #include "types.h" #undef COMMONAPI_INTERNAL_COMPILATION diff --git a/src/CommonAPI/Configuration.cpp b/src/CommonAPI/Configuration.cpp new file mode 100644 index 0000000..470015d --- /dev/null +++ b/src/CommonAPI/Configuration.cpp @@ -0,0 +1,166 @@ +/* Copyright (C) 2013 BMW Group + * Author: Manfred Bathelt (manfred.bathelt@bmw.de) + * Author: Juergen Gehring (juergen.gehring@bmw.de) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include + +#include "Configuration.h" +#include "utils.h" + + +namespace CommonAPI { + +std::unordered_map knownMiddlewareAliases_; +std::unordered_map knownMiddlewarePaths_; +std::unordered_map > knownGeneratedPaths_; +std::vector librarySearchPaths_; +std::string defaultBindingIdentifier_ = ""; + + +const Configuration& Configuration::getInstance() { + static Configuration* instance = NULL; + if (!instance) { + instance = new Configuration(); + instance->retrieveCommonApiConfiguration(); + } + return *instance; +} + +const std::vector& Configuration::getLibrarySearchPaths() const { + return librarySearchPaths_; +} + +const std::string& Configuration::getMiddlewareNameForAlias(const std::string& alias) const { + auto foundMiddlewareName = knownMiddlewareAliases_.find(alias); + if (foundMiddlewareName != knownMiddlewareAliases_.end()) { + return foundMiddlewareName->second; + } else { + return alias; + } +} + +const std::string& Configuration::getMiddlewareLibraryPath(const std::string& middlewareIdentifier) const { + auto foundMiddlewarePath = knownMiddlewarePaths_.find(middlewareIdentifier); + if (foundMiddlewarePath == knownMiddlewarePaths_.end()) { + static const std::string emptyReturn = ""; + return emptyReturn; + } + return foundMiddlewarePath->second; +} + +const std::vector& Configuration::getGenericLibraryPaths(const std::string& middlewareIdentifier) const { + const auto& generatedPathsForMiddleware = knownGeneratedPaths_.find(middlewareIdentifier); + if (generatedPathsForMiddleware != knownGeneratedPaths_.end()) { + return generatedPathsForMiddleware->second; + } + static const std::vector emptyReturn; + return emptyReturn; +} + +const std::string& Configuration::getDefaultMiddlewareIdentifier() const { + return defaultBindingIdentifier_; +} + +void Configuration::readConfigFile(std::ifstream& addressConfigFile) { + std::string currentlyParsedBindingIdentifier = ""; + bool endFile = false; + + std::string readLine; + + while (addressConfigFile.good()) { + getline(addressConfigFile, readLine); + const size_t readLineLength = readLine.length(); + + if (strncmp(readLine.c_str(), CATEGORY_IDENTIFIER_BINDING, strlen(CATEGORY_IDENTIFIER_BINDING)) == 0 + && readLine[readLineLength - 1] == CATEGORY_ENDING) { + + std::string newBindingIdentifier = readLine.substr( + strlen(CATEGORY_IDENTIFIER_BINDING), + readLineLength - (strlen(CATEGORY_IDENTIFIER_BINDING) + 1)); + + trim(newBindingIdentifier); + if (containsOnlyAlphanumericCharacters(newBindingIdentifier)) { + currentlyParsedBindingIdentifier = newBindingIdentifier; + } + + } else if (currentlyParsedBindingIdentifier != "") { + std::vector parameterElements = split(readLine, '='); + if (parameterElements.size() == 2) { + + if (parameterElements.at(0) == BINDING_PARAMETER_ALIAS) { + std::vector aliases = split(parameterElements.at(1), ':'); + for (const std::string& singleAlias: aliases) { + knownMiddlewareAliases_.insert( {singleAlias, currentlyParsedBindingIdentifier}); + } + + } else if (parameterElements.at(0) == BINDING_PARAMETER_LIBPATH) { + knownMiddlewarePaths_.insert( {currentlyParsedBindingIdentifier, parameterElements.at(1)}); + + } else if (parameterElements.at(0) == BINDING_PARAMETER_GENPATH) { + std::vector paths = split(parameterElements.at(1), ':'); + auto alreadyKnownPaths = knownGeneratedPaths_.find(currentlyParsedBindingIdentifier); + + if (alreadyKnownPaths == knownGeneratedPaths_.end()) { + const std::vector pathSet(paths.begin(), paths.end()); + knownGeneratedPaths_.insert( {currentlyParsedBindingIdentifier, std::move(pathSet)} ); + } else { + alreadyKnownPaths->second.insert(alreadyKnownPaths->second.end(), paths.begin(), paths.end()); + } + } + + } else if (parameterElements.size() == 1) { + if (parameterElements.at(0) == BINDING_PARAMETER_DEFAULT && defaultBindingIdentifier_ == "") { + defaultBindingIdentifier_ = currentlyParsedBindingIdentifier; + } + } + } + } +} + + +void Configuration::readEnvironmentVariables() { + librarySearchPaths_ = split(COMMONAPI_STD_LIB_PATH, ':'); + + const char* environmentBindingPath = getenv(COMMONAPI_ENVIRONMENT_BINDING_PATH); + if (environmentBindingPath) { + std::vector environmentPaths = split(environmentBindingPath, ':'); + librarySearchPaths_.insert(librarySearchPaths_.begin(), environmentPaths.begin(), environmentPaths.end()); + } +} + + +void Configuration::retrieveCommonApiConfiguration() { + readEnvironmentVariables(); + + std::string fqnOfConfigFile = getCurrentBinaryFileFQN(); + fqnOfConfigFile += COMMONAPI_CONFIG_SUFFIX; + + std::ifstream commonapiConfigFile; + commonapiConfigFile.open(fqnOfConfigFile.c_str()); + + if (commonapiConfigFile.is_open()) { + readConfigFile(commonapiConfigFile); + commonapiConfigFile.close(); + } + + commonapiConfigFile.clear(); + std::vector splittedConfigFQN = split(fqnOfConfigFile, '/'); + std::string globalConfigFQN = COMMONAPI_GLOBAL_CONFIG_ROOT + splittedConfigFQN.at(splittedConfigFQN.size() - 1); + commonapiConfigFile.open(globalConfigFQN); + if (commonapiConfigFile.is_open()) { + readConfigFile(commonapiConfigFile); + commonapiConfigFile.close(); + } + commonapiConfigFile.clear(); + + commonapiConfigFile.open(COMMONAPI_GLOBAL_CONFIG_FQN); + if (commonapiConfigFile.is_open()) { + readConfigFile(commonapiConfigFile); + commonapiConfigFile.close(); + } +} + + +} // namespace CommonAPI diff --git a/src/CommonAPI/Configuration.h b/src/CommonAPI/Configuration.h new file mode 100644 index 0000000..554518e --- /dev/null +++ b/src/CommonAPI/Configuration.h @@ -0,0 +1,121 @@ +/* Copyright (C) 2013 BMW Group + * Author: Manfred Bathelt (manfred.bathelt@bmw.de) + * Author: Juergen Gehring (juergen.gehring@bmw.de) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef COMMONAPI_CONFIGURATION_H_ +#define COMMONAPI_CONFIGURATION_H_ + + +#include +#include +#include +#include + + +namespace CommonAPI { + + +static const char COMMONAPI_CONFIG_SUFFIX[] = ".conf"; +static const char COMMONAPI_GLOBAL_CONFIG_ROOT[] = "/etc/CommonAPI/"; +static const char COMMONAPI_GLOBAL_CONFIG_FQN[] = "/etc/CommonAPI/CommonAPI.conf"; + +static const char COMMONAPI_STD_LIB_PATH[] = "/usr/lib:/usr/local/lib/"; +static const char COMMONAPI_ENVIRONMENT_BINDING_PATH[] = "COMMONAPI_BINDING_PATH"; + +static const char CATEGORY_ENDING = '}'; + +static const char CATEGORY_IDENTIFIER_BINDING[] = "{binding:"; + +static const char BINDING_PARAMETER_ALIAS[] = "alias"; +static const char BINDING_PARAMETER_LIBPATH[] = "libpath"; +static const char BINDING_PARAMETER_GENPATH[] = "genpath"; +static const char BINDING_PARAMETER_DEFAULT[] = "default"; + + +/** + * Represents the contents of all parsed CommonAPI Configuration files. + * + * For more information on how to configure CommonAPI, see attached documentation. + */ +class Configuration { + public: + /** + * Returns the instance of the Configuration. + * + * When first calling this method, all configuration files that are found are parsed and + * the values are stored within this class. + * + * @return The singleton instance of the CommonAPI Configuration. + */ + static const Configuration& getInstance(); + + Configuration(const Configuration&) = delete; + Configuration& operator=(const Configuration&) = delete; + Configuration(Configuration&&) = delete; + Configuration& operator=(Configuration&&) = delete; + + /** + * Returns the search paths on which binding specific libraries may be found. + * + * Default search paths are /usr/lib and /usr/local/lib, those two will always be returned. + * If additional search paths have been configured, those will also be returned. + * + * @return + */ + const std::vector& getLibrarySearchPaths() const; + + /** + * Returns the actual middleware identifier for the given alias. + * + * If no such alias has been configured, the given alias itself will be returned. + * + * @return The middleware identifier or the given alias itself, if no mapping to a middleware identifier was found. + */ + const std::string& getMiddlewareNameForAlias(const std::string& alias) const; + + /** + * Returns the specified library path for the given middleware identifier. + * + * If a path to a specific library has been configured for the given middleware identifier, this path will be returned. + * If no such path has been configured, the empty string will be returned. + * + * @return The path to the middleware library, if any is known, the empty string "" otherwise. + */ + const std::string& getMiddlewareLibraryPath(const std::string& middlewareIdentifier) const; + + /** + * Returns the paths to other generic libraries configured for a specific binding. + * + * This function is meant to be called by middleware libraries. Will return all configured paths to + * generic libraries. You likely wil want to use the utility functions provided in + * to do the loading. To arrange and time the loading is responsibility of the middleware only. + * + * @return A vector containing all generic libraries associated with the given middlewareIdentifier. + */ + const std::vector& getGenericLibraryPaths(const std::string& middlewareIdentifier) const; + + /** + * Returns the configured default middleware identifier. + * + * If no default has been configured, the empty string "" will be returned. + * + * @return The configured default middleware identifier. + */ + const std::string& getDefaultMiddlewareIdentifier() const; + + private: + Configuration() = default; + + void readConfigFile(std::ifstream& addressConfigFile); + void retrieveCommonApiConfiguration(); + void readEnvironmentVariables(); +}; + + + +} // namespace CommonAPI + +#endif /* COMMONAPI_CONFIGURATION_H_ */ diff --git a/src/CommonAPI/Factory.h b/src/CommonAPI/Factory.h index ede8536..91bafda 100644 --- a/src/CommonAPI/Factory.h +++ b/src/CommonAPI/Factory.h @@ -23,6 +23,7 @@ #include "Proxy.h" #include "Stub.h" #include "types.h" +#include "utils.h" namespace CommonAPI { diff --git a/src/CommonAPI/Factory.hpp b/src/CommonAPI/Factory.hpp index 2f481f5..6ba73f6 100644 --- a/src/CommonAPI/Factory.hpp +++ b/src/CommonAPI/Factory.hpp @@ -19,7 +19,10 @@ Factory::buildProxy(const std::string& participantId, const std::string& domain) { std::shared_ptr abstractMiddlewareProxy = createProxy(_ProxyClass<_AttributeExtensions...>::getInterfaceId(), participantId, serviceName, domain); - return std::make_shared<_ProxyClass<_AttributeExtensions...>>(abstractMiddlewareProxy); + if (abstractMiddlewareProxy) { + return std::make_shared<_ProxyClass<_AttributeExtensions...>>(abstractMiddlewareProxy); + } + return NULL; } template class _ProxyClass, typename ... _AttributeExtensions > @@ -43,7 +46,10 @@ Factory::buildProxyWithDefaultAttributeExtension(const std::string& participantI const std::string& domain) { std::shared_ptr abstractMiddlewareProxy = createProxy(DefaultAttributeProxyFactoryHelper<_ProxyClass, _AttributeExtension>::class_t::getInterfaceId(), participantId, serviceName, domain); - return std::make_shared::class_t>(abstractMiddlewareProxy); + if (abstractMiddlewareProxy) { + return std::make_shared::class_t>(abstractMiddlewareProxy); + } + return NULL; } template class _ProxyClass, template class _AttributeExtension> diff --git a/src/CommonAPI/MiddlewareInfo.h b/src/CommonAPI/MiddlewareInfo.h index 6b69691..ffbc21c 100644 --- a/src/CommonAPI/MiddlewareInfo.h +++ b/src/CommonAPI/MiddlewareInfo.h @@ -23,31 +23,16 @@ namespace CommonAPI { class Runtime; -inline int FNV1aHash(const char* s) { - const int FNV_offset_basis = 2166136261u; - const int FNV_prime = 16777619; - - int hashValue = FNV_offset_basis; - for (unsigned int i = 0; i < strlen(s); i++) { - hashValue = (hashValue ^ s[i]) * FNV_prime; - } - return hashValue; -} - - typedef std::shared_ptr (*MiddlewareRuntimeLoadFunction) (); struct MiddlewareInfo { const char* middlewareName_; - const int middlewareId_; MiddlewareRuntimeLoadFunction getInstance_; MiddlewareInfo(const char* middlewareName, MiddlewareRuntimeLoadFunction middlewareRuntimeLoadFunction): middlewareName_(middlewareName), - middlewareId_(FNV1aHash(middlewareName)), - getInstance_(middlewareRuntimeLoadFunction) { -} + getInstance_(middlewareRuntimeLoadFunction) {} }; diff --git a/src/CommonAPI/Runtime.cpp b/src/CommonAPI/Runtime.cpp index f5a8096..83ef63d 100644 --- a/src/CommonAPI/Runtime.cpp +++ b/src/CommonAPI/Runtime.cpp @@ -4,52 +4,350 @@ * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "Runtime.h" +#include +#include +#include +#include +#include #include +#include +#include + +#include "Runtime.h" +#include "Configuration.h" +#include "utils.h" namespace CommonAPI { -std::unordered_map* registeredRuntimeLoadFunctions_; +static std::unordered_map* registeredRuntimeLoadFunctions_; +static bool isDynamic_ = false; +static const char COMMONAPI_LIB_PREFIX[] = "libCommonAPI-"; +static const char MIDDLEWARE_INFO_SYMBOL_NAME[] = "middlewareInfo"; -void Runtime::registerRuntimeLoader(std::string middlewareName, MiddlewareRuntimeLoadFunction middlewareRuntimeLoadFunction) { - if(!registeredRuntimeLoadFunctions_) { + +inline bool Runtime::tryLoadLibrary(const std::string& libraryPath, void** sharedLibraryHandle, MiddlewareInfo** foundMiddlewareInfo) { + //In case we find an already loaded library again while looking for another one, + //there is no need to look at it + if (dlopen(libraryPath.c_str(), RTLD_NOLOAD)) { + return false; + } + //In order to place symbols of the newly loaded library ahead of already resolved symbols, we need + //RTLD_DEEPBIND. This is necessary for this case: A library already is linked at compile time, but while + //trying to resolve another library dynamically we might find the very same library again. + //dlopen() doesn't know about the compile time linked library and will close it if dlclose() ever is + //called, thereby causing memory corruptions and the like. Therefore, we must be able to access the + //middlewareInfo of the newly dlopened library in order to determine whether it already has been linked. + *sharedLibraryHandle = dlopen(libraryPath.c_str(), RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND); + if (sharedLibraryHandle == NULL) { + return false; + } + + *foundMiddlewareInfo = static_cast(dlsym(*sharedLibraryHandle, MIDDLEWARE_INFO_SYMBOL_NAME)); + + //In this context, a resolved value of NULL it is just as invalid as if dlerror() was set. + if (!*foundMiddlewareInfo) { + dlclose(*sharedLibraryHandle); + return false; + } + + if (!(*foundMiddlewareInfo)->middlewareName_ || !(*foundMiddlewareInfo)->getInstance_) { + dlclose(sharedLibraryHandle); + return false; + } + + return true; +} + + +bool Runtime::checkAndLoadLibrary(const std::string& libraryPath, const std::string& requestedBindingIdentifier, bool keepLibrary) { + void* sharedLibraryHandle = NULL; + MiddlewareInfo* foundMiddlewareInfo; + if (!tryLoadLibrary(libraryPath, &sharedLibraryHandle, &foundMiddlewareInfo)) { + return false; + } + + if (foundMiddlewareInfo->middlewareName_ != requestedBindingIdentifier) { + //If library was linked at compile time (and therefore an appropriate runtime loader is registered), + //the library must not be closed! + auto foundRegisteredRuntimeLoader = registeredRuntimeLoadFunctions_->find(foundMiddlewareInfo->middlewareName_); + if (foundRegisteredRuntimeLoader == registeredRuntimeLoadFunctions_->end()) { + dlclose(sharedLibraryHandle); + } + return false; + } + + if (!keepLibrary) { + dlclose(sharedLibraryHandle); + } else { + //Extend visibility to make symbols available to all other libraries loaded afterwards, + //e.g. libraries containing generated binding specific code. + sharedLibraryHandle = dlopen(libraryPath.c_str(), RTLD_NOW | RTLD_GLOBAL); + if (!sharedLibraryHandle) { + return false; + } + registeredRuntimeLoadFunctions_->insert( {foundMiddlewareInfo->middlewareName_, foundMiddlewareInfo->getInstance_} ); + } + + return true; +} + + +bool Runtime::checkAndLoadDefaultLibrary(std::string& foundBindingIdentifier, const std::string& libraryPath) { + void* sharedLibraryHandle = NULL; + MiddlewareInfo* foundMiddlewareInfo; + if (!tryLoadLibrary(libraryPath, &sharedLibraryHandle, &foundMiddlewareInfo)) { + return false; + } + + //Extend visibility to make symbols available to all other linked libraries, + //e.g. libraries containing generated binding specific code + sharedLibraryHandle = dlopen(libraryPath.c_str(), RTLD_NOW | RTLD_GLOBAL); + if (!sharedLibraryHandle) { + return false; + } + registeredRuntimeLoadFunctions_->insert( {foundMiddlewareInfo->middlewareName_, foundMiddlewareInfo->getInstance_} ); + foundBindingIdentifier = foundMiddlewareInfo->middlewareName_; + + return true; +} + + +const std::vector Runtime::readDirectory(const std::string& path) { + std::vector result; + struct stat filestat; + + DIR *directory = opendir(path.c_str()); + + if (!directory) { + return std::vector(); + } + + struct dirent* entry; + + while ((entry = readdir(directory))) { + const std::string fqnOfEntry = path + entry->d_name; + + if (stat(fqnOfEntry.c_str(), &filestat)) { + continue; + } + if (S_ISDIR(filestat.st_mode)) { + continue; + } + + if (strncmp(COMMONAPI_LIB_PREFIX, entry->d_name, strlen(COMMONAPI_LIB_PREFIX)) != 0) { + continue; + } + + const char* fileNamePtr = entry->d_name; + while ((fileNamePtr = strchr(fileNamePtr + 1, '.'))) { + if (strncmp(".so", fileNamePtr, 3) == 0) { + break; + } + } + + if (fileNamePtr) { + result.push_back(fqnOfEntry); + } + } + + closedir (directory); + + std::sort( result.begin(), result.end() ); + + return result; +} + + +struct LibraryVersion { + int32_t major; + int32_t minor; + int32_t revision; +}; + +bool operator<(LibraryVersion const& lhs, LibraryVersion const& rhs) { + if (lhs.major == rhs.major) { + if (lhs.minor == rhs.minor) { + return lhs.revision < rhs.revision; + } + return lhs.minor < rhs.minor; + } + return lhs.major < rhs.major; +} + + +std::shared_ptr Runtime::checkDynamicLibraries(const std::string& requestedMiddlewareName, LoadState& loadState) { + const std::string& middlewareLibraryPath = Configuration::getInstance().getMiddlewareLibraryPath(requestedMiddlewareName); + + if (middlewareLibraryPath != "") { + if (!checkAndLoadLibrary(middlewareLibraryPath, requestedMiddlewareName, true)) { + //A path for requestedMiddlewareName was configured, but no corresponding library was found + loadState = LoadState::CONFIGURATION_ERROR; + return std::shared_ptr(NULL); + } else { + const std::string currentBinaryFQN = getCurrentBinaryFileFQN(); + const uint32_t lastPathSeparatorPosition = currentBinaryFQN.find_last_of("/\\"); + const std::string currentBinaryPath = currentBinaryFQN.substr(0, lastPathSeparatorPosition + 1); + auto foundRuntimeLoader = registeredRuntimeLoadFunctions_->find(requestedMiddlewareName); + if (foundRuntimeLoader != registeredRuntimeLoadFunctions_->end()) { + return (foundRuntimeLoader->second)(); + } + //One should not get here + loadState = LoadState::BINDING_ERROR; + return std::shared_ptr(NULL); + } + } + + const std::vector& librarySearchPaths = Configuration::getInstance().getLibrarySearchPaths(); + + LibraryVersion highestVersionFound = {0, 0, 0}; + std::string fqnOfHighestVersion = ""; + struct stat filestat; + + for (const std::string& singleSearchPath: librarySearchPaths) { + std::vector orderedLibraries = readDirectory(singleSearchPath); + + for (const std::string& fqnOfEntry : orderedLibraries) { + std::string versionString; + LibraryVersion currentLibraryVersion = {-1, -1, -1}; + + + const char* fileNamePtr = fqnOfEntry.c_str(); + while ((fileNamePtr = strchr(fileNamePtr + 1, '.'))) { + if (strncmp(".so", fileNamePtr, 3) == 0) { + break; + } + } + + const char* positionOfFirstDot = strchr(fileNamePtr + 1, '.'); + if (positionOfFirstDot) { + versionString = positionOfFirstDot + 1; + } + + std::vector versionElements = split(versionString, '.'); + if (versionElements.size() >= 1 && containsOnlyDigits(versionElements[0])) { + currentLibraryVersion.major = strtol(versionElements[0].c_str(), NULL, 0); + } + if (versionElements.size() >= 3 && containsOnlyDigits(versionElements[2])) { + currentLibraryVersion.minor = strtol(versionElements[1].c_str(), NULL, 0); + currentLibraryVersion.revision = strtol(versionElements[2].c_str(), NULL, 0); + } + + if (highestVersionFound < currentLibraryVersion) { + if (!checkAndLoadLibrary(fqnOfEntry, requestedMiddlewareName, false)) { + continue; + } + highestVersionFound = currentLibraryVersion; + fqnOfHighestVersion = fqnOfEntry; + } + } + } + + if (fqnOfHighestVersion != "") { + checkAndLoadLibrary(fqnOfHighestVersion, requestedMiddlewareName, true); + + auto foundRuntimeLoader = registeredRuntimeLoadFunctions_->find(requestedMiddlewareName); + if (foundRuntimeLoader != registeredRuntimeLoadFunctions_->end()) { + std::shared_ptr loadedRuntime = foundRuntimeLoader->second(); + if (!loadedRuntime) { + loadState = LoadState::BINDING_ERROR; + } + return loadedRuntime; + } + } + + loadState = LoadState::NO_LIBRARY_FOUND; + + return std::shared_ptr(); +} + + +std::shared_ptr Runtime::checkDynamicLibraries(LoadState& loadState) { + const std::string& defaultBindingIdentifier = Configuration::getInstance().getDefaultMiddlewareIdentifier(); + if (defaultBindingIdentifier != "") { + return checkDynamicLibraries(defaultBindingIdentifier, loadState); + } + + const std::vector& librarySearchPaths = Configuration::getInstance().getLibrarySearchPaths(); + + for (const std::string& singleSearchPath : librarySearchPaths) { + std::vector orderedLibraries = readDirectory(singleSearchPath); + + for (const std::string& fqnOfEntry: orderedLibraries) { + std::string foundBindingName; + if (!checkAndLoadDefaultLibrary(foundBindingName, fqnOfEntry)) { + continue; + } + + auto foundRuntimeLoader = registeredRuntimeLoadFunctions_->find(foundBindingName); + if (foundRuntimeLoader != registeredRuntimeLoadFunctions_->end()) { + return (foundRuntimeLoader->second)(); + } + } + } + + loadState = LoadState::NO_LIBRARY_FOUND; + + return std::shared_ptr(); +} + + +void Runtime::registerRuntimeLoader(const std::string& middlewareName, const MiddlewareRuntimeLoadFunction& middlewareRuntimeLoadFunction) { + if (!registeredRuntimeLoadFunctions_) { registeredRuntimeLoadFunctions_ = new std::unordered_map(); } - registeredRuntimeLoadFunctions_->insert({middlewareName, middlewareRuntimeLoadFunction}); + if (!isDynamic_) { + registeredRuntimeLoadFunctions_->insert( {middlewareName, middlewareRuntimeLoadFunction}); + } } std::shared_ptr Runtime::load() { + LoadState dummyState; + return load(dummyState); +} + + +std::shared_ptr Runtime::load(LoadState& loadState) { + isDynamic_ = true; + loadState = LoadState::SUCCESS; if(!registeredRuntimeLoadFunctions_) { - registeredRuntimeLoadFunctions_ = new std::unordered_map {}; + registeredRuntimeLoadFunctions_ = new std::unordered_map(); } - auto begin = registeredRuntimeLoadFunctions_->begin(); + const auto defaultRuntimeLoader = registeredRuntimeLoadFunctions_->begin(); - if (begin != registeredRuntimeLoadFunctions_->end()) { - return (begin->second)(); + if (defaultRuntimeLoader != registeredRuntimeLoadFunctions_->end()) { + return (defaultRuntimeLoader->second)(); } - return std::shared_ptr(NULL); + return checkDynamicLibraries(loadState); } -std::shared_ptr Runtime::load(const std::string& middlewareName) { - if(!registeredRuntimeLoadFunctions_) { - registeredRuntimeLoadFunctions_ = new std::unordered_map {}; +std::shared_ptr Runtime::load(const std::string& middlewareIdOrAlias) { + LoadState dummyState; + return load(middlewareIdOrAlias, dummyState); +} + +std::shared_ptr Runtime::load(const std::string& middlewareIdOrAlias, LoadState& loadState) { + isDynamic_ = true; + loadState = LoadState::SUCCESS; + if (!registeredRuntimeLoadFunctions_) { + registeredRuntimeLoadFunctions_ = new std::unordered_map(); } - for (auto it = registeredRuntimeLoadFunctions_->begin(); it != registeredRuntimeLoadFunctions_->end(); ++it) { - if(it->first == middlewareName) { - return (it->second)(); - } + const std::string middlewareName = Configuration::getInstance().getMiddlewareNameForAlias(middlewareIdOrAlias); + + auto foundRuntimeLoader = registeredRuntimeLoadFunctions_->find(middlewareName); + if (foundRuntimeLoader != registeredRuntimeLoadFunctions_->end()) { + return (foundRuntimeLoader->second)(); } - return std::shared_ptr(NULL); + return checkDynamicLibraries(middlewareName, loadState); } diff --git a/src/CommonAPI/Runtime.h b/src/CommonAPI/Runtime.h index c0040a8..0a93ccc 100644 --- a/src/CommonAPI/Runtime.h +++ b/src/CommonAPI/Runtime.h @@ -17,12 +17,11 @@ #include "MainLoopContext.h" #include -#include #include -#include #include #include #include +#include namespace CommonAPI { @@ -40,41 +39,90 @@ class ServicePublisher; */ class Runtime { public: + enum class LoadState { + SUCCESS, + NO_LIBRARY_FOUND, + CONFIGURATION_ERROR, + BINDING_ERROR + }; + + virtual ~Runtime() {} + /** * \brief Loads the default runtime. * - * Loads the runtime for the default middleware binding. This either is the only binding available, - * or the one defined as default in the configuration. + * Loads the runtime for the default middleware binding. This can be + * * One of the middleware bindings that were linked at compile time + * * The first middleware binding that is encountered when resolving bindings at runtime + * * The middleware binding that was configured as default in the corresponding configuration + * file (throws an error if no such binding exists) * - * @return The runtime object for the default binding + * @return The runtime object for the default binding, or null if any error occurred */ static std::shared_ptr load(); + /** + * \brief Loads the default runtime and notifies the caller of any errors. + * + * Loads the runtime for the default middleware binding. This can be + * * One of the middleware bindings that were linked at compile time + * * The first middleware binding that is encountered when resolving bindings at runtime + * * The middleware binding that was configured as default in the corresponding configuration + * file (throws an error if no such binding exists) + * + * @param loadState: An enumeration that will be set appropriately after loading has finished or + * aborted. May be used for debugging purposes. + * + * @return The runtime object for the default binding, or null if any error occurred. In the latter + * case, loadState will be set to an appropriate value. + */ + static std::shared_ptr load(LoadState& loadState); + /** * \brief Loads specified runtime. * - * Loads the runtime for the specified middleware binding. The given middleware ID can be either + * Loads the runtime for the specified middleware binding. The given well known name can be either * the well known name defined by a binding, or a configured alias for a binding. * - * @return The runtime object for specified binding + * @param middlewareIdOrAlias A well known name or an alias for a binding + * + * @return The runtime object for specified binding, or null if any error occurred. + * + * @throw std::invalid_argument if a path for this middlewareId has been configured, but no appropriate library + * could be found there. */ - static std::shared_ptr load(const std::string& middlewareId); + static std::shared_ptr load(const std::string& middlewareIdOrAlias); + + /** + * \brief Loads specified runtime. + * + * Loads the runtime for the specified middleware binding. The given well known name can be either + * the well known name defined by a binding, or a configured alias for a binding. + * + * @param middlewareIdOrAlias A well known name or an alias for a binding. + * @param loadState: An enumeration that will be set appropriately after loading has finished or + * aborted. May be used for debugging purposes. + * + * @return The runtime object for specified binding, or null if any error occurred. In the latter + * case, loadState will be set to an appropriate value. + */ + static std::shared_ptr load(const std::string& middlewareIdOrAlias, LoadState& loadState); /** * \brief Called by bindings to register their runtime loaders. Do not call from applications. * * Called by bindings to register their runtime loaders. Do not call from applications. */ - static void registerRuntimeLoader(std::string middlewareName, MiddlewareRuntimeLoadFunction middlewareRuntimeLoadFunction); - - virtual ~Runtime() {} + static void registerRuntimeLoader(const std::string& middlewareName, const MiddlewareRuntimeLoadFunction& middlewareRuntimeLoadFunction); /** - * \brief Returns new MainLoopContext + * \brief Returns new MainLoopContext. * * Creates and returns a new MainLoopContext object. This context can be used to take * complete control over the order and time of execution of the abstract middleware - * dispatching mechanism. + * dispatching mechanism. Make sure to register all callback functions before subsequently + * handing it to createFactory(), as during creation of the factory object the callbacks may + * already be called. * * @return A new MainLoopContext object */ @@ -100,9 +148,9 @@ class Runtime { * * @return Factory object for this runtime */ - virtual std::shared_ptr createFactory(std::shared_ptr mainLoopContext = std::shared_ptr(NULL), - const std::string factoryName = "", - const bool nullOnInvalidName = false); + std::shared_ptr createFactory(std::shared_ptr mainLoopContext = std::shared_ptr(NULL), + const std::string factoryName = "", + const bool nullOnInvalidName = false); /** * \brief Create a factory for the loaded runtime. @@ -119,8 +167,8 @@ class Runtime { * * @return Factory object for this runtime */ - virtual std::shared_ptr createFactory(const std::string factoryNamey, - const bool nullOnInvalidName = false); + std::shared_ptr createFactory(const std::string factoryNamey, + const bool nullOnInvalidName = false); /** * \brief Returns the ServicePublisher object for this runtime. @@ -136,8 +184,20 @@ class Runtime { protected: virtual std::shared_ptr doCreateFactory(std::shared_ptr mainLoopContext, - const std::string factoryName, + const std::string& factoryName, const bool nullOnInvalidName = false) = 0; + + private: + static const std::vector readDirectory(const std::string& path); + + static std::shared_ptr checkDynamicLibraries(LoadState& loadState); + static std::shared_ptr checkDynamicLibraries(const std::string& middlewareName, LoadState& loadState); + + static bool tryLoadLibrary(const std::string& libraryPath, void** sharedLibraryHandle, MiddlewareInfo** foundMiddlewareInfo); + static bool checkAndLoadLibrary(const std::string& libraryPath, const std::string& requestedMiddlewareName, bool keepLibrary); + static bool checkAndLoadDefaultLibrary(std::string& foundBindingName, const std::string& libraryPath); + + static void closeHandle(void* libraryHandle); }; diff --git a/src/CommonAPI/utils.h b/src/CommonAPI/utils.h new file mode 100644 index 0000000..c1c1c54 --- /dev/null +++ b/src/CommonAPI/utils.h @@ -0,0 +1,340 @@ +/* Copyright (C) 2013 BMW Group + * Author: Manfred Bathelt (manfred.bathelt@bmw.de) + * Author: Juergen Gehring (juergen.gehring@bmw.de) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef COMMONAPI_UTILS_H_ +#define COMMONAPI_UTILS_H_ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +namespace CommonAPI { + + +#if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1) +# define COMMONAPI_DEPRECATED __attribute__ ((__deprecated__)) +#elif defined(_MSC_VER) && (_MSC_VER >= 1300) +# define COMMONAPI_DEPRECATED __declspec(deprecated) +#else +# define COMMONAPI_DEPRECATED +#endif + + +/** + * \brief Returns the fully qualified name of the binary. + * + * @return The name of the currently executing binary. + */ +inline std::string getCurrentBinaryFileFQN() { + char fqnOfBinary[FILENAME_MAX]; + char pathToProcessImage[FILENAME_MAX]; + + sprintf(pathToProcessImage, "/proc/%d/exe", getpid()); + const ssize_t lengthOfFqn = readlink(pathToProcessImage, fqnOfBinary, sizeof(fqnOfBinary) - 1); + + if (lengthOfFqn != -1) { + fqnOfBinary[lengthOfFqn] = '\0'; + return std::string(std::move(fqnOfBinary)); + } else { + return std::string(""); + } +} + +/** + * \brief Splits a std::string according to the given delim-char. + * + * The string will be splitted at each position the delim char is encountered. The delim itself + * will be removed from the result. + * + * @param s: The string that is to be splitted + * @param delim: The character that separates the resulting string tokens in the original string + * @param elems: Reference to the vector that shall be filled with the splitted string elements. + * + * @return A reference to the vector you passed in (elems) + */ +inline std::vector& split(const std::string& s, char delim, std::vector& elems) { + std::istringstream ss(s); + std::string item; + while (std::getline(ss, item, delim)) { + elems.push_back(item); + } + return elems; +} + +/** + * \brief Splits a std::string according to the given delim-char. + * + * The string will be splitted at each position the delim char is encountered. The delim itself + * will be removed from the result. + * + * @param s: The string that is to be splitted + * @param delim: The character that separates the resulting string tokens in the original string + * + * @return A vector containing the splitted string elements. + */ +inline std::vector split(const std::string& s, char delim) { + std::vector elems; + return split(s, delim, elems); +} + +/** + * \brief Trims whitespaces from beginning and end of a std::string. + * + * @param toTrim: The string that is to be trimmed. + */ +inline void trim(std::string& toTrim) { + toTrim.erase( + toTrim.begin(), + std::find_if(toTrim.begin(), + toTrim.end(), + std::not1(std::ptr_fun(std::isspace))) + ); + toTrim.erase( + std::find_if(toTrim.rbegin(), + toTrim.rend(), + std::not1(std::ptr_fun(std::isspace))).base(), + toTrim.end() + ); +} + +/** + * \brief Checks whether the given string contains nothing but digits. + * + * @param toCheck: The string that is to be checked on the presence of anything but digits. + * + * @return true if toCheck contains nothing but digits, false otherwise. + */ +inline bool containsOnlyDigits(const std::string& toCheck) { + auto firstNonDigitIt = std::find_if( + toCheck.begin(), + toCheck.end(), + [](char c) { + return !std::isdigit(c); + }); + + return firstNonDigitIt == toCheck.end(); +} + +/** + * \brief Checks whether the given string contains nothing but alphanumeric characters. + * + * @param toCheck: The string that is to be checked on the presence of anything but alphanumeric characters. + * + * @return true if toCheck contains nothing but alphanumeric characters, false otherwise. + */ +inline bool containsOnlyAlphanumericCharacters(const std::string& toCheck) { + auto firstNonAlphanumericCharacterIt = std::find_if( + toCheck.begin(), + toCheck.end(), + [](char c) { + return !std::isalnum(c); + }); + + return firstNonAlphanumericCharacterIt == toCheck.end(); +} + +/** + * \brief Checks whether the given std::string is a valid CommonAPI domain name. + * + * @param domainName: The std::string that is to be checked. + * + * @return true if domainName is a valid CommonAPI domainName, false otherwise. + */ +inline bool isValidDomainName(const std::string& domainName) { + return containsOnlyAlphanumericCharacters(domainName); +} + +/** + * \brief Checks whether the given std::string is a valid CommonAPI service name. + * + * @param serviceName: The std::string that is to be checked. + * + * @return true if serviceName is a valid CommonAPI serviceName, false otherwise. + */ +inline bool isValidServiceName(const std::string& serviceName) { + bool isValid = serviceName[0] != '.' && serviceName[serviceName.size() - 1] != '.'; + + if (isValid) { + std::vector splittedServiceName = split(serviceName, '.'); + + for (auto serviceNameElementIt = splittedServiceName.begin(); + serviceNameElementIt != splittedServiceName.end() && isValid; + ++serviceNameElementIt) { + isValid &= containsOnlyAlphanumericCharacters(*serviceNameElementIt); + } + } + + return isValid; +} + +/** + * \brief Checks whether the given std::string is a valid CommonAPI instance ID. + * + * @param instanceId: The std::string that is to be checked. + * + * @return true if instanceId is a valid CommonAPI instance ID, false otherwise. + */ +inline bool isValidInstanceId(const std::string& instanceId) { + //Validation rules for ServiceName and InstanceID are equivalent + return isValidServiceName(instanceId); +} + +/** + * \brief Checks whether the given std::string is a valid CommonAPI address. + * + * @param commonApiAddressName: The std::string that is to be checked. + * + * @return true if commonApiAddress is a valid CommonAPI address, false otherwise. + */ +inline bool isValidCommonApiAddress(const std::string& commonApiAddress) { + std::vector splittedAddress = split(commonApiAddress, ':'); + if (splittedAddress.size() != 3) { + return false; + } + return isValidDomainName(splittedAddress[0]) && isValidServiceName(splittedAddress[1]) && isValidInstanceId(splittedAddress[2]); +} + + +/** + * \brief Loads a specific generic library at runtime. + * + * The library will be loaded using dlopen(3) with the flags (RTLD_NOW | RTLD_GLOBAL), if all pre-checks are + * successful. Pre-checks include the verification that the given parameters actually point to a library and + * optionally if the library matches the standard name pattern for CommonAPI generic libraries. The standard + * name pattern is "libGen-.so[.major[.minor.revision]]". + * + * @param wellKnownMiddlewareName: The name of the middleware that requests the loading of the library. + * @param libraryName: The name of the library that shall be loaded. + * @param path: The path at which the library is to be found. path + library name together make up the fully + * qualified name of the library. + * @param checkStandardNamePattern: If set to true, it will be ensured the library matches the CommonAPI + * standard name pattern for generic libraries. This is meant as a safety measure + * to prevent the loading of unnecessary or the wrong libraries. Set to false if + * you are sure about what you are doing. + * @return true if the library could be loaded successfully, false otherwise. + * + * @note The well known middleware name is included as a parameter because the additional libraries normally are needed + * only by specific middlewares. This name however will only be taken into consideration during name checking + * if the checkStandardNamePattern flag is set to true. + */ +inline bool loadGenericLibrary(const std::string& wellKnownMiddlewareName, const std::string& libraryName, const std::string& path, bool checkStandardNamePattern = true) { + std::string fqnOfLibrary = path + libraryName; + struct stat filestat; + if (stat(fqnOfLibrary.c_str(), &filestat)) { + return false; + } + if (S_ISDIR(filestat.st_mode)) { + return false; + } + + if (checkStandardNamePattern) { + const std::string generatedLibPrefix = "lib" + wellKnownMiddlewareName + "Gen-"; + if (strncmp(generatedLibPrefix.c_str(), libraryName.c_str(), generatedLibPrefix.length()) != 0) { + return false; + } + + const char* fileNamePtr = libraryName.c_str(); + while ((fileNamePtr = strchr(fileNamePtr + 1, '.'))) { + if (strncmp(".so", fileNamePtr, 3) == 0) { + break; + } + } + + if (!fileNamePtr) { + return false; + } + } + + dlopen(fqnOfLibrary.c_str(), RTLD_NOW | RTLD_GLOBAL); + return true; +} + +/** + * \brief Loads a specific generic library at runtime. + * + * The library will be loaded using dlopen(3) with the flags (RTLD_NOW | RTLD_GLOBAL), if all pre-checks are + * successful. Pre-checks include the verification that the given parameters actually point to a library and + * optionally if the library matches the standard name pattern for CommonAPI generic libraries. The standard + * name pattern is "libGen-.so[.major[.minor.revision]]". + * + * @param wellKnownMiddlewareName: The name of the middleware that requests the loading of the library. + * @param fqnOfLibrary: The fully qualified name of the library. + * @param checkStandardNamePattern: If set to true, it will be ensured the library matches the CommonAPI + * standard name pattern for generic libraries. This is meant as a safety measure + * to prevent the loading of unnecessary or the wrong libraries. Set to false if + * you are sure about what you are doing. + * @return true if the library could be loaded successfully, false otherwise. + * + * @note The well known middleware name is included as a parameter because the additional libraries normally are needed + * only by specific middlewares. This name however will only be taken into consideration during name checking + * if the checkStandardNamePattern flag is set to true. + */ +inline bool loadGenericLibrary(const std::string& wellKnownMiddlewareName, + const std::string& fqnOfLibrary, + bool checkStandardNamePattern = true) { + uint32_t position = fqnOfLibrary.find_last_of("/\\"); + std::string path = fqnOfLibrary.substr(0, position + 1); + std::string file = fqnOfLibrary.substr(position + 1); + return loadGenericLibrary(wellKnownMiddlewareName, file, path, checkStandardNamePattern); +} + + +/** + * \brief Searches the given path for additional generic CommonAPI libraries and loads them. + * + * All libraries for which the pre-checks are successful will be loaded using dlopen(3) with the flags + * (RTLD_NOW | RTLD_GLOBAL). Pre-checks include the verification that the given parameters actually point + * to a library and if the library matches the standard name pattern for CommonAPI generic libraries. + * The standard name pattern is "libGen-.so[.major[.minor.revision]]". + * + * @param wellKnownMiddlewareName: The name of the middleware that requests the loading of the library. Will + * be used to perform the name check. + * @param singleSearchPath: The directory that is to be searched for libraries. + */ +inline void findAndLoadGenericLibraries(const std::string& requestedMiddlewareName, const std::string& singleSearchPath) { + DIR *directory = opendir(singleSearchPath.c_str()); + + if (directory != NULL) { + struct dirent* entry; + + while ((entry = readdir(directory))) { + loadGenericLibrary(requestedMiddlewareName, entry->d_name, singleSearchPath, true); + } + } +} + +/** + * \brief Searches the given paths for additional generic CommonAPI libraries and loads them. + * + * All libraries for which the pre-checks are successful will be loaded using dlopen(3) with the flags + * (RTLD_NOW | RTLD_GLOBAL). Pre-checks include the verification that the given parameters actually point + * to a library and if the library matches the standard name pattern for CommonAPI generic libraries. + * The standard name pattern is "libGen-.so[.major[.minor.revision]]". + * + * @param wellKnownMiddlewareName: The name of the middleware that requests the loading of the library. Will + * be used to perform the name check. + * @param searchPaths: The directories that are to be searched for libraries. + */ +inline void findAndLoadGenericLibraries(const std::string& requestedMiddlewareName, const std::vector& searchPaths) { + for (const std::string& singleSearchPath : searchPaths) { + findAndLoadGenericLibraries(requestedMiddlewareName, singleSearchPath.c_str()); + } +} + + +} //namespace CommonAPI + + +#endif /* COMMONAPI_UTILS_H_ */ -- cgit v1.2.1