Chris@2
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@5
|
2 /*
|
Chris@5
|
3 Copyright (c) 2016 Queen Mary, University of London
|
Chris@5
|
4
|
Chris@5
|
5 Permission is hereby granted, free of charge, to any person
|
Chris@5
|
6 obtaining a copy of this software and associated documentation
|
Chris@5
|
7 files (the "Software"), to deal in the Software without
|
Chris@5
|
8 restriction, including without limitation the rights to use, copy,
|
Chris@5
|
9 modify, merge, publish, distribute, sublicense, and/or sell copies
|
Chris@5
|
10 of the Software, and to permit persons to whom the Software is
|
Chris@5
|
11 furnished to do so, subject to the following conditions:
|
Chris@5
|
12
|
Chris@5
|
13 The above copyright notice and this permission notice shall be
|
Chris@5
|
14 included in all copies or substantial portions of the Software.
|
Chris@5
|
15
|
Chris@5
|
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
Chris@5
|
17 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
Chris@5
|
18 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
Chris@5
|
19 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
Chris@5
|
20 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
Chris@5
|
21 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
Chris@5
|
22 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
Chris@5
|
23
|
Chris@5
|
24 Except as contained in this notice, the names of the Centre for
|
Chris@5
|
25 Digital Music and Queen Mary, University of London shall not be
|
Chris@5
|
26 used in advertising or otherwise to promote the sale, use or other
|
Chris@5
|
27 dealings in this Software without prior written authorization.
|
Chris@5
|
28 */
|
Chris@2
|
29
|
Chris@2
|
30 #include "plugincandidates.h"
|
Chris@2
|
31
|
Chris@2
|
32 #include <set>
|
Chris@2
|
33 #include <stdexcept>
|
Chris@2
|
34 #include <iostream>
|
Chris@2
|
35
|
Chris@2
|
36 #include <QProcess>
|
Chris@2
|
37 #include <QDir>
|
Chris@2
|
38
|
Chris@4
|
39 #if defined(_WIN32)
|
Chris@2
|
40 #define PLUGIN_GLOB "*.dll"
|
Chris@4
|
41 #elif defined(__APPLE__)
|
Chris@2
|
42 #define PLUGIN_GLOB "*.dylib *.so"
|
Chris@2
|
43 #else
|
Chris@2
|
44 #define PLUGIN_GLOB "*.so"
|
Chris@2
|
45 #endif
|
Chris@2
|
46
|
Chris@2
|
47 using namespace std;
|
Chris@2
|
48
|
Chris@2
|
49 PluginCandidates::PluginCandidates(string helperExecutableName) :
|
Chris@2
|
50 m_helper(helperExecutableName)
|
Chris@2
|
51 {
|
Chris@2
|
52 }
|
Chris@2
|
53
|
Chris@2
|
54 vector<string>
|
Chris@4
|
55 PluginCandidates::getCandidateLibrariesFor(string tag) const
|
Chris@2
|
56 {
|
Chris@4
|
57 if (m_candidates.find(tag) == m_candidates.end()) return {};
|
Chris@4
|
58 else return m_candidates.at(tag);
|
Chris@2
|
59 }
|
Chris@2
|
60
|
Chris@2
|
61 vector<PluginCandidates::FailureRec>
|
Chris@4
|
62 PluginCandidates::getFailedLibrariesFor(string tag) const
|
Chris@2
|
63 {
|
Chris@4
|
64 if (m_failures.find(tag) == m_failures.end()) return {};
|
Chris@4
|
65 else return m_failures.at(tag);
|
Chris@2
|
66 }
|
Chris@2
|
67
|
Chris@2
|
68 vector<string>
|
Chris@2
|
69 PluginCandidates::getLibrariesInPath(vector<string> path)
|
Chris@2
|
70 {
|
Chris@2
|
71 vector<string> candidates;
|
Chris@2
|
72
|
Chris@2
|
73 for (string dirname: path) {
|
Chris@2
|
74
|
Chris@2
|
75 //#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
|
Chris@2
|
76 cerr << "getLibrariesInPath: scanning directory " << dirname << endl;
|
Chris@2
|
77 //#endif
|
Chris@2
|
78
|
Chris@2
|
79 QDir dir(dirname.c_str(), PLUGIN_GLOB,
|
Chris@2
|
80 QDir::Name | QDir::IgnoreCase,
|
Chris@2
|
81 QDir::Files | QDir::Readable);
|
Chris@2
|
82
|
Chris@2
|
83 for (unsigned int i = 0; i < dir.count(); ++i) {
|
Chris@2
|
84 QString soname = dir.filePath(dir[i]);
|
Chris@2
|
85 candidates.push_back(soname.toStdString());
|
Chris@2
|
86 }
|
Chris@2
|
87 }
|
Chris@2
|
88
|
Chris@2
|
89 return candidates;
|
Chris@2
|
90 }
|
Chris@2
|
91
|
Chris@2
|
92 void
|
Chris@2
|
93 PluginCandidates::scan(string tag,
|
Chris@2
|
94 vector<string> pluginPath,
|
Chris@4
|
95 string descriptorSymbolName)
|
Chris@2
|
96 {
|
Chris@2
|
97 vector<string> libraries = getLibrariesInPath(pluginPath);
|
Chris@2
|
98 vector<string> remaining = libraries;
|
Chris@2
|
99
|
Chris@2
|
100 int runlimit = 20;
|
Chris@2
|
101 int runcount = 0;
|
Chris@2
|
102
|
Chris@2
|
103 vector<string> result;
|
Chris@2
|
104
|
Chris@2
|
105 while (result.size() < libraries.size() && runcount < runlimit) {
|
Chris@4
|
106 vector<string> output = runHelper(remaining, descriptorSymbolName);
|
Chris@2
|
107 result.insert(result.end(), output.begin(), output.end());
|
Chris@2
|
108 int shortfall = int(remaining.size()) - int(output.size());
|
Chris@2
|
109 if (shortfall > 0) {
|
Chris@2
|
110 // Helper bailed out for some reason presumably associated
|
Chris@2
|
111 // with the plugin following the last one it reported
|
Chris@4
|
112 // on. Add a failure entry for that one and continue with
|
Chris@4
|
113 // the following ones.
|
Chris@4
|
114 cerr << "shortfall = " << shortfall << " (of " << remaining.size() << ")" << endl;
|
Chris@4
|
115 result.push_back("FAILURE|" +
|
Chris@4
|
116 *(remaining.rbegin() + shortfall - 1) +
|
Chris@4
|
117 "|Plugin load check failed");
|
Chris@4
|
118 remaining = vector<string>
|
Chris@4
|
119 (remaining.rbegin(), remaining.rbegin() + shortfall - 1);
|
Chris@2
|
120 }
|
Chris@2
|
121 ++runcount;
|
Chris@2
|
122 }
|
Chris@2
|
123
|
Chris@2
|
124 recordResult(tag, result);
|
Chris@2
|
125 }
|
Chris@2
|
126
|
Chris@2
|
127 vector<string>
|
Chris@2
|
128 PluginCandidates::runHelper(vector<string> libraries, string descriptor)
|
Chris@2
|
129 {
|
Chris@2
|
130 vector<string> output;
|
Chris@4
|
131 // cerr << "running helper with following library list:" << endl;
|
Chris@4
|
132 // for (auto &lib: libraries) cerr << lib << endl;
|
Chris@2
|
133
|
Chris@2
|
134 QProcess process;
|
Chris@2
|
135 process.setReadChannel(QProcess::StandardOutput);
|
Chris@4
|
136 process.setProcessChannelMode(QProcess::ForwardedErrorChannel);
|
Chris@2
|
137 process.start(m_helper.c_str(), { descriptor.c_str() });
|
Chris@2
|
138 if (!process.waitForStarted()) {
|
Chris@2
|
139 cerr << "helper failed to start" << endl;
|
Chris@2
|
140 throw runtime_error("plugin load helper failed to start");
|
Chris@2
|
141 }
|
Chris@2
|
142 for (auto &lib: libraries) {
|
Chris@2
|
143 process.write(lib.c_str(), lib.size());
|
Chris@2
|
144 process.write("\n", 1);
|
Chris@2
|
145 }
|
Chris@2
|
146
|
Chris@2
|
147 int buflen = 4096;
|
Chris@4
|
148 bool done = false;
|
Chris@4
|
149
|
Chris@4
|
150 while (!done) {
|
Chris@2
|
151 char buf[buflen];
|
Chris@2
|
152 qint64 linelen = process.readLine(buf, buflen);
|
Chris@4
|
153 if (linelen > 0) {
|
Chris@4
|
154 output.push_back(buf);
|
Chris@4
|
155 done = (output.size() == libraries.size());
|
Chris@4
|
156 } else if (linelen < 0) {
|
Chris@4
|
157 // error case
|
Chris@4
|
158 done = true;
|
Chris@4
|
159 } else {
|
Chris@4
|
160 // no error, but no line read (could just be between
|
Chris@4
|
161 // lines, or could be eof)
|
Chris@4
|
162 done = (process.state() == QProcess::NotRunning);
|
Chris@4
|
163 if (!done) process.waitForReadyRead(100);
|
Chris@2
|
164 }
|
Chris@4
|
165 }
|
Chris@4
|
166
|
Chris@4
|
167 if (process.state() != QProcess::NotRunning) {
|
Chris@4
|
168 process.close();
|
Chris@4
|
169 process.waitForFinished();
|
Chris@4
|
170 }
|
Chris@2
|
171
|
Chris@2
|
172 return output;
|
Chris@2
|
173 }
|
Chris@2
|
174
|
Chris@2
|
175 void
|
Chris@2
|
176 PluginCandidates::recordResult(string tag, vector<string> result)
|
Chris@2
|
177 {
|
Chris@4
|
178 for (auto &r: result) {
|
Chris@4
|
179
|
Chris@4
|
180 QString s(r.c_str());
|
Chris@4
|
181 QStringList bits = s.split("|");
|
Chris@4
|
182 if (bits.size() < 2 || bits.size() > 3) {
|
Chris@4
|
183 cerr << "Invalid helper output line: \"" << r << "\"" << endl;
|
Chris@4
|
184 continue;
|
Chris@4
|
185 }
|
Chris@4
|
186
|
Chris@4
|
187 string status = bits[0].toStdString();
|
Chris@4
|
188
|
Chris@4
|
189 string library = bits[1].toStdString();
|
Chris@4
|
190 if (bits.size() == 2) library = bits[1].trimmed().toStdString();
|
Chris@4
|
191
|
Chris@4
|
192 string message = "";
|
Chris@4
|
193 if (bits.size() > 2) message = bits[2].trimmed().toStdString();
|
Chris@4
|
194
|
Chris@4
|
195 if (status == "SUCCESS") {
|
Chris@4
|
196 m_candidates[tag].push_back(library);
|
Chris@4
|
197
|
Chris@4
|
198 } else if (status == "FAILURE") {
|
Chris@4
|
199 m_failures[tag].push_back({ library, message });
|
Chris@4
|
200
|
Chris@4
|
201 } else {
|
Chris@4
|
202 cerr << "Unexpected status " << status
|
Chris@4
|
203 << " in helper output line: \"" << r << "\"" << endl;
|
Chris@4
|
204 }
|
Chris@4
|
205 }
|
Chris@2
|
206 }
|
Chris@2
|
207
|