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