blob: 866baa72780ae37190cb7fb47fa0c90e3594ff4d [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 <nlohmann/json.hpp>
20#include <fstream>
21#include <regex>
James Feist8f2710a2018-05-09 17:18:55 -070022#include <iostream>
23#include <sdbusplus/asio/connection.hpp>
24#include <sdbusplus/asio/object_server.hpp>
James Feist11be6672018-04-06 14:05:32 -070025#include <boost/algorithm/string/case_conv.hpp>
James Feist3cb5fec2018-01-23 14:41:51 -080026#include <boost/algorithm/string/predicate.hpp>
27#include <boost/algorithm/string/replace.hpp>
28#include <boost/variant/apply_visitor.hpp>
29#include <boost/lexical_cast.hpp>
30#include <boost/container/flat_map.hpp>
31#include <boost/container/flat_set.hpp>
32#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/";
James Feistb4383f42018-08-06 16:54:10 -070036constexpr const char *configurationDirectory = "/usr/share/configurations";
37constexpr const char *schemaDirectory = "/usr/share/configurations/schemas";
38constexpr const char *globalSchema = "global.json";
James Feist3cb5fec2018-01-23 14:41:51 -080039constexpr const char *TEMPLATE_CHAR = "$";
James Feistc95cb142018-02-26 10:41:42 -080040constexpr const size_t PROPERTIES_CHANGED_UNTIL_FLUSH_COUNT = 20;
James Feist8f2710a2018-05-09 17:18:55 -070041constexpr const int32_t MAX_MAPPER_DEPTH = 0;
James Feist4131aea2018-03-09 09:47:30 -080042constexpr const size_t SLEEP_AFTER_PROPERTIES_CHANGE_SECONDS = 5;
James Feist3cb5fec2018-01-23 14:41:51 -080043
44namespace fs = std::experimental::filesystem;
45struct cmp_str
46{
47 bool operator()(const char *a, const char *b) const
48 {
49 return std::strcmp(a, b) < 0;
50 }
51};
52
James Feist8f2710a2018-05-09 17:18:55 -070053struct PerformProbe;
54
James Feist3cb5fec2018-01-23 14:41:51 -080055// underscore T for collison with dbus c api
56enum class probe_type_codes
57{
58 FALSE_T,
59 TRUE_T,
60 AND,
61 OR,
James Feist6bd2a022018-03-13 12:30:58 -070062 FOUND,
63 MATCH_ONE
James Feist3cb5fec2018-01-23 14:41:51 -080064};
65const static boost::container::flat_map<const char *, probe_type_codes, cmp_str>
66 PROBE_TYPES{{{"FALSE", probe_type_codes::FALSE_T},
67 {"TRUE", probe_type_codes::TRUE_T},
68 {"AND", probe_type_codes::AND},
69 {"OR", probe_type_codes::OR},
James Feist6bd2a022018-03-13 12:30:58 -070070 {"FOUND", probe_type_codes::FOUND},
71 {"MATCH_ONE", probe_type_codes::MATCH_ONE}}};
James Feist3cb5fec2018-01-23 14:41:51 -080072
James Feist97a63f12018-05-17 13:50:57 -070073static constexpr std::array<const char *, 1> SETTABLE_INTERFACES = {
James Feist1e3e6982018-08-03 16:09:28 -070074 "Thresholds"};
James Feist68500ff2018-08-08 15:40:42 -070075using JsonVariantType =
76 sdbusplus::message::variant<std::vector<std::string>, std::string, int64_t,
77 uint64_t, double, int32_t, uint32_t, int16_t,
78 uint16_t, uint8_t, bool>;
James Feist8f2710a2018-05-09 17:18:55 -070079using BasicVariantType =
80 sdbusplus::message::variant<std::string, int64_t, uint64_t, double, int32_t,
81 uint32_t, int16_t, uint16_t, uint8_t, bool>;
82
James Feist3cb5fec2018-01-23 14:41:51 -080083using GetSubTreeType = std::vector<
84 std::pair<std::string,
85 std::vector<std::pair<std::string, std::vector<std::string>>>>>;
86
87using ManagedObjectType = boost::container::flat_map<
James Feist8f2710a2018-05-09 17:18:55 -070088 sdbusplus::message::object_path,
James Feist3cb5fec2018-01-23 14:41:51 -080089 boost::container::flat_map<
90 std::string,
James Feist8f2710a2018-05-09 17:18:55 -070091 boost::container::flat_map<std::string, BasicVariantType>>>;
James Feist3cb5fec2018-01-23 14:41:51 -080092
93boost::container::flat_map<
94 std::string,
James Feist8f2710a2018-05-09 17:18:55 -070095 std::vector<boost::container::flat_map<std::string, BasicVariantType>>>
James Feist3cb5fec2018-01-23 14:41:51 -080096 DBUS_PROBE_OBJECTS;
97std::vector<std::string> PASSED_PROBES;
98
99// todo: pass this through nicer
James Feist8f2710a2018-05-09 17:18:55 -0700100std::shared_ptr<sdbusplus::asio::connection> SYSTEM_BUS;
James Feist3cb5fec2018-01-23 14:41:51 -0800101
James Feista6750242018-07-16 14:12:27 -0700102const std::regex ILLEGAL_DBUS_REGEX("[^A-Za-z0-9_.]");
James Feist1b2e2242018-01-30 13:45:19 -0800103
James Feist8f2710a2018-05-09 17:18:55 -0700104void registerCallbacks(boost::asio::io_service &io,
105 std::vector<sdbusplus::bus::match::match> &dbusMatches,
106 nlohmann::json &systemConfiguration,
107 sdbusplus::asio::object_server &objServer);
James Feist75fdeeb2018-02-20 14:26:16 -0800108
James Feist3cb5fec2018-01-23 14:41:51 -0800109// calls the mapper to find all exposed objects of an interface type
110// and creates a vector<flat_map> that contains all the key value pairs
111// getManagedObjects
James Feist8f2710a2018-05-09 17:18:55 -0700112void findDbusObjects(std::shared_ptr<PerformProbe> probe,
113 std::shared_ptr<sdbusplus::asio::connection> connection,
114 std::string &interface)
James Feist3cb5fec2018-01-23 14:41:51 -0800115{
James Feist8f2710a2018-05-09 17:18:55 -0700116
117 // store reference to pending callbacks so we don't overwhelm services
118 static boost::container::flat_map<
119 std::string, std::vector<std::shared_ptr<PerformProbe>>>
120 pendingProbes;
121
122 if (DBUS_PROBE_OBJECTS[interface].size())
123 {
124 return;
125 }
126
127 // add shared_ptr to vector of Probes waiting for callback from a specific
128 // interface to keep alive while waiting for response
129 std::array<const char *, 1> objects = {interface.c_str()};
130 std::vector<std::shared_ptr<PerformProbe>> &pending =
131 pendingProbes[interface];
132 auto iter = pending.emplace(pending.end(), probe);
133 // only allow first call to run to not overwhelm processes
134 if (iter != pending.begin())
135 {
136 return;
137 }
138
James Feist3cb5fec2018-01-23 14:41:51 -0800139 // find all connections in the mapper that expose a specific type
James Feist8f2710a2018-05-09 17:18:55 -0700140 connection->async_method_call(
James Feist0de40152018-07-25 11:56:12 -0700141 [connection, interface, probe](boost::system::error_code &ec,
142 const GetSubTreeType &interfaceSubtree) {
143 boost::container::flat_set<std::string> interfaceConnections;
James Feist8f2710a2018-05-09 17:18:55 -0700144 if (ec)
James Feist494155a2018-03-14 16:23:24 -0700145 {
James Feist0de40152018-07-25 11:56:12 -0700146 pendingProbes[interface].clear();
147 if (ec.value() == ENOENT)
James Feist8f2710a2018-05-09 17:18:55 -0700148 {
James Feist0de40152018-07-25 11:56:12 -0700149 return; // wasn't found by mapper
James Feist8f2710a2018-05-09 17:18:55 -0700150 }
James Feist0de40152018-07-25 11:56:12 -0700151 std::cerr << "Error communicating to mapper.\n";
152
153 // if we can't communicate to the mapper something is very wrong
154 std::exit(EXIT_FAILURE);
James Feist494155a2018-03-14 16:23:24 -0700155 }
James Feist8f2710a2018-05-09 17:18:55 -0700156 else
James Feist3cb5fec2018-01-23 14:41:51 -0800157 {
James Feist8f2710a2018-05-09 17:18:55 -0700158 for (auto &object : interfaceSubtree)
159 {
160 for (auto &connPair : object.second)
161 {
162 auto insertPair =
163 interfaceConnections.insert(connPair.first);
164 }
165 }
James Feist3cb5fec2018-01-23 14:41:51 -0800166 }
James Feist8f2710a2018-05-09 17:18:55 -0700167 // get managed objects for all interfaces
168 for (const auto &conn : interfaceConnections)
169 {
170 connection->async_method_call(
James Feist0de40152018-07-25 11:56:12 -0700171 [conn,
James Feist8f2710a2018-05-09 17:18:55 -0700172 interface](boost::system::error_code &ec,
173 const ManagedObjectType &managedInterface) {
174 if (ec)
175 {
176 std::cerr
177 << "error getting managed object for device "
178 << conn << "\n";
179 pendingProbes[interface].clear();
180 return;
181 }
182 for (auto &interfaceManagedObj : managedInterface)
183 {
184 auto ifaceObjFind =
185 interfaceManagedObj.second.find(interface);
186 if (ifaceObjFind !=
187 interfaceManagedObj.second.end())
188 {
189 std::vector<boost::container::flat_map<
190 std::string, BasicVariantType>>
191 &dbusObject = DBUS_PROBE_OBJECTS[interface];
192 dbusObject.emplace_back(ifaceObjFind->second);
193 }
194 }
195 pendingProbes[interface].clear();
196 },
197 conn.c_str(), "/", "org.freedesktop.DBus.ObjectManager",
198 "GetManagedObjects");
199 }
200 },
201 "xyz.openbmc_project.ObjectMapper",
202 "/xyz/openbmc_project/object_mapper",
203 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "", MAX_MAPPER_DEPTH,
204 objects);
James Feist3cb5fec2018-01-23 14:41:51 -0800205}
James Feist8f2710a2018-05-09 17:18:55 -0700206// probes dbus interface dictionary for a key with a value that matches a regex
James Feist3cb5fec2018-01-23 14:41:51 -0800207bool probeDbus(
208 const std::string &interface,
209 const std::map<std::string, nlohmann::json> &matches,
James Feist8f2710a2018-05-09 17:18:55 -0700210 std::vector<boost::container::flat_map<std::string, BasicVariantType>>
James Feist3cb5fec2018-01-23 14:41:51 -0800211 &devices,
212 bool &foundProbe)
213{
James Feist8f2710a2018-05-09 17:18:55 -0700214 std::vector<boost::container::flat_map<std::string, BasicVariantType>>
215 &dbusObject = DBUS_PROBE_OBJECTS[interface];
James Feist3cb5fec2018-01-23 14:41:51 -0800216 if (dbusObject.empty())
217 {
James Feist8f2710a2018-05-09 17:18:55 -0700218 foundProbe = false;
219 return false;
James Feist3cb5fec2018-01-23 14:41:51 -0800220 }
221 foundProbe = true;
222
223 bool foundMatch = false;
224 for (auto &device : dbusObject)
225 {
226 bool deviceMatches = true;
227 for (auto &match : matches)
228 {
229 auto deviceValue = device.find(match.first);
230 if (deviceValue != device.end())
231 {
232 switch (match.second.type())
233 {
James Feist9eb0b582018-04-27 12:15:46 -0700234 case nlohmann::json::value_t::string:
James Feist3cb5fec2018-01-23 14:41:51 -0800235 {
James Feist9eb0b582018-04-27 12:15:46 -0700236 std::regex search(match.second.get<std::string>());
237 std::smatch match;
238
239 // convert value to string respresentation
James Feist8f2710a2018-05-09 17:18:55 -0700240 std::string probeValue = mapbox::util::apply_visitor(
241 VariantToStringVisitor(), deviceValue->second);
James Feist9eb0b582018-04-27 12:15:46 -0700242 if (!std::regex_search(probeValue, match, search))
243 {
244 deviceMatches = false;
245 break;
246 }
James Feist3cb5fec2018-01-23 14:41:51 -0800247 break;
248 }
James Feist9eb0b582018-04-27 12:15:46 -0700249 case nlohmann::json::value_t::boolean:
250 case nlohmann::json::value_t::number_unsigned:
James Feist3cb5fec2018-01-23 14:41:51 -0800251 {
James Feist8f2710a2018-05-09 17:18:55 -0700252 unsigned int probeValue = mapbox::util::apply_visitor(
James Feist9eb0b582018-04-27 12:15:46 -0700253 VariantToUnsignedIntVisitor(), deviceValue->second);
James Feist3cb5fec2018-01-23 14:41:51 -0800254
James Feist9eb0b582018-04-27 12:15:46 -0700255 if (probeValue != match.second.get<unsigned int>())
256 {
257 deviceMatches = false;
258 }
259 break;
James Feist3cb5fec2018-01-23 14:41:51 -0800260 }
James Feist9eb0b582018-04-27 12:15:46 -0700261 case nlohmann::json::value_t::number_integer:
262 {
James Feist8f2710a2018-05-09 17:18:55 -0700263 int probeValue = mapbox::util::apply_visitor(
James Feist9eb0b582018-04-27 12:15:46 -0700264 VariantToIntVisitor(), deviceValue->second);
James Feist3cb5fec2018-01-23 14:41:51 -0800265
James Feist9eb0b582018-04-27 12:15:46 -0700266 if (probeValue != match.second.get<int>())
267 {
268 deviceMatches = false;
269 }
270 break;
James Feist3cb5fec2018-01-23 14:41:51 -0800271 }
James Feist9eb0b582018-04-27 12:15:46 -0700272 case nlohmann::json::value_t::number_float:
273 {
James Feist8f2710a2018-05-09 17:18:55 -0700274 float probeValue = mapbox::util::apply_visitor(
James Feist9eb0b582018-04-27 12:15:46 -0700275 VariantToFloatVisitor(), deviceValue->second);
276
277 if (probeValue != match.second.get<float>())
278 {
279 deviceMatches = false;
280 }
281 break;
282 }
James Feist3cb5fec2018-01-23 14:41:51 -0800283 }
284 }
285 else
286 {
287 deviceMatches = false;
288 break;
289 }
290 }
291 if (deviceMatches)
292 {
293 devices.emplace_back(
James Feist8f2710a2018-05-09 17:18:55 -0700294 boost::container::flat_map<std::string, BasicVariantType>(
James Feist3cb5fec2018-01-23 14:41:51 -0800295 device));
296 foundMatch = true;
297 deviceMatches = false; // for next iteration
298 }
299 }
300 return foundMatch;
301}
302
303// default probe entry point, iterates a list looking for specific types to
304// call specific probe functions
305bool probe(
James Feist8f2710a2018-05-09 17:18:55 -0700306 const std::vector<std::string> &probeCommand,
307 std::vector<boost::container::flat_map<std::string, BasicVariantType>>
James Feist3cb5fec2018-01-23 14:41:51 -0800308 &foundDevs)
309{
310 const static std::regex command(R"(\((.*)\))");
311 std::smatch match;
312 bool ret = false;
James Feist6bd2a022018-03-13 12:30:58 -0700313 bool matchOne = false;
James Feist3cb5fec2018-01-23 14:41:51 -0800314 bool cur = true;
315 probe_type_codes lastCommand = probe_type_codes::FALSE_T;
316
317 for (auto &probe : probeCommand)
318 {
319 bool foundProbe = false;
320 boost::container::flat_map<const char *, probe_type_codes,
321 cmp_str>::const_iterator probeType;
322
323 for (probeType = PROBE_TYPES.begin(); probeType != PROBE_TYPES.end();
324 probeType++)
325 {
326 if (probe.find(probeType->first) != std::string::npos)
327 {
328 foundProbe = true;
329 break;
330 }
331 }
332 if (foundProbe)
333 {
334 switch (probeType->second)
335 {
James Feist9eb0b582018-04-27 12:15:46 -0700336 case probe_type_codes::FALSE_T:
James Feist3cb5fec2018-01-23 14:41:51 -0800337 {
James Feist8f2710a2018-05-09 17:18:55 -0700338 return false;
James Feist9eb0b582018-04-27 12:15:46 -0700339 break;
James Feist3cb5fec2018-01-23 14:41:51 -0800340 }
James Feist9eb0b582018-04-27 12:15:46 -0700341 case probe_type_codes::TRUE_T:
342 {
James Feist8f2710a2018-05-09 17:18:55 -0700343 return true;
James Feist9eb0b582018-04-27 12:15:46 -0700344 break;
345 }
346 case probe_type_codes::MATCH_ONE:
347 {
348 // set current value to last, this probe type shouldn't
349 // affect the outcome
350 cur = ret;
351 matchOne = true;
352 break;
353 }
354 /*case probe_type_codes::AND:
355 break;
356 case probe_type_codes::OR:
357 break;
358 // these are no-ops until the last command switch
359 */
360 case probe_type_codes::FOUND:
361 {
362 if (!std::regex_search(probe, match, command))
363 {
364 std::cerr << "found probe sytax error " << probe
365 << "\n";
366 return false;
367 }
368 std::string commandStr = *(match.begin() + 1);
369 boost::replace_all(commandStr, "'", "");
370 cur = (std::find(PASSED_PROBES.begin(), PASSED_PROBES.end(),
371 commandStr) != PASSED_PROBES.end());
372 break;
373 }
James Feist3cb5fec2018-01-23 14:41:51 -0800374 }
375 }
376 // look on dbus for object
377 else
378 {
379 if (!std::regex_search(probe, match, command))
380 {
381 std::cerr << "dbus probe sytax error " << probe << "\n";
382 return false;
383 }
384 std::string commandStr = *(match.begin() + 1);
385 // convert single ticks and single slashes into legal json
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -0700386 boost::replace_all(commandStr, "'", "\"");
James Feist3f8a2782018-02-12 09:24:42 -0800387 boost::replace_all(commandStr, R"(\)", R"(\\)");
James Feist3cb5fec2018-01-23 14:41:51 -0800388 auto json = nlohmann::json::parse(commandStr, nullptr, false);
389 if (json.is_discarded())
390 {
391 std::cerr << "dbus command sytax error " << commandStr << "\n";
392 return false;
393 }
394 // we can match any (string, variant) property. (string, string)
395 // does a regex
396 std::map<std::string, nlohmann::json> dbusProbeMap =
397 json.get<std::map<std::string, nlohmann::json>>();
398 auto findStart = probe.find("(");
399 if (findStart == std::string::npos)
400 {
401 return false;
402 }
403 std::string probeInterface = probe.substr(0, findStart);
404 cur =
405 probeDbus(probeInterface, dbusProbeMap, foundDevs, foundProbe);
406 }
407
408 // some functions like AND and OR only take affect after the
409 // fact
410 switch (lastCommand)
411 {
James Feist9eb0b582018-04-27 12:15:46 -0700412 case probe_type_codes::AND:
413 ret = cur && ret;
414 break;
415 case probe_type_codes::OR:
416 ret = cur || ret;
417 break;
418 default:
419 ret = cur;
420 break;
James Feist3cb5fec2018-01-23 14:41:51 -0800421 }
422 lastCommand = probeType != PROBE_TYPES.end()
423 ? probeType->second
424 : probe_type_codes::FALSE_T;
James Feist3cb5fec2018-01-23 14:41:51 -0800425 }
426
427 // probe passed, but empty device
James Feist3cb5fec2018-01-23 14:41:51 -0800428 if (ret && foundDevs.size() == 0)
429 {
430 foundDevs.emplace_back(
James Feist8f2710a2018-05-09 17:18:55 -0700431 boost::container::flat_map<std::string, BasicVariantType>());
James Feist3cb5fec2018-01-23 14:41:51 -0800432 }
James Feist6bd2a022018-03-13 12:30:58 -0700433 if (matchOne && foundDevs.size() > 1)
434 {
435 foundDevs.erase(foundDevs.begin() + 1, foundDevs.end());
436 }
James Feist3cb5fec2018-01-23 14:41:51 -0800437 return ret;
438}
James Feist8f2710a2018-05-09 17:18:55 -0700439// this class finds the needed dbus fields and on destruction runs the probe
440struct PerformProbe : std::enable_shared_from_this<PerformProbe>
441{
James Feist3cb5fec2018-01-23 14:41:51 -0800442
James Feist8f2710a2018-05-09 17:18:55 -0700443 PerformProbe(
444 const std::vector<std::string> &probeCommand,
445 std::function<void(std::vector<boost::container::flat_map<
446 std::string, BasicVariantType>> &)> &&callback) :
447 _probeCommand(probeCommand),
448 _callback(std::move(callback))
449 {
450 }
451 ~PerformProbe()
452 {
453 if (probe(_probeCommand, _foundDevs))
454 {
455 _callback(_foundDevs);
456 }
457 }
458 void run()
459 {
460 // parse out dbus probes by discarding other probe types
461 boost::container::flat_map<const char *, probe_type_codes,
462 cmp_str>::const_iterator probeType;
463
464 std::vector<std::string> dbusProbes;
465 for (std::string &probe : _probeCommand)
466 {
467 bool found = false;
468 boost::container::flat_map<const char *, probe_type_codes,
469 cmp_str>::const_iterator probeType;
470 for (probeType = PROBE_TYPES.begin();
471 probeType != PROBE_TYPES.end(); probeType++)
472 {
473 if (probe.find(probeType->first) != std::string::npos)
474 {
475 found = true;
476 break;
477 }
478 }
479 if (found)
480 {
481 continue;
482 }
483 // syntax requires probe before first open brace
484 auto findStart = probe.find("(");
485 std::string interface = probe.substr(0, findStart);
486
487 findDbusObjects(shared_from_this(), SYSTEM_BUS, interface);
488 }
489 }
490 std::vector<std::string> _probeCommand;
491 std::function<void(
492 std::vector<boost::container::flat_map<std::string, BasicVariantType>>
493 &)>
494 _callback;
495 std::vector<boost::container::flat_map<std::string, BasicVariantType>>
496 _foundDevs;
497};
498
499// writes output files to persist data
James Feistbb43d022018-06-12 15:44:33 -0700500bool writeJsonFiles(const nlohmann::json &systemConfiguration)
James Feist1b2e2242018-01-30 13:45:19 -0800501{
502 std::experimental::filesystem::create_directory(OUTPUT_DIR);
503 std::ofstream output(std::string(OUTPUT_DIR) + "system.json");
James Feistbb43d022018-06-12 15:44:33 -0700504 if (!output.good())
505 {
506 return false;
507 }
James Feist1b2e2242018-01-30 13:45:19 -0800508 output << systemConfiguration.dump(4);
509 output.close();
James Feistbb43d022018-06-12 15:44:33 -0700510 return true;
James Feist8f2710a2018-05-09 17:18:55 -0700511}
James Feist1b2e2242018-01-30 13:45:19 -0800512
James Feist8f2710a2018-05-09 17:18:55 -0700513// template function to add array as dbus property
514template <typename PropertyType>
515void addArrayToDbus(const std::string &name, const nlohmann::json &array,
James Feistbb43d022018-06-12 15:44:33 -0700516 sdbusplus::asio::dbus_interface *iface,
517 sdbusplus::asio::PropertyPermission permission)
James Feist8f2710a2018-05-09 17:18:55 -0700518{
519 std::vector<PropertyType> values;
520 for (const auto &property : array)
James Feist1b2e2242018-01-30 13:45:19 -0800521 {
James Feist8f2710a2018-05-09 17:18:55 -0700522 auto ptr = property.get_ptr<const PropertyType *>();
523 if (ptr != nullptr)
James Feist1b2e2242018-01-30 13:45:19 -0800524 {
James Feist8f2710a2018-05-09 17:18:55 -0700525 values.emplace_back(*ptr);
James Feist1b2e2242018-01-30 13:45:19 -0800526 }
527 }
James Feistbb43d022018-06-12 15:44:33 -0700528 // todo(james), currently there are no reason to persist arrays, get around
529 // to it if needed
530
531 iface->register_property(name, values, permission);
James Feist1b2e2242018-01-30 13:45:19 -0800532}
James Feist97a63f12018-05-17 13:50:57 -0700533
534template <typename JsonType>
James Feistbb43d022018-06-12 15:44:33 -0700535bool setJsonFromPointer(const std::string &ptrStr, const JsonType &value,
James Feist97a63f12018-05-17 13:50:57 -0700536 nlohmann::json &systemConfiguration)
537{
538 try
539 {
540 nlohmann::json::json_pointer ptr(ptrStr);
541 nlohmann::json &ref = systemConfiguration[ptr];
542 ref = value;
543 return true;
544 }
545 catch (const std::out_of_range)
546 {
547 return false;
548 }
549}
James Feistbb43d022018-06-12 15:44:33 -0700550
551template <typename PropertyType>
552void addProperty(const std::string &propertyName, const PropertyType &value,
553 sdbusplus::asio::dbus_interface *iface,
554 nlohmann::json &systemConfiguration,
555 const std::string &jsonPointerString,
556 sdbusplus::asio::PropertyPermission permission)
557{
558 if (permission == sdbusplus::asio::PropertyPermission::readOnly)
559 {
560 iface->register_property(propertyName, value);
561 return;
562 }
James Feist68500ff2018-08-08 15:40:42 -0700563 iface->register_property(
564 propertyName, value,
565 [&systemConfiguration,
566 jsonPointerString{std::string(jsonPointerString)}](
567 const PropertyType &newVal, PropertyType &val) {
568 val = newVal;
569 if (!setJsonFromPointer(jsonPointerString, val,
570 systemConfiguration))
571 {
572 std::cerr << "error setting json field\n";
573 return -1;
574 }
575 if (writeJsonFiles(systemConfiguration))
576 {
577 std::cerr << "error setting json file\n";
578 return 1;
579 }
James Feistbb43d022018-06-12 15:44:33 -0700580 return -1;
James Feist68500ff2018-08-08 15:40:42 -0700581 });
James Feistbb43d022018-06-12 15:44:33 -0700582}
583
James Feist1b2e2242018-01-30 13:45:19 -0800584// adds simple json types to interface's properties
James Feistbb43d022018-06-12 15:44:33 -0700585void populateInterfaceFromJson(
586 nlohmann::json &systemConfiguration, const std::string &jsonPointerPath,
587 sdbusplus::asio::dbus_interface *iface, nlohmann::json &dict,
588 sdbusplus::asio::object_server &objServer,
589 sdbusplus::asio::PropertyPermission permission =
590 sdbusplus::asio::PropertyPermission::readOnly)
James Feist1b2e2242018-01-30 13:45:19 -0800591{
James Feist9eb0b582018-04-27 12:15:46 -0700592 for (auto &dictPair : dict.items())
James Feist1b2e2242018-01-30 13:45:19 -0800593 {
James Feist8f2710a2018-05-09 17:18:55 -0700594 auto type = dictPair.value().type();
595 bool array = false;
596 if (dictPair.value().type() == nlohmann::json::value_t::array)
597 {
598 array = true;
599 if (!dictPair.value().size())
600 {
601 continue;
602 }
603 type = dictPair.value()[0].type();
604 bool isLegal = true;
605 for (const auto &arrayItem : dictPair.value())
606 {
607 if (arrayItem.type() != type)
608 {
609 isLegal = false;
610 break;
611 }
612 }
613 if (!isLegal)
614 {
615 std::cerr << "dbus format error" << dictPair.value() << "\n";
616 continue;
617 }
618 if (type == nlohmann::json::value_t::object)
619 {
620 continue; // handled elsewhere
621 }
622 }
James Feist97a63f12018-05-17 13:50:57 -0700623 std::string key = jsonPointerPath + "/" + dictPair.key();
James Feistbb43d022018-06-12 15:44:33 -0700624 if (permission == sdbusplus::asio::PropertyPermission::readWrite)
625 {
626 // all setable numbers are doubles as it is difficult to always
627 // create a configuration file with all whole numbers as decimals
628 // i.e. 1.0
629 if (dictPair.value().is_number())
630 {
631 type = nlohmann::json::value_t::number_float;
632 }
633 }
634
James Feist8f2710a2018-05-09 17:18:55 -0700635 switch (type)
James Feist1b2e2242018-01-30 13:45:19 -0800636 {
James Feist9eb0b582018-04-27 12:15:46 -0700637 case (nlohmann::json::value_t::boolean):
638 {
James Feist8f2710a2018-05-09 17:18:55 -0700639 if (array)
640 {
641 // todo: array of bool isn't detected correctly by
642 // sdbusplus, change it to numbers
643 addArrayToDbus<uint64_t>(dictPair.key(), dictPair.value(),
James Feistbb43d022018-06-12 15:44:33 -0700644 iface, permission);
James Feist8f2710a2018-05-09 17:18:55 -0700645 }
James Feistbb43d022018-06-12 15:44:33 -0700646
James Feist97a63f12018-05-17 13:50:57 -0700647 else
648 {
James Feistbb43d022018-06-12 15:44:33 -0700649 addProperty(dictPair.key(), dictPair.value().get<bool>(),
650 iface, systemConfiguration, key, permission);
James Feist97a63f12018-05-17 13:50:57 -0700651 }
James Feist9eb0b582018-04-27 12:15:46 -0700652 break;
653 }
654 case (nlohmann::json::value_t::number_integer):
655 {
James Feist8f2710a2018-05-09 17:18:55 -0700656 if (array)
657 {
658 addArrayToDbus<int64_t>(dictPair.key(), dictPair.value(),
James Feistbb43d022018-06-12 15:44:33 -0700659 iface, permission);
James Feist97a63f12018-05-17 13:50:57 -0700660 }
661 else
662 {
James Feistbb43d022018-06-12 15:44:33 -0700663 addProperty(dictPair.key(), dictPair.value().get<int64_t>(),
664 iface, systemConfiguration, key,
665 sdbusplus::asio::PropertyPermission::readOnly);
James Feist97a63f12018-05-17 13:50:57 -0700666 }
James Feist9eb0b582018-04-27 12:15:46 -0700667 break;
668 }
669 case (nlohmann::json::value_t::number_unsigned):
670 {
James Feist8f2710a2018-05-09 17:18:55 -0700671 if (array)
672 {
673 addArrayToDbus<uint64_t>(dictPair.key(), dictPair.value(),
James Feistbb43d022018-06-12 15:44:33 -0700674 iface, permission);
James Feist97a63f12018-05-17 13:50:57 -0700675 }
676 else
677 {
James Feistbb43d022018-06-12 15:44:33 -0700678 addProperty(dictPair.key(),
679 dictPair.value().get<uint64_t>(), iface,
680 systemConfiguration, key,
681 sdbusplus::asio::PropertyPermission::readOnly);
James Feist97a63f12018-05-17 13:50:57 -0700682 }
James Feist9eb0b582018-04-27 12:15:46 -0700683 break;
684 }
685 case (nlohmann::json::value_t::number_float):
686 {
James Feist8f2710a2018-05-09 17:18:55 -0700687 if (array)
688 {
689 addArrayToDbus<double>(dictPair.key(), dictPair.value(),
James Feistbb43d022018-06-12 15:44:33 -0700690 iface, permission);
James Feist8f2710a2018-05-09 17:18:55 -0700691 }
James Feistbb43d022018-06-12 15:44:33 -0700692
James Feist97a63f12018-05-17 13:50:57 -0700693 else
694 {
James Feistbb43d022018-06-12 15:44:33 -0700695 addProperty(dictPair.key(), dictPair.value().get<double>(),
696 iface, systemConfiguration, key, permission);
James Feist97a63f12018-05-17 13:50:57 -0700697 }
James Feist9eb0b582018-04-27 12:15:46 -0700698 break;
699 }
700 case (nlohmann::json::value_t::string):
701 {
James Feist8f2710a2018-05-09 17:18:55 -0700702 if (array)
703 {
James Feistbb43d022018-06-12 15:44:33 -0700704 addArrayToDbus<std::string>(
705 dictPair.key(), dictPair.value(), iface, permission);
James Feist97a63f12018-05-17 13:50:57 -0700706 }
707 else
708 {
James Feistbb43d022018-06-12 15:44:33 -0700709 addProperty(dictPair.key(),
710 dictPair.value().get<std::string>(), iface,
711 systemConfiguration, key, permission);
James Feist97a63f12018-05-17 13:50:57 -0700712 }
James Feist9eb0b582018-04-27 12:15:46 -0700713 break;
714 }
James Feist1b2e2242018-01-30 13:45:19 -0800715 }
716 }
James Feist1b2e2242018-01-30 13:45:19 -0800717
James Feist8f2710a2018-05-09 17:18:55 -0700718 iface->initialize();
James Feist1b2e2242018-01-30 13:45:19 -0800719}
720
James Feist68500ff2018-08-08 15:40:42 -0700721void createAddObjectMethod(const std::string &jsonPointerPath,
722 const std::string &path,
723 nlohmann::json &systemConfiguration,
724 sdbusplus::asio::object_server &objServer)
725{
726 auto iface = objServer.add_interface(path, "xyz.openbmc_project.AddObject");
727
728 iface->register_method(
729 "AddObject",
730 [&systemConfiguration, &objServer,
731 jsonPointerPath{std::string(jsonPointerPath)},
732 path{std::string(path)}](
733 const boost::container::flat_map<std::string, JsonVariantType>
734 &data) {
735 nlohmann::json::json_pointer ptr(jsonPointerPath);
736 nlohmann::json &base = systemConfiguration[ptr];
737 auto findExposes = base.find("Exposes");
738
739 if (findExposes == base.end())
740 {
741 throw std::invalid_argument("Entity must have children.");
742 }
743
744 // this will throw invalid-argument to sdbusplus if invalid json
745 nlohmann::json newData{};
746 for (const auto &item : data)
747 {
748 nlohmann::json &newJson = newData[item.first];
749 mapbox::util::apply_visitor(
750 [&newJson](auto &&val) { newJson = std::move(val); },
751 item.second);
752 }
753
754 auto findName = newData.find("Name");
755 auto findType = newData.find("Type");
756 if (findName == newData.end() || findType == newData.end())
757 {
758 throw std::invalid_argument("AddObject missing Name or Type");
759 }
760 const std::string *type = findType->get_ptr<const std::string *>();
761 const std::string *name = findName->get_ptr<const std::string *>();
762 if (type == nullptr || name == nullptr)
763 {
764 throw std::invalid_argument("Type and Name must be a string.");
765 }
766
767 size_t lastIndex = 0;
768 // we add in the "exposes"
769 for (; lastIndex < findExposes->size(); lastIndex++)
770 {
771 if (findExposes->at(lastIndex)["Name"] == *name &&
772 findExposes->at(lastIndex)["Type"] == *type)
773 {
774 throw std::invalid_argument(
775 "Field already in JSON, not adding");
776 }
777 lastIndex++;
778 }
779
780 std::ifstream schemaFile(std::string(schemaDirectory) + "/" +
781 *type + ".json");
782 // todo(james) we might want to also make a list of 'can add'
783 // interfaces but for now I think the assumption if there is a
784 // schema avaliable that it is allowed to update is fine
785 if (!schemaFile.good())
786 {
787 throw std::invalid_argument(
788 "No schema avaliable, cannot validate.");
789 }
790 nlohmann::json schema =
791 nlohmann::json::parse(schemaFile, nullptr, false);
792 if (schema.is_discarded())
793 {
794 std::cerr << "Schema not legal" << *type << ".json\n";
795 throw DBusInternalError();
796 }
797 if (!validateJson(schema, newData))
798 {
799 throw std::invalid_argument("Data does not match schema");
800 }
801
802 if (!writeJsonFiles(systemConfiguration))
803 {
804 std::cerr << "Error writing json files\n";
805 throw DBusInternalError();
806 }
807 std::string dbusName = *name;
808
809 std::regex_replace(dbusName.begin(), dbusName.begin(),
810 dbusName.end(), ILLEGAL_DBUS_REGEX, "_");
811 auto iface = objServer.add_interface(
812 path + "/" + dbusName,
813 "xyz.openbmc_project.Configuration." + *type);
814 // permission is read-write, as since we just created it, must be
815 // runtime modifiable
816 populateInterfaceFromJson(
817 systemConfiguration,
818 jsonPointerPath + "/" + std::to_string(lastIndex), iface.get(),
819 newData, objServer,
820 sdbusplus::asio::PropertyPermission::readWrite);
821 // todo(james) generate patch
822 findExposes->push_back(newData);
823 });
824 iface->initialize();
825}
826
James Feist97a63f12018-05-17 13:50:57 -0700827void postToDbus(const nlohmann::json &newConfiguration,
828 nlohmann::json &systemConfiguration,
James Feist8f2710a2018-05-09 17:18:55 -0700829 sdbusplus::asio::object_server &objServer)
James Feist75fdeeb2018-02-20 14:26:16 -0800830
James Feist1b2e2242018-01-30 13:45:19 -0800831{
James Feist97a63f12018-05-17 13:50:57 -0700832 // iterate through boards
833 for (auto &boardPair : newConfiguration.items())
James Feist1b2e2242018-01-30 13:45:19 -0800834 {
835 std::string boardKey = boardPair.key();
James Feist97a63f12018-05-17 13:50:57 -0700836 std::vector<std::string> path;
837 std::string jsonPointerPath = "/" + boardKey;
838 // loop through newConfiguration, but use values from system
839 // configuration to be able to modify via dbus later
840 auto boardValues = systemConfiguration[boardKey];
James Feistd63d18a2018-07-19 15:23:45 -0700841 auto findBoardType = boardValues.find("Type");
James Feist1b2e2242018-01-30 13:45:19 -0800842 std::string boardType;
843 if (findBoardType != boardValues.end() &&
844 findBoardType->type() == nlohmann::json::value_t::string)
845 {
846 boardType = findBoardType->get<std::string>();
847 std::regex_replace(boardType.begin(), boardType.begin(),
848 boardType.end(), ILLEGAL_DBUS_REGEX, "_");
849 }
850 else
851 {
852 std::cerr << "Unable to find type for " << boardKey
853 << " reverting to Chassis.\n";
854 boardType = "Chassis";
855 }
James Feist11be6672018-04-06 14:05:32 -0700856 std::string boardtypeLower = boost::algorithm::to_lower_copy(boardType);
James Feist1b2e2242018-01-30 13:45:19 -0800857
858 std::regex_replace(boardKey.begin(), boardKey.begin(), boardKey.end(),
859 ILLEGAL_DBUS_REGEX, "_");
James Feist11be6672018-04-06 14:05:32 -0700860 std::string boardName = "/xyz/openbmc_project/inventory/system/" +
861 boardtypeLower + "/" + boardKey;
James Feist1b2e2242018-01-30 13:45:19 -0800862
James Feist8f2710a2018-05-09 17:18:55 -0700863 auto inventoryIface = objServer.add_interface(
864 boardName, "xyz.openbmc_project.Inventory.Item");
James Feist68500ff2018-08-08 15:40:42 -0700865
James Feist8f2710a2018-05-09 17:18:55 -0700866 auto boardIface = objServer.add_interface(
867 boardName, "xyz.openbmc_project.Inventory.Item." + boardType);
James Feist11be6672018-04-06 14:05:32 -0700868
James Feist68500ff2018-08-08 15:40:42 -0700869 createAddObjectMethod(jsonPointerPath, boardName, systemConfiguration,
870 objServer);
871
James Feist97a63f12018-05-17 13:50:57 -0700872 populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
873 boardIface.get(), boardValues, objServer);
874 jsonPointerPath += "/";
875 // iterate through board properties
James Feist9eb0b582018-04-27 12:15:46 -0700876 for (auto &boardField : boardValues.items())
James Feist11be6672018-04-06 14:05:32 -0700877 {
878 if (boardField.value().type() == nlohmann::json::value_t::object)
879 {
James Feist8f2710a2018-05-09 17:18:55 -0700880 auto iface =
881 objServer.add_interface(boardName, boardField.key());
James Feist97a63f12018-05-17 13:50:57 -0700882 populateInterfaceFromJson(
883 systemConfiguration, jsonPointerPath + boardField.key(),
884 iface.get(), boardField.value(), objServer);
James Feist11be6672018-04-06 14:05:32 -0700885 }
886 }
James Feist97a63f12018-05-17 13:50:57 -0700887
James Feist1e3e6982018-08-03 16:09:28 -0700888 auto exposes = boardValues.find("Exposes");
James Feist1b2e2242018-01-30 13:45:19 -0800889 if (exposes == boardValues.end())
890 {
891 continue;
892 }
James Feist97a63f12018-05-17 13:50:57 -0700893 // iterate through exposes
James Feist1e3e6982018-08-03 16:09:28 -0700894 jsonPointerPath += "Exposes/";
James Feist97a63f12018-05-17 13:50:57 -0700895
896 // store the board level pointer so we can modify it on the way down
897 std::string jsonPointerPathBoard = jsonPointerPath;
898 size_t exposesIndex = -1;
James Feist1b2e2242018-01-30 13:45:19 -0800899 for (auto &item : *exposes)
900 {
James Feist97a63f12018-05-17 13:50:57 -0700901 exposesIndex++;
902 jsonPointerPath = jsonPointerPathBoard;
903 jsonPointerPath += std::to_string(exposesIndex);
904
James Feistd63d18a2018-07-19 15:23:45 -0700905 auto findName = item.find("Name");
James Feist1b2e2242018-01-30 13:45:19 -0800906 if (findName == item.end())
907 {
908 std::cerr << "cannot find name in field " << item << "\n";
909 continue;
910 }
James Feist1e3e6982018-08-03 16:09:28 -0700911 auto findStatus = item.find("Status");
James Feist1b2e2242018-01-30 13:45:19 -0800912 // if status is not found it is assumed to be status = 'okay'
913 if (findStatus != item.end())
914 {
915 if (*findStatus == "disabled")
916 {
917 continue;
918 }
919 }
James Feistd63d18a2018-07-19 15:23:45 -0700920 auto findType = item.find("Type");
James Feist1b2e2242018-01-30 13:45:19 -0800921 std::string itemType;
922 if (findType != item.end())
923 {
924 itemType = findType->get<std::string>();
925 std::regex_replace(itemType.begin(), itemType.begin(),
926 itemType.end(), ILLEGAL_DBUS_REGEX, "_");
927 }
928 else
929 {
930 itemType = "unknown";
931 }
932 std::string itemName = findName->get<std::string>();
933 std::regex_replace(itemName.begin(), itemName.begin(),
934 itemName.end(), ILLEGAL_DBUS_REGEX, "_");
James Feist8f2710a2018-05-09 17:18:55 -0700935 auto itemIface = objServer.add_interface(
936 boardName + "/" + itemName,
James Feist1b2e2242018-01-30 13:45:19 -0800937 "xyz.openbmc_project.Configuration." + itemType);
938
James Feist97a63f12018-05-17 13:50:57 -0700939 populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
940 itemIface.get(), item, objServer);
James Feist1b2e2242018-01-30 13:45:19 -0800941
James Feist9eb0b582018-04-27 12:15:46 -0700942 for (auto &objectPair : item.items())
James Feist1b2e2242018-01-30 13:45:19 -0800943 {
James Feist97a63f12018-05-17 13:50:57 -0700944 jsonPointerPath = jsonPointerPathBoard +
945 std::to_string(exposesIndex) + "/" +
946 objectPair.key();
James Feist1b2e2242018-01-30 13:45:19 -0800947 if (objectPair.value().type() ==
948 nlohmann::json::value_t::object)
949 {
James Feist8f2710a2018-05-09 17:18:55 -0700950 auto objectIface = objServer.add_interface(
951 boardName + "/" + itemName,
James Feist1b2e2242018-01-30 13:45:19 -0800952 "xyz.openbmc_project.Configuration." + itemType + "." +
James Feist8f2710a2018-05-09 17:18:55 -0700953 objectPair.key());
James Feist97a63f12018-05-17 13:50:57 -0700954
955 populateInterfaceFromJson(
956 systemConfiguration, jsonPointerPath, objectIface.get(),
957 objectPair.value(), objServer);
James Feist1b2e2242018-01-30 13:45:19 -0800958 }
959 else if (objectPair.value().type() ==
960 nlohmann::json::value_t::array)
961 {
962 size_t index = 0;
James Feist8f2710a2018-05-09 17:18:55 -0700963 if (!objectPair.value().size())
James Feist1b2e2242018-01-30 13:45:19 -0800964 {
James Feist8f2710a2018-05-09 17:18:55 -0700965 continue;
966 }
967 bool isLegal = true;
968 auto type = objectPair.value()[0].type();
969 if (type != nlohmann::json::value_t::object)
970 {
971 continue;
972 }
973
974 // verify legal json
975 for (const auto &arrayItem : objectPair.value())
976 {
977 if (arrayItem.type() != type)
James Feist1b2e2242018-01-30 13:45:19 -0800978 {
James Feist8f2710a2018-05-09 17:18:55 -0700979 isLegal = false;
James Feist1b2e2242018-01-30 13:45:19 -0800980 break;
981 }
James Feist8f2710a2018-05-09 17:18:55 -0700982 }
983 if (!isLegal)
984 {
985 std::cerr << "dbus format error" << objectPair.value()
986 << "\n";
987 break;
988 }
989
990 for (auto &arrayItem : objectPair.value())
991 {
James Feistbb43d022018-06-12 15:44:33 -0700992 // limit what interfaces accept set for safety
993 auto permission =
994 std::find(SETTABLE_INTERFACES.begin(),
995 SETTABLE_INTERFACES.end(),
996 objectPair.key()) !=
997 SETTABLE_INTERFACES.end()
998 ? sdbusplus::asio::PropertyPermission::readWrite
999 : sdbusplus::asio::PropertyPermission::readOnly;
James Feist97a63f12018-05-17 13:50:57 -07001000
James Feist8f2710a2018-05-09 17:18:55 -07001001 auto objectIface = objServer.add_interface(
1002 boardName + "/" + itemName,
James Feist1b2e2242018-01-30 13:45:19 -08001003 "xyz.openbmc_project.Configuration." + itemType +
James Feistbb43d022018-06-12 15:44:33 -07001004 "." + objectPair.key() + std::to_string(index));
1005 populateInterfaceFromJson(systemConfiguration,
1006 jsonPointerPath + "/" +
1007 std::to_string(index),
1008 objectIface.get(), arrayItem,
1009 objServer, permission);
1010 index++;
James Feist1b2e2242018-01-30 13:45:19 -08001011 }
1012 }
1013 }
1014 }
1015 }
1016}
1017
1018// finds the template character (currently set to $) and replaces the value with
1019// the field found in a dbus object i.e. $ADDRESS would get populated with the
1020// ADDRESS field from a object on dbus
1021void templateCharReplace(
1022 nlohmann::json::iterator &keyPair,
James Feist8f2710a2018-05-09 17:18:55 -07001023 const boost::container::flat_map<std::string, BasicVariantType>
James Feist1b2e2242018-01-30 13:45:19 -08001024 &foundDevice,
1025 size_t &foundDeviceIdx)
1026{
James Feist11be6672018-04-06 14:05:32 -07001027 if (keyPair.value().type() == nlohmann::json::value_t::object)
1028 {
1029 for (auto nextLayer = keyPair.value().begin();
1030 nextLayer != keyPair.value().end(); nextLayer++)
1031 {
1032 templateCharReplace(nextLayer, foundDevice, foundDeviceIdx);
1033 }
1034 return;
1035 }
1036 else if (keyPair.value().type() != nlohmann::json::value_t::string)
James Feist1b2e2242018-01-30 13:45:19 -08001037 {
1038 return;
1039 }
1040
1041 std::string value = keyPair.value();
1042 if (value.find(TEMPLATE_CHAR) != std::string::npos)
1043 {
1044 std::string templateValue = value;
1045
1046 templateValue.erase(0, 1); // remove template character
1047
1048 // special case index
1049 if ("index" == templateValue)
1050 {
1051 keyPair.value() = foundDeviceIdx;
1052 }
1053 else
1054 {
James Feist13b86d62018-05-29 11:24:35 -07001055 bool found = false;
James Feist1b2e2242018-01-30 13:45:19 -08001056 for (auto &foundDevicePair : foundDevice)
1057 {
1058 if (boost::iequals(foundDevicePair.first, templateValue))
1059 {
James Feist13b86d62018-05-29 11:24:35 -07001060 mapbox::util::apply_visitor(
1061 [&](auto &&val) { keyPair.value() = val; },
1062 foundDevicePair.second);
1063 found = true;
James Feist1b2e2242018-01-30 13:45:19 -08001064 break;
1065 }
1066 }
James Feist13b86d62018-05-29 11:24:35 -07001067 if (!found)
James Feist1b2e2242018-01-30 13:45:19 -08001068 {
1069 std::cerr << "could not find symbol " << templateValue << "\n";
1070 }
James Feist1b2e2242018-01-30 13:45:19 -08001071 }
1072 }
1073}
1074
James Feist8f2710a2018-05-09 17:18:55 -07001075// reads json files out of the filesystem
1076bool findJsonFiles(std::list<nlohmann::json> &configurations)
James Feist3cb5fec2018-01-23 14:41:51 -08001077{
1078 // find configuration files
1079 std::vector<fs::path> jsonPaths;
James Feista3c180a2018-08-09 16:06:04 -07001080 if (!findFiles(fs::path(configurationDirectory),
1081 R"(.*\.json)", jsonPaths))
James Feist3cb5fec2018-01-23 14:41:51 -08001082 {
1083 std::cerr << "Unable to find any configuration files in "
James Feistb4383f42018-08-06 16:54:10 -07001084 << configurationDirectory << "\n";
James Feist75fdeeb2018-02-20 14:26:16 -08001085 return false;
James Feist3cb5fec2018-01-23 14:41:51 -08001086 }
James Feistb4383f42018-08-06 16:54:10 -07001087
1088 std::ifstream schemaStream(std::string(schemaDirectory) + "/" +
1089 globalSchema);
1090 if (!schemaStream.good())
1091 {
1092 std::cerr
1093 << "Cannot open schema file, cannot validate JSON, exiting\n\n";
1094 std::exit(EXIT_FAILURE);
1095 }
1096 nlohmann::json schema = nlohmann::json::parse(schemaStream, nullptr, false);
1097 if (schema.is_discarded())
1098 {
1099 std::cerr
1100 << "Illegal schema file detected, cannot validate JSON, exiting\n";
1101 std::exit(EXIT_FAILURE);
1102 }
1103
James Feist3cb5fec2018-01-23 14:41:51 -08001104 for (auto &jsonPath : jsonPaths)
1105 {
1106 std::ifstream jsonStream(jsonPath.c_str());
1107 if (!jsonStream.good())
1108 {
1109 std::cerr << "unable to open " << jsonPath.string() << "\n";
1110 continue;
1111 }
1112 auto data = nlohmann::json::parse(jsonStream, nullptr, false);
1113 if (data.is_discarded())
1114 {
1115 std::cerr << "syntax error in " << jsonPath.string() << "\n";
1116 continue;
1117 }
James Feistb4383f42018-08-06 16:54:10 -07001118 if (!validateJson(schema, data))
1119 {
1120 std::cerr << "Error validating " << jsonPath.string() << "\n";
1121 continue;
1122 }
1123
James Feist3cb5fec2018-01-23 14:41:51 -08001124 if (data.type() == nlohmann::json::value_t::array)
1125 {
1126 for (auto &d : data)
1127 {
1128 configurations.emplace_back(d);
1129 }
1130 }
1131 else
1132 {
1133 configurations.emplace_back(data);
1134 }
1135 }
James Feist75fdeeb2018-02-20 14:26:16 -08001136}
James Feist3cb5fec2018-01-23 14:41:51 -08001137
James Feist8f2710a2018-05-09 17:18:55 -07001138struct PerformScan : std::enable_shared_from_this<PerformScan>
James Feist75fdeeb2018-02-20 14:26:16 -08001139{
James Feist75fdeeb2018-02-20 14:26:16 -08001140
James Feist8f2710a2018-05-09 17:18:55 -07001141 PerformScan(nlohmann::json &systemConfiguration,
1142 std::list<nlohmann::json> &configurations,
1143 std::function<void(void)> &&callback) :
1144 _systemConfiguration(systemConfiguration),
1145 _configurations(configurations), _callback(std::move(callback))
James Feist3cb5fec2018-01-23 14:41:51 -08001146 {
James Feist8f2710a2018-05-09 17:18:55 -07001147 }
1148 void run()
1149 {
1150 for (auto it = _configurations.begin(); it != _configurations.end();)
James Feist3cb5fec2018-01-23 14:41:51 -08001151 {
James Feist1e3e6982018-08-03 16:09:28 -07001152 auto findProbe = it->find("Probe");
James Feistd63d18a2018-07-19 15:23:45 -07001153 auto findName = it->find("Name");
James Feist3cb5fec2018-01-23 14:41:51 -08001154
James Feist1b2e2242018-01-30 13:45:19 -08001155 nlohmann::json probeCommand;
1156 // check for poorly formatted fields, probe must be an array
1157 if (findProbe == it->end())
James Feist3cb5fec2018-01-23 14:41:51 -08001158 {
1159 std::cerr << "configuration file missing probe:\n " << *it
1160 << "\n";
James Feist8f2710a2018-05-09 17:18:55 -07001161 it = _configurations.erase(it);
1162 continue;
James Feist3cb5fec2018-01-23 14:41:51 -08001163 }
James Feist1b2e2242018-01-30 13:45:19 -08001164 else if ((*findProbe).type() != nlohmann::json::value_t::array)
James Feist3cb5fec2018-01-23 14:41:51 -08001165 {
1166 probeCommand = nlohmann::json::array();
1167 probeCommand.push_back(*findProbe);
1168 }
1169 else
1170 {
1171 probeCommand = *findProbe;
1172 }
James Feist1b2e2242018-01-30 13:45:19 -08001173
1174 if (findName == it->end())
1175 {
1176 std::cerr << "configuration file missing name:\n " << *it
1177 << "\n";
James Feist8f2710a2018-05-09 17:18:55 -07001178 it = _configurations.erase(it);
1179 continue;
James Feist1b2e2242018-01-30 13:45:19 -08001180 }
James Feist8f2710a2018-05-09 17:18:55 -07001181 std::string name = *findName;
James Feist1b2e2242018-01-30 13:45:19 -08001182
James Feist8f2710a2018-05-09 17:18:55 -07001183 if (std::find(PASSED_PROBES.begin(), PASSED_PROBES.end(), name) !=
1184 PASSED_PROBES.end())
James Feist3cb5fec2018-01-23 14:41:51 -08001185 {
James Feist8f2710a2018-05-09 17:18:55 -07001186 it = _configurations.erase(it);
1187 continue;
1188 }
1189 nlohmann::json *record = &(*it);
James Feist3cb5fec2018-01-23 14:41:51 -08001190
James Feist8f2710a2018-05-09 17:18:55 -07001191 // store reference to this to children to makes sure we don't get
1192 // destroyed too early
1193 auto thisRef = shared_from_this();
1194 auto p = std::make_shared<PerformProbe>(
1195 probeCommand,
1196 [&, record, name,
1197 thisRef](std::vector<boost::container::flat_map<
1198 std::string, BasicVariantType>> &foundDevices) {
1199 _passed = true;
James Feist3cb5fec2018-01-23 14:41:51 -08001200
James Feist8f2710a2018-05-09 17:18:55 -07001201 PASSED_PROBES.push_back(name);
1202 size_t foundDeviceIdx = 0;
1203
James Feistbe5425f2018-06-08 10:30:55 -07001204 // insert into configuration temporarly to be able to
1205 // reference ourselves
1206 _systemConfiguration[name] = *record;
1207
James Feist8f2710a2018-05-09 17:18:55 -07001208 for (auto &foundDevice : foundDevices)
James Feist3cb5fec2018-01-23 14:41:51 -08001209 {
James Feist8f2710a2018-05-09 17:18:55 -07001210 for (auto keyPair = record->begin();
1211 keyPair != record->end(); keyPair++)
James Feist3cb5fec2018-01-23 14:41:51 -08001212 {
James Feist1b2e2242018-01-30 13:45:19 -08001213 templateCharReplace(keyPair, foundDevice,
1214 foundDeviceIdx);
James Feist8f2710a2018-05-09 17:18:55 -07001215 }
James Feist1e3e6982018-08-03 16:09:28 -07001216 auto findExpose = record->find("Exposes");
James Feist8f2710a2018-05-09 17:18:55 -07001217 if (findExpose == record->end())
1218 {
1219 continue;
1220 }
1221 for (auto &expose : *findExpose)
1222 {
1223 for (auto keyPair = expose.begin();
1224 keyPair != expose.end(); keyPair++)
James Feist3cb5fec2018-01-23 14:41:51 -08001225 {
James Feist1b2e2242018-01-30 13:45:19 -08001226
James Feist8f2710a2018-05-09 17:18:55 -07001227 // fill in template characters with devices
1228 // found
1229 templateCharReplace(keyPair, foundDevice,
1230 foundDeviceIdx);
1231 // special case bind
James Feist1e3e6982018-08-03 16:09:28 -07001232 if (boost::starts_with(keyPair.key(), "Bind"))
James Feist8f2710a2018-05-09 17:18:55 -07001233 {
1234 if (keyPair.value().type() !=
1235 nlohmann::json::value_t::string)
James Feist3cb5fec2018-01-23 14:41:51 -08001236 {
James Feist8f2710a2018-05-09 17:18:55 -07001237 std::cerr << "bind_ value must be of "
1238 "type string "
1239 << keyPair.key() << "\n";
James Feist1b2e2242018-01-30 13:45:19 -08001240 continue;
1241 }
James Feist8f2710a2018-05-09 17:18:55 -07001242 bool foundBind = false;
1243 std::string bind = keyPair.key().substr(
James Feist1e3e6982018-08-03 16:09:28 -07001244 sizeof("Bind") - 1);
James Feistbe5425f2018-06-08 10:30:55 -07001245
James Feist8f2710a2018-05-09 17:18:55 -07001246 for (auto &configurationPair :
1247 _systemConfiguration.items())
James Feist1b2e2242018-01-30 13:45:19 -08001248 {
James Feist1b2e2242018-01-30 13:45:19 -08001249
James Feist8f2710a2018-05-09 17:18:55 -07001250 auto configListFind =
1251 configurationPair.value().find(
James Feist1e3e6982018-08-03 16:09:28 -07001252 "Exposes");
James Feist8f2710a2018-05-09 17:18:55 -07001253
1254 if (configListFind ==
1255 configurationPair.value()
1256 .end() ||
1257 configListFind->type() !=
1258 nlohmann::json::value_t::array)
1259 {
1260 continue;
1261 }
1262 for (auto &exposedObject :
1263 *configListFind)
1264 {
1265 std::string foundObjectName =
James Feistd63d18a2018-07-19 15:23:45 -07001266 (exposedObject)["Name"];
James Feist8f2710a2018-05-09 17:18:55 -07001267 if (boost::iequals(
1268 foundObjectName,
1269 keyPair.value()
1270 .get<std::string>()))
1271 {
James Feist1e3e6982018-08-03 16:09:28 -07001272 exposedObject["Status"] =
James Feist8f2710a2018-05-09 17:18:55 -07001273 "okay";
1274 expose[bind] = exposedObject;
1275
1276 foundBind = true;
1277 break;
1278 }
1279 }
1280 if (foundBind)
1281 {
James Feist3cb5fec2018-01-23 14:41:51 -08001282 break;
1283 }
1284 }
James Feist8f2710a2018-05-09 17:18:55 -07001285 if (!foundBind)
James Feist3cb5fec2018-01-23 14:41:51 -08001286 {
James Feist8f2710a2018-05-09 17:18:55 -07001287 std::cerr << "configuration file "
1288 "dependency error, "
1289 "could not find bind "
1290 << keyPair.value() << "\n";
James Feist3cb5fec2018-01-23 14:41:51 -08001291 }
1292 }
1293 }
1294 }
1295 }
James Feistbe5425f2018-06-08 10:30:55 -07001296 // overwrite ourselves with cleaned up version
James Feist8f2710a2018-05-09 17:18:55 -07001297 _systemConfiguration[name] = *record;
1298 });
1299 p->run();
1300 it++;
James Feist3cb5fec2018-01-23 14:41:51 -08001301 }
1302 }
James Feist75fdeeb2018-02-20 14:26:16 -08001303
James Feist8f2710a2018-05-09 17:18:55 -07001304 ~PerformScan()
James Feist75fdeeb2018-02-20 14:26:16 -08001305 {
James Feist8f2710a2018-05-09 17:18:55 -07001306 if (_passed)
1307 {
1308 auto nextScan = std::make_shared<PerformScan>(
1309 _systemConfiguration, _configurations, std::move(_callback));
1310 nextScan->run();
1311 }
1312 else
1313 {
1314 _callback();
1315 }
1316 }
1317 nlohmann::json &_systemConfiguration;
1318 std::list<nlohmann::json> _configurations;
1319 std::function<void(void)> _callback;
1320 std::vector<std::shared_ptr<PerformProbe>> _probes;
1321 bool _passed = false;
1322};
James Feistc95cb142018-02-26 10:41:42 -08001323
James Feist8f2710a2018-05-09 17:18:55 -07001324// main properties changed entry
1325void propertiesChangedCallback(
1326 boost::asio::io_service &io,
1327 std::vector<sdbusplus::bus::match::match> &dbusMatches,
1328 nlohmann::json &systemConfiguration,
1329 sdbusplus::asio::object_server &objServer)
1330{
1331 static boost::asio::deadline_timer timer(io);
1332 timer.expires_from_now(boost::posix_time::seconds(1));
1333
1334 // setup an async wait as we normally get flooded with new requests
1335 timer.async_wait([&](const boost::system::error_code &ec) {
James Feist8f2710a2018-05-09 17:18:55 -07001336 if (ec == boost::asio::error::operation_aborted)
1337 {
1338 // we were cancelled
1339 return;
1340 }
1341 else if (ec)
1342 {
1343 std::cerr << "async wait error " << ec << "\n";
1344 return;
1345 }
1346
1347 nlohmann::json oldConfiguration = systemConfiguration;
1348 DBUS_PROBE_OBJECTS.clear();
1349
1350 std::list<nlohmann::json> configurations;
1351 if (!findJsonFiles(configurations))
1352 {
1353 std::cerr << "cannot find json files\n";
1354 return;
1355 }
1356
1357 auto perfScan = std::make_shared<PerformScan>(
1358 systemConfiguration, configurations, [&, oldConfiguration]() {
1359 nlohmann::json newConfiguration = systemConfiguration;
James Feist4131aea2018-03-09 09:47:30 -08001360 for (auto it = newConfiguration.begin();
1361 it != newConfiguration.end();)
1362 {
1363 auto findKey = oldConfiguration.find(it.key());
1364 if (findKey != oldConfiguration.end())
1365 {
1366 it = newConfiguration.erase(it);
1367 }
1368 else
1369 {
1370 it++;
1371 }
1372 }
James Feist8f2710a2018-05-09 17:18:55 -07001373 registerCallbacks(io, dbusMatches, systemConfiguration,
1374 objServer);
1375 io.post([&, newConfiguration]() {
James Feista2a98112018-08-07 11:15:37 -07001376
1377#ifdef OVERLAYS
James Feist8f2710a2018-05-09 17:18:55 -07001378 // todo: for now, only add new configurations,
1379 // unload to come later unloadOverlays();
1380 loadOverlays(newConfiguration);
James Feista2a98112018-08-07 11:15:37 -07001381#endif
James Feistbb43d022018-06-12 15:44:33 -07001382 io.post([&]() {
1383 if (!writeJsonFiles(systemConfiguration))
1384 {
1385 std::cerr << "Error writing json files\n";
1386 }
1387 });
James Feist8f2710a2018-05-09 17:18:55 -07001388 io.post([&, newConfiguration]() {
James Feist97a63f12018-05-17 13:50:57 -07001389 postToDbus(newConfiguration, systemConfiguration,
1390 objServer);
James Feist8f2710a2018-05-09 17:18:55 -07001391 });
1392 });
1393 });
1394 perfScan->run();
1395 });
James Feist75fdeeb2018-02-20 14:26:16 -08001396}
1397
James Feist8f2710a2018-05-09 17:18:55 -07001398void registerCallbacks(boost::asio::io_service &io,
1399 std::vector<sdbusplus::bus::match::match> &dbusMatches,
1400 nlohmann::json &systemConfiguration,
1401 sdbusplus::asio::object_server &objServer)
James Feist75fdeeb2018-02-20 14:26:16 -08001402{
1403 static boost::container::flat_set<std::string> watchedObjects;
1404
1405 for (const auto &objectMap : DBUS_PROBE_OBJECTS)
1406 {
1407 auto findObject = watchedObjects.find(objectMap.first);
1408 if (findObject != watchedObjects.end())
1409 {
1410 continue;
1411 }
James Feist8f2710a2018-05-09 17:18:55 -07001412 std::function<void(sdbusplus::message::message & message)>
1413 eventHandler =
James Feist75fdeeb2018-02-20 14:26:16 -08001414
James Feist8f2710a2018-05-09 17:18:55 -07001415 [&](sdbusplus::message::message &) {
1416 propertiesChangedCallback(io, dbusMatches,
1417 systemConfiguration, objServer);
1418 };
1419
1420 sdbusplus::bus::match::match match(
1421 static_cast<sdbusplus::bus::bus &>(*SYSTEM_BUS),
1422 "type='signal',member='PropertiesChanged',arg0='" +
1423 objectMap.first + "'",
1424 eventHandler);
1425 dbusMatches.emplace_back(std::move(match));
James Feist75fdeeb2018-02-20 14:26:16 -08001426 }
1427}
1428
1429int main(int argc, char **argv)
1430{
1431 // setup connection to dbus
1432 boost::asio::io_service io;
James Feist8f2710a2018-05-09 17:18:55 -07001433 SYSTEM_BUS = std::make_shared<sdbusplus::asio::connection>(io);
James Feist75fdeeb2018-02-20 14:26:16 -08001434 SYSTEM_BUS->request_name("xyz.openbmc_project.EntityManager");
James Feist4131aea2018-03-09 09:47:30 -08001435
James Feist8f2710a2018-05-09 17:18:55 -07001436 sdbusplus::asio::object_server objServer(SYSTEM_BUS);
James Feistfd1264a2018-05-03 12:10:00 -07001437
James Feist8f2710a2018-05-09 17:18:55 -07001438 std::shared_ptr<sdbusplus::asio::dbus_interface> entityIface =
1439 objServer.add_interface("/xyz/openbmc_project/EntityManager",
1440 "xyz.openbmc_project.EntityManager");
James Feistfd1264a2018-05-03 12:10:00 -07001441
James Feist8f2710a2018-05-09 17:18:55 -07001442 std::shared_ptr<sdbusplus::asio::dbus_interface> inventoryIface =
1443 objServer.add_interface("/xyz/openbmc_project/inventory",
1444 "xyz.openbmc_project.Inventory.Manager");
James Feist4131aea2018-03-09 09:47:30 -08001445
1446 // to keep reference to the match / filter objects so they don't get
1447 // destroyed
James Feist8f2710a2018-05-09 17:18:55 -07001448 std::vector<sdbusplus::bus::match::match> dbusMatches;
1449
1450 nlohmann::json systemConfiguration = nlohmann::json::object();
1451
1452 inventoryIface->register_method(
1453 "Notify", [](const boost::container::flat_map<
1454 std::string,
1455 boost::container::flat_map<std::string, BasicVariantType>>
1456 &object) { return; });
1457 inventoryIface->initialize();
1458
1459 io.post([&]() {
1460 unloadAllOverlays();
1461 propertiesChangedCallback(io, dbusMatches, systemConfiguration,
1462 objServer);
1463 });
James Feist4131aea2018-03-09 09:47:30 -08001464
James Feistfd1264a2018-05-03 12:10:00 -07001465 entityIface->register_method("ReScan", [&]() {
James Feist8f2710a2018-05-09 17:18:55 -07001466 propertiesChangedCallback(io, dbusMatches, systemConfiguration,
1467 objServer);
James Feist75fdeeb2018-02-20 14:26:16 -08001468 });
James Feist8f2710a2018-05-09 17:18:55 -07001469 entityIface->initialize();
1470
James Feist1b2e2242018-01-30 13:45:19 -08001471 io.run();
James Feist3cb5fec2018-01-23 14:41:51 -08001472
1473 return 0;
James Feist75fdeeb2018-02-20 14:26:16 -08001474}