1 /** @file
2
3 Functionality allowing to load all plugins from a single config reload.
4
5 @section license License
6
7 Licensed to the Apache Software Foundation (ASF) under one
8 or more contributor license agreements. See the NOTICE file
9 distributed with this work for additional information
10 regarding copyright ownership. The ASF licenses this file
11 to you under the Apache License, Version 2.0 (the
12 "License"); you may not use this file except in compliance
13 with the License. You may obtain a copy of the License at
14
15 http://www.apache.org/licenses/LICENSE-2.0
16
17 Unless required by applicable law or agreed to in writing, software
18 distributed under the License is distributed on an "AS IS" BASIS,
19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 See the License for the specific language governing permissions and
21 limitations under the License.
22
23 */
24
25 #include <unordered_map>
26
27 #include "RemapPluginInfo.h"
28 #include "PluginFactory.h"
29 #ifdef PLUGIN_DSO_TESTS
30 #include "unit-tests/plugin_testing_common.h"
31 #else
32 #include "tscore/Diags.h"
33 #define PluginDebug Debug
34 #define PluginError Error
35 #endif
36 #include "P_EventSystem.h"
37
38 #include <algorithm> /* std::swap */
39
RemapPluginInst(RemapPluginInfo & plugin)40 RemapPluginInst::RemapPluginInst(RemapPluginInfo &plugin) : _plugin(plugin)
41 {
42 _plugin.acquire();
43 }
44
~RemapPluginInst()45 RemapPluginInst::~RemapPluginInst()
46 {
47 _plugin.release();
48 }
49
50 RemapPluginInst *
init(RemapPluginInfo * plugin,int argc,char ** argv,std::string & error)51 RemapPluginInst::init(RemapPluginInfo *plugin, int argc, char **argv, std::string &error)
52 {
53 RemapPluginInst *inst = new RemapPluginInst(*plugin);
54 if (plugin->initInstance(argc, argv, &(inst->_instance), error)) {
55 plugin->incInstanceCount();
56 return inst;
57 }
58 delete inst;
59 return nullptr;
60 }
61
62 void
done()63 RemapPluginInst::done()
64 {
65 _plugin.decInstanceCount();
66 _plugin.doneInstance(_instance);
67
68 if (0 == _plugin.instanceCount()) {
69 _plugin.done();
70 }
71 }
72
73 TSRemapStatus
doRemap(TSHttpTxn rh,TSRemapRequestInfo * rri)74 RemapPluginInst::doRemap(TSHttpTxn rh, TSRemapRequestInfo *rri)
75 {
76 return _plugin.doRemap(_instance, rh, rri);
77 }
78
79 void
osResponse(TSHttpTxn rh,int os_response_type)80 RemapPluginInst::osResponse(TSHttpTxn rh, int os_response_type)
81 {
82 _plugin.osResponse(_instance, rh, os_response_type);
83 }
84
PluginFactory()85 PluginFactory::PluginFactory()
86 {
87 _uuid = new ATSUuid();
88 if (nullptr != _uuid) {
89 _uuid->initialize(TS_UUID_V4);
90 if (!_uuid->valid()) {
91 /* Destroy and mark failure */
92 delete _uuid;
93 _uuid = nullptr;
94 }
95 }
96
97 PluginDebug(_tag, "created plugin factory %s", getUuid());
98 }
99
~PluginFactory()100 PluginFactory::~PluginFactory()
101 {
102 _instList.apply([](RemapPluginInst *pluginInst) -> void { delete pluginInst; });
103 _instList.clear();
104
105 fs::remove(_runtimeDir, _ec);
106
107 PluginDebug(_tag, "destroyed plugin factory %s", getUuid());
108 delete _uuid;
109 }
110
111 PluginFactory &
addSearchDir(const fs::path & searchDir)112 PluginFactory::addSearchDir(const fs::path &searchDir)
113 {
114 _searchDirs.push_back(searchDir);
115 PluginDebug(_tag, "added plugin search dir %s", searchDir.c_str());
116 return *this;
117 }
118
119 PluginFactory &
setRuntimeDir(const fs::path & runtimeDir)120 PluginFactory::setRuntimeDir(const fs::path &runtimeDir)
121 {
122 _runtimeDir = runtimeDir / fs::path(getUuid());
123 PluginDebug(_tag, "set plugin runtime dir %s", runtimeDir.c_str());
124 return *this;
125 }
126
127 const char *
getUuid()128 PluginFactory::getUuid()
129 {
130 return _uuid ? _uuid->getString() : "unknown";
131 }
132
133 /**
134 * @brief Loads, initializes and return a valid Remap Plugin instance.
135 *
136 * @param configPath plugin path as specified in the plugin
137 * @param argc number of parameters passed to the plugin during instance initialization
138 * @param argv parameters passed to the plugin during instance initialization
139 * @param context Plugin context is used from continuations to guarantee correct reference counting against the plugin.
140 * @param error human readable message if something goes wrong, empty otherwise
141 * @return pointer to a plugin instance, nullptr if failure
142 */
143 RemapPluginInst *
getRemapPlugin(const fs::path & configPath,int argc,char ** argv,std::string & error,bool dynamicReloadEnabled)144 PluginFactory::getRemapPlugin(const fs::path &configPath, int argc, char **argv, std::string &error, bool dynamicReloadEnabled)
145 {
146 /* Discover the effective path by looking into the search dirs */
147 fs::path effectivePath = getEffectivePath(configPath);
148 if (effectivePath.empty()) {
149 error.assign("failed to find plugin '").append(configPath.string()).append("'");
150 // The error will be reported by the caller but add debug log entry with this tag for convenience.
151 PluginDebug(_tag, "%s", error.c_str());
152 return nullptr;
153 }
154
155 // The plugin may have opt out by `TSPluginDSOReloadEnable`, let's check and overwrite
156 if (dynamicReloadEnabled && PluginDso::loadedPlugins()->isPluginInDsoOptOutTable(effectivePath)) {
157 // plugin not interested to be reload.
158 PluginDebug(_tag, "Plugin %s not interested in taking part of the reload.", effectivePath.c_str());
159 dynamicReloadEnabled = false;
160 }
161
162 /* Only one plugin with this effective path can be loaded by a plugin factory */
163 RemapPluginInfo *plugin = dynamic_cast<RemapPluginInfo *>(findByEffectivePath(effectivePath, dynamicReloadEnabled));
164 RemapPluginInst *inst = nullptr;
165
166 if (nullptr == plugin) {
167 /* The plugin requested have not been loaded yet. */
168 PluginDebug(_tag, "plugin '%s' has not been loaded yet, loading as remap plugin", configPath.c_str());
169
170 fs::path runtimePath;
171
172 // if dynamic reload enabled then create a temporary location to copy .so and load from there
173 // else load from original location
174 if (dynamicReloadEnabled) {
175 runtimePath /= _runtimeDir;
176 runtimePath /= effectivePath.relative_path();
177
178 fs::path parent = runtimePath.parent_path();
179 PluginDebug(_tag, "Using effectivePath: [%s] runtimePath: [%s] parent: [%s]", effectivePath.c_str(), runtimePath.c_str(),
180 parent.c_str());
181 if (!fs::create_directories(parent, _ec)) {
182 error.assign("failed to create plugin runtime dir");
183 return nullptr;
184 }
185 } else {
186 runtimePath = effectivePath;
187 PluginDebug(_tag, "Using effectivePath: [%s] runtimePath: [%s]", effectivePath.c_str(), runtimePath.c_str());
188 }
189
190 plugin = new RemapPluginInfo(configPath, effectivePath, runtimePath);
191 if (nullptr != plugin) {
192 if (plugin->load(error)) {
193 if (plugin->init(error)) {
194 PluginDso::loadedPlugins()->add(plugin);
195 inst = RemapPluginInst::init(plugin, argc, argv, error);
196 if (nullptr != inst) {
197 /* Plugin loading and instance init went fine. */
198 _instList.append(inst);
199 }
200 } else {
201 /* Plugin DSO load succeeded but instance init failed. */
202 PluginDebug(_tag, "plugin '%s' instance init failed", configPath.c_str());
203 plugin->unload(error);
204 delete plugin;
205 }
206
207 if (dynamicReloadEnabled && _preventiveCleaning) {
208 clean(error);
209 }
210 } else {
211 /* Plugin DSO load failed. */
212 PluginDebug(_tag, "plugin '%s' DSO load failed", configPath.c_str());
213 delete plugin;
214 }
215 }
216 } else {
217 PluginDebug(_tag, "plugin '%s' has already been loaded", configPath.c_str());
218 inst = RemapPluginInst::init(plugin, argc, argv, error);
219 if (nullptr != inst) {
220 _instList.append(inst);
221 }
222 }
223
224 return inst;
225 }
226
227 /**
228 * @brief full path to the first plugin found in the search path which will be used to be copied to runtime location and loaded.
229 *
230 * @param configPath path specified in the config file, it can be relative path.
231 * @return full path to the plugin.
232 */
233 fs::path
getEffectivePath(const fs::path & configPath)234 PluginFactory::getEffectivePath(const fs::path &configPath)
235 {
236 if (configPath.is_absolute()) {
237 if (fs::exists(configPath)) {
238 return fs::canonical(configPath.string(), _ec);
239 } else {
240 return fs::path();
241 }
242 }
243
244 fs::path path;
245
246 for (auto dir : _searchDirs) {
247 fs::path candidatePath = dir / configPath;
248 if (fs::exists(candidatePath)) {
249 path = fs::canonical(candidatePath, _ec);
250 break;
251 }
252 }
253
254 return path;
255 }
256
257 /**
258 * @brief Find a plugin by path from our linked plugin list by using plugin effective (canonical) path
259 *
260 * @param path effective (caninical) path
261 * @return plugin found or nullptr if not found
262 */
263 PluginDso *
findByEffectivePath(const fs::path & path,bool dynamicReloadEnabled)264 PluginFactory::findByEffectivePath(const fs::path &path, bool dynamicReloadEnabled)
265 {
266 return PluginDso::loadedPlugins()->findByEffectivePath(path, dynamicReloadEnabled);
267 }
268
269 /**
270 * @brief Tell all plugins instantiated by this factory that the configuration
271 * they are using is no longer the active one.
272 *
273 * This method would be useful only in case configs are reloaded independently from
274 * factory/plugins instantiation and initialization.
275 */
276 void
deactivate()277 PluginFactory::deactivate()
278 {
279 PluginDebug(_tag, "deactivate configuration used by factory '%s'", getUuid());
280
281 _instList.apply([](RemapPluginInst &pluginInst) -> void { pluginInst.done(); });
282 }
283
284 /**
285 * @brief Tell all plugins (that so wish) that remap.config is going to be reloaded
286 */
287 void
indicatePreReload()288 PluginFactory::indicatePreReload()
289 {
290 PluginDso::loadedPlugins()->indicatePreReload(getUuid());
291 }
292
293 /**
294 * @brief Tell all plugins (that so wish) that remap.config is done reloading
295 */
296 void
indicatePostReload(bool reloadSuccessful)297 PluginFactory::indicatePostReload(bool reloadSuccessful)
298 {
299 /* Find out which plugins (DSO) are actually instantiated by this factory */
300 std::unordered_map<PluginDso *, int> pluginUsed;
301 for (auto &inst : _instList) {
302 pluginUsed[&(inst._plugin)]++;
303 }
304
305 PluginDso::loadedPlugins()->indicatePostReload(reloadSuccessful, pluginUsed, getUuid());
306 }
307
308 void
clean(std::string & error)309 PluginFactory::clean(std::string &error)
310 {
311 fs::remove(_runtimeDir, _ec);
312 }
313