1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
2 
3 /*
4     Sonic Visualiser
5     An audio file viewer and annotation editor.
6     Centre for Digital Music, Queen Mary, University of London.
7     This file copyright 2006-2016 Chris Cannam and QMUL.
8 
9     This program is free software; you can redistribute it and/or
10     modify it under the terms of the GNU General Public License as
11     published by the Free Software Foundation; either version 2 of the
12     License, or (at your option) any later version.  See the file
13     COPYING included with this distribution for more information.
14 */
15 
16 #include "NativeVampPluginFactory.h"
17 #include "PluginIdentifier.h"
18 
19 #include <vamp-hostsdk/PluginHostAdapter.h>
20 #include <vamp-hostsdk/PluginWrapper.h>
21 
22 #include "system/System.h"
23 
24 #include "PluginScan.h"
25 
26 #include <QDir>
27 #include <QFile>
28 #include <QFileInfo>
29 #include <QTextStream>
30 
31 #include <iostream>
32 
33 #include "base/Profiler.h"
34 
35 #include <QMutex>
36 #include <QMutexLocker>
37 
38 using namespace std;
39 
40 //#define DEBUG_PLUGIN_SCAN_AND_INSTANTIATE 1
41 
42 class PluginDeletionNotifyAdapter : public Vamp::HostExt::PluginWrapper {
43 public:
PluginDeletionNotifyAdapter(Vamp::Plugin * plugin,NativeVampPluginFactory * factory)44     PluginDeletionNotifyAdapter(Vamp::Plugin *plugin,
45                                 NativeVampPluginFactory *factory) :
46         PluginWrapper(plugin), m_factory(factory) { }
47     ~PluginDeletionNotifyAdapter() override;
48 protected:
49     NativeVampPluginFactory *m_factory;
50 };
51 
~PluginDeletionNotifyAdapter()52 PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter()
53 {
54     // see notes in vamp-sdk/hostext/PluginLoader.cpp from which this is drawn
55     Vamp::Plugin *p = m_plugin;
56 
57 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
58     SVCERR << "PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter("
59            << this << " for plugin " << p << ")" << endl;
60 #endif
61 
62     delete m_plugin;
63     m_plugin = nullptr;
64     // acceptable use after free here, as pluginDeleted uses p only as
65     // pointer key and does not deref it
66     if (m_factory) m_factory->pluginDeleted(p);
67 }
68 
69 vector<QString>
getPluginPath()70 NativeVampPluginFactory::getPluginPath()
71 {
72     if (!m_pluginPath.empty()) return m_pluginPath;
73 
74     vector<string> p = Vamp::PluginHostAdapter::getPluginPath();
75     for (size_t i = 0; i < p.size(); ++i) m_pluginPath.push_back(p[i].c_str());
76     return m_pluginPath;
77 }
78 
79 static
80 QList<PluginScan::Candidate>
getCandidateLibraries()81 getCandidateLibraries()
82 {
83 #ifdef HAVE_PLUGIN_CHECKER_HELPER
84     return PluginScan::getInstance()->getCandidateLibrariesFor
85         (PluginScan::VampPlugin);
86 #else
87     auto path = Vamp::PluginHostAdapter::getPluginPath();
88     QList<PluginScan::Candidate> candidates;
89     for (string dirname: path) {
90         SVDEBUG << "NativeVampPluginFactory: scanning directory myself: "
91                 << dirname << endl;
92 #if defined(_WIN32)
93 #define PLUGIN_GLOB "*.dll"
94 #elif defined(__APPLE__)
95 #define PLUGIN_GLOB "*.dylib *.so"
96 #else
97 #define PLUGIN_GLOB "*.so"
98 #endif
99         QDir dir(dirname.c_str(), PLUGIN_GLOB,
100                  QDir::Name | QDir::IgnoreCase,
101                  QDir::Files | QDir::Readable);
102 
103         for (unsigned int i = 0; i < dir.count(); ++i) {
104             QString libpath = dir.filePath(dir[i]);
105             candidates.push_back({ libpath, "" });
106         }
107     }
108 
109     return candidates;
110 #endif
111 }
112 
113 vector<QString>
getPluginIdentifiers(QString &)114 NativeVampPluginFactory::getPluginIdentifiers(QString &)
115 {
116     Profiler profiler("NativeVampPluginFactory::getPluginIdentifiers");
117 
118     QMutexLocker locker(&m_mutex);
119 
120     if (!m_identifiers.empty()) {
121         return m_identifiers;
122     }
123 
124     auto candidates = getCandidateLibraries();
125 
126     SVDEBUG << "INFO: Have " << candidates.size() << " candidate Vamp plugin libraries" << endl;
127 
128     for (auto candidate : candidates) {
129 
130         QString libpath = candidate.libraryPath;
131 
132         SVDEBUG << "INFO: Considering candidate Vamp plugin library " << libpath << endl;
133 
134         void *libraryHandle = DLOPEN(libpath, RTLD_LAZY | RTLD_LOCAL);
135 
136         if (!libraryHandle) {
137             SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to load library " << libpath << ": " << DLERROR() << endl;
138             continue;
139         }
140 
141         VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction)
142             DLSYM(libraryHandle, "vampGetPluginDescriptor");
143 
144         if (!fn) {
145             SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: No descriptor function in " << libpath << endl;
146             if (DLCLOSE(libraryHandle) != 0) {
147                 SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to unload library " << libpath << endl;
148             }
149             continue;
150         }
151 
152 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
153         SVCERR << "NativeVampPluginFactory::getPluginIdentifiers: Vamp descriptor found" << endl;
154 #endif
155 
156         const VampPluginDescriptor *descriptor = nullptr;
157         int index = 0;
158 
159         map<string, int> known;
160         bool ok = true;
161 
162         while ((descriptor = fn(VAMP_API_VERSION, index))) {
163 
164             if (known.find(descriptor->identifier) != known.end()) {
165                 SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Plugin library "
166                         << libpath
167                         << " returns the same plugin identifier \""
168                         << descriptor->identifier << "\" at indices "
169                         << known[descriptor->identifier] << " and "
170                         << index << endl;
171                 SVDEBUG << "NativeVampPluginFactory::getPluginIdentifiers: Avoiding this library (obsolete API?)" << endl;
172                 ok = false;
173                 break;
174             } else {
175                 known[descriptor->identifier] = index;
176             }
177 
178             ++index;
179         }
180 
181         if (ok) {
182 
183             index = 0;
184 
185             while ((descriptor = fn(VAMP_API_VERSION, index))) {
186 
187                 QString id = PluginIdentifier::createIdentifier
188                     ("vamp", libpath, descriptor->identifier);
189                 m_identifiers.push_back(id);
190                 m_libraries[id] = libpath;
191 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
192                 SVCERR << "NativeVampPluginFactory::getPluginIdentifiers: Found plugin id " << id << " at index " << index << endl;
193 #endif
194                 ++index;
195             }
196         }
197 
198 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
199         SVCERR << "NativeVampPluginFactory::getPluginIdentifiers: unloading library " << libraryHandle << endl;
200 #endif
201 
202         if (DLCLOSE(libraryHandle) != 0) {
203             SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to unload library " << libpath << endl;
204         }
205     }
206 
207     generateTaxonomy();
208 
209     // Plugins can change the locale, revert it to default.
210     RestoreStartupLocale();
211 
212     return m_identifiers;
213 }
214 
215 QString
findPluginFile(QString soname,QString inDir)216 NativeVampPluginFactory::findPluginFile(QString soname, QString inDir)
217 {
218     QString file = "";
219 
220 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
221     SVCERR << "NativeVampPluginFactory::findPluginFile(\""
222               << soname << "\", \"" << inDir << "\")"
223               << endl;
224 #endif
225 
226     if (inDir != "") {
227 
228         QDir dir(inDir, PLUGIN_GLOB,
229                  QDir::Name | QDir::IgnoreCase,
230                  QDir::Files | QDir::Readable);
231         if (!dir.exists()) return "";
232 
233         file = dir.filePath(QFileInfo(soname).fileName());
234 
235         if (QFileInfo(file).exists() && QFileInfo(file).isFile()) {
236 
237 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
238             SVCERR << "NativeVampPluginFactory::findPluginFile: "
239                       << "found trivially at " << file << endl;
240 #endif
241 
242             return file;
243         }
244 
245         for (unsigned int j = 0; j < dir.count(); ++j) {
246             file = dir.filePath(dir[j]);
247             if (QFileInfo(file).baseName() == QFileInfo(soname).baseName()) {
248 
249 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
250                 SVCERR << "NativeVampPluginFactory::findPluginFile: "
251                           << "found \"" << soname << "\" at " << file << endl;
252 #endif
253 
254                 return file;
255             }
256         }
257 
258 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
259         SVCERR << "NativeVampPluginFactory::findPluginFile (with dir): "
260                   << "not found" << endl;
261 #endif
262 
263         return "";
264 
265     } else {
266 
267         QFileInfo fi(soname);
268 
269         if (fi.isAbsolute() && fi.exists() && fi.isFile()) {
270 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
271             SVCERR << "NativeVampPluginFactory::findPluginFile: "
272                       << "found trivially at " << soname << endl;
273 #endif
274             return soname;
275         }
276 
277         if (fi.isAbsolute() && fi.absolutePath() != "") {
278             file = findPluginFile(soname, fi.absolutePath());
279             if (file != "") return file;
280         }
281 
282         vector<QString> path = getPluginPath();
283         for (vector<QString>::iterator i = path.begin();
284              i != path.end(); ++i) {
285             if (*i != "") {
286                 file = findPluginFile(soname, *i);
287                 if (file != "") return file;
288             }
289         }
290 
291 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
292         SVCERR << "NativeVampPluginFactory::findPluginFile: "
293                   << "not found" << endl;
294 #endif
295 
296         return "";
297     }
298 }
299 
300 Vamp::Plugin *
instantiatePlugin(QString identifier,sv_samplerate_t inputSampleRate)301 NativeVampPluginFactory::instantiatePlugin(QString identifier,
302                                            sv_samplerate_t inputSampleRate)
303 {
304     Profiler profiler("NativeVampPluginFactory::instantiatePlugin");
305 
306     Vamp::Plugin *rv = nullptr;
307     Vamp::PluginHostAdapter *plugin = nullptr;
308 
309     const VampPluginDescriptor *descriptor = nullptr;
310     int index = 0;
311 
312     QString type, soname, label;
313     PluginIdentifier::parseIdentifier(identifier, type, soname, label);
314     if (type != "vamp") {
315 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
316         SVCERR << "NativeVampPluginFactory::instantiatePlugin: Wrong factory for plugin type " << type << endl;
317 #endif
318         return nullptr;
319     }
320 
321     QString found = findPluginFile(soname);
322 
323     if (found == "") {
324         SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: Failed to find library file " << soname << endl;
325         return nullptr;
326     } else if (found != soname) {
327 
328 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
329         SVCERR << "NativeVampPluginFactory::instantiatePlugin: Given library name was " << soname << ", found at " << found << endl;
330         SVCERR << soname << " -> " << found << endl;
331 #endif
332 
333     }
334 
335     soname = found;
336 
337     void *libraryHandle = DLOPEN(soname, RTLD_LAZY | RTLD_LOCAL);
338 
339     if (!libraryHandle) {
340         SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: Failed to load library " << soname << ": " << DLERROR() << endl;
341         return nullptr;
342     }
343 
344     VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction)
345         DLSYM(libraryHandle, "vampGetPluginDescriptor");
346 
347     if (!fn) {
348         SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: No descriptor function in " << soname << endl;
349         goto done;
350     }
351 
352     while ((descriptor = fn(VAMP_API_VERSION, index))) {
353         if (label == descriptor->identifier) break;
354         ++index;
355     }
356 
357     if (!descriptor) {
358         SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: Failed to find plugin \"" << label << "\" in library " << soname << endl;
359         goto done;
360     }
361 
362     plugin = new Vamp::PluginHostAdapter(descriptor, float(inputSampleRate));
363 
364     if (plugin) {
365         m_handleMap[plugin] = libraryHandle;
366         rv = new PluginDeletionNotifyAdapter(plugin, this);
367     }
368 
369 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
370     if (rv) {
371         SVCERR << "NativeVampPluginFactory::instantiatePlugin: Instantiated plugin " << label << " from library " << soname << ": descriptor " << descriptor << ", rv "<< rv << ", label " << rv->getName() << ", outputs " << rv->getOutputDescriptors().size() << endl;
372     }
373 #endif
374 
375 done:
376     if (!rv) {
377         SVCERR << "NativeVampPluginFactory::instantiatePlugin: Failed to construct plugin" << endl;
378         if (DLCLOSE(libraryHandle) != 0) {
379             SVDEBUG << "WARNING: NativeVampPluginFactory::instantiatePlugin: Failed to unload library " << soname << endl;
380         }
381     }
382 
383     return rv;
384 }
385 
386 void
pluginDeleted(Vamp::Plugin * plugin)387 NativeVampPluginFactory::pluginDeleted(Vamp::Plugin *plugin)
388 {
389     void *handle = m_handleMap[plugin];
390     if (!handle) return;
391 
392     m_handleMap.erase(plugin);
393 
394 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
395     SVCERR << "NativeVampPluginFactory::pluginDeleted: Removed from handle map, which now has " << m_handleMap.size() << " entries" << endl;
396 #endif
397 
398     for (auto h: m_handleMap) {
399         if (h.second == handle) {
400             // still in use
401             SVDEBUG << "NativeVampPluginFactory::pluginDeleted: Not unloading library " << handle << " as other plugins are still loaded from it" << endl;
402             return;
403         }
404     }
405 
406     SVDEBUG << "NativeVampPluginFactory::pluginDeleted: Unloading library " << handle << " after last plugin from this library " << plugin << " was deleted" << endl;
407     DLCLOSE(handle);
408 }
409 
410 QString
getPluginCategory(QString identifier)411 NativeVampPluginFactory::getPluginCategory(QString identifier)
412 {
413     return m_taxonomy[identifier];
414 }
415 
416 QString
getPluginLibraryPath(QString identifier)417 NativeVampPluginFactory::getPluginLibraryPath(QString identifier)
418 {
419     return m_libraries[identifier];
420 }
421 
422 void
generateTaxonomy()423 NativeVampPluginFactory::generateTaxonomy()
424 {
425     vector<QString> pluginPath = getPluginPath();
426     vector<QString> path;
427 
428     for (size_t i = 0; i < pluginPath.size(); ++i) {
429         if (pluginPath[i].contains("/lib/")) {
430             QString p(pluginPath[i]);
431             path.push_back(p);
432             p.replace("/lib/", "/share/");
433             path.push_back(p);
434         }
435         path.push_back(pluginPath[i]);
436     }
437 
438     for (size_t i = 0; i < path.size(); ++i) {
439 
440         QDir dir(path[i], "*.cat");
441 
442 //        SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: directory " << path[i] << " has " << dir.count() << " .cat files" << endl;
443         for (unsigned int j = 0; j < dir.count(); ++j) {
444 
445             QFile file(path[i] + "/" + dir[j]);
446 
447 //            SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: about to open " << (path[i]+ "/" + dir[j]) << endl;
448 
449             if (file.open(QIODevice::ReadOnly)) {
450                 QTextStream stream(&file);
451                 QString line;
452 
453                 while (!stream.atEnd()) {
454                     line = stream.readLine();
455                     QString id = PluginIdentifier::canonicalise
456                         (line.section("::", 0, 0));
457                     QString cat = line.section("::", 1, 1);
458                     m_taxonomy[id] = cat;
459                 }
460             }
461         }
462     }
463 }
464 
465 piper_vamp::PluginStaticData
getPluginStaticData(QString identifier)466 NativeVampPluginFactory::getPluginStaticData(QString identifier)
467 {
468     QMutexLocker locker(&m_mutex);
469 
470     if (m_pluginData.find(identifier) != m_pluginData.end()) {
471         return m_pluginData[identifier];
472     }
473 
474     QString type, soname, label;
475     PluginIdentifier::parseIdentifier(identifier, type, soname, label);
476     std::string pluginKey = (soname + ":" + label).toStdString();
477 
478     std::vector<std::string> catlist;
479     for (auto s: getPluginCategory(identifier).split(" > ")) {
480         catlist.push_back(s.toStdString());
481     }
482 
483     Vamp::Plugin *p = instantiatePlugin(identifier, 44100);
484     if (!p) return {};
485 
486     auto psd = piper_vamp::PluginStaticData::fromPlugin(pluginKey,
487                                                         catlist,
488                                                         p);
489 
490     delete p;
491 
492     m_pluginData[identifier] = psd;
493     return psd;
494 }
495 
496