Mercurial > hg > vamp-plugin-load-checker
changeset 45:ad02dff8ebfb
Merge from branch errorcode
author | Chris Cannam |
---|---|
date | Fri, 31 Aug 2018 15:14:57 +0100 |
parents | a28b19b136e8 (current diff) 0f7df035192d (diff) |
children | d7ec0b2a8802 c7cb58bb7c1f |
files | |
diffstat | 9 files changed, 304 insertions(+), 50 deletions(-) [+] |
line wrap: on
line diff
--- a/checker.pri Wed Aug 29 12:06:56 2018 +0100 +++ b/checker.pri Fri Aug 31 15:14:57 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 Fri Aug 31 15:14:57 2018 +0100 @@ -0,0 +1,77 @@ +/* -*- 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, provided that the library file itself + * exists + */ + 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 Wed Aug 29 12:06:56 2018 +0100 +++ b/checker/knownplugincandidates.h Fri Aug 31 15:14:57 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 Wed Aug 29 12:06:56 2018 +0100 +++ b/checker/plugincandidates.h Fri Aug 31 15:14:57 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-client.cpp Wed Aug 29 12:06:56 2018 +0100 +++ b/src/checker-client.cpp Fri Aug 31 15:14:57 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 Wed Aug 29 12:06:56 2018 +0100 +++ b/src/helper.cpp Fri Aug 31 15:14:57 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,8 +90,10 @@ #ifndef UNICODE #error "This must be compiled with UNICODE defined" #endif + static std::string lastLibraryName = ""; -static HMODULE LoadLibraryUTF8(std::string name) { + +static HMODULE loadLibraryUTF8(std::string name) { lastLibraryName = name; int n = name.size(); int wn = MultiByteToWideChar(CP_UTF8, 0, name.c_str(), n, 0, 0); @@ -90,18 +104,24 @@ delete[] wname; return h; } -static std::string GetErrorText() { + +static std::string getErrorText() { DWORD err = GetLastError(); - wchar_t *buffer; + wchar_t *buffer = 0; FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, + // the correct way to specify the user's default language, + // according to all resources I could find: MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR) &buffer, 0, NULL ); + if (!buffer) { + return "Unable to format error string (internal error)"; + } int wn = wcslen(buffer); int n = WideCharToMultiByte(CP_UTF8, 0, buffer, wn, 0, 0, 0, 0); if (n < 0) { @@ -114,6 +134,9 @@ std::string s(text); LocalFree(&buffer); delete[] text; + if (s == "") { + return s; + } for (int i = s.size(); i > 0; ) { --i; if (s[i] == '\n' || s[i] == '\r') { @@ -126,16 +149,48 @@ } return s; } -#define DLOPEN(a,b) LoadLibraryUTF8(a) + +#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()) +#define DLERROR() (getErrorText()) + +static bool libraryExists(std::string name) { + if (name == "") return false; + int n = name.size(); + int wn = MultiByteToWideChar(CP_UTF8, 0, name.c_str(), n, 0, 0); + wchar_t *wname = new wchar_t[wn+1]; + wn = MultiByteToWideChar(CP_UTF8, 0, name.c_str(), n, wname, wn); + wname[wn] = L'\0'; + FILE *f = _wfopen(wname, L"rb"); + delete[] wname; + if (f) { + fclose(f); + return true; + } else { + return false; + } +} + #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() + +static bool libraryExists(std::string name) { + if (name == "") return false; + FILE *f = fopen(name.c_str(), "r"); + if (f) { + fclose(f); + return true; + } else { + return false; + } +} + #endif //#include <unistd.h> @@ -144,50 +199,70 @@ 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) { + if (libraryExists(soname)) { + code = PluginCheckCode::FAIL_DEPENDENCY_MISSING; + } else { + code = PluginCheckCode::FAIL_LIBRARY_NOT_FOUND; + } + } +#else + if (!libraryExists(soname)) { + code = PluginCheckCode::FAIL_LIBRARY_NOT_FOUND; + } +#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 +270,7 @@ DLCLOSE(handle); - return msg; + return result; } int main(int argc, char **argv) @@ -210,7 +285,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 +314,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 Wed Aug 29 12:06:56 2018 +0100 +++ b/src/knownplugincandidates.cpp Fri Aug 31 15:14:57 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 Wed Aug 29 12:06:56 2018 +0100 +++ b/src/plugincandidates.cpp Fri Aug 31 15:14:57 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); + throw runtime_error("wrong version of plugin load helper found"); + } + 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 = QString(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");