pluginloader.cpp
1 //------------------------------------------------------------------------------
2 // pluginloader.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 // 02110-1301, USA.
19 //
20 //------------------------------------------------------------------------------
21 // Copyright (C) 2011 "Blzut3" <admin@maniacsvault.net>
22 //------------------------------------------------------------------------------
23 #include "pluginloader.h"
24 
25 #include "log.h"
26 #include "configuration/doomseekerconfig.h"
27 #include "ini/inisection.h"
28 #include "ini/inivariable.h"
29 #include "plugins/engineplugin.h"
30 #include "serverapi/masterclient.h"
31 #include "strings.h"
32 #include <cassert>
33 #include <QDir>
34 
35 #ifdef Q_OS_WIN32
36  #include <windows.h>
37  #define dlopen(a,b) LoadLibrary(a)
38  #define dlsym(a,b) GetProcAddress(a, b)
39  #define dlclose(a) FreeLibrary(a)
40  #define dlerror() GetLastError()
41  #ifdef _MSC_VER
42  #pragma warning(disable: 4251)
43  #endif
44 #else
45  #include <dlfcn.h>
46  #include <dirent.h>
47 #endif
48 
50 DClass<PluginLoader::Plugin>
51 {
52  public:
53  EnginePlugin *info;
54  QString file;
55  #ifdef Q_OS_WIN32
56  HMODULE library;
57  #else
58  void *library;
59  #endif
60 
61  static bool isForbiddenPlugin(QString file)
62  {
63  return QFileInfo(file).fileName().toLower().contains("vavoom");
64  }
65 };
66 
67 DPointered(PluginLoader::Plugin)
68 
69 PluginLoader::Plugin::Plugin(unsigned int type, QString file)
70 {
71  d->file = file;
72  d->library = NULL;
73  d->info = NULL;
75  {
76  gLog << QObject::tr("Skipping loading of forbidden plugin: %1").arg(file);
77  return;
78  }
79  // Load the library
80  d->library = dlopen(d->file.toUtf8().constData(), RTLD_NOW);
81 
82  if(d->library != NULL)
83  {
84  unsigned int (*doomSeekerABI)() = (unsigned int(*)()) (dlsym(d->library, "doomSeekerABI"));
85  if(!doomSeekerABI || doomSeekerABI() != DOOMSEEKER_ABI_VERSION)
86  {
87  // Unsupported version
88  QString reason;
89  if (doomSeekerABI != NULL)
90  {
91  reason = QObject::tr(
92  "plugin ABI version mismatch; plugin: %1, Doomseeker: %2").arg(
93  doomSeekerABI()).arg(DOOMSEEKER_ABI_VERSION);
94  }
95  else
96  {
97  reason = QObject::tr("plugin doesn't report its ABI version");
98  }
99  gLog << QObject::tr("Cannot load plugin %1, reason: %2.").arg(file, reason);
100  unload();
101  return;
102  }
103 
104  EnginePlugin *(*doomSeekerInit)() = (EnginePlugin *(*)()) (dlsym(d->library, "doomSeekerInit"));
105  if(doomSeekerInit == NULL)
106  { // This is not a valid plugin.
107  unload();
108  return;
109  }
110 
111  d->info = doomSeekerInit();
112  if(!info()->data()->valid)
113  {
114  unload();
115  return;
116  }
117 
118  gLog << QObject::tr("Loaded plugin: \"%1\"!").arg(info()->data()->name);
119  d->info->start();
120  }
121  else
122  {
123  gLog << QObject::tr("Failed to open plugin: %1").arg(file);
124  gLog << QString("Last error was: %1").arg(dlerror());
125  }
126 }
127 
128 PluginLoader::Plugin::~Plugin()
129 {
130  unload();
131 }
132 
133 void *PluginLoader::Plugin::function(const char* func) const
134 {
135  return (void *) dlsym(d->library, func);
136 }
137 
139 {
140  return d->info;
141 }
142 
143 void PluginLoader::Plugin::initConfig()
144 {
145  if (isValid())
146  {
147  IniSection cfgSection = gConfig.iniSectionForPlugin(info()->data()->name);
148  info()->setConfig(cfgSection);
149  }
150 }
151 
152 bool PluginLoader::Plugin::isValid() const
153 {
154  return d->library != NULL;
155 }
156 
157 void PluginLoader::Plugin::unload()
158 {
159  if (d->library != NULL)
160  {
161  dlclose(d->library);
162  d->library = NULL;
163  }
164 }
165 
167 DClass<PluginLoader>
168 {
169  public:
170  unsigned int type;
171  QString pluginsDirectory;
172  QList<PluginLoader::Plugin*> plugins;
173 };
174 
175 DPointered(PluginLoader)
176 
177 PluginLoader *PluginLoader::staticInstance = NULL;
178 
179 PluginLoader::PluginLoader(unsigned int type, const QStringList &directories)
180 {
181  d->type = type;
182  foreach (const QString &dir, directories)
183  {
184  d->pluginsDirectory = dir;
185  if (filesInDir())
186  {
187  break;
188  }
189  }
190  if (numPlugins() == 0) // No plugins?!
191  {
192  gLog << QObject::tr("Failed to locate plugins.");
193  }
194 }
195 
196 PluginLoader::~PluginLoader()
197 {
198  qDeleteAll(d->plugins);
199 }
200 
202 {
203  qDeleteAll(d->plugins);
204  d->plugins.clear();
205 }
206 
208 {
209  if (staticInstance != NULL)
210  {
211  delete staticInstance;
212  staticInstance = NULL;
213  }
214 }
215 
216 bool PluginLoader::filesInDir()
217 {
218  gLog << QString("Attempting to load plugins from directory: %1").arg(d->pluginsDirectory);
219  QDir dir(d->pluginsDirectory);
220  if (!dir.exists())
221  {
222  return false;
223  }
224 #ifdef Q_OS_WIN32
225  QStringList windowsNamesFilter;
226  windowsNamesFilter << "*.dll";
227  dir.setNameFilters(windowsNamesFilter);
228 #endif
229  foreach (const QString& entry, dir.entryList(QDir::Files))
230  {
231  QString pluginFilePath = Strings::combinePaths(d->pluginsDirectory, entry);
232  Plugin *plugin = new Plugin(d->type, pluginFilePath);
233  if (plugin->isValid())
234  d->plugins << plugin;
235  else
236  delete plugin;
237  }
238  return numPlugins() != 0;
239 }
240 
241 EnginePlugin *PluginLoader::info(int pluginIndex) const
242 {
243  const Plugin* p = plugin(pluginIndex);
244  if (p != NULL)
245  {
246  return p->info();
247  }
248  return NULL;
249 }
250 
251 void PluginLoader::init(const QStringList &directories)
252 {
253  if (staticInstance != NULL)
254  {
255  qDebug() << "Attempting to re-init PluginLoader";
256  assert(false);
257  return;
258  }
259  staticInstance = new PluginLoader(MAKEID('E', 'N', 'G', 'N'), directories);
260 }
261 
263 {
264  foreach (Plugin *plugin, d->plugins)
265  {
266  plugin->initConfig();
267  }
268 }
269 
271 {
272  assert(staticInstance != NULL);
273  return staticInstance;
274 }
275 
276 const unsigned int PluginLoader::numPlugins() const
277 {
278  return d->plugins.size();
279 }
280 
281 const QList<PluginLoader::Plugin*> &PluginLoader::plugins() const
282 {
283  return d->plugins;
284 }
285 
286 const PluginLoader::Plugin* PluginLoader::plugin(unsigned int index) const
287 {
288  return d->plugins[index];
289 }
290 
291 int PluginLoader::pluginIndexFromName(const QString& name) const
292 {
293  // Why the mangling?
294  // Ever since version 0.8.1b there was a bug that removed all spacebars
295  // from plugin names. This bug is fixed in a commit made on 2013-11-01,
296  // but the fix breaks at least some parts of configuration for plugins
297  // that have spacebars in their names. For example, all server
298  // configurations for Chocolate Doom won't load anymore. To prevent that,
299  // we need to treat spacebars as non-existent here. Simply put:
300  // "Chocolate Doom" == "ChocolateDoom"
301  QString mangledName = QString(name).replace(" ", "");
302  for (int i = 0; i < d->plugins.size(); ++i)
303  {
304  QString mangledCandidate = QString(d->plugins[i]->info()->data()->name).replace(" ", "");
305  if (mangledName.compare(mangledCandidate) == 0)
306  {
307  return i;
308  }
309  }
310 
311  return -1;
312 }
313 
314 void PluginLoader::resetPluginsDirectory(const QString& pluginsDirectory)
315 {
316  d->pluginsDirectory = pluginsDirectory;
317  clearPlugins();
318  filesInDir();
319 }
320 
321 const PluginLoader::Plugin* PluginLoader::operator[] (unsigned int index) const
322 {
323  return d->plugins[index];
324 }
static QString combinePaths(QString pathFront, QString pathEnd)
Definition: strings.cpp:147
void resetPluginsDirectory(const QString &pluginsDirectory)
Resets the plugins directory, clearing the loaded plugins and getting new loaded plugins in the proce...
const Plugin * plugin(unsigned int index) const
Returns the requested plugin or NULL.
void clearPlugins()
Clears the plugins list.
int pluginIndexFromName(const QString &name) const
Looks for a plugin which name equals to parameter.
EnginePlugin * info(int pluginIndex) const
Convenience method - calls Plugin::info() for specified plugin.
static void deinit()
Destroys the init() instance.
static void init(const QStringList &directories)
Attempts to load plugins from given set of directories.
const unsigned int numPlugins() const
Gets the number of loaded plugins.
static PluginLoader * instance()
Accesses instance of the class after init().
void initConfig()
Inits configuration for plugins.
EnginePlugin * info() const
Main plugin interface.
const Plugin * operator[](unsigned int index) const
Returns the requested plugin or NULL.
Definition: dptr.h:31
INI section representation.
Definition: inisection.h:40
Plugin(unsigned int type, QString file)
Inits a plugin.
void * function(const char *func) const
Returns a pointer to the requested function or NULL.