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