blob: 45d18e9d9f0d0e7274a6ad3f389c9e8aed085a75 [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,
57 FOUND
58};
59const static boost::container::flat_map<const char *, probe_type_codes, cmp_str>
60 PROBE_TYPES{{{"FALSE", probe_type_codes::FALSE_T},
61 {"TRUE", probe_type_codes::TRUE_T},
62 {"AND", probe_type_codes::AND},
63 {"OR", probe_type_codes::OR},
64 {"FOUND", probe_type_codes::FOUND}}};
65
66using GetSubTreeType = std::vector<
67 std::pair<std::string,
68 std::vector<std::pair<std::string, std::vector<std::string>>>>>;
69
70using ManagedObjectType = boost::container::flat_map<
71 dbus::object_path,
72 boost::container::flat_map<
73 std::string,
74 boost::container::flat_map<std::string, dbus::dbus_variant>>>;
75
76boost::container::flat_map<
77 std::string,
78 std::vector<boost::container::flat_map<std::string, dbus::dbus_variant>>>
79 DBUS_PROBE_OBJECTS;
80std::vector<std::string> PASSED_PROBES;
81
82// todo: pass this through nicer
83std::shared_ptr<dbus::connection> SYSTEM_BUS;
84
James Feist1b2e2242018-01-30 13:45:19 -080085std::regex ILLEGAL_DBUS_REGEX("[^A-Za-z0-9_]");
86
James Feist75fdeeb2018-02-20 14:26:16 -080087void registerCallbacks(
88 std::vector<std::pair<std::unique_ptr<dbus::match>,
89 std::shared_ptr<dbus::filter>>> &dbusMatches,
James Feist4131aea2018-03-09 09:47:30 -080090 nlohmann::json &systemConfiguration, dbus::DbusObjectServer &objServer);
James Feist75fdeeb2018-02-20 14:26:16 -080091
James Feist3cb5fec2018-01-23 14:41:51 -080092// calls the mapper to find all exposed objects of an interface type
93// and creates a vector<flat_map> that contains all the key value pairs
94// getManagedObjects
95bool findDbusObjects(
96 std::shared_ptr<dbus::connection> connection,
97 std::vector<boost::container::flat_map<std::string, dbus::dbus_variant>>
98 &interfaceDevices,
99 std::string interface)
100{
101 // find all connections in the mapper that expose a specific type
102 static const dbus::endpoint mapper("xyz.openbmc_project.ObjectMapper",
103 "/xyz/openbmc_project/object_mapper",
104 "xyz.openbmc_project.ObjectMapper",
105 "GetSubTree");
106 dbus::message getMap = dbus::message::new_call(mapper);
107 std::vector<std::string> objects = {interface};
108 if (!getMap.pack("", MAX_MAPPER_DEPTH, objects))
109 {
110 std::cerr << "Pack Failed GetSensorSubtree\n";
111 return false;
112 }
113 dbus::message getMapResp = connection->send(getMap);
114 GetSubTreeType interfaceSubtree;
115 if (!getMapResp.unpack(interfaceSubtree))
116 {
117 std::cerr << "Error communicating to mapper\n";
118 return false;
119 }
120 boost::container::flat_set<std::string> connections;
121 for (auto &object : interfaceSubtree)
122 {
123 for (auto &connPair : object.second)
124 {
125 connections.insert(connPair.first);
126 }
127 }
128 // iterate through the connections, adding creating individual device
129 // dictionaries
130 for (auto &conn : connections)
131 {
132 auto managedObj =
133 dbus::endpoint(conn, "/", "org.freedesktop.DBus.ObjectManager",
134 "GetManagedObjects");
135 dbus::message getManagedObj = dbus::message::new_call(managedObj);
136 dbus::message getManagedObjResp = connection->send(getManagedObj);
137 ManagedObjectType managedInterface;
138 if (!getManagedObjResp.unpack(managedInterface))
139 {
140 std::cerr << "error getting managed object for device " << conn
141 << "\n";
142 continue;
143 }
144 for (auto &interfaceManagedObj : managedInterface)
145 {
146 auto ifaceObjFind = interfaceManagedObj.second.find(interface);
147 if (ifaceObjFind != interfaceManagedObj.second.end())
148 {
149 interfaceDevices.emplace_back(ifaceObjFind->second);
150 }
151 }
152 }
153 return true;
154}
155
156// probes interface dictionary for a key with a value that matches a regex
157bool probeDbus(
158 const std::string &interface,
159 const std::map<std::string, nlohmann::json> &matches,
160 std::vector<boost::container::flat_map<std::string, dbus::dbus_variant>>
161 &devices,
162 bool &foundProbe)
163{
164 auto &dbusObject = DBUS_PROBE_OBJECTS[interface];
165 if (dbusObject.empty())
166 {
167 if (!findDbusObjects(SYSTEM_BUS, dbusObject, interface))
168 {
169 std::cerr << "Found no dbus objects with interface "
170 << interface << "\n";
171 foundProbe = false;
172 return false;
173 }
174 }
175 foundProbe = true;
176
177 bool foundMatch = false;
178 for (auto &device : dbusObject)
179 {
180 bool deviceMatches = true;
181 for (auto &match : matches)
182 {
183 auto deviceValue = device.find(match.first);
184 if (deviceValue != device.end())
185 {
186 switch (match.second.type())
187 {
188 case nlohmann::json::value_t::string:
189 {
190 std::regex search(match.second.get<std::string>());
191 std::smatch match;
192
193 // convert value to string respresentation
194 std::string probeValue = boost::apply_visitor(
195 [](const auto &x) {
196 return boost::lexical_cast<std::string>(x);
197 },
198 deviceValue->second);
199 if (!std::regex_search(probeValue, match, search))
200 {
201 deviceMatches = false;
202 break;
203 }
204 break;
205 }
206 case nlohmann::json::value_t::boolean:
207 case nlohmann::json::value_t::number_unsigned:
208 {
209 unsigned int probeValue = boost::apply_visitor(
210 VariantToUnsignedIntVisitor(), deviceValue->second);
211
212 if (probeValue != match.second.get<unsigned int>())
213 {
214 deviceMatches = false;
215 }
216 break;
217 }
218 case nlohmann::json::value_t::number_integer:
219 {
220 int probeValue = boost::apply_visitor(VariantToIntVisitor(),
221 deviceValue->second);
222
223 if (probeValue != match.second.get<int>())
224 {
225 deviceMatches = false;
226 }
227 break;
228 }
229 case nlohmann::json::value_t::number_float:
230 {
231 float probeValue = boost::apply_visitor(
232 VariantToFloatVisitor(), deviceValue->second);
233
234 if (probeValue != match.second.get<float>())
235 {
236 deviceMatches = false;
237 }
238 break;
239 }
240 }
241 }
242 else
243 {
244 deviceMatches = false;
245 break;
246 }
247 }
248 if (deviceMatches)
249 {
250 devices.emplace_back(
251 boost::container::flat_map<std::string, dbus::dbus_variant>(
252 device));
253 foundMatch = true;
254 deviceMatches = false; // for next iteration
255 }
256 }
257 return foundMatch;
258}
259
260// default probe entry point, iterates a list looking for specific types to
261// call specific probe functions
262bool probe(
263 const std::vector<std::string> probeCommand,
264 std::vector<boost::container::flat_map<std::string, dbus::dbus_variant>>
265 &foundDevs)
266{
267 const static std::regex command(R"(\((.*)\))");
268 std::smatch match;
269 bool ret = false;
270 bool cur = true;
271 probe_type_codes lastCommand = probe_type_codes::FALSE_T;
272
273 for (auto &probe : probeCommand)
274 {
275 bool foundProbe = false;
276 boost::container::flat_map<const char *, probe_type_codes,
277 cmp_str>::const_iterator probeType;
278
279 for (probeType = PROBE_TYPES.begin(); probeType != PROBE_TYPES.end();
280 probeType++)
281 {
282 if (probe.find(probeType->first) != std::string::npos)
283 {
284 foundProbe = true;
285 break;
286 }
287 }
288 if (foundProbe)
289 {
290 switch (probeType->second)
291 {
292 case probe_type_codes::FALSE_T:
293 {
294 return false; // todo, actually evaluate?
295 break;
296 }
297 case probe_type_codes::TRUE_T:
298 {
299 return true; // todo, actually evaluate?
300 break;
301 }
302 /*case probe_type_codes::AND:
303 break;
304 case probe_type_codes::OR:
305 break;
306 // these are no-ops until the last command switch
307 */
308 case probe_type_codes::FOUND:
309 {
310 if (!std::regex_search(probe, match, command))
311 {
312 std::cerr << "found probe sytax error " << probe << "\n";
313 return false;
314 }
315 std::string commandStr = *(match.begin() + 1);
James Feist3f8a2782018-02-12 09:24:42 -0800316 boost::replace_all(commandStr, "'", "");
James Feist3cb5fec2018-01-23 14:41:51 -0800317 cur = (std::find(PASSED_PROBES.begin(), PASSED_PROBES.end(),
318 commandStr) != PASSED_PROBES.end());
319 break;
320 }
321 }
322 }
323 // look on dbus for object
324 else
325 {
326 if (!std::regex_search(probe, match, command))
327 {
328 std::cerr << "dbus probe sytax error " << probe << "\n";
329 return false;
330 }
331 std::string commandStr = *(match.begin() + 1);
332 // convert single ticks and single slashes into legal json
James Feist3f8a2782018-02-12 09:24:42 -0800333 boost::replace_all(commandStr, "'", R"(")");
334 boost::replace_all(commandStr, R"(\)", R"(\\)");
James Feist3cb5fec2018-01-23 14:41:51 -0800335 auto json = nlohmann::json::parse(commandStr, nullptr, false);
336 if (json.is_discarded())
337 {
338 std::cerr << "dbus command sytax error " << commandStr << "\n";
339 return false;
340 }
341 // we can match any (string, variant) property. (string, string)
342 // does a regex
343 std::map<std::string, nlohmann::json> dbusProbeMap =
344 json.get<std::map<std::string, nlohmann::json>>();
345 auto findStart = probe.find("(");
346 if (findStart == std::string::npos)
347 {
348 return false;
349 }
350 std::string probeInterface = probe.substr(0, findStart);
351 cur =
352 probeDbus(probeInterface, dbusProbeMap, foundDevs, foundProbe);
353 }
354
355 // some functions like AND and OR only take affect after the
356 // fact
357 switch (lastCommand)
358 {
359 case probe_type_codes::AND:
360 ret = cur && ret;
361 break;
362 case probe_type_codes::OR:
363 ret = cur || ret;
364 break;
365 default:
366 ret = cur;
367 break;
368 }
369 lastCommand = probeType != PROBE_TYPES.end()
370 ? probeType->second
371 : probe_type_codes::FALSE_T;
372
373 if (!foundProbe)
374 {
375 std::cerr << "Illegal probe type " << probe << "\n";
376 return false;
377 }
378 }
379
380 // probe passed, but empty device
381 // todo: should this be done in main?
382 if (ret && foundDevs.size() == 0)
383 {
384 foundDevs.emplace_back(
385 boost::container::flat_map<std::string, dbus::dbus_variant>());
386 }
387 return ret;
388}
389
James Feist1b2e2242018-01-30 13:45:19 -0800390// this function is temporary, no need to have once dbus is solified.
391void writeJsonFiles(nlohmann::json &systemConfiguration)
392{
393 std::experimental::filesystem::create_directory(OUTPUT_DIR);
394 std::ofstream output(std::string(OUTPUT_DIR) + "system.json");
395 output << systemConfiguration.dump(4);
396 output.close();
397
398 auto flat = nlohmann::json::array();
399 for (auto &pair : nlohmann::json::iterator_wrapper(systemConfiguration))
400 {
401 auto value = pair.value();
402 auto exposes = value.find("exposes");
403 if (exposes != value.end())
404 {
405 for (auto &item : *exposes)
406 {
407 flat.push_back(item);
408 }
409 }
410 }
411 output = std::ofstream(std::string(OUTPUT_DIR) + "flattened.json");
412 output << flat.dump(4);
413 output.close();
414}
415// adds simple json types to interface's properties
416void populateInterfaceFromJson(dbus::DbusInterface *iface, nlohmann::json dict,
417 dbus::DbusObjectServer &objServer)
418{
419 std::vector<std::pair<std::string, dbus::dbus_variant>> properties;
420 static size_t flushCount = 0;
421
422 for (auto &dictPair : nlohmann::json::iterator_wrapper(dict))
423 {
424 switch (dictPair.value().type())
425 {
426 case (nlohmann::json::value_t::boolean):
427 {
428 properties.emplace_back(std::string(dictPair.key()),
429 dictPair.value().get<bool>());
430 break;
431 }
432 case (nlohmann::json::value_t::number_integer):
433 {
434 properties.emplace_back(std::string(dictPair.key()),
435 dictPair.value().get<int64_t>());
436 break;
437 }
438 case (nlohmann::json::value_t::number_unsigned):
439 {
440 properties.emplace_back(std::string(dictPair.key()),
441 dictPair.value().get<uint64_t>());
442 break;
443 }
444 case (nlohmann::json::value_t::number_float):
445 {
446 properties.emplace_back(std::string(dictPair.key()),
447 dictPair.value().get<float>());
448 break;
449 }
450 case (nlohmann::json::value_t::string):
451 {
452 properties.emplace_back(std::string(dictPair.key()),
453 dictPair.value().get<std::string>());
454 break;
455 }
456 }
457 }
458 if (!properties.empty())
459 {
460 iface->set_properties(properties);
461
462 // flush the queue after adding an amount of properties so we don't hang
James Feistc95cb142018-02-26 10:41:42 -0800463 if (flushCount++ > PROPERTIES_CHANGED_UNTIL_FLUSH_COUNT)
James Feist1b2e2242018-01-30 13:45:19 -0800464 {
465 objServer.flush();
466 flushCount = 0;
467 }
468 }
469}
470
471void postToDbus(const nlohmann::json &systemConfiguration,
James Feist75fdeeb2018-02-20 14:26:16 -0800472 dbus::DbusObjectServer &objServer)
473
James Feist1b2e2242018-01-30 13:45:19 -0800474{
475 for (auto &boardPair :
476 nlohmann::json::iterator_wrapper(systemConfiguration))
477 {
478 std::string boardKey = boardPair.key();
479 auto boardValues = boardPair.value();
480 auto findBoardType = boardValues.find("type");
481 std::string boardType;
482 if (findBoardType != boardValues.end() &&
483 findBoardType->type() == nlohmann::json::value_t::string)
484 {
485 boardType = findBoardType->get<std::string>();
486 std::regex_replace(boardType.begin(), boardType.begin(),
487 boardType.end(), ILLEGAL_DBUS_REGEX, "_");
488 }
489 else
490 {
491 std::cerr << "Unable to find type for " << boardKey
492 << " reverting to Chassis.\n";
493 boardType = "Chassis";
494 }
495
496 std::regex_replace(boardKey.begin(), boardKey.begin(), boardKey.end(),
497 ILLEGAL_DBUS_REGEX, "_");
498 std::string boardName =
499 "/xyz/openbmc_project/Inventory/Item/" + boardType + "/" + boardKey;
500 auto boardObject = objServer.add_object(boardName);
501
502 auto boardIface = boardObject->add_interface(
503 "xyz.openbmc_project.Configuration." + boardType);
504 populateInterfaceFromJson(boardIface.get(), boardValues, objServer);
505 auto exposes = boardValues.find("exposes");
506 if (exposes == boardValues.end())
507 {
508 continue;
509 }
510 for (auto &item : *exposes)
511 {
512 auto findName = item.find("name");
513 if (findName == item.end())
514 {
515 std::cerr << "cannot find name in field " << item << "\n";
516 continue;
517 }
518 auto findStatus = item.find("status");
519 // if status is not found it is assumed to be status = 'okay'
520 if (findStatus != item.end())
521 {
522 if (*findStatus == "disabled")
523 {
524 continue;
525 }
526 }
527 auto findType = item.find("type");
528 std::string itemType;
529 if (findType != item.end())
530 {
531 itemType = findType->get<std::string>();
532 std::regex_replace(itemType.begin(), itemType.begin(),
533 itemType.end(), ILLEGAL_DBUS_REGEX, "_");
534 }
535 else
536 {
537 itemType = "unknown";
538 }
539 std::string itemName = findName->get<std::string>();
540 std::regex_replace(itemName.begin(), itemName.begin(),
541 itemName.end(), ILLEGAL_DBUS_REGEX, "_");
542 auto itemObject = objServer.add_object(boardName + "/" + itemName);
543 auto itemIface = itemObject->add_interface(
544 "xyz.openbmc_project.Configuration." + itemType);
545
546 populateInterfaceFromJson(itemIface.get(), item, objServer);
547
548 for (auto &objectPair : nlohmann::json::iterator_wrapper(item))
549 {
550 if (objectPair.value().type() ==
551 nlohmann::json::value_t::object)
552 {
553 auto objectIface = itemObject->add_interface(
554 "xyz.openbmc_project.Configuration." + itemType + "." +
555 objectPair.key());
556 populateInterfaceFromJson(objectIface.get(),
557 objectPair.value(), objServer);
558 }
559 else if (objectPair.value().type() ==
560 nlohmann::json::value_t::array)
561 {
562 size_t index = 0;
563 for (auto &arrayItem : objectPair.value())
564 {
565 if (arrayItem.type() != nlohmann::json::value_t::object)
566 {
567 std::cerr << "dbus format error" << arrayItem
568 << "\n";
569 break;
570 }
571 auto objectIface = itemObject->add_interface(
572 "xyz.openbmc_project.Configuration." + itemType +
573 "." + objectPair.key() + "." +
574 std::to_string(index));
575 index++;
576 populateInterfaceFromJson(objectIface.get(), arrayItem,
577 objServer);
578 }
579 }
580 }
581 }
582 }
583}
584
585// finds the template character (currently set to $) and replaces the value with
586// the field found in a dbus object i.e. $ADDRESS would get populated with the
587// ADDRESS field from a object on dbus
588void templateCharReplace(
589 nlohmann::json::iterator &keyPair,
590 const boost::container::flat_map<std::string, dbus::dbus_variant>
591 &foundDevice,
592 size_t &foundDeviceIdx)
593{
594 if (keyPair.value().type() != nlohmann::json::value_t::string)
595 {
596 return;
597 }
598
599 std::string value = keyPair.value();
600 if (value.find(TEMPLATE_CHAR) != std::string::npos)
601 {
602 std::string templateValue = value;
603
604 templateValue.erase(0, 1); // remove template character
605
606 // special case index
607 if ("index" == templateValue)
608 {
609 keyPair.value() = foundDeviceIdx;
610 }
611 else
612 {
613 std::string subsitute;
614 for (auto &foundDevicePair : foundDevice)
615 {
616 if (boost::iequals(foundDevicePair.first, templateValue))
617 {
618 // convert value to string
619 // respresentation
620 subsitute = boost::apply_visitor(
621 [](const auto &x) {
622 return boost::lexical_cast<std::string>(x);
623 },
624 foundDevicePair.second);
625 break;
626 }
627 }
628 if (!subsitute.size())
629 {
630 std::cerr << "could not find symbol " << templateValue << "\n";
631 }
632 else
633 {
634 keyPair.value() = subsitute;
635 }
636 }
637 }
638}
639
James Feist75fdeeb2018-02-20 14:26:16 -0800640bool findJsonFiles(std::vector<nlohmann::json> &configurations)
James Feist3cb5fec2018-01-23 14:41:51 -0800641{
642 // find configuration files
643 std::vector<fs::path> jsonPaths;
644 if (!find_files(fs::path(CONFIGURATION_DIR), R"(.*\.json)", jsonPaths, 0))
645 {
646 std::cerr << "Unable to find any configuration files in "
647 << CONFIGURATION_DIR << "\n";
James Feist75fdeeb2018-02-20 14:26:16 -0800648 return false;
James Feist3cb5fec2018-01-23 14:41:51 -0800649 }
James Feist3cb5fec2018-01-23 14:41:51 -0800650 for (auto &jsonPath : jsonPaths)
651 {
652 std::ifstream jsonStream(jsonPath.c_str());
653 if (!jsonStream.good())
654 {
655 std::cerr << "unable to open " << jsonPath.string() << "\n";
656 continue;
657 }
658 auto data = nlohmann::json::parse(jsonStream, nullptr, false);
659 if (data.is_discarded())
660 {
661 std::cerr << "syntax error in " << jsonPath.string() << "\n";
662 continue;
663 }
664 if (data.type() == nlohmann::json::value_t::array)
665 {
666 for (auto &d : data)
667 {
668 configurations.emplace_back(d);
669 }
670 }
671 else
672 {
673 configurations.emplace_back(data);
674 }
675 }
James Feist75fdeeb2018-02-20 14:26:16 -0800676}
James Feist3cb5fec2018-01-23 14:41:51 -0800677
James Feist75fdeeb2018-02-20 14:26:16 -0800678bool rescan(nlohmann::json &systemConfiguration)
679{
680 std::vector<nlohmann::json> configurations;
681 if (!findJsonFiles(configurations))
682 {
683 false;
684 }
685 // preprocess already passed configurations and missing fields
686 if (systemConfiguration.size())
687 {
688 for (auto it = configurations.begin(); it != configurations.end();)
689 {
690 auto findName = it->find("name");
691 if (findName == it->end())
692 {
693 std::cerr << "configuration missing name field " << *it << "\n";
694 it = configurations.erase(it);
695 continue;
696 }
697 else if (findName->type() != nlohmann::json::value_t::string)
698 {
699 std::cerr << "name field must be a string " << *findName
700 << "\n";
701 it = configurations.erase(it);
702 continue;
703 }
704 auto findAlreadyFound =
705 systemConfiguration.find(findName->get<std::string>());
706 if (findAlreadyFound != systemConfiguration.end())
707 {
708 it = configurations.erase(it);
709 continue;
710 }
711 // TODO: add in tags to determine if configuration should be
712 // refreshed on AC / DC / Always.
713 it++;
714 }
715 }
716
717 // probe until no probes pass
James Feist3cb5fec2018-01-23 14:41:51 -0800718 bool probePassed = true;
James Feist3cb5fec2018-01-23 14:41:51 -0800719 while (probePassed)
720 {
721 probePassed = false;
722 for (auto it = configurations.begin(); it != configurations.end();)
723 {
724 bool eraseConfig = false;
James Feist1b2e2242018-01-30 13:45:19 -0800725 auto findProbe = it->find("probe");
726 auto findName = it->find("name");
James Feist3cb5fec2018-01-23 14:41:51 -0800727
James Feist1b2e2242018-01-30 13:45:19 -0800728 nlohmann::json probeCommand;
729 // check for poorly formatted fields, probe must be an array
730 if (findProbe == it->end())
James Feist3cb5fec2018-01-23 14:41:51 -0800731 {
732 std::cerr << "configuration file missing probe:\n " << *it
733 << "\n";
734 eraseConfig = true;
735 }
James Feist1b2e2242018-01-30 13:45:19 -0800736 else if ((*findProbe).type() != nlohmann::json::value_t::array)
James Feist3cb5fec2018-01-23 14:41:51 -0800737 {
738 probeCommand = nlohmann::json::array();
739 probeCommand.push_back(*findProbe);
740 }
741 else
742 {
743 probeCommand = *findProbe;
744 }
James Feist1b2e2242018-01-30 13:45:19 -0800745
746 if (findName == it->end())
747 {
748 std::cerr << "configuration file missing name:\n " << *it
749 << "\n";
750 eraseConfig = true;
751 }
752
James Feist3cb5fec2018-01-23 14:41:51 -0800753 std::vector<
754 boost::container::flat_map<std::string, dbus::dbus_variant>>
755 foundDevices;
James Feist1b2e2242018-01-30 13:45:19 -0800756 if (!eraseConfig && probe(probeCommand, foundDevices))
James Feist3cb5fec2018-01-23 14:41:51 -0800757 {
758 eraseConfig = true;
759 probePassed = true;
James Feist7b7e4e82018-01-24 14:56:00 -0800760 std::string name = *findName;
761 PASSED_PROBES.push_back(name);
James Feist3cb5fec2018-01-23 14:41:51 -0800762
763 size_t foundDeviceIdx = 0;
764
765 for (auto &foundDevice : foundDevices)
766 {
James Feist1b2e2242018-01-30 13:45:19 -0800767 for (auto keyPair = it->begin(); keyPair != it->end();
768 keyPair++)
James Feist3cb5fec2018-01-23 14:41:51 -0800769 {
James Feist1b2e2242018-01-30 13:45:19 -0800770 templateCharReplace(keyPair, foundDevice,
771 foundDeviceIdx);
772 }
773 auto findExpose = it->find("exposes");
774 if (findExpose == it->end())
775 {
James Feist3cb5fec2018-01-23 14:41:51 -0800776 continue;
777 }
778 for (auto &expose : *findExpose)
779 {
780 for (auto keyPair = expose.begin();
781 keyPair != expose.end(); keyPair++)
782 {
James Feist1b2e2242018-01-30 13:45:19 -0800783
James Feist3cb5fec2018-01-23 14:41:51 -0800784 // fill in template characters with devices
785 // found
James Feist1b2e2242018-01-30 13:45:19 -0800786 templateCharReplace(keyPair, foundDevice,
787 foundDeviceIdx);
788 // special case bind
789 if (boost::starts_with(keyPair.key(), "bind_"))
James Feist3cb5fec2018-01-23 14:41:51 -0800790 {
James Feist1b2e2242018-01-30 13:45:19 -0800791 if (keyPair.value().type() !=
792 nlohmann::json::value_t::string)
James Feist3cb5fec2018-01-23 14:41:51 -0800793 {
James Feist1b2e2242018-01-30 13:45:19 -0800794 std::cerr
795 << "bind_ value must be of type string "
796 << keyPair.key() << "\n";
797 continue;
James Feist3cb5fec2018-01-23 14:41:51 -0800798 }
James Feist1b2e2242018-01-30 13:45:19 -0800799 bool foundBind = false;
800 std::string bind =
801 keyPair.key().substr(sizeof("bind_") - 1);
802 for (auto &configurationPair :
803 nlohmann::json::iterator_wrapper(
804 systemConfiguration))
James Feist3cb5fec2018-01-23 14:41:51 -0800805 {
James Feist1b2e2242018-01-30 13:45:19 -0800806
807 auto configListFind =
808 configurationPair.value().find(
809 "exposes");
810
811 if (configListFind ==
812 configurationPair.value().end() ||
813 configListFind->type() !=
814 nlohmann::json::value_t::array)
James Feist3cb5fec2018-01-23 14:41:51 -0800815 {
James Feist1b2e2242018-01-30 13:45:19 -0800816 continue;
817 }
818 for (auto &exposedObject : *configListFind)
819 {
820 std::string foundObjectName =
821 (exposedObject)["name"];
822 if (boost::iequals(
823 foundObjectName,
824 keyPair.value()
825 .get<std::string>()))
James Feist3cb5fec2018-01-23 14:41:51 -0800826 {
James Feistc95cb142018-02-26 10:41:42 -0800827 exposedObject["status"] = "okay";
James Feist1b2e2242018-01-30 13:45:19 -0800828 expose[bind] = exposedObject;
James Feist1b2e2242018-01-30 13:45:19 -0800829
830 foundBind = true;
James Feist3cb5fec2018-01-23 14:41:51 -0800831 break;
832 }
833 }
James Feist1b2e2242018-01-30 13:45:19 -0800834 if (foundBind)
James Feist3cb5fec2018-01-23 14:41:51 -0800835 {
James Feist1b2e2242018-01-30 13:45:19 -0800836 break;
James Feist3cb5fec2018-01-23 14:41:51 -0800837 }
838 }
James Feist1b2e2242018-01-30 13:45:19 -0800839 if (!foundBind)
840 {
841 std::cerr << "configuration file "
842 "dependency error, "
843 "could not find bind "
844 << keyPair.value() << "\n";
845 }
James Feist3cb5fec2018-01-23 14:41:51 -0800846 }
847 }
848 }
James Feist3cb5fec2018-01-23 14:41:51 -0800849 }
James Feist1b2e2242018-01-30 13:45:19 -0800850 systemConfiguration[name] = (*it);
851 foundDeviceIdx++;
James Feist3cb5fec2018-01-23 14:41:51 -0800852 }
853
854 if (eraseConfig)
855 {
856 it = configurations.erase(it);
857 }
858 else
859 {
860 it++;
861 }
862 }
863 }
James Feist75fdeeb2018-02-20 14:26:16 -0800864}
865
866void propertiesChangedCallback(
867 std::vector<std::pair<std::unique_ptr<dbus::match>,
868 std::shared_ptr<dbus::filter>>> &dbusMatches,
James Feist4131aea2018-03-09 09:47:30 -0800869 nlohmann::json &systemConfiguration, dbus::DbusObjectServer &objServer,
870 std::shared_ptr<dbus::filter> dbusFilter)
James Feist75fdeeb2018-02-20 14:26:16 -0800871{
872 static std::future<void> future;
James Feist4131aea2018-03-09 09:47:30 -0800873 static std::atomic_bool threadRunning(false);
874 static std::atomic_bool pendingCallback(false);
James Feist75fdeeb2018-02-20 14:26:16 -0800875 bool notRunning = false;
876 if (threadRunning.compare_exchange_strong(notRunning, true))
877 {
878 future = std::async(std::launch::async, [&] {
James Feistc95cb142018-02-26 10:41:42 -0800879
James Feist4131aea2018-03-09 09:47:30 -0800880 do
881 {
882 std::this_thread::sleep_for(std::chrono::seconds(
883 SLEEP_AFTER_PROPERTIES_CHANGE_SECONDS));
884 auto oldConfiguration = systemConfiguration;
885 DBUS_PROBE_OBJECTS.clear();
886 pendingCallback = false;
887 rescan(systemConfiguration);
888 auto newConfiguration = systemConfiguration;
889 for (auto it = newConfiguration.begin();
890 it != newConfiguration.end();)
891 {
892 auto findKey = oldConfiguration.find(it.key());
893 if (findKey != oldConfiguration.end())
894 {
895 it = newConfiguration.erase(it);
896 }
897 else
898 {
899 it++;
900 }
901 }
902
903 registerCallbacks(dbusMatches, systemConfiguration, objServer);
904 // todo: for now, only add new configurations, unload to come
905 // later
906 // unloadOverlays();
907 loadOverlays(newConfiguration);
908 // this line to be removed in future
909 writeJsonFiles(systemConfiguration);
910 // only post new items to bus for now
911 postToDbus(newConfiguration, objServer);
912 } while (pendingCallback);
James Feist75fdeeb2018-02-20 14:26:16 -0800913 threadRunning = false;
914 });
915 }
James Feist4131aea2018-03-09 09:47:30 -0800916 else
917 {
918 pendingCallback = true;
919 }
James Feist75fdeeb2018-02-20 14:26:16 -0800920 if (dbusFilter != nullptr)
921 {
922 dbusFilter->async_dispatch([&, dbusFilter](boost::system::error_code ec,
923 dbus::message) {
924 if (ec)
925 {
926 std::cerr << "properties changed callback error " << ec << "\n";
927 }
James Feist4131aea2018-03-09 09:47:30 -0800928 propertiesChangedCallback(dbusMatches, systemConfiguration,
929 objServer, dbusFilter);
James Feist75fdeeb2018-02-20 14:26:16 -0800930 });
931 }
932}
933
934void registerCallbacks(
935 std::vector<std::pair<std::unique_ptr<dbus::match>,
936 std::shared_ptr<dbus::filter>>> &dbusMatches,
James Feist4131aea2018-03-09 09:47:30 -0800937 nlohmann::json &systemConfiguration, dbus::DbusObjectServer &objServer)
James Feist75fdeeb2018-02-20 14:26:16 -0800938{
939 static boost::container::flat_set<std::string> watchedObjects;
940
941 for (const auto &objectMap : DBUS_PROBE_OBJECTS)
942 {
943 auto findObject = watchedObjects.find(objectMap.first);
944 if (findObject != watchedObjects.end())
945 {
946 continue;
947 }
948 // this creates a filter for properties changed for any new probe type
949 auto propertyChange = std::make_unique<dbus::match>(
950 SYSTEM_BUS,
951 "type='signal',member='PropertiesChanged',arg0='" +
952 objectMap.first + "'");
953 auto filter =
954 std::make_shared<dbus::filter>(SYSTEM_BUS, [](dbus::message &m) {
955 auto member = m.get_member();
956 return member == "PropertiesChanged";
957 });
958
959 filter->async_dispatch([&, filter](boost::system::error_code ec,
960 dbus::message) {
961 if (ec)
962 {
963 std::cerr << "register callbacks callback error " << ec << "\n";
964 }
James Feist4131aea2018-03-09 09:47:30 -0800965 propertiesChangedCallback(dbusMatches, systemConfiguration,
966 objServer, filter);
James Feist75fdeeb2018-02-20 14:26:16 -0800967 });
968 dbusMatches.emplace_back(std::move(propertyChange), filter);
969 }
970}
971
972int main(int argc, char **argv)
973{
974 // setup connection to dbus
975 boost::asio::io_service io;
976 SYSTEM_BUS = std::make_shared<dbus::connection>(io, dbus::bus::system);
James Feist4131aea2018-03-09 09:47:30 -0800977
James Feist75fdeeb2018-02-20 14:26:16 -0800978 dbus::DbusObjectServer objServer(SYSTEM_BUS);
979 SYSTEM_BUS->request_name("xyz.openbmc_project.EntityManager");
James Feist75fdeeb2018-02-20 14:26:16 -0800980 std::vector<
981 std::pair<std::unique_ptr<dbus::match>, std::shared_ptr<dbus::filter>>>
982 dbusMatches;
James Feist4131aea2018-03-09 09:47:30 -0800983
984 nlohmann::json systemConfiguration = nlohmann::json::object();
985 auto iface = std::make_shared<dbus::DbusInterface>(
986 "xyz.openbmc_project.EntityManager", SYSTEM_BUS);
987 io.post([&]() {
988 unloadAllOverlays();
989 propertiesChangedCallback(dbusMatches, systemConfiguration, objServer,
990 nullptr);
991 auto object = std::make_shared<dbus::DbusObject>(
992 SYSTEM_BUS, "/xyz/openbmc_project/EntityManager");
993 objServer.register_object(object);
994
995 object->register_interface(iface);
996
997 });
998
999 // to keep reference to the match / filter objects so they don't get
1000 // destroyed
1001
James Feist75fdeeb2018-02-20 14:26:16 -08001002 iface->register_method("ReScan", [&]() {
James Feist4131aea2018-03-09 09:47:30 -08001003 propertiesChangedCallback(dbusMatches, systemConfiguration, objServer,
1004 nullptr);
James Feist75fdeeb2018-02-20 14:26:16 -08001005 return std::tuple<>(); // this is a bug in boost-dbus, needs some sort
1006 // of return
1007 });
James Feist75fdeeb2018-02-20 14:26:16 -08001008
James Feist1b2e2242018-01-30 13:45:19 -08001009 io.run();
James Feist3cb5fec2018-01-23 14:41:51 -08001010
1011 return 0;
James Feist75fdeeb2018-02-20 14:26:16 -08001012}