1 /*
2  * Copyright (c) 2015, Intel Corporation
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without modification,
6  * are permitted provided that the following conditions are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice, this
9  * list of conditions and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright notice,
12  * this list of conditions and the following disclaimer in the documentation and/or
13  * other materials provided with the distribution.
14  *
15  * 3. Neither the name of the copyright holder nor the names of its contributors
16  * may be used to endorse or promote products derived from this software without
17  * specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
23  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #include <ParameterMgrFullConnector.h>
32 #include <Tokenizer.h>
33 #include <Utility.h>
34 
35 #include <iostream>
36 #include <sstream>
37 #include <memory>
38 #include <string>
39 #include <limits>
40 #include <numeric>
41 #include <algorithm>
42 #include <stdexcept>
43 
44 using std::string;
45 
46 class MyLogger final : public CParameterMgrFullConnector::ILogger
47 {
48 public:
info(const std::string & log)49     void info(const std::string &log) override { std::cerr << "Info: " << log << std::endl; }
50 
warning(const std::string & log)51     void warning(const std::string &log) override { std::cerr << "Warning: " << log << std::endl; }
52 };
53 
54 class XmlGenerator
55 {
56 public:
57     using Exception = std::runtime_error;
58 
XmlGenerator(const string & toplevelConfig,bool validate,bool verbose,string schemasDir)59     XmlGenerator(const string &toplevelConfig, bool validate, bool verbose, string schemasDir)
60         : mConnector(toplevelConfig), mCommandHandler(mConnector.createCommandHandler())
61     {
62         if (verbose) {
63             mLogger.reset(new MyLogger);
64             mConnector.setLogger(mLogger.get());
65         }
66 
67         mConnector.setSchemaUri(schemasDir);
68         mConnector.setValidateSchemasOnStart(validate);
69 
70         // Disable irrelevant failure conditions
71         mConnector.setFailureOnMissingSubsystem(false);
72         mConnector.setFailureOnFailedSettingsLoad(false);
73 
74         // Disable the remote interface because we don't need it and it might
75         // get in the way (e.g. the port is already in use)
76         mConnector.setForceNoRemoteInterface(true);
77     }
78 
79     /** Reads each line of the input stream and takes an action accordingly
80      *
81      * Returns when the input stream reaches end of file
82      *
83      * The commands are the usual PF tunning commands and some additional specials.
84      * Special commands:
85      *  - `createSelectionCriterion inclusive|exclusive <name> <value> [value, ...]`
86      *    Create a criterion with the given properties.
87      *  - `start` start the Parameter Framework. All criteria must have been created.
88      *
89      * @param[in] input The input stream to read from
90      *
91      * @return the number of error that occurred
92      */
93     size_t parse(std::istream &input);
94 
95     /** Check for elements belonging to several domains
96      *
97      * Prints conflicting elements, if any, on the error output.
98      *
99      * @returns true if there are conflicting elements, false otherwise
100      */
101     bool conflictingElements();
102 
103     /** Prints the Parameter Framework's instance configuration
104      *
105      * @param[out] output The stream to which output the configuration
106      */
107     void exportDomains(std::ostream &output);
108 
109 private:
110     void addCriteria(std::vector<string> &tokens);
111     void start();
112 
113     CParameterMgrFullConnector mConnector;
114     std::unique_ptr<MyLogger> mLogger;
115     std::unique_ptr<CommandHandlerInterface> mCommandHandler;
116 };
117 
addCriteria(std::vector<string> & tokens)118 void XmlGenerator::addCriteria(std::vector<string> &tokens)
119 {
120     if (tokens.size() < 3) {
121         throw Exception("Not enough arguments to criterion creation request");
122     }
123 
124     auto inclusiveness = tokens.front() == "inclusive";
125     tokens.erase(begin(tokens));
126 
127     auto name = tokens.front();
128     tokens.erase(begin(tokens));
129 
130     auto criterionType = mConnector.createSelectionCriterionType(inclusiveness);
131     if (criterionType == nullptr) {
132         throw Exception("Failed to create an " + string(inclusiveness ? "inclusive" : "exclusive") +
133                         " criterion type");
134     }
135 
136     int index = 0;
137     for (const auto &literalValue : tokens) {
138         // inclusive criteria are bitfields
139         int numericalValue = inclusiveness ? 1 << index : index;
140         string error;
141         bool success = criterionType->addValuePair(numericalValue, literalValue, error);
142 
143         if (not success) {
144             std::ostringstream message;
145             message << "Valuepair (" << numericalValue << ", '" << literalValue
146                     << "') rejected for " << name << ": " << error;
147             throw Exception(message.str());
148         }
149         index++;
150     }
151 
152     // We don't need to keep a reference to the criterion - no need to store
153     // the returned pointer.
154     if (mConnector.createSelectionCriterion(name, criterionType) == nullptr) {
155         throw Exception("Failed to create criterion '" + name + "'");
156     }
157 }
158 
parse(std::istream & input)159 size_t XmlGenerator::parse(std::istream &input)
160 {
161     string line;
162     size_t errorNb = 0;
163     while (not input.eof()) {
164         std::getline(std::cin, line);
165 
166         auto tokens = Tokenizer(line, string(1, '\0'), false).split();
167         if (tokens.empty()) {
168             continue;
169         }
170         auto command = tokens.front();
171         tokens.erase(begin(tokens)); // drop the command name
172 
173         if (command == "createSelectionCriterion") {
174             addCriteria(tokens);
175         } else if (command == "start") {
176             start();
177         } else {
178             string output;
179             if (not mCommandHandler->process(command, tokens, output)) {
180                 errorNb++;
181 
182                 std::cerr << accumulate(begin(tokens), end(tokens),
183                                         "Failed to executing command: `" + command + "'",
184                                         [](string l, string r) { return l + " `" + r + "'"; })
185                           << std::endl
186                           << output << std::endl;
187             }
188         }
189     }
190     return errorNb;
191 }
192 
conflictingElements()193 bool XmlGenerator::conflictingElements()
194 {
195     string conflicting;
196     if (not mCommandHandler->process("listConflictingElements", {}, conflicting)) {
197         // Should not happen
198         throw Exception("Failed to list conflicting elements");
199     }
200 
201     if (not conflicting.empty()) {
202         std::cerr << "There are conflicting elements:" << std::endl << conflicting;
203         return true;
204     }
205 
206     return false;
207 }
208 
start()209 void XmlGenerator::start()
210 {
211     string error;
212     if (not mConnector.start(error)) {
213         throw Exception("Start failed: " + error);
214     }
215 
216     error.clear();
217     // Switch to tunning mode as the tunning commands
218     // are the only commands possible with this connector.
219     if (not mConnector.setTuningMode(true, error)) {
220         throw Exception("Failed to turn tuning mode on: " + error);
221     }
222 }
223 
exportDomains(std::ostream & output)224 void XmlGenerator::exportDomains(std::ostream &output)
225 {
226     string error;
227     string domains;
228     if (not mConnector.exportDomainsXml(domains, true, false, error)) {
229         throw Exception("Export failed: " + error);
230     } else {
231         output << domains;
232     }
233 }
234 
235 static const char *usage =
236     R"(Usage: domainGeneratorConnector <top-level config> <verbose> <validate> <path>
237 
238  <verbose>       'verbose': verbose, else: terse
239  <validate>      'validate': validate, else: don't validate
240  <path>          path to the schemas' directory
241 
242 All arguments are mandatory. If no validation is required,
243 the path to the schemas can be an empty string.
244 
245 Exit with the number of (recoverable or not error) that occured.
246 
247 This program is not intended to be used standalone but rather called through
248 domainGenerator.py)";
249 
250 /** On linux at least, a program can not exit with a value greater than 255.
251  * @return min(code, 255);
252  */
253 template <class T>
normalizeExitCode(T code)254 static inline int normalizeExitCode(T code)
255 {
256     return int(std::min<T>(code, std::numeric_limits<uint8_t>::max()));
257 }
258 
main(int argc,char * argv[])259 int main(int argc, char *argv[])
260 {
261     using std::endl;
262 
263     if (argc <= 4) {
264         std::cerr << usage << std::endl;
265         return 1;
266     }
267 
268     string toplevelConfig = argv[1];
269     bool verbose = string(argv[2]) == "verbose";
270     bool validate = string(argv[3]) == "validate";
271     string schemasDir = argv[4];
272 
273     if (verbose) {
274         std::cerr << "Domain generator config:" << endl
275                   << "    toplevelConfig=" << toplevelConfig << endl
276                   << "    verbose=" << verbose << endl
277                   << "    validate=" << validate << endl
278                   << "    schemasDir=" << schemasDir << endl;
279     }
280 
281     try {
282         XmlGenerator xmlGenerator(toplevelConfig, validate, verbose, schemasDir);
283         auto errorNb = xmlGenerator.parse(std::cin);
284         if (xmlGenerator.conflictingElements()) {
285             errorNb++;
286         }
287         xmlGenerator.exportDomains(std::cout);
288 
289         return normalizeExitCode(errorNb);
290     } catch (std::exception &e) {
291         std::cerr << e.what() << std::endl;
292         return 1;
293     }
294 }
295