blob: e247db76bf14ab98bca9cb5b7ccee622ee8e22df [file] [log] [blame]
James Feist3cb5fec2018-01-23 14:41:51 -08001/*
2// Copyright (c) 2018 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
16
17#include <Utils.hpp>
18#include <dbus/properties.hpp>
19#include <nlohmann/json.hpp>
20#include <fstream>
21#include <regex>
22#include <boost/algorithm/string/predicate.hpp>
23#include <boost/algorithm/string/replace.hpp>
24#include <boost/variant/apply_visitor.hpp>
25#include <boost/lexical_cast.hpp>
26#include <boost/container/flat_map.hpp>
27#include <boost/container/flat_set.hpp>
28#include <VariantVisitors.hpp>
James Feist7b7e4e82018-01-24 14:56:00 -080029#include <experimental/filesystem>
James Feist3cb5fec2018-01-23 14:41:51 -080030
31constexpr const char *OUTPUT_DIR = "/var/configuration/";
32constexpr const char *CONFIGURATION_DIR = "/usr/share/configurations";
33constexpr const char *TEMPLATE_CHAR = "$";
34constexpr const size_t MAX_MAPPER_DEPTH = 99;
35
36namespace fs = std::experimental::filesystem;
37struct cmp_str
38{
39 bool operator()(const char *a, const char *b) const
40 {
41 return std::strcmp(a, b) < 0;
42 }
43};
44
45// underscore T for collison with dbus c api
46enum class probe_type_codes
47{
48 FALSE_T,
49 TRUE_T,
50 AND,
51 OR,
52 FOUND
53};
54const static boost::container::flat_map<const char *, probe_type_codes, cmp_str>
55 PROBE_TYPES{{{"FALSE", probe_type_codes::FALSE_T},
56 {"TRUE", probe_type_codes::TRUE_T},
57 {"AND", probe_type_codes::AND},
58 {"OR", probe_type_codes::OR},
59 {"FOUND", probe_type_codes::FOUND}}};
60
61using GetSubTreeType = std::vector<
62 std::pair<std::string,
63 std::vector<std::pair<std::string, std::vector<std::string>>>>>;
64
65using ManagedObjectType = boost::container::flat_map<
66 dbus::object_path,
67 boost::container::flat_map<
68 std::string,
69 boost::container::flat_map<std::string, dbus::dbus_variant>>>;
70
71boost::container::flat_map<
72 std::string,
73 std::vector<boost::container::flat_map<std::string, dbus::dbus_variant>>>
74 DBUS_PROBE_OBJECTS;
75std::vector<std::string> PASSED_PROBES;
76
77// todo: pass this through nicer
78std::shared_ptr<dbus::connection> SYSTEM_BUS;
79
80// calls the mapper to find all exposed objects of an interface type
81// and creates a vector<flat_map> that contains all the key value pairs
82// getManagedObjects
83bool findDbusObjects(
84 std::shared_ptr<dbus::connection> connection,
85 std::vector<boost::container::flat_map<std::string, dbus::dbus_variant>>
86 &interfaceDevices,
87 std::string interface)
88{
89 // find all connections in the mapper that expose a specific type
90 static const dbus::endpoint mapper("xyz.openbmc_project.ObjectMapper",
91 "/xyz/openbmc_project/object_mapper",
92 "xyz.openbmc_project.ObjectMapper",
93 "GetSubTree");
94 dbus::message getMap = dbus::message::new_call(mapper);
95 std::vector<std::string> objects = {interface};
96 if (!getMap.pack("", MAX_MAPPER_DEPTH, objects))
97 {
98 std::cerr << "Pack Failed GetSensorSubtree\n";
99 return false;
100 }
101 dbus::message getMapResp = connection->send(getMap);
102 GetSubTreeType interfaceSubtree;
103 if (!getMapResp.unpack(interfaceSubtree))
104 {
105 std::cerr << "Error communicating to mapper\n";
106 return false;
107 }
108 boost::container::flat_set<std::string> connections;
109 for (auto &object : interfaceSubtree)
110 {
111 for (auto &connPair : object.second)
112 {
113 connections.insert(connPair.first);
114 }
115 }
116 // iterate through the connections, adding creating individual device
117 // dictionaries
118 for (auto &conn : connections)
119 {
120 auto managedObj =
121 dbus::endpoint(conn, "/", "org.freedesktop.DBus.ObjectManager",
122 "GetManagedObjects");
123 dbus::message getManagedObj = dbus::message::new_call(managedObj);
124 dbus::message getManagedObjResp = connection->send(getManagedObj);
125 ManagedObjectType managedInterface;
126 if (!getManagedObjResp.unpack(managedInterface))
127 {
128 std::cerr << "error getting managed object for device " << conn
129 << "\n";
130 continue;
131 }
132 for (auto &interfaceManagedObj : managedInterface)
133 {
134 auto ifaceObjFind = interfaceManagedObj.second.find(interface);
135 if (ifaceObjFind != interfaceManagedObj.second.end())
136 {
137 interfaceDevices.emplace_back(ifaceObjFind->second);
138 }
139 }
140 }
141 return true;
142}
143
144// probes interface dictionary for a key with a value that matches a regex
145bool probeDbus(
146 const std::string &interface,
147 const std::map<std::string, nlohmann::json> &matches,
148 std::vector<boost::container::flat_map<std::string, dbus::dbus_variant>>
149 &devices,
150 bool &foundProbe)
151{
152 auto &dbusObject = DBUS_PROBE_OBJECTS[interface];
153 if (dbusObject.empty())
154 {
155 if (!findDbusObjects(SYSTEM_BUS, dbusObject, interface))
156 {
157 std::cerr << "Found no dbus objects with interface "
158 << interface << "\n";
159 foundProbe = false;
160 return false;
161 }
162 }
163 foundProbe = true;
164
165 bool foundMatch = false;
166 for (auto &device : dbusObject)
167 {
168 bool deviceMatches = true;
169 for (auto &match : matches)
170 {
171 auto deviceValue = device.find(match.first);
172 if (deviceValue != device.end())
173 {
174 switch (match.second.type())
175 {
176 case nlohmann::json::value_t::string:
177 {
178 std::regex search(match.second.get<std::string>());
179 std::smatch match;
180
181 // convert value to string respresentation
182 std::string probeValue = boost::apply_visitor(
183 [](const auto &x) {
184 return boost::lexical_cast<std::string>(x);
185 },
186 deviceValue->second);
187 if (!std::regex_search(probeValue, match, search))
188 {
189 deviceMatches = false;
190 break;
191 }
192 break;
193 }
194 case nlohmann::json::value_t::boolean:
195 case nlohmann::json::value_t::number_unsigned:
196 {
197 unsigned int probeValue = boost::apply_visitor(
198 VariantToUnsignedIntVisitor(), deviceValue->second);
199
200 if (probeValue != match.second.get<unsigned int>())
201 {
202 deviceMatches = false;
203 }
204 break;
205 }
206 case nlohmann::json::value_t::number_integer:
207 {
208 int probeValue = boost::apply_visitor(VariantToIntVisitor(),
209 deviceValue->second);
210
211 if (probeValue != match.second.get<int>())
212 {
213 deviceMatches = false;
214 }
215 break;
216 }
217 case nlohmann::json::value_t::number_float:
218 {
219 float probeValue = boost::apply_visitor(
220 VariantToFloatVisitor(), deviceValue->second);
221
222 if (probeValue != match.second.get<float>())
223 {
224 deviceMatches = false;
225 }
226 break;
227 }
228 }
229 }
230 else
231 {
232 deviceMatches = false;
233 break;
234 }
235 }
236 if (deviceMatches)
237 {
238 devices.emplace_back(
239 boost::container::flat_map<std::string, dbus::dbus_variant>(
240 device));
241 foundMatch = true;
242 deviceMatches = false; // for next iteration
243 }
244 }
245 return foundMatch;
246}
247
248// default probe entry point, iterates a list looking for specific types to
249// call specific probe functions
250bool probe(
251 const std::vector<std::string> probeCommand,
252 std::vector<boost::container::flat_map<std::string, dbus::dbus_variant>>
253 &foundDevs)
254{
255 const static std::regex command(R"(\((.*)\))");
256 std::smatch match;
257 bool ret = false;
258 bool cur = true;
259 probe_type_codes lastCommand = probe_type_codes::FALSE_T;
260
261 for (auto &probe : probeCommand)
262 {
263 bool foundProbe = false;
264 boost::container::flat_map<const char *, probe_type_codes,
265 cmp_str>::const_iterator probeType;
266
267 for (probeType = PROBE_TYPES.begin(); probeType != PROBE_TYPES.end();
268 probeType++)
269 {
270 if (probe.find(probeType->first) != std::string::npos)
271 {
272 foundProbe = true;
273 break;
274 }
275 }
276 if (foundProbe)
277 {
278 switch (probeType->second)
279 {
280 case probe_type_codes::FALSE_T:
281 {
282 return false; // todo, actually evaluate?
283 break;
284 }
285 case probe_type_codes::TRUE_T:
286 {
287 return true; // todo, actually evaluate?
288 break;
289 }
290 /*case probe_type_codes::AND:
291 break;
292 case probe_type_codes::OR:
293 break;
294 // these are no-ops until the last command switch
295 */
296 case probe_type_codes::FOUND:
297 {
298 if (!std::regex_search(probe, match, command))
299 {
300 std::cerr << "found probe sytax error " << probe << "\n";
301 return false;
302 }
303 std::string commandStr = *(match.begin() + 1);
James Feist3f8a2782018-02-12 09:24:42 -0800304 boost::replace_all(commandStr, "'", "");
James Feist3cb5fec2018-01-23 14:41:51 -0800305 cur = (std::find(PASSED_PROBES.begin(), PASSED_PROBES.end(),
306 commandStr) != PASSED_PROBES.end());
307 break;
308 }
309 }
310 }
311 // look on dbus for object
312 else
313 {
314 if (!std::regex_search(probe, match, command))
315 {
316 std::cerr << "dbus probe sytax error " << probe << "\n";
317 return false;
318 }
319 std::string commandStr = *(match.begin() + 1);
320 // convert single ticks and single slashes into legal json
James Feist3f8a2782018-02-12 09:24:42 -0800321 boost::replace_all(commandStr, "'", R"(")");
322 boost::replace_all(commandStr, R"(\)", R"(\\)");
James Feist3cb5fec2018-01-23 14:41:51 -0800323 auto json = nlohmann::json::parse(commandStr, nullptr, false);
324 if (json.is_discarded())
325 {
326 std::cerr << "dbus command sytax error " << commandStr << "\n";
327 return false;
328 }
329 // we can match any (string, variant) property. (string, string)
330 // does a regex
331 std::map<std::string, nlohmann::json> dbusProbeMap =
332 json.get<std::map<std::string, nlohmann::json>>();
333 auto findStart = probe.find("(");
334 if (findStart == std::string::npos)
335 {
336 return false;
337 }
338 std::string probeInterface = probe.substr(0, findStart);
339 cur =
340 probeDbus(probeInterface, dbusProbeMap, foundDevs, foundProbe);
341 }
342
343 // some functions like AND and OR only take affect after the
344 // fact
345 switch (lastCommand)
346 {
347 case probe_type_codes::AND:
348 ret = cur && ret;
349 break;
350 case probe_type_codes::OR:
351 ret = cur || ret;
352 break;
353 default:
354 ret = cur;
355 break;
356 }
357 lastCommand = probeType != PROBE_TYPES.end()
358 ? probeType->second
359 : probe_type_codes::FALSE_T;
360
361 if (!foundProbe)
362 {
363 std::cerr << "Illegal probe type " << probe << "\n";
364 return false;
365 }
366 }
367
368 // probe passed, but empty device
369 // todo: should this be done in main?
370 if (ret && foundDevs.size() == 0)
371 {
372 foundDevs.emplace_back(
373 boost::container::flat_map<std::string, dbus::dbus_variant>());
374 }
375 return ret;
376}
377
378int main(int argc, char **argv)
379{
380 // find configuration files
381 std::vector<fs::path> jsonPaths;
382 if (!find_files(fs::path(CONFIGURATION_DIR), R"(.*\.json)", jsonPaths, 0))
383 {
384 std::cerr << "Unable to find any configuration files in "
385 << CONFIGURATION_DIR << "\n";
386 return 1;
387 }
388 // setup connection to dbus
389 boost::asio::io_service io;
390 SYSTEM_BUS = std::make_shared<dbus::connection>(io, dbus::bus::system);
391 dbus::DbusObjectServer objServer(SYSTEM_BUS);
392 SYSTEM_BUS->request_name("xyz.openbmc_project.EntityManager");
393
394 std::vector<nlohmann::json> configurations;
395 for (auto &jsonPath : jsonPaths)
396 {
397 std::ifstream jsonStream(jsonPath.c_str());
398 if (!jsonStream.good())
399 {
400 std::cerr << "unable to open " << jsonPath.string() << "\n";
401 continue;
402 }
403 auto data = nlohmann::json::parse(jsonStream, nullptr, false);
404 if (data.is_discarded())
405 {
406 std::cerr << "syntax error in " << jsonPath.string() << "\n";
407 continue;
408 }
409 if (data.type() == nlohmann::json::value_t::array)
410 {
411 for (auto &d : data)
412 {
413 configurations.emplace_back(d);
414 }
415 }
416 else
417 {
418 configurations.emplace_back(data);
419 }
420 }
421
422 // keep looping as long as at least 1 new probe passed, removing
423 // configurations from the memory store once the probe passes
424 bool probePassed = true;
James Feist7b7e4e82018-01-24 14:56:00 -0800425 nlohmann::json systemConfiguration = nlohmann::json::object();
James Feist3cb5fec2018-01-23 14:41:51 -0800426 while (probePassed)
427 {
428 probePassed = false;
429 for (auto it = configurations.begin(); it != configurations.end();)
430 {
431 bool eraseConfig = false;
432 auto findProbe = (*it).find("probe");
433 auto findName = (*it).find("name");
434
435 // check for poorly formatted fields
436 if (findProbe == (*it).end())
437 {
438 std::cerr << "configuration file missing probe:\n " << *it
439 << "\n";
440 eraseConfig = true;
441 }
442 if (findName == (*it).end())
443 {
444 std::cerr << "configuration file missing name:\n " << *it
445 << "\n";
446 eraseConfig = true;
447 }
448
449 nlohmann::json probeCommand;
450 if ((*findProbe).type() != nlohmann::json::value_t::array)
451 {
452 probeCommand = nlohmann::json::array();
453 probeCommand.push_back(*findProbe);
454 }
455 else
456 {
457 probeCommand = *findProbe;
458 }
459 std::vector<
460 boost::container::flat_map<std::string, dbus::dbus_variant>>
461 foundDevices;
462 if (probe(probeCommand, foundDevices))
463 {
464 eraseConfig = true;
465 probePassed = true;
James Feist7b7e4e82018-01-24 14:56:00 -0800466 std::string name = *findName;
467 PASSED_PROBES.push_back(name);
James Feist3cb5fec2018-01-23 14:41:51 -0800468
469 size_t foundDeviceIdx = 0;
470
471 for (auto &foundDevice : foundDevices)
472 {
473 auto findExpose = (*it).find("exposes");
474 if (findExpose == (*it).end())
475 {
476 std::cerr
477 << "Warning, configuration file missing exposes"
478 << *it << "\n";
479 continue;
480 }
481 for (auto &expose : *findExpose)
482 {
483 for (auto keyPair = expose.begin();
484 keyPair != expose.end(); keyPair++)
485 {
486 // fill in template characters with devices
487 // found
488 if (keyPair.value().type() ==
489 nlohmann::json::value_t::string)
490 {
491 std::string value = keyPair.value();
492 if (value.find(TEMPLATE_CHAR) !=
493 std::string::npos)
494 {
495 std::string templateValue = value;
496
497 templateValue.erase(
498 0, 1); // remove template character
499
500 // special case index
501 if ("index" == templateValue)
502 {
503 keyPair.value() = foundDeviceIdx;
504 }
505 else
506 {
507 std::string subsitute;
508 for (auto &foundDevicePair :
509 foundDevice)
510 {
511 if (boost::iequals(
512 foundDevicePair.first,
513 templateValue))
514 {
515 // convert value to string
516 // respresentation
517 subsitute =
518 boost::apply_visitor(
519 [](const auto &x) {
520 return boost::
521 lexical_cast<
522 std::
523 string>(
524 x);
525 },
526 foundDevicePair.second);
527 break;
528 }
529 }
530 if (!subsitute.size())
531 {
532 std::cerr << "could not find "
533 << templateValue
534 << " in device "
535 << expose["name"] << "\n";
536 }
537 else
538 {
539 keyPair.value() = subsitute;
540 }
541 }
542 }
543
544 // special case bind
545 if (boost::starts_with(keyPair.key(), "bind_"))
546 {
547 bool foundBind = false;
548 std::string bind = keyPair.key().substr(
549 sizeof("bind_") - 1);
James Feist7b7e4e82018-01-24 14:56:00 -0800550 for (auto &configurationPair :
551 nlohmann::json::iterator_wrapper(
552 systemConfiguration))
James Feist3cb5fec2018-01-23 14:41:51 -0800553 {
554 auto &configList =
James Feist7b7e4e82018-01-24 14:56:00 -0800555 configurationPair
556 .value()["exposes"];
James Feist3cb5fec2018-01-23 14:41:51 -0800557 for (auto exposedObjectIt =
558 configList.begin();
559 exposedObjectIt !=
560 configList.end();)
561 {
562 std::string foundObjectName =
563 (*exposedObjectIt)["name"];
564 if (boost::iequals(foundObjectName,
565 value))
566 {
567 (*exposedObjectIt)["status"] =
568 "okay"; // todo: is this the
569 // right spot?
570 expose[bind] =
571 (*exposedObjectIt);
572 foundBind = true;
573 exposedObjectIt =
574 configList.erase(
575 exposedObjectIt);
576 break;
577 }
578 else
579 {
580 exposedObjectIt++;
581 }
582 }
583 if (foundBind)
584 {
585 break;
586 }
587 }
588 if (!foundBind)
589 {
590 std::cerr << "configuration file "
591 "dependency error, "
592 "could not find bind "
593 << value << "\n";
594 return 1;
595 }
596 }
597 }
598 }
599 }
James Feist7b7e4e82018-01-24 14:56:00 -0800600 systemConfiguration[name] = (*it);
James Feist3cb5fec2018-01-23 14:41:51 -0800601 foundDeviceIdx++;
602 }
603 }
604
605 if (eraseConfig)
606 {
607 it = configurations.erase(it);
608 }
609 else
610 {
611 it++;
612 }
613 }
614 }
615 // below here is temporary, to be added to dbus
James Feist7b7e4e82018-01-24 14:56:00 -0800616 std::experimental::filesystem::create_directory(OUTPUT_DIR);
James Feist3cb5fec2018-01-23 14:41:51 -0800617 std::ofstream output(std::string(OUTPUT_DIR) + "system.json");
618 output << systemConfiguration.dump(4);
619 output.close();
620
621 auto flat = nlohmann::json::array();
622 for (auto &pair : nlohmann::json::iterator_wrapper(systemConfiguration))
623 {
624 auto value = pair.value();
625 auto exposes = value.find("exposes");
626 if (exposes != value.end())
627 {
628 for (auto &item : *exposes)
629 {
630 flat.push_back(item);
631 }
632 }
633 }
634 output = std::ofstream(std::string(OUTPUT_DIR) + "flattened.json");
635 output << flat.dump(4);
636 output.close();
637
638 return 0;
639}