Mercurial > hg > vamp-plugin-load-checker
changeset 40:40c6936c2fc9 errorcode
Return numerical error codes, so the caller can apply i18n on display; distinguish explicitly the common win32 architecture problems
author | Chris Cannam |
---|---|
date | Wed, 29 Aug 2018 17:40:22 +0100 |
parents | a43d7a2867d2 |
children | 7f5cf0fed473 |
files | checker.pri checker/checkcode.h checker/knownplugincandidates.h checker/plugincandidates.h src/checker.cpp src/helper.cpp src/knownplugincandidates.cpp src/plugincandidates.cpp version.h |
diffstat | 9 files changed, 255 insertions(+), 46 deletions(-) [+] |
line wrap: on
line diff
--- a/checker.pri Tue Aug 28 14:33:41 2018 +0100 +++ b/checker.pri Wed Aug 29 17:40:22 2018 +0100 @@ -12,6 +12,7 @@ INCLUDEPATH += checker HEADERS += \ + checker/checkcode.h \ checker/plugincandidates.h \ checker/knownplugincandidates.h \ checker/knownplugins.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/checker/checkcode.h Wed Aug 29 17:40:22 2018 +0100 @@ -0,0 +1,76 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Copyright (c) 2016-2018 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music and Queen Mary, University of London shall not be + used in advertising or otherwise to promote the sale, use or other + dealings in this Software without prior written authorization. +*/ + +#ifndef CHECK_CODE_H +#define CHECK_CODE_H + +enum class PluginCheckCode { + + SUCCESS = 0, + + /** Plugin library file is not found + */ + FAIL_LIBRARY_NOT_FOUND = 1, + + /** Plugin library does appear to be a library, but its + * architecture differs from that of the checker program, in + * a way that can be distinguished from other loader + * failures. On Windows this may arise from system error 193, + * ERROR_BAD_EXE_FORMAT + */ + FAIL_WRONG_ARCHITECTURE = 2, + + /** Plugin library depends on some other library that cannot + * be loaded. On Windows this may arise from system error 126, + * ERROR_MOD_NOT_FOUND + */ + FAIL_DEPENDENCY_MISSING = 3, + + /** Plugin library cannot be loaded for some other reason + */ + FAIL_NOT_LOADABLE = 4, + + /** Plugin library can be loaded, but the expected plugin + * descriptor symbol is missing + */ + FAIL_DESCRIPTOR_MISSING = 5, + + /** Plugin library can be loaded and descriptor called, but no + * plugins are found in it + */ + FAIL_NO_PLUGINS = 6, + + /** Failure but no meaningful error code provided, or failure + * read from an older helper version that did not support + * error codes + */ + FAIL_OTHER = 7 +}; + +#endif
--- a/checker/knownplugincandidates.h Tue Aug 28 14:33:41 2018 +0100 +++ b/checker/knownplugincandidates.h Wed Aug 29 17:40:22 2018 +0100 @@ -76,6 +76,9 @@ return m_helperExecutableName; } + std::vector<PluginCandidates::FailureRec> getFailures() const; + + /** Return a non-localised HTML failure report */ std::string getFailureReport() const; private:
--- a/checker/plugincandidates.h Tue Aug 28 14:33:41 2018 +0100 +++ b/checker/plugincandidates.h Wed Aug 29 17:40:22 2018 +0100 @@ -1,6 +1,6 @@ /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ /* - Copyright (c) 2016 Queen Mary, University of London + Copyright (c) 2016-2018 Queen Mary, University of London Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -34,6 +34,8 @@ #include <vector> #include <map> +#include "checkcode.h" + /** * Class to identify and list candidate shared-library files possibly * containing plugins. Uses a separate process (the "helper", whose @@ -81,9 +83,16 @@ * successfully during the scan for the given tag. */ stringlist getCandidateLibrariesFor(std::string tag) const; + + struct FailureRec { - struct FailureRec { + /// Path of failed library file std::string library; + + /// General class of failure + PluginCheckCode code; + + /// Optional additional system-level message, already translated std::string message; }; @@ -99,6 +108,7 @@ LogCallback *m_logCallback; stringlist getLibrariesInPath(stringlist path); + std::string getHelperCompatibilityVersion(); stringlist runHelper(stringlist libraries, std::string descriptor); void recordResult(std::string tag, stringlist results); void log(std::string);
--- a/src/checker.cpp Tue Aug 28 14:33:41 2018 +0100 +++ b/src/checker.cpp Wed Aug 29 17:40:22 2018 +0100 @@ -35,7 +35,7 @@ struct LogCallback : PluginCandidates::LogCallback { virtual void log(string message) { - cerr << "checker: log: " << message; + cerr << "checker: log: " << message << "\n"; } };
--- a/src/helper.cpp Tue Aug 28 14:33:41 2018 +0100 +++ b/src/helper.cpp Wed Aug 29 17:40:22 2018 +0100 @@ -19,7 +19,17 @@ * SUCCESS|/path/to/libname.so| * * Output line for failed load of library libname.so: - * FAILURE|/path/to/libname.so|Reason for failure if available + * FAILURE|/path/to/libname.so|Error message [failureCode] + * + * or: + * FAILURE|/path/to/libname.so|[failureCode] + * + * where the error message is an optional system-level message, such + * as may be returned from strerror or similar (which should be in the + * native language for the system ready to show the user), and the + * failureCode in square brackets is a mandatory number corresponding + * to one of the PluginCandidates::FailureCode values (requiring + * conversion to a translated string by the client). * * Although this program was written for use with Vamp audio analysis * plugins, it also works with other plugin formats. The program has @@ -64,6 +74,8 @@ #include "../version.h" +#include "../checker/checkcode.h" + static const char programName[] = "vamp-plugin-load-checker"; #ifdef _WIN32 @@ -78,7 +90,9 @@ #ifndef UNICODE #error "This must be compiled with UNICODE defined" #endif + static std::string lastLibraryName = ""; + static HMODULE LoadLibraryUTF8(std::string name) { lastLibraryName = name; int n = name.size(); @@ -90,6 +104,7 @@ delete[] wname; return h; } + static std::string GetErrorText() { DWORD err = GetLastError(); wchar_t *buffer; @@ -99,7 +114,7 @@ FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + MAKELANGID(LANG_USER_DEFAULT, SUBLANG_USER_DEFAULT), (LPWSTR) &buffer, 0, NULL ); int wn = wcslen(buffer); @@ -126,16 +141,20 @@ } return s; } + #define DLOPEN(a,b) LoadLibraryUTF8(a) #define DLSYM(a,b) (void *)GetProcAddress((HINSTANCE)(a),(b).c_str()) #define DLCLOSE(a) (!FreeLibrary((HINSTANCE)(a))) #define DLERROR() (GetErrorText()) + #else + #include <dlfcn.h> #define DLOPEN(a,b) dlopen((a).c_str(),(b)) #define DLSYM(a,b) dlsym((a),(b).c_str()) #define DLCLOSE(a) dlclose((a)) #define DLERROR() dlerror() + #endif //#include <unistd.h> @@ -144,50 +163,62 @@ string error() { - string e = DLERROR(); - if (e == "") return "(unknown error)"; - else return e; + return DLERROR(); } -string checkLADSPAStyleDescriptorFn(void *f) +struct Result { + PluginCheckCode code; + string message; +}; + +Result checkLADSPAStyleDescriptorFn(void *f) { typedef const void *(*DFn)(unsigned long); DFn fn = DFn(f); unsigned long index = 0; while (fn(index)) ++index; - if (index == 0) return "Library contains no plugins"; - return ""; + if (index == 0) return { PluginCheckCode::FAIL_NO_PLUGINS, "" }; + return { PluginCheckCode::SUCCESS, "" }; } -string checkVampDescriptorFn(void *f) +Result checkVampDescriptorFn(void *f) { typedef const void *(*DFn)(unsigned int, unsigned int); DFn fn = DFn(f); unsigned int index = 0; while (fn(2, index)) ++index; - if (index == 0) return "Library contains no plugins"; - return ""; + if (index == 0) return { PluginCheckCode::FAIL_NO_PLUGINS, "" }; + return { PluginCheckCode::SUCCESS, "" }; } -string check(string soname, string descriptor) +Result check(string soname, string descriptor) { void *handle = DLOPEN(soname, RTLD_NOW | RTLD_LOCAL); if (!handle) { - return "Unable to open plugin library: " + error(); + PluginCheckCode code = PluginCheckCode::FAIL_NOT_LOADABLE; + string message = error(); +#ifdef _WIN32 + DWORD err = GetLastError(); + if (err == ERROR_BAD_EXE_FORMAT) { + code = PluginCheckCode::FAIL_WRONG_ARCHITECTURE; + } else if (err == ERROR_MOD_NOT_FOUND) { + code = PluginCheckCode::FAIL_DEPENDENCY_MISSING; + } +#endif + return { code, message }; } - string msg = ""; + Result result { PluginCheckCode::SUCCESS, "" }; void *fn = DLSYM(handle, descriptor); if (!fn) { - msg = "Failed to find plugin descriptor " + descriptor + - " in library: " + error(); + result = { PluginCheckCode::FAIL_DESCRIPTOR_MISSING, error() }; } else if (descriptor == "ladspa_descriptor") { - msg = checkLADSPAStyleDescriptorFn(fn); + result = checkLADSPAStyleDescriptorFn(fn); } else if (descriptor == "dssi_descriptor") { - msg = checkLADSPAStyleDescriptorFn(fn); + result = checkLADSPAStyleDescriptorFn(fn); } else if (descriptor == "vampGetPluginDescriptor") { - msg = checkVampDescriptorFn(fn); + result = checkVampDescriptorFn(fn); } else { cerr << "Note: no descriptor logic known for descriptor function \"" << descriptor << "\"; not actually calling it" << endl; @@ -195,7 +226,7 @@ DLCLOSE(handle); - return msg; + return result; } int main(int argc, char **argv) @@ -210,7 +241,7 @@ if (opt == "-?" || opt == "-h" || opt == "--help") { showUsage = true; } else if (opt == "-v" || opt == "--version") { - cout << CHECKER_VERSION << endl; + cout << CHECKER_COMPATIBILITY_VERSION << endl; return 0; } } @@ -239,12 +270,19 @@ #endif while (getline(cin, soname)) { - string report = check(soname, descriptor); - if (report != "") { - cout << "FAILURE|" << soname << "|" << report << endl; + Result result = check(soname, descriptor); + if (result.code == PluginCheckCode::SUCCESS) { + cout << "SUCCESS|" << soname << "|" << endl; + } else { + if (result.message == "") { + cout << "FAILURE|" << soname + << "|[" << int(result.code) << "]" << endl; + } else { + cout << "FAILURE|" << soname + << "|" << result.message << " [" + << int(result.code) << "]" << endl; + } allGood = false; - } else { - cout << "SUCCESS|" << soname << "|" << endl; } }
--- a/src/knownplugincandidates.cpp Tue Aug 28 14:33:41 2018 +0100 +++ b/src/knownplugincandidates.cpp Wed Aug 29 17:40:22 2018 +0100 @@ -66,8 +66,8 @@ } } -string -KnownPluginCandidates::getFailureReport() const +vector<PluginCandidates::FailureRec> +KnownPluginCandidates::getFailures() const { vector<PluginCandidates::FailureRec> failures; @@ -76,6 +76,13 @@ failures.insert(failures.end(), ff.begin(), ff.end()); } + return failures; +} + +string +KnownPluginCandidates::getFailureReport() const +{ + auto failures = getFailures(); if (failures.empty()) return ""; int n = int(failures.size());
--- a/src/plugincandidates.cpp Tue Aug 28 14:33:41 2018 +0100 +++ b/src/plugincandidates.cpp Wed Aug 29 17:40:22 2018 +0100 @@ -1,6 +1,6 @@ /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ /* - Copyright (c) 2016 Queen Mary, University of London + Copyright (c) 2016-2018 Queen Mary, University of London Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -29,6 +29,8 @@ #include "plugincandidates.h" +#include "../version.h" + #include <set> #include <stdexcept> #include <iostream> @@ -112,6 +114,14 @@ vector<string> pluginPath, string descriptorSymbolName) { + string helperVersion = getHelperCompatibilityVersion(); + if (helperVersion != CHECKER_COMPATIBILITY_VERSION) { + log("wrong plugin checker helper version found: expected v" + + string(CHECKER_COMPATIBILITY_VERSION) + ", found v" + + helperVersion); + return; + } + vector<string> libraries = getLibrariesInPath(pluginPath); vector<string> remaining = libraries; @@ -141,18 +151,14 @@ recordResult(tag, result); } -vector<string> -PluginCandidates::runHelper(vector<string> libraries, string descriptor) +string +PluginCandidates::getHelperCompatibilityVersion() { - vector<string> output; - - log("running helper " + m_helper + " with following library list:"); - for (auto &lib: libraries) log(lib); - QProcess process; process.setReadChannel(QProcess::StandardOutput); process.setProcessChannelMode(QProcess::ForwardedErrorChannel); - process.start(m_helper.c_str(), { descriptor.c_str() }); + process.start(m_helper.c_str(), { "--version" }); + if (!process.waitForStarted()) { QProcess::ProcessError err = process.error(); if (err == QProcess::FailedToStart) { @@ -168,6 +174,47 @@ } throw runtime_error("plugin load helper failed to start"); } + process.waitForFinished(); + + QByteArray output = process.readAllStandardOutput(); + while (output.endsWith('\n') || output.endsWith('\r')) { + output.chop(1); + } + + string versionString = output.toStdString(); + log("read version string from helper: " + versionString); + return versionString; +} + +vector<string> +PluginCandidates::runHelper(vector<string> libraries, string descriptor) +{ + vector<string> output; + + log("running helper " + m_helper + " with following library list:"); + for (auto &lib: libraries) log(lib); + + QProcess process; + process.setReadChannel(QProcess::StandardOutput); + process.setProcessChannelMode(QProcess::ForwardedErrorChannel); + process.start(m_helper.c_str(), { descriptor.c_str() }); + + if (!process.waitForStarted()) { + QProcess::ProcessError err = process.error(); + if (err == QProcess::FailedToStart) { + std::cerr << "Unable to start helper process " << m_helper + << std::endl; + } else if (err == QProcess::Crashed) { + std::cerr << "Helper process " << m_helper + << " crashed on startup" << std::endl; + } else { + std::cerr << "Helper process " << m_helper + << " failed on startup with error code " + << err << std::endl; + } + throw runtime_error("plugin load helper failed to start"); + } + for (auto &lib: libraries) { process.write(lib.c_str(), lib.size()); process.write("\n", 1); @@ -235,16 +282,43 @@ string status = bits[0].toStdString(); string library = bits[1].toStdString(); - if (bits.size() == 2) library = bits[1].trimmed().toStdString(); + if (bits.size() == 2) { + library = bits[1].trimmed().toStdString(); + } - string message = ""; - if (bits.size() > 2) message = bits[2].trimmed().toStdString(); - if (status == "SUCCESS") { m_candidates[tag].push_back(library); } else if (status == "FAILURE") { - m_failures[tag].push_back({ library, message }); + + QString messageAndCode = ""; + if (bits.size() > 2) { + messageAndCode = bits[2].trimmed(); + } + + PluginCheckCode code = PluginCheckCode::FAIL_OTHER; + string message = ""; + + QRegExp codeRE("^(.*) *\\[([0-9]+)\\]$"); + if (codeRE.exactMatch(messageAndCode)) { + QStringList caps(codeRE.capturedTexts()); + if (caps.length() == 3) { + message = caps[1].toStdString(); + code = PluginCheckCode(caps[2].toInt()); + log("split failure report into message and failure code " + + caps[2].toStdString()); + } else { + log("unable to split out failure code from report"); + } + } else { + log("failure message does not give a failure code"); + } + + if (message == "") { + message = messageAndCode.toStdString(); + } + + m_failures[tag].push_back({ library, code, message }); } else { log("unexpected status \"" + status + "\" in output line");