blob: a72769358c90888d7e210d24f634ce755b828686 [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 {
James Feist9eb0b582018-04-27 12:15:46 -0700223 case nlohmann::json::value_t::string:
James Feist3cb5fec2018-01-23 14:41:51 -0800224 {
James Feist9eb0b582018-04-27 12:15:46 -0700225 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 }
James Feist3cb5fec2018-01-23 14:41:51 -0800239 break;
240 }
James Feist9eb0b582018-04-27 12:15:46 -0700241 case nlohmann::json::value_t::boolean:
242 case nlohmann::json::value_t::number_unsigned:
James Feist3cb5fec2018-01-23 14:41:51 -0800243 {
James Feist9eb0b582018-04-27 12:15:46 -0700244 unsigned int probeValue = boost::apply_visitor(
245 VariantToUnsignedIntVisitor(), deviceValue->second);
James Feist3cb5fec2018-01-23 14:41:51 -0800246
James Feist9eb0b582018-04-27 12:15:46 -0700247 if (probeValue != match.second.get<unsigned int>())
248 {
249 deviceMatches = false;
250 }
251 break;
James Feist3cb5fec2018-01-23 14:41:51 -0800252 }
James Feist9eb0b582018-04-27 12:15:46 -0700253 case nlohmann::json::value_t::number_integer:
254 {
255 int probeValue = boost::apply_visitor(
256 VariantToIntVisitor(), deviceValue->second);
James Feist3cb5fec2018-01-23 14:41:51 -0800257
James Feist9eb0b582018-04-27 12:15:46 -0700258 if (probeValue != match.second.get<int>())
259 {
260 deviceMatches = false;
261 }
262 break;
James Feist3cb5fec2018-01-23 14:41:51 -0800263 }
James Feist9eb0b582018-04-27 12:15:46 -0700264 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 }
James Feist3cb5fec2018-01-23 14:41:51 -0800275 }
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 {
James Feist9eb0b582018-04-27 12:15:46 -0700328 case probe_type_codes::FALSE_T:
James Feist3cb5fec2018-01-23 14:41:51 -0800329 {
James Feist9eb0b582018-04-27 12:15:46 -0700330 return false; // todo, actually evaluate?
331 break;
James Feist3cb5fec2018-01-23 14:41:51 -0800332 }
James Feist9eb0b582018-04-27 12:15:46 -0700333 case probe_type_codes::TRUE_T:
334 {
335 return true; // todo, actually evaluate?
336 break;
337 }
338 case probe_type_codes::MATCH_ONE:
339 {
340 // set current value to last, this probe type shouldn't
341 // affect the outcome
342 cur = ret;
343 matchOne = true;
344 break;
345 }
346 /*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
357 << "\n";
358 return false;
359 }
360 std::string commandStr = *(match.begin() + 1);
361 boost::replace_all(commandStr, "'", "");
362 cur = (std::find(PASSED_PROBES.begin(), PASSED_PROBES.end(),
363 commandStr) != PASSED_PROBES.end());
364 break;
365 }
James Feist3cb5fec2018-01-23 14:41:51 -0800366 }
367 }
368 // look on dbus for object
369 else
370 {
371 if (!std::regex_search(probe, match, command))
372 {
373 std::cerr << "dbus probe sytax error " << probe << "\n";
374 return false;
375 }
376 std::string commandStr = *(match.begin() + 1);
377 // convert single ticks and single slashes into legal json
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -0700378 boost::replace_all(commandStr, "'", "\"");
James Feist3f8a2782018-02-12 09:24:42 -0800379 boost::replace_all(commandStr, R"(\)", R"(\\)");
James Feist3cb5fec2018-01-23 14:41:51 -0800380 auto json = nlohmann::json::parse(commandStr, nullptr, false);
381 if (json.is_discarded())
382 {
383 std::cerr << "dbus command sytax error " << commandStr << "\n";
384 return false;
385 }
386 // we can match any (string, variant) property. (string, string)
387 // does a regex
388 std::map<std::string, nlohmann::json> dbusProbeMap =
389 json.get<std::map<std::string, nlohmann::json>>();
390 auto findStart = probe.find("(");
391 if (findStart == std::string::npos)
392 {
393 return false;
394 }
395 std::string probeInterface = probe.substr(0, findStart);
396 cur =
397 probeDbus(probeInterface, dbusProbeMap, foundDevs, foundProbe);
398 }
399
400 // some functions like AND and OR only take affect after the
401 // fact
402 switch (lastCommand)
403 {
James Feist9eb0b582018-04-27 12:15:46 -0700404 case probe_type_codes::AND:
405 ret = cur && ret;
406 break;
407 case probe_type_codes::OR:
408 ret = cur || ret;
409 break;
410 default:
411 ret = cur;
412 break;
James Feist3cb5fec2018-01-23 14:41:51 -0800413 }
414 lastCommand = probeType != PROBE_TYPES.end()
415 ? probeType->second
416 : probe_type_codes::FALSE_T;
417
418 if (!foundProbe)
419 {
420 std::cerr << "Illegal probe type " << probe << "\n";
421 return false;
422 }
423 }
424
425 // probe passed, but empty device
426 // todo: should this be done in main?
427 if (ret && foundDevs.size() == 0)
428 {
429 foundDevs.emplace_back(
430 boost::container::flat_map<std::string, dbus::dbus_variant>());
431 }
James Feist6bd2a022018-03-13 12:30:58 -0700432 if (matchOne && foundDevs.size() > 1)
433 {
434 foundDevs.erase(foundDevs.begin() + 1, foundDevs.end());
435 }
James Feist3cb5fec2018-01-23 14:41:51 -0800436 return ret;
437}
438
James Feist1b2e2242018-01-30 13:45:19 -0800439// this function is temporary, no need to have once dbus is solified.
440void writeJsonFiles(nlohmann::json &systemConfiguration)
441{
442 std::experimental::filesystem::create_directory(OUTPUT_DIR);
443 std::ofstream output(std::string(OUTPUT_DIR) + "system.json");
444 output << systemConfiguration.dump(4);
445 output.close();
446
447 auto flat = nlohmann::json::array();
James Feist9eb0b582018-04-27 12:15:46 -0700448 for (auto &pair : systemConfiguration.items())
James Feist1b2e2242018-01-30 13:45:19 -0800449 {
450 auto value = pair.value();
451 auto exposes = value.find("exposes");
452 if (exposes != value.end())
453 {
454 for (auto &item : *exposes)
455 {
456 flat.push_back(item);
457 }
458 }
459 }
460 output = std::ofstream(std::string(OUTPUT_DIR) + "flattened.json");
461 output << flat.dump(4);
462 output.close();
463}
464// adds simple json types to interface's properties
465void populateInterfaceFromJson(dbus::DbusInterface *iface, nlohmann::json dict,
466 dbus::DbusObjectServer &objServer)
467{
468 std::vector<std::pair<std::string, dbus::dbus_variant>> properties;
469 static size_t flushCount = 0;
470
James Feist9eb0b582018-04-27 12:15:46 -0700471 for (auto &dictPair : dict.items())
James Feist1b2e2242018-01-30 13:45:19 -0800472 {
473 switch (dictPair.value().type())
474 {
James Feist9eb0b582018-04-27 12:15:46 -0700475 case (nlohmann::json::value_t::boolean):
476 {
477 properties.emplace_back(std::string(dictPair.key()),
478 dictPair.value().get<bool>());
479 break;
480 }
481 case (nlohmann::json::value_t::number_integer):
482 {
483 properties.emplace_back(std::string(dictPair.key()),
484 dictPair.value().get<int64_t>());
485 break;
486 }
487 case (nlohmann::json::value_t::number_unsigned):
488 {
489 properties.emplace_back(std::string(dictPair.key()),
490 dictPair.value().get<uint64_t>());
491 break;
492 }
493 case (nlohmann::json::value_t::number_float):
494 {
495 properties.emplace_back(std::string(dictPair.key()),
496 dictPair.value().get<float>());
497 break;
498 }
499 case (nlohmann::json::value_t::string):
500 {
501 properties.emplace_back(std::string(dictPair.key()),
502 dictPair.value().get<std::string>());
503 break;
504 }
James Feist1b2e2242018-01-30 13:45:19 -0800505 }
506 }
507 if (!properties.empty())
508 {
509 iface->set_properties(properties);
510
511 // flush the queue after adding an amount of properties so we don't hang
James Feistc95cb142018-02-26 10:41:42 -0800512 if (flushCount++ > PROPERTIES_CHANGED_UNTIL_FLUSH_COUNT)
James Feist1b2e2242018-01-30 13:45:19 -0800513 {
514 objServer.flush();
515 flushCount = 0;
516 }
517 }
518}
519
520void postToDbus(const nlohmann::json &systemConfiguration,
James Feist75fdeeb2018-02-20 14:26:16 -0800521 dbus::DbusObjectServer &objServer)
522
James Feist1b2e2242018-01-30 13:45:19 -0800523{
James Feist9eb0b582018-04-27 12:15:46 -0700524 for (auto &boardPair : systemConfiguration.items())
James Feist1b2e2242018-01-30 13:45:19 -0800525 {
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 Feist9eb0b582018-04-27 12:15:46 -0700557 for (auto &boardField : boardValues.items())
James Feist11be6672018-04-06 14:05:32 -0700558 {
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
James Feist9eb0b582018-04-27 12:15:46 -0700609 for (auto &objectPair : item.items())
James Feist1b2e2242018-01-30 13:45:19 -0800610 {
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 :
James Feist9eb0b582018-04-27 12:15:46 -0700873 systemConfiguration.items())
James Feist3cb5fec2018-01-23 14:41:51 -0800874 {
James Feist1b2e2242018-01-30 13:45:19 -0800875
876 auto configListFind =
877 configurationPair.value().find(
878 "exposes");
879
880 if (configListFind ==
881 configurationPair.value().end() ||
882 configListFind->type() !=
883 nlohmann::json::value_t::array)
James Feist3cb5fec2018-01-23 14:41:51 -0800884 {
James Feist1b2e2242018-01-30 13:45:19 -0800885 continue;
886 }
887 for (auto &exposedObject : *configListFind)
888 {
889 std::string foundObjectName =
890 (exposedObject)["name"];
891 if (boost::iequals(
892 foundObjectName,
893 keyPair.value()
894 .get<std::string>()))
James Feist3cb5fec2018-01-23 14:41:51 -0800895 {
James Feistc95cb142018-02-26 10:41:42 -0800896 exposedObject["status"] = "okay";
James Feist1b2e2242018-01-30 13:45:19 -0800897 expose[bind] = exposedObject;
James Feist1b2e2242018-01-30 13:45:19 -0800898
899 foundBind = true;
James Feist3cb5fec2018-01-23 14:41:51 -0800900 break;
901 }
902 }
James Feist1b2e2242018-01-30 13:45:19 -0800903 if (foundBind)
James Feist3cb5fec2018-01-23 14:41:51 -0800904 {
James Feist1b2e2242018-01-30 13:45:19 -0800905 break;
James Feist3cb5fec2018-01-23 14:41:51 -0800906 }
907 }
James Feist1b2e2242018-01-30 13:45:19 -0800908 if (!foundBind)
909 {
910 std::cerr << "configuration file "
911 "dependency error, "
912 "could not find bind "
913 << keyPair.value() << "\n";
914 }
James Feist3cb5fec2018-01-23 14:41:51 -0800915 }
916 }
917 }
James Feist3cb5fec2018-01-23 14:41:51 -0800918 }
James Feist1b2e2242018-01-30 13:45:19 -0800919 systemConfiguration[name] = (*it);
920 foundDeviceIdx++;
James Feist3cb5fec2018-01-23 14:41:51 -0800921 }
922
923 if (eraseConfig)
924 {
925 it = configurations.erase(it);
926 }
927 else
928 {
929 it++;
930 }
931 }
932 }
James Feist75fdeeb2018-02-20 14:26:16 -0800933}
934
935void propertiesChangedCallback(
936 std::vector<std::pair<std::unique_ptr<dbus::match>,
937 std::shared_ptr<dbus::filter>>> &dbusMatches,
James Feist4131aea2018-03-09 09:47:30 -0800938 nlohmann::json &systemConfiguration, dbus::DbusObjectServer &objServer,
939 std::shared_ptr<dbus::filter> dbusFilter)
James Feist75fdeeb2018-02-20 14:26:16 -0800940{
941 static std::future<void> future;
James Feist4131aea2018-03-09 09:47:30 -0800942 static std::atomic_bool threadRunning(false);
943 static std::atomic_bool pendingCallback(false);
James Feist75fdeeb2018-02-20 14:26:16 -0800944 bool notRunning = false;
945 if (threadRunning.compare_exchange_strong(notRunning, true))
946 {
947 future = std::async(std::launch::async, [&] {
James Feistc95cb142018-02-26 10:41:42 -0800948
James Feist4131aea2018-03-09 09:47:30 -0800949 do
950 {
951 std::this_thread::sleep_for(std::chrono::seconds(
952 SLEEP_AFTER_PROPERTIES_CHANGE_SECONDS));
953 auto oldConfiguration = systemConfiguration;
954 DBUS_PROBE_OBJECTS.clear();
955 pendingCallback = false;
956 rescan(systemConfiguration);
957 auto newConfiguration = systemConfiguration;
958 for (auto it = newConfiguration.begin();
959 it != newConfiguration.end();)
960 {
961 auto findKey = oldConfiguration.find(it.key());
962 if (findKey != oldConfiguration.end())
963 {
964 it = newConfiguration.erase(it);
965 }
966 else
967 {
968 it++;
969 }
970 }
971
972 registerCallbacks(dbusMatches, systemConfiguration, objServer);
973 // todo: for now, only add new configurations, unload to come
974 // later
975 // unloadOverlays();
976 loadOverlays(newConfiguration);
977 // this line to be removed in future
978 writeJsonFiles(systemConfiguration);
979 // only post new items to bus for now
980 postToDbus(newConfiguration, objServer);
981 } while (pendingCallback);
James Feist75fdeeb2018-02-20 14:26:16 -0800982 threadRunning = false;
983 });
984 }
James Feist4131aea2018-03-09 09:47:30 -0800985 else
986 {
987 pendingCallback = true;
988 }
James Feist75fdeeb2018-02-20 14:26:16 -0800989 if (dbusFilter != nullptr)
990 {
991 dbusFilter->async_dispatch([&, dbusFilter](boost::system::error_code ec,
992 dbus::message) {
993 if (ec)
994 {
995 std::cerr << "properties changed callback error " << ec << "\n";
996 }
James Feist4131aea2018-03-09 09:47:30 -0800997 propertiesChangedCallback(dbusMatches, systemConfiguration,
998 objServer, dbusFilter);
James Feist75fdeeb2018-02-20 14:26:16 -0800999 });
1000 }
1001}
1002
1003void registerCallbacks(
1004 std::vector<std::pair<std::unique_ptr<dbus::match>,
1005 std::shared_ptr<dbus::filter>>> &dbusMatches,
James Feist4131aea2018-03-09 09:47:30 -08001006 nlohmann::json &systemConfiguration, dbus::DbusObjectServer &objServer)
James Feist75fdeeb2018-02-20 14:26:16 -08001007{
1008 static boost::container::flat_set<std::string> watchedObjects;
1009
1010 for (const auto &objectMap : DBUS_PROBE_OBJECTS)
1011 {
1012 auto findObject = watchedObjects.find(objectMap.first);
1013 if (findObject != watchedObjects.end())
1014 {
1015 continue;
1016 }
1017 // this creates a filter for properties changed for any new probe type
1018 auto propertyChange = std::make_unique<dbus::match>(
James Feist11be6672018-04-06 14:05:32 -07001019 SYSTEM_BUS, "type='signal',member='PropertiesChanged',arg0='" +
1020 objectMap.first + "'");
James Feist75fdeeb2018-02-20 14:26:16 -08001021 auto filter =
1022 std::make_shared<dbus::filter>(SYSTEM_BUS, [](dbus::message &m) {
1023 auto member = m.get_member();
1024 return member == "PropertiesChanged";
1025 });
1026
1027 filter->async_dispatch([&, filter](boost::system::error_code ec,
1028 dbus::message) {
1029 if (ec)
1030 {
1031 std::cerr << "register callbacks callback error " << ec << "\n";
1032 }
James Feist4131aea2018-03-09 09:47:30 -08001033 propertiesChangedCallback(dbusMatches, systemConfiguration,
1034 objServer, filter);
James Feist75fdeeb2018-02-20 14:26:16 -08001035 });
1036 dbusMatches.emplace_back(std::move(propertyChange), filter);
1037 }
1038}
1039
1040int main(int argc, char **argv)
1041{
1042 // setup connection to dbus
1043 boost::asio::io_service io;
1044 SYSTEM_BUS = std::make_shared<dbus::connection>(io, dbus::bus::system);
James Feist4131aea2018-03-09 09:47:30 -08001045
James Feist75fdeeb2018-02-20 14:26:16 -08001046 dbus::DbusObjectServer objServer(SYSTEM_BUS);
1047 SYSTEM_BUS->request_name("xyz.openbmc_project.EntityManager");
James Feist75fdeeb2018-02-20 14:26:16 -08001048 std::vector<
1049 std::pair<std::unique_ptr<dbus::match>, std::shared_ptr<dbus::filter>>>
1050 dbusMatches;
James Feist4131aea2018-03-09 09:47:30 -08001051
1052 nlohmann::json systemConfiguration = nlohmann::json::object();
James Feistfd1264a2018-05-03 12:10:00 -07001053 auto entityIface = std::make_shared<dbus::DbusInterface>(
James Feist4131aea2018-03-09 09:47:30 -08001054 "xyz.openbmc_project.EntityManager", SYSTEM_BUS);
James Feistfd1264a2018-05-03 12:10:00 -07001055 auto inventoryIface = std::make_shared<dbus::DbusInterface>(
1056 "xyz.openbmc_project.Inventory.Manager", SYSTEM_BUS);
1057 auto inventoryObject = std::make_shared<dbus::DbusObject>(
1058 SYSTEM_BUS, "/xyz/openbmc_project/inventory");
1059 objServer.register_object(inventoryObject);
1060
1061 inventoryIface->register_method(
1062 "Notify",
1063 [](const boost::container::flat_map<
1064 std::string,
1065 boost::container::flat_map<std::string, dbus::dbus_variant>>
1066 &object) { return std::tuple<>(); });
1067
1068 inventoryObject->register_interface(inventoryIface);
James Feist4131aea2018-03-09 09:47:30 -08001069 io.post([&]() {
1070 unloadAllOverlays();
1071 propertiesChangedCallback(dbusMatches, systemConfiguration, objServer,
1072 nullptr);
James Feistfd1264a2018-05-03 12:10:00 -07001073 auto entityObject = std::make_shared<dbus::DbusObject>(
James Feist4131aea2018-03-09 09:47:30 -08001074 SYSTEM_BUS, "/xyz/openbmc_project/EntityManager");
James Feistfd1264a2018-05-03 12:10:00 -07001075 objServer.register_object(entityObject);
1076 entityObject->register_interface(entityIface);
James Feist4131aea2018-03-09 09:47:30 -08001077
1078 });
1079
1080 // to keep reference to the match / filter objects so they don't get
1081 // destroyed
1082
James Feistfd1264a2018-05-03 12:10:00 -07001083 entityIface->register_method("ReScan", [&]() {
James Feist4131aea2018-03-09 09:47:30 -08001084 propertiesChangedCallback(dbusMatches, systemConfiguration, objServer,
1085 nullptr);
James Feist75fdeeb2018-02-20 14:26:16 -08001086 return std::tuple<>(); // this is a bug in boost-dbus, needs some sort
1087 // of return
1088 });
James Feist75fdeeb2018-02-20 14:26:16 -08001089
James Feist1b2e2242018-01-30 13:45:19 -08001090 io.run();
James Feist3cb5fec2018-01-23 14:41:51 -08001091
1092 return 0;
James Feist75fdeeb2018-02-20 14:26:16 -08001093}