blob: 76e302f215a575f52807f7860a965dc6e00364f4 [file] [log] [blame]
/** @file
A class that deals with plugin Dynamic Shared Objects (DSO)
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@section details Details
Implements code necessary for Reverse Proxy which mostly consists of
general purpose hostname substitution in URLs.
*/
#include "PluginDso.h"
#include "P_Freer.h"
#include "P_EventSystem.h"
#ifdef PLUGIN_DSO_TESTS
#include "unit-tests/plugin_testing_common.h"
#else
#include "tscore/Diags.h"
#define PluginDebug Debug
#define PluginError Error
#endif
PluginDso::PluginDso(const fs::path &configPath, const fs::path &effectivePath, const fs::path &runtimePath)
: _configPath(configPath), _effectivePath(effectivePath), _runtimePath(runtimePath)
{
PluginDebug(_tag, "PluginDso (%p) created _configPath: [%s] _effectivePath: [%s] _runtimePath: [%s]", this, _configPath.c_str(),
_effectivePath.c_str(), _runtimePath.c_str());
}
PluginDso::~PluginDso()
{
std::string error;
(void)unload(error);
}
bool
PluginDso::load(std::string &error)
{
/* Clear all errors */
error.clear();
_errorCode.clear();
bool result = true;
if (isLoaded()) {
error.append("plugin already loaded");
return false;
}
PluginDebug(_tag, "plugin '%s' started loading DSO", _configPath.c_str());
/* Find plugin DSO looking through the search dirs */
if (_effectivePath.empty()) {
error.append("empty effective path");
result = false;
} else {
PluginDebug(_tag, "plugin '%s' effective path: %s", _configPath.c_str(), _effectivePath.c_str());
/* Copy the installed plugin DSO to a runtime directory if dynamic reload enabled */
std::error_code ec;
if (isDynamicReloadEnabled() && !copy(_effectivePath, _runtimePath, ec)) {
std::string temp_error;
temp_error.append("failed to create a copy: ").append(strerror(ec.value()));
error.assign(temp_error);
result = false;
} else {
PluginDebug(_tag, "plugin '%s' runtime path: %s", _configPath.c_str(), _runtimePath.c_str());
/* Save the time for later checking if DSO got modified in consecutive DSO reloads */
std::error_code ec;
fs::file_status fs = fs::status(_effectivePath, ec);
_mtime = fs::modification_time(fs);
PluginDebug(_tag, "plugin '%s' modification time %ld", _configPath.c_str(), _mtime);
/* Now attemt to load the plugin DSO */
if ((_dlh = dlopen(_runtimePath.c_str(), RTLD_NOW | RTLD_LOCAL)) == nullptr) {
#if defined(freebsd) || defined(openbsd)
char *err = (char *)dlerror();
#else
char *err = dlerror();
#endif
error.append(err ? err : "Unknown dlopen() error");
_dlh = nullptr; /* mark that the constructor failed. */
clean(error);
result = false;
PluginError("plugin '%s' failed to load: %s", _configPath.c_str(), error.c_str());
}
}
/* Remove the runtime DSO copy even if we succeed loading to avoid leftovers after crashes */
if (_preventiveCleaning) {
clean(error);
}
}
PluginDebug(_tag, "plugin '%s' finished loading DSO", _configPath.c_str());
return result;
}
/**
* @brief unload plugin DSO
*
* @param error - error messages in case of failure.
* @return true - success, false - failure during unload.
*/
bool
PluginDso::unload(std::string &error)
{
/* clean errors */
error.clear();
bool result = false;
if (isLoaded()) {
result = (0 == dlclose(_dlh));
_dlh = nullptr;
if (true == result) {
clean(error);
} else {
error.append("failed to unload plugin");
}
} else {
error.append("no plugin loaded");
result = false;
}
return result;
}
/**
* @brief returns the address of a symbol in the plugin DSO
*
* @param symbol symbol name
* @param address reference to the address to be returned to the caller
* @param error error messages in case of symbol is not found
* @return true if success, false could not find the symbol (symbol can be nullptr itself)
*/
bool
PluginDso::getSymbol(const char *symbol, void *&address, std::string &error) const
{
/* Clear the errors */
dlerror();
error.clear();
address = dlsym(_dlh, symbol);
char *err = dlerror();
if (nullptr == address && nullptr != err) {
/* symbol really cannot be found */
error.assign(err);
return false;
}
return true;
}
/**
* @brief shows if the DSO corresponding to this effective path has already been loaded.
* @return true - loaded, false - not loaded
*/
bool
PluginDso::isLoaded()
{
return nullptr != _dlh;
}
/**
* @brief full path to the first plugin found in the search path which will be used to be loaded.
*
* @return full path to the plugin DSO.
*/
const fs::path &
PluginDso::effectivePath() const
{
return _effectivePath;
}
/**
* @brief full path to the runtime location of the plugin DSO actually loaded.
*
* @return full path to the runtime plugin DSO.
*/
const fs::path &
PluginDso::runtimePath() const
{
return _runtimePath;
}
/**
* @brief DSO modification time at the moment of DSO load.
*
* @return modification time.
*/
time_t
PluginDso::modTime() const
{
return _mtime;
}
/**
* @brief file handle returned by dlopen syscall
*
* @return dlopen filehandle
*/
void *
PluginDso::dlOpenHandle() const
{
return _dlh;
}
/**
* @brief clean files created by the plugin instance and handle errors
*
* @param error a human readable error message if something goes wrong
* @ return void
*/
void
PluginDso::clean(std::string &error)
{
if (!isDynamicReloadEnabled()) {
return;
}
if (false == remove(_runtimePath, _errorCode)) {
error.append("failed to remove runtime copy: ").append(_errorCode.message());
}
}
void
PluginDso::acquire()
{
this->refcount_inc();
PluginDebug(_tag, "plugin DSO acquire (ref-count:%d, dso-addr:%p)", this->refcount(), this);
}
void
PluginDso::release()
{
PluginDebug(_tag, "plugin DSO release (ref-count:%d, dso-addr:%p)", this->refcount() - 1, this);
if (0 == this->refcount_dec()) {
PluginDebug(_tag, "unloading plugin DSO '%s' (dso-addr:%p)", _configPath.c_str(), this);
_plugins->remove(this);
}
}
void
PluginDso::incInstanceCount()
{
_instanceCount.refcount_inc();
PluginDebug(_tag, "instance count (inst-count:%d, dso-addr:%p)", _instanceCount.refcount(), this);
}
void
PluginDso::decInstanceCount()
{
_instanceCount.refcount_dec();
PluginDebug(_tag, "instance count (inst-count:%d, dso-addr:%p)", _instanceCount.refcount(), this);
}
int
PluginDso::instanceCount()
{
return _instanceCount.refcount();
}
bool
PluginDso::isDynamicReloadEnabled() const
{
return (_runtimePath != _effectivePath);
}
void
PluginDso::LoadedPlugins::add(PluginDso *plugin)
{
SCOPED_MUTEX_LOCK(lock, _mutex, this_ethread());
_list.append(plugin);
}
void
PluginDso::LoadedPlugins::remove(PluginDso *plugin)
{
SCOPED_MUTEX_LOCK(lock, _mutex, this_ethread());
_list.erase(plugin);
this_ethread()->schedule_imm(new DeleterContinuation<PluginDso>(plugin));
}
/* check if need to reload the plugin DSO
* if dynamic reload not enabled: check if plugin Dso with same effective path already loaded
* if dynamic reload enabled: check if plugin Dso with same effective path and same time stamp already loaded
* return pointer to already loaded plugin if found, else return null
*/
PluginDso *
PluginDso::LoadedPlugins::findByEffectivePath(const fs::path &path, bool dynamicReloadEnabled)
{
struct stat sb;
time_t mtime = 0;
if (0 == stat(path.c_str(), &sb)) {
mtime = sb.st_mtime;
}
SCOPED_MUTEX_LOCK(lock, _mutex, this_ethread());
auto spot = std::find_if(_list.begin(), _list.end(), [&](PluginDso const &plugin) -> bool {
return ((!dynamicReloadEnabled || (mtime == plugin.modTime())) &&
(0 == path.string().compare(plugin.effectivePath().string())));
});
return spot == _list.end() ? nullptr : static_cast<PluginDso *>(spot);
}
void
PluginDso::LoadedPlugins::indicatePreReload(const char *factoryId)
{
SCOPED_MUTEX_LOCK(lock, _mutex, this_ethread());
PluginDebug(_tag, "indicated config is going to be reloaded by factory '%s' to %zu plugin%s", factoryId, _list.count(),
_list.count() != 1 ? "s" : "");
_list.apply([](PluginDso &plugin) -> void { plugin.indicatePreReload(); });
}
void
PluginDso::LoadedPlugins::indicatePostReload(bool reloadSuccessful, const std::unordered_map<PluginDso *, int> &pluginUsed,
const char *factoryId)
{
SCOPED_MUTEX_LOCK(lock, _mutex, this_ethread());
PluginDebug(_tag, "indicated config is done reloading by factory '%s' to %zu plugin%s", factoryId, _list.count(),
_list.count() != 1 ? "s" : "");
for (auto &plugin : _list) {
TSRemapReloadStatus status = TSREMAP_CONFIG_RELOAD_FAILURE;
if (reloadSuccessful) {
/* reload succeeded but was the plugin instantiated by this factory? */
status = (pluginUsed.end() == pluginUsed.find(&plugin) ? TSREMAP_CONFIG_RELOAD_SUCCESS_PLUGIN_UNUSED :
TSREMAP_CONFIG_RELOAD_SUCCESS_PLUGIN_USED);
}
plugin.indicatePostReload(status);
}
}
bool
PluginDso::LoadedPlugins::addPluginPathToDsoOptOutTable(std::string_view pluginPath)
{
std::error_code ec;
auto effectivePath = fs::canonical(fs::path{pluginPath}, ec);
if (ec) {
PluginError("Error getting the canonical path: %s", ec.message().c_str());
return false;
}
{
SCOPED_MUTEX_LOCK(lock, _mutex, this_ethread());
_optoutDsoReloadPlugins.push_front(DisableDSOReloadPluginInfo{effectivePath});
}
return true;
}
void
PluginDso::LoadedPlugins::removePluginPathFromDsoOptOutTable(std::string_view pluginPath)
{
std::error_code ec;
auto effectivePath = fs::canonical(fs::path{pluginPath}, ec);
if (ec) {
PluginError("Error getting the canonical path: %s", ec.message().c_str());
return;
}
{
SCOPED_MUTEX_LOCK(lock, _mutex, this_ethread());
_optoutDsoReloadPlugins.remove_if([&effectivePath](auto const &info) { return info.dsoEffectivePath == effectivePath; });
}
}
bool
PluginDso::LoadedPlugins::isPluginInDsoOptOutTable(const fs::path &effectivePath)
{
SCOPED_MUTEX_LOCK(lock, _mutex, this_ethread());
const auto found = std::find_if(std::begin(this->_optoutDsoReloadPlugins), std::end(this->_optoutDsoReloadPlugins),
[&effectivePath](const auto &info) { return info.dsoEffectivePath == effectivePath; });
// if is found, then the plugin opt out.
return (found != std::end(this->_optoutDsoReloadPlugins));
}
Ptr<PluginDso::LoadedPlugins> PluginDso::_plugins;