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