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");
--- a/version.h	Wed Aug 29 12:06:56 2018 +0100
+++ b/version.h	Fri Aug 31 15:14:57 2018 +0100
@@ -1,1 +1,1 @@
-#define CHECKER_VERSION "2.0"
+#define CHECKER_COMPATIBILITY_VERSION "3"