annotate plugin/PluginScan.cpp @ 1562:830972646ccd

Handle security errors, format slightly differently for message box
author Chris Cannam
date Fri, 02 Nov 2018 14:40:44 +0000
parents d7fdc77252c6
children 232d6ddf257d
rev   line source
Chris@1178 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@1178 2
Chris@1178 3 /*
Chris@1178 4 Sonic Visualiser
Chris@1178 5 An audio file viewer and annotation editor.
Chris@1178 6 Centre for Digital Music, Queen Mary, University of London.
Chris@1178 7
Chris@1178 8 This program is free software; you can redistribute it and/or
Chris@1178 9 modify it under the terms of the GNU General Public License as
Chris@1178 10 published by the Free Software Foundation; either version 2 of the
Chris@1178 11 License, or (at your option) any later version. See the file
Chris@1178 12 COPYING included with this distribution for more information.
Chris@1178 13 */
Chris@1178 14
Chris@1178 15 #include "PluginScan.h"
Chris@1178 16
Chris@1178 17 #include "base/Debug.h"
Chris@1246 18 #include "base/Preferences.h"
Chris@1241 19 #include "base/HelperExecPath.h"
Chris@1178 20
Chris@1501 21 #include <sstream>
Chris@1180 22
Chris@1178 23 #include <QMutex>
Chris@1181 24 #include <QCoreApplication>
Chris@1178 25
Chris@1178 26 using std::string;
Chris@1178 27
Chris@1249 28 class PluginScan::Logger
Chris@1249 29 #ifdef HAVE_PLUGIN_CHECKER_HELPER
Chris@1249 30 : public PluginCandidates::LogCallback
Chris@1249 31 #endif
Chris@1180 32 {
Chris@1180 33 protected:
Chris@1180 34 void log(std::string message) {
Chris@1264 35 SVDEBUG << "PluginScan: " << message << endl;
Chris@1180 36 }
Chris@1180 37 };
Chris@1180 38
Chris@1180 39 PluginScan *PluginScan::getInstance()
Chris@1180 40 {
Chris@1178 41 static QMutex mutex;
Chris@1178 42 static PluginScan *m_instance = 0;
Chris@1178 43 mutex.lock();
Chris@1178 44 if (!m_instance) m_instance = new PluginScan();
Chris@1178 45 mutex.unlock();
Chris@1178 46 return m_instance;
Chris@1178 47 }
Chris@1178 48
Chris@1246 49 PluginScan::PluginScan() : m_succeeded(false), m_logger(new Logger) {
Chris@1178 50 }
Chris@1178 51
Chris@1178 52 PluginScan::~PluginScan() {
Chris@1246 53 QMutexLocker locker(&m_mutex);
Chris@1241 54 clear();
Chris@1180 55 delete m_logger;
Chris@1396 56 SVDEBUG << "PluginScan::~PluginScan completed" << endl;
Chris@1178 57 }
Chris@1178 58
Chris@1178 59 void
Chris@1241 60 PluginScan::scan()
Chris@1178 61 {
Chris@1249 62 #ifdef HAVE_PLUGIN_CHECKER_HELPER
Chris@1249 63
Chris@1246 64 QMutexLocker locker(&m_mutex);
Chris@1246 65
Chris@1246 66 bool inProcess = Preferences::getInstance()->getRunPluginsInProcess();
Chris@1246 67
Chris@1246 68 HelperExecPath hep(inProcess ?
Chris@1246 69 HelperExecPath::NativeArchitectureOnly :
Chris@1246 70 HelperExecPath::AllInstalled);
Chris@1246 71
Chris@1352 72 QString helperName("vamp-plugin-load-checker");
Chris@1246 73 auto helpers = hep.getHelperExecutables(helperName);
Chris@1241 74
Chris@1241 75 clear();
Chris@1241 76
Chris@1246 77 for (auto p: helpers) {
Chris@1247 78 SVDEBUG << "NOTE: PluginScan: Found helper: " << p.executable << endl;
Chris@1246 79 }
Chris@1246 80
Chris@1246 81 if (helpers.empty()) {
Chris@1247 82 SVDEBUG << "NOTE: No plugin checker helpers found in installation;"
Chris@1246 83 << " found none of the following:" << endl;
Chris@1246 84 for (auto d: hep.getHelperCandidatePaths(helperName)) {
Chris@1247 85 SVDEBUG << "NOTE: " << d << endl;
Chris@1246 86 }
Chris@1246 87 }
Chris@1246 88
Chris@1246 89 for (auto p: helpers) {
Chris@1241 90 try {
Chris@1474 91 KnownPluginCandidates *kp = new KnownPluginCandidates
Chris@1246 92 (p.executable.toStdString(), m_logger);
Chris@1246 93 if (m_kp.find(p.tag) != m_kp.end()) {
Chris@1247 94 SVDEBUG << "WARNING: PluginScan::scan: Duplicate tag " << p.tag
Chris@1246 95 << " for helpers" << endl;
Chris@1246 96 continue;
Chris@1246 97 }
Chris@1246 98 m_kp[p.tag] = kp;
Chris@1241 99 m_succeeded = true;
Chris@1241 100 } catch (const std::exception &e) {
Chris@1247 101 SVDEBUG << "ERROR: PluginScan::scan: " << e.what()
Chris@1246 102 << " (with helper path = " << p.executable << ")" << endl;
Chris@1241 103 }
Chris@1241 104 }
Chris@1249 105
Chris@1396 106 SVDEBUG << "PluginScan::scan complete" << endl;
Chris@1249 107 #endif
Chris@1241 108 }
Chris@1241 109
Chris@1246 110 bool
Chris@1246 111 PluginScan::scanSucceeded() const
Chris@1246 112 {
Chris@1246 113 QMutexLocker locker(&m_mutex);
Chris@1246 114 return m_succeeded;
Chris@1246 115 }
Chris@1246 116
Chris@1241 117 void
Chris@1241 118 PluginScan::clear()
Chris@1241 119 {
Chris@1246 120 for (auto &p: m_kp) {
Chris@1246 121 delete p.second;
Chris@1246 122 }
Chris@1241 123 m_kp.clear();
Chris@1179 124 m_succeeded = false;
Chris@1179 125 }
Chris@1179 126
Chris@1246 127 QList<PluginScan::Candidate>
Chris@1278 128 PluginScan::getCandidateLibrariesFor(PluginType
Chris@1278 129 #ifdef HAVE_PLUGIN_CHECKER_HELPER
Chris@1278 130 type
Chris@1278 131 #endif
Chris@1278 132 ) const
Chris@1179 133 {
Chris@1249 134 #ifdef HAVE_PLUGIN_CHECKER_HELPER
Chris@1249 135
Chris@1246 136 QMutexLocker locker(&m_mutex);
Chris@1246 137
Chris@1180 138 KnownPlugins::PluginType kpt;
Chris@1180 139 switch (type) {
Chris@1180 140 case VampPlugin: kpt = KnownPlugins::VampPlugin; break;
Chris@1180 141 case LADSPAPlugin: kpt = KnownPlugins::LADSPAPlugin; break;
Chris@1180 142 case DSSIPlugin: kpt = KnownPlugins::DSSIPlugin; break;
Chris@1180 143 default: throw std::logic_error("Inconsistency in plugin type enums");
Chris@1180 144 }
Chris@1180 145
Chris@1246 146 QList<Candidate> candidates;
Chris@1245 147
Chris@1246 148 for (auto rec: m_kp) {
Chris@1245 149
Chris@1474 150 KnownPluginCandidates *kp = rec.second;
Chris@1246 151
Chris@1241 152 auto c = kp->getCandidateLibrariesFor(kpt);
Chris@1245 153
Chris@1247 154 SVDEBUG << "PluginScan: helper \"" << kp->getHelperExecutableName()
Chris@1247 155 << "\" likes " << c.size() << " libraries of type "
Chris@1247 156 << kp->getTagFor(kpt) << endl;
Chris@1245 157
Chris@1246 158 for (auto s: c) {
Chris@1246 159 candidates.push_back({ s.c_str(), rec.first });
Chris@1246 160 }
Chris@1245 161
Chris@1245 162 if (type != VampPlugin) {
Chris@1245 163 // We are only interested in querying multiple helpers
Chris@1245 164 // when dealing with Vamp plugins, for which we can use
Chris@1245 165 // external servers and so in some cases can support
Chris@1245 166 // additional architectures. Other plugin formats are
Chris@1245 167 // loaded directly and so must match the host, which is
Chris@1245 168 // what the first helper is supposed to handle -- so
Chris@1245 169 // break after the first one if not querying Vamp
Chris@1245 170 break;
Chris@1245 171 }
Chris@1241 172 }
Chris@1245 173
Chris@1179 174 return candidates;
Chris@1249 175
Chris@1249 176 #else
Chris@1249 177 return {};
Chris@1249 178 #endif
Chris@1178 179 }
Chris@1178 180
Chris@1501 181 #ifdef HAVE_PLUGIN_CHECKER_HELPER
Chris@1501 182 QString
Chris@1501 183 PluginScan::formatFailureReport(QString tag,
Chris@1501 184 std::vector<PluginCandidates::FailureRec> failures) const
Chris@1501 185 {
Chris@1501 186 int n = int(failures.size());
Chris@1501 187 int i = 0;
Chris@1501 188
Chris@1501 189 std::ostringstream os;
Chris@1501 190
Chris@1501 191 os << "<ul>";
Chris@1501 192 for (auto f: failures) {
Chris@1562 193 os << "<li><code>" + f.library + "</code>";
Chris@1501 194
Chris@1501 195 SVDEBUG << "PluginScan::formatFailureReport: tag is \"" << tag
Chris@1501 196 << "\", failure code is " << int(f.code) << ", message is \""
Chris@1501 197 << f.message << "\"" << endl;
Chris@1501 198
Chris@1501 199 QString userMessage = QString::fromStdString(f.message);
Chris@1501 200
Chris@1501 201 switch (f.code) {
Chris@1501 202
Chris@1501 203 case PluginCheckCode::FAIL_LIBRARY_NOT_FOUND:
Chris@1501 204 userMessage = QObject::tr("Library file could not be opened");
Chris@1501 205 break;
Chris@1501 206
Chris@1501 207 case PluginCheckCode::FAIL_WRONG_ARCHITECTURE:
Chris@1501 208 if (tag == "64" || (sizeof(void *) == 8 && tag == "")) {
Chris@1501 209 userMessage = QObject::tr
Chris@1505 210 ("Library has wrong architecture - possibly a 32-bit plugin installed in a 64-bit plugin folder");
Chris@1501 211 } else if (tag == "32" || (sizeof(void *) == 4 && tag == "")) {
Chris@1501 212 userMessage = QObject::tr
Chris@1505 213 ("Library has wrong architecture - possibly a 64-bit plugin installed in a 32-bit plugin folder");
Chris@1501 214 }
Chris@1501 215 break;
Chris@1501 216
Chris@1501 217 case PluginCheckCode::FAIL_DEPENDENCY_MISSING:
Chris@1501 218 userMessage = QObject::tr
Chris@1501 219 ("Library depends on another library that cannot be found: %1")
Chris@1501 220 .arg(userMessage);
Chris@1501 221 break;
Chris@1501 222
Chris@1501 223 case PluginCheckCode::FAIL_NOT_LOADABLE:
Chris@1501 224 userMessage = QObject::tr
Chris@1501 225 ("Library cannot be loaded: %1").arg(userMessage);
Chris@1501 226 break;
Chris@1501 227
Chris@1562 228 case PluginCheckCode::FAIL_FORBIDDEN:
Chris@1562 229 userMessage = QObject::tr
Chris@1562 230 ("Permission to load library was refused");
Chris@1562 231 break;
Chris@1562 232
Chris@1501 233 case PluginCheckCode::FAIL_DESCRIPTOR_MISSING:
Chris@1501 234 userMessage = QObject::tr
Chris@1501 235 ("Not a valid plugin library (no descriptor found)");
Chris@1501 236 break;
Chris@1501 237
Chris@1501 238 case PluginCheckCode::FAIL_NO_PLUGINS:
Chris@1501 239 userMessage = QObject::tr
Chris@1501 240 ("Library contains no plugins");
Chris@1501 241 break;
Chris@1501 242
Chris@1501 243 case PluginCheckCode::FAIL_OTHER:
Chris@1501 244 if (userMessage == "") {
Chris@1501 245 userMessage = QObject::tr
Chris@1501 246 ("Unknown error");
Chris@1501 247 }
Chris@1501 248 break;
Chris@1501 249
Chris@1501 250 case PluginCheckCode::SUCCESS:
Chris@1501 251 // success shouldn't happen here!
Chris@1501 252 break;
Chris@1501 253 }
Chris@1501 254
Chris@1501 255 os << "<br><i>" + userMessage.toStdString() + "</i>";
Chris@1501 256 os << "</li>";
Chris@1501 257
Chris@1501 258 if (n > 10) {
Chris@1501 259 if (++i == 5) {
Chris@1501 260 os << "<li>";
Chris@1501 261 os << QObject::tr("... and %n further failure(s)",
Chris@1501 262 "", n - i)
Chris@1501 263 .toStdString();
Chris@1501 264 os << "</li>";
Chris@1501 265 break;
Chris@1501 266 }
Chris@1501 267 }
Chris@1501 268 }
Chris@1501 269 os << "</ul>";
Chris@1501 270
Chris@1501 271 return QString::fromStdString(os.str());
Chris@1501 272 }
Chris@1501 273 #endif
Chris@1501 274
Chris@1178 275 QString
Chris@1178 276 PluginScan::getStartupFailureReport() const
Chris@1178 277 {
Chris@1249 278 #ifdef HAVE_PLUGIN_CHECKER_HELPER
Chris@1249 279
Chris@1246 280 QMutexLocker locker(&m_mutex);
Chris@1246 281
Chris@1179 282 if (!m_succeeded) {
Chris@1429 283 return QObject::tr("<b>Failed to scan for plugins</b>"
Chris@1429 284 "<p>Failed to scan for plugins at startup. Possibly "
Chris@1352 285 "the plugin checker program was not correctly "
Chris@1181 286 "installed alongside %1?</p>")
Chris@1181 287 .arg(QCoreApplication::applicationName());
Chris@1179 288 }
Chris@1241 289 if (m_kp.empty()) {
Chris@1429 290 return QObject::tr("<b>Did not scan for plugins</b>"
Chris@1429 291 "<p>Apparently no scan for plugins was attempted "
Chris@1429 292 "(internal error?)</p>");
Chris@1179 293 }
Chris@1179 294
Chris@1241 295 QString report;
Chris@1241 296 for (auto kp: m_kp) {
Chris@1501 297 auto failures = kp.second->getFailures();
Chris@1502 298 if (!failures.empty()) {
Chris@1502 299 report += formatFailureReport(kp.first, failures);
Chris@1502 300 }
Chris@1241 301 }
Chris@1179 302 if (report == "") {
Chris@1429 303 return report;
Chris@1179 304 }
Chris@1179 305
Chris@1562 306 return QObject::tr("<p>Failed to load one or more plugin libraries:</p>")
Chris@1429 307 + report
Chris@1181 308 + QObject::tr("<p>These plugins may be incompatible with the system, "
Chris@1181 309 "and will be ignored during this run of %1.</p>")
Chris@1181 310 .arg(QCoreApplication::applicationName());
Chris@1249 311
Chris@1249 312 #else
Chris@1249 313 return "";
Chris@1249 314 #endif
Chris@1178 315 }
Chris@1178 316