annotate src/helper.cpp @ 64:e839338d3869 tip

Further Windows fix
author Chris Cannam
date Wed, 15 Apr 2020 16:30:40 +0100
parents 091baff0a983
children
rev   line source
Chris@2 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@0 2
Chris@0 3 /**
Chris@28 4 * [Vamp] Plugin Load Checker
Chris@0 5 *
Chris@4 6 * This program accepts the name of a descriptor symbol as its only
Chris@1 7 * command-line argument. It then reads a list of plugin library paths
Chris@1 8 * from stdin, one per line. For each path read, it attempts to load
Chris@4 9 * that library and retrieve the named descriptor symbol, printing a
Chris@1 10 * line to stdout reporting whether this was successful or not and
Chris@1 11 * then flushing stdout. The output line format is described
Chris@1 12 * below. The program exits with code 0 if all libraries were loaded
Chris@1 13 * successfully and non-zero otherwise.
Chris@0 14 *
Chris@0 15 * Note that library paths must be ready to pass to dlopen() or
Chris@0 16 * equivalent; this usually means they should be absolute paths.
Chris@0 17 *
Chris@0 18 * Output line for successful load of library libname.so:
Chris@0 19 * SUCCESS|/path/to/libname.so|
Chris@0 20 *
Chris@0 21 * Output line for failed load of library libname.so:
Chris@40 22 * FAILURE|/path/to/libname.so|Error message [failureCode]
Chris@40 23 *
Chris@40 24 * or:
Chris@40 25 * FAILURE|/path/to/libname.so|[failureCode]
Chris@40 26 *
Chris@40 27 * where the error message is an optional system-level message, such
Chris@40 28 * as may be returned from strerror or similar (which should be in the
Chris@40 29 * native language for the system ready to show the user), and the
Chris@40 30 * failureCode in square brackets is a mandatory number corresponding
Chris@40 31 * to one of the PluginCandidates::FailureCode values (requiring
Chris@40 32 * conversion to a translated string by the client).
Chris@0 33 *
Chris@28 34 * Although this program was written for use with Vamp audio analysis
Chris@28 35 * plugins, it also works with other plugin formats. The program has
Chris@28 36 * some hardcoded knowledge of Vamp, LADSPA, and DSSI plugins, but it
Chris@28 37 * can be used with any plugins that involve loading DLLs and looking
Chris@28 38 * up descriptor functions from them.
Chris@28 39 *
Chris@2 40 * Sometimes plugins will crash completely on load, bringing down this
Chris@2 41 * program with them. If the program exits before all listed plugins
Chris@2 42 * have been checked, this means that the plugin following the last
Chris@2 43 * reported one has crashed. Typically the caller may want to run it
Chris@2 44 * again, omitting that plugin.
Chris@0 45 */
Chris@0 46
Chris@5 47 /*
Chris@28 48 Copyright (c) 2016-2017 Queen Mary, University of London
Chris@5 49
Chris@5 50 Permission is hereby granted, free of charge, to any person
Chris@5 51 obtaining a copy of this software and associated documentation
Chris@5 52 files (the "Software"), to deal in the Software without
Chris@5 53 restriction, including without limitation the rights to use, copy,
Chris@5 54 modify, merge, publish, distribute, sublicense, and/or sell copies
Chris@5 55 of the Software, and to permit persons to whom the Software is
Chris@5 56 furnished to do so, subject to the following conditions:
Chris@5 57
Chris@5 58 The above copyright notice and this permission notice shall be
Chris@5 59 included in all copies or substantial portions of the Software.
Chris@5 60
Chris@5 61 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
Chris@5 62 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
Chris@5 63 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
Chris@5 64 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
Chris@5 65 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
Chris@5 66 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
Chris@5 67 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Chris@5 68
Chris@5 69 Except as contained in this notice, the names of the Centre for
Chris@5 70 Digital Music and Queen Mary, University of London shall not be
Chris@5 71 used in advertising or otherwise to promote the sale, use or other
Chris@5 72 dealings in this Software without prior written authorization.
Chris@5 73 */
Chris@5 74
Chris@27 75 #include "../version.h"
Chris@27 76
Chris@40 77 #include "../checker/checkcode.h"
Chris@40 78
Chris@28 79 static const char programName[] = "vamp-plugin-load-checker";
Chris@28 80
Chris@0 81 #ifdef _WIN32
Chris@0 82 #include <windows.h>
Chris@0 83 #include <process.h>
Chris@49 84 #include <io.h>
Chris@49 85 #else
Chris@49 86 #include <unistd.h>
Chris@38 87 #endif
Chris@38 88
Chris@64 89 #include <signal.h>
Chris@49 90 #include <fcntl.h>
Chris@49 91
Chris@10 92 #include <string>
Chris@38 93 #include <iostream>
Chris@50 94 #include <stdexcept>
Chris@38 95
Chris@62 96 static std::string currentSoname = "";
Chris@62 97
Chris@38 98 #ifdef _WIN32
Chris@38 99 #ifndef UNICODE
Chris@38 100 #error "This must be compiled with UNICODE defined"
Chris@38 101 #endif
Chris@40 102
Chris@42 103 static HMODULE loadLibraryUTF8(std::string name) {
Chris@10 104 int n = name.size();
Chris@16 105 int wn = MultiByteToWideChar(CP_UTF8, 0, name.c_str(), n, 0, 0);
Chris@16 106 wchar_t *wname = new wchar_t[wn+1];
Chris@16 107 wn = MultiByteToWideChar(CP_UTF8, 0, name.c_str(), n, wname, wn);
Chris@16 108 wname[wn] = L'\0';
Chris@10 109 HMODULE h = LoadLibraryW(wname);
Chris@10 110 delete[] wname;
Chris@10 111 return h;
Chris@10 112 }
Chris@40 113
Chris@42 114 static std::string getErrorText() {
Chris@38 115 DWORD err = GetLastError();
Chris@42 116 wchar_t *buffer = 0;
Chris@38 117 FormatMessageW(
Chris@10 118 FORMAT_MESSAGE_ALLOCATE_BUFFER |
Chris@10 119 FORMAT_MESSAGE_FROM_SYSTEM |
Chris@10 120 FORMAT_MESSAGE_IGNORE_INSERTS,
Chris@10 121 NULL,
Chris@10 122 err,
Chris@42 123 // the correct way to specify the user's default language,
Chris@42 124 // according to all resources I could find:
Chris@42 125 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
Chris@38 126 (LPWSTR) &buffer,
Chris@10 127 0, NULL );
Chris@42 128 if (!buffer) {
Chris@42 129 return "Unable to format error string (internal error)";
Chris@42 130 }
Chris@16 131 int wn = wcslen(buffer);
Chris@16 132 int n = WideCharToMultiByte(CP_UTF8, 0, buffer, wn, 0, 0, 0, 0);
Chris@17 133 if (n < 0) {
Chris@46 134 LocalFree(buffer);
Chris@17 135 return "Unable to convert error string (internal error)";
Chris@17 136 }
Chris@16 137 char *text = new char[n+1];
Chris@17 138 (void)WideCharToMultiByte(CP_UTF8, 0, buffer, wn, text, n, 0, 0);
Chris@16 139 text[n] = '\0';
Chris@10 140 std::string s(text);
Chris@46 141 LocalFree(buffer);
Chris@10 142 delete[] text;
Chris@42 143 if (s == "") {
Chris@42 144 return s;
Chris@42 145 }
Chris@10 146 for (int i = s.size(); i > 0; ) {
Chris@10 147 --i;
Chris@10 148 if (s[i] == '\n' || s[i] == '\r') {
Chris@10 149 s.erase(i, 1);
Chris@10 150 }
Chris@10 151 }
Chris@18 152 std::size_t pos = s.find("%1");
Chris@62 153 if (pos != std::string::npos && currentSoname != "") {
Chris@62 154 s.replace(pos, 2, currentSoname);
Chris@18 155 }
Chris@10 156 return s;
Chris@10 157 }
Chris@40 158
Chris@42 159 #define DLOPEN(a,b) loadLibraryUTF8(a)
Chris@10 160 #define DLSYM(a,b) (void *)GetProcAddress((HINSTANCE)(a),(b).c_str())
Chris@0 161 #define DLCLOSE(a) (!FreeLibrary((HINSTANCE)(a)))
Chris@42 162 #define DLERROR() (getErrorText())
Chris@42 163
Chris@42 164 static bool libraryExists(std::string name) {
Chris@42 165 if (name == "") return false;
Chris@42 166 int n = name.size();
Chris@42 167 int wn = MultiByteToWideChar(CP_UTF8, 0, name.c_str(), n, 0, 0);
Chris@42 168 wchar_t *wname = new wchar_t[wn+1];
Chris@42 169 wn = MultiByteToWideChar(CP_UTF8, 0, name.c_str(), n, wname, wn);
Chris@42 170 wname[wn] = L'\0';
Chris@42 171 FILE *f = _wfopen(wname, L"rb");
Chris@42 172 delete[] wname;
Chris@42 173 if (f) {
Chris@42 174 fclose(f);
Chris@42 175 return true;
Chris@42 176 } else {
Chris@42 177 return false;
Chris@42 178 }
Chris@42 179 }
Chris@40 180
Chris@0 181 #else
Chris@40 182
Chris@0 183 #include <dlfcn.h>
Chris@0 184 #define DLOPEN(a,b) dlopen((a).c_str(),(b))
Chris@0 185 #define DLSYM(a,b) dlsym((a),(b).c_str())
Chris@0 186 #define DLCLOSE(a) dlclose((a))
Chris@0 187 #define DLERROR() dlerror()
Chris@40 188
Chris@42 189 static bool libraryExists(std::string name) {
Chris@42 190 if (name == "") return false;
Chris@42 191 FILE *f = fopen(name.c_str(), "r");
Chris@42 192 if (f) {
Chris@42 193 fclose(f);
Chris@42 194 return true;
Chris@42 195 } else {
Chris@42 196 return false;
Chris@42 197 }
Chris@42 198 }
Chris@42 199
Chris@0 200 #endif
Chris@0 201
Chris@0 202 using namespace std;
Chris@0 203
Chris@0 204 string error()
Chris@0 205 {
Chris@40 206 return DLERROR();
Chris@0 207 }
Chris@0 208
Chris@40 209 struct Result {
Chris@40 210 PluginCheckCode code;
Chris@40 211 string message;
Chris@40 212 };
Chris@40 213
Chris@40 214 Result checkLADSPAStyleDescriptorFn(void *f)
Chris@23 215 {
Chris@23 216 typedef const void *(*DFn)(unsigned long);
Chris@23 217 DFn fn = DFn(f);
Chris@23 218 unsigned long index = 0;
Chris@23 219 while (fn(index)) ++index;
Chris@40 220 if (index == 0) return { PluginCheckCode::FAIL_NO_PLUGINS, "" };
Chris@40 221 return { PluginCheckCode::SUCCESS, "" };
Chris@23 222 }
Chris@23 223
Chris@40 224 Result checkVampDescriptorFn(void *f)
Chris@23 225 {
Chris@23 226 typedef const void *(*DFn)(unsigned int, unsigned int);
Chris@23 227 DFn fn = DFn(f);
Chris@23 228 unsigned int index = 0;
Chris@25 229 while (fn(2, index)) ++index;
Chris@40 230 if (index == 0) return { PluginCheckCode::FAIL_NO_PLUGINS, "" };
Chris@40 231 return { PluginCheckCode::SUCCESS, "" };
Chris@23 232 }
Chris@23 233
Chris@40 234 Result check(string soname, string descriptor)
Chris@0 235 {
Chris@0 236 void *handle = DLOPEN(soname, RTLD_NOW | RTLD_LOCAL);
Chris@0 237 if (!handle) {
Chris@40 238 PluginCheckCode code = PluginCheckCode::FAIL_NOT_LOADABLE;
Chris@40 239 string message = error();
Chris@40 240 #ifdef _WIN32
Chris@40 241 DWORD err = GetLastError();
Chris@40 242 if (err == ERROR_BAD_EXE_FORMAT) {
Chris@40 243 code = PluginCheckCode::FAIL_WRONG_ARCHITECTURE;
Chris@40 244 } else if (err == ERROR_MOD_NOT_FOUND) {
Chris@42 245 if (libraryExists(soname)) {
Chris@42 246 code = PluginCheckCode::FAIL_DEPENDENCY_MISSING;
Chris@42 247 } else {
Chris@42 248 code = PluginCheckCode::FAIL_LIBRARY_NOT_FOUND;
Chris@42 249 }
Chris@42 250 }
Chris@52 251 #else // !_WIN32
Chris@52 252 #ifdef __APPLE__
Chris@52 253 if (errno == EPERM) {
Chris@51 254 // This may be unreliable, but it seems to be set by
Chris@51 255 // something dlopen() calls in the case where a library
Chris@51 256 // can't be loaded for code-signing-related reasons on
Chris@51 257 // macOS
Chris@51 258 code = PluginCheckCode::FAIL_FORBIDDEN;
Chris@52 259 } else if (!libraryExists(soname)) {
Chris@52 260 code = PluginCheckCode::FAIL_LIBRARY_NOT_FOUND;
Chris@40 261 }
Chris@52 262 #else // !__APPLE__
Chris@52 263 if (!libraryExists(soname)) {
Chris@52 264 code = PluginCheckCode::FAIL_LIBRARY_NOT_FOUND;
Chris@52 265 }
Chris@52 266 #endif // !__APPLE__
Chris@52 267 #endif // !_WIN32
Chris@52 268
Chris@40 269 return { code, message };
Chris@0 270 }
Chris@0 271
Chris@40 272 Result result { PluginCheckCode::SUCCESS, "" };
Chris@55 273
Chris@0 274 void *fn = DLSYM(handle, descriptor);
Chris@0 275 if (!fn) {
Chris@40 276 result = { PluginCheckCode::FAIL_DESCRIPTOR_MISSING, error() };
Chris@38 277 } else if (descriptor == "ladspa_descriptor") {
Chris@40 278 result = checkLADSPAStyleDescriptorFn(fn);
Chris@23 279 } else if (descriptor == "dssi_descriptor") {
Chris@40 280 result = checkLADSPAStyleDescriptorFn(fn);
Chris@23 281 } else if (descriptor == "vampGetPluginDescriptor") {
Chris@40 282 result = checkVampDescriptorFn(fn);
Chris@23 283 } else {
Chris@23 284 cerr << "Note: no descriptor logic known for descriptor function \""
Chris@23 285 << descriptor << "\"; not actually calling it" << endl;
Chris@23 286 }
Chris@38 287
Chris@38 288 DLCLOSE(handle);
Chris@23 289
Chris@40 290 return result;
Chris@0 291 }
Chris@0 292
Chris@49 293 // We write our output to stdout, but want to ensure that the plugin
Chris@49 294 // doesn't write anything itself. To do this we open a null file
Chris@49 295 // descriptor and dup2() it into place of stdout in the gaps between
Chris@49 296 // our own output activity.
Chris@49 297
Chris@49 298 static int normalFd = -1;
Chris@49 299 static int suspendedFd = -1;
Chris@49 300
Chris@49 301 static void initFds()
Chris@49 302 {
Chris@49 303 #ifdef _WIN32
Chris@49 304 normalFd = _dup(1);
Chris@49 305 suspendedFd = _open("NUL", _O_WRONLY);
Chris@49 306 #else
Chris@49 307 normalFd = dup(1);
Chris@49 308 suspendedFd = open("/dev/null", O_WRONLY);
Chris@49 309 #endif
Chris@49 310
Chris@49 311 if (normalFd < 0 || suspendedFd < 0) {
Chris@50 312 throw std::runtime_error
Chris@50 313 ("Failed to initialise fds for stdio suspend/resume");
Chris@49 314 }
Chris@49 315 }
Chris@49 316
Chris@49 317 static void suspendOutput()
Chris@49 318 {
Chris@49 319 #ifdef _WIN32
Chris@49 320 _dup2(suspendedFd, 1);
Chris@49 321 #else
Chris@49 322 dup2(suspendedFd, 1);
Chris@49 323 #endif
Chris@49 324 }
Chris@49 325
Chris@49 326 static void resumeOutput()
Chris@49 327 {
Chris@49 328 fflush(stdout);
Chris@49 329 #ifdef _WIN32
Chris@49 330 _dup2(normalFd, 1);
Chris@49 331 #else
Chris@49 332 dup2(normalFd, 1);
Chris@49 333 #endif
Chris@49 334 }
Chris@49 335
Chris@62 336 static void
Chris@62 337 signalHandler(int signal)
Chris@62 338 {
Chris@62 339 cerr << "Signal " << signal << " caught" << endl;
Chris@62 340 cout << "FAILURE|" << currentSoname << "|[" << int(PluginCheckCode::FAIL_NOT_LOADABLE) << "]" << endl;
Chris@62 341 exit(1);
Chris@62 342 }
Chris@62 343
Chris@0 344 int main(int argc, char **argv)
Chris@0 345 {
Chris@0 346 bool allGood = true;
Chris@0 347 string soname;
Chris@0 348
Chris@27 349 bool showUsage = false;
Chris@27 350
Chris@27 351 if (argc > 1) {
Chris@27 352 string opt = argv[1];
Chris@27 353 if (opt == "-?" || opt == "-h" || opt == "--help") {
Chris@27 354 showUsage = true;
Chris@27 355 } else if (opt == "-v" || opt == "--version") {
Chris@40 356 cout << CHECKER_COMPATIBILITY_VERSION << endl;
Chris@27 357 return 0;
Chris@27 358 }
Chris@27 359 }
Chris@27 360
Chris@27 361 if (argc != 2 || showUsage) {
Chris@27 362 cerr << endl;
Chris@28 363 cerr << programName << ": Test shared library objects for plugins to be" << endl;
Chris@27 364 cerr << "loaded via descriptor functions." << endl;
Chris@28 365 cerr << "\n Usage: " << programName << " <descriptorname>\n"
Chris@11 366 "\nwhere descriptorname is the name of a plugin descriptor symbol to be sought\n"
Chris@11 367 "in each library (e.g. vampGetPluginDescriptor for Vamp plugins). The list of\n"
Chris@11 368 "candidate plugin library filenames is read from stdin.\n" << endl;
Chris@11 369 return 2;
Chris@1 370 }
Chris@1 371
Chris@62 372 signal(SIGINT, signalHandler);
Chris@62 373 signal(SIGTERM, signalHandler);
Chris@64 374 signal(SIGSEGV, signalHandler);
Chris@64 375 signal(SIGILL, signalHandler);
Chris@64 376 signal(SIGABRT, signalHandler);
Chris@64 377 signal(SIGFPE, signalHandler);
Chris@62 378
Chris@62 379 #ifndef _WIN32
Chris@62 380 signal(SIGHUP, signalHandler);
Chris@62 381 signal(SIGQUIT, signalHandler);
Chris@62 382 signal(SIGBUS, signalHandler);
Chris@62 383 #endif
Chris@62 384
Chris@1 385 string descriptor = argv[1];
Chris@1 386
Chris@32 387 #ifdef _WIN32
Chris@32 388 // Avoid showing the error-handler dialog for missing DLLs,
Chris@32 389 // failing quietly instead. It's permissible for this program
Chris@32 390 // to simply fail when a DLL can't be loaded -- showing the
Chris@32 391 // error dialog wouldn't change this anyway, it would just
Chris@32 392 // block the program until the user clicked it away and then
Chris@32 393 // fail anyway.
Chris@32 394 SetErrorMode(SEM_FAILCRITICALERRORS);
Chris@32 395 #endif
Chris@32 396
Chris@49 397 initFds();
Chris@49 398 suspendOutput();
Chris@49 399
Chris@0 400 while (getline(cin, soname)) {
Chris@62 401
Chris@62 402 currentSoname = soname;
Chris@62 403
Chris@40 404 Result result = check(soname, descriptor);
Chris@49 405 resumeOutput();
Chris@40 406 if (result.code == PluginCheckCode::SUCCESS) {
Chris@40 407 cout << "SUCCESS|" << soname << "|" << endl;
Chris@40 408 } else {
Chris@40 409 if (result.message == "") {
Chris@40 410 cout << "FAILURE|" << soname
Chris@40 411 << "|[" << int(result.code) << "]" << endl;
Chris@40 412 } else {
Chris@51 413 for (size_t i = 0; i < result.message.size(); ++i) {
Chris@51 414 if (result.message[i] == '\n' ||
Chris@51 415 result.message[i] == '\r') {
Chris@51 416 result.message[i] = ' ';
Chris@51 417 }
Chris@51 418 }
Chris@40 419 cout << "FAILURE|" << soname
Chris@40 420 << "|" << result.message << " ["
Chris@40 421 << int(result.code) << "]" << endl;
Chris@40 422 }
Chris@11 423 allGood = false;
Chris@11 424 }
Chris@49 425 suspendOutput();
Chris@0 426 }
Chris@11 427
Chris@0 428 return allGood ? 0 : 1;
Chris@0 429 }