├── version.h ├── checker-lib.pro ├── .hgignore ├── checker.pro ├── checker-client.pro ├── checker.pri ├── helper.pro ├── COPYING ├── src ├── checker-client.cpp ├── knownplugincandidates.cpp ├── knownplugins.cpp ├── plugincandidates.cpp └── helper.cpp ├── checker ├── checkcode.h ├── knownplugins.h ├── knownplugincandidates.h └── plugincandidates.h └── README /version.h: -------------------------------------------------------------------------------- 1 | #define CHECKER_COMPATIBILITY_VERSION "4" 2 | -------------------------------------------------------------------------------- /checker-lib.pro: -------------------------------------------------------------------------------- 1 | 2 | TEMPLATE = lib 3 | 4 | CONFIG += staticlib 5 | 6 | include(checker.pri) 7 | 8 | TARGET = checker 9 | 10 | 11 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | *~ 3 | *.o 4 | *.a 5 | Makefile* 6 | checker-client 7 | plugin-checker-helper 8 | vamp-plugin-load-checker 9 | *.vcxproj* 10 | *.sln 11 | *.tlog 12 | o 13 | *.exe 14 | *.lib 15 | -------------------------------------------------------------------------------- /checker.pro: -------------------------------------------------------------------------------- 1 | 2 | TEMPLATE = subdirs 3 | SUBDIRS = sub_checker_lib sub_checker_client sub_helper 4 | 5 | sub_checker_lib.file = checker-lib.pro 6 | sub_checker_client.file = checker-client.pro 7 | sub_helper.file = helper.pro 8 | 9 | CONFIG += ordered 10 | -------------------------------------------------------------------------------- /checker-client.pro: -------------------------------------------------------------------------------- 1 | 2 | TEMPLATE = app 3 | 4 | include(checker.pri) 5 | 6 | # Using the "console" CONFIG flag above should ensure this happens for 7 | # normal Windows builds, but this may be necessary when cross-compiling 8 | win32-x-g++:QMAKE_LFLAGS += -Wl,-subsystem,console 9 | 10 | macx*: CONFIG -= app_bundle 11 | 12 | TARGET = checker-client 13 | 14 | SOURCES += \ 15 | src/checker-client.cpp 16 | 17 | -------------------------------------------------------------------------------- /checker.pri: -------------------------------------------------------------------------------- 1 | 2 | CONFIG += qt stl c++11 exceptions console warn_on 3 | QT -= xml network gui widgets 4 | 5 | !win32 { 6 | QMAKE_CXXFLAGS_DEBUG += -Werror 7 | } 8 | 9 | OBJECTS_DIR = o 10 | MOC_DIR = o 11 | 12 | INCLUDEPATH += checker 13 | 14 | HEADERS += \ 15 | checker/checkcode.h \ 16 | checker/plugincandidates.h \ 17 | checker/knownplugincandidates.h \ 18 | checker/knownplugins.h 19 | 20 | SOURCES += \ 21 | src/plugincandidates.cpp \ 22 | src/knownplugincandidates.cpp \ 23 | src/knownplugins.cpp 24 | 25 | 26 | -------------------------------------------------------------------------------- /helper.pro: -------------------------------------------------------------------------------- 1 | 2 | TEMPLATE = app 3 | 4 | CONFIG += stl c++11 exceptions console warn_on 5 | CONFIG -= qt 6 | 7 | # Using the "console" CONFIG flag above should ensure this happens for 8 | # normal Windows builds, but this may be necessary when cross-compiling 9 | win32-x-g++:QMAKE_LFLAGS += -Wl,-subsystem,console 10 | 11 | macx*: CONFIG -= app_bundle 12 | 13 | !win32* { 14 | QMAKE_CXXFLAGS_DEBUG += -Werror 15 | } 16 | 17 | linux* { 18 | LIBS += -ldl 19 | } 20 | 21 | TARGET = vamp-plugin-load-checker 22 | 23 | OBJECTS_DIR = o 24 | MOC_DIR = o 25 | 26 | SOURCES += \ 27 | src/helper.cpp 28 | 29 | exists(../platform-helpers.pri) { 30 | include(../platform-helpers.pri) 31 | } 32 | 33 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2016 Queen Mary, University of London 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, copy, 8 | modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 19 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 20 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | Except as contained in this notice, the names of the Centre for 24 | Digital Music and Queen Mary, University of London shall not be 25 | used in advertising or otherwise to promote the sale, use or other 26 | dealings in this Software without prior written authorization. 27 | 28 | -------------------------------------------------------------------------------- /src/checker-client.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ 2 | /* 3 | Copyright (c) 2016-2018 Queen Mary, University of London 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, copy, 9 | modify, merge, publish, distribute, sublicense, and/or sell copies 10 | of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 21 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | Except as contained in this notice, the names of the Centre for 25 | Digital Music and Queen Mary, University of London shall not be 26 | used in advertising or otherwise to promote the sale, use or other 27 | dealings in this Software without prior written authorization. 28 | */ 29 | 30 | #include "knownplugincandidates.h" 31 | 32 | #include 33 | 34 | using namespace std; 35 | 36 | struct LogCallback : PluginCandidates::LogCallback { 37 | virtual void log(string message) { 38 | cerr << "checker: log: " << message << "\n"; 39 | } 40 | }; 41 | 42 | int main(int, char **) 43 | { 44 | LogCallback cb; 45 | KnownPluginCandidates kp("./vamp-plugin-load-checker", &cb); 46 | 47 | for (auto t: kp.getKnownPluginTypes()) { 48 | cout << "successful libraries for plugin type \"" 49 | << kp.getTagFor(t) << "\":" << endl; 50 | for (auto lib: kp.getCandidateLibrariesFor(t)) { 51 | cout << lib << endl; 52 | } 53 | } 54 | 55 | cout << "Failure message (if any):" << endl; 56 | cout << kp.getFailureReport() << endl; 57 | } 58 | 59 | -------------------------------------------------------------------------------- /checker/checkcode.h: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ 2 | /* 3 | Copyright (c) 2016-2018 Queen Mary, University of London 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, copy, 9 | modify, merge, publish, distribute, sublicense, and/or sell copies 10 | of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 21 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | Except as contained in this notice, the names of the Centre for 25 | Digital Music and Queen Mary, University of London shall not be 26 | used in advertising or otherwise to promote the sale, use or other 27 | dealings in this Software without prior written authorization. 28 | */ 29 | 30 | #ifndef CHECK_CODE_H 31 | #define CHECK_CODE_H 32 | 33 | enum class PluginCheckCode { 34 | 35 | SUCCESS = 0, 36 | 37 | /** Plugin library file is not found 38 | */ 39 | FAIL_LIBRARY_NOT_FOUND = 1, 40 | 41 | /** Plugin library does appear to be a library, but its 42 | * architecture differs from that of the checker program, in 43 | * a way that can be distinguished from other loader 44 | * failures. On Windows this may arise from system error 193, 45 | * ERROR_BAD_EXE_FORMAT 46 | */ 47 | FAIL_WRONG_ARCHITECTURE = 2, 48 | 49 | /** Plugin library depends on some other library that cannot be 50 | * loaded. On Windows this may arise from system error 126, 51 | * ERROR_MOD_NOT_FOUND, provided that the library file itself 52 | * exists 53 | */ 54 | FAIL_DEPENDENCY_MISSING = 3, 55 | 56 | /** Plugin library loading was refused for some security-related 57 | * reason 58 | */ 59 | FAIL_FORBIDDEN = 4, 60 | 61 | /** Plugin library cannot be loaded for some other reason 62 | */ 63 | FAIL_NOT_LOADABLE = 5, 64 | 65 | /** Plugin library can be loaded, but the expected plugin 66 | * descriptor symbol is missing 67 | */ 68 | FAIL_DESCRIPTOR_MISSING = 6, 69 | 70 | /** Plugin library can be loaded and descriptor called, but no 71 | * plugins are found in it 72 | */ 73 | FAIL_NO_PLUGINS = 7, 74 | 75 | /** Plugin library was on a list of libraries to ignore, so was 76 | * not attempted at all 77 | */ 78 | FAIL_ON_IGNORE_LIST = 8, 79 | 80 | /** Failure but no meaningful error code provided, or failure 81 | * read from an older helper version that did not support 82 | * error codes 83 | */ 84 | FAIL_OTHER = 999 85 | }; 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /checker/knownplugins.h: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ 2 | /* 3 | Copyright (c) 2016-2018 Queen Mary, University of London 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, copy, 9 | modify, merge, publish, distribute, sublicense, and/or sell copies 10 | of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 21 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | Except as contained in this notice, the names of the Centre for 25 | Digital Music and Queen Mary, University of London shall not be 26 | used in advertising or otherwise to promote the sale, use or other 27 | dealings in this Software without prior written authorization. 28 | */ 29 | 30 | #ifndef KNOWN_PLUGINS_H 31 | #define KNOWN_PLUGINS_H 32 | 33 | #include 34 | #include 35 | #include 36 | 37 | /** 38 | * Class to provide information about a hardcoded set of known plugin 39 | * formats. 40 | * 41 | * Requires C++11 and the Qt5 or Qt6 QtCore library. 42 | */ 43 | class KnownPlugins 44 | { 45 | typedef std::vector stringlist; 46 | 47 | public: 48 | enum PluginType { 49 | VampPlugin, 50 | LADSPAPlugin, 51 | DSSIPlugin 52 | }; 53 | 54 | enum BinaryFormat { 55 | FormatNative, 56 | FormatNonNative32Bit // i.e. a 32-bit plugin but on a 64-bit host 57 | }; 58 | 59 | KnownPlugins(BinaryFormat format); 60 | 61 | std::vector getKnownPluginTypes() const; 62 | 63 | std::string getTagFor(PluginType type) const { 64 | return m_known.at(type).tag; 65 | } 66 | 67 | std::string getPathEnvironmentVariableFor(PluginType type) const { 68 | return m_known.at(type).variable; 69 | } 70 | 71 | stringlist getDefaultPathFor(PluginType type) const { 72 | return m_known.at(type).defaultPath; 73 | } 74 | 75 | stringlist getPathFor(PluginType type) const { 76 | return m_known.at(type).path; 77 | } 78 | 79 | std::string getDescriptorFor(PluginType type) const { 80 | return m_known.at(type).descriptor; 81 | } 82 | 83 | private: 84 | struct TypeRec { 85 | std::string tag; 86 | std::string variable; 87 | stringlist defaultPath; 88 | stringlist path; 89 | std::string descriptor; 90 | }; 91 | typedef std::map Known; 92 | Known m_known; 93 | 94 | std::string getUnexpandedDefaultPathString(PluginType type); 95 | std::string getDefaultPathString(PluginType type); 96 | 97 | stringlist expandPathString(std::string pathString); 98 | stringlist expandConventionalPath(PluginType type, std::string variable); 99 | 100 | BinaryFormat m_format; 101 | }; 102 | 103 | #endif 104 | -------------------------------------------------------------------------------- /checker/knownplugincandidates.h: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ 2 | /* 3 | Copyright (c) 2016-2018 Queen Mary, University of London 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, copy, 9 | modify, merge, publish, distribute, sublicense, and/or sell copies 10 | of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 21 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | Except as contained in this notice, the names of the Centre for 25 | Digital Music and Queen Mary, University of London shall not be 26 | used in advertising or otherwise to promote the sale, use or other 27 | dealings in this Software without prior written authorization. 28 | */ 29 | 30 | #ifndef KNOWN_PLUGIN_CANDIDATES_H 31 | #define KNOWN_PLUGIN_CANDIDATES_H 32 | 33 | #include "plugincandidates.h" 34 | #include "knownplugins.h" 35 | 36 | #include 37 | #include 38 | #include 39 | 40 | /** 41 | * Class to identify and list candidate shared-library files possibly 42 | * containing plugins in a hardcoded set of known formats. Uses a 43 | * separate process (the "helper", whose executable name must be 44 | * provided at construction) to test-load each library in order to 45 | * winnow out any that fail to load or crash on load. 46 | * 47 | * In v1 of the checker library this was the role of the KnownPlugins 48 | * class, but that has been changed so that it only provides static 49 | * known information about plugin formats and paths, and this class 50 | * has been introduced instead (tying that static information together 51 | * with a PluginCandidates object that handles the run-time query). 52 | * 53 | * Requires C++11 and the Qt5 QtCore library. 54 | */ 55 | class KnownPluginCandidates 56 | { 57 | typedef std::vector stringlist; 58 | 59 | public: 60 | KnownPluginCandidates(std::string helperExecutableName, 61 | stringlist librariesToIgnore, 62 | PluginCandidates::LogCallback *cb = 0); 63 | 64 | std::vector getKnownPluginTypes() const { 65 | return m_known.getKnownPluginTypes(); 66 | } 67 | 68 | std::string getTagFor(KnownPlugins::PluginType type) const { 69 | return m_known.getTagFor(type); 70 | } 71 | 72 | stringlist getCandidateLibrariesFor(KnownPlugins::PluginType type) const { 73 | return m_candidates.getCandidateLibrariesFor(m_known.getTagFor(type)); 74 | } 75 | 76 | std::string getHelperExecutableName() const { 77 | return m_helperExecutableName; 78 | } 79 | 80 | std::vector> getFailures() const; 82 | 83 | /** Return a non-localised HTML failure report */ 84 | std::string getFailureReport() const; 85 | 86 | private: 87 | KnownPlugins m_known; 88 | PluginCandidates m_candidates; 89 | std::string m_helperExecutableName; 90 | }; 91 | 92 | #endif 93 | 94 | -------------------------------------------------------------------------------- /src/knownplugincandidates.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ 2 | /* 3 | Copyright (c) 2016-2018 Queen Mary, University of London 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, copy, 9 | modify, merge, publish, distribute, sublicense, and/or sell copies 10 | of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 21 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | Except as contained in this notice, the names of the Centre for 25 | Digital Music and Queen Mary, University of London shall not be 26 | used in advertising or otherwise to promote the sale, use or other 27 | dealings in this Software without prior written authorization. 28 | */ 29 | 30 | #include "knownplugincandidates.h" 31 | 32 | #include 33 | 34 | using namespace std; 35 | 36 | /** This returns true if the helper has a name ending in "-32". By our 37 | * convention, this means that it is a 32-bit helper found on a 38 | * 64-bit system, so (depending on the OS) we may need to look in 39 | * 32-bit-specific paths. Note that is32bit() is *not* usually true 40 | * on 32-bit systems; it's used specifically to indicate a 41 | * "non-native" 32-bit helper. 42 | */ 43 | static 44 | bool 45 | is32bit(string helperExecutableName) 46 | { 47 | return helperExecutableName.find("-32") != string::npos; 48 | } 49 | 50 | KnownPluginCandidates::KnownPluginCandidates(string helperExecutableName, 51 | stringlist librariesToIgnore, 52 | PluginCandidates::LogCallback *cb) : 53 | m_known(is32bit(helperExecutableName) ? 54 | KnownPlugins::FormatNonNative32Bit : 55 | KnownPlugins::FormatNative), 56 | m_candidates(helperExecutableName, librariesToIgnore), 57 | m_helperExecutableName(helperExecutableName) 58 | { 59 | m_candidates.setLogCallback(cb); 60 | 61 | auto knownTypes = m_known.getKnownPluginTypes(); 62 | 63 | for (auto type: knownTypes) { 64 | m_candidates.scan(m_known.getTagFor(type), 65 | m_known.getPathFor(type), 66 | m_known.getDescriptorFor(type)); 67 | } 68 | } 69 | 70 | vector> 71 | KnownPluginCandidates::getFailures() const 72 | { 73 | vector> 74 | failures; 75 | 76 | for (auto t: getKnownPluginTypes()) { 77 | auto ff = m_candidates.getFailedLibrariesFor(m_known.getTagFor(t)); 78 | for (const auto &f : ff) { 79 | failures.push_back({ t, f }); 80 | } 81 | } 82 | 83 | return failures; 84 | } 85 | 86 | string 87 | KnownPluginCandidates::getFailureReport() const 88 | { 89 | auto failures = getFailures(); 90 | if (failures.empty()) return ""; 91 | 92 | int n = int(failures.size()); 93 | int i = 0; 94 | 95 | ostringstream os; 96 | 97 | os << "
    "; 98 | for (auto p: failures) { 99 | auto f = p.second; 100 | os << "
  • " + f.library; 101 | if (f.message != "") { 102 | os << "
    " + f.message + ""; 103 | } else { 104 | os << "
    unknown error"; 105 | } 106 | os << "
  • "; 107 | 108 | if (n > 10) { 109 | if (++i == 5) { 110 | os << "
  • (... and " << (n - i) << " further failures)
  • "; 111 | break; 112 | } 113 | } 114 | } 115 | os << "
"; 116 | 117 | return os.str(); 118 | } 119 | -------------------------------------------------------------------------------- /checker/plugincandidates.h: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ 2 | /* 3 | Copyright (c) 2016-2018 Queen Mary, University of London 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, copy, 9 | modify, merge, publish, distribute, sublicense, and/or sell copies 10 | of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 21 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | Except as contained in this notice, the names of the Centre for 25 | Digital Music and Queen Mary, University of London shall not be 26 | used in advertising or otherwise to promote the sale, use or other 27 | dealings in this Software without prior written authorization. 28 | */ 29 | 30 | #ifndef PLUGIN_CANDIDATES_H 31 | #define PLUGIN_CANDIDATES_H 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #include "checkcode.h" 39 | 40 | class QProcess; 41 | 42 | /** 43 | * Class to identify and list candidate shared-library files possibly 44 | * containing plugins. Uses a separate process (the "helper", whose 45 | * executable name must be provided at construction) to test-load each 46 | * library in order to winnow out any that fail to load or crash on 47 | * load. 48 | * 49 | * Requires C++11 and the Qt5 QtCore library. 50 | */ 51 | class PluginCandidates 52 | { 53 | typedef std::vector stringlist; 54 | 55 | public: 56 | /** Construct a PluginCandidates scanner that uses the given 57 | * executable as its load check helper. 58 | */ 59 | PluginCandidates(std::string helperExecutableName, 60 | stringlist librariesToIgnore); 61 | 62 | struct LogCallback { 63 | virtual ~LogCallback() { } 64 | 65 | /// implementation is responsible for adding \n and flushing output 66 | virtual void log(std::string) = 0; 67 | }; 68 | 69 | /** Set a callback to be called for log output. 70 | */ 71 | void setLogCallback(LogCallback *cb); 72 | 73 | /** Scan the libraries found in the given plugin path (i.e. list 74 | * of plugin directories), checking that the given descriptor 75 | * symbol can be looked up in each. Store the results 76 | * internally, associated with the given (arbitrary) tag, for 77 | * later querying using getCandidateLibrariesFor() and 78 | * getFailedLibrariesFor(). 79 | * 80 | * Not thread-safe. 81 | */ 82 | void scan(std::string tag, 83 | stringlist pluginPath, 84 | std::string descriptorSymbolName); 85 | 86 | /** Return list of plugin library paths that were checked 87 | * successfully during the scan for the given tag. 88 | */ 89 | stringlist getCandidateLibrariesFor(std::string tag) const; 90 | 91 | struct FailureRec { 92 | 93 | /// Path of failed library file 94 | std::string library; 95 | 96 | /// General class of failure 97 | PluginCheckCode code; 98 | 99 | /// Optional additional system-level message, already translated 100 | std::string message; 101 | }; 102 | 103 | /** Return list of failure reports arising from the prior scan for 104 | * the given tag. 105 | */ 106 | std::vector getFailedLibrariesFor(std::string tag) const; 107 | 108 | private: 109 | std::string m_helper; 110 | std::map m_candidates; 111 | std::map > m_failures; 112 | std::set m_toIgnore; 113 | LogCallback *m_logCallback; 114 | 115 | stringlist getLibrariesInPath(stringlist path); 116 | std::string getHelperCompatibilityVersion(); 117 | stringlist runHelper(stringlist libraries, std::string descriptor); 118 | void recordResult(std::string tag, stringlist results); 119 | void logErrors(QProcess *); 120 | void log(std::string); 121 | }; 122 | 123 | #endif 124 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | 2 | [Vamp] Plugin Load Checker 3 | ========================== 4 | 5 | This is a very small command-line program (C++98, no particular 6 | dependencies) for testing plugin libraries to see if they are 7 | loadable. You run the program, pass it a list of library paths to 8 | stdin, it tries to load them, and it reports to stdout whether each 9 | load succeeded or not. 10 | 11 | The program was written for use with Vamp audio analysis plugins, but 12 | it also works with other plugin formats. It has some hardcoded 13 | knowledge of Vamp, LADSPA, and DSSI plugins but it can be used with 14 | any plugins that involve loading DLLs and looking up descriptor 15 | functions from them. 16 | 17 | It comes with a library (C++11, Qt) that searches for candidate plugin 18 | files for some known formats in standard locations and runs the 19 | checker program as a separate process to check whether they can be 20 | loaded. This can be used to scan plugins and blacklist any that might 21 | crash a host on load. 22 | 23 | 24 | About the command-line program 25 | ------------------------------ 26 | 27 | The program (vamp-plugin-load-checker) accepts the name of a 28 | descriptor symbol as its only command-line argument. It then reads a 29 | list of plugin library paths from stdin, one per line. For each path 30 | read, it attempts to load that library and retrieve the named 31 | descriptor symbol, printing a line to stdout reporting whether this 32 | was successful or not and then flushing stdout. The output line format 33 | is described below. The program exits with code 0 if all libraries 34 | were loaded successfully and non-zero otherwise. 35 | 36 | Note that library paths must be ready to pass to dlopen() or 37 | equivalent; this usually means they should be absolute paths. 38 | 39 | Output line for successful load of library libname.so: 40 | SUCCESS|/path/to/libname.so| 41 | 42 | Output line for failed load of library libname.so: 43 | FAILURE|/path/to/libname.so|Reason for failure if available 44 | 45 | Although this program was written for use with Vamp audio analysis 46 | plugins, it also works with other plugin formats. The program has some 47 | hardcoded knowledge of Vamp, LADSPA, and DSSI plugins, but it can be 48 | used with any plugins that involve loading DLLs and looking up 49 | descriptor functions from them. 50 | 51 | Sometimes plugins will crash completely on load, bringing down this 52 | program with them. If the program exits before all listed plugins have 53 | been checked, this means that the plugin following the last reported 54 | one has crashed. Typically the caller may want to run it again, 55 | omitting that plugin. 56 | 57 | This program (src/helper.cpp) is written in C++98 and has no 58 | particular dependencies apart from the dynamic loader library. 59 | 60 | 61 | About the library 62 | ----------------- 63 | 64 | Two C++ classes are provided for use by a host application: 65 | PluginCandidates and KnownPlugins. 66 | 67 | PluginCandidates knows how to invoke the checker program (if you 68 | provide the path to it) and will do so for a set of plugin paths of 69 | your request, returning success or failure reports to you. 70 | 71 | KnownPlugins knows about a limited set of plugin formats (currently 72 | Vamp, LADSPA, DSSI) and will use PluginCandidates to test all plugins 73 | found in those formats' standard installation directories. 74 | 75 | These are C++11 classes using the Qt toolkit. 76 | 77 | 78 | How to compile 79 | -------------- 80 | 81 | A Qt project (checker.pro) is provided, which compiles the program and 82 | library: 83 | 84 | $ qmake checker.pro 85 | $ make 86 | 87 | It also builds a program called checker-client which exercises the 88 | library by using a KnownPlugins object with the program it just 89 | compiled and printing out the results. 90 | 91 | To compile only the command-line program, you should be able to use a 92 | single C++ compiler invocation like: 93 | 94 | $ c++ -o vamp-plugin-load-checker src/helper.cpp -ldl 95 | 96 | I expect that most often the program and library will be compiled as 97 | part of a larger host application. (They were written for use with 98 | Sonic Visualiser.) 99 | 100 | 101 | Copyright and licence 102 | --------------------- 103 | 104 | Written by Chris Cannam at the Centre for Digital Music, Queen Mary 105 | University of London. 106 | 107 | Copyright (c) 2016-2018 Queen Mary, University of London. 108 | 109 | Permission is hereby granted, free of charge, to any person 110 | obtaining a copy of this software and associated documentation 111 | files (the "Software"), to deal in the Software without 112 | restriction, including without limitation the rights to use, copy, 113 | modify, merge, publish, distribute, sublicense, and/or sell copies 114 | of the Software, and to permit persons to whom the Software is 115 | furnished to do so, subject to the following conditions: 116 | 117 | The above copyright notice and this permission notice shall be 118 | included in all copies or substantial portions of the Software. 119 | 120 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 121 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 122 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 123 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 124 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 125 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 126 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 127 | 128 | Except as contained in this notice, the names of the Centre for 129 | Digital Music and Queen Mary, University of London shall not be 130 | used in advertising or otherwise to promote the sale, use or other 131 | dealings in this Software without prior written authorization. 132 | 133 | -------------------------------------------------------------------------------- /src/knownplugins.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ 2 | /* 3 | Copyright (c) 2016-2018 Queen Mary, University of London 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, copy, 9 | modify, merge, publish, distribute, sublicense, and/or sell copies 10 | of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 21 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | Except as contained in this notice, the names of the Centre for 25 | Digital Music and Queen Mary, University of London shall not be 26 | used in advertising or otherwise to promote the sale, use or other 27 | dealings in this Software without prior written authorization. 28 | */ 29 | 30 | #include "knownplugins.h" 31 | 32 | #include 33 | 34 | #if defined(_WIN32) 35 | #include 36 | #define PATH_SEPARATOR ';' 37 | #else 38 | #define PATH_SEPARATOR ':' 39 | #endif 40 | 41 | using namespace std; 42 | 43 | static bool 44 | getEnvUtf8(std::string variable, std::string &value) 45 | { 46 | value = ""; 47 | 48 | #ifdef _WIN32 49 | int wvarlen = MultiByteToWideChar(CP_UTF8, 0, 50 | variable.c_str(), int(variable.length()), 51 | 0, 0); 52 | if (wvarlen < 0) { 53 | cerr << "WARNING: Unable to convert environment variable name " 54 | << variable << " to wide characters" << endl; 55 | return false; 56 | } 57 | 58 | wchar_t *wvarbuf = new wchar_t[wvarlen + 1]; 59 | (void)MultiByteToWideChar(CP_UTF8, 0, 60 | variable.c_str(), int(variable.length()), 61 | wvarbuf, wvarlen); 62 | wvarbuf[wvarlen] = L'\0'; 63 | 64 | wchar_t *wvalue = _wgetenv(wvarbuf); 65 | 66 | delete[] wvarbuf; 67 | 68 | if (!wvalue) { 69 | return false; 70 | } 71 | 72 | int wvallen = int(wcslen(wvalue)); 73 | int vallen = WideCharToMultiByte(CP_UTF8, 0, 74 | wvalue, wvallen, 75 | 0, 0, 0, 0); 76 | if (vallen < 0) { 77 | cerr << "WARNING: Unable to convert environment value to UTF-8" << endl; 78 | return false; 79 | } 80 | 81 | char *val = new char[vallen + 1]; 82 | (void)WideCharToMultiByte(CP_UTF8, 0, 83 | wvalue, wvallen, 84 | val, vallen, 0, 0); 85 | val[vallen] = '\0'; 86 | 87 | value = val; 88 | 89 | delete[] val; 90 | return true; 91 | 92 | #else 93 | 94 | char *val = getenv(variable.c_str()); 95 | if (!val) { 96 | return false; 97 | } 98 | 99 | value = val; 100 | return true; 101 | 102 | #endif 103 | } 104 | 105 | KnownPlugins::KnownPlugins(BinaryFormat format) : 106 | m_format(format) 107 | { 108 | string variableSuffix = ""; 109 | if (m_format == FormatNonNative32Bit) { 110 | variableSuffix = "_32"; 111 | } 112 | 113 | m_known[VampPlugin] = { 114 | "vamp", 115 | "VAMP_PATH" + variableSuffix, 116 | {}, {}, 117 | "vampGetPluginDescriptor" 118 | }; 119 | 120 | m_known[LADSPAPlugin] = { 121 | "ladspa", 122 | "LADSPA_PATH" + variableSuffix, 123 | {}, {}, 124 | "ladspa_descriptor" 125 | }; 126 | 127 | m_known[DSSIPlugin] = { 128 | "dssi", 129 | "DSSI_PATH" + variableSuffix, 130 | {}, {}, 131 | "dssi_descriptor" 132 | }; 133 | 134 | for (auto &k: m_known) { 135 | k.second.defaultPath = expandPathString(getDefaultPathString(k.first)); 136 | k.second.path = expandConventionalPath(k.first, k.second.variable); 137 | } 138 | } 139 | 140 | vector 141 | KnownPlugins::getKnownPluginTypes() const 142 | { 143 | vector kt; 144 | 145 | for (const auto &k: m_known) { 146 | kt.push_back(k.first); 147 | } 148 | 149 | return kt; 150 | } 151 | 152 | string 153 | KnownPlugins::getUnexpandedDefaultPathString(PluginType type) 154 | { 155 | switch (type) { 156 | 157 | #if defined(_WIN32) 158 | 159 | case VampPlugin: 160 | return "%ProgramFiles%\\Vamp Plugins"; 161 | case LADSPAPlugin: 162 | return "%ProgramFiles%\\LADSPA Plugins;%ProgramFiles%\\Audacity\\Plug-Ins"; 163 | case DSSIPlugin: 164 | return "%ProgramFiles%\\DSSI Plugins"; 165 | 166 | #elif defined(__APPLE__) 167 | 168 | case VampPlugin: 169 | return "$HOME/Library/Audio/Plug-Ins/Vamp:/Library/Audio/Plug-Ins/Vamp"; 170 | case LADSPAPlugin: 171 | return "$HOME/Library/Audio/Plug-Ins/LADSPA:/Library/Audio/Plug-Ins/LADSPA"; 172 | case DSSIPlugin: 173 | return "$HOME/Library/Audio/Plug-Ins/DSSI:/Library/Audio/Plug-Ins/DSSI"; 174 | 175 | #else /* Linux, BSDs, etc */ 176 | 177 | case VampPlugin: 178 | return "$HOME/vamp:$HOME/.vamp:/usr/local/lib/vamp:/usr/lib/vamp"; 179 | case LADSPAPlugin: 180 | return "$HOME/ladspa:$HOME/.ladspa:/usr/local/lib/ladspa:/usr/lib/ladspa"; 181 | case DSSIPlugin: 182 | return "$HOME/dssi:$HOME/.dssi:/usr/local/lib/dssi:/usr/lib/dssi"; 183 | #endif 184 | } 185 | 186 | throw logic_error("unknown or unhandled plugin type"); 187 | } 188 | 189 | string 190 | KnownPlugins::getDefaultPathString(PluginType type) 191 | { 192 | string path = getUnexpandedDefaultPathString(type); 193 | 194 | if (path == "") { 195 | return path; 196 | } 197 | 198 | string home; 199 | if (getEnvUtf8("HOME", home)) { 200 | string::size_type f; 201 | while ((f = path.find("$HOME")) != string::npos && 202 | f < path.length()) { 203 | path.replace(f, 5, home); 204 | } 205 | } 206 | 207 | #ifdef _WIN32 208 | string pfiles, pfiles32; 209 | if (!getEnvUtf8("ProgramFiles", pfiles)) { 210 | pfiles = "C:\\Program Files"; 211 | } 212 | if (!getEnvUtf8("ProgramFiles(x86)", pfiles32)) { 213 | pfiles32 = "C:\\Program Files (x86)"; 214 | } 215 | 216 | string::size_type f; 217 | while ((f = path.find("%ProgramFiles%")) != string::npos && 218 | f < path.length()) { 219 | if (m_format == FormatNonNative32Bit) { 220 | path.replace(f, 14, pfiles32); 221 | } else { 222 | path.replace(f, 14, pfiles); 223 | } 224 | } 225 | #endif 226 | 227 | return path; 228 | } 229 | 230 | vector 231 | KnownPlugins::expandPathString(string path) 232 | { 233 | vector pathList; 234 | 235 | string::size_type index = 0, newindex = 0; 236 | 237 | while ((newindex = path.find(PATH_SEPARATOR, index)) < path.size()) { 238 | pathList.push_back(path.substr(index, newindex - index).c_str()); 239 | index = newindex + 1; 240 | } 241 | 242 | pathList.push_back(path.substr(index)); 243 | 244 | return pathList; 245 | } 246 | 247 | vector 248 | KnownPlugins::expandConventionalPath(PluginType type, string var) 249 | { 250 | string path; 251 | 252 | if (!getEnvUtf8(var, path)) { 253 | path = getDefaultPathString(type); 254 | } 255 | 256 | return expandPathString(path); 257 | } 258 | -------------------------------------------------------------------------------- /src/plugincandidates.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ 2 | /* 3 | Copyright (c) 2016-2018 Queen Mary, University of London 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, copy, 9 | modify, merge, publish, distribute, sublicense, and/or sell copies 10 | of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 21 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | Except as contained in this notice, the names of the Centre for 25 | Digital Music and Queen Mary, University of London shall not be 26 | used in advertising or otherwise to promote the sale, use or other 27 | dealings in this Software without prior written authorization. 28 | */ 29 | 30 | #include "plugincandidates.h" 31 | 32 | #include "../version.h" 33 | 34 | #include 35 | #include 36 | #include 37 | 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #if defined(_WIN32) 44 | #define PLUGIN_GLOB "*.dll" 45 | #elif defined(__APPLE__) 46 | #define PLUGIN_GLOB "*.dylib *.so" 47 | #else 48 | #define PLUGIN_GLOB "*.so" 49 | #endif 50 | 51 | using namespace std; 52 | 53 | PluginCandidates::PluginCandidates(string helperExecutableName, 54 | stringlist librariesToIgnore) : 55 | m_helper(helperExecutableName), 56 | m_logCallback(nullptr) 57 | { 58 | for (auto library : librariesToIgnore) { 59 | m_toIgnore.insert(library); 60 | } 61 | } 62 | 63 | void 64 | PluginCandidates::setLogCallback(LogCallback *cb) 65 | { 66 | m_logCallback = cb; 67 | } 68 | 69 | vector 70 | PluginCandidates::getCandidateLibrariesFor(string tag) const 71 | { 72 | if (m_candidates.find(tag) == m_candidates.end()) return {}; 73 | else return m_candidates.at(tag); 74 | } 75 | 76 | vector 77 | PluginCandidates::getFailedLibrariesFor(string tag) const 78 | { 79 | if (m_failures.find(tag) == m_failures.end()) return {}; 80 | else return m_failures.at(tag); 81 | } 82 | 83 | void 84 | PluginCandidates::log(string message) 85 | { 86 | if (m_logCallback) { 87 | m_logCallback->log("PluginCandidates: " + message); 88 | } else { 89 | cerr << "PluginCandidates: " << message << endl; 90 | } 91 | } 92 | 93 | vector 94 | PluginCandidates::getLibrariesInPath(vector path) 95 | { 96 | vector candidates; 97 | 98 | for (string dirname: path) { 99 | 100 | log("Scanning directory " + dirname); 101 | 102 | QDir dir(dirname.c_str(), PLUGIN_GLOB, 103 | QDir::Name | QDir::IgnoreCase, 104 | QDir::Files | QDir::Readable); 105 | 106 | for (unsigned int i = 0; i < dir.count(); ++i) { 107 | QString soname = dir.filePath(dir[i]); 108 | // NB this means the library names passed to the helper 109 | // are UTF-8 encoded 110 | candidates.push_back(soname.toStdString()); 111 | } 112 | } 113 | 114 | return candidates; 115 | } 116 | 117 | void 118 | PluginCandidates::scan(string tag, 119 | vector pluginPath, 120 | string descriptorSymbolName) 121 | { 122 | string helperVersion = getHelperCompatibilityVersion(); 123 | if (helperVersion != CHECKER_COMPATIBILITY_VERSION) { 124 | log("Wrong plugin checker helper version found: expected v" + 125 | string(CHECKER_COMPATIBILITY_VERSION) + ", found v" + 126 | helperVersion); 127 | throw runtime_error("wrong version of plugin load helper found"); 128 | } 129 | 130 | vector libraries = getLibrariesInPath(pluginPath); 131 | vector remaining; 132 | 133 | for (auto library : libraries) { 134 | if (m_toIgnore.find(library) == m_toIgnore.end()) { 135 | remaining.push_back(library); 136 | } else { 137 | m_failures[tag].push_back({ 138 | library, 139 | PluginCheckCode::FAIL_ON_IGNORE_LIST, 140 | {} 141 | }); 142 | } 143 | } 144 | 145 | auto toTest = remaining.size(); 146 | int runlimit = 20; 147 | int runcount = 0; 148 | 149 | vector result; 150 | 151 | while (result.size() < toTest && runcount < runlimit) { 152 | vector output = runHelper(remaining, descriptorSymbolName); 153 | result.insert(result.end(), output.begin(), output.end()); 154 | int shortfall = int(remaining.size()) - int(output.size()); 155 | if (shortfall > 0) { 156 | // Helper bailed out for some reason presumably associated 157 | // with the plugin following the last one it reported 158 | // on. Add a failure entry for that one and continue with 159 | // the following ones. 160 | string failed = *(remaining.rbegin() + shortfall - 1); 161 | log("Helper output ended before result for plugin " + failed); 162 | result.push_back("FAILURE|" + failed + "|Plugin load check failed or timed out"); 163 | remaining = vector 164 | (remaining.rbegin(), remaining.rbegin() + shortfall - 1); 165 | } 166 | ++runcount; 167 | } 168 | 169 | recordResult(tag, result); 170 | } 171 | 172 | string 173 | PluginCandidates::getHelperCompatibilityVersion() 174 | { 175 | QProcess process; 176 | process.setReadChannel(QProcess::StandardOutput); 177 | process.setProcessChannelMode(QProcess::ForwardedErrorChannel); 178 | process.start(m_helper.c_str(), { "--version" }); 179 | 180 | if (!process.waitForStarted()) { 181 | QProcess::ProcessError err = process.error(); 182 | if (err == QProcess::FailedToStart) { 183 | std::cerr << "Unable to start helper process " << m_helper 184 | << std::endl; 185 | } else if (err == QProcess::Crashed) { 186 | std::cerr << "Helper process " << m_helper 187 | << " crashed on startup" << std::endl; 188 | } else { 189 | std::cerr << "Helper process " << m_helper 190 | << " failed on startup with error code " 191 | << err << std::endl; 192 | } 193 | throw runtime_error("plugin load helper failed to start"); 194 | } 195 | process.waitForFinished(); 196 | 197 | QByteArray output = process.readAllStandardOutput(); 198 | while (output.endsWith('\n') || output.endsWith('\r')) { 199 | output.chop(1); 200 | } 201 | 202 | string versionString = QString(output).toStdString(); 203 | log("Read version string from helper: " + versionString); 204 | return versionString; 205 | } 206 | 207 | vector 208 | PluginCandidates::runHelper(vector libraries, string descriptor) 209 | { 210 | vector output; 211 | 212 | log("Running helper " + m_helper + " with following library list:"); 213 | for (auto &lib: libraries) log(lib); 214 | 215 | QProcess process; 216 | process.setReadChannel(QProcess::StandardOutput); 217 | 218 | if (m_logCallback) { 219 | log("Log callback is set: using separate-channels mode to gather stderr"); 220 | process.setProcessChannelMode(QProcess::SeparateChannels); 221 | } else { 222 | process.setProcessChannelMode(QProcess::ForwardedErrorChannel); 223 | } 224 | 225 | process.start(m_helper.c_str(), { descriptor.c_str() }); 226 | 227 | if (!process.waitForStarted()) { 228 | QProcess::ProcessError err = process.error(); 229 | if (err == QProcess::FailedToStart) { 230 | std::cerr << "Unable to start helper process " << m_helper 231 | << std::endl; 232 | } else if (err == QProcess::Crashed) { 233 | std::cerr << "Helper process " << m_helper 234 | << " crashed on startup" << std::endl; 235 | } else { 236 | std::cerr << "Helper process " << m_helper 237 | << " failed on startup with error code " 238 | << err << std::endl; 239 | } 240 | logErrors(&process); 241 | throw runtime_error("plugin load helper failed to start"); 242 | } 243 | 244 | log("Helper " + m_helper + " started OK"); 245 | logErrors(&process); 246 | 247 | for (auto &lib: libraries) { 248 | process.write(lib.c_str(), lib.size()); 249 | process.write("\n", 1); 250 | } 251 | 252 | QElapsedTimer t; 253 | t.start(); 254 | int timeout = 15000; // ms 255 | 256 | const int buflen = 4096; 257 | bool done = false; 258 | 259 | while (!done) { 260 | char buf[buflen]; 261 | qint64 linelen = process.readLine(buf, buflen); 262 | if (linelen > 0) { 263 | output.push_back(buf); 264 | done = (output.size() == libraries.size()); 265 | } else if (linelen < 0) { 266 | // error case 267 | log("Received error code while reading from helper"); 268 | done = true; 269 | } else { 270 | // no error, but no line read (could just be between 271 | // lines, or could be eof) 272 | done = (process.state() == QProcess::NotRunning); 273 | if (!done) { 274 | if (t.elapsed() > timeout) { 275 | // this is purely an emergency measure 276 | log("Timeout: helper took too long, killing it"); 277 | process.kill(); 278 | done = true; 279 | } else { 280 | process.waitForReadyRead(200); 281 | } 282 | } 283 | } 284 | logErrors(&process); 285 | } 286 | 287 | if (process.state() != QProcess::NotRunning) { 288 | process.close(); 289 | process.waitForFinished(); 290 | logErrors(&process); 291 | } 292 | 293 | log("Helper completed"); 294 | 295 | return output; 296 | } 297 | 298 | void 299 | PluginCandidates::logErrors(QProcess *p) 300 | { 301 | p->setReadChannel(QProcess::StandardError); 302 | 303 | qint64 byteCount = p->bytesAvailable(); 304 | if (byteCount == 0) { 305 | p->setReadChannel(QProcess::StandardOutput); 306 | return; 307 | } 308 | 309 | QByteArray buffer = p->read(byteCount); 310 | while (buffer.endsWith('\n') || buffer.endsWith('\r')) { 311 | buffer.chop(1); 312 | } 313 | std::string str(buffer.constData(), buffer.size()); 314 | log("Helper stderr output follows:\n" + str); 315 | log("Helper stderr output ends"); 316 | 317 | p->setReadChannel(QProcess::StandardOutput); 318 | } 319 | 320 | void 321 | PluginCandidates::recordResult(string tag, vector result) 322 | { 323 | for (auto &r: result) { 324 | 325 | QString s(r.c_str()); 326 | QStringList bits = s.split("|"); 327 | 328 | log(("Read output line from helper: " + s.trimmed()).toStdString()); 329 | 330 | if (bits.size() < 2 || bits.size() > 3) { 331 | log("Invalid output line (wrong number of |-separated fields)"); 332 | continue; 333 | } 334 | 335 | string status = bits[0].toStdString(); 336 | 337 | string library = bits[1].toStdString(); 338 | if (bits.size() == 2) { 339 | library = bits[1].trimmed().toStdString(); 340 | } 341 | 342 | if (status == "SUCCESS") { 343 | m_candidates[tag].push_back(library); 344 | 345 | } else if (status == "FAILURE") { 346 | 347 | QString messageAndCode = ""; 348 | if (bits.size() > 2) { 349 | messageAndCode = bits[2].trimmed(); 350 | } 351 | 352 | PluginCheckCode code = PluginCheckCode::FAIL_OTHER; 353 | string message = ""; 354 | 355 | QRegularExpression codeRE("^(.*) *\\[([0-9]+)\\]$"); 356 | auto match = codeRE.match(messageAndCode); 357 | if (match.hasMatch()) { 358 | QStringList caps(match.capturedTexts()); 359 | if (caps.length() == 3) { 360 | message = caps[1].toStdString(); 361 | code = PluginCheckCode(caps[2].toInt()); 362 | log("Split failure report into message and failure code " 363 | + caps[2].toStdString()); 364 | } else { 365 | log("Unable to split out failure code from report"); 366 | } 367 | } else { 368 | log("Failure message does not give a failure code"); 369 | } 370 | 371 | if (message == "") { 372 | message = messageAndCode.toStdString(); 373 | } 374 | 375 | m_failures[tag].push_back({ library, code, message }); 376 | 377 | } else { 378 | log("Unexpected status \"" + status + "\" in output line"); 379 | } 380 | } 381 | } 382 | 383 | -------------------------------------------------------------------------------- /src/helper.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ 2 | 3 | /** 4 | * [Vamp] Plugin Load Checker 5 | * 6 | * This program accepts the name of a descriptor symbol as its only 7 | * command-line argument. It then reads a list of plugin library paths 8 | * from stdin, one per line. For each path read, it attempts to load 9 | * that library and retrieve the named descriptor symbol, printing a 10 | * line to stdout reporting whether this was successful or not and 11 | * then flushing stdout. The output line format is described 12 | * below. The program exits with code 0 if all libraries were loaded 13 | * successfully and non-zero otherwise. 14 | * 15 | * Note that library paths must be ready to pass to dlopen() or 16 | * equivalent; this usually means they should be absolute paths. 17 | * 18 | * Output line for successful load of library libname.so: 19 | * SUCCESS|/path/to/libname.so| 20 | * 21 | * Output line for failed load of library libname.so: 22 | * FAILURE|/path/to/libname.so|Error message [failureCode] 23 | * 24 | * or: 25 | * FAILURE|/path/to/libname.so|[failureCode] 26 | * 27 | * where the error message is an optional system-level message, such 28 | * as may be returned from strerror or similar (which should be in the 29 | * native language for the system ready to show the user), and the 30 | * failureCode in square brackets is a mandatory number corresponding 31 | * to one of the PluginCandidates::FailureCode values (requiring 32 | * conversion to a translated string by the client). 33 | * 34 | * Although this program was written for use with Vamp audio analysis 35 | * plugins, it also works with other plugin formats. The program has 36 | * some hardcoded knowledge of Vamp, LADSPA, and DSSI plugins, but it 37 | * can be used with any plugins that involve loading DLLs and looking 38 | * up descriptor functions from them. 39 | * 40 | * Sometimes plugins will crash completely on load, bringing down this 41 | * program with them. If the program exits before all listed plugins 42 | * have been checked, this means that the plugin following the last 43 | * reported one has crashed. Typically the caller may want to run it 44 | * again, omitting that plugin. 45 | */ 46 | 47 | /* 48 | Copyright (c) 2016-2017 Queen Mary, University of London 49 | 50 | Permission is hereby granted, free of charge, to any person 51 | obtaining a copy of this software and associated documentation 52 | files (the "Software"), to deal in the Software without 53 | restriction, including without limitation the rights to use, copy, 54 | modify, merge, publish, distribute, sublicense, and/or sell copies 55 | of the Software, and to permit persons to whom the Software is 56 | furnished to do so, subject to the following conditions: 57 | 58 | The above copyright notice and this permission notice shall be 59 | included in all copies or substantial portions of the Software. 60 | 61 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 62 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 63 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 64 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 65 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 66 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 67 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 68 | 69 | Except as contained in this notice, the names of the Centre for 70 | Digital Music and Queen Mary, University of London shall not be 71 | used in advertising or otherwise to promote the sale, use or other 72 | dealings in this Software without prior written authorization. 73 | */ 74 | 75 | #include "../version.h" 76 | 77 | #include "../checker/checkcode.h" 78 | 79 | static const char programName[] = "vamp-plugin-load-checker"; 80 | 81 | #ifdef _WIN32 82 | #include 83 | #include 84 | #include 85 | #else 86 | #include 87 | #endif 88 | 89 | #include 90 | #include 91 | 92 | #include 93 | #include 94 | #include 95 | 96 | static std::string currentSoname = ""; 97 | 98 | #ifdef _WIN32 99 | #ifndef UNICODE 100 | #error "This must be compiled with UNICODE defined" 101 | #endif 102 | 103 | static HMODULE loadLibraryUTF8(std::string name) { 104 | int n = name.size(); 105 | int wn = MultiByteToWideChar(CP_UTF8, 0, name.c_str(), n, 0, 0); 106 | wchar_t *wname = new wchar_t[wn+1]; 107 | wn = MultiByteToWideChar(CP_UTF8, 0, name.c_str(), n, wname, wn); 108 | wname[wn] = L'\0'; 109 | HMODULE h = LoadLibraryW(wname); 110 | delete[] wname; 111 | return h; 112 | } 113 | 114 | static std::string getErrorText() { 115 | DWORD err = GetLastError(); 116 | wchar_t *buffer = 0; 117 | FormatMessageW( 118 | FORMAT_MESSAGE_ALLOCATE_BUFFER | 119 | FORMAT_MESSAGE_FROM_SYSTEM | 120 | FORMAT_MESSAGE_IGNORE_INSERTS, 121 | NULL, 122 | err, 123 | // the correct way to specify the user's default language, 124 | // according to all resources I could find: 125 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 126 | (LPWSTR) &buffer, 127 | 0, NULL ); 128 | if (!buffer) { 129 | return "Unable to format error string (internal error)"; 130 | } 131 | int wn = wcslen(buffer); 132 | int n = WideCharToMultiByte(CP_UTF8, 0, buffer, wn, 0, 0, 0, 0); 133 | if (n < 0) { 134 | LocalFree(buffer); 135 | return "Unable to convert error string (internal error)"; 136 | } 137 | char *text = new char[n+1]; 138 | (void)WideCharToMultiByte(CP_UTF8, 0, buffer, wn, text, n, 0, 0); 139 | text[n] = '\0'; 140 | std::string s(text); 141 | LocalFree(buffer); 142 | delete[] text; 143 | if (s == "") { 144 | return s; 145 | } 146 | for (int i = s.size(); i > 0; ) { 147 | --i; 148 | if (s[i] == '\n' || s[i] == '\r') { 149 | s.erase(i, 1); 150 | } 151 | } 152 | std::size_t pos = s.find("%1"); 153 | if (pos != std::string::npos && currentSoname != "") { 154 | s.replace(pos, 2, currentSoname); 155 | } 156 | return s; 157 | } 158 | 159 | #define DLOPEN(a,b) loadLibraryUTF8(a) 160 | #define DLSYM(a,b) (void *)GetProcAddress((HINSTANCE)(a),(b).c_str()) 161 | #define DLCLOSE(a) (!FreeLibrary((HINSTANCE)(a))) 162 | #define DLERROR() (getErrorText()) 163 | 164 | static bool libraryExists(std::string name) { 165 | if (name == "") return false; 166 | int n = name.size(); 167 | int wn = MultiByteToWideChar(CP_UTF8, 0, name.c_str(), n, 0, 0); 168 | wchar_t *wname = new wchar_t[wn+1]; 169 | wn = MultiByteToWideChar(CP_UTF8, 0, name.c_str(), n, wname, wn); 170 | wname[wn] = L'\0'; 171 | FILE *f = _wfopen(wname, L"rb"); 172 | delete[] wname; 173 | if (f) { 174 | fclose(f); 175 | return true; 176 | } else { 177 | return false; 178 | } 179 | } 180 | 181 | #else 182 | 183 | #include 184 | #define DLOPEN(a,b) dlopen((a).c_str(),(b)) 185 | #define DLSYM(a,b) dlsym((a),(b).c_str()) 186 | #define DLCLOSE(a) dlclose((a)) 187 | #define DLERROR() dlerror() 188 | 189 | static bool libraryExists(std::string name) { 190 | if (name == "") return false; 191 | FILE *f = fopen(name.c_str(), "r"); 192 | if (f) { 193 | fclose(f); 194 | return true; 195 | } else { 196 | return false; 197 | } 198 | } 199 | 200 | #endif 201 | 202 | using namespace std; 203 | 204 | string error() 205 | { 206 | return DLERROR(); 207 | } 208 | 209 | struct Result { 210 | PluginCheckCode code; 211 | string message; 212 | }; 213 | 214 | Result checkLADSPAStyleDescriptorFn(void *f) 215 | { 216 | typedef const void *(*DFn)(unsigned long); 217 | DFn fn = DFn(f); 218 | unsigned long index = 0; 219 | while (fn(index)) ++index; 220 | if (index == 0) return { PluginCheckCode::FAIL_NO_PLUGINS, "" }; 221 | return { PluginCheckCode::SUCCESS, "" }; 222 | } 223 | 224 | Result checkVampDescriptorFn(void *f) 225 | { 226 | typedef const void *(*DFn)(unsigned int, unsigned int); 227 | DFn fn = DFn(f); 228 | unsigned int index = 0; 229 | while (fn(2, index)) ++index; 230 | if (index == 0) return { PluginCheckCode::FAIL_NO_PLUGINS, "" }; 231 | return { PluginCheckCode::SUCCESS, "" }; 232 | } 233 | 234 | Result check(string soname, string descriptor) 235 | { 236 | void *handle = DLOPEN(soname, RTLD_NOW | RTLD_LOCAL); 237 | if (!handle) { 238 | PluginCheckCode code = PluginCheckCode::FAIL_NOT_LOADABLE; 239 | string message = error(); 240 | #ifdef _WIN32 241 | DWORD err = GetLastError(); 242 | if (err == ERROR_BAD_EXE_FORMAT) { 243 | code = PluginCheckCode::FAIL_WRONG_ARCHITECTURE; 244 | } else if (err == ERROR_MOD_NOT_FOUND) { 245 | if (libraryExists(soname)) { 246 | code = PluginCheckCode::FAIL_DEPENDENCY_MISSING; 247 | } else { 248 | code = PluginCheckCode::FAIL_LIBRARY_NOT_FOUND; 249 | } 250 | } 251 | #else // !_WIN32 252 | #ifdef __APPLE__ 253 | if (errno == EPERM) { 254 | // This may be unreliable, but it seems to be set by 255 | // something dlopen() calls in the case where a library 256 | // can't be loaded for code-signing-related reasons on 257 | // macOS 258 | code = PluginCheckCode::FAIL_FORBIDDEN; 259 | } else if (!libraryExists(soname)) { 260 | code = PluginCheckCode::FAIL_LIBRARY_NOT_FOUND; 261 | } 262 | #else // !__APPLE__ 263 | if (!libraryExists(soname)) { 264 | code = PluginCheckCode::FAIL_LIBRARY_NOT_FOUND; 265 | } 266 | #endif // !__APPLE__ 267 | #endif // !_WIN32 268 | 269 | return { code, message }; 270 | } 271 | 272 | Result result { PluginCheckCode::SUCCESS, "" }; 273 | 274 | void *fn = DLSYM(handle, descriptor); 275 | if (!fn) { 276 | result = { PluginCheckCode::FAIL_DESCRIPTOR_MISSING, error() }; 277 | } else if (descriptor == "ladspa_descriptor") { 278 | result = checkLADSPAStyleDescriptorFn(fn); 279 | } else if (descriptor == "dssi_descriptor") { 280 | result = checkLADSPAStyleDescriptorFn(fn); 281 | } else if (descriptor == "vampGetPluginDescriptor") { 282 | result = checkVampDescriptorFn(fn); 283 | } else { 284 | cerr << "Note: no descriptor logic known for descriptor function \"" 285 | << descriptor << "\"; not actually calling it" << endl; 286 | } 287 | 288 | DLCLOSE(handle); 289 | 290 | return result; 291 | } 292 | 293 | // We write our output to stdout, but want to ensure that the plugin 294 | // doesn't write anything itself. To do this we open a null file 295 | // descriptor and dup2() it into place of stdout in the gaps between 296 | // our own output activity. 297 | 298 | static int normalFd = -1; 299 | static int suspendedFd = -1; 300 | 301 | static void initFds() 302 | { 303 | #ifdef _WIN32 304 | normalFd = _dup(1); 305 | suspendedFd = _open("NUL", _O_WRONLY); 306 | #else 307 | normalFd = dup(1); 308 | suspendedFd = open("/dev/null", O_WRONLY); 309 | #endif 310 | 311 | if (normalFd < 0 || suspendedFd < 0) { 312 | throw std::runtime_error 313 | ("Failed to initialise fds for stdio suspend/resume"); 314 | } 315 | } 316 | 317 | static void suspendOutput() 318 | { 319 | #ifdef _WIN32 320 | _dup2(suspendedFd, 1); 321 | #else 322 | dup2(suspendedFd, 1); 323 | #endif 324 | } 325 | 326 | static void resumeOutput() 327 | { 328 | fflush(stdout); 329 | #ifdef _WIN32 330 | _dup2(normalFd, 1); 331 | #else 332 | dup2(normalFd, 1); 333 | #endif 334 | } 335 | 336 | static void 337 | signalHandler(int signal) 338 | { 339 | cerr << "Signal " << signal << " caught" << endl; 340 | cout << "FAILURE|" << currentSoname << "|[" << int(PluginCheckCode::FAIL_NOT_LOADABLE) << "]" << endl; 341 | exit(1); 342 | } 343 | 344 | int main(int argc, char **argv) 345 | { 346 | bool allGood = true; 347 | string soname; 348 | 349 | bool showUsage = false; 350 | 351 | if (argc > 1) { 352 | string opt = argv[1]; 353 | if (opt == "-?" || opt == "-h" || opt == "--help") { 354 | showUsage = true; 355 | } else if (opt == "-v" || opt == "--version") { 356 | cout << CHECKER_COMPATIBILITY_VERSION << endl; 357 | return 0; 358 | } 359 | } 360 | 361 | if (argc != 2 || showUsage) { 362 | cerr << endl; 363 | cerr << programName << ": Test shared library objects for plugins to be" << endl; 364 | cerr << "loaded via descriptor functions." << endl; 365 | cerr << "\n Usage: " << programName << " \n" 366 | "\nwhere descriptorname is the name of a plugin descriptor symbol to be sought\n" 367 | "in each library (e.g. vampGetPluginDescriptor for Vamp plugins). The list of\n" 368 | "candidate plugin library filenames is read from stdin.\n" << endl; 369 | return 2; 370 | } 371 | 372 | signal(SIGINT, signalHandler); 373 | signal(SIGTERM, signalHandler); 374 | signal(SIGSEGV, signalHandler); 375 | signal(SIGILL, signalHandler); 376 | signal(SIGABRT, signalHandler); 377 | signal(SIGFPE, signalHandler); 378 | 379 | #ifndef _WIN32 380 | signal(SIGHUP, signalHandler); 381 | signal(SIGQUIT, signalHandler); 382 | signal(SIGBUS, signalHandler); 383 | #endif 384 | 385 | string descriptor = argv[1]; 386 | 387 | #ifdef _WIN32 388 | // Avoid showing the error-handler dialog for missing DLLs, 389 | // failing quietly instead. It's permissible for this program 390 | // to simply fail when a DLL can't be loaded -- showing the 391 | // error dialog wouldn't change this anyway, it would just 392 | // block the program until the user clicked it away and then 393 | // fail anyway. 394 | SetErrorMode(SEM_FAILCRITICALERRORS); 395 | #endif 396 | 397 | initFds(); 398 | suspendOutput(); 399 | 400 | while (getline(cin, soname)) { 401 | 402 | currentSoname = soname; 403 | 404 | Result result = check(soname, descriptor); 405 | resumeOutput(); 406 | if (result.code == PluginCheckCode::SUCCESS) { 407 | cout << "SUCCESS|" << soname << "|" << endl; 408 | } else { 409 | if (result.message == "") { 410 | cout << "FAILURE|" << soname 411 | << "|[" << int(result.code) << "]" << endl; 412 | } else { 413 | for (size_t i = 0; i < result.message.size(); ++i) { 414 | if (result.message[i] == '\n' || 415 | result.message[i] == '\r') { 416 | result.message[i] = ' '; 417 | } 418 | } 419 | cout << "FAILURE|" << soname 420 | << "|" << result.message << " [" 421 | << int(result.code) << "]" << endl; 422 | } 423 | allGood = false; 424 | } 425 | suspendOutput(); 426 | } 427 | 428 | return allGood ? 0 : 1; 429 | } 430 | --------------------------------------------------------------------------------