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@49
|
89 #include <fcntl.h>
|
Chris@49
|
90
|
Chris@10
|
91 #include <string>
|
Chris@38
|
92 #include <iostream>
|
Chris@50
|
93 #include <stdexcept>
|
Chris@38
|
94
|
Chris@62
|
95 static std::string currentSoname = "";
|
Chris@62
|
96
|
Chris@38
|
97 #ifdef _WIN32
|
Chris@38
|
98 #ifndef UNICODE
|
Chris@38
|
99 #error "This must be compiled with UNICODE defined"
|
Chris@38
|
100 #endif
|
Chris@40
|
101
|
Chris@42
|
102 static HMODULE loadLibraryUTF8(std::string name) {
|
Chris@10
|
103 int n = name.size();
|
Chris@16
|
104 int wn = MultiByteToWideChar(CP_UTF8, 0, name.c_str(), n, 0, 0);
|
Chris@16
|
105 wchar_t *wname = new wchar_t[wn+1];
|
Chris@16
|
106 wn = MultiByteToWideChar(CP_UTF8, 0, name.c_str(), n, wname, wn);
|
Chris@16
|
107 wname[wn] = L'\0';
|
Chris@10
|
108 HMODULE h = LoadLibraryW(wname);
|
Chris@10
|
109 delete[] wname;
|
Chris@10
|
110 return h;
|
Chris@10
|
111 }
|
Chris@40
|
112
|
Chris@42
|
113 static std::string getErrorText() {
|
Chris@38
|
114 DWORD err = GetLastError();
|
Chris@42
|
115 wchar_t *buffer = 0;
|
Chris@38
|
116 FormatMessageW(
|
Chris@10
|
117 FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
Chris@10
|
118 FORMAT_MESSAGE_FROM_SYSTEM |
|
Chris@10
|
119 FORMAT_MESSAGE_IGNORE_INSERTS,
|
Chris@10
|
120 NULL,
|
Chris@10
|
121 err,
|
Chris@42
|
122 // the correct way to specify the user's default language,
|
Chris@42
|
123 // according to all resources I could find:
|
Chris@42
|
124 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
Chris@38
|
125 (LPWSTR) &buffer,
|
Chris@10
|
126 0, NULL );
|
Chris@42
|
127 if (!buffer) {
|
Chris@42
|
128 return "Unable to format error string (internal error)";
|
Chris@42
|
129 }
|
Chris@16
|
130 int wn = wcslen(buffer);
|
Chris@16
|
131 int n = WideCharToMultiByte(CP_UTF8, 0, buffer, wn, 0, 0, 0, 0);
|
Chris@17
|
132 if (n < 0) {
|
Chris@46
|
133 LocalFree(buffer);
|
Chris@17
|
134 return "Unable to convert error string (internal error)";
|
Chris@17
|
135 }
|
Chris@16
|
136 char *text = new char[n+1];
|
Chris@17
|
137 (void)WideCharToMultiByte(CP_UTF8, 0, buffer, wn, text, n, 0, 0);
|
Chris@16
|
138 text[n] = '\0';
|
Chris@10
|
139 std::string s(text);
|
Chris@46
|
140 LocalFree(buffer);
|
Chris@10
|
141 delete[] text;
|
Chris@42
|
142 if (s == "") {
|
Chris@42
|
143 return s;
|
Chris@42
|
144 }
|
Chris@10
|
145 for (int i = s.size(); i > 0; ) {
|
Chris@10
|
146 --i;
|
Chris@10
|
147 if (s[i] == '\n' || s[i] == '\r') {
|
Chris@10
|
148 s.erase(i, 1);
|
Chris@10
|
149 }
|
Chris@10
|
150 }
|
Chris@18
|
151 std::size_t pos = s.find("%1");
|
Chris@62
|
152 if (pos != std::string::npos && currentSoname != "") {
|
Chris@62
|
153 s.replace(pos, 2, currentSoname);
|
Chris@18
|
154 }
|
Chris@10
|
155 return s;
|
Chris@10
|
156 }
|
Chris@40
|
157
|
Chris@42
|
158 #define DLOPEN(a,b) loadLibraryUTF8(a)
|
Chris@10
|
159 #define DLSYM(a,b) (void *)GetProcAddress((HINSTANCE)(a),(b).c_str())
|
Chris@0
|
160 #define DLCLOSE(a) (!FreeLibrary((HINSTANCE)(a)))
|
Chris@42
|
161 #define DLERROR() (getErrorText())
|
Chris@42
|
162
|
Chris@42
|
163 static bool libraryExists(std::string name) {
|
Chris@42
|
164 if (name == "") return false;
|
Chris@42
|
165 int n = name.size();
|
Chris@42
|
166 int wn = MultiByteToWideChar(CP_UTF8, 0, name.c_str(), n, 0, 0);
|
Chris@42
|
167 wchar_t *wname = new wchar_t[wn+1];
|
Chris@42
|
168 wn = MultiByteToWideChar(CP_UTF8, 0, name.c_str(), n, wname, wn);
|
Chris@42
|
169 wname[wn] = L'\0';
|
Chris@42
|
170 FILE *f = _wfopen(wname, L"rb");
|
Chris@42
|
171 delete[] wname;
|
Chris@42
|
172 if (f) {
|
Chris@42
|
173 fclose(f);
|
Chris@42
|
174 return true;
|
Chris@42
|
175 } else {
|
Chris@42
|
176 return false;
|
Chris@42
|
177 }
|
Chris@42
|
178 }
|
Chris@40
|
179
|
Chris@0
|
180 #else
|
Chris@40
|
181
|
Chris@0
|
182 #include <dlfcn.h>
|
Chris@0
|
183 #define DLOPEN(a,b) dlopen((a).c_str(),(b))
|
Chris@0
|
184 #define DLSYM(a,b) dlsym((a),(b).c_str())
|
Chris@0
|
185 #define DLCLOSE(a) dlclose((a))
|
Chris@0
|
186 #define DLERROR() dlerror()
|
Chris@40
|
187
|
Chris@42
|
188 static bool libraryExists(std::string name) {
|
Chris@42
|
189 if (name == "") return false;
|
Chris@42
|
190 FILE *f = fopen(name.c_str(), "r");
|
Chris@42
|
191 if (f) {
|
Chris@42
|
192 fclose(f);
|
Chris@42
|
193 return true;
|
Chris@42
|
194 } else {
|
Chris@42
|
195 return false;
|
Chris@42
|
196 }
|
Chris@42
|
197 }
|
Chris@42
|
198
|
Chris@0
|
199 #endif
|
Chris@0
|
200
|
Chris@0
|
201 using namespace std;
|
Chris@0
|
202
|
Chris@0
|
203 string error()
|
Chris@0
|
204 {
|
Chris@40
|
205 return DLERROR();
|
Chris@0
|
206 }
|
Chris@0
|
207
|
Chris@40
|
208 struct Result {
|
Chris@40
|
209 PluginCheckCode code;
|
Chris@40
|
210 string message;
|
Chris@40
|
211 };
|
Chris@40
|
212
|
Chris@40
|
213 Result checkLADSPAStyleDescriptorFn(void *f)
|
Chris@23
|
214 {
|
Chris@23
|
215 typedef const void *(*DFn)(unsigned long);
|
Chris@23
|
216 DFn fn = DFn(f);
|
Chris@23
|
217 unsigned long index = 0;
|
Chris@23
|
218 while (fn(index)) ++index;
|
Chris@40
|
219 if (index == 0) return { PluginCheckCode::FAIL_NO_PLUGINS, "" };
|
Chris@40
|
220 return { PluginCheckCode::SUCCESS, "" };
|
Chris@23
|
221 }
|
Chris@23
|
222
|
Chris@40
|
223 Result checkVampDescriptorFn(void *f)
|
Chris@23
|
224 {
|
Chris@23
|
225 typedef const void *(*DFn)(unsigned int, unsigned int);
|
Chris@23
|
226 DFn fn = DFn(f);
|
Chris@23
|
227 unsigned int index = 0;
|
Chris@25
|
228 while (fn(2, index)) ++index;
|
Chris@40
|
229 if (index == 0) return { PluginCheckCode::FAIL_NO_PLUGINS, "" };
|
Chris@40
|
230 return { PluginCheckCode::SUCCESS, "" };
|
Chris@23
|
231 }
|
Chris@23
|
232
|
Chris@40
|
233 Result check(string soname, string descriptor)
|
Chris@0
|
234 {
|
Chris@0
|
235 void *handle = DLOPEN(soname, RTLD_NOW | RTLD_LOCAL);
|
Chris@0
|
236 if (!handle) {
|
Chris@40
|
237 PluginCheckCode code = PluginCheckCode::FAIL_NOT_LOADABLE;
|
Chris@40
|
238 string message = error();
|
Chris@40
|
239 #ifdef _WIN32
|
Chris@40
|
240 DWORD err = GetLastError();
|
Chris@40
|
241 if (err == ERROR_BAD_EXE_FORMAT) {
|
Chris@40
|
242 code = PluginCheckCode::FAIL_WRONG_ARCHITECTURE;
|
Chris@40
|
243 } else if (err == ERROR_MOD_NOT_FOUND) {
|
Chris@42
|
244 if (libraryExists(soname)) {
|
Chris@42
|
245 code = PluginCheckCode::FAIL_DEPENDENCY_MISSING;
|
Chris@42
|
246 } else {
|
Chris@42
|
247 code = PluginCheckCode::FAIL_LIBRARY_NOT_FOUND;
|
Chris@42
|
248 }
|
Chris@42
|
249 }
|
Chris@52
|
250 #else // !_WIN32
|
Chris@52
|
251 #ifdef __APPLE__
|
Chris@52
|
252 if (errno == EPERM) {
|
Chris@51
|
253 // This may be unreliable, but it seems to be set by
|
Chris@51
|
254 // something dlopen() calls in the case where a library
|
Chris@51
|
255 // can't be loaded for code-signing-related reasons on
|
Chris@51
|
256 // macOS
|
Chris@51
|
257 code = PluginCheckCode::FAIL_FORBIDDEN;
|
Chris@52
|
258 } else if (!libraryExists(soname)) {
|
Chris@52
|
259 code = PluginCheckCode::FAIL_LIBRARY_NOT_FOUND;
|
Chris@40
|
260 }
|
Chris@52
|
261 #else // !__APPLE__
|
Chris@52
|
262 if (!libraryExists(soname)) {
|
Chris@52
|
263 code = PluginCheckCode::FAIL_LIBRARY_NOT_FOUND;
|
Chris@52
|
264 }
|
Chris@52
|
265 #endif // !__APPLE__
|
Chris@52
|
266 #endif // !_WIN32
|
Chris@52
|
267
|
Chris@40
|
268 return { code, message };
|
Chris@0
|
269 }
|
Chris@0
|
270
|
Chris@40
|
271 Result result { PluginCheckCode::SUCCESS, "" };
|
Chris@55
|
272
|
Chris@0
|
273 void *fn = DLSYM(handle, descriptor);
|
Chris@0
|
274 if (!fn) {
|
Chris@40
|
275 result = { PluginCheckCode::FAIL_DESCRIPTOR_MISSING, error() };
|
Chris@38
|
276 } else if (descriptor == "ladspa_descriptor") {
|
Chris@40
|
277 result = checkLADSPAStyleDescriptorFn(fn);
|
Chris@23
|
278 } else if (descriptor == "dssi_descriptor") {
|
Chris@40
|
279 result = checkLADSPAStyleDescriptorFn(fn);
|
Chris@23
|
280 } else if (descriptor == "vampGetPluginDescriptor") {
|
Chris@40
|
281 result = checkVampDescriptorFn(fn);
|
Chris@23
|
282 } else {
|
Chris@23
|
283 cerr << "Note: no descriptor logic known for descriptor function \""
|
Chris@23
|
284 << descriptor << "\"; not actually calling it" << endl;
|
Chris@23
|
285 }
|
Chris@38
|
286
|
Chris@38
|
287 DLCLOSE(handle);
|
Chris@23
|
288
|
Chris@40
|
289 return result;
|
Chris@0
|
290 }
|
Chris@0
|
291
|
Chris@49
|
292 // We write our output to stdout, but want to ensure that the plugin
|
Chris@49
|
293 // doesn't write anything itself. To do this we open a null file
|
Chris@49
|
294 // descriptor and dup2() it into place of stdout in the gaps between
|
Chris@49
|
295 // our own output activity.
|
Chris@49
|
296
|
Chris@49
|
297 static int normalFd = -1;
|
Chris@49
|
298 static int suspendedFd = -1;
|
Chris@49
|
299
|
Chris@49
|
300 static void initFds()
|
Chris@49
|
301 {
|
Chris@49
|
302 #ifdef _WIN32
|
Chris@49
|
303 normalFd = _dup(1);
|
Chris@49
|
304 suspendedFd = _open("NUL", _O_WRONLY);
|
Chris@49
|
305 #else
|
Chris@49
|
306 normalFd = dup(1);
|
Chris@49
|
307 suspendedFd = open("/dev/null", O_WRONLY);
|
Chris@49
|
308 #endif
|
Chris@49
|
309
|
Chris@49
|
310 if (normalFd < 0 || suspendedFd < 0) {
|
Chris@50
|
311 throw std::runtime_error
|
Chris@50
|
312 ("Failed to initialise fds for stdio suspend/resume");
|
Chris@49
|
313 }
|
Chris@49
|
314 }
|
Chris@49
|
315
|
Chris@49
|
316 static void suspendOutput()
|
Chris@49
|
317 {
|
Chris@49
|
318 #ifdef _WIN32
|
Chris@49
|
319 _dup2(suspendedFd, 1);
|
Chris@49
|
320 #else
|
Chris@49
|
321 dup2(suspendedFd, 1);
|
Chris@49
|
322 #endif
|
Chris@49
|
323 }
|
Chris@49
|
324
|
Chris@49
|
325 static void resumeOutput()
|
Chris@49
|
326 {
|
Chris@49
|
327 fflush(stdout);
|
Chris@49
|
328 #ifdef _WIN32
|
Chris@49
|
329 _dup2(normalFd, 1);
|
Chris@49
|
330 #else
|
Chris@49
|
331 dup2(normalFd, 1);
|
Chris@49
|
332 #endif
|
Chris@49
|
333 }
|
Chris@49
|
334
|
Chris@62
|
335 static void
|
Chris@62
|
336 signalHandler(int signal)
|
Chris@62
|
337 {
|
Chris@62
|
338 cerr << "Signal " << signal << " caught" << endl;
|
Chris@62
|
339 cout << "FAILURE|" << currentSoname << "|[" << int(PluginCheckCode::FAIL_NOT_LOADABLE) << "]" << endl;
|
Chris@62
|
340 exit(1);
|
Chris@62
|
341 }
|
Chris@62
|
342
|
Chris@0
|
343 int main(int argc, char **argv)
|
Chris@0
|
344 {
|
Chris@0
|
345 bool allGood = true;
|
Chris@0
|
346 string soname;
|
Chris@0
|
347
|
Chris@27
|
348 bool showUsage = false;
|
Chris@27
|
349
|
Chris@27
|
350 if (argc > 1) {
|
Chris@27
|
351 string opt = argv[1];
|
Chris@27
|
352 if (opt == "-?" || opt == "-h" || opt == "--help") {
|
Chris@27
|
353 showUsage = true;
|
Chris@27
|
354 } else if (opt == "-v" || opt == "--version") {
|
Chris@40
|
355 cout << CHECKER_COMPATIBILITY_VERSION << endl;
|
Chris@27
|
356 return 0;
|
Chris@27
|
357 }
|
Chris@27
|
358 }
|
Chris@27
|
359
|
Chris@27
|
360 if (argc != 2 || showUsage) {
|
Chris@27
|
361 cerr << endl;
|
Chris@28
|
362 cerr << programName << ": Test shared library objects for plugins to be" << endl;
|
Chris@27
|
363 cerr << "loaded via descriptor functions." << endl;
|
Chris@28
|
364 cerr << "\n Usage: " << programName << " <descriptorname>\n"
|
Chris@11
|
365 "\nwhere descriptorname is the name of a plugin descriptor symbol to be sought\n"
|
Chris@11
|
366 "in each library (e.g. vampGetPluginDescriptor for Vamp plugins). The list of\n"
|
Chris@11
|
367 "candidate plugin library filenames is read from stdin.\n" << endl;
|
Chris@11
|
368 return 2;
|
Chris@1
|
369 }
|
Chris@1
|
370
|
Chris@62
|
371 signal(SIGINT, signalHandler);
|
Chris@62
|
372 signal(SIGTERM, signalHandler);
|
Chris@62
|
373
|
Chris@62
|
374 #ifndef _WIN32
|
Chris@62
|
375 signal(SIGHUP, signalHandler);
|
Chris@62
|
376 signal(SIGQUIT, signalHandler);
|
Chris@62
|
377 signal(SIGILL, signalHandler);
|
Chris@62
|
378 signal(SIGABRT, signalHandler);
|
Chris@62
|
379 signal(SIGSEGV, signalHandler);
|
Chris@62
|
380 signal(SIGBUS, signalHandler);
|
Chris@62
|
381 #endif
|
Chris@62
|
382
|
Chris@1
|
383 string descriptor = argv[1];
|
Chris@1
|
384
|
Chris@32
|
385 #ifdef _WIN32
|
Chris@32
|
386 // Avoid showing the error-handler dialog for missing DLLs,
|
Chris@32
|
387 // failing quietly instead. It's permissible for this program
|
Chris@32
|
388 // to simply fail when a DLL can't be loaded -- showing the
|
Chris@32
|
389 // error dialog wouldn't change this anyway, it would just
|
Chris@32
|
390 // block the program until the user clicked it away and then
|
Chris@32
|
391 // fail anyway.
|
Chris@32
|
392 SetErrorMode(SEM_FAILCRITICALERRORS);
|
Chris@32
|
393 #endif
|
Chris@32
|
394
|
Chris@49
|
395 initFds();
|
Chris@49
|
396 suspendOutput();
|
Chris@49
|
397
|
Chris@0
|
398 while (getline(cin, soname)) {
|
Chris@62
|
399
|
Chris@62
|
400 currentSoname = soname;
|
Chris@62
|
401
|
Chris@40
|
402 Result result = check(soname, descriptor);
|
Chris@49
|
403 resumeOutput();
|
Chris@40
|
404 if (result.code == PluginCheckCode::SUCCESS) {
|
Chris@40
|
405 cout << "SUCCESS|" << soname << "|" << endl;
|
Chris@40
|
406 } else {
|
Chris@40
|
407 if (result.message == "") {
|
Chris@40
|
408 cout << "FAILURE|" << soname
|
Chris@40
|
409 << "|[" << int(result.code) << "]" << endl;
|
Chris@40
|
410 } else {
|
Chris@51
|
411 for (size_t i = 0; i < result.message.size(); ++i) {
|
Chris@51
|
412 if (result.message[i] == '\n' ||
|
Chris@51
|
413 result.message[i] == '\r') {
|
Chris@51
|
414 result.message[i] = ' ';
|
Chris@51
|
415 }
|
Chris@51
|
416 }
|
Chris@40
|
417 cout << "FAILURE|" << soname
|
Chris@40
|
418 << "|" << result.message << " ["
|
Chris@40
|
419 << int(result.code) << "]" << endl;
|
Chris@40
|
420 }
|
Chris@11
|
421 allGood = false;
|
Chris@11
|
422 }
|
Chris@49
|
423 suspendOutput();
|
Chris@0
|
424 }
|
Chris@11
|
425
|
Chris@0
|
426 return allGood ? 0 : 1;
|
Chris@0
|
427 }
|