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@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@2
|
92 QDir::Name | QDir::IgnoreCase,
|
Chris@2
|
93 QDir::Files | QDir::Readable);
|
Chris@2
|
94
|
Chris@2
|
95 for (unsigned int i = 0; i < dir.count(); ++i) {
|
Chris@2
|
96 QString soname = dir.filePath(dir[i]);
|
Chris@2
|
97 candidates.push_back(soname.toStdString());
|
Chris@2
|
98 }
|
Chris@2
|
99 }
|
Chris@2
|
100
|
Chris@2
|
101 return candidates;
|
Chris@2
|
102 }
|
Chris@2
|
103
|
Chris@2
|
104 void
|
Chris@2
|
105 PluginCandidates::scan(string tag,
|
Chris@2
|
106 vector<string> pluginPath,
|
Chris@4
|
107 string descriptorSymbolName)
|
Chris@2
|
108 {
|
Chris@2
|
109 vector<string> libraries = getLibrariesInPath(pluginPath);
|
Chris@2
|
110 vector<string> remaining = libraries;
|
Chris@2
|
111
|
Chris@2
|
112 int runlimit = 20;
|
Chris@2
|
113 int runcount = 0;
|
Chris@2
|
114
|
Chris@2
|
115 vector<string> result;
|
Chris@2
|
116
|
Chris@2
|
117 while (result.size() < libraries.size() && runcount < runlimit) {
|
Chris@4
|
118 vector<string> output = runHelper(remaining, descriptorSymbolName);
|
Chris@2
|
119 result.insert(result.end(), output.begin(), output.end());
|
Chris@2
|
120 int shortfall = int(remaining.size()) - int(output.size());
|
Chris@2
|
121 if (shortfall > 0) {
|
Chris@2
|
122 // Helper bailed out for some reason presumably associated
|
Chris@2
|
123 // with the plugin following the last one it reported
|
Chris@4
|
124 // on. Add a failure entry for that one and continue with
|
Chris@4
|
125 // the following ones.
|
Chris@6
|
126 string failed = *(remaining.rbegin() + shortfall - 1);
|
Chris@6
|
127 log("helper output ended before result for plugin " + failed + "\n");
|
Chris@6
|
128 result.push_back("FAILURE|" + failed + "|Plugin load check failed or timed out");
|
Chris@4
|
129 remaining = vector<string>
|
Chris@4
|
130 (remaining.rbegin(), remaining.rbegin() + shortfall - 1);
|
Chris@2
|
131 }
|
Chris@2
|
132 ++runcount;
|
Chris@2
|
133 }
|
Chris@2
|
134
|
Chris@2
|
135 recordResult(tag, result);
|
Chris@2
|
136 }
|
Chris@2
|
137
|
Chris@2
|
138 vector<string>
|
Chris@2
|
139 PluginCandidates::runHelper(vector<string> libraries, string descriptor)
|
Chris@2
|
140 {
|
Chris@2
|
141 vector<string> output;
|
Chris@6
|
142
|
Chris@6
|
143 log("running helper with following library list:\n");
|
Chris@6
|
144 for (auto &lib: libraries) log(lib + "\n");
|
Chris@2
|
145
|
Chris@2
|
146 QProcess process;
|
Chris@2
|
147 process.setReadChannel(QProcess::StandardOutput);
|
Chris@4
|
148 process.setProcessChannelMode(QProcess::ForwardedErrorChannel);
|
Chris@2
|
149 process.start(m_helper.c_str(), { descriptor.c_str() });
|
Chris@2
|
150 if (!process.waitForStarted()) {
|
Chris@2
|
151 cerr << "helper failed to start" << endl;
|
Chris@2
|
152 throw runtime_error("plugin load helper failed to start");
|
Chris@2
|
153 }
|
Chris@2
|
154 for (auto &lib: libraries) {
|
Chris@2
|
155 process.write(lib.c_str(), lib.size());
|
Chris@2
|
156 process.write("\n", 1);
|
Chris@2
|
157 }
|
Chris@2
|
158
|
Chris@6
|
159 QTime t;
|
Chris@6
|
160 t.start();
|
Chris@6
|
161 int timeout = 3000; // ms
|
Chris@6
|
162
|
Chris@2
|
163 int buflen = 4096;
|
Chris@4
|
164 bool done = false;
|
Chris@4
|
165
|
Chris@4
|
166 while (!done) {
|
Chris@2
|
167 char buf[buflen];
|
Chris@2
|
168 qint64 linelen = process.readLine(buf, buflen);
|
Chris@4
|
169 if (linelen > 0) {
|
Chris@4
|
170 output.push_back(buf);
|
Chris@4
|
171 done = (output.size() == libraries.size());
|
Chris@4
|
172 } else if (linelen < 0) {
|
Chris@4
|
173 // error case
|
Chris@6
|
174 log("received error code while reading from helper\n");
|
Chris@4
|
175 done = true;
|
Chris@4
|
176 } else {
|
Chris@4
|
177 // no error, but no line read (could just be between
|
Chris@4
|
178 // lines, or could be eof)
|
Chris@4
|
179 done = (process.state() == QProcess::NotRunning);
|
Chris@6
|
180 if (!done) {
|
Chris@6
|
181 if (t.elapsed() > timeout) {
|
Chris@6
|
182 // this is purely an emergency measure
|
Chris@6
|
183 log("timeout: helper took too long, killing it\n");
|
Chris@6
|
184 process.kill();
|
Chris@6
|
185 done = true;
|
Chris@6
|
186 } else {
|
Chris@6
|
187 process.waitForReadyRead(200);
|
Chris@6
|
188 }
|
Chris@6
|
189 }
|
Chris@2
|
190 }
|
Chris@4
|
191 }
|
Chris@4
|
192
|
Chris@4
|
193 if (process.state() != QProcess::NotRunning) {
|
Chris@4
|
194 process.close();
|
Chris@4
|
195 process.waitForFinished();
|
Chris@4
|
196 }
|
Chris@2
|
197
|
Chris@2
|
198 return output;
|
Chris@2
|
199 }
|
Chris@2
|
200
|
Chris@2
|
201 void
|
Chris@2
|
202 PluginCandidates::recordResult(string tag, vector<string> result)
|
Chris@2
|
203 {
|
Chris@4
|
204 for (auto &r: result) {
|
Chris@4
|
205
|
Chris@4
|
206 QString s(r.c_str());
|
Chris@4
|
207 QStringList bits = s.split("|");
|
Chris@6
|
208
|
Chris@6
|
209 log("read output line from helper: " + r);
|
Chris@6
|
210
|
Chris@4
|
211 if (bits.size() < 2 || bits.size() > 3) {
|
Chris@6
|
212 log("invalid output line (wrong number of |-separated fields)\n");
|
Chris@4
|
213 continue;
|
Chris@4
|
214 }
|
Chris@4
|
215
|
Chris@4
|
216 string status = bits[0].toStdString();
|
Chris@4
|
217
|
Chris@4
|
218 string library = bits[1].toStdString();
|
Chris@4
|
219 if (bits.size() == 2) library = bits[1].trimmed().toStdString();
|
Chris@4
|
220
|
Chris@4
|
221 string message = "";
|
Chris@4
|
222 if (bits.size() > 2) message = bits[2].trimmed().toStdString();
|
Chris@4
|
223
|
Chris@4
|
224 if (status == "SUCCESS") {
|
Chris@4
|
225 m_candidates[tag].push_back(library);
|
Chris@4
|
226
|
Chris@4
|
227 } else if (status == "FAILURE") {
|
Chris@4
|
228 m_failures[tag].push_back({ library, message });
|
Chris@4
|
229
|
Chris@4
|
230 } else {
|
Chris@6
|
231 log("unexpected status \"" + status + "\" in output line\n");
|
Chris@4
|
232 }
|
Chris@4
|
233 }
|
Chris@2
|
234 }
|
Chris@2
|
235
|