blob: c9d0c1585aaad99cf4499a5b96ca353a9d660818 [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{
James Feist494155a2018-03-14 16:23:24 -0700103 // todo: this is only static because the mapper is unreliable as of today
104 static boost::container::flat_map<std::string,
105 boost::container::flat_set<std::string>>
106 connections;
James Feist3cb5fec2018-01-23 14:41:51 -0800107 // find all connections in the mapper that expose a specific type
108 static const dbus::endpoint mapper("xyz.openbmc_project.ObjectMapper",
109 "/xyz/openbmc_project/object_mapper",
110 "xyz.openbmc_project.ObjectMapper",
111 "GetSubTree");
112 dbus::message getMap = dbus::message::new_call(mapper);
113 std::vector<std::string> objects = {interface};
114 if (!getMap.pack("", MAX_MAPPER_DEPTH, objects))
115 {
116 std::cerr << "Pack Failed GetSensorSubtree\n";
117 return false;
118 }
James Feist494155a2018-03-14 16:23:24 -0700119
James Feist3cb5fec2018-01-23 14:41:51 -0800120 GetSubTreeType interfaceSubtree;
James Feist494155a2018-03-14 16:23:24 -0700121 size_t retries = 1;
122 bool unpackStatus = false;
123 // the mapper seems to hang occasionally, not responding, so we give it a
124 // timeout and retries
125 do
James Feist3cb5fec2018-01-23 14:41:51 -0800126 {
James Feist494155a2018-03-14 16:23:24 -0700127 dbus::message getMapResp =
128 connection->send(getMap, std::chrono::seconds(2));
129 unpackStatus = getMapResp.unpack(interfaceSubtree);
130
131 } while (retries-- && !unpackStatus);
132
133 auto &interfaceConnections = connections[interface];
134 if (!unpackStatus)
James Feist3cb5fec2018-01-23 14:41:51 -0800135 {
James Feist494155a2018-03-14 16:23:24 -0700136 std::cerr << "Error communicating to mapper, using cached data if "
137 "available\n";
138 if (interfaceConnections.empty())
James Feist3cb5fec2018-01-23 14:41:51 -0800139 {
James Feist494155a2018-03-14 16:23:24 -0700140 return false;
141 }
142 }
143
144 if (unpackStatus)
145 {
146 interfaceConnections.clear();
147 for (auto &object : interfaceSubtree)
148 {
149 for (auto &connPair : object.second)
150 {
151 interfaceConnections.insert(connPair.first);
152 }
James Feist3cb5fec2018-01-23 14:41:51 -0800153 }
154 }
155 // iterate through the connections, adding creating individual device
156 // dictionaries
James Feist494155a2018-03-14 16:23:24 -0700157 for (auto &conn : interfaceConnections)
James Feist3cb5fec2018-01-23 14:41:51 -0800158 {
159 auto managedObj =
160 dbus::endpoint(conn, "/", "org.freedesktop.DBus.ObjectManager",
161 "GetManagedObjects");
162 dbus::message getManagedObj = dbus::message::new_call(managedObj);
James Feist3cb5fec2018-01-23 14:41:51 -0800163 ManagedObjectType managedInterface;
James Feist494155a2018-03-14 16:23:24 -0700164 retries = 1;
165 unpackStatus = false;
166 do
167 {
168 dbus::message getManagedObjResp = connection->send(getManagedObj);
169 unpackStatus = getManagedObjResp.unpack(managedInterface);
170 } while (retries-- && !unpackStatus);
171
172 if (!unpackStatus)
James Feist3cb5fec2018-01-23 14:41:51 -0800173 {
174 std::cerr << "error getting managed object for device " << conn
175 << "\n";
176 continue;
177 }
178 for (auto &interfaceManagedObj : managedInterface)
179 {
180 auto ifaceObjFind = interfaceManagedObj.second.find(interface);
181 if (ifaceObjFind != interfaceManagedObj.second.end())
182 {
183 interfaceDevices.emplace_back(ifaceObjFind->second);
184 }
185 }
186 }
187 return true;
188}
189
190// probes interface dictionary for a key with a value that matches a regex
191bool probeDbus(
192 const std::string &interface,
193 const std::map<std::string, nlohmann::json> &matches,
194 std::vector<boost::container::flat_map<std::string, dbus::dbus_variant>>
195 &devices,
196 bool &foundProbe)
197{
198 auto &dbusObject = DBUS_PROBE_OBJECTS[interface];
199 if (dbusObject.empty())
200 {
201 if (!findDbusObjects(SYSTEM_BUS, dbusObject, interface))
202 {
203 std::cerr << "Found no dbus objects with interface "
204 << interface << "\n";
205 foundProbe = false;
206 return false;
207 }
208 }
209 foundProbe = true;
210
211 bool foundMatch = false;
212 for (auto &device : dbusObject)
213 {
214 bool deviceMatches = true;
215 for (auto &match : matches)
216 {
217 auto deviceValue = device.find(match.first);
218 if (deviceValue != device.end())
219 {
220 switch (match.second.type())
221 {
222 case nlohmann::json::value_t::string:
223 {
224 std::regex search(match.second.get<std::string>());
225 std::smatch match;
226
227 // convert value to string respresentation
228 std::string probeValue = boost::apply_visitor(
229 [](const auto &x) {
230 return boost::lexical_cast<std::string>(x);
231 },
232 deviceValue->second);
233 if (!std::regex_search(probeValue, match, search))
234 {
235 deviceMatches = false;
236 break;
237 }
238 break;
239 }
240 case nlohmann::json::value_t::boolean:
241 case nlohmann::json::value_t::number_unsigned:
242 {
243 unsigned int probeValue = boost::apply_visitor(
244 VariantToUnsignedIntVisitor(), deviceValue->second);
245
246 if (probeValue != match.second.get<unsigned int>())
247 {
248 deviceMatches = false;
249 }
250 break;
251 }
252 case nlohmann::json::value_t::number_integer:
253 {
254 int probeValue = boost::apply_visitor(VariantToIntVisitor(),
255 deviceValue->second);
256
257 if (probeValue != match.second.get<int>())
258 {
259 deviceMatches = false;
260 }
261 break;
262 }
263 case nlohmann::json::value_t::number_float:
264 {
265 float probeValue = boost::apply_visitor(
266 VariantToFloatVisitor(), deviceValue->second);
267
268 if (probeValue != match.second.get<float>())
269 {
270 deviceMatches = false;
271 }
272 break;
273 }
274 }
275 }
276 else
277 {
278 deviceMatches = false;
279 break;
280 }
281 }
282 if (deviceMatches)
283 {
284 devices.emplace_back(
285 boost::container::flat_map<std::string, dbus::dbus_variant>(
286 device));
287 foundMatch = true;
288 deviceMatches = false; // for next iteration
289 }
290 }
291 return foundMatch;
292}
293
294// default probe entry point, iterates a list looking for specific types to
295// call specific probe functions
296bool probe(
297 const std::vector<std::string> probeCommand,
298 std::vector<boost::container::flat_map<std::string, dbus::dbus_variant>>
299 &foundDevs)
300{
301 const static std::regex command(R"(\((.*)\))");
302 std::smatch match;
303 bool ret = false;
James Feist6bd2a022018-03-13 12:30:58 -0700304 bool matchOne = false;
James Feist3cb5fec2018-01-23 14:41:51 -0800305 bool cur = true;
306 probe_type_codes lastCommand = probe_type_codes::FALSE_T;
307
308 for (auto &probe : probeCommand)
309 {
310 bool foundProbe = false;
311 boost::container::flat_map<const char *, probe_type_codes,
312 cmp_str>::const_iterator probeType;
313
314 for (probeType = PROBE_TYPES.begin(); probeType != PROBE_TYPES.end();
315 probeType++)
316 {
317 if (probe.find(probeType->first) != std::string::npos)
318 {
319 foundProbe = true;
320 break;
321 }
322 }
323 if (foundProbe)
324 {
325 switch (probeType->second)
326 {
327 case probe_type_codes::FALSE_T:
328 {
329 return false; // todo, actually evaluate?
330 break;
331 }
332 case probe_type_codes::TRUE_T:
333 {
334 return true; // todo, actually evaluate?
335 break;
336 }
James Feist6bd2a022018-03-13 12:30:58 -0700337 case probe_type_codes::MATCH_ONE:
338 {
339 // set current value to last, this probe type shouldn't affect
340 // the outcome
341 cur = ret;
342 matchOne = true;
343 break;
344 }
James Feist3cb5fec2018-01-23 14:41:51 -0800345 /*case probe_type_codes::AND:
346 break;
347 case probe_type_codes::OR:
348 break;
349 // these are no-ops until the last command switch
350 */
351 case probe_type_codes::FOUND:
352 {
353 if (!std::regex_search(probe, match, command))
354 {
355 std::cerr << "found probe sytax error " << probe << "\n";
356 return false;
357 }
358 std::string commandStr = *(match.begin() + 1);
James Feist3f8a2782018-02-12 09:24:42 -0800359 boost::replace_all(commandStr, "'", "");
James Feist3cb5fec2018-01-23 14:41:51 -0800360 cur = (std::find(PASSED_PROBES.begin(), PASSED_PROBES.end(),
361 commandStr) != PASSED_PROBES.end());
362 break;
363 }
364 }
365 }
366 // look on dbus for object
367 else
368 {
369 if (!std::regex_search(probe, match, command))
370 {
371 std::cerr << "dbus probe sytax error " << probe << "\n";
372 return false;
373 }
374 std::string commandStr = *(match.begin() + 1);
375 // convert single ticks and single slashes into legal json
James Feist3f8a2782018-02-12 09:24:42 -0800376 boost::replace_all(commandStr, "'", R"(")");
377 boost::replace_all(commandStr, R"(\)", R"(\\)");
James Feist3cb5fec2018-01-23 14:41:51 -0800378 auto json = nlohmann::json::parse(commandStr, nullptr, false);
379 if (json.is_discarded())
380 {
381 std::cerr << "dbus command sytax error " << commandStr << "\n";
382 return false;
383 }
384 // we can match any (string, variant) property. (string, string)
385 // does a regex
386 std::map<std::string, nlohmann::json> dbusProbeMap =
387 json.get<std::map<std::string, nlohmann::json>>();
388 auto findStart = probe.find("(");
389 if (findStart == std::string::npos)
390 {
391 return false;
392 }
393 std::string probeInterface = probe.substr(0, findStart);
394 cur =
395 probeDbus(probeInterface, dbusProbeMap, foundDevs, foundProbe);
396 }
397
398 // some functions like AND and OR only take affect after the
399 // fact
400 switch (lastCommand)
401 {
402 case probe_type_codes::AND:
403 ret = cur && ret;
404 break;
405 case probe_type_codes::OR:
406 ret = cur || ret;
407 break;
408 default:
409 ret = cur;
410 break;
411 }
412 lastCommand = probeType != PROBE_TYPES.end()
413 ? probeType->second
414 : probe_type_codes::FALSE_T;
415
416 if (!foundProbe)
417 {
418 std::cerr << "Illegal probe type " << probe << "\n";
419 return false;
420 }
421 }
422
423 // probe passed, but empty device
424 // todo: should this be done in main?
425 if (ret && foundDevs.size() == 0)
426 {
427 foundDevs.emplace_back(
428 boost::container::flat_map<std::string, dbus::dbus_variant>());
429 }
James Feist6bd2a022018-03-13 12:30:58 -0700430 if (matchOne && foundDevs.size() > 1)
431 {
432 foundDevs.erase(foundDevs.begin() + 1, foundDevs.end());
433 }
James Feist3cb5fec2018-01-23 14:41:51 -0800434 return ret;
435}
436
James Feist1b2e2242018-01-30 13:45:19 -0800437// this function is temporary, no need to have once dbus is solified.
438void writeJsonFiles(nlohmann::json &systemConfiguration)
439{
440 std::experimental::filesystem::create_directory(OUTPUT_DIR);
441 std::ofstream output(std::string(OUTPUT_DIR) + "system.json");
442 output << systemConfiguration.dump(4);
443 output.close();
444
445 auto flat = nlohmann::json::array();
446 for (auto &pair : nlohmann::json::iterator_wrapper(systemConfiguration))
447 {
448 auto value = pair.value();
449 auto exposes = value.find("exposes");
450 if (exposes != value.end())
451 {
452 for (auto &item : *exposes)
453 {
454 flat.push_back(item);
455 }
456 }
457 }
458 output = std::ofstream(std::string(OUTPUT_DIR) + "flattened.json");
459 output << flat.dump(4);
460 output.close();
461}
462// adds simple json types to interface's properties
463void populateInterfaceFromJson(dbus::DbusInterface *iface, nlohmann::json dict,
464 dbus::DbusObjectServer &objServer)
465{
466 std::vector<std::pair<std::string, dbus::dbus_variant>> properties;
467 static size_t flushCount = 0;
468
469 for (auto &dictPair : nlohmann::json::iterator_wrapper(dict))
470 {
471 switch (dictPair.value().type())
472 {
473 case (nlohmann::json::value_t::boolean):
474 {
475 properties.emplace_back(std::string(dictPair.key()),
476 dictPair.value().get<bool>());
477 break;
478 }
479 case (nlohmann::json::value_t::number_integer):
480 {
481 properties.emplace_back(std::string(dictPair.key()),
482 dictPair.value().get<int64_t>());
483 break;
484 }
485 case (nlohmann::json::value_t::number_unsigned):
486 {
487 properties.emplace_back(std::string(dictPair.key()),
488 dictPair.value().get<uint64_t>());
489 break;
490 }
491 case (nlohmann::json::value_t::number_float):
492 {
493 properties.emplace_back(std::string(dictPair.key()),
494 dictPair.value().get<float>());
495 break;
496 }
497 case (nlohmann::json::value_t::string):
498 {
499 properties.emplace_back(std::string(dictPair.key()),
500 dictPair.value().get<std::string>());
501 break;
502 }
503 }
504 }
505 if (!properties.empty())
506 {
507 iface->set_properties(properties);
508
509 // flush the queue after adding an amount of properties so we don't hang
James Feistc95cb142018-02-26 10:41:42 -0800510 if (flushCount++ > PROPERTIES_CHANGED_UNTIL_FLUSH_COUNT)
James Feist1b2e2242018-01-30 13:45:19 -0800511 {
512 objServer.flush();
513 flushCount = 0;
514 }
515 }
516}
517
518void postToDbus(const nlohmann::json &systemConfiguration,
James Feist75fdeeb2018-02-20 14:26:16 -0800519 dbus::DbusObjectServer &objServer)
520
James Feist1b2e2242018-01-30 13:45:19 -0800521{
522 for (auto &boardPair :
523 nlohmann::json::iterator_wrapper(systemConfiguration))
524 {
525 std::string boardKey = boardPair.key();
526 auto boardValues = boardPair.value();
527 auto findBoardType = boardValues.find("type");
528 std::string boardType;
529 if (findBoardType != boardValues.end() &&
530 findBoardType->type() == nlohmann::json::value_t::string)
531 {
532 boardType = findBoardType->get<std::string>();
533 std::regex_replace(boardType.begin(), boardType.begin(),
534 boardType.end(), ILLEGAL_DBUS_REGEX, "_");
535 }
536 else
537 {
538 std::cerr << "Unable to find type for " << boardKey
539 << " reverting to Chassis.\n";
540 boardType = "Chassis";
541 }
542
543 std::regex_replace(boardKey.begin(), boardKey.begin(), boardKey.end(),
544 ILLEGAL_DBUS_REGEX, "_");
545 std::string boardName =
546 "/xyz/openbmc_project/Inventory/Item/" + boardType + "/" + boardKey;
547 auto boardObject = objServer.add_object(boardName);
548
549 auto boardIface = boardObject->add_interface(
550 "xyz.openbmc_project.Configuration." + boardType);
551 populateInterfaceFromJson(boardIface.get(), boardValues, objServer);
552 auto exposes = boardValues.find("exposes");
553 if (exposes == boardValues.end())
554 {
555 continue;
556 }
557 for (auto &item : *exposes)
558 {
559 auto findName = item.find("name");
560 if (findName == item.end())
561 {
562 std::cerr << "cannot find name in field " << item << "\n";
563 continue;
564 }
565 auto findStatus = item.find("status");
566 // if status is not found it is assumed to be status = 'okay'
567 if (findStatus != item.end())
568 {
569 if (*findStatus == "disabled")
570 {
571 continue;
572 }
573 }
574 auto findType = item.find("type");
575 std::string itemType;
576 if (findType != item.end())
577 {
578 itemType = findType->get<std::string>();
579 std::regex_replace(itemType.begin(), itemType.begin(),
580 itemType.end(), ILLEGAL_DBUS_REGEX, "_");
581 }
582 else
583 {
584 itemType = "unknown";
585 }
586 std::string itemName = findName->get<std::string>();
587 std::regex_replace(itemName.begin(), itemName.begin(),
588 itemName.end(), ILLEGAL_DBUS_REGEX, "_");
589 auto itemObject = objServer.add_object(boardName + "/" + itemName);
590 auto itemIface = itemObject->add_interface(
591 "xyz.openbmc_project.Configuration." + itemType);
592
593 populateInterfaceFromJson(itemIface.get(), item, objServer);
594
595 for (auto &objectPair : nlohmann::json::iterator_wrapper(item))
596 {
597 if (objectPair.value().type() ==
598 nlohmann::json::value_t::object)
599 {
600 auto objectIface = itemObject->add_interface(
601 "xyz.openbmc_project.Configuration." + itemType + "." +
602 objectPair.key());
603 populateInterfaceFromJson(objectIface.get(),
604 objectPair.value(), objServer);
605 }
606 else if (objectPair.value().type() ==
607 nlohmann::json::value_t::array)
608 {
609 size_t index = 0;
610 for (auto &arrayItem : objectPair.value())
611 {
612 if (arrayItem.type() != nlohmann::json::value_t::object)
613 {
614 std::cerr << "dbus format error" << arrayItem
615 << "\n";
616 break;
617 }
618 auto objectIface = itemObject->add_interface(
619 "xyz.openbmc_project.Configuration." + itemType +
620 "." + objectPair.key() + "." +
621 std::to_string(index));
622 index++;
623 populateInterfaceFromJson(objectIface.get(), arrayItem,
624 objServer);
625 }
626 }
627 }
628 }
629 }
630}
631
632// finds the template character (currently set to $) and replaces the value with
633// the field found in a dbus object i.e. $ADDRESS would get populated with the
634// ADDRESS field from a object on dbus
635void templateCharReplace(
636 nlohmann::json::iterator &keyPair,
637 const boost::container::flat_map<std::string, dbus::dbus_variant>
638 &foundDevice,
639 size_t &foundDeviceIdx)
640{
641 if (keyPair.value().type() != nlohmann::json::value_t::string)
642 {
643 return;
644 }
645
646 std::string value = keyPair.value();
647 if (value.find(TEMPLATE_CHAR) != std::string::npos)
648 {
649 std::string templateValue = value;
650
651 templateValue.erase(0, 1); // remove template character
652
653 // special case index
654 if ("index" == templateValue)
655 {
656 keyPair.value() = foundDeviceIdx;
657 }
658 else
659 {
660 std::string subsitute;
661 for (auto &foundDevicePair : foundDevice)
662 {
663 if (boost::iequals(foundDevicePair.first, templateValue))
664 {
665 // convert value to string
666 // respresentation
667 subsitute = boost::apply_visitor(
668 [](const auto &x) {
669 return boost::lexical_cast<std::string>(x);
670 },
671 foundDevicePair.second);
672 break;
673 }
674 }
675 if (!subsitute.size())
676 {
677 std::cerr << "could not find symbol " << templateValue << "\n";
678 }
679 else
680 {
681 keyPair.value() = subsitute;
682 }
683 }
684 }
685}
686
James Feist75fdeeb2018-02-20 14:26:16 -0800687bool findJsonFiles(std::vector<nlohmann::json> &configurations)
James Feist3cb5fec2018-01-23 14:41:51 -0800688{
689 // find configuration files
690 std::vector<fs::path> jsonPaths;
691 if (!find_files(fs::path(CONFIGURATION_DIR), R"(.*\.json)", jsonPaths, 0))
692 {
693 std::cerr << "Unable to find any configuration files in "
694 << CONFIGURATION_DIR << "\n";
James Feist75fdeeb2018-02-20 14:26:16 -0800695 return false;
James Feist3cb5fec2018-01-23 14:41:51 -0800696 }
James Feist3cb5fec2018-01-23 14:41:51 -0800697 for (auto &jsonPath : jsonPaths)
698 {
699 std::ifstream jsonStream(jsonPath.c_str());
700 if (!jsonStream.good())
701 {
702 std::cerr << "unable to open " << jsonPath.string() << "\n";
703 continue;
704 }
705 auto data = nlohmann::json::parse(jsonStream, nullptr, false);
706 if (data.is_discarded())
707 {
708 std::cerr << "syntax error in " << jsonPath.string() << "\n";
709 continue;
710 }
711 if (data.type() == nlohmann::json::value_t::array)
712 {
713 for (auto &d : data)
714 {
715 configurations.emplace_back(d);
716 }
717 }
718 else
719 {
720 configurations.emplace_back(data);
721 }
722 }
James Feist75fdeeb2018-02-20 14:26:16 -0800723}
James Feist3cb5fec2018-01-23 14:41:51 -0800724
James Feist75fdeeb2018-02-20 14:26:16 -0800725bool rescan(nlohmann::json &systemConfiguration)
726{
727 std::vector<nlohmann::json> configurations;
728 if (!findJsonFiles(configurations))
729 {
730 false;
731 }
732 // preprocess already passed configurations and missing fields
733 if (systemConfiguration.size())
734 {
735 for (auto it = configurations.begin(); it != configurations.end();)
736 {
737 auto findName = it->find("name");
738 if (findName == it->end())
739 {
740 std::cerr << "configuration missing name field " << *it << "\n";
741 it = configurations.erase(it);
742 continue;
743 }
744 else if (findName->type() != nlohmann::json::value_t::string)
745 {
746 std::cerr << "name field must be a string " << *findName
747 << "\n";
748 it = configurations.erase(it);
749 continue;
750 }
751 auto findAlreadyFound =
752 systemConfiguration.find(findName->get<std::string>());
753 if (findAlreadyFound != systemConfiguration.end())
754 {
755 it = configurations.erase(it);
756 continue;
757 }
758 // TODO: add in tags to determine if configuration should be
759 // refreshed on AC / DC / Always.
760 it++;
761 }
762 }
763
764 // probe until no probes pass
James Feist3cb5fec2018-01-23 14:41:51 -0800765 bool probePassed = true;
James Feist3cb5fec2018-01-23 14:41:51 -0800766 while (probePassed)
767 {
768 probePassed = false;
769 for (auto it = configurations.begin(); it != configurations.end();)
770 {
771 bool eraseConfig = false;
James Feist1b2e2242018-01-30 13:45:19 -0800772 auto findProbe = it->find("probe");
773 auto findName = it->find("name");
James Feist3cb5fec2018-01-23 14:41:51 -0800774
James Feist1b2e2242018-01-30 13:45:19 -0800775 nlohmann::json probeCommand;
776 // check for poorly formatted fields, probe must be an array
777 if (findProbe == it->end())
James Feist3cb5fec2018-01-23 14:41:51 -0800778 {
779 std::cerr << "configuration file missing probe:\n " << *it
780 << "\n";
781 eraseConfig = true;
782 }
James Feist1b2e2242018-01-30 13:45:19 -0800783 else if ((*findProbe).type() != nlohmann::json::value_t::array)
James Feist3cb5fec2018-01-23 14:41:51 -0800784 {
785 probeCommand = nlohmann::json::array();
786 probeCommand.push_back(*findProbe);
787 }
788 else
789 {
790 probeCommand = *findProbe;
791 }
James Feist1b2e2242018-01-30 13:45:19 -0800792
793 if (findName == it->end())
794 {
795 std::cerr << "configuration file missing name:\n " << *it
796 << "\n";
797 eraseConfig = true;
798 }
799
James Feist3cb5fec2018-01-23 14:41:51 -0800800 std::vector<
801 boost::container::flat_map<std::string, dbus::dbus_variant>>
802 foundDevices;
James Feist1b2e2242018-01-30 13:45:19 -0800803 if (!eraseConfig && probe(probeCommand, foundDevices))
James Feist3cb5fec2018-01-23 14:41:51 -0800804 {
805 eraseConfig = true;
806 probePassed = true;
James Feist7b7e4e82018-01-24 14:56:00 -0800807 std::string name = *findName;
808 PASSED_PROBES.push_back(name);
James Feist3cb5fec2018-01-23 14:41:51 -0800809
810 size_t foundDeviceIdx = 0;
811
812 for (auto &foundDevice : foundDevices)
813 {
James Feist1b2e2242018-01-30 13:45:19 -0800814 for (auto keyPair = it->begin(); keyPair != it->end();
815 keyPair++)
James Feist3cb5fec2018-01-23 14:41:51 -0800816 {
James Feist1b2e2242018-01-30 13:45:19 -0800817 templateCharReplace(keyPair, foundDevice,
818 foundDeviceIdx);
819 }
820 auto findExpose = it->find("exposes");
821 if (findExpose == it->end())
822 {
James Feist3cb5fec2018-01-23 14:41:51 -0800823 continue;
824 }
825 for (auto &expose : *findExpose)
826 {
827 for (auto keyPair = expose.begin();
828 keyPair != expose.end(); keyPair++)
829 {
James Feist1b2e2242018-01-30 13:45:19 -0800830
James Feist3cb5fec2018-01-23 14:41:51 -0800831 // fill in template characters with devices
832 // found
James Feist1b2e2242018-01-30 13:45:19 -0800833 templateCharReplace(keyPair, foundDevice,
834 foundDeviceIdx);
835 // special case bind
836 if (boost::starts_with(keyPair.key(), "bind_"))
James Feist3cb5fec2018-01-23 14:41:51 -0800837 {
James Feist1b2e2242018-01-30 13:45:19 -0800838 if (keyPair.value().type() !=
839 nlohmann::json::value_t::string)
James Feist3cb5fec2018-01-23 14:41:51 -0800840 {
James Feist1b2e2242018-01-30 13:45:19 -0800841 std::cerr
842 << "bind_ value must be of type string "
843 << keyPair.key() << "\n";
844 continue;
James Feist3cb5fec2018-01-23 14:41:51 -0800845 }
James Feist1b2e2242018-01-30 13:45:19 -0800846 bool foundBind = false;
847 std::string bind =
848 keyPair.key().substr(sizeof("bind_") - 1);
849 for (auto &configurationPair :
850 nlohmann::json::iterator_wrapper(
851 systemConfiguration))
James Feist3cb5fec2018-01-23 14:41:51 -0800852 {
James Feist1b2e2242018-01-30 13:45:19 -0800853
854 auto configListFind =
855 configurationPair.value().find(
856 "exposes");
857
858 if (configListFind ==
859 configurationPair.value().end() ||
860 configListFind->type() !=
861 nlohmann::json::value_t::array)
James Feist3cb5fec2018-01-23 14:41:51 -0800862 {
James Feist1b2e2242018-01-30 13:45:19 -0800863 continue;
864 }
865 for (auto &exposedObject : *configListFind)
866 {
867 std::string foundObjectName =
868 (exposedObject)["name"];
869 if (boost::iequals(
870 foundObjectName,
871 keyPair.value()
872 .get<std::string>()))
James Feist3cb5fec2018-01-23 14:41:51 -0800873 {
James Feistc95cb142018-02-26 10:41:42 -0800874 exposedObject["status"] = "okay";
James Feist1b2e2242018-01-30 13:45:19 -0800875 expose[bind] = exposedObject;
James Feist1b2e2242018-01-30 13:45:19 -0800876
877 foundBind = true;
James Feist3cb5fec2018-01-23 14:41:51 -0800878 break;
879 }
880 }
James Feist1b2e2242018-01-30 13:45:19 -0800881 if (foundBind)
James Feist3cb5fec2018-01-23 14:41:51 -0800882 {
James Feist1b2e2242018-01-30 13:45:19 -0800883 break;
James Feist3cb5fec2018-01-23 14:41:51 -0800884 }
885 }
James Feist1b2e2242018-01-30 13:45:19 -0800886 if (!foundBind)
887 {
888 std::cerr << "configuration file "
889 "dependency error, "
890 "could not find bind "
891 << keyPair.value() << "\n";
892 }
James Feist3cb5fec2018-01-23 14:41:51 -0800893 }
894 }
895 }
James Feist3cb5fec2018-01-23 14:41:51 -0800896 }
James Feist1b2e2242018-01-30 13:45:19 -0800897 systemConfiguration[name] = (*it);
898 foundDeviceIdx++;
James Feist3cb5fec2018-01-23 14:41:51 -0800899 }
900
901 if (eraseConfig)
902 {
903 it = configurations.erase(it);
904 }
905 else
906 {
907 it++;
908 }
909 }
910 }
James Feist75fdeeb2018-02-20 14:26:16 -0800911}
912
913void propertiesChangedCallback(
914 std::vector<std::pair<std::unique_ptr<dbus::match>,
915 std::shared_ptr<dbus::filter>>> &dbusMatches,
James Feist4131aea2018-03-09 09:47:30 -0800916 nlohmann::json &systemConfiguration, dbus::DbusObjectServer &objServer,
917 std::shared_ptr<dbus::filter> dbusFilter)
James Feist75fdeeb2018-02-20 14:26:16 -0800918{
919 static std::future<void> future;
James Feist4131aea2018-03-09 09:47:30 -0800920 static std::atomic_bool threadRunning(false);
921 static std::atomic_bool pendingCallback(false);
James Feist75fdeeb2018-02-20 14:26:16 -0800922 bool notRunning = false;
923 if (threadRunning.compare_exchange_strong(notRunning, true))
924 {
925 future = std::async(std::launch::async, [&] {
James Feistc95cb142018-02-26 10:41:42 -0800926
James Feist4131aea2018-03-09 09:47:30 -0800927 do
928 {
929 std::this_thread::sleep_for(std::chrono::seconds(
930 SLEEP_AFTER_PROPERTIES_CHANGE_SECONDS));
931 auto oldConfiguration = systemConfiguration;
932 DBUS_PROBE_OBJECTS.clear();
933 pendingCallback = false;
934 rescan(systemConfiguration);
935 auto newConfiguration = systemConfiguration;
936 for (auto it = newConfiguration.begin();
937 it != newConfiguration.end();)
938 {
939 auto findKey = oldConfiguration.find(it.key());
940 if (findKey != oldConfiguration.end())
941 {
942 it = newConfiguration.erase(it);
943 }
944 else
945 {
946 it++;
947 }
948 }
949
950 registerCallbacks(dbusMatches, systemConfiguration, objServer);
951 // todo: for now, only add new configurations, unload to come
952 // later
953 // unloadOverlays();
954 loadOverlays(newConfiguration);
955 // this line to be removed in future
956 writeJsonFiles(systemConfiguration);
957 // only post new items to bus for now
958 postToDbus(newConfiguration, objServer);
959 } while (pendingCallback);
James Feist75fdeeb2018-02-20 14:26:16 -0800960 threadRunning = false;
961 });
962 }
James Feist4131aea2018-03-09 09:47:30 -0800963 else
964 {
965 pendingCallback = true;
966 }
James Feist75fdeeb2018-02-20 14:26:16 -0800967 if (dbusFilter != nullptr)
968 {
969 dbusFilter->async_dispatch([&, dbusFilter](boost::system::error_code ec,
970 dbus::message) {
971 if (ec)
972 {
973 std::cerr << "properties changed callback error " << ec << "\n";
974 }
James Feist4131aea2018-03-09 09:47:30 -0800975 propertiesChangedCallback(dbusMatches, systemConfiguration,
976 objServer, dbusFilter);
James Feist75fdeeb2018-02-20 14:26:16 -0800977 });
978 }
979}
980
981void registerCallbacks(
982 std::vector<std::pair<std::unique_ptr<dbus::match>,
983 std::shared_ptr<dbus::filter>>> &dbusMatches,
James Feist4131aea2018-03-09 09:47:30 -0800984 nlohmann::json &systemConfiguration, dbus::DbusObjectServer &objServer)
James Feist75fdeeb2018-02-20 14:26:16 -0800985{
986 static boost::container::flat_set<std::string> watchedObjects;
987
988 for (const auto &objectMap : DBUS_PROBE_OBJECTS)
989 {
990 auto findObject = watchedObjects.find(objectMap.first);
991 if (findObject != watchedObjects.end())
992 {
993 continue;
994 }
995 // this creates a filter for properties changed for any new probe type
996 auto propertyChange = std::make_unique<dbus::match>(
997 SYSTEM_BUS,
998 "type='signal',member='PropertiesChanged',arg0='" +
999 objectMap.first + "'");
1000 auto filter =
1001 std::make_shared<dbus::filter>(SYSTEM_BUS, [](dbus::message &m) {
1002 auto member = m.get_member();
1003 return member == "PropertiesChanged";
1004 });
1005
1006 filter->async_dispatch([&, filter](boost::system::error_code ec,
1007 dbus::message) {
1008 if (ec)
1009 {
1010 std::cerr << "register callbacks callback error " << ec << "\n";
1011 }
James Feist4131aea2018-03-09 09:47:30 -08001012 propertiesChangedCallback(dbusMatches, systemConfiguration,
1013 objServer, filter);
James Feist75fdeeb2018-02-20 14:26:16 -08001014 });
1015 dbusMatches.emplace_back(std::move(propertyChange), filter);
1016 }
1017}
1018
1019int main(int argc, char **argv)
1020{
1021 // setup connection to dbus
1022 boost::asio::io_service io;
1023 SYSTEM_BUS = std::make_shared<dbus::connection>(io, dbus::bus::system);
James Feist4131aea2018-03-09 09:47:30 -08001024
James Feist75fdeeb2018-02-20 14:26:16 -08001025 dbus::DbusObjectServer objServer(SYSTEM_BUS);
1026 SYSTEM_BUS->request_name("xyz.openbmc_project.EntityManager");
James Feist75fdeeb2018-02-20 14:26:16 -08001027 std::vector<
1028 std::pair<std::unique_ptr<dbus::match>, std::shared_ptr<dbus::filter>>>
1029 dbusMatches;
James Feist4131aea2018-03-09 09:47:30 -08001030
1031 nlohmann::json systemConfiguration = nlohmann::json::object();
1032 auto iface = std::make_shared<dbus::DbusInterface>(
1033 "xyz.openbmc_project.EntityManager", SYSTEM_BUS);
1034 io.post([&]() {
1035 unloadAllOverlays();
1036 propertiesChangedCallback(dbusMatches, systemConfiguration, objServer,
1037 nullptr);
1038 auto object = std::make_shared<dbus::DbusObject>(
1039 SYSTEM_BUS, "/xyz/openbmc_project/EntityManager");
1040 objServer.register_object(object);
1041
1042 object->register_interface(iface);
1043
1044 });
1045
1046 // to keep reference to the match / filter objects so they don't get
1047 // destroyed
1048
James Feist75fdeeb2018-02-20 14:26:16 -08001049 iface->register_method("ReScan", [&]() {
James Feist4131aea2018-03-09 09:47:30 -08001050 propertiesChangedCallback(dbusMatches, systemConfiguration, objServer,
1051 nullptr);
James Feist75fdeeb2018-02-20 14:26:16 -08001052 return std::tuple<>(); // this is a bug in boost-dbus, needs some sort
1053 // of return
1054 });
James Feist75fdeeb2018-02-20 14:26:16 -08001055
James Feist1b2e2242018-01-30 13:45:19 -08001056 io.run();
James Feist3cb5fec2018-01-23 14:41:51 -08001057
1058 return 0;
James Feist75fdeeb2018-02-20 14:26:16 -08001059}