blob: 7b91159d490ae904372e86cb29af7b3f3235d61f [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>
James Feistc95cb142018-02-26 10:41:42 -080018#include <Overlay.hpp>
James Feist3cb5fec2018-01-23 14:41:51 -080019#include <dbus/properties.hpp>
20#include <nlohmann/json.hpp>
21#include <fstream>
James Feist75fdeeb2018-02-20 14:26:16 -080022#include <future>
James Feist3cb5fec2018-01-23 14:41:51 -080023#include <regex>
24#include <boost/algorithm/string/predicate.hpp>
25#include <boost/algorithm/string/replace.hpp>
26#include <boost/variant/apply_visitor.hpp>
27#include <boost/lexical_cast.hpp>
28#include <boost/container/flat_map.hpp>
29#include <boost/container/flat_set.hpp>
James Feist1b2e2242018-01-30 13:45:19 -080030#include <dbus/connection.hpp>
James Feist3cb5fec2018-01-23 14:41:51 -080031#include <VariantVisitors.hpp>
James Feist7b7e4e82018-01-24 14:56:00 -080032#include <experimental/filesystem>
James Feist3cb5fec2018-01-23 14:41:51 -080033
34constexpr const char *OUTPUT_DIR = "/var/configuration/";
35constexpr const char *CONFIGURATION_DIR = "/usr/share/configurations";
36constexpr const char *TEMPLATE_CHAR = "$";
James Feistc95cb142018-02-26 10:41:42 -080037constexpr const size_t PROPERTIES_CHANGED_UNTIL_FLUSH_COUNT = 20;
James Feist3cb5fec2018-01-23 14:41:51 -080038constexpr const size_t MAX_MAPPER_DEPTH = 99;
James Feist4131aea2018-03-09 09:47:30 -080039constexpr const size_t SLEEP_AFTER_PROPERTIES_CHANGE_SECONDS = 5;
James Feist3cb5fec2018-01-23 14:41:51 -080040
41namespace fs = std::experimental::filesystem;
42struct cmp_str
43{
44 bool operator()(const char *a, const char *b) const
45 {
46 return std::strcmp(a, b) < 0;
47 }
48};
49
50// underscore T for collison with dbus c api
51enum class probe_type_codes
52{
53 FALSE_T,
54 TRUE_T,
55 AND,
56 OR,
James Feist6bd2a022018-03-13 12:30:58 -070057 FOUND,
58 MATCH_ONE
James Feist3cb5fec2018-01-23 14:41:51 -080059};
60const static boost::container::flat_map<const char *, probe_type_codes, cmp_str>
61 PROBE_TYPES{{{"FALSE", probe_type_codes::FALSE_T},
62 {"TRUE", probe_type_codes::TRUE_T},
63 {"AND", probe_type_codes::AND},
64 {"OR", probe_type_codes::OR},
James Feist6bd2a022018-03-13 12:30:58 -070065 {"FOUND", probe_type_codes::FOUND},
66 {"MATCH_ONE", probe_type_codes::MATCH_ONE}}};
James Feist3cb5fec2018-01-23 14:41:51 -080067
68using GetSubTreeType = std::vector<
69 std::pair<std::string,
70 std::vector<std::pair<std::string, std::vector<std::string>>>>>;
71
72using ManagedObjectType = boost::container::flat_map<
73 dbus::object_path,
74 boost::container::flat_map<
75 std::string,
76 boost::container::flat_map<std::string, dbus::dbus_variant>>>;
77
78boost::container::flat_map<
79 std::string,
80 std::vector<boost::container::flat_map<std::string, dbus::dbus_variant>>>
81 DBUS_PROBE_OBJECTS;
82std::vector<std::string> PASSED_PROBES;
83
84// todo: pass this through nicer
85std::shared_ptr<dbus::connection> SYSTEM_BUS;
86
James Feist1b2e2242018-01-30 13:45:19 -080087std::regex ILLEGAL_DBUS_REGEX("[^A-Za-z0-9_]");
88
James Feist75fdeeb2018-02-20 14:26:16 -080089void registerCallbacks(
90 std::vector<std::pair<std::unique_ptr<dbus::match>,
91 std::shared_ptr<dbus::filter>>> &dbusMatches,
James Feist4131aea2018-03-09 09:47:30 -080092 nlohmann::json &systemConfiguration, dbus::DbusObjectServer &objServer);
James Feist75fdeeb2018-02-20 14:26:16 -080093
James Feist3cb5fec2018-01-23 14:41:51 -080094// calls the mapper to find all exposed objects of an interface type
95// and creates a vector<flat_map> that contains all the key value pairs
96// getManagedObjects
97bool findDbusObjects(
98 std::shared_ptr<dbus::connection> connection,
99 std::vector<boost::container::flat_map<std::string, dbus::dbus_variant>>
100 &interfaceDevices,
101 std::string interface)
102{
103 // find all connections in the mapper that expose a specific type
104 static const dbus::endpoint mapper("xyz.openbmc_project.ObjectMapper",
105 "/xyz/openbmc_project/object_mapper",
106 "xyz.openbmc_project.ObjectMapper",
107 "GetSubTree");
108 dbus::message getMap = dbus::message::new_call(mapper);
109 std::vector<std::string> objects = {interface};
110 if (!getMap.pack("", MAX_MAPPER_DEPTH, objects))
111 {
112 std::cerr << "Pack Failed GetSensorSubtree\n";
113 return false;
114 }
115 dbus::message getMapResp = connection->send(getMap);
116 GetSubTreeType interfaceSubtree;
117 if (!getMapResp.unpack(interfaceSubtree))
118 {
119 std::cerr << "Error communicating to mapper\n";
120 return false;
121 }
122 boost::container::flat_set<std::string> connections;
123 for (auto &object : interfaceSubtree)
124 {
125 for (auto &connPair : object.second)
126 {
127 connections.insert(connPair.first);
128 }
129 }
130 // iterate through the connections, adding creating individual device
131 // dictionaries
132 for (auto &conn : connections)
133 {
134 auto managedObj =
135 dbus::endpoint(conn, "/", "org.freedesktop.DBus.ObjectManager",
136 "GetManagedObjects");
137 dbus::message getManagedObj = dbus::message::new_call(managedObj);
138 dbus::message getManagedObjResp = connection->send(getManagedObj);
139 ManagedObjectType managedInterface;
140 if (!getManagedObjResp.unpack(managedInterface))
141 {
142 std::cerr << "error getting managed object for device " << conn
143 << "\n";
144 continue;
145 }
146 for (auto &interfaceManagedObj : managedInterface)
147 {
148 auto ifaceObjFind = interfaceManagedObj.second.find(interface);
149 if (ifaceObjFind != interfaceManagedObj.second.end())
150 {
151 interfaceDevices.emplace_back(ifaceObjFind->second);
152 }
153 }
154 }
155 return true;
156}
157
158// probes interface dictionary for a key with a value that matches a regex
159bool probeDbus(
160 const std::string &interface,
161 const std::map<std::string, nlohmann::json> &matches,
162 std::vector<boost::container::flat_map<std::string, dbus::dbus_variant>>
163 &devices,
164 bool &foundProbe)
165{
166 auto &dbusObject = DBUS_PROBE_OBJECTS[interface];
167 if (dbusObject.empty())
168 {
169 if (!findDbusObjects(SYSTEM_BUS, dbusObject, interface))
170 {
171 std::cerr << "Found no dbus objects with interface "
172 << interface << "\n";
173 foundProbe = false;
174 return false;
175 }
176 }
177 foundProbe = true;
178
179 bool foundMatch = false;
180 for (auto &device : dbusObject)
181 {
182 bool deviceMatches = true;
183 for (auto &match : matches)
184 {
185 auto deviceValue = device.find(match.first);
186 if (deviceValue != device.end())
187 {
188 switch (match.second.type())
189 {
190 case nlohmann::json::value_t::string:
191 {
192 std::regex search(match.second.get<std::string>());
193 std::smatch match;
194
195 // convert value to string respresentation
196 std::string probeValue = boost::apply_visitor(
197 [](const auto &x) {
198 return boost::lexical_cast<std::string>(x);
199 },
200 deviceValue->second);
201 if (!std::regex_search(probeValue, match, search))
202 {
203 deviceMatches = false;
204 break;
205 }
206 break;
207 }
208 case nlohmann::json::value_t::boolean:
209 case nlohmann::json::value_t::number_unsigned:
210 {
211 unsigned int probeValue = boost::apply_visitor(
212 VariantToUnsignedIntVisitor(), deviceValue->second);
213
214 if (probeValue != match.second.get<unsigned int>())
215 {
216 deviceMatches = false;
217 }
218 break;
219 }
220 case nlohmann::json::value_t::number_integer:
221 {
222 int probeValue = boost::apply_visitor(VariantToIntVisitor(),
223 deviceValue->second);
224
225 if (probeValue != match.second.get<int>())
226 {
227 deviceMatches = false;
228 }
229 break;
230 }
231 case nlohmann::json::value_t::number_float:
232 {
233 float probeValue = boost::apply_visitor(
234 VariantToFloatVisitor(), deviceValue->second);
235
236 if (probeValue != match.second.get<float>())
237 {
238 deviceMatches = false;
239 }
240 break;
241 }
242 }
243 }
244 else
245 {
246 deviceMatches = false;
247 break;
248 }
249 }
250 if (deviceMatches)
251 {
252 devices.emplace_back(
253 boost::container::flat_map<std::string, dbus::dbus_variant>(
254 device));
255 foundMatch = true;
256 deviceMatches = false; // for next iteration
257 }
258 }
259 return foundMatch;
260}
261
262// default probe entry point, iterates a list looking for specific types to
263// call specific probe functions
264bool probe(
265 const std::vector<std::string> probeCommand,
266 std::vector<boost::container::flat_map<std::string, dbus::dbus_variant>>
267 &foundDevs)
268{
269 const static std::regex command(R"(\((.*)\))");
270 std::smatch match;
271 bool ret = false;
James Feist6bd2a022018-03-13 12:30:58 -0700272 bool matchOne = false;
James Feist3cb5fec2018-01-23 14:41:51 -0800273 bool cur = true;
274 probe_type_codes lastCommand = probe_type_codes::FALSE_T;
275
276 for (auto &probe : probeCommand)
277 {
278 bool foundProbe = false;
279 boost::container::flat_map<const char *, probe_type_codes,
280 cmp_str>::const_iterator probeType;
281
282 for (probeType = PROBE_TYPES.begin(); probeType != PROBE_TYPES.end();
283 probeType++)
284 {
285 if (probe.find(probeType->first) != std::string::npos)
286 {
287 foundProbe = true;
288 break;
289 }
290 }
291 if (foundProbe)
292 {
293 switch (probeType->second)
294 {
295 case probe_type_codes::FALSE_T:
296 {
297 return false; // todo, actually evaluate?
298 break;
299 }
300 case probe_type_codes::TRUE_T:
301 {
302 return true; // todo, actually evaluate?
303 break;
304 }
James Feist6bd2a022018-03-13 12:30:58 -0700305 case probe_type_codes::MATCH_ONE:
306 {
307 // set current value to last, this probe type shouldn't affect
308 // the outcome
309 cur = ret;
310 matchOne = true;
311 break;
312 }
James Feist3cb5fec2018-01-23 14:41:51 -0800313 /*case probe_type_codes::AND:
314 break;
315 case probe_type_codes::OR:
316 break;
317 // these are no-ops until the last command switch
318 */
319 case probe_type_codes::FOUND:
320 {
321 if (!std::regex_search(probe, match, command))
322 {
323 std::cerr << "found probe sytax error " << probe << "\n";
324 return false;
325 }
326 std::string commandStr = *(match.begin() + 1);
James Feist3f8a2782018-02-12 09:24:42 -0800327 boost::replace_all(commandStr, "'", "");
James Feist3cb5fec2018-01-23 14:41:51 -0800328 cur = (std::find(PASSED_PROBES.begin(), PASSED_PROBES.end(),
329 commandStr) != PASSED_PROBES.end());
330 break;
331 }
332 }
333 }
334 // look on dbus for object
335 else
336 {
337 if (!std::regex_search(probe, match, command))
338 {
339 std::cerr << "dbus probe sytax error " << probe << "\n";
340 return false;
341 }
342 std::string commandStr = *(match.begin() + 1);
343 // convert single ticks and single slashes into legal json
James Feist3f8a2782018-02-12 09:24:42 -0800344 boost::replace_all(commandStr, "'", R"(")");
345 boost::replace_all(commandStr, R"(\)", R"(\\)");
James Feist3cb5fec2018-01-23 14:41:51 -0800346 auto json = nlohmann::json::parse(commandStr, nullptr, false);
347 if (json.is_discarded())
348 {
349 std::cerr << "dbus command sytax error " << commandStr << "\n";
350 return false;
351 }
352 // we can match any (string, variant) property. (string, string)
353 // does a regex
354 std::map<std::string, nlohmann::json> dbusProbeMap =
355 json.get<std::map<std::string, nlohmann::json>>();
356 auto findStart = probe.find("(");
357 if (findStart == std::string::npos)
358 {
359 return false;
360 }
361 std::string probeInterface = probe.substr(0, findStart);
362 cur =
363 probeDbus(probeInterface, dbusProbeMap, foundDevs, foundProbe);
364 }
365
366 // some functions like AND and OR only take affect after the
367 // fact
368 switch (lastCommand)
369 {
370 case probe_type_codes::AND:
371 ret = cur && ret;
372 break;
373 case probe_type_codes::OR:
374 ret = cur || ret;
375 break;
376 default:
377 ret = cur;
378 break;
379 }
380 lastCommand = probeType != PROBE_TYPES.end()
381 ? probeType->second
382 : probe_type_codes::FALSE_T;
383
384 if (!foundProbe)
385 {
386 std::cerr << "Illegal probe type " << probe << "\n";
387 return false;
388 }
389 }
390
391 // probe passed, but empty device
392 // todo: should this be done in main?
393 if (ret && foundDevs.size() == 0)
394 {
395 foundDevs.emplace_back(
396 boost::container::flat_map<std::string, dbus::dbus_variant>());
397 }
James Feist6bd2a022018-03-13 12:30:58 -0700398 if (matchOne && foundDevs.size() > 1)
399 {
400 foundDevs.erase(foundDevs.begin() + 1, foundDevs.end());
401 }
James Feist3cb5fec2018-01-23 14:41:51 -0800402 return ret;
403}
404
James Feist1b2e2242018-01-30 13:45:19 -0800405// this function is temporary, no need to have once dbus is solified.
406void writeJsonFiles(nlohmann::json &systemConfiguration)
407{
408 std::experimental::filesystem::create_directory(OUTPUT_DIR);
409 std::ofstream output(std::string(OUTPUT_DIR) + "system.json");
410 output << systemConfiguration.dump(4);
411 output.close();
412
413 auto flat = nlohmann::json::array();
414 for (auto &pair : nlohmann::json::iterator_wrapper(systemConfiguration))
415 {
416 auto value = pair.value();
417 auto exposes = value.find("exposes");
418 if (exposes != value.end())
419 {
420 for (auto &item : *exposes)
421 {
422 flat.push_back(item);
423 }
424 }
425 }
426 output = std::ofstream(std::string(OUTPUT_DIR) + "flattened.json");
427 output << flat.dump(4);
428 output.close();
429}
430// adds simple json types to interface's properties
431void populateInterfaceFromJson(dbus::DbusInterface *iface, nlohmann::json dict,
432 dbus::DbusObjectServer &objServer)
433{
434 std::vector<std::pair<std::string, dbus::dbus_variant>> properties;
435 static size_t flushCount = 0;
436
437 for (auto &dictPair : nlohmann::json::iterator_wrapper(dict))
438 {
439 switch (dictPair.value().type())
440 {
441 case (nlohmann::json::value_t::boolean):
442 {
443 properties.emplace_back(std::string(dictPair.key()),
444 dictPair.value().get<bool>());
445 break;
446 }
447 case (nlohmann::json::value_t::number_integer):
448 {
449 properties.emplace_back(std::string(dictPair.key()),
450 dictPair.value().get<int64_t>());
451 break;
452 }
453 case (nlohmann::json::value_t::number_unsigned):
454 {
455 properties.emplace_back(std::string(dictPair.key()),
456 dictPair.value().get<uint64_t>());
457 break;
458 }
459 case (nlohmann::json::value_t::number_float):
460 {
461 properties.emplace_back(std::string(dictPair.key()),
462 dictPair.value().get<float>());
463 break;
464 }
465 case (nlohmann::json::value_t::string):
466 {
467 properties.emplace_back(std::string(dictPair.key()),
468 dictPair.value().get<std::string>());
469 break;
470 }
471 }
472 }
473 if (!properties.empty())
474 {
475 iface->set_properties(properties);
476
477 // flush the queue after adding an amount of properties so we don't hang
James Feistc95cb142018-02-26 10:41:42 -0800478 if (flushCount++ > PROPERTIES_CHANGED_UNTIL_FLUSH_COUNT)
James Feist1b2e2242018-01-30 13:45:19 -0800479 {
480 objServer.flush();
481 flushCount = 0;
482 }
483 }
484}
485
486void postToDbus(const nlohmann::json &systemConfiguration,
James Feist75fdeeb2018-02-20 14:26:16 -0800487 dbus::DbusObjectServer &objServer)
488
James Feist1b2e2242018-01-30 13:45:19 -0800489{
490 for (auto &boardPair :
491 nlohmann::json::iterator_wrapper(systemConfiguration))
492 {
493 std::string boardKey = boardPair.key();
494 auto boardValues = boardPair.value();
495 auto findBoardType = boardValues.find("type");
496 std::string boardType;
497 if (findBoardType != boardValues.end() &&
498 findBoardType->type() == nlohmann::json::value_t::string)
499 {
500 boardType = findBoardType->get<std::string>();
501 std::regex_replace(boardType.begin(), boardType.begin(),
502 boardType.end(), ILLEGAL_DBUS_REGEX, "_");
503 }
504 else
505 {
506 std::cerr << "Unable to find type for " << boardKey
507 << " reverting to Chassis.\n";
508 boardType = "Chassis";
509 }
510
511 std::regex_replace(boardKey.begin(), boardKey.begin(), boardKey.end(),
512 ILLEGAL_DBUS_REGEX, "_");
513 std::string boardName =
514 "/xyz/openbmc_project/Inventory/Item/" + boardType + "/" + boardKey;
515 auto boardObject = objServer.add_object(boardName);
516
517 auto boardIface = boardObject->add_interface(
518 "xyz.openbmc_project.Configuration." + boardType);
519 populateInterfaceFromJson(boardIface.get(), boardValues, objServer);
520 auto exposes = boardValues.find("exposes");
521 if (exposes == boardValues.end())
522 {
523 continue;
524 }
525 for (auto &item : *exposes)
526 {
527 auto findName = item.find("name");
528 if (findName == item.end())
529 {
530 std::cerr << "cannot find name in field " << item << "\n";
531 continue;
532 }
533 auto findStatus = item.find("status");
534 // if status is not found it is assumed to be status = 'okay'
535 if (findStatus != item.end())
536 {
537 if (*findStatus == "disabled")
538 {
539 continue;
540 }
541 }
542 auto findType = item.find("type");
543 std::string itemType;
544 if (findType != item.end())
545 {
546 itemType = findType->get<std::string>();
547 std::regex_replace(itemType.begin(), itemType.begin(),
548 itemType.end(), ILLEGAL_DBUS_REGEX, "_");
549 }
550 else
551 {
552 itemType = "unknown";
553 }
554 std::string itemName = findName->get<std::string>();
555 std::regex_replace(itemName.begin(), itemName.begin(),
556 itemName.end(), ILLEGAL_DBUS_REGEX, "_");
557 auto itemObject = objServer.add_object(boardName + "/" + itemName);
558 auto itemIface = itemObject->add_interface(
559 "xyz.openbmc_project.Configuration." + itemType);
560
561 populateInterfaceFromJson(itemIface.get(), item, objServer);
562
563 for (auto &objectPair : nlohmann::json::iterator_wrapper(item))
564 {
565 if (objectPair.value().type() ==
566 nlohmann::json::value_t::object)
567 {
568 auto objectIface = itemObject->add_interface(
569 "xyz.openbmc_project.Configuration." + itemType + "." +
570 objectPair.key());
571 populateInterfaceFromJson(objectIface.get(),
572 objectPair.value(), objServer);
573 }
574 else if (objectPair.value().type() ==
575 nlohmann::json::value_t::array)
576 {
577 size_t index = 0;
578 for (auto &arrayItem : objectPair.value())
579 {
580 if (arrayItem.type() != nlohmann::json::value_t::object)
581 {
582 std::cerr << "dbus format error" << arrayItem
583 << "\n";
584 break;
585 }
586 auto objectIface = itemObject->add_interface(
587 "xyz.openbmc_project.Configuration." + itemType +
588 "." + objectPair.key() + "." +
589 std::to_string(index));
590 index++;
591 populateInterfaceFromJson(objectIface.get(), arrayItem,
592 objServer);
593 }
594 }
595 }
596 }
597 }
598}
599
600// finds the template character (currently set to $) and replaces the value with
601// the field found in a dbus object i.e. $ADDRESS would get populated with the
602// ADDRESS field from a object on dbus
603void templateCharReplace(
604 nlohmann::json::iterator &keyPair,
605 const boost::container::flat_map<std::string, dbus::dbus_variant>
606 &foundDevice,
607 size_t &foundDeviceIdx)
608{
609 if (keyPair.value().type() != nlohmann::json::value_t::string)
610 {
611 return;
612 }
613
614 std::string value = keyPair.value();
615 if (value.find(TEMPLATE_CHAR) != std::string::npos)
616 {
617 std::string templateValue = value;
618
619 templateValue.erase(0, 1); // remove template character
620
621 // special case index
622 if ("index" == templateValue)
623 {
624 keyPair.value() = foundDeviceIdx;
625 }
626 else
627 {
628 std::string subsitute;
629 for (auto &foundDevicePair : foundDevice)
630 {
631 if (boost::iequals(foundDevicePair.first, templateValue))
632 {
633 // convert value to string
634 // respresentation
635 subsitute = boost::apply_visitor(
636 [](const auto &x) {
637 return boost::lexical_cast<std::string>(x);
638 },
639 foundDevicePair.second);
640 break;
641 }
642 }
643 if (!subsitute.size())
644 {
645 std::cerr << "could not find symbol " << templateValue << "\n";
646 }
647 else
648 {
649 keyPair.value() = subsitute;
650 }
651 }
652 }
653}
654
James Feist75fdeeb2018-02-20 14:26:16 -0800655bool findJsonFiles(std::vector<nlohmann::json> &configurations)
James Feist3cb5fec2018-01-23 14:41:51 -0800656{
657 // find configuration files
658 std::vector<fs::path> jsonPaths;
659 if (!find_files(fs::path(CONFIGURATION_DIR), R"(.*\.json)", jsonPaths, 0))
660 {
661 std::cerr << "Unable to find any configuration files in "
662 << CONFIGURATION_DIR << "\n";
James Feist75fdeeb2018-02-20 14:26:16 -0800663 return false;
James Feist3cb5fec2018-01-23 14:41:51 -0800664 }
James Feist3cb5fec2018-01-23 14:41:51 -0800665 for (auto &jsonPath : jsonPaths)
666 {
667 std::ifstream jsonStream(jsonPath.c_str());
668 if (!jsonStream.good())
669 {
670 std::cerr << "unable to open " << jsonPath.string() << "\n";
671 continue;
672 }
673 auto data = nlohmann::json::parse(jsonStream, nullptr, false);
674 if (data.is_discarded())
675 {
676 std::cerr << "syntax error in " << jsonPath.string() << "\n";
677 continue;
678 }
679 if (data.type() == nlohmann::json::value_t::array)
680 {
681 for (auto &d : data)
682 {
683 configurations.emplace_back(d);
684 }
685 }
686 else
687 {
688 configurations.emplace_back(data);
689 }
690 }
James Feist75fdeeb2018-02-20 14:26:16 -0800691}
James Feist3cb5fec2018-01-23 14:41:51 -0800692
James Feist75fdeeb2018-02-20 14:26:16 -0800693bool rescan(nlohmann::json &systemConfiguration)
694{
695 std::vector<nlohmann::json> configurations;
696 if (!findJsonFiles(configurations))
697 {
698 false;
699 }
700 // preprocess already passed configurations and missing fields
701 if (systemConfiguration.size())
702 {
703 for (auto it = configurations.begin(); it != configurations.end();)
704 {
705 auto findName = it->find("name");
706 if (findName == it->end())
707 {
708 std::cerr << "configuration missing name field " << *it << "\n";
709 it = configurations.erase(it);
710 continue;
711 }
712 else if (findName->type() != nlohmann::json::value_t::string)
713 {
714 std::cerr << "name field must be a string " << *findName
715 << "\n";
716 it = configurations.erase(it);
717 continue;
718 }
719 auto findAlreadyFound =
720 systemConfiguration.find(findName->get<std::string>());
721 if (findAlreadyFound != systemConfiguration.end())
722 {
723 it = configurations.erase(it);
724 continue;
725 }
726 // TODO: add in tags to determine if configuration should be
727 // refreshed on AC / DC / Always.
728 it++;
729 }
730 }
731
732 // probe until no probes pass
James Feist3cb5fec2018-01-23 14:41:51 -0800733 bool probePassed = true;
James Feist3cb5fec2018-01-23 14:41:51 -0800734 while (probePassed)
735 {
736 probePassed = false;
737 for (auto it = configurations.begin(); it != configurations.end();)
738 {
739 bool eraseConfig = false;
James Feist1b2e2242018-01-30 13:45:19 -0800740 auto findProbe = it->find("probe");
741 auto findName = it->find("name");
James Feist3cb5fec2018-01-23 14:41:51 -0800742
James Feist1b2e2242018-01-30 13:45:19 -0800743 nlohmann::json probeCommand;
744 // check for poorly formatted fields, probe must be an array
745 if (findProbe == it->end())
James Feist3cb5fec2018-01-23 14:41:51 -0800746 {
747 std::cerr << "configuration file missing probe:\n " << *it
748 << "\n";
749 eraseConfig = true;
750 }
James Feist1b2e2242018-01-30 13:45:19 -0800751 else if ((*findProbe).type() != nlohmann::json::value_t::array)
James Feist3cb5fec2018-01-23 14:41:51 -0800752 {
753 probeCommand = nlohmann::json::array();
754 probeCommand.push_back(*findProbe);
755 }
756 else
757 {
758 probeCommand = *findProbe;
759 }
James Feist1b2e2242018-01-30 13:45:19 -0800760
761 if (findName == it->end())
762 {
763 std::cerr << "configuration file missing name:\n " << *it
764 << "\n";
765 eraseConfig = true;
766 }
767
James Feist3cb5fec2018-01-23 14:41:51 -0800768 std::vector<
769 boost::container::flat_map<std::string, dbus::dbus_variant>>
770 foundDevices;
James Feist1b2e2242018-01-30 13:45:19 -0800771 if (!eraseConfig && probe(probeCommand, foundDevices))
James Feist3cb5fec2018-01-23 14:41:51 -0800772 {
773 eraseConfig = true;
774 probePassed = true;
James Feist7b7e4e82018-01-24 14:56:00 -0800775 std::string name = *findName;
776 PASSED_PROBES.push_back(name);
James Feist3cb5fec2018-01-23 14:41:51 -0800777
778 size_t foundDeviceIdx = 0;
779
780 for (auto &foundDevice : foundDevices)
781 {
James Feist1b2e2242018-01-30 13:45:19 -0800782 for (auto keyPair = it->begin(); keyPair != it->end();
783 keyPair++)
James Feist3cb5fec2018-01-23 14:41:51 -0800784 {
James Feist1b2e2242018-01-30 13:45:19 -0800785 templateCharReplace(keyPair, foundDevice,
786 foundDeviceIdx);
787 }
788 auto findExpose = it->find("exposes");
789 if (findExpose == it->end())
790 {
James Feist3cb5fec2018-01-23 14:41:51 -0800791 continue;
792 }
793 for (auto &expose : *findExpose)
794 {
795 for (auto keyPair = expose.begin();
796 keyPair != expose.end(); keyPair++)
797 {
James Feist1b2e2242018-01-30 13:45:19 -0800798
James Feist3cb5fec2018-01-23 14:41:51 -0800799 // fill in template characters with devices
800 // found
James Feist1b2e2242018-01-30 13:45:19 -0800801 templateCharReplace(keyPair, foundDevice,
802 foundDeviceIdx);
803 // special case bind
804 if (boost::starts_with(keyPair.key(), "bind_"))
James Feist3cb5fec2018-01-23 14:41:51 -0800805 {
James Feist1b2e2242018-01-30 13:45:19 -0800806 if (keyPair.value().type() !=
807 nlohmann::json::value_t::string)
James Feist3cb5fec2018-01-23 14:41:51 -0800808 {
James Feist1b2e2242018-01-30 13:45:19 -0800809 std::cerr
810 << "bind_ value must be of type string "
811 << keyPair.key() << "\n";
812 continue;
James Feist3cb5fec2018-01-23 14:41:51 -0800813 }
James Feist1b2e2242018-01-30 13:45:19 -0800814 bool foundBind = false;
815 std::string bind =
816 keyPair.key().substr(sizeof("bind_") - 1);
817 for (auto &configurationPair :
818 nlohmann::json::iterator_wrapper(
819 systemConfiguration))
James Feist3cb5fec2018-01-23 14:41:51 -0800820 {
James Feist1b2e2242018-01-30 13:45:19 -0800821
822 auto configListFind =
823 configurationPair.value().find(
824 "exposes");
825
826 if (configListFind ==
827 configurationPair.value().end() ||
828 configListFind->type() !=
829 nlohmann::json::value_t::array)
James Feist3cb5fec2018-01-23 14:41:51 -0800830 {
James Feist1b2e2242018-01-30 13:45:19 -0800831 continue;
832 }
833 for (auto &exposedObject : *configListFind)
834 {
835 std::string foundObjectName =
836 (exposedObject)["name"];
837 if (boost::iequals(
838 foundObjectName,
839 keyPair.value()
840 .get<std::string>()))
James Feist3cb5fec2018-01-23 14:41:51 -0800841 {
James Feistc95cb142018-02-26 10:41:42 -0800842 exposedObject["status"] = "okay";
James Feist1b2e2242018-01-30 13:45:19 -0800843 expose[bind] = exposedObject;
James Feist1b2e2242018-01-30 13:45:19 -0800844
845 foundBind = true;
James Feist3cb5fec2018-01-23 14:41:51 -0800846 break;
847 }
848 }
James Feist1b2e2242018-01-30 13:45:19 -0800849 if (foundBind)
James Feist3cb5fec2018-01-23 14:41:51 -0800850 {
James Feist1b2e2242018-01-30 13:45:19 -0800851 break;
James Feist3cb5fec2018-01-23 14:41:51 -0800852 }
853 }
James Feist1b2e2242018-01-30 13:45:19 -0800854 if (!foundBind)
855 {
856 std::cerr << "configuration file "
857 "dependency error, "
858 "could not find bind "
859 << keyPair.value() << "\n";
860 }
James Feist3cb5fec2018-01-23 14:41:51 -0800861 }
862 }
863 }
James Feist3cb5fec2018-01-23 14:41:51 -0800864 }
James Feist1b2e2242018-01-30 13:45:19 -0800865 systemConfiguration[name] = (*it);
866 foundDeviceIdx++;
James Feist3cb5fec2018-01-23 14:41:51 -0800867 }
868
869 if (eraseConfig)
870 {
871 it = configurations.erase(it);
872 }
873 else
874 {
875 it++;
876 }
877 }
878 }
James Feist75fdeeb2018-02-20 14:26:16 -0800879}
880
881void propertiesChangedCallback(
882 std::vector<std::pair<std::unique_ptr<dbus::match>,
883 std::shared_ptr<dbus::filter>>> &dbusMatches,
James Feist4131aea2018-03-09 09:47:30 -0800884 nlohmann::json &systemConfiguration, dbus::DbusObjectServer &objServer,
885 std::shared_ptr<dbus::filter> dbusFilter)
James Feist75fdeeb2018-02-20 14:26:16 -0800886{
887 static std::future<void> future;
James Feist4131aea2018-03-09 09:47:30 -0800888 static std::atomic_bool threadRunning(false);
889 static std::atomic_bool pendingCallback(false);
James Feist75fdeeb2018-02-20 14:26:16 -0800890 bool notRunning = false;
891 if (threadRunning.compare_exchange_strong(notRunning, true))
892 {
893 future = std::async(std::launch::async, [&] {
James Feistc95cb142018-02-26 10:41:42 -0800894
James Feist4131aea2018-03-09 09:47:30 -0800895 do
896 {
897 std::this_thread::sleep_for(std::chrono::seconds(
898 SLEEP_AFTER_PROPERTIES_CHANGE_SECONDS));
899 auto oldConfiguration = systemConfiguration;
900 DBUS_PROBE_OBJECTS.clear();
901 pendingCallback = false;
902 rescan(systemConfiguration);
903 auto newConfiguration = systemConfiguration;
904 for (auto it = newConfiguration.begin();
905 it != newConfiguration.end();)
906 {
907 auto findKey = oldConfiguration.find(it.key());
908 if (findKey != oldConfiguration.end())
909 {
910 it = newConfiguration.erase(it);
911 }
912 else
913 {
914 it++;
915 }
916 }
917
918 registerCallbacks(dbusMatches, systemConfiguration, objServer);
919 // todo: for now, only add new configurations, unload to come
920 // later
921 // unloadOverlays();
922 loadOverlays(newConfiguration);
923 // this line to be removed in future
924 writeJsonFiles(systemConfiguration);
925 // only post new items to bus for now
926 postToDbus(newConfiguration, objServer);
927 } while (pendingCallback);
James Feist75fdeeb2018-02-20 14:26:16 -0800928 threadRunning = false;
929 });
930 }
James Feist4131aea2018-03-09 09:47:30 -0800931 else
932 {
933 pendingCallback = true;
934 }
James Feist75fdeeb2018-02-20 14:26:16 -0800935 if (dbusFilter != nullptr)
936 {
937 dbusFilter->async_dispatch([&, dbusFilter](boost::system::error_code ec,
938 dbus::message) {
939 if (ec)
940 {
941 std::cerr << "properties changed callback error " << ec << "\n";
942 }
James Feist4131aea2018-03-09 09:47:30 -0800943 propertiesChangedCallback(dbusMatches, systemConfiguration,
944 objServer, dbusFilter);
James Feist75fdeeb2018-02-20 14:26:16 -0800945 });
946 }
947}
948
949void registerCallbacks(
950 std::vector<std::pair<std::unique_ptr<dbus::match>,
951 std::shared_ptr<dbus::filter>>> &dbusMatches,
James Feist4131aea2018-03-09 09:47:30 -0800952 nlohmann::json &systemConfiguration, dbus::DbusObjectServer &objServer)
James Feist75fdeeb2018-02-20 14:26:16 -0800953{
954 static boost::container::flat_set<std::string> watchedObjects;
955
956 for (const auto &objectMap : DBUS_PROBE_OBJECTS)
957 {
958 auto findObject = watchedObjects.find(objectMap.first);
959 if (findObject != watchedObjects.end())
960 {
961 continue;
962 }
963 // this creates a filter for properties changed for any new probe type
964 auto propertyChange = std::make_unique<dbus::match>(
965 SYSTEM_BUS,
966 "type='signal',member='PropertiesChanged',arg0='" +
967 objectMap.first + "'");
968 auto filter =
969 std::make_shared<dbus::filter>(SYSTEM_BUS, [](dbus::message &m) {
970 auto member = m.get_member();
971 return member == "PropertiesChanged";
972 });
973
974 filter->async_dispatch([&, filter](boost::system::error_code ec,
975 dbus::message) {
976 if (ec)
977 {
978 std::cerr << "register callbacks callback error " << ec << "\n";
979 }
James Feist4131aea2018-03-09 09:47:30 -0800980 propertiesChangedCallback(dbusMatches, systemConfiguration,
981 objServer, filter);
James Feist75fdeeb2018-02-20 14:26:16 -0800982 });
983 dbusMatches.emplace_back(std::move(propertyChange), filter);
984 }
985}
986
987int main(int argc, char **argv)
988{
989 // setup connection to dbus
990 boost::asio::io_service io;
991 SYSTEM_BUS = std::make_shared<dbus::connection>(io, dbus::bus::system);
James Feist4131aea2018-03-09 09:47:30 -0800992
James Feist75fdeeb2018-02-20 14:26:16 -0800993 dbus::DbusObjectServer objServer(SYSTEM_BUS);
994 SYSTEM_BUS->request_name("xyz.openbmc_project.EntityManager");
James Feist75fdeeb2018-02-20 14:26:16 -0800995 std::vector<
996 std::pair<std::unique_ptr<dbus::match>, std::shared_ptr<dbus::filter>>>
997 dbusMatches;
James Feist4131aea2018-03-09 09:47:30 -0800998
999 nlohmann::json systemConfiguration = nlohmann::json::object();
1000 auto iface = std::make_shared<dbus::DbusInterface>(
1001 "xyz.openbmc_project.EntityManager", SYSTEM_BUS);
1002 io.post([&]() {
1003 unloadAllOverlays();
1004 propertiesChangedCallback(dbusMatches, systemConfiguration, objServer,
1005 nullptr);
1006 auto object = std::make_shared<dbus::DbusObject>(
1007 SYSTEM_BUS, "/xyz/openbmc_project/EntityManager");
1008 objServer.register_object(object);
1009
1010 object->register_interface(iface);
1011
1012 });
1013
1014 // to keep reference to the match / filter objects so they don't get
1015 // destroyed
1016
James Feist75fdeeb2018-02-20 14:26:16 -08001017 iface->register_method("ReScan", [&]() {
James Feist4131aea2018-03-09 09:47:30 -08001018 propertiesChangedCallback(dbusMatches, systemConfiguration, objServer,
1019 nullptr);
James Feist75fdeeb2018-02-20 14:26:16 -08001020 return std::tuple<>(); // this is a bug in boost-dbus, needs some sort
1021 // of return
1022 });
James Feist75fdeeb2018-02-20 14:26:16 -08001023
James Feist1b2e2242018-01-30 13:45:19 -08001024 io.run();
James Feist3cb5fec2018-01-23 14:41:51 -08001025
1026 return 0;
James Feist75fdeeb2018-02-20 14:26:16 -08001027}