blob: 6210f46a5fe11ba47c4f09261b5135ae9c2bb6de [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;
James Feistee0de612018-10-12 11:15:46 -070045namespace variant_ns = sdbusplus::message::variant_ns;
James Feist3cb5fec2018-01-23 14:41:51 -080046struct cmp_str
47{
48 bool operator()(const char *a, const char *b) const
49 {
50 return std::strcmp(a, b) < 0;
51 }
52};
53
James Feist8f2710a2018-05-09 17:18:55 -070054struct PerformProbe;
55
James Feist3cb5fec2018-01-23 14:41:51 -080056// underscore T for collison with dbus c api
57enum class probe_type_codes
58{
59 FALSE_T,
60 TRUE_T,
61 AND,
62 OR,
James Feist6bd2a022018-03-13 12:30:58 -070063 FOUND,
64 MATCH_ONE
James Feist3cb5fec2018-01-23 14:41:51 -080065};
66const static boost::container::flat_map<const char *, probe_type_codes, cmp_str>
67 PROBE_TYPES{{{"FALSE", probe_type_codes::FALSE_T},
68 {"TRUE", probe_type_codes::TRUE_T},
69 {"AND", probe_type_codes::AND},
70 {"OR", probe_type_codes::OR},
James Feist6bd2a022018-03-13 12:30:58 -070071 {"FOUND", probe_type_codes::FOUND},
72 {"MATCH_ONE", probe_type_codes::MATCH_ONE}}};
James Feist3cb5fec2018-01-23 14:41:51 -080073
James Feistc6248a52018-08-14 10:09:45 -070074static constexpr std::array<const char *, 3> settableInterfaces = {
75 "Thresholds", "Pid", "Pid.Zone"};
James Feist68500ff2018-08-08 15:40:42 -070076using JsonVariantType =
77 sdbusplus::message::variant<std::vector<std::string>, std::string, int64_t,
78 uint64_t, double, int32_t, uint32_t, int16_t,
79 uint16_t, uint8_t, bool>;
James Feist8f2710a2018-05-09 17:18:55 -070080using BasicVariantType =
81 sdbusplus::message::variant<std::string, int64_t, uint64_t, double, int32_t,
82 uint32_t, int16_t, uint16_t, uint8_t, bool>;
83
James Feist3cb5fec2018-01-23 14:41:51 -080084using GetSubTreeType = std::vector<
85 std::pair<std::string,
86 std::vector<std::pair<std::string, std::vector<std::string>>>>>;
87
88using ManagedObjectType = boost::container::flat_map<
James Feist8f2710a2018-05-09 17:18:55 -070089 sdbusplus::message::object_path,
James Feist3cb5fec2018-01-23 14:41:51 -080090 boost::container::flat_map<
91 std::string,
James Feist8f2710a2018-05-09 17:18:55 -070092 boost::container::flat_map<std::string, BasicVariantType>>>;
James Feist3cb5fec2018-01-23 14:41:51 -080093
94boost::container::flat_map<
95 std::string,
James Feist8f2710a2018-05-09 17:18:55 -070096 std::vector<boost::container::flat_map<std::string, BasicVariantType>>>
James Feist3cb5fec2018-01-23 14:41:51 -080097 DBUS_PROBE_OBJECTS;
98std::vector<std::string> PASSED_PROBES;
99
100// todo: pass this through nicer
James Feist8f2710a2018-05-09 17:18:55 -0700101std::shared_ptr<sdbusplus::asio::connection> SYSTEM_BUS;
James Feist3cb5fec2018-01-23 14:41:51 -0800102
James Feista6750242018-07-16 14:12:27 -0700103const std::regex ILLEGAL_DBUS_REGEX("[^A-Za-z0-9_.]");
James Feist1b2e2242018-01-30 13:45:19 -0800104
James Feist8f2710a2018-05-09 17:18:55 -0700105void registerCallbacks(boost::asio::io_service &io,
106 std::vector<sdbusplus::bus::match::match> &dbusMatches,
107 nlohmann::json &systemConfiguration,
108 sdbusplus::asio::object_server &objServer);
James Feist75fdeeb2018-02-20 14:26:16 -0800109
James Feist3cb5fec2018-01-23 14:41:51 -0800110// calls the mapper to find all exposed objects of an interface type
111// and creates a vector<flat_map> that contains all the key value pairs
112// getManagedObjects
James Feist8f2710a2018-05-09 17:18:55 -0700113void findDbusObjects(std::shared_ptr<PerformProbe> probe,
114 std::shared_ptr<sdbusplus::asio::connection> connection,
115 std::string &interface)
James Feist3cb5fec2018-01-23 14:41:51 -0800116{
James Feist8f2710a2018-05-09 17:18:55 -0700117
118 // store reference to pending callbacks so we don't overwhelm services
119 static boost::container::flat_map<
120 std::string, std::vector<std::shared_ptr<PerformProbe>>>
121 pendingProbes;
122
123 if (DBUS_PROBE_OBJECTS[interface].size())
124 {
125 return;
126 }
127
128 // add shared_ptr to vector of Probes waiting for callback from a specific
129 // interface to keep alive while waiting for response
130 std::array<const char *, 1> objects = {interface.c_str()};
131 std::vector<std::shared_ptr<PerformProbe>> &pending =
132 pendingProbes[interface];
133 auto iter = pending.emplace(pending.end(), probe);
134 // only allow first call to run to not overwhelm processes
135 if (iter != pending.begin())
136 {
137 return;
138 }
139
James Feist3cb5fec2018-01-23 14:41:51 -0800140 // find all connections in the mapper that expose a specific type
James Feist8f2710a2018-05-09 17:18:55 -0700141 connection->async_method_call(
James Feist0de40152018-07-25 11:56:12 -0700142 [connection, interface, probe](boost::system::error_code &ec,
143 const GetSubTreeType &interfaceSubtree) {
144 boost::container::flat_set<std::string> interfaceConnections;
James Feist8f2710a2018-05-09 17:18:55 -0700145 if (ec)
James Feist494155a2018-03-14 16:23:24 -0700146 {
James Feist0de40152018-07-25 11:56:12 -0700147 pendingProbes[interface].clear();
148 if (ec.value() == ENOENT)
James Feist8f2710a2018-05-09 17:18:55 -0700149 {
James Feist0de40152018-07-25 11:56:12 -0700150 return; // wasn't found by mapper
James Feist8f2710a2018-05-09 17:18:55 -0700151 }
James Feist0de40152018-07-25 11:56:12 -0700152 std::cerr << "Error communicating to mapper.\n";
153
154 // if we can't communicate to the mapper something is very wrong
155 std::exit(EXIT_FAILURE);
James Feist494155a2018-03-14 16:23:24 -0700156 }
James Feist8f2710a2018-05-09 17:18:55 -0700157 else
James Feist3cb5fec2018-01-23 14:41:51 -0800158 {
James Feist8f2710a2018-05-09 17:18:55 -0700159 for (auto &object : interfaceSubtree)
160 {
161 for (auto &connPair : object.second)
162 {
163 auto insertPair =
164 interfaceConnections.insert(connPair.first);
165 }
166 }
James Feist3cb5fec2018-01-23 14:41:51 -0800167 }
James Feist8f2710a2018-05-09 17:18:55 -0700168 // get managed objects for all interfaces
169 for (const auto &conn : interfaceConnections)
170 {
171 connection->async_method_call(
James Feist0de40152018-07-25 11:56:12 -0700172 [conn,
James Feist8f2710a2018-05-09 17:18:55 -0700173 interface](boost::system::error_code &ec,
174 const ManagedObjectType &managedInterface) {
175 if (ec)
176 {
177 std::cerr
178 << "error getting managed object for device "
179 << conn << "\n";
180 pendingProbes[interface].clear();
181 return;
182 }
183 for (auto &interfaceManagedObj : managedInterface)
184 {
185 auto ifaceObjFind =
186 interfaceManagedObj.second.find(interface);
187 if (ifaceObjFind !=
188 interfaceManagedObj.second.end())
189 {
190 std::vector<boost::container::flat_map<
191 std::string, BasicVariantType>>
192 &dbusObject = DBUS_PROBE_OBJECTS[interface];
193 dbusObject.emplace_back(ifaceObjFind->second);
194 }
195 }
196 pendingProbes[interface].clear();
197 },
198 conn.c_str(), "/", "org.freedesktop.DBus.ObjectManager",
199 "GetManagedObjects");
200 }
201 },
202 "xyz.openbmc_project.ObjectMapper",
203 "/xyz/openbmc_project/object_mapper",
204 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "", MAX_MAPPER_DEPTH,
205 objects);
James Feist3cb5fec2018-01-23 14:41:51 -0800206}
James Feist8f2710a2018-05-09 17:18:55 -0700207// probes dbus interface dictionary for a key with a value that matches a regex
James Feist3cb5fec2018-01-23 14:41:51 -0800208bool probeDbus(
209 const std::string &interface,
210 const std::map<std::string, nlohmann::json> &matches,
James Feist8f2710a2018-05-09 17:18:55 -0700211 std::vector<boost::container::flat_map<std::string, BasicVariantType>>
James Feist3cb5fec2018-01-23 14:41:51 -0800212 &devices,
213 bool &foundProbe)
214{
James Feist8f2710a2018-05-09 17:18:55 -0700215 std::vector<boost::container::flat_map<std::string, BasicVariantType>>
216 &dbusObject = DBUS_PROBE_OBJECTS[interface];
James Feist3cb5fec2018-01-23 14:41:51 -0800217 if (dbusObject.empty())
218 {
James Feist8f2710a2018-05-09 17:18:55 -0700219 foundProbe = false;
220 return false;
James Feist3cb5fec2018-01-23 14:41:51 -0800221 }
222 foundProbe = true;
223
224 bool foundMatch = false;
225 for (auto &device : dbusObject)
226 {
227 bool deviceMatches = true;
228 for (auto &match : matches)
229 {
230 auto deviceValue = device.find(match.first);
231 if (deviceValue != device.end())
232 {
233 switch (match.second.type())
234 {
James Feist9eb0b582018-04-27 12:15:46 -0700235 case nlohmann::json::value_t::string:
James Feist3cb5fec2018-01-23 14:41:51 -0800236 {
James Feist9eb0b582018-04-27 12:15:46 -0700237 std::regex search(match.second.get<std::string>());
238 std::smatch match;
239
240 // convert value to string respresentation
James Feistee0de612018-10-12 11:15:46 -0700241 std::string probeValue = variant_ns::apply_visitor(
James Feist8f2710a2018-05-09 17:18:55 -0700242 VariantToStringVisitor(), deviceValue->second);
James Feist9eb0b582018-04-27 12:15:46 -0700243 if (!std::regex_search(probeValue, match, search))
244 {
245 deviceMatches = false;
246 break;
247 }
James Feist3cb5fec2018-01-23 14:41:51 -0800248 break;
249 }
James Feist9eb0b582018-04-27 12:15:46 -0700250 case nlohmann::json::value_t::boolean:
251 case nlohmann::json::value_t::number_unsigned:
James Feist3cb5fec2018-01-23 14:41:51 -0800252 {
James Feistee0de612018-10-12 11:15:46 -0700253 unsigned int probeValue = variant_ns::apply_visitor(
James Feist9eb0b582018-04-27 12:15:46 -0700254 VariantToUnsignedIntVisitor(), deviceValue->second);
James Feist3cb5fec2018-01-23 14:41:51 -0800255
James Feist9eb0b582018-04-27 12:15:46 -0700256 if (probeValue != match.second.get<unsigned int>())
257 {
258 deviceMatches = false;
259 }
260 break;
James Feist3cb5fec2018-01-23 14:41:51 -0800261 }
James Feist9eb0b582018-04-27 12:15:46 -0700262 case nlohmann::json::value_t::number_integer:
263 {
James Feistee0de612018-10-12 11:15:46 -0700264 int probeValue = variant_ns::apply_visitor(
James Feist9eb0b582018-04-27 12:15:46 -0700265 VariantToIntVisitor(), deviceValue->second);
James Feist3cb5fec2018-01-23 14:41:51 -0800266
James Feist9eb0b582018-04-27 12:15:46 -0700267 if (probeValue != match.second.get<int>())
268 {
269 deviceMatches = false;
270 }
271 break;
James Feist3cb5fec2018-01-23 14:41:51 -0800272 }
James Feist9eb0b582018-04-27 12:15:46 -0700273 case nlohmann::json::value_t::number_float:
274 {
James Feistee0de612018-10-12 11:15:46 -0700275 float probeValue = variant_ns::apply_visitor(
James Feist9eb0b582018-04-27 12:15:46 -0700276 VariantToFloatVisitor(), deviceValue->second);
277
278 if (probeValue != match.second.get<float>())
279 {
280 deviceMatches = false;
281 }
282 break;
283 }
James Feist3cb5fec2018-01-23 14:41:51 -0800284 }
285 }
286 else
287 {
288 deviceMatches = false;
289 break;
290 }
291 }
292 if (deviceMatches)
293 {
294 devices.emplace_back(
James Feist8f2710a2018-05-09 17:18:55 -0700295 boost::container::flat_map<std::string, BasicVariantType>(
James Feist3cb5fec2018-01-23 14:41:51 -0800296 device));
297 foundMatch = true;
298 deviceMatches = false; // for next iteration
299 }
300 }
301 return foundMatch;
302}
303
304// default probe entry point, iterates a list looking for specific types to
305// call specific probe functions
306bool probe(
James Feist8f2710a2018-05-09 17:18:55 -0700307 const std::vector<std::string> &probeCommand,
308 std::vector<boost::container::flat_map<std::string, BasicVariantType>>
James Feist3cb5fec2018-01-23 14:41:51 -0800309 &foundDevs)
310{
311 const static std::regex command(R"(\((.*)\))");
312 std::smatch match;
313 bool ret = false;
James Feist6bd2a022018-03-13 12:30:58 -0700314 bool matchOne = false;
James Feist3cb5fec2018-01-23 14:41:51 -0800315 bool cur = true;
316 probe_type_codes lastCommand = probe_type_codes::FALSE_T;
317
318 for (auto &probe : probeCommand)
319 {
320 bool foundProbe = false;
321 boost::container::flat_map<const char *, probe_type_codes,
322 cmp_str>::const_iterator probeType;
323
324 for (probeType = PROBE_TYPES.begin(); probeType != PROBE_TYPES.end();
325 probeType++)
326 {
327 if (probe.find(probeType->first) != std::string::npos)
328 {
329 foundProbe = true;
330 break;
331 }
332 }
333 if (foundProbe)
334 {
335 switch (probeType->second)
336 {
James Feist9eb0b582018-04-27 12:15:46 -0700337 case probe_type_codes::FALSE_T:
James Feist3cb5fec2018-01-23 14:41:51 -0800338 {
James Feist8f2710a2018-05-09 17:18:55 -0700339 return false;
James Feist9eb0b582018-04-27 12:15:46 -0700340 break;
James Feist3cb5fec2018-01-23 14:41:51 -0800341 }
James Feist9eb0b582018-04-27 12:15:46 -0700342 case probe_type_codes::TRUE_T:
343 {
James Feist8f2710a2018-05-09 17:18:55 -0700344 return true;
James Feist9eb0b582018-04-27 12:15:46 -0700345 break;
346 }
347 case probe_type_codes::MATCH_ONE:
348 {
349 // set current value to last, this probe type shouldn't
350 // affect the outcome
351 cur = ret;
352 matchOne = true;
353 break;
354 }
355 /*case probe_type_codes::AND:
356 break;
357 case probe_type_codes::OR:
358 break;
359 // these are no-ops until the last command switch
360 */
361 case probe_type_codes::FOUND:
362 {
363 if (!std::regex_search(probe, match, command))
364 {
365 std::cerr << "found probe sytax error " << probe
366 << "\n";
367 return false;
368 }
369 std::string commandStr = *(match.begin() + 1);
370 boost::replace_all(commandStr, "'", "");
371 cur = (std::find(PASSED_PROBES.begin(), PASSED_PROBES.end(),
372 commandStr) != PASSED_PROBES.end());
373 break;
374 }
James Feist3cb5fec2018-01-23 14:41:51 -0800375 }
376 }
377 // look on dbus for object
378 else
379 {
380 if (!std::regex_search(probe, match, command))
381 {
382 std::cerr << "dbus probe sytax error " << probe << "\n";
383 return false;
384 }
385 std::string commandStr = *(match.begin() + 1);
386 // convert single ticks and single slashes into legal json
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -0700387 boost::replace_all(commandStr, "'", "\"");
James Feist3f8a2782018-02-12 09:24:42 -0800388 boost::replace_all(commandStr, R"(\)", R"(\\)");
James Feist3cb5fec2018-01-23 14:41:51 -0800389 auto json = nlohmann::json::parse(commandStr, nullptr, false);
390 if (json.is_discarded())
391 {
392 std::cerr << "dbus command sytax error " << commandStr << "\n";
393 return false;
394 }
395 // we can match any (string, variant) property. (string, string)
396 // does a regex
397 std::map<std::string, nlohmann::json> dbusProbeMap =
398 json.get<std::map<std::string, nlohmann::json>>();
399 auto findStart = probe.find("(");
400 if (findStart == std::string::npos)
401 {
402 return false;
403 }
404 std::string probeInterface = probe.substr(0, findStart);
405 cur =
406 probeDbus(probeInterface, dbusProbeMap, foundDevs, foundProbe);
407 }
408
409 // some functions like AND and OR only take affect after the
410 // fact
411 switch (lastCommand)
412 {
James Feist9eb0b582018-04-27 12:15:46 -0700413 case probe_type_codes::AND:
414 ret = cur && ret;
415 break;
416 case probe_type_codes::OR:
417 ret = cur || ret;
418 break;
419 default:
420 ret = cur;
421 break;
James Feist3cb5fec2018-01-23 14:41:51 -0800422 }
423 lastCommand = probeType != PROBE_TYPES.end()
424 ? probeType->second
425 : probe_type_codes::FALSE_T;
James Feist3cb5fec2018-01-23 14:41:51 -0800426 }
427
428 // probe passed, but empty device
James Feist3cb5fec2018-01-23 14:41:51 -0800429 if (ret && foundDevs.size() == 0)
430 {
431 foundDevs.emplace_back(
James Feist8f2710a2018-05-09 17:18:55 -0700432 boost::container::flat_map<std::string, BasicVariantType>());
James Feist3cb5fec2018-01-23 14:41:51 -0800433 }
James Feist6bd2a022018-03-13 12:30:58 -0700434 if (matchOne && foundDevs.size() > 1)
435 {
436 foundDevs.erase(foundDevs.begin() + 1, foundDevs.end());
437 }
James Feist3cb5fec2018-01-23 14:41:51 -0800438 return ret;
439}
James Feist8f2710a2018-05-09 17:18:55 -0700440// this class finds the needed dbus fields and on destruction runs the probe
441struct PerformProbe : std::enable_shared_from_this<PerformProbe>
442{
James Feist3cb5fec2018-01-23 14:41:51 -0800443
James Feist8f2710a2018-05-09 17:18:55 -0700444 PerformProbe(
445 const std::vector<std::string> &probeCommand,
446 std::function<void(std::vector<boost::container::flat_map<
447 std::string, BasicVariantType>> &)> &&callback) :
448 _probeCommand(probeCommand),
449 _callback(std::move(callback))
450 {
451 }
452 ~PerformProbe()
453 {
454 if (probe(_probeCommand, _foundDevs))
455 {
456 _callback(_foundDevs);
457 }
458 }
459 void run()
460 {
461 // parse out dbus probes by discarding other probe types
462 boost::container::flat_map<const char *, probe_type_codes,
463 cmp_str>::const_iterator probeType;
464
465 std::vector<std::string> dbusProbes;
466 for (std::string &probe : _probeCommand)
467 {
468 bool found = false;
469 boost::container::flat_map<const char *, probe_type_codes,
470 cmp_str>::const_iterator probeType;
471 for (probeType = PROBE_TYPES.begin();
472 probeType != PROBE_TYPES.end(); probeType++)
473 {
474 if (probe.find(probeType->first) != std::string::npos)
475 {
476 found = true;
477 break;
478 }
479 }
480 if (found)
481 {
482 continue;
483 }
484 // syntax requires probe before first open brace
485 auto findStart = probe.find("(");
486 std::string interface = probe.substr(0, findStart);
487
488 findDbusObjects(shared_from_this(), SYSTEM_BUS, interface);
489 }
490 }
491 std::vector<std::string> _probeCommand;
492 std::function<void(
493 std::vector<boost::container::flat_map<std::string, BasicVariantType>>
494 &)>
495 _callback;
496 std::vector<boost::container::flat_map<std::string, BasicVariantType>>
497 _foundDevs;
498};
499
500// writes output files to persist data
James Feistbb43d022018-06-12 15:44:33 -0700501bool writeJsonFiles(const nlohmann::json &systemConfiguration)
James Feist1b2e2242018-01-30 13:45:19 -0800502{
503 std::experimental::filesystem::create_directory(OUTPUT_DIR);
504 std::ofstream output(std::string(OUTPUT_DIR) + "system.json");
James Feistbb43d022018-06-12 15:44:33 -0700505 if (!output.good())
506 {
507 return false;
508 }
James Feist1b2e2242018-01-30 13:45:19 -0800509 output << systemConfiguration.dump(4);
510 output.close();
James Feistbb43d022018-06-12 15:44:33 -0700511 return true;
James Feist8f2710a2018-05-09 17:18:55 -0700512}
James Feist1b2e2242018-01-30 13:45:19 -0800513
James Feist8f2710a2018-05-09 17:18:55 -0700514// template function to add array as dbus property
515template <typename PropertyType>
516void addArrayToDbus(const std::string &name, const nlohmann::json &array,
James Feistbb43d022018-06-12 15:44:33 -0700517 sdbusplus::asio::dbus_interface *iface,
518 sdbusplus::asio::PropertyPermission permission)
James Feist8f2710a2018-05-09 17:18:55 -0700519{
520 std::vector<PropertyType> values;
521 for (const auto &property : array)
James Feist1b2e2242018-01-30 13:45:19 -0800522 {
James Feist8f2710a2018-05-09 17:18:55 -0700523 auto ptr = property.get_ptr<const PropertyType *>();
524 if (ptr != nullptr)
James Feist1b2e2242018-01-30 13:45:19 -0800525 {
James Feist8f2710a2018-05-09 17:18:55 -0700526 values.emplace_back(*ptr);
James Feist1b2e2242018-01-30 13:45:19 -0800527 }
528 }
James Feistbb43d022018-06-12 15:44:33 -0700529 // todo(james), currently there are no reason to persist arrays, get around
530 // to it if needed
531
532 iface->register_property(name, values, permission);
James Feist1b2e2242018-01-30 13:45:19 -0800533}
James Feist97a63f12018-05-17 13:50:57 -0700534
535template <typename JsonType>
James Feistbb43d022018-06-12 15:44:33 -0700536bool setJsonFromPointer(const std::string &ptrStr, const JsonType &value,
James Feist97a63f12018-05-17 13:50:57 -0700537 nlohmann::json &systemConfiguration)
538{
539 try
540 {
541 nlohmann::json::json_pointer ptr(ptrStr);
542 nlohmann::json &ref = systemConfiguration[ptr];
543 ref = value;
544 return true;
545 }
546 catch (const std::out_of_range)
547 {
548 return false;
549 }
550}
James Feistbb43d022018-06-12 15:44:33 -0700551
552template <typename PropertyType>
553void addProperty(const std::string &propertyName, const PropertyType &value,
554 sdbusplus::asio::dbus_interface *iface,
555 nlohmann::json &systemConfiguration,
556 const std::string &jsonPointerString,
557 sdbusplus::asio::PropertyPermission permission)
558{
559 if (permission == sdbusplus::asio::PropertyPermission::readOnly)
560 {
561 iface->register_property(propertyName, value);
562 return;
563 }
James Feist68500ff2018-08-08 15:40:42 -0700564 iface->register_property(
565 propertyName, value,
566 [&systemConfiguration,
567 jsonPointerString{std::string(jsonPointerString)}](
568 const PropertyType &newVal, PropertyType &val) {
569 val = newVal;
570 if (!setJsonFromPointer(jsonPointerString, val,
571 systemConfiguration))
572 {
573 std::cerr << "error setting json field\n";
574 return -1;
575 }
James Feistc6248a52018-08-14 10:09:45 -0700576 if (!writeJsonFiles(systemConfiguration))
James Feist68500ff2018-08-08 15:40:42 -0700577 {
578 std::cerr << "error setting json file\n";
James Feistc6248a52018-08-14 10:09:45 -0700579 return -1;
580 }
581 return 1;
582 });
583}
584
585void createDeleteObjectMethod(
586 const std::string &jsonPointerPath,
587 const std::shared_ptr<sdbusplus::asio::dbus_interface> &iface,
588 sdbusplus::asio::object_server &objServer,
589 nlohmann::json &systemConfiguration)
590{
591 std::weak_ptr<sdbusplus::asio::dbus_interface> interface = iface;
592 iface->register_method(
593 "Delete", [&objServer, &systemConfiguration, interface,
594 jsonPointerPath{std::string(jsonPointerPath)}]() {
595 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
596 interface.lock();
597 if (!iface)
598 {
599 // this technically can't happen as the pointer is pointing to
600 // us
601 throw DBusInternalError();
602 }
603 nlohmann::json::json_pointer ptr(jsonPointerPath);
604 if (!objServer.remove_interface(iface))
605 {
606 std::cerr << "Can't delete interface " << jsonPointerPath
607 << "\n";
608 throw DBusInternalError();
609 }
610 systemConfiguration[ptr] = nullptr;
611
612 if (!writeJsonFiles(systemConfiguration))
613 {
614 std::cerr << "error setting json file\n";
615 throw DBusInternalError();
James Feist68500ff2018-08-08 15:40:42 -0700616 }
James Feistbb43d022018-06-12 15:44:33 -0700617 return -1;
James Feist68500ff2018-08-08 15:40:42 -0700618 });
James Feistbb43d022018-06-12 15:44:33 -0700619}
620
James Feist1b2e2242018-01-30 13:45:19 -0800621// adds simple json types to interface's properties
James Feistbb43d022018-06-12 15:44:33 -0700622void populateInterfaceFromJson(
623 nlohmann::json &systemConfiguration, const std::string &jsonPointerPath,
James Feistc6248a52018-08-14 10:09:45 -0700624 std::shared_ptr<sdbusplus::asio::dbus_interface> &iface,
625 nlohmann::json &dict, sdbusplus::asio::object_server &objServer,
James Feistbb43d022018-06-12 15:44:33 -0700626 sdbusplus::asio::PropertyPermission permission =
627 sdbusplus::asio::PropertyPermission::readOnly)
James Feist1b2e2242018-01-30 13:45:19 -0800628{
James Feist9eb0b582018-04-27 12:15:46 -0700629 for (auto &dictPair : dict.items())
James Feist1b2e2242018-01-30 13:45:19 -0800630 {
James Feist8f2710a2018-05-09 17:18:55 -0700631 auto type = dictPair.value().type();
632 bool array = false;
633 if (dictPair.value().type() == nlohmann::json::value_t::array)
634 {
635 array = true;
636 if (!dictPair.value().size())
637 {
638 continue;
639 }
640 type = dictPair.value()[0].type();
641 bool isLegal = true;
642 for (const auto &arrayItem : dictPair.value())
643 {
644 if (arrayItem.type() != type)
645 {
646 isLegal = false;
647 break;
648 }
649 }
650 if (!isLegal)
651 {
652 std::cerr << "dbus format error" << dictPair.value() << "\n";
653 continue;
654 }
655 if (type == nlohmann::json::value_t::object)
656 {
657 continue; // handled elsewhere
658 }
659 }
James Feist97a63f12018-05-17 13:50:57 -0700660 std::string key = jsonPointerPath + "/" + dictPair.key();
James Feistbb43d022018-06-12 15:44:33 -0700661 if (permission == sdbusplus::asio::PropertyPermission::readWrite)
662 {
663 // all setable numbers are doubles as it is difficult to always
664 // create a configuration file with all whole numbers as decimals
665 // i.e. 1.0
666 if (dictPair.value().is_number())
667 {
668 type = nlohmann::json::value_t::number_float;
669 }
670 }
671
James Feist8f2710a2018-05-09 17:18:55 -0700672 switch (type)
James Feist1b2e2242018-01-30 13:45:19 -0800673 {
James Feist9eb0b582018-04-27 12:15:46 -0700674 case (nlohmann::json::value_t::boolean):
675 {
James Feist8f2710a2018-05-09 17:18:55 -0700676 if (array)
677 {
678 // todo: array of bool isn't detected correctly by
679 // sdbusplus, change it to numbers
680 addArrayToDbus<uint64_t>(dictPair.key(), dictPair.value(),
James Feistc6248a52018-08-14 10:09:45 -0700681 iface.get(), permission);
James Feist8f2710a2018-05-09 17:18:55 -0700682 }
James Feistbb43d022018-06-12 15:44:33 -0700683
James Feist97a63f12018-05-17 13:50:57 -0700684 else
685 {
James Feistbb43d022018-06-12 15:44:33 -0700686 addProperty(dictPair.key(), dictPair.value().get<bool>(),
James Feistc6248a52018-08-14 10:09:45 -0700687 iface.get(), systemConfiguration, key,
688 permission);
James Feist97a63f12018-05-17 13:50:57 -0700689 }
James Feist9eb0b582018-04-27 12:15:46 -0700690 break;
691 }
692 case (nlohmann::json::value_t::number_integer):
693 {
James Feist8f2710a2018-05-09 17:18:55 -0700694 if (array)
695 {
696 addArrayToDbus<int64_t>(dictPair.key(), dictPair.value(),
James Feistc6248a52018-08-14 10:09:45 -0700697 iface.get(), permission);
James Feist97a63f12018-05-17 13:50:57 -0700698 }
699 else
700 {
James Feistbb43d022018-06-12 15:44:33 -0700701 addProperty(dictPair.key(), dictPair.value().get<int64_t>(),
James Feistc6248a52018-08-14 10:09:45 -0700702 iface.get(), systemConfiguration, key,
James Feistbb43d022018-06-12 15:44:33 -0700703 sdbusplus::asio::PropertyPermission::readOnly);
James Feist97a63f12018-05-17 13:50:57 -0700704 }
James Feist9eb0b582018-04-27 12:15:46 -0700705 break;
706 }
707 case (nlohmann::json::value_t::number_unsigned):
708 {
James Feist8f2710a2018-05-09 17:18:55 -0700709 if (array)
710 {
711 addArrayToDbus<uint64_t>(dictPair.key(), dictPair.value(),
James Feistc6248a52018-08-14 10:09:45 -0700712 iface.get(), permission);
James Feist97a63f12018-05-17 13:50:57 -0700713 }
714 else
715 {
James Feistbb43d022018-06-12 15:44:33 -0700716 addProperty(dictPair.key(),
James Feistc6248a52018-08-14 10:09:45 -0700717 dictPair.value().get<uint64_t>(), iface.get(),
James Feistbb43d022018-06-12 15:44:33 -0700718 systemConfiguration, key,
719 sdbusplus::asio::PropertyPermission::readOnly);
James Feist97a63f12018-05-17 13:50:57 -0700720 }
James Feist9eb0b582018-04-27 12:15:46 -0700721 break;
722 }
723 case (nlohmann::json::value_t::number_float):
724 {
James Feist8f2710a2018-05-09 17:18:55 -0700725 if (array)
726 {
727 addArrayToDbus<double>(dictPair.key(), dictPair.value(),
James Feistc6248a52018-08-14 10:09:45 -0700728 iface.get(), permission);
James Feist8f2710a2018-05-09 17:18:55 -0700729 }
James Feistbb43d022018-06-12 15:44:33 -0700730
James Feist97a63f12018-05-17 13:50:57 -0700731 else
732 {
James Feistbb43d022018-06-12 15:44:33 -0700733 addProperty(dictPair.key(), dictPair.value().get<double>(),
James Feistc6248a52018-08-14 10:09:45 -0700734 iface.get(), systemConfiguration, key,
735 permission);
James Feist97a63f12018-05-17 13:50:57 -0700736 }
James Feist9eb0b582018-04-27 12:15:46 -0700737 break;
738 }
739 case (nlohmann::json::value_t::string):
740 {
James Feist8f2710a2018-05-09 17:18:55 -0700741 if (array)
742 {
James Feistc6248a52018-08-14 10:09:45 -0700743 addArrayToDbus<std::string>(dictPair.key(),
744 dictPair.value(), iface.get(),
745 permission);
James Feist97a63f12018-05-17 13:50:57 -0700746 }
747 else
748 {
James Feistc6248a52018-08-14 10:09:45 -0700749 addProperty(
750 dictPair.key(), dictPair.value().get<std::string>(),
751 iface.get(), systemConfiguration, key, permission);
James Feist97a63f12018-05-17 13:50:57 -0700752 }
James Feist9eb0b582018-04-27 12:15:46 -0700753 break;
754 }
James Feist1b2e2242018-01-30 13:45:19 -0800755 }
756 }
James Feistc6248a52018-08-14 10:09:45 -0700757 if (permission == sdbusplus::asio::PropertyPermission::readWrite)
758 {
759 createDeleteObjectMethod(jsonPointerPath, iface, objServer,
760 systemConfiguration);
761 }
James Feist8f2710a2018-05-09 17:18:55 -0700762 iface->initialize();
James Feist1b2e2242018-01-30 13:45:19 -0800763}
764
James Feistc6248a52018-08-14 10:09:45 -0700765sdbusplus::asio::PropertyPermission getPermission(const std::string &interface)
766{
767 return std::find(settableInterfaces.begin(), settableInterfaces.end(),
768 interface) != settableInterfaces.end()
769 ? sdbusplus::asio::PropertyPermission::readWrite
770 : sdbusplus::asio::PropertyPermission::readOnly;
771}
772
James Feist68500ff2018-08-08 15:40:42 -0700773void createAddObjectMethod(const std::string &jsonPointerPath,
774 const std::string &path,
775 nlohmann::json &systemConfiguration,
776 sdbusplus::asio::object_server &objServer)
777{
778 auto iface = objServer.add_interface(path, "xyz.openbmc_project.AddObject");
779
780 iface->register_method(
781 "AddObject",
782 [&systemConfiguration, &objServer,
783 jsonPointerPath{std::string(jsonPointerPath)},
784 path{std::string(path)}](
785 const boost::container::flat_map<std::string, JsonVariantType>
786 &data) {
787 nlohmann::json::json_pointer ptr(jsonPointerPath);
788 nlohmann::json &base = systemConfiguration[ptr];
789 auto findExposes = base.find("Exposes");
790
791 if (findExposes == base.end())
792 {
793 throw std::invalid_argument("Entity must have children.");
794 }
795
796 // this will throw invalid-argument to sdbusplus if invalid json
797 nlohmann::json newData{};
798 for (const auto &item : data)
799 {
800 nlohmann::json &newJson = newData[item.first];
James Feistee0de612018-10-12 11:15:46 -0700801 variant_ns::apply_visitor(
James Feist68500ff2018-08-08 15:40:42 -0700802 [&newJson](auto &&val) { newJson = std::move(val); },
803 item.second);
804 }
805
806 auto findName = newData.find("Name");
807 auto findType = newData.find("Type");
808 if (findName == newData.end() || findType == newData.end())
809 {
810 throw std::invalid_argument("AddObject missing Name or Type");
811 }
812 const std::string *type = findType->get_ptr<const std::string *>();
813 const std::string *name = findName->get_ptr<const std::string *>();
814 if (type == nullptr || name == nullptr)
815 {
816 throw std::invalid_argument("Type and Name must be a string.");
817 }
818
819 size_t lastIndex = 0;
820 // we add in the "exposes"
821 for (; lastIndex < findExposes->size(); lastIndex++)
822 {
823 if (findExposes->at(lastIndex)["Name"] == *name &&
824 findExposes->at(lastIndex)["Type"] == *type)
825 {
826 throw std::invalid_argument(
827 "Field already in JSON, not adding");
828 }
829 lastIndex++;
830 }
831
832 std::ifstream schemaFile(std::string(schemaDirectory) + "/" +
833 *type + ".json");
834 // todo(james) we might want to also make a list of 'can add'
835 // interfaces but for now I think the assumption if there is a
836 // schema avaliable that it is allowed to update is fine
837 if (!schemaFile.good())
838 {
839 throw std::invalid_argument(
840 "No schema avaliable, cannot validate.");
841 }
842 nlohmann::json schema =
843 nlohmann::json::parse(schemaFile, nullptr, false);
844 if (schema.is_discarded())
845 {
846 std::cerr << "Schema not legal" << *type << ".json\n";
847 throw DBusInternalError();
848 }
849 if (!validateJson(schema, newData))
850 {
851 throw std::invalid_argument("Data does not match schema");
852 }
853
854 if (!writeJsonFiles(systemConfiguration))
855 {
856 std::cerr << "Error writing json files\n";
857 throw DBusInternalError();
858 }
859 std::string dbusName = *name;
860
861 std::regex_replace(dbusName.begin(), dbusName.begin(),
862 dbusName.end(), ILLEGAL_DBUS_REGEX, "_");
863 auto iface = objServer.add_interface(
864 path + "/" + dbusName,
865 "xyz.openbmc_project.Configuration." + *type);
866 // permission is read-write, as since we just created it, must be
867 // runtime modifiable
868 populateInterfaceFromJson(
869 systemConfiguration,
James Feistc6248a52018-08-14 10:09:45 -0700870 jsonPointerPath + "/" + std::to_string(lastIndex), iface,
James Feist68500ff2018-08-08 15:40:42 -0700871 newData, objServer,
872 sdbusplus::asio::PropertyPermission::readWrite);
873 // todo(james) generate patch
874 findExposes->push_back(newData);
875 });
876 iface->initialize();
877}
878
James Feist97a63f12018-05-17 13:50:57 -0700879void postToDbus(const nlohmann::json &newConfiguration,
880 nlohmann::json &systemConfiguration,
James Feist8f2710a2018-05-09 17:18:55 -0700881 sdbusplus::asio::object_server &objServer)
James Feist75fdeeb2018-02-20 14:26:16 -0800882
James Feist1b2e2242018-01-30 13:45:19 -0800883{
James Feist97a63f12018-05-17 13:50:57 -0700884 // iterate through boards
885 for (auto &boardPair : newConfiguration.items())
James Feist1b2e2242018-01-30 13:45:19 -0800886 {
887 std::string boardKey = boardPair.key();
James Feist97a63f12018-05-17 13:50:57 -0700888 std::vector<std::string> path;
889 std::string jsonPointerPath = "/" + boardKey;
890 // loop through newConfiguration, but use values from system
891 // configuration to be able to modify via dbus later
892 auto boardValues = systemConfiguration[boardKey];
James Feistd63d18a2018-07-19 15:23:45 -0700893 auto findBoardType = boardValues.find("Type");
James Feist1b2e2242018-01-30 13:45:19 -0800894 std::string boardType;
895 if (findBoardType != boardValues.end() &&
896 findBoardType->type() == nlohmann::json::value_t::string)
897 {
898 boardType = findBoardType->get<std::string>();
899 std::regex_replace(boardType.begin(), boardType.begin(),
900 boardType.end(), ILLEGAL_DBUS_REGEX, "_");
901 }
902 else
903 {
904 std::cerr << "Unable to find type for " << boardKey
905 << " reverting to Chassis.\n";
906 boardType = "Chassis";
907 }
James Feist11be6672018-04-06 14:05:32 -0700908 std::string boardtypeLower = boost::algorithm::to_lower_copy(boardType);
James Feist1b2e2242018-01-30 13:45:19 -0800909
910 std::regex_replace(boardKey.begin(), boardKey.begin(), boardKey.end(),
911 ILLEGAL_DBUS_REGEX, "_");
James Feist11be6672018-04-06 14:05:32 -0700912 std::string boardName = "/xyz/openbmc_project/inventory/system/" +
913 boardtypeLower + "/" + boardKey;
James Feist1b2e2242018-01-30 13:45:19 -0800914
James Feist8f2710a2018-05-09 17:18:55 -0700915 auto inventoryIface = objServer.add_interface(
916 boardName, "xyz.openbmc_project.Inventory.Item");
James Feist68500ff2018-08-08 15:40:42 -0700917
James Feist8f2710a2018-05-09 17:18:55 -0700918 auto boardIface = objServer.add_interface(
919 boardName, "xyz.openbmc_project.Inventory.Item." + boardType);
James Feist11be6672018-04-06 14:05:32 -0700920
James Feist68500ff2018-08-08 15:40:42 -0700921 createAddObjectMethod(jsonPointerPath, boardName, systemConfiguration,
922 objServer);
923
James Feist97a63f12018-05-17 13:50:57 -0700924 populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
James Feistc6248a52018-08-14 10:09:45 -0700925 boardIface, boardValues, objServer);
James Feist97a63f12018-05-17 13:50:57 -0700926 jsonPointerPath += "/";
927 // iterate through board properties
James Feist9eb0b582018-04-27 12:15:46 -0700928 for (auto &boardField : boardValues.items())
James Feist11be6672018-04-06 14:05:32 -0700929 {
930 if (boardField.value().type() == nlohmann::json::value_t::object)
931 {
James Feist8f2710a2018-05-09 17:18:55 -0700932 auto iface =
933 objServer.add_interface(boardName, boardField.key());
James Feistc6248a52018-08-14 10:09:45 -0700934 populateInterfaceFromJson(systemConfiguration,
935 jsonPointerPath + boardField.key(),
936 iface, boardField.value(), objServer);
James Feist11be6672018-04-06 14:05:32 -0700937 }
938 }
James Feist97a63f12018-05-17 13:50:57 -0700939
James Feist1e3e6982018-08-03 16:09:28 -0700940 auto exposes = boardValues.find("Exposes");
James Feist1b2e2242018-01-30 13:45:19 -0800941 if (exposes == boardValues.end())
942 {
943 continue;
944 }
James Feist97a63f12018-05-17 13:50:57 -0700945 // iterate through exposes
James Feist1e3e6982018-08-03 16:09:28 -0700946 jsonPointerPath += "Exposes/";
James Feist97a63f12018-05-17 13:50:57 -0700947
948 // store the board level pointer so we can modify it on the way down
949 std::string jsonPointerPathBoard = jsonPointerPath;
950 size_t exposesIndex = -1;
James Feist1b2e2242018-01-30 13:45:19 -0800951 for (auto &item : *exposes)
952 {
James Feist97a63f12018-05-17 13:50:57 -0700953 exposesIndex++;
954 jsonPointerPath = jsonPointerPathBoard;
955 jsonPointerPath += std::to_string(exposesIndex);
956
James Feistd63d18a2018-07-19 15:23:45 -0700957 auto findName = item.find("Name");
James Feist1b2e2242018-01-30 13:45:19 -0800958 if (findName == item.end())
959 {
960 std::cerr << "cannot find name in field " << item << "\n";
961 continue;
962 }
James Feist1e3e6982018-08-03 16:09:28 -0700963 auto findStatus = item.find("Status");
James Feist1b2e2242018-01-30 13:45:19 -0800964 // if status is not found it is assumed to be status = 'okay'
965 if (findStatus != item.end())
966 {
967 if (*findStatus == "disabled")
968 {
969 continue;
970 }
971 }
James Feistd63d18a2018-07-19 15:23:45 -0700972 auto findType = item.find("Type");
James Feist1b2e2242018-01-30 13:45:19 -0800973 std::string itemType;
974 if (findType != item.end())
975 {
976 itemType = findType->get<std::string>();
977 std::regex_replace(itemType.begin(), itemType.begin(),
978 itemType.end(), ILLEGAL_DBUS_REGEX, "_");
979 }
980 else
981 {
982 itemType = "unknown";
983 }
984 std::string itemName = findName->get<std::string>();
985 std::regex_replace(itemName.begin(), itemName.begin(),
986 itemName.end(), ILLEGAL_DBUS_REGEX, "_");
James Feistc6248a52018-08-14 10:09:45 -0700987
James Feist8f2710a2018-05-09 17:18:55 -0700988 auto itemIface = objServer.add_interface(
989 boardName + "/" + itemName,
James Feist1b2e2242018-01-30 13:45:19 -0800990 "xyz.openbmc_project.Configuration." + itemType);
991
James Feist97a63f12018-05-17 13:50:57 -0700992 populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
James Feistc6248a52018-08-14 10:09:45 -0700993 itemIface, item, objServer,
994 getPermission(itemType));
James Feist1b2e2242018-01-30 13:45:19 -0800995
James Feist9eb0b582018-04-27 12:15:46 -0700996 for (auto &objectPair : item.items())
James Feist1b2e2242018-01-30 13:45:19 -0800997 {
James Feist97a63f12018-05-17 13:50:57 -0700998 jsonPointerPath = jsonPointerPathBoard +
999 std::to_string(exposesIndex) + "/" +
1000 objectPair.key();
James Feist1b2e2242018-01-30 13:45:19 -08001001 if (objectPair.value().type() ==
1002 nlohmann::json::value_t::object)
1003 {
James Feist8f2710a2018-05-09 17:18:55 -07001004 auto objectIface = objServer.add_interface(
1005 boardName + "/" + itemName,
James Feist1b2e2242018-01-30 13:45:19 -08001006 "xyz.openbmc_project.Configuration." + itemType + "." +
James Feist8f2710a2018-05-09 17:18:55 -07001007 objectPair.key());
James Feist97a63f12018-05-17 13:50:57 -07001008
1009 populateInterfaceFromJson(
James Feistc6248a52018-08-14 10:09:45 -07001010 systemConfiguration, jsonPointerPath, objectIface,
1011 objectPair.value(), objServer, getPermission(itemType));
James Feist1b2e2242018-01-30 13:45:19 -08001012 }
1013 else if (objectPair.value().type() ==
1014 nlohmann::json::value_t::array)
1015 {
1016 size_t index = 0;
James Feist8f2710a2018-05-09 17:18:55 -07001017 if (!objectPair.value().size())
James Feist1b2e2242018-01-30 13:45:19 -08001018 {
James Feist8f2710a2018-05-09 17:18:55 -07001019 continue;
1020 }
1021 bool isLegal = true;
1022 auto type = objectPair.value()[0].type();
1023 if (type != nlohmann::json::value_t::object)
1024 {
1025 continue;
1026 }
1027
1028 // verify legal json
1029 for (const auto &arrayItem : objectPair.value())
1030 {
1031 if (arrayItem.type() != type)
James Feist1b2e2242018-01-30 13:45:19 -08001032 {
James Feist8f2710a2018-05-09 17:18:55 -07001033 isLegal = false;
James Feist1b2e2242018-01-30 13:45:19 -08001034 break;
1035 }
James Feist8f2710a2018-05-09 17:18:55 -07001036 }
1037 if (!isLegal)
1038 {
1039 std::cerr << "dbus format error" << objectPair.value()
1040 << "\n";
1041 break;
1042 }
1043
1044 for (auto &arrayItem : objectPair.value())
1045 {
James Feist97a63f12018-05-17 13:50:57 -07001046
James Feist8f2710a2018-05-09 17:18:55 -07001047 auto objectIface = objServer.add_interface(
1048 boardName + "/" + itemName,
James Feist1b2e2242018-01-30 13:45:19 -08001049 "xyz.openbmc_project.Configuration." + itemType +
James Feistbb43d022018-06-12 15:44:33 -07001050 "." + objectPair.key() + std::to_string(index));
James Feistc6248a52018-08-14 10:09:45 -07001051 populateInterfaceFromJson(
1052 systemConfiguration,
1053 jsonPointerPath + "/" + std::to_string(index),
1054 objectIface, arrayItem, objServer,
1055 getPermission(objectPair.key()));
James Feistbb43d022018-06-12 15:44:33 -07001056 index++;
James Feist1b2e2242018-01-30 13:45:19 -08001057 }
1058 }
1059 }
1060 }
1061 }
1062}
1063
1064// finds the template character (currently set to $) and replaces the value with
1065// the field found in a dbus object i.e. $ADDRESS would get populated with the
1066// ADDRESS field from a object on dbus
1067void templateCharReplace(
1068 nlohmann::json::iterator &keyPair,
James Feist8f2710a2018-05-09 17:18:55 -07001069 const boost::container::flat_map<std::string, BasicVariantType>
James Feist1b2e2242018-01-30 13:45:19 -08001070 &foundDevice,
1071 size_t &foundDeviceIdx)
1072{
James Feist11be6672018-04-06 14:05:32 -07001073 if (keyPair.value().type() == nlohmann::json::value_t::object)
1074 {
1075 for (auto nextLayer = keyPair.value().begin();
1076 nextLayer != keyPair.value().end(); nextLayer++)
1077 {
1078 templateCharReplace(nextLayer, foundDevice, foundDeviceIdx);
1079 }
1080 return;
1081 }
1082 else if (keyPair.value().type() != nlohmann::json::value_t::string)
James Feist1b2e2242018-01-30 13:45:19 -08001083 {
1084 return;
1085 }
1086
1087 std::string value = keyPair.value();
1088 if (value.find(TEMPLATE_CHAR) != std::string::npos)
1089 {
1090 std::string templateValue = value;
1091
1092 templateValue.erase(0, 1); // remove template character
1093
1094 // special case index
1095 if ("index" == templateValue)
1096 {
1097 keyPair.value() = foundDeviceIdx;
1098 }
1099 else
1100 {
James Feist13b86d62018-05-29 11:24:35 -07001101 bool found = false;
James Feist1b2e2242018-01-30 13:45:19 -08001102 for (auto &foundDevicePair : foundDevice)
1103 {
1104 if (boost::iequals(foundDevicePair.first, templateValue))
1105 {
James Feistee0de612018-10-12 11:15:46 -07001106 variant_ns::apply_visitor(
James Feist13b86d62018-05-29 11:24:35 -07001107 [&](auto &&val) { keyPair.value() = val; },
1108 foundDevicePair.second);
1109 found = true;
James Feist1b2e2242018-01-30 13:45:19 -08001110 break;
1111 }
1112 }
James Feist13b86d62018-05-29 11:24:35 -07001113 if (!found)
James Feist1b2e2242018-01-30 13:45:19 -08001114 {
1115 std::cerr << "could not find symbol " << templateValue << "\n";
1116 }
James Feist1b2e2242018-01-30 13:45:19 -08001117 }
1118 }
1119}
1120
James Feist8f2710a2018-05-09 17:18:55 -07001121// reads json files out of the filesystem
1122bool findJsonFiles(std::list<nlohmann::json> &configurations)
James Feist3cb5fec2018-01-23 14:41:51 -08001123{
1124 // find configuration files
1125 std::vector<fs::path> jsonPaths;
James Feista3c180a2018-08-09 16:06:04 -07001126 if (!findFiles(fs::path(configurationDirectory),
1127 R"(.*\.json)", jsonPaths))
James Feist3cb5fec2018-01-23 14:41:51 -08001128 {
1129 std::cerr << "Unable to find any configuration files in "
James Feistb4383f42018-08-06 16:54:10 -07001130 << configurationDirectory << "\n";
James Feist75fdeeb2018-02-20 14:26:16 -08001131 return false;
James Feist3cb5fec2018-01-23 14:41:51 -08001132 }
James Feistb4383f42018-08-06 16:54:10 -07001133
1134 std::ifstream schemaStream(std::string(schemaDirectory) + "/" +
1135 globalSchema);
1136 if (!schemaStream.good())
1137 {
1138 std::cerr
1139 << "Cannot open schema file, cannot validate JSON, exiting\n\n";
1140 std::exit(EXIT_FAILURE);
1141 }
1142 nlohmann::json schema = nlohmann::json::parse(schemaStream, nullptr, false);
1143 if (schema.is_discarded())
1144 {
1145 std::cerr
1146 << "Illegal schema file detected, cannot validate JSON, exiting\n";
1147 std::exit(EXIT_FAILURE);
1148 }
1149
James Feist3cb5fec2018-01-23 14:41:51 -08001150 for (auto &jsonPath : jsonPaths)
1151 {
1152 std::ifstream jsonStream(jsonPath.c_str());
1153 if (!jsonStream.good())
1154 {
1155 std::cerr << "unable to open " << jsonPath.string() << "\n";
1156 continue;
1157 }
1158 auto data = nlohmann::json::parse(jsonStream, nullptr, false);
1159 if (data.is_discarded())
1160 {
1161 std::cerr << "syntax error in " << jsonPath.string() << "\n";
1162 continue;
1163 }
James Feistb4383f42018-08-06 16:54:10 -07001164 if (!validateJson(schema, data))
1165 {
1166 std::cerr << "Error validating " << jsonPath.string() << "\n";
1167 continue;
1168 }
1169
James Feist3cb5fec2018-01-23 14:41:51 -08001170 if (data.type() == nlohmann::json::value_t::array)
1171 {
1172 for (auto &d : data)
1173 {
1174 configurations.emplace_back(d);
1175 }
1176 }
1177 else
1178 {
1179 configurations.emplace_back(data);
1180 }
1181 }
James Feist75fdeeb2018-02-20 14:26:16 -08001182}
James Feist3cb5fec2018-01-23 14:41:51 -08001183
James Feist8f2710a2018-05-09 17:18:55 -07001184struct PerformScan : std::enable_shared_from_this<PerformScan>
James Feist75fdeeb2018-02-20 14:26:16 -08001185{
James Feist75fdeeb2018-02-20 14:26:16 -08001186
James Feist8f2710a2018-05-09 17:18:55 -07001187 PerformScan(nlohmann::json &systemConfiguration,
1188 std::list<nlohmann::json> &configurations,
1189 std::function<void(void)> &&callback) :
1190 _systemConfiguration(systemConfiguration),
1191 _configurations(configurations), _callback(std::move(callback))
James Feist3cb5fec2018-01-23 14:41:51 -08001192 {
James Feist8f2710a2018-05-09 17:18:55 -07001193 }
1194 void run()
1195 {
1196 for (auto it = _configurations.begin(); it != _configurations.end();)
James Feist3cb5fec2018-01-23 14:41:51 -08001197 {
James Feist1e3e6982018-08-03 16:09:28 -07001198 auto findProbe = it->find("Probe");
James Feistd63d18a2018-07-19 15:23:45 -07001199 auto findName = it->find("Name");
James Feist3cb5fec2018-01-23 14:41:51 -08001200
James Feist1b2e2242018-01-30 13:45:19 -08001201 nlohmann::json probeCommand;
1202 // check for poorly formatted fields, probe must be an array
1203 if (findProbe == it->end())
James Feist3cb5fec2018-01-23 14:41:51 -08001204 {
1205 std::cerr << "configuration file missing probe:\n " << *it
1206 << "\n";
James Feist8f2710a2018-05-09 17:18:55 -07001207 it = _configurations.erase(it);
1208 continue;
James Feist3cb5fec2018-01-23 14:41:51 -08001209 }
James Feist1b2e2242018-01-30 13:45:19 -08001210 else if ((*findProbe).type() != nlohmann::json::value_t::array)
James Feist3cb5fec2018-01-23 14:41:51 -08001211 {
1212 probeCommand = nlohmann::json::array();
1213 probeCommand.push_back(*findProbe);
1214 }
1215 else
1216 {
1217 probeCommand = *findProbe;
1218 }
James Feist1b2e2242018-01-30 13:45:19 -08001219
1220 if (findName == it->end())
1221 {
1222 std::cerr << "configuration file missing name:\n " << *it
1223 << "\n";
James Feist8f2710a2018-05-09 17:18:55 -07001224 it = _configurations.erase(it);
1225 continue;
James Feist1b2e2242018-01-30 13:45:19 -08001226 }
James Feist8f2710a2018-05-09 17:18:55 -07001227 std::string name = *findName;
James Feist1b2e2242018-01-30 13:45:19 -08001228
James Feist8f2710a2018-05-09 17:18:55 -07001229 if (std::find(PASSED_PROBES.begin(), PASSED_PROBES.end(), name) !=
1230 PASSED_PROBES.end())
James Feist3cb5fec2018-01-23 14:41:51 -08001231 {
James Feist8f2710a2018-05-09 17:18:55 -07001232 it = _configurations.erase(it);
1233 continue;
1234 }
1235 nlohmann::json *record = &(*it);
James Feist3cb5fec2018-01-23 14:41:51 -08001236
James Feist8f2710a2018-05-09 17:18:55 -07001237 // store reference to this to children to makes sure we don't get
1238 // destroyed too early
1239 auto thisRef = shared_from_this();
1240 auto p = std::make_shared<PerformProbe>(
1241 probeCommand,
1242 [&, record, name,
1243 thisRef](std::vector<boost::container::flat_map<
1244 std::string, BasicVariantType>> &foundDevices) {
1245 _passed = true;
James Feist3cb5fec2018-01-23 14:41:51 -08001246
James Feist8f2710a2018-05-09 17:18:55 -07001247 PASSED_PROBES.push_back(name);
1248 size_t foundDeviceIdx = 0;
1249
James Feistbe5425f2018-06-08 10:30:55 -07001250 // insert into configuration temporarly to be able to
1251 // reference ourselves
1252 _systemConfiguration[name] = *record;
1253
James Feist8f2710a2018-05-09 17:18:55 -07001254 for (auto &foundDevice : foundDevices)
James Feist3cb5fec2018-01-23 14:41:51 -08001255 {
James Feist8f2710a2018-05-09 17:18:55 -07001256 for (auto keyPair = record->begin();
1257 keyPair != record->end(); keyPair++)
James Feist3cb5fec2018-01-23 14:41:51 -08001258 {
James Feist1b2e2242018-01-30 13:45:19 -08001259 templateCharReplace(keyPair, foundDevice,
1260 foundDeviceIdx);
James Feist8f2710a2018-05-09 17:18:55 -07001261 }
James Feist1e3e6982018-08-03 16:09:28 -07001262 auto findExpose = record->find("Exposes");
James Feist8f2710a2018-05-09 17:18:55 -07001263 if (findExpose == record->end())
1264 {
1265 continue;
1266 }
1267 for (auto &expose : *findExpose)
1268 {
1269 for (auto keyPair = expose.begin();
1270 keyPair != expose.end(); keyPair++)
James Feist3cb5fec2018-01-23 14:41:51 -08001271 {
James Feist1b2e2242018-01-30 13:45:19 -08001272
James Feist8f2710a2018-05-09 17:18:55 -07001273 // fill in template characters with devices
1274 // found
1275 templateCharReplace(keyPair, foundDevice,
1276 foundDeviceIdx);
1277 // special case bind
James Feist1e3e6982018-08-03 16:09:28 -07001278 if (boost::starts_with(keyPair.key(), "Bind"))
James Feist8f2710a2018-05-09 17:18:55 -07001279 {
1280 if (keyPair.value().type() !=
1281 nlohmann::json::value_t::string)
James Feist3cb5fec2018-01-23 14:41:51 -08001282 {
James Feist8f2710a2018-05-09 17:18:55 -07001283 std::cerr << "bind_ value must be of "
1284 "type string "
1285 << keyPair.key() << "\n";
James Feist1b2e2242018-01-30 13:45:19 -08001286 continue;
1287 }
James Feist8f2710a2018-05-09 17:18:55 -07001288 bool foundBind = false;
1289 std::string bind = keyPair.key().substr(
James Feist1e3e6982018-08-03 16:09:28 -07001290 sizeof("Bind") - 1);
James Feistbe5425f2018-06-08 10:30:55 -07001291
James Feist8f2710a2018-05-09 17:18:55 -07001292 for (auto &configurationPair :
1293 _systemConfiguration.items())
James Feist1b2e2242018-01-30 13:45:19 -08001294 {
James Feist1b2e2242018-01-30 13:45:19 -08001295
James Feist8f2710a2018-05-09 17:18:55 -07001296 auto configListFind =
1297 configurationPair.value().find(
James Feist1e3e6982018-08-03 16:09:28 -07001298 "Exposes");
James Feist8f2710a2018-05-09 17:18:55 -07001299
1300 if (configListFind ==
1301 configurationPair.value()
1302 .end() ||
1303 configListFind->type() !=
1304 nlohmann::json::value_t::array)
1305 {
1306 continue;
1307 }
1308 for (auto &exposedObject :
1309 *configListFind)
1310 {
1311 std::string foundObjectName =
James Feistd63d18a2018-07-19 15:23:45 -07001312 (exposedObject)["Name"];
James Feist8f2710a2018-05-09 17:18:55 -07001313 if (boost::iequals(
1314 foundObjectName,
1315 keyPair.value()
1316 .get<std::string>()))
1317 {
James Feist1e3e6982018-08-03 16:09:28 -07001318 exposedObject["Status"] =
James Feist8f2710a2018-05-09 17:18:55 -07001319 "okay";
1320 expose[bind] = exposedObject;
1321
1322 foundBind = true;
1323 break;
1324 }
1325 }
1326 if (foundBind)
1327 {
James Feist3cb5fec2018-01-23 14:41:51 -08001328 break;
1329 }
1330 }
James Feist8f2710a2018-05-09 17:18:55 -07001331 if (!foundBind)
James Feist3cb5fec2018-01-23 14:41:51 -08001332 {
James Feist8f2710a2018-05-09 17:18:55 -07001333 std::cerr << "configuration file "
1334 "dependency error, "
1335 "could not find bind "
1336 << keyPair.value() << "\n";
James Feist3cb5fec2018-01-23 14:41:51 -08001337 }
1338 }
1339 }
1340 }
1341 }
James Feistbe5425f2018-06-08 10:30:55 -07001342 // overwrite ourselves with cleaned up version
James Feist8f2710a2018-05-09 17:18:55 -07001343 _systemConfiguration[name] = *record;
1344 });
1345 p->run();
1346 it++;
James Feist3cb5fec2018-01-23 14:41:51 -08001347 }
1348 }
James Feist75fdeeb2018-02-20 14:26:16 -08001349
James Feist8f2710a2018-05-09 17:18:55 -07001350 ~PerformScan()
James Feist75fdeeb2018-02-20 14:26:16 -08001351 {
James Feist8f2710a2018-05-09 17:18:55 -07001352 if (_passed)
1353 {
1354 auto nextScan = std::make_shared<PerformScan>(
1355 _systemConfiguration, _configurations, std::move(_callback));
1356 nextScan->run();
1357 }
1358 else
1359 {
1360 _callback();
1361 }
1362 }
1363 nlohmann::json &_systemConfiguration;
1364 std::list<nlohmann::json> _configurations;
1365 std::function<void(void)> _callback;
1366 std::vector<std::shared_ptr<PerformProbe>> _probes;
1367 bool _passed = false;
1368};
James Feistc95cb142018-02-26 10:41:42 -08001369
James Feist8f2710a2018-05-09 17:18:55 -07001370// main properties changed entry
1371void propertiesChangedCallback(
1372 boost::asio::io_service &io,
1373 std::vector<sdbusplus::bus::match::match> &dbusMatches,
1374 nlohmann::json &systemConfiguration,
1375 sdbusplus::asio::object_server &objServer)
1376{
1377 static boost::asio::deadline_timer timer(io);
1378 timer.expires_from_now(boost::posix_time::seconds(1));
1379
1380 // setup an async wait as we normally get flooded with new requests
1381 timer.async_wait([&](const boost::system::error_code &ec) {
James Feist8f2710a2018-05-09 17:18:55 -07001382 if (ec == boost::asio::error::operation_aborted)
1383 {
1384 // we were cancelled
1385 return;
1386 }
1387 else if (ec)
1388 {
1389 std::cerr << "async wait error " << ec << "\n";
1390 return;
1391 }
1392
1393 nlohmann::json oldConfiguration = systemConfiguration;
1394 DBUS_PROBE_OBJECTS.clear();
1395
1396 std::list<nlohmann::json> configurations;
1397 if (!findJsonFiles(configurations))
1398 {
1399 std::cerr << "cannot find json files\n";
1400 return;
1401 }
1402
1403 auto perfScan = std::make_shared<PerformScan>(
1404 systemConfiguration, configurations, [&, oldConfiguration]() {
1405 nlohmann::json newConfiguration = systemConfiguration;
James Feist4131aea2018-03-09 09:47:30 -08001406 for (auto it = newConfiguration.begin();
1407 it != newConfiguration.end();)
1408 {
1409 auto findKey = oldConfiguration.find(it.key());
1410 if (findKey != oldConfiguration.end())
1411 {
1412 it = newConfiguration.erase(it);
1413 }
1414 else
1415 {
1416 it++;
1417 }
1418 }
James Feist8f2710a2018-05-09 17:18:55 -07001419 registerCallbacks(io, dbusMatches, systemConfiguration,
1420 objServer);
1421 io.post([&, newConfiguration]() {
James Feista2a98112018-08-07 11:15:37 -07001422
1423#ifdef OVERLAYS
James Feist8f2710a2018-05-09 17:18:55 -07001424 // todo: for now, only add new configurations,
1425 // unload to come later unloadOverlays();
1426 loadOverlays(newConfiguration);
James Feista2a98112018-08-07 11:15:37 -07001427#endif
James Feistbb43d022018-06-12 15:44:33 -07001428 io.post([&]() {
1429 if (!writeJsonFiles(systemConfiguration))
1430 {
1431 std::cerr << "Error writing json files\n";
1432 }
1433 });
James Feist8f2710a2018-05-09 17:18:55 -07001434 io.post([&, newConfiguration]() {
James Feist97a63f12018-05-17 13:50:57 -07001435 postToDbus(newConfiguration, systemConfiguration,
1436 objServer);
James Feist8f2710a2018-05-09 17:18:55 -07001437 });
1438 });
1439 });
1440 perfScan->run();
1441 });
James Feist75fdeeb2018-02-20 14:26:16 -08001442}
1443
James Feist8f2710a2018-05-09 17:18:55 -07001444void registerCallbacks(boost::asio::io_service &io,
1445 std::vector<sdbusplus::bus::match::match> &dbusMatches,
1446 nlohmann::json &systemConfiguration,
1447 sdbusplus::asio::object_server &objServer)
James Feist75fdeeb2018-02-20 14:26:16 -08001448{
1449 static boost::container::flat_set<std::string> watchedObjects;
1450
1451 for (const auto &objectMap : DBUS_PROBE_OBJECTS)
1452 {
1453 auto findObject = watchedObjects.find(objectMap.first);
1454 if (findObject != watchedObjects.end())
1455 {
1456 continue;
1457 }
James Feist8f2710a2018-05-09 17:18:55 -07001458 std::function<void(sdbusplus::message::message & message)>
1459 eventHandler =
James Feist75fdeeb2018-02-20 14:26:16 -08001460
James Feist8f2710a2018-05-09 17:18:55 -07001461 [&](sdbusplus::message::message &) {
1462 propertiesChangedCallback(io, dbusMatches,
1463 systemConfiguration, objServer);
1464 };
1465
1466 sdbusplus::bus::match::match match(
1467 static_cast<sdbusplus::bus::bus &>(*SYSTEM_BUS),
1468 "type='signal',member='PropertiesChanged',arg0='" +
1469 objectMap.first + "'",
1470 eventHandler);
1471 dbusMatches.emplace_back(std::move(match));
James Feist75fdeeb2018-02-20 14:26:16 -08001472 }
1473}
1474
1475int main(int argc, char **argv)
1476{
1477 // setup connection to dbus
1478 boost::asio::io_service io;
James Feist8f2710a2018-05-09 17:18:55 -07001479 SYSTEM_BUS = std::make_shared<sdbusplus::asio::connection>(io);
James Feist75fdeeb2018-02-20 14:26:16 -08001480 SYSTEM_BUS->request_name("xyz.openbmc_project.EntityManager");
James Feist4131aea2018-03-09 09:47:30 -08001481
James Feist8f2710a2018-05-09 17:18:55 -07001482 sdbusplus::asio::object_server objServer(SYSTEM_BUS);
James Feistfd1264a2018-05-03 12:10:00 -07001483
James Feist8f2710a2018-05-09 17:18:55 -07001484 std::shared_ptr<sdbusplus::asio::dbus_interface> entityIface =
1485 objServer.add_interface("/xyz/openbmc_project/EntityManager",
1486 "xyz.openbmc_project.EntityManager");
James Feistfd1264a2018-05-03 12:10:00 -07001487
James Feist8f2710a2018-05-09 17:18:55 -07001488 std::shared_ptr<sdbusplus::asio::dbus_interface> inventoryIface =
1489 objServer.add_interface("/xyz/openbmc_project/inventory",
1490 "xyz.openbmc_project.Inventory.Manager");
James Feist4131aea2018-03-09 09:47:30 -08001491
1492 // to keep reference to the match / filter objects so they don't get
1493 // destroyed
James Feist8f2710a2018-05-09 17:18:55 -07001494 std::vector<sdbusplus::bus::match::match> dbusMatches;
1495
1496 nlohmann::json systemConfiguration = nlohmann::json::object();
1497
1498 inventoryIface->register_method(
1499 "Notify", [](const boost::container::flat_map<
1500 std::string,
1501 boost::container::flat_map<std::string, BasicVariantType>>
1502 &object) { return; });
1503 inventoryIface->initialize();
1504
1505 io.post([&]() {
1506 unloadAllOverlays();
1507 propertiesChangedCallback(io, dbusMatches, systemConfiguration,
1508 objServer);
1509 });
James Feist4131aea2018-03-09 09:47:30 -08001510
James Feistfd1264a2018-05-03 12:10:00 -07001511 entityIface->register_method("ReScan", [&]() {
James Feist8f2710a2018-05-09 17:18:55 -07001512 propertiesChangedCallback(io, dbusMatches, systemConfiguration,
1513 objServer);
James Feist75fdeeb2018-02-20 14:26:16 -08001514 });
James Feist8f2710a2018-05-09 17:18:55 -07001515 entityIface->initialize();
1516
James Feist1b2e2242018-01-30 13:45:19 -08001517 io.run();
James Feist3cb5fec2018-01-23 14:41:51 -08001518
1519 return 0;
James Feist75fdeeb2018-02-20 14:26:16 -08001520}