blob: 021126fbd7097d232c59ba7cb0a43e0ca8d3011c [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>
James Feist11be6672018-04-06 14:05:32 -070024#include <boost/algorithm/string/case_conv.hpp>
James Feist3cb5fec2018-01-23 14:41:51 -080025#include <boost/algorithm/string/predicate.hpp>
26#include <boost/algorithm/string/replace.hpp>
27#include <boost/variant/apply_visitor.hpp>
28#include <boost/lexical_cast.hpp>
29#include <boost/container/flat_map.hpp>
30#include <boost/container/flat_set.hpp>
James Feist1b2e2242018-01-30 13:45:19 -080031#include <dbus/connection.hpp>
James Feist3cb5fec2018-01-23 14:41:51 -080032#include <VariantVisitors.hpp>
James Feist7b7e4e82018-01-24 14:56:00 -080033#include <experimental/filesystem>
James Feist3cb5fec2018-01-23 14:41:51 -080034
35constexpr const char *OUTPUT_DIR = "/var/configuration/";
36constexpr const char *CONFIGURATION_DIR = "/usr/share/configurations";
37constexpr const char *TEMPLATE_CHAR = "$";
James Feistc95cb142018-02-26 10:41:42 -080038constexpr const size_t PROPERTIES_CHANGED_UNTIL_FLUSH_COUNT = 20;
James Feist3cb5fec2018-01-23 14:41:51 -080039constexpr const size_t MAX_MAPPER_DEPTH = 99;
James Feist4131aea2018-03-09 09:47:30 -080040constexpr const size_t SLEEP_AFTER_PROPERTIES_CHANGE_SECONDS = 5;
James Feist3cb5fec2018-01-23 14:41:51 -080041
42namespace fs = std::experimental::filesystem;
43struct cmp_str
44{
45 bool operator()(const char *a, const char *b) const
46 {
47 return std::strcmp(a, b) < 0;
48 }
49};
50
51// underscore T for collison with dbus c api
52enum class probe_type_codes
53{
54 FALSE_T,
55 TRUE_T,
56 AND,
57 OR,
James Feist6bd2a022018-03-13 12:30:58 -070058 FOUND,
59 MATCH_ONE
James Feist3cb5fec2018-01-23 14:41:51 -080060};
61const static boost::container::flat_map<const char *, probe_type_codes, cmp_str>
62 PROBE_TYPES{{{"FALSE", probe_type_codes::FALSE_T},
63 {"TRUE", probe_type_codes::TRUE_T},
64 {"AND", probe_type_codes::AND},
65 {"OR", probe_type_codes::OR},
James Feist6bd2a022018-03-13 12:30:58 -070066 {"FOUND", probe_type_codes::FOUND},
67 {"MATCH_ONE", probe_type_codes::MATCH_ONE}}};
James Feist3cb5fec2018-01-23 14:41:51 -080068
69using GetSubTreeType = std::vector<
70 std::pair<std::string,
71 std::vector<std::pair<std::string, std::vector<std::string>>>>>;
72
73using ManagedObjectType = boost::container::flat_map<
74 dbus::object_path,
75 boost::container::flat_map<
76 std::string,
77 boost::container::flat_map<std::string, dbus::dbus_variant>>>;
78
79boost::container::flat_map<
80 std::string,
81 std::vector<boost::container::flat_map<std::string, dbus::dbus_variant>>>
82 DBUS_PROBE_OBJECTS;
83std::vector<std::string> PASSED_PROBES;
84
85// todo: pass this through nicer
86std::shared_ptr<dbus::connection> SYSTEM_BUS;
87
James Feist1b2e2242018-01-30 13:45:19 -080088std::regex ILLEGAL_DBUS_REGEX("[^A-Za-z0-9_]");
89
James Feist75fdeeb2018-02-20 14:26:16 -080090void registerCallbacks(
91 std::vector<std::pair<std::unique_ptr<dbus::match>,
92 std::shared_ptr<dbus::filter>>> &dbusMatches,
James Feist4131aea2018-03-09 09:47:30 -080093 nlohmann::json &systemConfiguration, dbus::DbusObjectServer &objServer);
James Feist75fdeeb2018-02-20 14:26:16 -080094
James Feist3cb5fec2018-01-23 14:41:51 -080095// calls the mapper to find all exposed objects of an interface type
96// and creates a vector<flat_map> that contains all the key value pairs
97// getManagedObjects
98bool findDbusObjects(
99 std::shared_ptr<dbus::connection> connection,
100 std::vector<boost::container::flat_map<std::string, dbus::dbus_variant>>
101 &interfaceDevices,
102 std::string interface)
103{
James Feist494155a2018-03-14 16:23:24 -0700104 // todo: this is only static because the mapper is unreliable as of today
105 static boost::container::flat_map<std::string,
106 boost::container::flat_set<std::string>>
107 connections;
James Feist3cb5fec2018-01-23 14:41:51 -0800108 // find all connections in the mapper that expose a specific type
109 static const dbus::endpoint mapper("xyz.openbmc_project.ObjectMapper",
110 "/xyz/openbmc_project/object_mapper",
111 "xyz.openbmc_project.ObjectMapper",
112 "GetSubTree");
113 dbus::message getMap = dbus::message::new_call(mapper);
114 std::vector<std::string> objects = {interface};
115 if (!getMap.pack("", MAX_MAPPER_DEPTH, objects))
116 {
117 std::cerr << "Pack Failed GetSensorSubtree\n";
118 return false;
119 }
James Feist494155a2018-03-14 16:23:24 -0700120
James Feist3cb5fec2018-01-23 14:41:51 -0800121 GetSubTreeType interfaceSubtree;
James Feist494155a2018-03-14 16:23:24 -0700122 size_t retries = 1;
123 bool unpackStatus = false;
124 // the mapper seems to hang occasionally, not responding, so we give it a
125 // timeout and retries
126 do
James Feist3cb5fec2018-01-23 14:41:51 -0800127 {
James Feist494155a2018-03-14 16:23:24 -0700128 dbus::message getMapResp =
129 connection->send(getMap, std::chrono::seconds(2));
130 unpackStatus = getMapResp.unpack(interfaceSubtree);
131
132 } while (retries-- && !unpackStatus);
133
134 auto &interfaceConnections = connections[interface];
135 if (!unpackStatus)
James Feist3cb5fec2018-01-23 14:41:51 -0800136 {
James Feist494155a2018-03-14 16:23:24 -0700137 std::cerr << "Error communicating to mapper, using cached data if "
138 "available\n";
139 if (interfaceConnections.empty())
James Feist3cb5fec2018-01-23 14:41:51 -0800140 {
James Feist494155a2018-03-14 16:23:24 -0700141 return false;
142 }
143 }
144
145 if (unpackStatus)
146 {
147 interfaceConnections.clear();
148 for (auto &object : interfaceSubtree)
149 {
150 for (auto &connPair : object.second)
151 {
152 interfaceConnections.insert(connPair.first);
153 }
James Feist3cb5fec2018-01-23 14:41:51 -0800154 }
155 }
156 // iterate through the connections, adding creating individual device
157 // dictionaries
James Feist494155a2018-03-14 16:23:24 -0700158 for (auto &conn : interfaceConnections)
James Feist3cb5fec2018-01-23 14:41:51 -0800159 {
160 auto managedObj =
161 dbus::endpoint(conn, "/", "org.freedesktop.DBus.ObjectManager",
162 "GetManagedObjects");
163 dbus::message getManagedObj = dbus::message::new_call(managedObj);
James Feist3cb5fec2018-01-23 14:41:51 -0800164 ManagedObjectType managedInterface;
James Feist494155a2018-03-14 16:23:24 -0700165 retries = 1;
166 unpackStatus = false;
167 do
168 {
169 dbus::message getManagedObjResp = connection->send(getManagedObj);
170 unpackStatus = getManagedObjResp.unpack(managedInterface);
171 } while (retries-- && !unpackStatus);
172
173 if (!unpackStatus)
James Feist3cb5fec2018-01-23 14:41:51 -0800174 {
175 std::cerr << "error getting managed object for device " << conn
176 << "\n";
177 continue;
178 }
179 for (auto &interfaceManagedObj : managedInterface)
180 {
181 auto ifaceObjFind = interfaceManagedObj.second.find(interface);
182 if (ifaceObjFind != interfaceManagedObj.second.end())
183 {
184 interfaceDevices.emplace_back(ifaceObjFind->second);
185 }
186 }
187 }
188 return true;
189}
190
191// probes interface dictionary for a key with a value that matches a regex
192bool probeDbus(
193 const std::string &interface,
194 const std::map<std::string, nlohmann::json> &matches,
195 std::vector<boost::container::flat_map<std::string, dbus::dbus_variant>>
196 &devices,
197 bool &foundProbe)
198{
199 auto &dbusObject = DBUS_PROBE_OBJECTS[interface];
200 if (dbusObject.empty())
201 {
202 if (!findDbusObjects(SYSTEM_BUS, dbusObject, interface))
203 {
204 std::cerr << "Found no dbus objects with interface "
205 << interface << "\n";
206 foundProbe = false;
207 return false;
208 }
209 }
210 foundProbe = true;
211
212 bool foundMatch = false;
213 for (auto &device : dbusObject)
214 {
215 bool deviceMatches = true;
216 for (auto &match : matches)
217 {
218 auto deviceValue = device.find(match.first);
219 if (deviceValue != device.end())
220 {
221 switch (match.second.type())
222 {
223 case nlohmann::json::value_t::string:
224 {
225 std::regex search(match.second.get<std::string>());
226 std::smatch match;
227
228 // convert value to string respresentation
229 std::string probeValue = boost::apply_visitor(
230 [](const auto &x) {
231 return boost::lexical_cast<std::string>(x);
232 },
233 deviceValue->second);
234 if (!std::regex_search(probeValue, match, search))
235 {
236 deviceMatches = false;
237 break;
238 }
239 break;
240 }
241 case nlohmann::json::value_t::boolean:
242 case nlohmann::json::value_t::number_unsigned:
243 {
244 unsigned int probeValue = boost::apply_visitor(
245 VariantToUnsignedIntVisitor(), deviceValue->second);
246
247 if (probeValue != match.second.get<unsigned int>())
248 {
249 deviceMatches = false;
250 }
251 break;
252 }
253 case nlohmann::json::value_t::number_integer:
254 {
255 int probeValue = boost::apply_visitor(VariantToIntVisitor(),
256 deviceValue->second);
257
258 if (probeValue != match.second.get<int>())
259 {
260 deviceMatches = false;
261 }
262 break;
263 }
264 case nlohmann::json::value_t::number_float:
265 {
266 float probeValue = boost::apply_visitor(
267 VariantToFloatVisitor(), deviceValue->second);
268
269 if (probeValue != match.second.get<float>())
270 {
271 deviceMatches = false;
272 }
273 break;
274 }
275 }
276 }
277 else
278 {
279 deviceMatches = false;
280 break;
281 }
282 }
283 if (deviceMatches)
284 {
285 devices.emplace_back(
286 boost::container::flat_map<std::string, dbus::dbus_variant>(
287 device));
288 foundMatch = true;
289 deviceMatches = false; // for next iteration
290 }
291 }
292 return foundMatch;
293}
294
295// default probe entry point, iterates a list looking for specific types to
296// call specific probe functions
297bool probe(
298 const std::vector<std::string> probeCommand,
299 std::vector<boost::container::flat_map<std::string, dbus::dbus_variant>>
300 &foundDevs)
301{
302 const static std::regex command(R"(\((.*)\))");
303 std::smatch match;
304 bool ret = false;
James Feist6bd2a022018-03-13 12:30:58 -0700305 bool matchOne = false;
James Feist3cb5fec2018-01-23 14:41:51 -0800306 bool cur = true;
307 probe_type_codes lastCommand = probe_type_codes::FALSE_T;
308
309 for (auto &probe : probeCommand)
310 {
311 bool foundProbe = false;
312 boost::container::flat_map<const char *, probe_type_codes,
313 cmp_str>::const_iterator probeType;
314
315 for (probeType = PROBE_TYPES.begin(); probeType != PROBE_TYPES.end();
316 probeType++)
317 {
318 if (probe.find(probeType->first) != std::string::npos)
319 {
320 foundProbe = true;
321 break;
322 }
323 }
324 if (foundProbe)
325 {
326 switch (probeType->second)
327 {
328 case probe_type_codes::FALSE_T:
329 {
330 return false; // todo, actually evaluate?
331 break;
332 }
333 case probe_type_codes::TRUE_T:
334 {
335 return true; // todo, actually evaluate?
336 break;
337 }
James Feist6bd2a022018-03-13 12:30:58 -0700338 case probe_type_codes::MATCH_ONE:
339 {
340 // set current value to last, this probe type shouldn't affect
341 // the outcome
342 cur = ret;
343 matchOne = true;
344 break;
345 }
James Feist3cb5fec2018-01-23 14:41:51 -0800346 /*case probe_type_codes::AND:
347 break;
348 case probe_type_codes::OR:
349 break;
350 // these are no-ops until the last command switch
351 */
352 case probe_type_codes::FOUND:
353 {
354 if (!std::regex_search(probe, match, command))
355 {
356 std::cerr << "found probe sytax error " << probe << "\n";
357 return false;
358 }
359 std::string commandStr = *(match.begin() + 1);
James Feist3f8a2782018-02-12 09:24:42 -0800360 boost::replace_all(commandStr, "'", "");
James Feist3cb5fec2018-01-23 14:41:51 -0800361 cur = (std::find(PASSED_PROBES.begin(), PASSED_PROBES.end(),
362 commandStr) != PASSED_PROBES.end());
363 break;
364 }
365 }
366 }
367 // look on dbus for object
368 else
369 {
370 if (!std::regex_search(probe, match, command))
371 {
372 std::cerr << "dbus probe sytax error " << probe << "\n";
373 return false;
374 }
375 std::string commandStr = *(match.begin() + 1);
376 // convert single ticks and single slashes into legal json
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -0700377 boost::replace_all(commandStr, "'", "\"");
James Feist3f8a2782018-02-12 09:24:42 -0800378 boost::replace_all(commandStr, R"(\)", R"(\\)");
James Feist3cb5fec2018-01-23 14:41:51 -0800379 auto json = nlohmann::json::parse(commandStr, nullptr, false);
380 if (json.is_discarded())
381 {
382 std::cerr << "dbus command sytax error " << commandStr << "\n";
383 return false;
384 }
385 // we can match any (string, variant) property. (string, string)
386 // does a regex
387 std::map<std::string, nlohmann::json> dbusProbeMap =
388 json.get<std::map<std::string, nlohmann::json>>();
389 auto findStart = probe.find("(");
390 if (findStart == std::string::npos)
391 {
392 return false;
393 }
394 std::string probeInterface = probe.substr(0, findStart);
395 cur =
396 probeDbus(probeInterface, dbusProbeMap, foundDevs, foundProbe);
397 }
398
399 // some functions like AND and OR only take affect after the
400 // fact
401 switch (lastCommand)
402 {
403 case probe_type_codes::AND:
404 ret = cur && ret;
405 break;
406 case probe_type_codes::OR:
407 ret = cur || ret;
408 break;
409 default:
410 ret = cur;
411 break;
412 }
413 lastCommand = probeType != PROBE_TYPES.end()
414 ? probeType->second
415 : probe_type_codes::FALSE_T;
416
417 if (!foundProbe)
418 {
419 std::cerr << "Illegal probe type " << probe << "\n";
420 return false;
421 }
422 }
423
424 // probe passed, but empty device
425 // todo: should this be done in main?
426 if (ret && foundDevs.size() == 0)
427 {
428 foundDevs.emplace_back(
429 boost::container::flat_map<std::string, dbus::dbus_variant>());
430 }
James Feist6bd2a022018-03-13 12:30:58 -0700431 if (matchOne && foundDevs.size() > 1)
432 {
433 foundDevs.erase(foundDevs.begin() + 1, foundDevs.end());
434 }
James Feist3cb5fec2018-01-23 14:41:51 -0800435 return ret;
436}
437
James Feist1b2e2242018-01-30 13:45:19 -0800438// this function is temporary, no need to have once dbus is solified.
439void writeJsonFiles(nlohmann::json &systemConfiguration)
440{
441 std::experimental::filesystem::create_directory(OUTPUT_DIR);
442 std::ofstream output(std::string(OUTPUT_DIR) + "system.json");
443 output << systemConfiguration.dump(4);
444 output.close();
445
446 auto flat = nlohmann::json::array();
447 for (auto &pair : nlohmann::json::iterator_wrapper(systemConfiguration))
448 {
449 auto value = pair.value();
450 auto exposes = value.find("exposes");
451 if (exposes != value.end())
452 {
453 for (auto &item : *exposes)
454 {
455 flat.push_back(item);
456 }
457 }
458 }
459 output = std::ofstream(std::string(OUTPUT_DIR) + "flattened.json");
460 output << flat.dump(4);
461 output.close();
462}
463// adds simple json types to interface's properties
464void populateInterfaceFromJson(dbus::DbusInterface *iface, nlohmann::json dict,
465 dbus::DbusObjectServer &objServer)
466{
467 std::vector<std::pair<std::string, dbus::dbus_variant>> properties;
468 static size_t flushCount = 0;
469
470 for (auto &dictPair : nlohmann::json::iterator_wrapper(dict))
471 {
472 switch (dictPair.value().type())
473 {
474 case (nlohmann::json::value_t::boolean):
475 {
476 properties.emplace_back(std::string(dictPair.key()),
477 dictPair.value().get<bool>());
478 break;
479 }
480 case (nlohmann::json::value_t::number_integer):
481 {
482 properties.emplace_back(std::string(dictPair.key()),
483 dictPair.value().get<int64_t>());
484 break;
485 }
486 case (nlohmann::json::value_t::number_unsigned):
487 {
488 properties.emplace_back(std::string(dictPair.key()),
489 dictPair.value().get<uint64_t>());
490 break;
491 }
492 case (nlohmann::json::value_t::number_float):
493 {
494 properties.emplace_back(std::string(dictPair.key()),
495 dictPair.value().get<float>());
496 break;
497 }
498 case (nlohmann::json::value_t::string):
499 {
500 properties.emplace_back(std::string(dictPair.key()),
501 dictPair.value().get<std::string>());
502 break;
503 }
504 }
505 }
506 if (!properties.empty())
507 {
508 iface->set_properties(properties);
509
510 // flush the queue after adding an amount of properties so we don't hang
James Feistc95cb142018-02-26 10:41:42 -0800511 if (flushCount++ > PROPERTIES_CHANGED_UNTIL_FLUSH_COUNT)
James Feist1b2e2242018-01-30 13:45:19 -0800512 {
513 objServer.flush();
514 flushCount = 0;
515 }
516 }
517}
518
519void postToDbus(const nlohmann::json &systemConfiguration,
James Feist75fdeeb2018-02-20 14:26:16 -0800520 dbus::DbusObjectServer &objServer)
521
James Feist1b2e2242018-01-30 13:45:19 -0800522{
523 for (auto &boardPair :
524 nlohmann::json::iterator_wrapper(systemConfiguration))
525 {
526 std::string boardKey = boardPair.key();
527 auto boardValues = boardPair.value();
528 auto findBoardType = boardValues.find("type");
529 std::string boardType;
530 if (findBoardType != boardValues.end() &&
531 findBoardType->type() == nlohmann::json::value_t::string)
532 {
533 boardType = findBoardType->get<std::string>();
534 std::regex_replace(boardType.begin(), boardType.begin(),
535 boardType.end(), ILLEGAL_DBUS_REGEX, "_");
536 }
537 else
538 {
539 std::cerr << "Unable to find type for " << boardKey
540 << " reverting to Chassis.\n";
541 boardType = "Chassis";
542 }
James Feist11be6672018-04-06 14:05:32 -0700543 std::string boardtypeLower = boost::algorithm::to_lower_copy(boardType);
James Feist1b2e2242018-01-30 13:45:19 -0800544
545 std::regex_replace(boardKey.begin(), boardKey.begin(), boardKey.end(),
546 ILLEGAL_DBUS_REGEX, "_");
James Feist11be6672018-04-06 14:05:32 -0700547 std::string boardName = "/xyz/openbmc_project/inventory/system/" +
548 boardtypeLower + "/" + boardKey;
James Feist1b2e2242018-01-30 13:45:19 -0800549 auto boardObject = objServer.add_object(boardName);
550
James Feist11be6672018-04-06 14:05:32 -0700551 auto boardIface =
552 boardObject->add_interface("xyz.openbmc_project.Inventory.Item");
553
554 boardObject->add_interface("xyz.openbmc_project.Inventory.Item." +
555 boardType);
James Feist1b2e2242018-01-30 13:45:19 -0800556 populateInterfaceFromJson(boardIface.get(), boardValues, objServer);
James Feist11be6672018-04-06 14:05:32 -0700557 for (auto &boardField : nlohmann::json::iterator_wrapper(boardValues))
558 {
559 if (boardField.value().type() == nlohmann::json::value_t::object)
560 {
561 auto iface = boardObject->add_interface(boardField.key());
562 populateInterfaceFromJson(iface.get(), boardField.value(),
563 objServer);
564 }
565 }
James Feist1b2e2242018-01-30 13:45:19 -0800566 auto exposes = boardValues.find("exposes");
567 if (exposes == boardValues.end())
568 {
569 continue;
570 }
571 for (auto &item : *exposes)
572 {
573 auto findName = item.find("name");
574 if (findName == item.end())
575 {
576 std::cerr << "cannot find name in field " << item << "\n";
577 continue;
578 }
579 auto findStatus = item.find("status");
580 // if status is not found it is assumed to be status = 'okay'
581 if (findStatus != item.end())
582 {
583 if (*findStatus == "disabled")
584 {
585 continue;
586 }
587 }
588 auto findType = item.find("type");
589 std::string itemType;
590 if (findType != item.end())
591 {
592 itemType = findType->get<std::string>();
593 std::regex_replace(itemType.begin(), itemType.begin(),
594 itemType.end(), ILLEGAL_DBUS_REGEX, "_");
595 }
596 else
597 {
598 itemType = "unknown";
599 }
600 std::string itemName = findName->get<std::string>();
601 std::regex_replace(itemName.begin(), itemName.begin(),
602 itemName.end(), ILLEGAL_DBUS_REGEX, "_");
603 auto itemObject = objServer.add_object(boardName + "/" + itemName);
604 auto itemIface = itemObject->add_interface(
605 "xyz.openbmc_project.Configuration." + itemType);
606
607 populateInterfaceFromJson(itemIface.get(), item, objServer);
608
609 for (auto &objectPair : nlohmann::json::iterator_wrapper(item))
610 {
611 if (objectPair.value().type() ==
612 nlohmann::json::value_t::object)
613 {
614 auto objectIface = itemObject->add_interface(
615 "xyz.openbmc_project.Configuration." + itemType + "." +
616 objectPair.key());
617 populateInterfaceFromJson(objectIface.get(),
618 objectPair.value(), objServer);
619 }
620 else if (objectPair.value().type() ==
621 nlohmann::json::value_t::array)
622 {
623 size_t index = 0;
624 for (auto &arrayItem : objectPair.value())
625 {
626 if (arrayItem.type() != nlohmann::json::value_t::object)
627 {
628 std::cerr << "dbus format error" << arrayItem
629 << "\n";
630 break;
631 }
632 auto objectIface = itemObject->add_interface(
633 "xyz.openbmc_project.Configuration." + itemType +
634 "." + objectPair.key() + "." +
635 std::to_string(index));
636 index++;
637 populateInterfaceFromJson(objectIface.get(), arrayItem,
638 objServer);
639 }
640 }
641 }
642 }
643 }
644}
645
646// finds the template character (currently set to $) and replaces the value with
647// the field found in a dbus object i.e. $ADDRESS would get populated with the
648// ADDRESS field from a object on dbus
649void templateCharReplace(
650 nlohmann::json::iterator &keyPair,
651 const boost::container::flat_map<std::string, dbus::dbus_variant>
652 &foundDevice,
653 size_t &foundDeviceIdx)
654{
James Feist11be6672018-04-06 14:05:32 -0700655 if (keyPair.value().type() == nlohmann::json::value_t::object)
656 {
657 for (auto nextLayer = keyPair.value().begin();
658 nextLayer != keyPair.value().end(); nextLayer++)
659 {
660 templateCharReplace(nextLayer, foundDevice, foundDeviceIdx);
661 }
662 return;
663 }
664 else if (keyPair.value().type() != nlohmann::json::value_t::string)
James Feist1b2e2242018-01-30 13:45:19 -0800665 {
666 return;
667 }
668
669 std::string value = keyPair.value();
670 if (value.find(TEMPLATE_CHAR) != std::string::npos)
671 {
672 std::string templateValue = value;
673
674 templateValue.erase(0, 1); // remove template character
675
676 // special case index
677 if ("index" == templateValue)
678 {
679 keyPair.value() = foundDeviceIdx;
680 }
681 else
682 {
Gunnar Millsb3e42fe2018-06-13 15:48:27 -0500683 std::string substitute;
James Feist1b2e2242018-01-30 13:45:19 -0800684 for (auto &foundDevicePair : foundDevice)
685 {
686 if (boost::iequals(foundDevicePair.first, templateValue))
687 {
688 // convert value to string
689 // respresentation
Gunnar Millsb3e42fe2018-06-13 15:48:27 -0500690 substitute = boost::apply_visitor(
James Feist1b2e2242018-01-30 13:45:19 -0800691 [](const auto &x) {
692 return boost::lexical_cast<std::string>(x);
693 },
694 foundDevicePair.second);
695 break;
696 }
697 }
Gunnar Millsb3e42fe2018-06-13 15:48:27 -0500698 if (!substitute.size())
James Feist1b2e2242018-01-30 13:45:19 -0800699 {
700 std::cerr << "could not find symbol " << templateValue << "\n";
701 }
702 else
703 {
Gunnar Millsb3e42fe2018-06-13 15:48:27 -0500704 keyPair.value() = substitute;
James Feist1b2e2242018-01-30 13:45:19 -0800705 }
706 }
707 }
708}
709
James Feist75fdeeb2018-02-20 14:26:16 -0800710bool findJsonFiles(std::vector<nlohmann::json> &configurations)
James Feist3cb5fec2018-01-23 14:41:51 -0800711{
712 // find configuration files
713 std::vector<fs::path> jsonPaths;
714 if (!find_files(fs::path(CONFIGURATION_DIR), R"(.*\.json)", jsonPaths, 0))
715 {
716 std::cerr << "Unable to find any configuration files in "
717 << CONFIGURATION_DIR << "\n";
James Feist75fdeeb2018-02-20 14:26:16 -0800718 return false;
James Feist3cb5fec2018-01-23 14:41:51 -0800719 }
James Feist3cb5fec2018-01-23 14:41:51 -0800720 for (auto &jsonPath : jsonPaths)
721 {
722 std::ifstream jsonStream(jsonPath.c_str());
723 if (!jsonStream.good())
724 {
725 std::cerr << "unable to open " << jsonPath.string() << "\n";
726 continue;
727 }
728 auto data = nlohmann::json::parse(jsonStream, nullptr, false);
729 if (data.is_discarded())
730 {
731 std::cerr << "syntax error in " << jsonPath.string() << "\n";
732 continue;
733 }
734 if (data.type() == nlohmann::json::value_t::array)
735 {
736 for (auto &d : data)
737 {
738 configurations.emplace_back(d);
739 }
740 }
741 else
742 {
743 configurations.emplace_back(data);
744 }
745 }
James Feist75fdeeb2018-02-20 14:26:16 -0800746}
James Feist3cb5fec2018-01-23 14:41:51 -0800747
James Feist75fdeeb2018-02-20 14:26:16 -0800748bool rescan(nlohmann::json &systemConfiguration)
749{
750 std::vector<nlohmann::json> configurations;
751 if (!findJsonFiles(configurations))
752 {
753 false;
754 }
755 // preprocess already passed configurations and missing fields
756 if (systemConfiguration.size())
757 {
758 for (auto it = configurations.begin(); it != configurations.end();)
759 {
760 auto findName = it->find("name");
761 if (findName == it->end())
762 {
763 std::cerr << "configuration missing name field " << *it << "\n";
764 it = configurations.erase(it);
765 continue;
766 }
767 else if (findName->type() != nlohmann::json::value_t::string)
768 {
769 std::cerr << "name field must be a string " << *findName
770 << "\n";
771 it = configurations.erase(it);
772 continue;
773 }
774 auto findAlreadyFound =
775 systemConfiguration.find(findName->get<std::string>());
776 if (findAlreadyFound != systemConfiguration.end())
777 {
778 it = configurations.erase(it);
779 continue;
780 }
781 // TODO: add in tags to determine if configuration should be
782 // refreshed on AC / DC / Always.
783 it++;
784 }
785 }
786
787 // probe until no probes pass
James Feist3cb5fec2018-01-23 14:41:51 -0800788 bool probePassed = true;
James Feist3cb5fec2018-01-23 14:41:51 -0800789 while (probePassed)
790 {
791 probePassed = false;
792 for (auto it = configurations.begin(); it != configurations.end();)
793 {
794 bool eraseConfig = false;
James Feist1b2e2242018-01-30 13:45:19 -0800795 auto findProbe = it->find("probe");
796 auto findName = it->find("name");
James Feist3cb5fec2018-01-23 14:41:51 -0800797
James Feist1b2e2242018-01-30 13:45:19 -0800798 nlohmann::json probeCommand;
799 // check for poorly formatted fields, probe must be an array
800 if (findProbe == it->end())
James Feist3cb5fec2018-01-23 14:41:51 -0800801 {
802 std::cerr << "configuration file missing probe:\n " << *it
803 << "\n";
804 eraseConfig = true;
805 }
James Feist1b2e2242018-01-30 13:45:19 -0800806 else if ((*findProbe).type() != nlohmann::json::value_t::array)
James Feist3cb5fec2018-01-23 14:41:51 -0800807 {
808 probeCommand = nlohmann::json::array();
809 probeCommand.push_back(*findProbe);
810 }
811 else
812 {
813 probeCommand = *findProbe;
814 }
James Feist1b2e2242018-01-30 13:45:19 -0800815
816 if (findName == it->end())
817 {
818 std::cerr << "configuration file missing name:\n " << *it
819 << "\n";
820 eraseConfig = true;
821 }
822
James Feist3cb5fec2018-01-23 14:41:51 -0800823 std::vector<
824 boost::container::flat_map<std::string, dbus::dbus_variant>>
825 foundDevices;
James Feist1b2e2242018-01-30 13:45:19 -0800826 if (!eraseConfig && probe(probeCommand, foundDevices))
James Feist3cb5fec2018-01-23 14:41:51 -0800827 {
828 eraseConfig = true;
829 probePassed = true;
James Feist7b7e4e82018-01-24 14:56:00 -0800830 std::string name = *findName;
831 PASSED_PROBES.push_back(name);
James Feist3cb5fec2018-01-23 14:41:51 -0800832
833 size_t foundDeviceIdx = 0;
834
835 for (auto &foundDevice : foundDevices)
836 {
James Feist1b2e2242018-01-30 13:45:19 -0800837 for (auto keyPair = it->begin(); keyPair != it->end();
838 keyPair++)
James Feist3cb5fec2018-01-23 14:41:51 -0800839 {
James Feist1b2e2242018-01-30 13:45:19 -0800840 templateCharReplace(keyPair, foundDevice,
841 foundDeviceIdx);
842 }
843 auto findExpose = it->find("exposes");
844 if (findExpose == it->end())
845 {
James Feist3cb5fec2018-01-23 14:41:51 -0800846 continue;
847 }
848 for (auto &expose : *findExpose)
849 {
850 for (auto keyPair = expose.begin();
851 keyPair != expose.end(); keyPair++)
852 {
James Feist1b2e2242018-01-30 13:45:19 -0800853
James Feist3cb5fec2018-01-23 14:41:51 -0800854 // fill in template characters with devices
855 // found
James Feist1b2e2242018-01-30 13:45:19 -0800856 templateCharReplace(keyPair, foundDevice,
857 foundDeviceIdx);
858 // special case bind
859 if (boost::starts_with(keyPair.key(), "bind_"))
James Feist3cb5fec2018-01-23 14:41:51 -0800860 {
James Feist1b2e2242018-01-30 13:45:19 -0800861 if (keyPair.value().type() !=
862 nlohmann::json::value_t::string)
James Feist3cb5fec2018-01-23 14:41:51 -0800863 {
James Feist1b2e2242018-01-30 13:45:19 -0800864 std::cerr
865 << "bind_ value must be of type string "
866 << keyPair.key() << "\n";
867 continue;
James Feist3cb5fec2018-01-23 14:41:51 -0800868 }
James Feist1b2e2242018-01-30 13:45:19 -0800869 bool foundBind = false;
870 std::string bind =
871 keyPair.key().substr(sizeof("bind_") - 1);
872 for (auto &configurationPair :
873 nlohmann::json::iterator_wrapper(
874 systemConfiguration))
James Feist3cb5fec2018-01-23 14:41:51 -0800875 {
James Feist1b2e2242018-01-30 13:45:19 -0800876
877 auto configListFind =
878 configurationPair.value().find(
879 "exposes");
880
881 if (configListFind ==
882 configurationPair.value().end() ||
883 configListFind->type() !=
884 nlohmann::json::value_t::array)
James Feist3cb5fec2018-01-23 14:41:51 -0800885 {
James Feist1b2e2242018-01-30 13:45:19 -0800886 continue;
887 }
888 for (auto &exposedObject : *configListFind)
889 {
890 std::string foundObjectName =
891 (exposedObject)["name"];
892 if (boost::iequals(
893 foundObjectName,
894 keyPair.value()
895 .get<std::string>()))
James Feist3cb5fec2018-01-23 14:41:51 -0800896 {
James Feistc95cb142018-02-26 10:41:42 -0800897 exposedObject["status"] = "okay";
James Feist1b2e2242018-01-30 13:45:19 -0800898 expose[bind] = exposedObject;
James Feist1b2e2242018-01-30 13:45:19 -0800899
900 foundBind = true;
James Feist3cb5fec2018-01-23 14:41:51 -0800901 break;
902 }
903 }
James Feist1b2e2242018-01-30 13:45:19 -0800904 if (foundBind)
James Feist3cb5fec2018-01-23 14:41:51 -0800905 {
James Feist1b2e2242018-01-30 13:45:19 -0800906 break;
James Feist3cb5fec2018-01-23 14:41:51 -0800907 }
908 }
James Feist1b2e2242018-01-30 13:45:19 -0800909 if (!foundBind)
910 {
911 std::cerr << "configuration file "
912 "dependency error, "
913 "could not find bind "
914 << keyPair.value() << "\n";
915 }
James Feist3cb5fec2018-01-23 14:41:51 -0800916 }
917 }
918 }
James Feist3cb5fec2018-01-23 14:41:51 -0800919 }
James Feist1b2e2242018-01-30 13:45:19 -0800920 systemConfiguration[name] = (*it);
921 foundDeviceIdx++;
James Feist3cb5fec2018-01-23 14:41:51 -0800922 }
923
924 if (eraseConfig)
925 {
926 it = configurations.erase(it);
927 }
928 else
929 {
930 it++;
931 }
932 }
933 }
James Feist75fdeeb2018-02-20 14:26:16 -0800934}
935
936void propertiesChangedCallback(
937 std::vector<std::pair<std::unique_ptr<dbus::match>,
938 std::shared_ptr<dbus::filter>>> &dbusMatches,
James Feist4131aea2018-03-09 09:47:30 -0800939 nlohmann::json &systemConfiguration, dbus::DbusObjectServer &objServer,
940 std::shared_ptr<dbus::filter> dbusFilter)
James Feist75fdeeb2018-02-20 14:26:16 -0800941{
942 static std::future<void> future;
James Feist4131aea2018-03-09 09:47:30 -0800943 static std::atomic_bool threadRunning(false);
944 static std::atomic_bool pendingCallback(false);
James Feist75fdeeb2018-02-20 14:26:16 -0800945 bool notRunning = false;
946 if (threadRunning.compare_exchange_strong(notRunning, true))
947 {
948 future = std::async(std::launch::async, [&] {
James Feistc95cb142018-02-26 10:41:42 -0800949
James Feist4131aea2018-03-09 09:47:30 -0800950 do
951 {
952 std::this_thread::sleep_for(std::chrono::seconds(
953 SLEEP_AFTER_PROPERTIES_CHANGE_SECONDS));
954 auto oldConfiguration = systemConfiguration;
955 DBUS_PROBE_OBJECTS.clear();
956 pendingCallback = false;
957 rescan(systemConfiguration);
958 auto newConfiguration = systemConfiguration;
959 for (auto it = newConfiguration.begin();
960 it != newConfiguration.end();)
961 {
962 auto findKey = oldConfiguration.find(it.key());
963 if (findKey != oldConfiguration.end())
964 {
965 it = newConfiguration.erase(it);
966 }
967 else
968 {
969 it++;
970 }
971 }
972
973 registerCallbacks(dbusMatches, systemConfiguration, objServer);
974 // todo: for now, only add new configurations, unload to come
975 // later
976 // unloadOverlays();
977 loadOverlays(newConfiguration);
978 // this line to be removed in future
979 writeJsonFiles(systemConfiguration);
980 // only post new items to bus for now
981 postToDbus(newConfiguration, objServer);
982 } while (pendingCallback);
James Feist75fdeeb2018-02-20 14:26:16 -0800983 threadRunning = false;
984 });
985 }
James Feist4131aea2018-03-09 09:47:30 -0800986 else
987 {
988 pendingCallback = true;
989 }
James Feist75fdeeb2018-02-20 14:26:16 -0800990 if (dbusFilter != nullptr)
991 {
992 dbusFilter->async_dispatch([&, dbusFilter](boost::system::error_code ec,
993 dbus::message) {
994 if (ec)
995 {
996 std::cerr << "properties changed callback error " << ec << "\n";
997 }
James Feist4131aea2018-03-09 09:47:30 -0800998 propertiesChangedCallback(dbusMatches, systemConfiguration,
999 objServer, dbusFilter);
James Feist75fdeeb2018-02-20 14:26:16 -08001000 });
1001 }
1002}
1003
1004void registerCallbacks(
1005 std::vector<std::pair<std::unique_ptr<dbus::match>,
1006 std::shared_ptr<dbus::filter>>> &dbusMatches,
James Feist4131aea2018-03-09 09:47:30 -08001007 nlohmann::json &systemConfiguration, dbus::DbusObjectServer &objServer)
James Feist75fdeeb2018-02-20 14:26:16 -08001008{
1009 static boost::container::flat_set<std::string> watchedObjects;
1010
1011 for (const auto &objectMap : DBUS_PROBE_OBJECTS)
1012 {
1013 auto findObject = watchedObjects.find(objectMap.first);
1014 if (findObject != watchedObjects.end())
1015 {
1016 continue;
1017 }
1018 // this creates a filter for properties changed for any new probe type
1019 auto propertyChange = std::make_unique<dbus::match>(
James Feist11be6672018-04-06 14:05:32 -07001020 SYSTEM_BUS, "type='signal',member='PropertiesChanged',arg0='" +
1021 objectMap.first + "'");
James Feist75fdeeb2018-02-20 14:26:16 -08001022 auto filter =
1023 std::make_shared<dbus::filter>(SYSTEM_BUS, [](dbus::message &m) {
1024 auto member = m.get_member();
1025 return member == "PropertiesChanged";
1026 });
1027
1028 filter->async_dispatch([&, filter](boost::system::error_code ec,
1029 dbus::message) {
1030 if (ec)
1031 {
1032 std::cerr << "register callbacks callback error " << ec << "\n";
1033 }
James Feist4131aea2018-03-09 09:47:30 -08001034 propertiesChangedCallback(dbusMatches, systemConfiguration,
1035 objServer, filter);
James Feist75fdeeb2018-02-20 14:26:16 -08001036 });
1037 dbusMatches.emplace_back(std::move(propertyChange), filter);
1038 }
1039}
1040
1041int main(int argc, char **argv)
1042{
1043 // setup connection to dbus
1044 boost::asio::io_service io;
1045 SYSTEM_BUS = std::make_shared<dbus::connection>(io, dbus::bus::system);
James Feist4131aea2018-03-09 09:47:30 -08001046
James Feist75fdeeb2018-02-20 14:26:16 -08001047 dbus::DbusObjectServer objServer(SYSTEM_BUS);
1048 SYSTEM_BUS->request_name("xyz.openbmc_project.EntityManager");
James Feist75fdeeb2018-02-20 14:26:16 -08001049 std::vector<
1050 std::pair<std::unique_ptr<dbus::match>, std::shared_ptr<dbus::filter>>>
1051 dbusMatches;
James Feist4131aea2018-03-09 09:47:30 -08001052
1053 nlohmann::json systemConfiguration = nlohmann::json::object();
1054 auto iface = std::make_shared<dbus::DbusInterface>(
1055 "xyz.openbmc_project.EntityManager", SYSTEM_BUS);
1056 io.post([&]() {
1057 unloadAllOverlays();
1058 propertiesChangedCallback(dbusMatches, systemConfiguration, objServer,
1059 nullptr);
1060 auto object = std::make_shared<dbus::DbusObject>(
1061 SYSTEM_BUS, "/xyz/openbmc_project/EntityManager");
1062 objServer.register_object(object);
1063
1064 object->register_interface(iface);
1065
1066 });
1067
1068 // to keep reference to the match / filter objects so they don't get
1069 // destroyed
1070
James Feist75fdeeb2018-02-20 14:26:16 -08001071 iface->register_method("ReScan", [&]() {
James Feist4131aea2018-03-09 09:47:30 -08001072 propertiesChangedCallback(dbusMatches, systemConfiguration, objServer,
1073 nullptr);
James Feist75fdeeb2018-02-20 14:26:16 -08001074 return std::tuple<>(); // this is a bug in boost-dbus, needs some sort
1075 // of return
1076 });
James Feist75fdeeb2018-02-20 14:26:16 -08001077
James Feist1b2e2242018-01-30 13:45:19 -08001078 io.run();
James Feist3cb5fec2018-01-23 14:41:51 -08001079
1080 return 0;
James Feist75fdeeb2018-02-20 14:26:16 -08001081}