1 /**
2  * Orthanc - A Lightweight, RESTful DICOM Store
3  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4  * Department, University Hospital of Liege, Belgium
5  * Copyright (C) 2017-2021 Osimis S.A., Belgium
6  *
7  * This program is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * In addition, as a special exception, the copyright holders of this
13  * program give permission to link the code of its release with the
14  * OpenSSL project's "OpenSSL" library (or with modified versions of it
15  * that use the same license as the "OpenSSL" library), and distribute
16  * the linked executables. You must obey the GNU General Public License
17  * in all respects for all of the code used other than "OpenSSL". If you
18  * modify file(s) with this exception, you may extend this exception to
19  * your version of the file(s), but you are not obligated to do so. If
20  * you do not wish to do so, delete this exception statement from your
21  * version. If you delete this exception statement from all source files
22  * in the program, then also delete it here.
23  *
24  * This program is distributed in the hope that it will be useful, but
25  * WITHOUT ANY WARRANTY; without even the implied warranty of
26  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27  * General Public License for more details.
28  *
29  * You should have received a copy of the GNU General Public License
30  * along with this program. If not, see <http://www.gnu.org/licenses/>.
31  **/
32 
33 
34 #include "../../Sources/PrecompiledHeadersServer.h"
35 #include "PluginsManager.h"
36 
37 #if ORTHANC_ENABLE_PLUGINS != 1
38 #error The plugin support is disabled
39 #endif
40 
41 #include "../../../OrthancFramework/Sources/HttpServer/HttpOutput.h"
42 #include "../../../OrthancFramework/Sources/Logging.h"
43 #include "../../../OrthancFramework/Sources/OrthancException.h"
44 #include "../../../OrthancFramework/Sources/Toolbox.h"
45 
46 #include <cassert>
47 #include <memory>
48 #include <boost/filesystem.hpp>
49 
50 #ifdef WIN32
51 #define PLUGIN_EXTENSION ".dll"
52 #elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
53 #define PLUGIN_EXTENSION ".so"
54 #elif defined(__APPLE__) && defined(__MACH__)
55 #define PLUGIN_EXTENSION ".dylib"
56 #else
57 #error Support your platform here
58 #endif
59 
60 
61 namespace Orthanc
62 {
Plugin(PluginsManager & pluginManager,const std::string & path)63   PluginsManager::Plugin::Plugin(PluginsManager& pluginManager,
64                                  const std::string& path) :
65     library_(path),
66     pluginManager_(pluginManager)
67   {
68     memset(&context_, 0, sizeof(context_));
69     context_.pluginsManager = this;
70     context_.orthancVersion = ORTHANC_VERSION;
71     context_.Free = ::free;
72     context_.InvokeService = InvokeService;
73   }
74 
75 
CallInitialize(SharedLibrary & plugin,const OrthancPluginContext & context)76   static void CallInitialize(SharedLibrary& plugin,
77                              const OrthancPluginContext& context)
78   {
79     typedef int32_t (*Initialize) (const OrthancPluginContext*);
80 
81 #if defined(_WIN32)
82     Initialize initialize = (Initialize) plugin.GetFunction("OrthancPluginInitialize");
83 #else
84     /**
85      * gcc would complain about "ISO C++ forbids casting between
86      * pointer-to-function and pointer-to-object" without the trick
87      * below, that is known as "the POSIX.1-2003 (Technical Corrigendum
88      * 1) workaround". See the man page of "dlsym()".
89      * http://www.trilithium.com/johan/2004/12/problem-with-dlsym/
90      * http://stackoverflow.com/a/14543811/881731
91      **/
92 
93     Initialize initialize;
94     *(void **) (&initialize) = plugin.GetFunction("OrthancPluginInitialize");
95 #endif
96 
97     assert(initialize != NULL);
98     int32_t error = initialize(&context);
99 
100     if (error != 0)
101     {
102       LOG(ERROR) << "Error while initializing plugin " << plugin.GetPath()
103                  << " (code " << error << ")";
104       throw OrthancException(ErrorCode_SharedLibrary);
105     }
106   }
107 
108 
CallFinalize(SharedLibrary & plugin)109   static void CallFinalize(SharedLibrary& plugin)
110   {
111     typedef void (*Finalize) ();
112 
113 #if defined(_WIN32)
114     Finalize finalize = (Finalize) plugin.GetFunction("OrthancPluginFinalize");
115 #else
116     Finalize finalize;
117     *(void **) (&finalize) = plugin.GetFunction("OrthancPluginFinalize");
118 #endif
119 
120     assert(finalize != NULL);
121     finalize();
122   }
123 
124 
CallGetName(SharedLibrary & plugin)125   static const char* CallGetName(SharedLibrary& plugin)
126   {
127     typedef const char* (*GetName) ();
128 
129 #if defined(_WIN32)
130     GetName getName = (GetName) plugin.GetFunction("OrthancPluginGetName");
131 #else
132     GetName getName;
133     *(void **) (&getName) = plugin.GetFunction("OrthancPluginGetName");
134 #endif
135 
136     assert(getName != NULL);
137     return getName();
138   }
139 
140 
CallGetVersion(SharedLibrary & plugin)141   static const char* CallGetVersion(SharedLibrary& plugin)
142   {
143     typedef const char* (*GetVersion) ();
144 
145 #if defined(_WIN32)
146     GetVersion getVersion = (GetVersion) plugin.GetFunction("OrthancPluginGetVersion");
147 #else
148     GetVersion getVersion;
149     *(void **) (&getVersion) = plugin.GetFunction("OrthancPluginGetVersion");
150 #endif
151 
152     assert(getVersion != NULL);
153     return getVersion();
154   }
155 
156 
InvokeService(OrthancPluginContext * context,_OrthancPluginService service,const void * params)157   OrthancPluginErrorCode PluginsManager::InvokeService(OrthancPluginContext* context,
158                                                        _OrthancPluginService service,
159                                                        const void* params)
160   {
161     switch (service)
162     {
163       case _OrthancPluginService_LogError:
164         LOG(ERROR) << reinterpret_cast<const char*>(params);
165         return OrthancPluginErrorCode_Success;
166 
167       case _OrthancPluginService_LogWarning:
168         LOG(WARNING) << reinterpret_cast<const char*>(params);
169         return OrthancPluginErrorCode_Success;
170 
171       case _OrthancPluginService_LogInfo:
172         CLOG(INFO, PLUGINS) << reinterpret_cast<const char*>(params);
173         return OrthancPluginErrorCode_Success;
174 
175       default:
176         break;
177     }
178 
179     Plugin* that = reinterpret_cast<Plugin*>(context->pluginsManager);
180 
181     for (std::list<IPluginServiceProvider*>::iterator
182            it = that->GetPluginManager().serviceProviders_.begin();
183          it != that->GetPluginManager().serviceProviders_.end(); ++it)
184     {
185       try
186       {
187         if ((*it)->InvokeService(that->GetSharedLibrary(), service, params))
188         {
189           return OrthancPluginErrorCode_Success;
190         }
191       }
192       catch (OrthancException& e)
193       {
194         // This service provider has failed
195         if (e.GetErrorCode() != ErrorCode_UnknownResource)  // This error code is valid in plugins
196         {
197           LOG(ERROR) << "Exception while invoking plugin service " << service << ": " << e.What();
198         }
199 
200         return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
201       }
202     }
203 
204     LOG(ERROR) << "Plugin invoking unknown service: " << service;
205     return OrthancPluginErrorCode_UnknownPluginService;
206   }
207 
208 
PluginsManager()209   PluginsManager::PluginsManager()
210   {
211   }
212 
~PluginsManager()213   PluginsManager::~PluginsManager()
214   {
215     for (Plugins::iterator it = plugins_.begin(); it != plugins_.end(); ++it)
216     {
217       if (it->second != NULL)
218       {
219         LOG(WARNING) << "Unregistering plugin '" << it->first
220                      << "' (version " << it->second->GetVersion() << ")";
221 
222         CallFinalize(it->second->GetSharedLibrary());
223         delete it->second;
224       }
225     }
226   }
227 
228 
IsOrthancPlugin(SharedLibrary & library)229   static bool IsOrthancPlugin(SharedLibrary& library)
230   {
231     return (library.HasFunction("OrthancPluginInitialize") &&
232             library.HasFunction("OrthancPluginFinalize") &&
233             library.HasFunction("OrthancPluginGetName") &&
234             library.HasFunction("OrthancPluginGetVersion"));
235   }
236 
237 
RegisterPlugin(const std::string & path)238   void PluginsManager::RegisterPlugin(const std::string& path)
239   {
240     if (!boost::filesystem::exists(path))
241     {
242       LOG(ERROR) << "Inexistent path to plugins: " << path;
243       return;
244     }
245 
246     if (boost::filesystem::is_directory(path))
247     {
248       ScanFolderForPlugins(path, false);
249       return;
250     }
251 
252     std::unique_ptr<Plugin> plugin(new Plugin(*this, path));
253 
254     if (!IsOrthancPlugin(plugin->GetSharedLibrary()))
255     {
256       LOG(ERROR) << "Plugin " << plugin->GetSharedLibrary().GetPath()
257                  << " does not declare the proper entry functions";
258       throw OrthancException(ErrorCode_SharedLibrary);
259     }
260 
261     std::string name(CallGetName(plugin->GetSharedLibrary()));
262     if (plugins_.find(name) != plugins_.end())
263     {
264       LOG(ERROR) << "Plugin '" << name << "' already registered";
265       throw OrthancException(ErrorCode_SharedLibrary);
266     }
267 
268     plugin->SetVersion(CallGetVersion(plugin->GetSharedLibrary()));
269     LOG(WARNING) << "Registering plugin '" << name
270                  << "' (version " << plugin->GetVersion() << ")";
271 
272     CallInitialize(plugin->GetSharedLibrary(), plugin->GetContext());
273 
274     plugins_[name] = plugin.release();
275   }
276 
277 
ScanFolderForPlugins(const std::string & folder,bool isRecursive)278   void PluginsManager::ScanFolderForPlugins(const std::string& folder,
279                                             bool isRecursive)
280   {
281     using namespace boost::filesystem;
282 
283     if (!exists(folder))
284     {
285       return;
286     }
287 
288     CLOG(INFO, PLUGINS) << "Scanning folder " << folder << " for plugins";
289 
290     directory_iterator end_it; // default construction yields past-the-end
291     for (directory_iterator it(folder);
292           it != end_it;
293           ++it)
294     {
295       std::string path = it->path().string();
296 
297       if (is_directory(it->status()))
298       {
299         if (isRecursive)
300         {
301           ScanFolderForPlugins(path, true);
302         }
303       }
304       else
305       {
306         std::string extension = boost::filesystem::extension(it->path());
307         Toolbox::ToLowerCase(extension);
308 
309         if (extension == PLUGIN_EXTENSION)
310         {
311           CLOG(INFO, PLUGINS) << "Found a shared library: " << it->path();
312 
313           SharedLibrary plugin(path);
314           if (IsOrthancPlugin(plugin))
315           {
316             RegisterPlugin(path);
317           }
318         }
319       }
320     }
321   }
322 
323 
ListPlugins(std::list<std::string> & result) const324   void PluginsManager::ListPlugins(std::list<std::string>& result) const
325   {
326     result.clear();
327 
328     for (Plugins::const_iterator it = plugins_.begin();
329          it != plugins_.end(); ++it)
330     {
331       result.push_back(it->first);
332     }
333   }
334 
335 
HasPlugin(const std::string & name) const336   bool PluginsManager::HasPlugin(const std::string& name) const
337   {
338     return plugins_.find(name) != plugins_.end();
339   }
340 
341 
GetPluginVersion(const std::string & name) const342   const std::string& PluginsManager::GetPluginVersion(const std::string& name) const
343   {
344     Plugins::const_iterator it = plugins_.find(name);
345     if (it == plugins_.end())
346     {
347       throw OrthancException(ErrorCode_ParameterOutOfRange);
348     }
349     else
350     {
351       return it->second->GetVersion();
352     }
353   }
354 
355 
GetPluginName(SharedLibrary & library)356   std::string PluginsManager::GetPluginName(SharedLibrary& library)
357   {
358     return CallGetName(library);
359   }
360 }
361