1 
2 //
3 // This source file is part of appleseed.
4 // Visit https://appleseedhq.net/ for additional information and resources.
5 //
6 // This software is released under the MIT license.
7 //
8 // Copyright (c) 2018 Francois Beaune, The appleseedhq Organization
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining a copy
11 // of this software and associated documentation files (the "Software"), to deal
12 // in the Software without restriction, including without limitation the rights
13 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 // copies of the Software, and to permit persons to whom the Software is
15 // furnished to do so, subject to the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be included in
18 // all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 // THE SOFTWARE.
27 //
28 
29 // Interface header.
30 #include "pluginstore.h"
31 
32 // appleseed.renderer headers.
33 #include "renderer/global/globallogger.h"
34 #include "renderer/utility/plugin.h"
35 
36 // appleseed.foundation headers.
37 #include "foundation/platform/path.h"
38 #include "foundation/platform/sharedlibrary.h"
39 #include "foundation/utility/searchpaths.h"
40 
41 // Boost headers.
42 #include "boost/filesystem.hpp"
43 #include "boost/thread/locks.hpp"
44 #include "boost/thread/mutex.hpp"
45 
46 // Standard headers.
47 #include <cassert>
48 #include <cstddef>
49 #include <map>
50 #include <memory>
51 #include <string>
52 
53 using namespace foundation;
54 using namespace std;
55 
56 namespace bf = boost::filesystem;
57 
58 namespace renderer
59 {
60 
61 struct PluginStore::Impl
62 {
63     typedef PluginStore::PluginHandlerType PluginHandlerType;
64     typedef map<string, PluginHandlerType> PluginHandlerMap;
65 
66     struct PluginDeleter;
67     typedef unique_ptr<Plugin, PluginDeleter> PluginUniquePtr;
68 
69     typedef map<string, PluginUniquePtr> PluginMap;
70     typedef map<Plugin*, PluginMap::const_iterator> PluginInverseMap;
71 
72     struct PluginDeleter
73     {
operator ()renderer::PluginStore::Impl::PluginDeleter74         void operator()(Plugin* plugin)
75         {
76             RENDERER_LOG_INFO("unloading plugin %s...", plugin->get_filepath());
77 
78             // Try to call the plugin's uninitialization function if defined.
79             const auto uninit_fn = reinterpret_cast<Plugin::UnInitPluginFnType>(plugin->get_symbol("uninitialize_plugin"));
80             if (uninit_fn != nullptr)
81                 uninit_fn();
82 
83             // Delete the plugin.
84             delete plugin;
85         }
86     };
87 
88     boost::mutex        m_store_mutex;
89     PluginHandlerMap    m_plugin_handlers;
90     PluginMap           m_plugin_map;
91     PluginInverseMap    m_plugin_inverse_map;
92 
load_plugin_no_lockrenderer::PluginStore::Impl93     Plugin* load_plugin_no_lock(const char* filepath)
94     {
95         auto plugin_map_it = m_plugin_map.find(filepath);
96 
97         if (plugin_map_it == m_plugin_map.end())
98         {
99             RENDERER_LOG_INFO("loading plugin %s...", filepath);
100 
101             // Load the plugin.
102             PluginUniquePtr plugin(new Plugin(filepath));
103 
104             // Try to call the plugin's initialization function if defined.
105             const auto init_fn = reinterpret_cast<Plugin::InitPluginFnType>(plugin->get_symbol("initialize_plugin"));
106             if (init_fn != nullptr)
107             {
108                 if (!init_fn())
109                 {
110                     RENDERER_LOG_WARNING("plugin %s failed to initialize itself.", filepath);
111                     return nullptr;
112                 }
113             }
114 
115             // Insert the plugin into the map.
116             const auto insert_result =
117                 m_plugin_map.insert(PluginMap::value_type(filepath, std::move(plugin)));
118             assert(insert_result.second);
119             plugin_map_it = insert_result.first;
120 
121             // Insert the corresponding entry into the inverse map.
122 #ifndef NDEBUG
123             const auto inverse_insert_result =
124 #endif
125                 m_plugin_inverse_map.insert(PluginInverseMap::value_type(plugin_map_it->second.get(), plugin_map_it));
126             assert(inverse_insert_result.second);
127 
128             RENDERER_LOG_DEBUG("plugin %s successfully loaded and initialized.", filepath);
129         }
130         else
131         {
132             RENDERER_LOG_DEBUG("plugin %s already loaded.", filepath);
133         }
134 
135         return plugin_map_it->second.get();
136     }
137 
load_plugin_and_invoke_handlers_no_lockrenderer::PluginStore::Impl138     Plugin* load_plugin_and_invoke_handlers_no_lock(const char* filepath)
139     {
140         // Load the plugin.
141         Plugin* plugin = load_plugin_no_lock(filepath);
142 
143         if (plugin != nullptr)
144         {
145             // Invoke plugin handlers.
146             for (const auto& plugin_handler_item : m_plugin_handlers)
147             {
148                 const string& entry_point_name = plugin_handler_item.first;
149                 const PluginHandlerType& plugin_handler = plugin_handler_item.second;
150 
151                 // If the plugin exposes the expected entry point then pass it to the plugin handler.
152                 void* plugin_entry_point = plugin->get_symbol(entry_point_name.c_str());
153                 if (plugin_entry_point != nullptr)
154                     plugin_handler(plugin, plugin_entry_point);
155             }
156         }
157 
158         return plugin;
159     }
160 
load_all_plugins_from_path_no_lockrenderer::PluginStore::Impl161     void load_all_plugins_from_path_no_lock(bf::path path)
162     {
163         path = safe_canonical(path);
164 
165         // Only consider directories.
166         if (!bf::exists(path) || !bf::is_directory(path))
167         {
168             RENDERER_LOG_WARNING("not scanning %s for plugins since it doesn't exist or it isn't a directory.",
169                 path.string().c_str());
170             return;
171         }
172 
173         RENDERER_LOG_INFO("scanning %s for plugins...", path.string().c_str());
174 
175         // Iterate over all files in this directory.
176         for (bf::directory_iterator i(path), e; i != e; ++i)
177         {
178             const bf::path& filepath = i->path();
179 
180             // Only consider files.
181             if (!bf::is_regular_file(filepath))
182                 continue;
183 
184             // Only consider shared libraries.
185             if (lower_case(filepath.extension().string()) != SharedLibrary::get_default_file_extension())
186                 continue;
187 
188             // Load the plugin and invoke plugin handlers.
189             load_plugin_and_invoke_handlers_no_lock(filepath.string().c_str());
190         }
191     }
192 };
193 
PluginStore()194 PluginStore::PluginStore()
195   : impl(new Impl())
196 {
197 }
198 
~PluginStore()199 PluginStore::~PluginStore()
200 {
201     unload_all_plugins();
202     delete impl;
203 }
204 
register_plugin_handler(const char * entry_point_name,const PluginHandlerType & plugin_handler)205 void PluginStore::register_plugin_handler(
206     const char*                 entry_point_name,
207     const PluginHandlerType&    plugin_handler)
208 {
209     boost::lock_guard<boost::mutex> lock(impl->m_store_mutex);
210     impl->m_plugin_handlers.insert(
211         Impl::PluginHandlerMap::value_type(entry_point_name, plugin_handler));
212 }
213 
unload_all_plugins()214 void PluginStore::unload_all_plugins()
215 {
216     boost::lock_guard<boost::mutex> lock(impl->m_store_mutex);
217 
218     RENDERER_LOG_INFO("unloading all plugins...");
219 
220     impl->m_plugin_inverse_map.clear();
221     impl->m_plugin_map.clear();
222 }
223 
load_plugin(const char * filepath)224 Plugin* PluginStore::load_plugin(const char* filepath)
225 {
226     boost::lock_guard<boost::mutex> lock(impl->m_store_mutex);
227     return impl->load_plugin_and_invoke_handlers_no_lock(filepath);
228 }
229 
unload_plugin(Plugin * plugin)230 void PluginStore::unload_plugin(Plugin* plugin)
231 {
232     boost::lock_guard<boost::mutex> lock(impl->m_store_mutex);
233 
234     const auto plugin_map_inverse_it = impl->m_plugin_inverse_map.find(plugin);
235     assert(plugin_map_inverse_it != impl->m_plugin_inverse_map.end());
236 
237     impl->m_plugin_map.erase(plugin_map_inverse_it->second);
238     impl->m_plugin_inverse_map.erase(plugin_map_inverse_it);
239 }
240 
load_all_plugins_from_paths(const SearchPaths & search_paths)241 void PluginStore::load_all_plugins_from_paths(const SearchPaths& search_paths)
242 {
243     boost::lock_guard<boost::mutex> lock(impl->m_store_mutex);
244 
245     for (size_t i = 0, e = search_paths.get_path_count(); i < e; ++i)
246     {
247         bf::path search_path(search_paths.get_path(i));
248 
249         // Make the search path absolute if it isn't already.
250         if (!search_path.is_absolute() && search_paths.has_root_path())
251             search_path = search_paths.get_root_path().c_str() / search_path;
252 
253         // Load all plugins from this search path.
254         impl->load_all_plugins_from_path_no_lock(search_path);
255     }
256 }
257 
load_all_plugins_from_path(const char * path)258 void PluginStore::load_all_plugins_from_path(const char* path)
259 {
260     boost::lock_guard<boost::mutex> lock(impl->m_store_mutex);
261     impl->load_all_plugins_from_path_no_lock(path);
262 }
263 
264 }   // namespace renderer
265