blob: 62c57494375ce189c07bd31e516e5fb26422bcd8 [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*/
Brad Bishop1fb9f3f2020-08-28 08:15:13 -040016/// \file EntityManager.cpp
James Feist3cb5fec2018-01-23 14:41:51 -080017
James Feist1df06a42019-04-11 14:23:04 -070018#include "EntityManager.hpp"
19
Patrick Venturea49dc332019-10-26 08:32:02 -070020#include "Overlay.hpp"
21#include "Utils.hpp"
James Feist481c5d52019-08-13 14:40:40 -070022#include "VariantVisitors.hpp"
23
James Feist11be6672018-04-06 14:05:32 -070024#include <boost/algorithm/string/case_conv.hpp>
James Feistf5125b02019-06-06 11:27:43 -070025#include <boost/algorithm/string/classification.hpp>
James Feist3cb5fec2018-01-23 14:41:51 -080026#include <boost/algorithm/string/predicate.hpp>
27#include <boost/algorithm/string/replace.hpp>
James Feistf5125b02019-06-06 11:27:43 -070028#include <boost/algorithm/string/split.hpp>
James Feist02d2b932020-02-06 16:28:48 -080029#include <boost/asio/io_context.hpp>
James Feistb1728ca2020-04-30 15:40:55 -070030#include <boost/asio/steady_timer.hpp>
James Feist3cb5fec2018-01-23 14:41:51 -080031#include <boost/container/flat_map.hpp>
32#include <boost/container/flat_set.hpp>
James Feistf5125b02019-06-06 11:27:43 -070033#include <boost/range/iterator_range.hpp>
James Feist8c505da2020-05-28 10:06:33 -070034#include <nlohmann/json.hpp>
35#include <sdbusplus/asio/connection.hpp>
36#include <sdbusplus/asio/object_server.hpp>
37
James Feist637b3ef2019-04-15 16:35:30 -070038#include <filesystem>
James Feista465ccc2019-02-08 12:51:01 -080039#include <fstream>
40#include <iostream>
James Feista465ccc2019-02-08 12:51:01 -080041#include <regex>
James Feista465ccc2019-02-08 12:51:01 -080042#include <variant>
James Feista465ccc2019-02-08 12:51:01 -080043constexpr const char* configurationDirectory = PACKAGE_DIR "configurations";
44constexpr const char* schemaDirectory = PACKAGE_DIR "configurations/schemas";
James Feist1df06a42019-04-11 14:23:04 -070045constexpr const char* tempConfigDir = "/tmp/configuration/";
46constexpr const char* lastConfiguration = "/tmp/configuration/last.json";
47constexpr const char* currentConfiguration = "/var/configuration/system.json";
James Feista465ccc2019-02-08 12:51:01 -080048constexpr const char* globalSchema = "global.json";
James Feist8f2710a2018-05-09 17:18:55 -070049constexpr const int32_t MAX_MAPPER_DEPTH = 0;
James Feist3cb5fec2018-01-23 14:41:51 -080050
James Feistf1b14142019-04-10 15:22:09 -070051constexpr const bool DEBUG = false;
52
James Feist3cb5fec2018-01-23 14:41:51 -080053struct cmp_str
54{
James Feista465ccc2019-02-08 12:51:01 -080055 bool operator()(const char* a, const char* b) const
James Feist3cb5fec2018-01-23 14:41:51 -080056 {
57 return std::strcmp(a, b) < 0;
58 }
59};
60
61// underscore T for collison with dbus c api
62enum class probe_type_codes
63{
64 FALSE_T,
65 TRUE_T,
66 AND,
67 OR,
James Feist6bd2a022018-03-13 12:30:58 -070068 FOUND,
69 MATCH_ONE
James Feist3cb5fec2018-01-23 14:41:51 -080070};
James Feista465ccc2019-02-08 12:51:01 -080071const static boost::container::flat_map<const char*, probe_type_codes, cmp_str>
James Feist3cb5fec2018-01-23 14:41:51 -080072 PROBE_TYPES{{{"FALSE", probe_type_codes::FALSE_T},
73 {"TRUE", probe_type_codes::TRUE_T},
74 {"AND", probe_type_codes::AND},
75 {"OR", probe_type_codes::OR},
James Feist6bd2a022018-03-13 12:30:58 -070076 {"FOUND", probe_type_codes::FOUND},
77 {"MATCH_ONE", probe_type_codes::MATCH_ONE}}};
James Feist3cb5fec2018-01-23 14:41:51 -080078
Adrian Ambrożewiczc789fca2020-05-14 15:50:05 +020079static constexpr std::array<const char*, 6> settableInterfaces = {
80 "FanProfile", "Pid", "Pid.Zone", "Stepwise", "Thresholds", "Polling"};
James Feist68500ff2018-08-08 15:40:42 -070081using JsonVariantType =
James Feist338b8a72019-03-01 10:16:45 -080082 std::variant<std::vector<std::string>, std::vector<double>, std::string,
83 int64_t, uint64_t, double, int32_t, uint32_t, int16_t,
84 uint16_t, uint8_t, bool>;
James Feist3cb5fec2018-01-23 14:41:51 -080085using GetSubTreeType = std::vector<
86 std::pair<std::string,
87 std::vector<std::pair<std::string, std::vector<std::string>>>>>;
88
89using ManagedObjectType = boost::container::flat_map<
James Feist8f2710a2018-05-09 17:18:55 -070090 sdbusplus::message::object_path,
James Feist3cb5fec2018-01-23 14:41:51 -080091 boost::container::flat_map<
92 std::string,
James Feist8f2710a2018-05-09 17:18:55 -070093 boost::container::flat_map<std::string, BasicVariantType>>>;
James Feist3cb5fec2018-01-23 14:41:51 -080094
James Feistd58879a82019-09-11 11:26:07 -070095// store reference to all interfaces so we can destroy them later
96boost::container::flat_map<
James Feist02d2b932020-02-06 16:28:48 -080097 std::string, std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>>
James Feistd58879a82019-09-11 11:26:07 -070098 inventory;
99
James Feist3cb5fec2018-01-23 14:41:51 -0800100// todo: pass this through nicer
James Feist8f2710a2018-05-09 17:18:55 -0700101std::shared_ptr<sdbusplus::asio::connection> SYSTEM_BUS;
James Feist1df06a42019-04-11 14:23:04 -0700102static nlohmann::json lastJson;
James Feist3cb5fec2018-01-23 14:41:51 -0800103
James Feist02d2b932020-02-06 16:28:48 -0800104boost::asio::io_context io;
105
Johnathan Mantey2015f752019-03-26 15:22:31 -0700106const std::regex ILLEGAL_DBUS_PATH_REGEX("[^A-Za-z0-9_.]");
107const std::regex ILLEGAL_DBUS_MEMBER_REGEX("[^A-Za-z0-9_]");
James Feist1b2e2242018-01-30 13:45:19 -0800108
James Feist4dc617b2020-05-01 09:54:47 -0700109void registerCallback(nlohmann::json& systemConfiguration,
110 sdbusplus::asio::object_server& objServer,
111 const std::string& interfaces);
James Feist75fdeeb2018-02-20 14:26:16 -0800112
James Feistd58879a82019-09-11 11:26:07 -0700113static std::shared_ptr<sdbusplus::asio::dbus_interface>
114 createInterface(sdbusplus::asio::object_server& objServer,
115 const std::string& path, const std::string& interface,
James Feist02d2b932020-02-06 16:28:48 -0800116 const std::string& parent, bool checkNull = false)
James Feistd58879a82019-09-11 11:26:07 -0700117{
James Feist02d2b932020-02-06 16:28:48 -0800118 // on first add we have no reason to check for null before add, as there
119 // won't be any. For dynamically added interfaces, we check for null so that
120 // a constant delete/add will not create a memory leak
121
122 auto ptr = objServer.add_interface(path, interface);
123 auto& dataVector = inventory[parent];
124 if (checkNull)
125 {
126 auto it = std::find_if(dataVector.begin(), dataVector.end(),
127 [](const auto& p) { return p.expired(); });
128 if (it != dataVector.end())
129 {
130 *it = ptr;
131 return ptr;
132 }
133 }
134 dataVector.emplace_back(ptr);
135 return ptr;
James Feistd58879a82019-09-11 11:26:07 -0700136}
137
James Feistb1728ca2020-04-30 15:40:55 -0700138void getInterfaces(
139 const std::tuple<std::string, std::string, std::string>& call,
140 const std::vector<std::shared_ptr<PerformProbe>>& probeVector,
141 std::shared_ptr<PerformScan> scan, size_t retries = 5)
142{
143 if (!retries)
144 {
145 std::cerr << "retries exhausted on " << std::get<0>(call) << " "
146 << std::get<1>(call) << " " << std::get<2>(call) << "\n";
147 return;
148 }
149
150 SYSTEM_BUS->async_method_call(
151 [call, scan, probeVector, retries](
152 boost::system::error_code& errc,
153 const boost::container::flat_map<std::string, BasicVariantType>&
154 resp) {
155 if (errc)
156 {
157 std::cerr << "error calling getall on " << std::get<0>(call)
158 << " " << std::get<1>(call) << " "
159 << std::get<2>(call) << "\n";
160
161 std::shared_ptr<boost::asio::steady_timer> timer =
162 std::make_shared<boost::asio::steady_timer>(io);
163 timer->expires_after(std::chrono::seconds(2));
164
165 timer->async_wait([timer, call, scan, probeVector,
166 retries](const boost::system::error_code&) {
167 getInterfaces(call, probeVector, scan, retries - 1);
168 });
169 return;
170 }
171
172 scan->dbusProbeObjects[std::get<2>(call)].emplace_back(resp);
173 },
174 std::get<0>(call), std::get<1>(call), "org.freedesktop.DBus.Properties",
175 "GetAll", std::get<2>(call));
176
177 if constexpr (DEBUG)
178 {
179 std::cerr << __func__ << " " << __LINE__ << "\n";
180 }
181}
182
James Feist3cb5fec2018-01-23 14:41:51 -0800183// calls the mapper to find all exposed objects of an interface type
184// and creates a vector<flat_map> that contains all the key value pairs
185// getManagedObjects
James Feistb1728ca2020-04-30 15:40:55 -0700186void findDbusObjects(std::vector<std::shared_ptr<PerformProbe>>&& probeVector,
187 boost::container::flat_set<std::string>&& interfaces,
James Feist733f7652019-11-13 14:32:29 -0800188 std::shared_ptr<PerformScan> scan)
James Feist3cb5fec2018-01-23 14:41:51 -0800189{
James Feist8f2710a2018-05-09 17:18:55 -0700190
James Feist733f7652019-11-13 14:32:29 -0800191 for (const auto& [interface, _] : scan->dbusProbeObjects)
192 {
193 interfaces.erase(interface);
194 }
195 if (interfaces.empty())
196 {
197 return;
198 }
199
James Feist3cb5fec2018-01-23 14:41:51 -0800200 // find all connections in the mapper that expose a specific type
James Feistb1728ca2020-04-30 15:40:55 -0700201 SYSTEM_BUS->async_method_call(
202 [interfaces{std::move(interfaces)}, probeVector{std::move(probeVector)},
James Feist733f7652019-11-13 14:32:29 -0800203 scan](boost::system::error_code& ec,
204 const GetSubTreeType& interfaceSubtree) {
James Feistb1728ca2020-04-30 15:40:55 -0700205 boost::container::flat_set<
206 std::tuple<std::string, std::string, std::string>>
207 interfaceConnections;
James Feist8f2710a2018-05-09 17:18:55 -0700208 if (ec)
James Feist494155a2018-03-14 16:23:24 -0700209 {
James Feist0de40152018-07-25 11:56:12 -0700210 if (ec.value() == ENOENT)
James Feist8f2710a2018-05-09 17:18:55 -0700211 {
James Feist0de40152018-07-25 11:56:12 -0700212 return; // wasn't found by mapper
James Feist8f2710a2018-05-09 17:18:55 -0700213 }
James Feist0de40152018-07-25 11:56:12 -0700214 std::cerr << "Error communicating to mapper.\n";
215
216 // if we can't communicate to the mapper something is very wrong
217 std::exit(EXIT_FAILURE);
James Feist494155a2018-03-14 16:23:24 -0700218 }
James Feist787c3c32019-11-07 14:42:58 -0800219
James Feistb1728ca2020-04-30 15:40:55 -0700220 for (const auto& [path, object] : interfaceSubtree)
James Feist3cb5fec2018-01-23 14:41:51 -0800221 {
James Feistb1728ca2020-04-30 15:40:55 -0700222 for (const auto& [busname, ifaces] : object)
James Feist8f2710a2018-05-09 17:18:55 -0700223 {
James Feistb1728ca2020-04-30 15:40:55 -0700224 for (const std::string& iface : ifaces)
225 {
226 auto ifaceObjFind = interfaces.find(iface);
227
228 if (ifaceObjFind != interfaces.end())
229 {
230 interfaceConnections.emplace(busname, path, iface);
231 }
232 }
James Feist8f2710a2018-05-09 17:18:55 -0700233 }
James Feist3cb5fec2018-01-23 14:41:51 -0800234 }
James Feist787c3c32019-11-07 14:42:58 -0800235
James Feist63845bf2019-01-24 12:19:51 -0800236 if (interfaceConnections.empty())
237 {
James Feist63845bf2019-01-24 12:19:51 -0800238 return;
239 }
James Feist787c3c32019-11-07 14:42:58 -0800240
James Feistb1728ca2020-04-30 15:40:55 -0700241 for (const auto& call : interfaceConnections)
242 {
243 getInterfaces(call, probeVector, scan);
James Feist8f2710a2018-05-09 17:18:55 -0700244 }
245 },
246 "xyz.openbmc_project.ObjectMapper",
247 "/xyz/openbmc_project/object_mapper",
James Feist5131ac22018-10-25 12:22:23 -0700248 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", MAX_MAPPER_DEPTH,
James Feist787c3c32019-11-07 14:42:58 -0800249 interfaces);
James Feistb1728ca2020-04-30 15:40:55 -0700250
251 if constexpr (DEBUG)
252 {
253 std::cerr << __func__ << " " << __LINE__ << "\n";
254 }
James Feist3cb5fec2018-01-23 14:41:51 -0800255}
James Feistb1728ca2020-04-30 15:40:55 -0700256
James Feist8f2710a2018-05-09 17:18:55 -0700257// probes dbus interface dictionary for a key with a value that matches a regex
James Feist08a5b172019-08-28 14:47:47 -0700258bool probeDbus(const std::string& interface,
259 const std::map<std::string, nlohmann::json>& matches,
James Feist733f7652019-11-13 14:32:29 -0800260 FoundDeviceT& devices, std::shared_ptr<PerformScan> scan,
261 bool& foundProbe)
James Feist3cb5fec2018-01-23 14:41:51 -0800262{
James Feista465ccc2019-02-08 12:51:01 -0800263 std::vector<boost::container::flat_map<std::string, BasicVariantType>>&
James Feist733f7652019-11-13 14:32:29 -0800264 dbusObject = scan->dbusProbeObjects[interface];
James Feist3cb5fec2018-01-23 14:41:51 -0800265 if (dbusObject.empty())
266 {
James Feist8f2710a2018-05-09 17:18:55 -0700267 foundProbe = false;
268 return false;
James Feist3cb5fec2018-01-23 14:41:51 -0800269 }
270 foundProbe = true;
271
272 bool foundMatch = false;
James Feista465ccc2019-02-08 12:51:01 -0800273 for (auto& device : dbusObject)
James Feist3cb5fec2018-01-23 14:41:51 -0800274 {
275 bool deviceMatches = true;
James Feista465ccc2019-02-08 12:51:01 -0800276 for (auto& match : matches)
James Feist3cb5fec2018-01-23 14:41:51 -0800277 {
278 auto deviceValue = device.find(match.first);
279 if (deviceValue != device.end())
280 {
281 switch (match.second.type())
282 {
James Feist9eb0b582018-04-27 12:15:46 -0700283 case nlohmann::json::value_t::string:
James Feist3cb5fec2018-01-23 14:41:51 -0800284 {
James Feist9eb0b582018-04-27 12:15:46 -0700285 std::regex search(match.second.get<std::string>());
James Feist98132792019-07-09 13:29:09 -0700286 std::smatch regMatch;
James Feist9eb0b582018-04-27 12:15:46 -0700287
288 // convert value to string respresentation
James Feista465ccc2019-02-08 12:51:01 -0800289 std::string probeValue = std::visit(
James Feist8f2710a2018-05-09 17:18:55 -0700290 VariantToStringVisitor(), deviceValue->second);
James Feist98132792019-07-09 13:29:09 -0700291 if (!std::regex_search(probeValue, regMatch, search))
James Feist9eb0b582018-04-27 12:15:46 -0700292 {
293 deviceMatches = false;
294 break;
295 }
James Feist3cb5fec2018-01-23 14:41:51 -0800296 break;
297 }
James Feist9eb0b582018-04-27 12:15:46 -0700298 case nlohmann::json::value_t::boolean:
299 case nlohmann::json::value_t::number_unsigned:
James Feist3cb5fec2018-01-23 14:41:51 -0800300 {
James Feista465ccc2019-02-08 12:51:01 -0800301 unsigned int probeValue = std::visit(
James Feist9eb0b582018-04-27 12:15:46 -0700302 VariantToUnsignedIntVisitor(), deviceValue->second);
James Feist3cb5fec2018-01-23 14:41:51 -0800303
James Feist9eb0b582018-04-27 12:15:46 -0700304 if (probeValue != match.second.get<unsigned int>())
305 {
306 deviceMatches = false;
307 }
308 break;
James Feist3cb5fec2018-01-23 14:41:51 -0800309 }
James Feist9eb0b582018-04-27 12:15:46 -0700310 case nlohmann::json::value_t::number_integer:
311 {
James Feista465ccc2019-02-08 12:51:01 -0800312 int probeValue = std::visit(VariantToIntVisitor(),
313 deviceValue->second);
James Feist3cb5fec2018-01-23 14:41:51 -0800314
James Feist9eb0b582018-04-27 12:15:46 -0700315 if (probeValue != match.second.get<int>())
316 {
317 deviceMatches = false;
318 }
319 break;
James Feist3cb5fec2018-01-23 14:41:51 -0800320 }
James Feist9eb0b582018-04-27 12:15:46 -0700321 case nlohmann::json::value_t::number_float:
322 {
James Feista465ccc2019-02-08 12:51:01 -0800323 float probeValue = std::visit(VariantToFloatVisitor(),
324 deviceValue->second);
James Feist9eb0b582018-04-27 12:15:46 -0700325
326 if (probeValue != match.second.get<float>())
327 {
328 deviceMatches = false;
329 }
330 break;
331 }
James Feist0eb40352019-04-09 14:44:04 -0700332 default:
333 {
334 std::cerr << "unexpected dbus probe type "
335 << match.second.type_name() << "\n";
Brad Bishop42124852020-08-19 17:17:01 -0400336 deviceMatches = false;
James Feist0eb40352019-04-09 14:44:04 -0700337 }
James Feist3cb5fec2018-01-23 14:41:51 -0800338 }
339 }
340 else
341 {
342 deviceMatches = false;
343 break;
344 }
345 }
346 if (deviceMatches)
347 {
James Feistf5125b02019-06-06 11:27:43 -0700348 devices.emplace_back(device);
James Feist3cb5fec2018-01-23 14:41:51 -0800349 foundMatch = true;
350 deviceMatches = false; // for next iteration
351 }
352 }
353 return foundMatch;
354}
355
356// default probe entry point, iterates a list looking for specific types to
357// call specific probe functions
358bool probe(
James Feista465ccc2019-02-08 12:51:01 -0800359 const std::vector<std::string>& probeCommand,
James Feist733f7652019-11-13 14:32:29 -0800360 std::shared_ptr<PerformScan> scan,
James Feist08a5b172019-08-28 14:47:47 -0700361 std::vector<boost::container::flat_map<std::string, BasicVariantType>>&
362 foundDevs)
James Feist3cb5fec2018-01-23 14:41:51 -0800363{
364 const static std::regex command(R"(\((.*)\))");
365 std::smatch match;
366 bool ret = false;
James Feist6bd2a022018-03-13 12:30:58 -0700367 bool matchOne = false;
James Feist3cb5fec2018-01-23 14:41:51 -0800368 bool cur = true;
369 probe_type_codes lastCommand = probe_type_codes::FALSE_T;
James Feist54a0dca2019-06-26 10:34:54 -0700370 bool first = true;
James Feist3cb5fec2018-01-23 14:41:51 -0800371
James Feista465ccc2019-02-08 12:51:01 -0800372 for (auto& probe : probeCommand)
James Feist3cb5fec2018-01-23 14:41:51 -0800373 {
374 bool foundProbe = false;
James Feista465ccc2019-02-08 12:51:01 -0800375 boost::container::flat_map<const char*, probe_type_codes,
James Feist3cb5fec2018-01-23 14:41:51 -0800376 cmp_str>::const_iterator probeType;
377
378 for (probeType = PROBE_TYPES.begin(); probeType != PROBE_TYPES.end();
Patrick Venture4b7a7452019-10-28 08:41:02 -0700379 ++probeType)
James Feist3cb5fec2018-01-23 14:41:51 -0800380 {
381 if (probe.find(probeType->first) != std::string::npos)
382 {
383 foundProbe = true;
384 break;
385 }
386 }
387 if (foundProbe)
388 {
389 switch (probeType->second)
390 {
James Feist9eb0b582018-04-27 12:15:46 -0700391 case probe_type_codes::FALSE_T:
James Feist3cb5fec2018-01-23 14:41:51 -0800392 {
James Feiste31e00a2019-07-24 10:45:43 -0700393 cur = false;
394 break;
James Feist3cb5fec2018-01-23 14:41:51 -0800395 }
James Feist9eb0b582018-04-27 12:15:46 -0700396 case probe_type_codes::TRUE_T:
397 {
James Feiste31e00a2019-07-24 10:45:43 -0700398 cur = true;
399 break;
James Feist9eb0b582018-04-27 12:15:46 -0700400 }
401 case probe_type_codes::MATCH_ONE:
402 {
403 // set current value to last, this probe type shouldn't
404 // affect the outcome
405 cur = ret;
406 matchOne = true;
407 break;
408 }
409 /*case probe_type_codes::AND:
410 break;
411 case probe_type_codes::OR:
412 break;
413 // these are no-ops until the last command switch
414 */
415 case probe_type_codes::FOUND:
416 {
417 if (!std::regex_search(probe, match, command))
418 {
James Feist0eb40352019-04-09 14:44:04 -0700419 std::cerr << "found probe syntax error " << probe
James Feist9eb0b582018-04-27 12:15:46 -0700420 << "\n";
421 return false;
422 }
423 std::string commandStr = *(match.begin() + 1);
424 boost::replace_all(commandStr, "'", "");
James Feist733f7652019-11-13 14:32:29 -0800425 cur = (std::find(scan->passedProbes.begin(),
426 scan->passedProbes.end(),
427 commandStr) != scan->passedProbes.end());
James Feist9eb0b582018-04-27 12:15:46 -0700428 break;
429 }
James Feist0eb40352019-04-09 14:44:04 -0700430 default:
431 {
432 break;
433 }
James Feist3cb5fec2018-01-23 14:41:51 -0800434 }
435 }
436 // look on dbus for object
437 else
438 {
439 if (!std::regex_search(probe, match, command))
440 {
James Feist0eb40352019-04-09 14:44:04 -0700441 std::cerr << "dbus probe syntax error " << probe << "\n";
James Feist3cb5fec2018-01-23 14:41:51 -0800442 return false;
443 }
444 std::string commandStr = *(match.begin() + 1);
445 // convert single ticks and single slashes into legal json
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -0700446 boost::replace_all(commandStr, "'", "\"");
James Feist3f8a2782018-02-12 09:24:42 -0800447 boost::replace_all(commandStr, R"(\)", R"(\\)");
James Feist3cb5fec2018-01-23 14:41:51 -0800448 auto json = nlohmann::json::parse(commandStr, nullptr, false);
449 if (json.is_discarded())
450 {
James Feist0eb40352019-04-09 14:44:04 -0700451 std::cerr << "dbus command syntax error " << commandStr << "\n";
James Feist3cb5fec2018-01-23 14:41:51 -0800452 return false;
453 }
454 // we can match any (string, variant) property. (string, string)
455 // does a regex
456 std::map<std::string, nlohmann::json> dbusProbeMap =
457 json.get<std::map<std::string, nlohmann::json>>();
458 auto findStart = probe.find("(");
459 if (findStart == std::string::npos)
460 {
461 return false;
462 }
463 std::string probeInterface = probe.substr(0, findStart);
James Feist733f7652019-11-13 14:32:29 -0800464 cur = probeDbus(probeInterface, dbusProbeMap, foundDevs, scan,
465 foundProbe);
James Feist3cb5fec2018-01-23 14:41:51 -0800466 }
467
468 // some functions like AND and OR only take affect after the
469 // fact
James Feist54a0dca2019-06-26 10:34:54 -0700470 if (lastCommand == probe_type_codes::AND)
James Feist3cb5fec2018-01-23 14:41:51 -0800471 {
James Feist54a0dca2019-06-26 10:34:54 -0700472 ret = cur && ret;
473 }
474 else if (lastCommand == probe_type_codes::OR)
475 {
476 ret = cur || ret;
477 }
478
479 if (first)
480 {
481 ret = cur;
482 first = false;
James Feist3cb5fec2018-01-23 14:41:51 -0800483 }
484 lastCommand = probeType != PROBE_TYPES.end()
485 ? probeType->second
486 : probe_type_codes::FALSE_T;
James Feist3cb5fec2018-01-23 14:41:51 -0800487 }
488
489 // probe passed, but empty device
James Feist3cb5fec2018-01-23 14:41:51 -0800490 if (ret && foundDevs.size() == 0)
491 {
James Feist08a5b172019-08-28 14:47:47 -0700492 foundDevs.emplace_back(
493 boost::container::flat_map<std::string, BasicVariantType>{});
James Feist3cb5fec2018-01-23 14:41:51 -0800494 }
James Feist0eb40352019-04-09 14:44:04 -0700495 if (matchOne && ret)
James Feist6bd2a022018-03-13 12:30:58 -0700496 {
James Feist71f295f2019-06-20 13:35:12 -0700497 // match the last one
498 auto last = foundDevs.back();
James Feist0eb40352019-04-09 14:44:04 -0700499 foundDevs.clear();
James Feist71f295f2019-06-20 13:35:12 -0700500
501 foundDevs.emplace_back(std::move(last));
James Feist6bd2a022018-03-13 12:30:58 -0700502 }
James Feist3cb5fec2018-01-23 14:41:51 -0800503 return ret;
504}
505
James Feist7d807752019-11-13 14:47:57 -0800506PerformProbe::PerformProbe(const std::vector<std::string>& probeCommand,
507 std::shared_ptr<PerformScan>& scanPtr,
508 std::function<void(FoundDeviceT&)>&& callback) :
509 _probeCommand(probeCommand),
510 scan(scanPtr), _callback(std::move(callback))
James Feist8c505da2020-05-28 10:06:33 -0700511{}
James Feist7d807752019-11-13 14:47:57 -0800512PerformProbe::~PerformProbe()
513{
514 FoundDeviceT foundDevs;
515 if (probe(_probeCommand, scan, foundDevs))
James Feist8f2710a2018-05-09 17:18:55 -0700516 {
James Feist7d807752019-11-13 14:47:57 -0800517 _callback(foundDevs);
James Feist8f2710a2018-05-09 17:18:55 -0700518 }
James Feist7d807752019-11-13 14:47:57 -0800519}
James Feist8f2710a2018-05-09 17:18:55 -0700520
521// writes output files to persist data
James Feista465ccc2019-02-08 12:51:01 -0800522bool writeJsonFiles(const nlohmann::json& systemConfiguration)
James Feist1b2e2242018-01-30 13:45:19 -0800523{
James Feist1df06a42019-04-11 14:23:04 -0700524 std::filesystem::create_directory(configurationOutDir);
525 std::ofstream output(currentConfiguration);
James Feistbb43d022018-06-12 15:44:33 -0700526 if (!output.good())
527 {
528 return false;
529 }
James Feist1b2e2242018-01-30 13:45:19 -0800530 output << systemConfiguration.dump(4);
531 output.close();
James Feistbb43d022018-06-12 15:44:33 -0700532 return true;
James Feist8f2710a2018-05-09 17:18:55 -0700533}
James Feist1b2e2242018-01-30 13:45:19 -0800534
James Feist97a63f12018-05-17 13:50:57 -0700535template <typename JsonType>
James Feista465ccc2019-02-08 12:51:01 -0800536bool setJsonFromPointer(const std::string& ptrStr, const JsonType& value,
537 nlohmann::json& systemConfiguration)
James Feist97a63f12018-05-17 13:50:57 -0700538{
539 try
540 {
541 nlohmann::json::json_pointer ptr(ptrStr);
James Feista465ccc2019-02-08 12:51:01 -0800542 nlohmann::json& ref = systemConfiguration[ptr];
James Feist97a63f12018-05-17 13:50:57 -0700543 ref = value;
544 return true;
545 }
James Feist98132792019-07-09 13:29:09 -0700546 catch (const std::out_of_range&)
James Feist97a63f12018-05-17 13:50:57 -0700547 {
548 return false;
549 }
550}
James Feistbb43d022018-06-12 15:44:33 -0700551
James Feistebcc26b2019-03-22 12:30:43 -0700552// template function to add array as dbus property
553template <typename PropertyType>
554void addArrayToDbus(const std::string& name, const nlohmann::json& array,
555 sdbusplus::asio::dbus_interface* iface,
556 sdbusplus::asio::PropertyPermission permission,
557 nlohmann::json& systemConfiguration,
558 const std::string& jsonPointerString)
559{
560 std::vector<PropertyType> values;
561 for (const auto& property : array)
562 {
563 auto ptr = property.get_ptr<const PropertyType*>();
564 if (ptr != nullptr)
565 {
566 values.emplace_back(*ptr);
567 }
568 }
569
570 if (permission == sdbusplus::asio::PropertyPermission::readOnly)
571 {
572 iface->register_property(name, values);
573 }
574 else
575 {
576 iface->register_property(
577 name, values,
578 [&systemConfiguration,
579 jsonPointerString{std::string(jsonPointerString)}](
580 const std::vector<PropertyType>& newVal,
581 std::vector<PropertyType>& val) {
582 val = newVal;
583 if (!setJsonFromPointer(jsonPointerString, val,
584 systemConfiguration))
585 {
586 std::cerr << "error setting json field\n";
587 return -1;
588 }
589 if (!writeJsonFiles(systemConfiguration))
590 {
591 std::cerr << "error setting json file\n";
592 return -1;
593 }
594 return 1;
595 });
596 }
597}
598
James Feistbb43d022018-06-12 15:44:33 -0700599template <typename PropertyType>
James Feista465ccc2019-02-08 12:51:01 -0800600void addProperty(const std::string& propertyName, const PropertyType& value,
601 sdbusplus::asio::dbus_interface* iface,
602 nlohmann::json& systemConfiguration,
603 const std::string& jsonPointerString,
James Feistbb43d022018-06-12 15:44:33 -0700604 sdbusplus::asio::PropertyPermission permission)
605{
606 if (permission == sdbusplus::asio::PropertyPermission::readOnly)
607 {
608 iface->register_property(propertyName, value);
609 return;
610 }
James Feist68500ff2018-08-08 15:40:42 -0700611 iface->register_property(
612 propertyName, value,
613 [&systemConfiguration,
614 jsonPointerString{std::string(jsonPointerString)}](
James Feista465ccc2019-02-08 12:51:01 -0800615 const PropertyType& newVal, PropertyType& val) {
James Feist68500ff2018-08-08 15:40:42 -0700616 val = newVal;
617 if (!setJsonFromPointer(jsonPointerString, val,
618 systemConfiguration))
619 {
620 std::cerr << "error setting json field\n";
621 return -1;
622 }
James Feistc6248a52018-08-14 10:09:45 -0700623 if (!writeJsonFiles(systemConfiguration))
James Feist68500ff2018-08-08 15:40:42 -0700624 {
625 std::cerr << "error setting json file\n";
James Feistc6248a52018-08-14 10:09:45 -0700626 return -1;
627 }
628 return 1;
629 });
630}
631
632void createDeleteObjectMethod(
James Feista465ccc2019-02-08 12:51:01 -0800633 const std::string& jsonPointerPath,
634 const std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
635 sdbusplus::asio::object_server& objServer,
636 nlohmann::json& systemConfiguration)
James Feistc6248a52018-08-14 10:09:45 -0700637{
638 std::weak_ptr<sdbusplus::asio::dbus_interface> interface = iface;
639 iface->register_method(
640 "Delete", [&objServer, &systemConfiguration, interface,
641 jsonPointerPath{std::string(jsonPointerPath)}]() {
James Feist98132792019-07-09 13:29:09 -0700642 std::shared_ptr<sdbusplus::asio::dbus_interface> dbusInterface =
James Feistc6248a52018-08-14 10:09:45 -0700643 interface.lock();
James Feist98132792019-07-09 13:29:09 -0700644 if (!dbusInterface)
James Feistc6248a52018-08-14 10:09:45 -0700645 {
646 // this technically can't happen as the pointer is pointing to
647 // us
648 throw DBusInternalError();
649 }
650 nlohmann::json::json_pointer ptr(jsonPointerPath);
James Feistc6248a52018-08-14 10:09:45 -0700651 systemConfiguration[ptr] = nullptr;
652
James Feist02d2b932020-02-06 16:28:48 -0800653 // todo(james): dig through sdbusplus to find out why we can't
654 // delete it in a method call
655 io.post([&objServer, dbusInterface]() mutable {
656 objServer.remove_interface(dbusInterface);
657 });
658
James Feistc6248a52018-08-14 10:09:45 -0700659 if (!writeJsonFiles(systemConfiguration))
660 {
661 std::cerr << "error setting json file\n";
662 throw DBusInternalError();
James Feist68500ff2018-08-08 15:40:42 -0700663 }
James Feist68500ff2018-08-08 15:40:42 -0700664 });
James Feistbb43d022018-06-12 15:44:33 -0700665}
666
James Feist1b2e2242018-01-30 13:45:19 -0800667// adds simple json types to interface's properties
James Feistbb43d022018-06-12 15:44:33 -0700668void populateInterfaceFromJson(
James Feista465ccc2019-02-08 12:51:01 -0800669 nlohmann::json& systemConfiguration, const std::string& jsonPointerPath,
670 std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
671 nlohmann::json& dict, sdbusplus::asio::object_server& objServer,
James Feistbb43d022018-06-12 15:44:33 -0700672 sdbusplus::asio::PropertyPermission permission =
673 sdbusplus::asio::PropertyPermission::readOnly)
James Feist1b2e2242018-01-30 13:45:19 -0800674{
James Feista465ccc2019-02-08 12:51:01 -0800675 for (auto& dictPair : dict.items())
James Feist1b2e2242018-01-30 13:45:19 -0800676 {
James Feist8f2710a2018-05-09 17:18:55 -0700677 auto type = dictPair.value().type();
678 bool array = false;
679 if (dictPair.value().type() == nlohmann::json::value_t::array)
680 {
681 array = true;
682 if (!dictPair.value().size())
683 {
684 continue;
685 }
686 type = dictPair.value()[0].type();
687 bool isLegal = true;
James Feista465ccc2019-02-08 12:51:01 -0800688 for (const auto& arrayItem : dictPair.value())
James Feist8f2710a2018-05-09 17:18:55 -0700689 {
690 if (arrayItem.type() != type)
691 {
692 isLegal = false;
693 break;
694 }
695 }
696 if (!isLegal)
697 {
698 std::cerr << "dbus format error" << dictPair.value() << "\n";
699 continue;
700 }
James Feista218ddb2019-04-11 14:01:31 -0700701 }
702 if (type == nlohmann::json::value_t::object)
703 {
704 continue; // handled elsewhere
James Feist8f2710a2018-05-09 17:18:55 -0700705 }
James Feist97a63f12018-05-17 13:50:57 -0700706 std::string key = jsonPointerPath + "/" + dictPair.key();
James Feistbb43d022018-06-12 15:44:33 -0700707 if (permission == sdbusplus::asio::PropertyPermission::readWrite)
708 {
709 // all setable numbers are doubles as it is difficult to always
710 // create a configuration file with all whole numbers as decimals
711 // i.e. 1.0
James Feistebcc26b2019-03-22 12:30:43 -0700712 if (array)
713 {
714 if (dictPair.value()[0].is_number())
715 {
716 type = nlohmann::json::value_t::number_float;
717 }
718 }
719 else if (dictPair.value().is_number())
James Feistbb43d022018-06-12 15:44:33 -0700720 {
721 type = nlohmann::json::value_t::number_float;
722 }
723 }
724
James Feist8f2710a2018-05-09 17:18:55 -0700725 switch (type)
James Feist1b2e2242018-01-30 13:45:19 -0800726 {
James Feist9eb0b582018-04-27 12:15:46 -0700727 case (nlohmann::json::value_t::boolean):
728 {
James Feist8f2710a2018-05-09 17:18:55 -0700729 if (array)
730 {
731 // todo: array of bool isn't detected correctly by
732 // sdbusplus, change it to numbers
733 addArrayToDbus<uint64_t>(dictPair.key(), dictPair.value(),
James Feistebcc26b2019-03-22 12:30:43 -0700734 iface.get(), permission,
735 systemConfiguration, key);
James Feist8f2710a2018-05-09 17:18:55 -0700736 }
James Feistbb43d022018-06-12 15:44:33 -0700737
James Feist97a63f12018-05-17 13:50:57 -0700738 else
739 {
James Feistbb43d022018-06-12 15:44:33 -0700740 addProperty(dictPair.key(), dictPair.value().get<bool>(),
James Feistc6248a52018-08-14 10:09:45 -0700741 iface.get(), systemConfiguration, key,
742 permission);
James Feist97a63f12018-05-17 13:50:57 -0700743 }
James Feist9eb0b582018-04-27 12:15:46 -0700744 break;
745 }
746 case (nlohmann::json::value_t::number_integer):
747 {
James Feist8f2710a2018-05-09 17:18:55 -0700748 if (array)
749 {
750 addArrayToDbus<int64_t>(dictPair.key(), dictPair.value(),
James Feistebcc26b2019-03-22 12:30:43 -0700751 iface.get(), permission,
752 systemConfiguration, key);
James Feist97a63f12018-05-17 13:50:57 -0700753 }
754 else
755 {
James Feistbb43d022018-06-12 15:44:33 -0700756 addProperty(dictPair.key(), dictPair.value().get<int64_t>(),
James Feistc6248a52018-08-14 10:09:45 -0700757 iface.get(), systemConfiguration, key,
James Feistbb43d022018-06-12 15:44:33 -0700758 sdbusplus::asio::PropertyPermission::readOnly);
James Feist97a63f12018-05-17 13:50:57 -0700759 }
James Feist9eb0b582018-04-27 12:15:46 -0700760 break;
761 }
762 case (nlohmann::json::value_t::number_unsigned):
763 {
James Feist8f2710a2018-05-09 17:18:55 -0700764 if (array)
765 {
766 addArrayToDbus<uint64_t>(dictPair.key(), dictPair.value(),
James Feistebcc26b2019-03-22 12:30:43 -0700767 iface.get(), permission,
768 systemConfiguration, key);
James Feist97a63f12018-05-17 13:50:57 -0700769 }
770 else
771 {
James Feistbb43d022018-06-12 15:44:33 -0700772 addProperty(dictPair.key(),
James Feistc6248a52018-08-14 10:09:45 -0700773 dictPair.value().get<uint64_t>(), iface.get(),
James Feistbb43d022018-06-12 15:44:33 -0700774 systemConfiguration, key,
775 sdbusplus::asio::PropertyPermission::readOnly);
James Feist97a63f12018-05-17 13:50:57 -0700776 }
James Feist9eb0b582018-04-27 12:15:46 -0700777 break;
778 }
779 case (nlohmann::json::value_t::number_float):
780 {
James Feist8f2710a2018-05-09 17:18:55 -0700781 if (array)
782 {
783 addArrayToDbus<double>(dictPair.key(), dictPair.value(),
James Feistebcc26b2019-03-22 12:30:43 -0700784 iface.get(), permission,
785 systemConfiguration, key);
James Feist8f2710a2018-05-09 17:18:55 -0700786 }
James Feistbb43d022018-06-12 15:44:33 -0700787
James Feist97a63f12018-05-17 13:50:57 -0700788 else
789 {
James Feistbb43d022018-06-12 15:44:33 -0700790 addProperty(dictPair.key(), dictPair.value().get<double>(),
James Feistc6248a52018-08-14 10:09:45 -0700791 iface.get(), systemConfiguration, key,
792 permission);
James Feist97a63f12018-05-17 13:50:57 -0700793 }
James Feist9eb0b582018-04-27 12:15:46 -0700794 break;
795 }
796 case (nlohmann::json::value_t::string):
797 {
James Feist8f2710a2018-05-09 17:18:55 -0700798 if (array)
799 {
James Feistebcc26b2019-03-22 12:30:43 -0700800 addArrayToDbus<std::string>(
801 dictPair.key(), dictPair.value(), iface.get(),
802 permission, systemConfiguration, key);
James Feist97a63f12018-05-17 13:50:57 -0700803 }
804 else
805 {
James Feistc6248a52018-08-14 10:09:45 -0700806 addProperty(
807 dictPair.key(), dictPair.value().get<std::string>(),
808 iface.get(), systemConfiguration, key, permission);
James Feist97a63f12018-05-17 13:50:57 -0700809 }
James Feist9eb0b582018-04-27 12:15:46 -0700810 break;
811 }
James Feist0eb40352019-04-09 14:44:04 -0700812 default:
813 {
James Feista218ddb2019-04-11 14:01:31 -0700814 std::cerr << "Unexpected json type in system configuration "
815 << dictPair.key() << ": "
816 << dictPair.value().type_name() << "\n";
James Feist0eb40352019-04-09 14:44:04 -0700817 break;
818 }
James Feist1b2e2242018-01-30 13:45:19 -0800819 }
820 }
James Feistc6248a52018-08-14 10:09:45 -0700821 if (permission == sdbusplus::asio::PropertyPermission::readWrite)
822 {
823 createDeleteObjectMethod(jsonPointerPath, iface, objServer,
824 systemConfiguration);
825 }
James Feist8f2710a2018-05-09 17:18:55 -0700826 iface->initialize();
James Feist1b2e2242018-01-30 13:45:19 -0800827}
828
James Feista465ccc2019-02-08 12:51:01 -0800829sdbusplus::asio::PropertyPermission getPermission(const std::string& interface)
James Feistc6248a52018-08-14 10:09:45 -0700830{
831 return std::find(settableInterfaces.begin(), settableInterfaces.end(),
832 interface) != settableInterfaces.end()
833 ? sdbusplus::asio::PropertyPermission::readWrite
834 : sdbusplus::asio::PropertyPermission::readOnly;
835}
836
James Feista465ccc2019-02-08 12:51:01 -0800837void createAddObjectMethod(const std::string& jsonPointerPath,
838 const std::string& path,
839 nlohmann::json& systemConfiguration,
James Feistd58879a82019-09-11 11:26:07 -0700840 sdbusplus::asio::object_server& objServer,
841 const std::string& board)
James Feist68500ff2018-08-08 15:40:42 -0700842{
James Feistd58879a82019-09-11 11:26:07 -0700843 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = createInterface(
844 objServer, path, "xyz.openbmc_project.AddObject", board);
James Feist68500ff2018-08-08 15:40:42 -0700845
846 iface->register_method(
847 "AddObject",
848 [&systemConfiguration, &objServer,
James Feistd58879a82019-09-11 11:26:07 -0700849 jsonPointerPath{std::string(jsonPointerPath)}, path{std::string(path)},
850 board](const boost::container::flat_map<std::string, JsonVariantType>&
851 data) {
James Feist68500ff2018-08-08 15:40:42 -0700852 nlohmann::json::json_pointer ptr(jsonPointerPath);
James Feista465ccc2019-02-08 12:51:01 -0800853 nlohmann::json& base = systemConfiguration[ptr];
James Feist68500ff2018-08-08 15:40:42 -0700854 auto findExposes = base.find("Exposes");
855
856 if (findExposes == base.end())
857 {
858 throw std::invalid_argument("Entity must have children.");
859 }
860
861 // this will throw invalid-argument to sdbusplus if invalid json
862 nlohmann::json newData{};
James Feista465ccc2019-02-08 12:51:01 -0800863 for (const auto& item : data)
James Feist68500ff2018-08-08 15:40:42 -0700864 {
James Feista465ccc2019-02-08 12:51:01 -0800865 nlohmann::json& newJson = newData[item.first];
866 std::visit([&newJson](auto&& val) { newJson = std::move(val); },
867 item.second);
James Feist68500ff2018-08-08 15:40:42 -0700868 }
869
870 auto findName = newData.find("Name");
871 auto findType = newData.find("Type");
872 if (findName == newData.end() || findType == newData.end())
873 {
874 throw std::invalid_argument("AddObject missing Name or Type");
875 }
James Feista465ccc2019-02-08 12:51:01 -0800876 const std::string* type = findType->get_ptr<const std::string*>();
877 const std::string* name = findName->get_ptr<const std::string*>();
James Feist68500ff2018-08-08 15:40:42 -0700878 if (type == nullptr || name == nullptr)
879 {
880 throw std::invalid_argument("Type and Name must be a string.");
881 }
882
James Feist02d2b932020-02-06 16:28:48 -0800883 bool foundNull = false;
James Feist68500ff2018-08-08 15:40:42 -0700884 size_t lastIndex = 0;
885 // we add in the "exposes"
James Feist02d2b932020-02-06 16:28:48 -0800886 for (const auto& expose : *findExposes)
James Feist68500ff2018-08-08 15:40:42 -0700887 {
James Feist02d2b932020-02-06 16:28:48 -0800888 if (expose.is_null())
889 {
890 foundNull = true;
891 continue;
892 }
893
894 if (expose["Name"] == *name && expose["Type"] == *type)
James Feist68500ff2018-08-08 15:40:42 -0700895 {
896 throw std::invalid_argument(
897 "Field already in JSON, not adding");
898 }
James Feist02d2b932020-02-06 16:28:48 -0800899
900 if (foundNull)
901 {
902 continue;
903 }
904
James Feist68500ff2018-08-08 15:40:42 -0700905 lastIndex++;
906 }
907
908 std::ifstream schemaFile(std::string(schemaDirectory) + "/" +
909 *type + ".json");
910 // todo(james) we might want to also make a list of 'can add'
911 // interfaces but for now I think the assumption if there is a
912 // schema avaliable that it is allowed to update is fine
913 if (!schemaFile.good())
914 {
915 throw std::invalid_argument(
916 "No schema avaliable, cannot validate.");
917 }
918 nlohmann::json schema =
919 nlohmann::json::parse(schemaFile, nullptr, false);
920 if (schema.is_discarded())
921 {
922 std::cerr << "Schema not legal" << *type << ".json\n";
923 throw DBusInternalError();
924 }
925 if (!validateJson(schema, newData))
926 {
927 throw std::invalid_argument("Data does not match schema");
928 }
James Feist02d2b932020-02-06 16:28:48 -0800929 if (foundNull)
930 {
931 findExposes->at(lastIndex) = newData;
932 }
933 else
934 {
935 findExposes->push_back(newData);
936 }
James Feist68500ff2018-08-08 15:40:42 -0700937 if (!writeJsonFiles(systemConfiguration))
938 {
939 std::cerr << "Error writing json files\n";
940 throw DBusInternalError();
941 }
942 std::string dbusName = *name;
943
944 std::regex_replace(dbusName.begin(), dbusName.begin(),
Johnathan Mantey2015f752019-03-26 15:22:31 -0700945 dbusName.end(), ILLEGAL_DBUS_MEMBER_REGEX, "_");
James Feistd58879a82019-09-11 11:26:07 -0700946
947 std::shared_ptr<sdbusplus::asio::dbus_interface> interface =
948 createInterface(objServer, path + "/" + dbusName,
949 "xyz.openbmc_project.Configuration." + *type,
James Feist02d2b932020-02-06 16:28:48 -0800950 board, true);
James Feist68500ff2018-08-08 15:40:42 -0700951 // permission is read-write, as since we just created it, must be
952 // runtime modifiable
953 populateInterfaceFromJson(
954 systemConfiguration,
James Feist16a02f22019-05-13 15:21:37 -0700955 jsonPointerPath + "/Exposes/" + std::to_string(lastIndex),
James Feist98132792019-07-09 13:29:09 -0700956 interface, newData, objServer,
James Feist68500ff2018-08-08 15:40:42 -0700957 sdbusplus::asio::PropertyPermission::readWrite);
James Feist68500ff2018-08-08 15:40:42 -0700958 });
959 iface->initialize();
960}
961
James Feista465ccc2019-02-08 12:51:01 -0800962void postToDbus(const nlohmann::json& newConfiguration,
963 nlohmann::json& systemConfiguration,
964 sdbusplus::asio::object_server& objServer)
James Feist75fdeeb2018-02-20 14:26:16 -0800965
James Feist1b2e2242018-01-30 13:45:19 -0800966{
James Feist97a63f12018-05-17 13:50:57 -0700967 // iterate through boards
James Feista465ccc2019-02-08 12:51:01 -0800968 for (auto& boardPair : newConfiguration.items())
James Feist1b2e2242018-01-30 13:45:19 -0800969 {
James Feistf1b14142019-04-10 15:22:09 -0700970 std::string boardKey = boardPair.value()["Name"];
James Feistd58879a82019-09-11 11:26:07 -0700971 std::string boardKeyOrig = boardPair.value()["Name"];
James Feistf1b14142019-04-10 15:22:09 -0700972 std::string jsonPointerPath = "/" + boardPair.key();
James Feist97a63f12018-05-17 13:50:57 -0700973 // loop through newConfiguration, but use values from system
974 // configuration to be able to modify via dbus later
James Feistf1b14142019-04-10 15:22:09 -0700975 auto boardValues = systemConfiguration[boardPair.key()];
James Feistd63d18a2018-07-19 15:23:45 -0700976 auto findBoardType = boardValues.find("Type");
James Feist1b2e2242018-01-30 13:45:19 -0800977 std::string boardType;
978 if (findBoardType != boardValues.end() &&
979 findBoardType->type() == nlohmann::json::value_t::string)
980 {
981 boardType = findBoardType->get<std::string>();
982 std::regex_replace(boardType.begin(), boardType.begin(),
Johnathan Mantey2015f752019-03-26 15:22:31 -0700983 boardType.end(), ILLEGAL_DBUS_MEMBER_REGEX, "_");
James Feist1b2e2242018-01-30 13:45:19 -0800984 }
985 else
986 {
987 std::cerr << "Unable to find type for " << boardKey
988 << " reverting to Chassis.\n";
989 boardType = "Chassis";
990 }
James Feist11be6672018-04-06 14:05:32 -0700991 std::string boardtypeLower = boost::algorithm::to_lower_copy(boardType);
James Feist1b2e2242018-01-30 13:45:19 -0800992
993 std::regex_replace(boardKey.begin(), boardKey.begin(), boardKey.end(),
Johnathan Mantey2015f752019-03-26 15:22:31 -0700994 ILLEGAL_DBUS_MEMBER_REGEX, "_");
James Feist11be6672018-04-06 14:05:32 -0700995 std::string boardName = "/xyz/openbmc_project/inventory/system/" +
996 boardtypeLower + "/" + boardKey;
James Feist1b2e2242018-01-30 13:45:19 -0800997
James Feistd58879a82019-09-11 11:26:07 -0700998 std::shared_ptr<sdbusplus::asio::dbus_interface> inventoryIface =
999 createInterface(objServer, boardName,
1000 "xyz.openbmc_project.Inventory.Item", boardKey);
James Feist68500ff2018-08-08 15:40:42 -07001001
James Feistd58879a82019-09-11 11:26:07 -07001002 std::shared_ptr<sdbusplus::asio::dbus_interface> boardIface =
1003 createInterface(objServer, boardName,
1004 "xyz.openbmc_project.Inventory.Item." + boardType,
1005 boardKeyOrig);
James Feist11be6672018-04-06 14:05:32 -07001006
James Feist68500ff2018-08-08 15:40:42 -07001007 createAddObjectMethod(jsonPointerPath, boardName, systemConfiguration,
James Feistd58879a82019-09-11 11:26:07 -07001008 objServer, boardKeyOrig);
James Feist68500ff2018-08-08 15:40:42 -07001009
James Feist97a63f12018-05-17 13:50:57 -07001010 populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
James Feistc6248a52018-08-14 10:09:45 -07001011 boardIface, boardValues, objServer);
James Feist97a63f12018-05-17 13:50:57 -07001012 jsonPointerPath += "/";
1013 // iterate through board properties
James Feista465ccc2019-02-08 12:51:01 -08001014 for (auto& boardField : boardValues.items())
James Feist11be6672018-04-06 14:05:32 -07001015 {
1016 if (boardField.value().type() == nlohmann::json::value_t::object)
1017 {
James Feistd58879a82019-09-11 11:26:07 -07001018 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
1019 createInterface(objServer, boardName, boardField.key(),
1020 boardKeyOrig);
1021
James Feistc6248a52018-08-14 10:09:45 -07001022 populateInterfaceFromJson(systemConfiguration,
1023 jsonPointerPath + boardField.key(),
1024 iface, boardField.value(), objServer);
James Feist11be6672018-04-06 14:05:32 -07001025 }
1026 }
James Feist97a63f12018-05-17 13:50:57 -07001027
James Feist1e3e6982018-08-03 16:09:28 -07001028 auto exposes = boardValues.find("Exposes");
James Feist1b2e2242018-01-30 13:45:19 -08001029 if (exposes == boardValues.end())
1030 {
1031 continue;
1032 }
James Feist97a63f12018-05-17 13:50:57 -07001033 // iterate through exposes
James Feist1e3e6982018-08-03 16:09:28 -07001034 jsonPointerPath += "Exposes/";
James Feist97a63f12018-05-17 13:50:57 -07001035
1036 // store the board level pointer so we can modify it on the way down
1037 std::string jsonPointerPathBoard = jsonPointerPath;
1038 size_t exposesIndex = -1;
James Feista465ccc2019-02-08 12:51:01 -08001039 for (auto& item : *exposes)
James Feist1b2e2242018-01-30 13:45:19 -08001040 {
James Feist97a63f12018-05-17 13:50:57 -07001041 exposesIndex++;
1042 jsonPointerPath = jsonPointerPathBoard;
1043 jsonPointerPath += std::to_string(exposesIndex);
1044
James Feistd63d18a2018-07-19 15:23:45 -07001045 auto findName = item.find("Name");
James Feist1b2e2242018-01-30 13:45:19 -08001046 if (findName == item.end())
1047 {
1048 std::cerr << "cannot find name in field " << item << "\n";
1049 continue;
1050 }
James Feist1e3e6982018-08-03 16:09:28 -07001051 auto findStatus = item.find("Status");
James Feist1b2e2242018-01-30 13:45:19 -08001052 // if status is not found it is assumed to be status = 'okay'
1053 if (findStatus != item.end())
1054 {
1055 if (*findStatus == "disabled")
1056 {
1057 continue;
1058 }
1059 }
James Feistd63d18a2018-07-19 15:23:45 -07001060 auto findType = item.find("Type");
James Feist1b2e2242018-01-30 13:45:19 -08001061 std::string itemType;
1062 if (findType != item.end())
1063 {
1064 itemType = findType->get<std::string>();
1065 std::regex_replace(itemType.begin(), itemType.begin(),
Johnathan Mantey2015f752019-03-26 15:22:31 -07001066 itemType.end(), ILLEGAL_DBUS_PATH_REGEX,
1067 "_");
James Feist1b2e2242018-01-30 13:45:19 -08001068 }
1069 else
1070 {
1071 itemType = "unknown";
1072 }
1073 std::string itemName = findName->get<std::string>();
1074 std::regex_replace(itemName.begin(), itemName.begin(),
Johnathan Mantey2015f752019-03-26 15:22:31 -07001075 itemName.end(), ILLEGAL_DBUS_MEMBER_REGEX, "_");
James Feistc6248a52018-08-14 10:09:45 -07001076
James Feistd58879a82019-09-11 11:26:07 -07001077 std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface =
1078 createInterface(objServer, boardName + "/" + itemName,
1079 "xyz.openbmc_project.Configuration." + itemType,
1080 boardKeyOrig);
James Feist1b2e2242018-01-30 13:45:19 -08001081
James Feist97a63f12018-05-17 13:50:57 -07001082 populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
James Feistc6248a52018-08-14 10:09:45 -07001083 itemIface, item, objServer,
1084 getPermission(itemType));
James Feist1b2e2242018-01-30 13:45:19 -08001085
James Feista465ccc2019-02-08 12:51:01 -08001086 for (auto& objectPair : item.items())
James Feist1b2e2242018-01-30 13:45:19 -08001087 {
James Feist97a63f12018-05-17 13:50:57 -07001088 jsonPointerPath = jsonPointerPathBoard +
1089 std::to_string(exposesIndex) + "/" +
1090 objectPair.key();
James Feist1b2e2242018-01-30 13:45:19 -08001091 if (objectPair.value().type() ==
1092 nlohmann::json::value_t::object)
1093 {
James Feistd58879a82019-09-11 11:26:07 -07001094 std::shared_ptr<sdbusplus::asio::dbus_interface>
1095 objectIface = createInterface(
1096 objServer, boardName + "/" + itemName,
1097 "xyz.openbmc_project.Configuration." + itemType +
1098 "." + objectPair.key(),
1099 boardKeyOrig);
James Feist97a63f12018-05-17 13:50:57 -07001100
Adrian Ambrożewiczc789fca2020-05-14 15:50:05 +02001101 populateInterfaceFromJson(systemConfiguration,
1102 jsonPointerPath, objectIface,
1103 objectPair.value(), objServer,
1104 getPermission(objectPair.key()));
James Feist1b2e2242018-01-30 13:45:19 -08001105 }
1106 else if (objectPair.value().type() ==
1107 nlohmann::json::value_t::array)
1108 {
1109 size_t index = 0;
James Feist8f2710a2018-05-09 17:18:55 -07001110 if (!objectPair.value().size())
James Feist1b2e2242018-01-30 13:45:19 -08001111 {
James Feist8f2710a2018-05-09 17:18:55 -07001112 continue;
1113 }
1114 bool isLegal = true;
1115 auto type = objectPair.value()[0].type();
1116 if (type != nlohmann::json::value_t::object)
1117 {
1118 continue;
1119 }
1120
1121 // verify legal json
James Feista465ccc2019-02-08 12:51:01 -08001122 for (const auto& arrayItem : objectPair.value())
James Feist8f2710a2018-05-09 17:18:55 -07001123 {
1124 if (arrayItem.type() != type)
James Feist1b2e2242018-01-30 13:45:19 -08001125 {
James Feist8f2710a2018-05-09 17:18:55 -07001126 isLegal = false;
James Feist1b2e2242018-01-30 13:45:19 -08001127 break;
1128 }
James Feist8f2710a2018-05-09 17:18:55 -07001129 }
1130 if (!isLegal)
1131 {
1132 std::cerr << "dbus format error" << objectPair.value()
1133 << "\n";
1134 break;
1135 }
1136
James Feista465ccc2019-02-08 12:51:01 -08001137 for (auto& arrayItem : objectPair.value())
James Feist8f2710a2018-05-09 17:18:55 -07001138 {
James Feist97a63f12018-05-17 13:50:57 -07001139
James Feistd58879a82019-09-11 11:26:07 -07001140 std::shared_ptr<sdbusplus::asio::dbus_interface>
1141 objectIface = createInterface(
1142 objServer, boardName + "/" + itemName,
1143 "xyz.openbmc_project.Configuration." +
1144 itemType + "." + objectPair.key() +
1145 std::to_string(index),
1146 boardKeyOrig);
1147
James Feistc6248a52018-08-14 10:09:45 -07001148 populateInterfaceFromJson(
1149 systemConfiguration,
1150 jsonPointerPath + "/" + std::to_string(index),
1151 objectIface, arrayItem, objServer,
1152 getPermission(objectPair.key()));
James Feistbb43d022018-06-12 15:44:33 -07001153 index++;
James Feist1b2e2242018-01-30 13:45:19 -08001154 }
1155 }
1156 }
1157 }
1158 }
1159}
1160
James Feist8f2710a2018-05-09 17:18:55 -07001161// reads json files out of the filesystem
James Feista465ccc2019-02-08 12:51:01 -08001162bool findJsonFiles(std::list<nlohmann::json>& configurations)
James Feist3cb5fec2018-01-23 14:41:51 -08001163{
1164 // find configuration files
Ed Tanous072e25d2018-12-16 21:45:20 -08001165 std::vector<std::filesystem::path> jsonPaths;
Johnathan Mantey2015f752019-03-26 15:22:31 -07001166 if (!findFiles(std::filesystem::path(configurationDirectory), R"(.*\.json)",
1167 jsonPaths))
James Feist3cb5fec2018-01-23 14:41:51 -08001168 {
1169 std::cerr << "Unable to find any configuration files in "
James Feistb4383f42018-08-06 16:54:10 -07001170 << configurationDirectory << "\n";
James Feist75fdeeb2018-02-20 14:26:16 -08001171 return false;
James Feist3cb5fec2018-01-23 14:41:51 -08001172 }
James Feistb4383f42018-08-06 16:54:10 -07001173
1174 std::ifstream schemaStream(std::string(schemaDirectory) + "/" +
1175 globalSchema);
1176 if (!schemaStream.good())
1177 {
1178 std::cerr
1179 << "Cannot open schema file, cannot validate JSON, exiting\n\n";
1180 std::exit(EXIT_FAILURE);
Ed Tanous072e25d2018-12-16 21:45:20 -08001181 return false;
James Feistb4383f42018-08-06 16:54:10 -07001182 }
1183 nlohmann::json schema = nlohmann::json::parse(schemaStream, nullptr, false);
1184 if (schema.is_discarded())
1185 {
1186 std::cerr
1187 << "Illegal schema file detected, cannot validate JSON, exiting\n";
1188 std::exit(EXIT_FAILURE);
Ed Tanous072e25d2018-12-16 21:45:20 -08001189 return false;
James Feistb4383f42018-08-06 16:54:10 -07001190 }
1191
James Feista465ccc2019-02-08 12:51:01 -08001192 for (auto& jsonPath : jsonPaths)
James Feist3cb5fec2018-01-23 14:41:51 -08001193 {
1194 std::ifstream jsonStream(jsonPath.c_str());
1195 if (!jsonStream.good())
1196 {
1197 std::cerr << "unable to open " << jsonPath.string() << "\n";
1198 continue;
1199 }
1200 auto data = nlohmann::json::parse(jsonStream, nullptr, false);
1201 if (data.is_discarded())
1202 {
1203 std::cerr << "syntax error in " << jsonPath.string() << "\n";
1204 continue;
1205 }
James Feist8da99192019-01-24 08:20:16 -08001206 /*
1207 * todo(james): reenable this once less things are in flight
1208 *
James Feistb4383f42018-08-06 16:54:10 -07001209 if (!validateJson(schema, data))
1210 {
1211 std::cerr << "Error validating " << jsonPath.string() << "\n";
1212 continue;
1213 }
James Feist8da99192019-01-24 08:20:16 -08001214 */
James Feistb4383f42018-08-06 16:54:10 -07001215
James Feist3cb5fec2018-01-23 14:41:51 -08001216 if (data.type() == nlohmann::json::value_t::array)
1217 {
James Feista465ccc2019-02-08 12:51:01 -08001218 for (auto& d : data)
James Feist3cb5fec2018-01-23 14:41:51 -08001219 {
1220 configurations.emplace_back(d);
1221 }
1222 }
1223 else
1224 {
1225 configurations.emplace_back(data);
1226 }
1227 }
Ed Tanous072e25d2018-12-16 21:45:20 -08001228 return true;
James Feist75fdeeb2018-02-20 14:26:16 -08001229}
James Feist3cb5fec2018-01-23 14:41:51 -08001230
James Feist94219d12020-03-03 11:52:25 -08001231std::string getRecordName(
1232 const boost::container::flat_map<std::string, BasicVariantType>& probe,
1233 const std::string& probeName)
1234{
1235 if (probe.empty())
1236 {
1237 return probeName;
1238 }
1239
1240 // use an array so alphabetical order from the
1241 // flat_map is maintained
1242 auto device = nlohmann::json::array();
1243 for (auto& devPair : probe)
1244 {
1245 device.push_back(devPair.first);
1246 std::visit([&device](auto&& v) { device.push_back(v); },
1247 devPair.second);
1248 }
1249 size_t hash = std::hash<std::string>{}(probeName + device.dump());
1250 // hashes are hard to distinguish, use the
1251 // non-hashed version if we want debug
1252 if constexpr (DEBUG)
1253 {
1254 return probeName + device.dump();
1255 }
1256 else
1257 {
1258 return std::to_string(hash);
1259 }
1260}
1261
James Feist4dc617b2020-05-01 09:54:47 -07001262PerformScan::PerformScan(nlohmann::json& systemConfiguration,
1263 nlohmann::json& missingConfigurations,
1264 std::list<nlohmann::json>& configurations,
1265 sdbusplus::asio::object_server& objServerIn,
1266 std::function<void()>&& callback) :
James Feist733f7652019-11-13 14:32:29 -08001267 _systemConfiguration(systemConfiguration),
1268 _missingConfigurations(missingConfigurations),
James Feist4dc617b2020-05-01 09:54:47 -07001269 _configurations(configurations), objServer(objServerIn),
1270 _callback(std::move(callback))
James Feist8c505da2020-05-28 10:06:33 -07001271{}
James Feist733f7652019-11-13 14:32:29 -08001272void PerformScan::run()
1273{
1274 boost::container::flat_set<std::string> dbusProbeInterfaces;
1275 std::vector<std::shared_ptr<PerformProbe>> dbusProbePointers;
James Feist75fdeeb2018-02-20 14:26:16 -08001276
James Feist733f7652019-11-13 14:32:29 -08001277 for (auto it = _configurations.begin(); it != _configurations.end();)
James Feist3cb5fec2018-01-23 14:41:51 -08001278 {
James Feist733f7652019-11-13 14:32:29 -08001279 auto findProbe = it->find("Probe");
1280 auto findName = it->find("Name");
James Feist787c3c32019-11-07 14:42:58 -08001281
James Feist733f7652019-11-13 14:32:29 -08001282 nlohmann::json probeCommand;
1283 // check for poorly formatted fields, probe must be an array
1284 if (findProbe == it->end())
James Feist3cb5fec2018-01-23 14:41:51 -08001285 {
James Feist733f7652019-11-13 14:32:29 -08001286 std::cerr << "configuration file missing probe:\n " << *it << "\n";
1287 it = _configurations.erase(it);
1288 continue;
1289 }
1290 else if ((*findProbe).type() != nlohmann::json::value_t::array)
1291 {
1292 probeCommand = nlohmann::json::array();
1293 probeCommand.push_back(*findProbe);
1294 }
1295 else
1296 {
1297 probeCommand = *findProbe;
1298 }
James Feist3cb5fec2018-01-23 14:41:51 -08001299
James Feist733f7652019-11-13 14:32:29 -08001300 if (findName == it->end())
1301 {
1302 std::cerr << "configuration file missing name:\n " << *it << "\n";
1303 it = _configurations.erase(it);
1304 continue;
1305 }
1306 std::string probeName = *findName;
James Feist1b2e2242018-01-30 13:45:19 -08001307
James Feist733f7652019-11-13 14:32:29 -08001308 if (std::find(passedProbes.begin(), passedProbes.end(), probeName) !=
1309 passedProbes.end())
1310 {
1311 it = _configurations.erase(it);
1312 continue;
1313 }
1314 nlohmann::json* recordPtr = &(*it);
James Feist1b2e2242018-01-30 13:45:19 -08001315
James Feist733f7652019-11-13 14:32:29 -08001316 // store reference to this to children to makes sure we don't get
1317 // destroyed too early
1318 auto thisRef = shared_from_this();
1319 auto probePointer = std::make_shared<PerformProbe>(
1320 probeCommand, thisRef,
1321 [&, recordPtr, probeName](FoundDeviceT& foundDevices) {
James Feist08a5b172019-08-28 14:47:47 -07001322 _passed = true;
James Feist3cb5fec2018-01-23 14:41:51 -08001323
James Feist35f5e0e2020-03-16 14:02:27 -07001324 std::set<nlohmann::json> usedNames;
James Feist733f7652019-11-13 14:32:29 -08001325 passedProbes.push_back(probeName);
James Feist94219d12020-03-03 11:52:25 -08001326 std::list<size_t> indexes(foundDevices.size());
1327 std::iota(indexes.begin(), indexes.end(), 1);
James Feist8f2710a2018-05-09 17:18:55 -07001328
James Feist35f5e0e2020-03-16 14:02:27 -07001329 size_t indexIdx = probeName.find("$");
1330 bool hasTemplateName = (indexIdx != std::string::npos);
James Feist94219d12020-03-03 11:52:25 -08001331
1332 // copy over persisted configurations and make sure we remove
1333 // indexes that are already used
1334 for (auto itr = foundDevices.begin();
1335 itr != foundDevices.end();)
James Feist08a5b172019-08-28 14:47:47 -07001336 {
James Feist94219d12020-03-03 11:52:25 -08001337 std::string recordName = getRecordName(*itr, probeName);
James Feistf1b14142019-04-10 15:22:09 -07001338
James Feist08a5b172019-08-28 14:47:47 -07001339 auto fromLastJson = lastJson.find(recordName);
1340 if (fromLastJson != lastJson.end())
1341 {
James Feist02d2b932020-02-06 16:28:48 -08001342 auto findExposes = fromLastJson->find("Exposes");
1343 // delete nulls from any updates
1344 if (findExposes != fromLastJson->end())
1345 {
1346 auto copy = nlohmann::json::array();
1347 for (auto& expose : *findExposes)
1348 {
1349 if (expose.is_null())
1350 {
1351 continue;
1352 }
1353 copy.emplace_back(expose);
1354 }
1355 *findExposes = copy;
1356 }
1357
James Feist08a5b172019-08-28 14:47:47 -07001358 // keep user changes
1359 _systemConfiguration[recordName] = *fromLastJson;
James Feist899e17f2019-09-13 11:46:29 -07001360 _missingConfigurations.erase(recordName);
James Feist94219d12020-03-03 11:52:25 -08001361 itr = foundDevices.erase(itr);
James Feist35f5e0e2020-03-16 14:02:27 -07001362 if (hasTemplateName)
James Feist94219d12020-03-03 11:52:25 -08001363 {
1364 auto nameIt = fromLastJson->find("Name");
1365 if (nameIt == fromLastJson->end())
1366 {
1367 std::cerr << "Last JSON Illegal\n";
1368 continue;
1369 }
1370
1371 int index = std::stoi(
1372 nameIt->get<std::string>().substr(indexIdx),
1373 nullptr, 0);
James Feist35f5e0e2020-03-16 14:02:27 -07001374 usedNames.insert(nameIt.value());
James Feist94219d12020-03-03 11:52:25 -08001375 auto usedIt = std::find(indexes.begin(),
1376 indexes.end(), index);
1377
1378 if (usedIt == indexes.end())
1379 {
1380 continue; // less items now
1381 }
1382 indexes.erase(usedIt);
1383 }
1384
James Feist08a5b172019-08-28 14:47:47 -07001385 continue;
1386 }
James Feist94219d12020-03-03 11:52:25 -08001387 itr++;
1388 }
James Feist35f5e0e2020-03-16 14:02:27 -07001389
1390 std::optional<std::string> replaceStr;
1391
James Feist94219d12020-03-03 11:52:25 -08001392 for (auto& foundDevice : foundDevices)
1393 {
1394 nlohmann::json record = *recordPtr;
1395 std::string recordName =
1396 getRecordName(foundDevice, probeName);
1397 size_t foundDeviceIdx = indexes.front();
1398 indexes.pop_front();
James Feistf1b14142019-04-10 15:22:09 -07001399
James Feist35f5e0e2020-03-16 14:02:27 -07001400 // check name first so we have no duplicate names
1401 auto getName = record.find("Name");
1402 if (getName == record.end())
1403 {
1404 std::cerr << "Record Missing Name! " << record.dump();
1405 continue; // this should be impossible at this level
1406 }
1407
1408 nlohmann::json copyForName = {{"Name", getName.value()}};
1409 nlohmann::json::iterator copyIt = copyForName.begin();
1410 std::optional<std::string> replaceVal = templateCharReplace(
1411 copyIt, foundDevice, foundDeviceIdx, replaceStr);
1412
1413 if (!replaceStr && replaceVal)
1414 {
1415 if (usedNames.find(copyIt.value()) != usedNames.end())
1416 {
1417 replaceStr = replaceVal;
1418 copyForName = {{"Name", getName.value()}};
1419 copyIt = copyForName.begin();
1420 templateCharReplace(copyIt, foundDevice,
1421 foundDeviceIdx, replaceStr);
1422 }
1423 }
1424
1425 if (replaceStr)
1426 {
1427 std::cerr << "Duplicates found, replacing "
1428 << *replaceStr
1429 << " with found device index.\n Consider "
1430 "fixing template to not have duplicates\n";
1431 }
James Feist08a5b172019-08-28 14:47:47 -07001432
1433 for (auto keyPair = record.begin(); keyPair != record.end();
1434 keyPair++)
1435 {
James Feist35f5e0e2020-03-16 14:02:27 -07001436 if (keyPair.key() == "Name")
1437 {
1438 keyPair.value() = copyIt.value();
1439 usedNames.insert(copyIt.value());
1440
1441 continue; // already covered above
1442 }
James Feist08a5b172019-08-28 14:47:47 -07001443 templateCharReplace(keyPair, foundDevice,
James Feist35f5e0e2020-03-16 14:02:27 -07001444 foundDeviceIdx, replaceStr);
James Feist08a5b172019-08-28 14:47:47 -07001445 }
1446
James Feist35f5e0e2020-03-16 14:02:27 -07001447 // insert into configuration temporarily to be able to
1448 // reference ourselves
1449
1450 _systemConfiguration[recordName] = record;
1451
James Feist08a5b172019-08-28 14:47:47 -07001452 auto findExpose = record.find("Exposes");
1453 if (findExpose == record.end())
1454 {
James Feistf1b14142019-04-10 15:22:09 -07001455 _systemConfiguration[recordName] = record;
James Feist08a5b172019-08-28 14:47:47 -07001456 continue;
1457 }
James Feistf1b14142019-04-10 15:22:09 -07001458
James Feist08a5b172019-08-28 14:47:47 -07001459 for (auto& expose : *findExpose)
1460 {
1461 for (auto keyPair = expose.begin();
1462 keyPair != expose.end(); keyPair++)
James Feistf1b14142019-04-10 15:22:09 -07001463 {
James Feist08a5b172019-08-28 14:47:47 -07001464
1465 templateCharReplace(keyPair, foundDevice,
James Feist35f5e0e2020-03-16 14:02:27 -07001466 foundDeviceIdx, replaceStr);
James Feist08a5b172019-08-28 14:47:47 -07001467
James Feist668bbb12020-02-05 14:27:26 -08001468 bool isBind =
1469 boost::starts_with(keyPair.key(), "Bind");
1470 bool isDisable = keyPair.key() == "DisableNode";
1471
1472 // special cases
1473 if (!(isBind || isDisable))
James Feistf1b14142019-04-10 15:22:09 -07001474 {
James Feist668bbb12020-02-05 14:27:26 -08001475 continue;
1476 }
1477
1478 if (keyPair.value().type() !=
1479 nlohmann::json::value_t::string &&
1480 keyPair.value().type() !=
1481 nlohmann::json::value_t::array)
1482 {
1483 std::cerr << "Value is invalid type "
1484 << keyPair.key() << "\n";
1485 continue;
1486 }
1487
1488 std::vector<std::string> matches;
1489 if (keyPair.value().type() ==
1490 nlohmann::json::value_t::string)
1491 {
1492 matches.emplace_back(keyPair.value());
1493 }
1494 else
1495 {
1496 for (const auto& value : keyPair.value())
James Feistf1b14142019-04-10 15:22:09 -07001497 {
James Feist668bbb12020-02-05 14:27:26 -08001498 if (value.type() !=
1499 nlohmann::json::value_t::string)
1500 {
1501 std::cerr << "Value is invalid type "
1502 << value << "\n";
1503 break;
1504 }
1505 matches.emplace_back(value);
James Feistf1b14142019-04-10 15:22:09 -07001506 }
James Feist668bbb12020-02-05 14:27:26 -08001507 }
James Feist08a5b172019-08-28 14:47:47 -07001508
James Feist668bbb12020-02-05 14:27:26 -08001509 std::set<std::string> foundMatches;
1510 for (auto& configurationPair :
1511 _systemConfiguration.items())
1512 {
1513 if (isDisable)
James Feist8f2710a2018-05-09 17:18:55 -07001514 {
James Feist668bbb12020-02-05 14:27:26 -08001515 // don't disable ourselves
1516 if (configurationPair.key() == recordName)
James Feist3cb5fec2018-01-23 14:41:51 -08001517 {
James Feist1b2e2242018-01-30 13:45:19 -08001518 continue;
1519 }
James Feist668bbb12020-02-05 14:27:26 -08001520 }
1521 auto configListFind =
1522 configurationPair.value().find("Exposes");
James Feist8f2710a2018-05-09 17:18:55 -07001523
James Feist668bbb12020-02-05 14:27:26 -08001524 if (configListFind ==
1525 configurationPair.value().end() ||
1526 configListFind->type() !=
1527 nlohmann::json::value_t::array)
James Feist08a5b172019-08-28 14:47:47 -07001528 {
James Feist668bbb12020-02-05 14:27:26 -08001529 continue;
James Feist08a5b172019-08-28 14:47:47 -07001530 }
James Feist668bbb12020-02-05 14:27:26 -08001531 for (auto& exposedObject : *configListFind)
1532 {
1533 auto matchIt = std::find_if(
1534 matches.begin(), matches.end(),
1535 [name = (exposedObject)["Name"]
1536 .get<std::string>()](
1537 const std::string& s) {
1538 return s == name;
1539 });
1540 if (matchIt == matches.end())
1541 {
1542 continue;
1543 }
1544 foundMatches.insert(*matchIt);
1545
1546 if (isBind)
1547 {
1548 std::string bind = keyPair.key().substr(
1549 sizeof("Bind") - 1);
1550
1551 exposedObject["Status"] = "okay";
1552 expose[bind] = exposedObject;
1553 }
1554 else if (isDisable)
1555 {
1556 exposedObject["Status"] = "disabled";
1557 }
1558 }
1559 }
1560 if (foundMatches.size() != matches.size())
1561 {
1562 std::cerr << "configuration file "
1563 "dependency error, "
1564 "could not find "
1565 << keyPair.key() << " "
1566 << keyPair.value() << "\n";
James Feist3cb5fec2018-01-23 14:41:51 -08001567 }
1568 }
1569 }
James Feist08a5b172019-08-28 14:47:47 -07001570 // overwrite ourselves with cleaned up version
1571 _systemConfiguration[recordName] = record;
James Feist899e17f2019-09-13 11:46:29 -07001572 _missingConfigurations.erase(recordName);
James Feist08a5b172019-08-28 14:47:47 -07001573 }
1574 });
James Feist787c3c32019-11-07 14:42:58 -08001575
James Feist733f7652019-11-13 14:32:29 -08001576 // parse out dbus probes by discarding other probe types, store in a
1577 // map
1578 for (const std::string& probe : probeCommand)
1579 {
1580 bool found = false;
1581 boost::container::flat_map<const char*, probe_type_codes,
1582 cmp_str>::const_iterator probeType;
1583 for (probeType = PROBE_TYPES.begin();
1584 probeType != PROBE_TYPES.end(); ++probeType)
James Feist787c3c32019-11-07 14:42:58 -08001585 {
James Feist733f7652019-11-13 14:32:29 -08001586 if (probe.find(probeType->first) != std::string::npos)
James Feist787c3c32019-11-07 14:42:58 -08001587 {
James Feist733f7652019-11-13 14:32:29 -08001588 found = true;
1589 break;
James Feist787c3c32019-11-07 14:42:58 -08001590 }
James Feist787c3c32019-11-07 14:42:58 -08001591 }
James Feist733f7652019-11-13 14:32:29 -08001592 if (found)
1593 {
1594 continue;
1595 }
1596 // syntax requires probe before first open brace
1597 auto findStart = probe.find("(");
1598 std::string interface = probe.substr(0, findStart);
1599 dbusProbeInterfaces.emplace(interface);
1600 dbusProbePointers.emplace_back(probePointer);
James Feist3cb5fec2018-01-23 14:41:51 -08001601 }
James Feist733f7652019-11-13 14:32:29 -08001602 it++;
James Feist3cb5fec2018-01-23 14:41:51 -08001603 }
James Feist75fdeeb2018-02-20 14:26:16 -08001604
James Feist4dc617b2020-05-01 09:54:47 -07001605 for (const std::string& interface : dbusProbeInterfaces)
1606 {
1607 registerCallback(_systemConfiguration, objServer, interface);
1608 }
1609
James Feist733f7652019-11-13 14:32:29 -08001610 // probe vector stores a shared_ptr to each PerformProbe that cares
1611 // about a dbus interface
James Feistb1728ca2020-04-30 15:40:55 -07001612 findDbusObjects(std::move(dbusProbePointers),
1613 std::move(dbusProbeInterfaces), shared_from_this());
1614 if constexpr (DEBUG)
1615 {
1616 std::cerr << __func__ << " " << __LINE__ << "\n";
1617 }
James Feist733f7652019-11-13 14:32:29 -08001618}
1619
1620PerformScan::~PerformScan()
1621{
1622 if (_passed)
James Feist75fdeeb2018-02-20 14:26:16 -08001623 {
James Feist733f7652019-11-13 14:32:29 -08001624 auto nextScan = std::make_shared<PerformScan>(
1625 _systemConfiguration, _missingConfigurations, _configurations,
James Feist4dc617b2020-05-01 09:54:47 -07001626 objServer, std::move(_callback));
James Feist733f7652019-11-13 14:32:29 -08001627 nextScan->passedProbes = std::move(passedProbes);
1628 nextScan->dbusProbeObjects = std::move(dbusProbeObjects);
1629 nextScan->run();
James Feistb1728ca2020-04-30 15:40:55 -07001630
1631 if constexpr (DEBUG)
1632 {
1633 std::cerr << __func__ << " " << __LINE__ << "\n";
1634 }
James Feist8f2710a2018-05-09 17:18:55 -07001635 }
James Feist733f7652019-11-13 14:32:29 -08001636 else
1637 {
James Feist4dc617b2020-05-01 09:54:47 -07001638 _callback();
James Feistb1728ca2020-04-30 15:40:55 -07001639
1640 if constexpr (DEBUG)
1641 {
1642 std::cerr << __func__ << " " << __LINE__ << "\n";
1643 }
James Feist733f7652019-11-13 14:32:29 -08001644 }
1645}
James Feistc95cb142018-02-26 10:41:42 -08001646
James Feistb1728ca2020-04-30 15:40:55 -07001647void startRemovedTimer(boost::asio::steady_timer& timer,
James Feist1df06a42019-04-11 14:23:04 -07001648 nlohmann::json& systemConfiguration)
1649{
1650 static bool scannedPowerOff = false;
1651 static bool scannedPowerOn = false;
1652
James Feistfb00f392019-06-25 14:16:48 -07001653 if (systemConfiguration.empty() || lastJson.empty())
1654 {
1655 return; // not ready yet
1656 }
James Feist1df06a42019-04-11 14:23:04 -07001657 if (scannedPowerOn)
1658 {
1659 return;
1660 }
1661
1662 if (!isPowerOn() && scannedPowerOff)
1663 {
1664 return;
1665 }
1666
James Feistb1728ca2020-04-30 15:40:55 -07001667 timer.expires_after(std::chrono::seconds(10));
James Feist1a996582019-05-14 15:10:06 -07001668 timer.async_wait(
1669 [&systemConfiguration](const boost::system::error_code& ec) {
1670 if (ec == boost::asio::error::operation_aborted)
James Feist1df06a42019-04-11 14:23:04 -07001671 {
James Feist1a996582019-05-14 15:10:06 -07001672 // we were cancelled
1673 return;
1674 }
1675
1676 bool powerOff = !isPowerOn();
1677 for (const auto& item : lastJson.items())
1678 {
1679 if (systemConfiguration.find(item.key()) ==
1680 systemConfiguration.end())
James Feist1df06a42019-04-11 14:23:04 -07001681 {
James Feist1a996582019-05-14 15:10:06 -07001682 bool isDetectedPowerOn = false;
1683 auto powerState = item.value().find("PowerState");
1684 if (powerState != item.value().end())
James Feist1df06a42019-04-11 14:23:04 -07001685 {
James Feist1a996582019-05-14 15:10:06 -07001686 auto ptr = powerState->get_ptr<const std::string*>();
1687 if (ptr)
James Feist1df06a42019-04-11 14:23:04 -07001688 {
James Feist1a996582019-05-14 15:10:06 -07001689 if (*ptr == "On" || *ptr == "BiosPost")
1690 {
1691 isDetectedPowerOn = true;
1692 }
James Feist1df06a42019-04-11 14:23:04 -07001693 }
1694 }
James Feist1a996582019-05-14 15:10:06 -07001695 if (powerOff && isDetectedPowerOn)
1696 {
1697 // power not on yet, don't know if it's there or not
1698 continue;
1699 }
1700 if (!powerOff && scannedPowerOff && isDetectedPowerOn)
1701 {
1702 // already logged it when power was off
1703 continue;
1704 }
James Feist1df06a42019-04-11 14:23:04 -07001705
James Feist1a996582019-05-14 15:10:06 -07001706 logDeviceRemoved(item.value());
1707 }
James Feist1df06a42019-04-11 14:23:04 -07001708 }
James Feist1a996582019-05-14 15:10:06 -07001709 scannedPowerOff = true;
1710 if (!powerOff)
1711 {
1712 scannedPowerOn = true;
1713 }
1714 });
James Feist1df06a42019-04-11 14:23:04 -07001715}
1716
James Feist8f2710a2018-05-09 17:18:55 -07001717// main properties changed entry
James Feist4dc617b2020-05-01 09:54:47 -07001718void propertiesChangedCallback(nlohmann::json& systemConfiguration,
1719 sdbusplus::asio::object_server& objServer)
James Feist8f2710a2018-05-09 17:18:55 -07001720{
James Feist2539ccd2020-05-01 16:15:08 -07001721 static bool inProgress = false;
James Feistb1728ca2020-04-30 15:40:55 -07001722 static boost::asio::steady_timer timer(io);
James Feist899e17f2019-09-13 11:46:29 -07001723 static size_t instance = 0;
1724 instance++;
1725 size_t count = instance;
James Feist1df06a42019-04-11 14:23:04 -07001726
James Feistb1728ca2020-04-30 15:40:55 -07001727 timer.expires_after(std::chrono::seconds(5));
James Feist8f2710a2018-05-09 17:18:55 -07001728
1729 // setup an async wait as we normally get flooded with new requests
James Feist4dc617b2020-05-01 09:54:47 -07001730 timer.async_wait([&systemConfiguration, &objServer,
James Feist899e17f2019-09-13 11:46:29 -07001731 count](const boost::system::error_code& ec) {
James Feist8f2710a2018-05-09 17:18:55 -07001732 if (ec == boost::asio::error::operation_aborted)
1733 {
1734 // we were cancelled
1735 return;
1736 }
1737 else if (ec)
1738 {
1739 std::cerr << "async wait error " << ec << "\n";
1740 return;
1741 }
1742
James Feist2539ccd2020-05-01 16:15:08 -07001743 if (inProgress)
1744 {
1745 propertiesChangedCallback(systemConfiguration, objServer);
1746 return;
1747 }
1748 inProgress = true;
1749
James Feist8f2710a2018-05-09 17:18:55 -07001750 nlohmann::json oldConfiguration = systemConfiguration;
James Feist899e17f2019-09-13 11:46:29 -07001751 auto missingConfigurations = std::make_shared<nlohmann::json>();
1752 *missingConfigurations = systemConfiguration;
1753
James Feist8f2710a2018-05-09 17:18:55 -07001754 std::list<nlohmann::json> configurations;
1755 if (!findJsonFiles(configurations))
1756 {
1757 std::cerr << "cannot find json files\n";
James Feist2539ccd2020-05-01 16:15:08 -07001758 inProgress = false;
James Feist8f2710a2018-05-09 17:18:55 -07001759 return;
1760 }
1761
1762 auto perfScan = std::make_shared<PerformScan>(
James Feist899e17f2019-09-13 11:46:29 -07001763 systemConfiguration, *missingConfigurations, configurations,
James Feist4dc617b2020-05-01 09:54:47 -07001764 objServer,
1765 [&systemConfiguration, &objServer, count, oldConfiguration,
1766 missingConfigurations]() {
James Feist899e17f2019-09-13 11:46:29 -07001767 // this is something that since ac has been applied to the bmc
1768 // we saw, and we no longer see it
1769 bool powerOff = !isPowerOn();
1770 for (const auto& item : missingConfigurations->items())
1771 {
1772 bool isDetectedPowerOn = false;
1773 auto powerState = item.value().find("PowerState");
1774 if (powerState != item.value().end())
1775 {
1776 auto ptr = powerState->get_ptr<const std::string*>();
1777 if (ptr)
1778 {
1779 if (*ptr == "On" || *ptr == "BiosPost")
1780 {
1781 isDetectedPowerOn = true;
1782 }
1783 }
1784 }
1785 if (powerOff && isDetectedPowerOn)
1786 {
1787 // power not on yet, don't know if it's there or not
1788 continue;
1789 }
1790 std::string name = item.value()["Name"].get<std::string>();
James Feist02d2b932020-02-06 16:28:48 -08001791 std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>&
James Feist899e17f2019-09-13 11:46:29 -07001792 ifaces = inventory[name];
1793 for (auto& iface : ifaces)
1794 {
James Feist02d2b932020-02-06 16:28:48 -08001795 auto sharedPtr = iface.lock();
1796 if (!sharedPtr)
1797 {
1798 continue; // was already deleted elsewhere
1799 }
1800 objServer.remove_interface(sharedPtr);
James Feist899e17f2019-09-13 11:46:29 -07001801 }
1802 ifaces.clear();
1803 systemConfiguration.erase(item.key());
1804 logDeviceRemoved(item.value());
1805 }
1806
James Feist8f2710a2018-05-09 17:18:55 -07001807 nlohmann::json newConfiguration = systemConfiguration;
James Feist4131aea2018-03-09 09:47:30 -08001808 for (auto it = newConfiguration.begin();
1809 it != newConfiguration.end();)
1810 {
1811 auto findKey = oldConfiguration.find(it.key());
1812 if (findKey != oldConfiguration.end())
1813 {
1814 it = newConfiguration.erase(it);
1815 }
1816 else
1817 {
1818 it++;
1819 }
1820 }
James Feist899e17f2019-09-13 11:46:29 -07001821 for (const auto& item : newConfiguration.items())
1822 {
1823 logDeviceAdded(item.value());
1824 }
1825
James Feist2539ccd2020-05-01 16:15:08 -07001826 inProgress = false;
1827
James Feist8f2710a2018-05-09 17:18:55 -07001828 io.post([&, newConfiguration]() {
James Feist8f2710a2018-05-09 17:18:55 -07001829 loadOverlays(newConfiguration);
James Feistce4367c2018-10-16 09:19:57 -07001830
James Feistbb43d022018-06-12 15:44:33 -07001831 io.post([&]() {
1832 if (!writeJsonFiles(systemConfiguration))
1833 {
1834 std::cerr << "Error writing json files\n";
1835 }
1836 });
James Feist8f2710a2018-05-09 17:18:55 -07001837 io.post([&, newConfiguration]() {
James Feist97a63f12018-05-17 13:50:57 -07001838 postToDbus(newConfiguration, systemConfiguration,
1839 objServer);
James Feist899e17f2019-09-13 11:46:29 -07001840 if (count != instance)
James Feist1df06a42019-04-11 14:23:04 -07001841 {
James Feist899e17f2019-09-13 11:46:29 -07001842 return;
James Feist1df06a42019-04-11 14:23:04 -07001843 }
James Feist899e17f2019-09-13 11:46:29 -07001844 startRemovedTimer(timer, systemConfiguration);
James Feist8f2710a2018-05-09 17:18:55 -07001845 });
1846 });
1847 });
1848 perfScan->run();
1849 });
James Feist75fdeeb2018-02-20 14:26:16 -08001850}
1851
James Feist4dc617b2020-05-01 09:54:47 -07001852void registerCallback(nlohmann::json& systemConfiguration,
1853 sdbusplus::asio::object_server& objServer,
1854 const std::string& interface)
James Feist75fdeeb2018-02-20 14:26:16 -08001855{
James Feist4dc617b2020-05-01 09:54:47 -07001856 static boost::container::flat_map<std::string, sdbusplus::bus::match::match>
1857 dbusMatches;
James Feist75fdeeb2018-02-20 14:26:16 -08001858
James Feist4dc617b2020-05-01 09:54:47 -07001859 auto find = dbusMatches.find(interface);
1860 if (find != dbusMatches.end())
James Feist75fdeeb2018-02-20 14:26:16 -08001861 {
James Feist4dc617b2020-05-01 09:54:47 -07001862 return;
James Feist75fdeeb2018-02-20 14:26:16 -08001863 }
James Feist4dc617b2020-05-01 09:54:47 -07001864 std::function<void(sdbusplus::message::message & message)> eventHandler =
1865
1866 [&](sdbusplus::message::message&) {
1867 propertiesChangedCallback(systemConfiguration, objServer);
1868 };
1869
1870 sdbusplus::bus::match::match match(
1871 static_cast<sdbusplus::bus::bus&>(*SYSTEM_BUS),
1872 "type='signal',member='PropertiesChanged',arg0='" + interface + "'",
1873 eventHandler);
1874 dbusMatches.emplace(interface, std::move(match));
James Feist75fdeeb2018-02-20 14:26:16 -08001875}
1876
James Feist98132792019-07-09 13:29:09 -07001877int main()
James Feist75fdeeb2018-02-20 14:26:16 -08001878{
1879 // setup connection to dbus
James Feist8f2710a2018-05-09 17:18:55 -07001880 SYSTEM_BUS = std::make_shared<sdbusplus::asio::connection>(io);
James Feist75fdeeb2018-02-20 14:26:16 -08001881 SYSTEM_BUS->request_name("xyz.openbmc_project.EntityManager");
James Feist4131aea2018-03-09 09:47:30 -08001882
James Feist8f2710a2018-05-09 17:18:55 -07001883 sdbusplus::asio::object_server objServer(SYSTEM_BUS);
James Feistfd1264a2018-05-03 12:10:00 -07001884
James Feist8f2710a2018-05-09 17:18:55 -07001885 std::shared_ptr<sdbusplus::asio::dbus_interface> entityIface =
1886 objServer.add_interface("/xyz/openbmc_project/EntityManager",
1887 "xyz.openbmc_project.EntityManager");
James Feistfd1264a2018-05-03 12:10:00 -07001888
James Feist4131aea2018-03-09 09:47:30 -08001889 // to keep reference to the match / filter objects so they don't get
1890 // destroyed
James Feist8f2710a2018-05-09 17:18:55 -07001891
1892 nlohmann::json systemConfiguration = nlohmann::json::object();
1893
James Feist4dc617b2020-05-01 09:54:47 -07001894 io.post(
1895 [&]() { propertiesChangedCallback(systemConfiguration, objServer); });
James Feist4131aea2018-03-09 09:47:30 -08001896
James Feistfd1264a2018-05-03 12:10:00 -07001897 entityIface->register_method("ReScan", [&]() {
James Feist4dc617b2020-05-01 09:54:47 -07001898 propertiesChangedCallback(systemConfiguration, objServer);
James Feist75fdeeb2018-02-20 14:26:16 -08001899 });
James Feist8f2710a2018-05-09 17:18:55 -07001900 entityIface->initialize();
1901
James Feist1df06a42019-04-11 14:23:04 -07001902 if (fwVersionIsSame())
1903 {
1904 if (std::filesystem::is_regular_file(currentConfiguration))
1905 {
1906 // this file could just be deleted, but it's nice for debug
1907 std::filesystem::create_directory(tempConfigDir);
1908 std::filesystem::remove(lastConfiguration);
1909 std::filesystem::copy(currentConfiguration, lastConfiguration);
1910 std::filesystem::remove(currentConfiguration);
1911
1912 std::ifstream jsonStream(lastConfiguration);
1913 if (jsonStream.good())
1914 {
1915 auto data = nlohmann::json::parse(jsonStream, nullptr, false);
1916 if (data.is_discarded())
1917 {
1918 std::cerr << "syntax error in " << lastConfiguration
1919 << "\n";
1920 }
1921 else
1922 {
1923 lastJson = std::move(data);
1924 }
1925 }
1926 else
1927 {
1928 std::cerr << "unable to open " << lastConfiguration << "\n";
1929 }
1930 }
1931 }
1932 else
1933 {
1934 // not an error, just logging at this level to make it in the journal
1935 std::cerr << "Clearing previous configuration\n";
1936 std::filesystem::remove(currentConfiguration);
1937 }
1938
1939 // some boards only show up after power is on, we want to not say they are
1940 // removed until the same state happens
1941 setupPowerMatch(SYSTEM_BUS);
1942
James Feist1b2e2242018-01-30 13:45:19 -08001943 io.run();
James Feist3cb5fec2018-01-23 14:41:51 -08001944
1945 return 0;
James Feist75fdeeb2018-02-20 14:26:16 -08001946}