blob: 37e68a2c9f2a18524821f8f9c4b7b053b846231a [file] [log] [blame]
/*
// Copyright (c) 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
*/
#include <Utils.hpp>
#include <Overlay.hpp>
#include <nlohmann/json.hpp>
#include <fstream>
#include <regex>
#include <iostream>
#include <sdbusplus/asio/connection.hpp>
#include <sdbusplus/asio/object_server.hpp>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/variant/apply_visitor.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/container/flat_map.hpp>
#include <boost/container/flat_set.hpp>
#include <VariantVisitors.hpp>
#include <experimental/filesystem>
constexpr const char *OUTPUT_DIR = "/var/configuration/";
constexpr const char *CONFIGURATION_DIR = "/usr/share/configurations";
constexpr const char *TEMPLATE_CHAR = "$";
constexpr const size_t PROPERTIES_CHANGED_UNTIL_FLUSH_COUNT = 20;
constexpr const int32_t MAX_MAPPER_DEPTH = 0;
constexpr const size_t SLEEP_AFTER_PROPERTIES_CHANGE_SECONDS = 5;
namespace fs = std::experimental::filesystem;
struct cmp_str
{
bool operator()(const char *a, const char *b) const
{
return std::strcmp(a, b) < 0;
}
};
struct PerformProbe;
// underscore T for collison with dbus c api
enum class probe_type_codes
{
FALSE_T,
TRUE_T,
AND,
OR,
FOUND,
MATCH_ONE
};
const static boost::container::flat_map<const char *, probe_type_codes, cmp_str>
PROBE_TYPES{{{"FALSE", probe_type_codes::FALSE_T},
{"TRUE", probe_type_codes::TRUE_T},
{"AND", probe_type_codes::AND},
{"OR", probe_type_codes::OR},
{"FOUND", probe_type_codes::FOUND},
{"MATCH_ONE", probe_type_codes::MATCH_ONE}}};
static constexpr std::array<const char *, 1> SETTABLE_INTERFACES = {
"thresholds"};
using BasicVariantType =
sdbusplus::message::variant<std::string, int64_t, uint64_t, double, int32_t,
uint32_t, int16_t, uint16_t, uint8_t, bool>;
using GetSubTreeType = std::vector<
std::pair<std::string,
std::vector<std::pair<std::string, std::vector<std::string>>>>>;
using ManagedObjectType = boost::container::flat_map<
sdbusplus::message::object_path,
boost::container::flat_map<
std::string,
boost::container::flat_map<std::string, BasicVariantType>>>;
boost::container::flat_map<
std::string,
std::vector<boost::container::flat_map<std::string, BasicVariantType>>>
DBUS_PROBE_OBJECTS;
std::vector<std::string> PASSED_PROBES;
// todo: pass this through nicer
std::shared_ptr<sdbusplus::asio::connection> SYSTEM_BUS;
const std::regex ILLEGAL_DBUS_REGEX("[^A-Za-z0-9_]");
void registerCallbacks(boost::asio::io_service &io,
std::vector<sdbusplus::bus::match::match> &dbusMatches,
nlohmann::json &systemConfiguration,
sdbusplus::asio::object_server &objServer);
// calls the mapper to find all exposed objects of an interface type
// and creates a vector<flat_map> that contains all the key value pairs
// getManagedObjects
void findDbusObjects(std::shared_ptr<PerformProbe> probe,
std::shared_ptr<sdbusplus::asio::connection> connection,
std::string &interface)
{
// todo: this is only static because the mapper is unreliable as of today
static boost::container::flat_map<std::string,
boost::container::flat_set<std::string>>
connections;
// store reference to pending callbacks so we don't overwhelm services
static boost::container::flat_map<
std::string, std::vector<std::shared_ptr<PerformProbe>>>
pendingProbes;
if (DBUS_PROBE_OBJECTS[interface].size())
{
return;
}
// add shared_ptr to vector of Probes waiting for callback from a specific
// interface to keep alive while waiting for response
std::array<const char *, 1> objects = {interface.c_str()};
std::vector<std::shared_ptr<PerformProbe>> &pending =
pendingProbes[interface];
auto iter = pending.emplace(pending.end(), probe);
// only allow first call to run to not overwhelm processes
if (iter != pending.begin())
{
return;
}
// find all connections in the mapper that expose a specific type
connection->async_method_call(
[&, connection, interface](boost::system::error_code &ec,
const GetSubTreeType &interfaceSubtree) {
auto &interfaceConnections = connections[interface];
if (ec)
{
std::cerr
<< "Error communicating to mapper, using cached data if "
"available\n";
if (interfaceConnections.empty())
{
return;
}
}
else
{
for (auto &object : interfaceSubtree)
{
for (auto &connPair : object.second)
{
auto insertPair =
interfaceConnections.insert(connPair.first);
}
}
}
// get managed objects for all interfaces
for (const auto &conn : interfaceConnections)
{
connection->async_method_call(
[&, conn,
interface](boost::system::error_code &ec,
const ManagedObjectType &managedInterface) {
if (ec)
{
std::cerr
<< "error getting managed object for device "
<< conn << "\n";
pendingProbes[interface].clear();
return;
}
for (auto &interfaceManagedObj : managedInterface)
{
auto ifaceObjFind =
interfaceManagedObj.second.find(interface);
if (ifaceObjFind !=
interfaceManagedObj.second.end())
{
std::vector<boost::container::flat_map<
std::string, BasicVariantType>>
&dbusObject = DBUS_PROBE_OBJECTS[interface];
dbusObject.emplace_back(ifaceObjFind->second);
}
}
pendingProbes[interface].clear();
},
conn.c_str(), "/", "org.freedesktop.DBus.ObjectManager",
"GetManagedObjects");
}
},
"xyz.openbmc_project.ObjectMapper",
"/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper", "GetSubTree", "", MAX_MAPPER_DEPTH,
objects);
}
// probes dbus interface dictionary for a key with a value that matches a regex
bool probeDbus(
const std::string &interface,
const std::map<std::string, nlohmann::json> &matches,
std::vector<boost::container::flat_map<std::string, BasicVariantType>>
&devices,
bool &foundProbe)
{
std::vector<boost::container::flat_map<std::string, BasicVariantType>>
&dbusObject = DBUS_PROBE_OBJECTS[interface];
if (dbusObject.empty())
{
foundProbe = false;
return false;
}
foundProbe = true;
bool foundMatch = false;
for (auto &device : dbusObject)
{
bool deviceMatches = true;
for (auto &match : matches)
{
auto deviceValue = device.find(match.first);
if (deviceValue != device.end())
{
switch (match.second.type())
{
case nlohmann::json::value_t::string:
{
std::regex search(match.second.get<std::string>());
std::smatch match;
// convert value to string respresentation
std::string probeValue = mapbox::util::apply_visitor(
VariantToStringVisitor(), deviceValue->second);
if (!std::regex_search(probeValue, match, search))
{
deviceMatches = false;
break;
}
break;
}
case nlohmann::json::value_t::boolean:
case nlohmann::json::value_t::number_unsigned:
{
unsigned int probeValue = mapbox::util::apply_visitor(
VariantToUnsignedIntVisitor(), deviceValue->second);
if (probeValue != match.second.get<unsigned int>())
{
deviceMatches = false;
}
break;
}
case nlohmann::json::value_t::number_integer:
{
int probeValue = mapbox::util::apply_visitor(
VariantToIntVisitor(), deviceValue->second);
if (probeValue != match.second.get<int>())
{
deviceMatches = false;
}
break;
}
case nlohmann::json::value_t::number_float:
{
float probeValue = mapbox::util::apply_visitor(
VariantToFloatVisitor(), deviceValue->second);
if (probeValue != match.second.get<float>())
{
deviceMatches = false;
}
break;
}
}
}
else
{
deviceMatches = false;
break;
}
}
if (deviceMatches)
{
devices.emplace_back(
boost::container::flat_map<std::string, BasicVariantType>(
device));
foundMatch = true;
deviceMatches = false; // for next iteration
}
}
return foundMatch;
}
// default probe entry point, iterates a list looking for specific types to
// call specific probe functions
bool probe(
const std::vector<std::string> &probeCommand,
std::vector<boost::container::flat_map<std::string, BasicVariantType>>
&foundDevs)
{
const static std::regex command(R"(\((.*)\))");
std::smatch match;
bool ret = false;
bool matchOne = false;
bool cur = true;
probe_type_codes lastCommand = probe_type_codes::FALSE_T;
for (auto &probe : probeCommand)
{
bool foundProbe = false;
boost::container::flat_map<const char *, probe_type_codes,
cmp_str>::const_iterator probeType;
for (probeType = PROBE_TYPES.begin(); probeType != PROBE_TYPES.end();
probeType++)
{
if (probe.find(probeType->first) != std::string::npos)
{
foundProbe = true;
break;
}
}
if (foundProbe)
{
switch (probeType->second)
{
case probe_type_codes::FALSE_T:
{
return false;
break;
}
case probe_type_codes::TRUE_T:
{
return true;
break;
}
case probe_type_codes::MATCH_ONE:
{
// set current value to last, this probe type shouldn't
// affect the outcome
cur = ret;
matchOne = true;
break;
}
/*case probe_type_codes::AND:
break;
case probe_type_codes::OR:
break;
// these are no-ops until the last command switch
*/
case probe_type_codes::FOUND:
{
if (!std::regex_search(probe, match, command))
{
std::cerr << "found probe sytax error " << probe
<< "\n";
return false;
}
std::string commandStr = *(match.begin() + 1);
boost::replace_all(commandStr, "'", "");
cur = (std::find(PASSED_PROBES.begin(), PASSED_PROBES.end(),
commandStr) != PASSED_PROBES.end());
break;
}
}
}
// look on dbus for object
else
{
if (!std::regex_search(probe, match, command))
{
std::cerr << "dbus probe sytax error " << probe << "\n";
return false;
}
std::string commandStr = *(match.begin() + 1);
// convert single ticks and single slashes into legal json
boost::replace_all(commandStr, "'", "\"");
boost::replace_all(commandStr, R"(\)", R"(\\)");
auto json = nlohmann::json::parse(commandStr, nullptr, false);
if (json.is_discarded())
{
std::cerr << "dbus command sytax error " << commandStr << "\n";
return false;
}
// we can match any (string, variant) property. (string, string)
// does a regex
std::map<std::string, nlohmann::json> dbusProbeMap =
json.get<std::map<std::string, nlohmann::json>>();
auto findStart = probe.find("(");
if (findStart == std::string::npos)
{
return false;
}
std::string probeInterface = probe.substr(0, findStart);
cur =
probeDbus(probeInterface, dbusProbeMap, foundDevs, foundProbe);
}
// some functions like AND and OR only take affect after the
// fact
switch (lastCommand)
{
case probe_type_codes::AND:
ret = cur && ret;
break;
case probe_type_codes::OR:
ret = cur || ret;
break;
default:
ret = cur;
break;
}
lastCommand = probeType != PROBE_TYPES.end()
? probeType->second
: probe_type_codes::FALSE_T;
if (!foundProbe)
{
std::cerr << "Illegal probe type " << probe << "\n";
return false;
}
}
// probe passed, but empty device
if (ret && foundDevs.size() == 0)
{
foundDevs.emplace_back(
boost::container::flat_map<std::string, BasicVariantType>());
}
if (matchOne && foundDevs.size() > 1)
{
foundDevs.erase(foundDevs.begin() + 1, foundDevs.end());
}
return ret;
}
// this class finds the needed dbus fields and on destruction runs the probe
struct PerformProbe : std::enable_shared_from_this<PerformProbe>
{
PerformProbe(
const std::vector<std::string> &probeCommand,
std::function<void(std::vector<boost::container::flat_map<
std::string, BasicVariantType>> &)> &&callback) :
_probeCommand(probeCommand),
_callback(std::move(callback))
{
}
~PerformProbe()
{
if (probe(_probeCommand, _foundDevs))
{
_callback(_foundDevs);
}
}
void run()
{
// parse out dbus probes by discarding other probe types
boost::container::flat_map<const char *, probe_type_codes,
cmp_str>::const_iterator probeType;
std::vector<std::string> dbusProbes;
for (std::string &probe : _probeCommand)
{
bool found = false;
boost::container::flat_map<const char *, probe_type_codes,
cmp_str>::const_iterator probeType;
for (probeType = PROBE_TYPES.begin();
probeType != PROBE_TYPES.end(); probeType++)
{
if (probe.find(probeType->first) != std::string::npos)
{
found = true;
break;
}
}
if (found)
{
continue;
}
// syntax requires probe before first open brace
auto findStart = probe.find("(");
std::string interface = probe.substr(0, findStart);
findDbusObjects(shared_from_this(), SYSTEM_BUS, interface);
}
}
std::vector<std::string> _probeCommand;
std::function<void(
std::vector<boost::container::flat_map<std::string, BasicVariantType>>
&)>
_callback;
std::vector<boost::container::flat_map<std::string, BasicVariantType>>
_foundDevs;
};
// writes output files to persist data
void writeJsonFiles(const nlohmann::json &systemConfiguration)
{
std::experimental::filesystem::create_directory(OUTPUT_DIR);
std::ofstream output(std::string(OUTPUT_DIR) + "system.json");
output << systemConfiguration.dump(4);
output.close();
}
// template function to add array as dbus property
template <typename PropertyType>
void addArrayToDbus(const std::string &name, const nlohmann::json &array,
sdbusplus::asio::dbus_interface *iface)
{
std::vector<PropertyType> values;
for (const auto &property : array)
{
auto ptr = property.get_ptr<const PropertyType *>();
if (ptr != nullptr)
{
values.emplace_back(*ptr);
}
}
iface->register_property(name, values);
}
template <typename JsonType>
bool SetJsonFromPointer(const std::string &ptrStr, const JsonType &value,
nlohmann::json &systemConfiguration)
{
try
{
nlohmann::json::json_pointer ptr(ptrStr);
nlohmann::json &ref = systemConfiguration[ptr];
ref = value;
return true;
}
catch (const std::out_of_range)
{
return false;
}
}
// adds simple json types to interface's properties
void populateInterfaceFromJson(nlohmann::json &systemConfiguration,
const std::string &jsonPointerPath,
sdbusplus::asio::dbus_interface *iface,
nlohmann::json &dict,
sdbusplus::asio::object_server &objServer,
bool setable = false)
{
for (auto &dictPair : dict.items())
{
auto type = dictPair.value().type();
bool array = false;
if (dictPair.value().type() == nlohmann::json::value_t::array)
{
array = true;
if (!dictPair.value().size())
{
continue;
}
type = dictPair.value()[0].type();
bool isLegal = true;
for (const auto &arrayItem : dictPair.value())
{
if (arrayItem.type() != type)
{
isLegal = false;
break;
}
}
if (!isLegal)
{
std::cerr << "dbus format error" << dictPair.value() << "\n";
continue;
}
if (type == nlohmann::json::value_t::object)
{
continue; // handled elsewhere
}
}
std::string key = jsonPointerPath + "/" + dictPair.key();
switch (type)
{
case (nlohmann::json::value_t::boolean):
{
if (array)
{
// todo: array of bool isn't detected correctly by
// sdbusplus, change it to numbers
addArrayToDbus<uint64_t>(dictPair.key(), dictPair.value(),
iface);
break;
}
if (setable)
{
iface->register_property(
std::string(dictPair.key()),
dictPair.value().get<bool>(),
[&, key](const bool &newVal, bool &val) {
val = newVal;
if (!SetJsonFromPointer(key, val,
systemConfiguration))
{
std::cerr << "error writing json\n";
return -1;
}
writeJsonFiles(systemConfiguration);
return 1;
});
}
else
{
iface->register_property(std::string(dictPair.key()),
dictPair.value().get<bool>());
}
break;
}
case (nlohmann::json::value_t::number_integer):
{
if (array)
{
addArrayToDbus<int64_t>(dictPair.key(), dictPair.value(),
iface);
break;
}
if (setable)
{
iface->register_property(
std::string(dictPair.key()),
dictPair.value().get<int64_t>(),
[&, key](const int64_t &newVal, int64_t &val) {
val = newVal;
if (!SetJsonFromPointer(key, val,
systemConfiguration))
{
std::cerr << "error writing json\n";
return -1;
}
writeJsonFiles(systemConfiguration);
return 1;
});
}
else
{
iface->register_property(std::string(dictPair.key()),
dictPair.value().get<int64_t>());
}
break;
}
case (nlohmann::json::value_t::number_unsigned):
{
if (array)
{
addArrayToDbus<uint64_t>(dictPair.key(), dictPair.value(),
iface);
break;
}
if (setable)
{
iface->register_property(
std::string(dictPair.key()),
dictPair.value().get<uint64_t>(),
[&, key](const uint64_t &newVal, uint64_t &val) {
val = newVal;
if (!SetJsonFromPointer(key, val,
systemConfiguration))
{
std::cerr << "error writing json\n";
return -1;
}
writeJsonFiles(systemConfiguration);
return 1;
});
}
else
{
iface->register_property(std::string(dictPair.key()),
dictPair.value().get<uint64_t>());
}
break;
}
case (nlohmann::json::value_t::number_float):
{
if (array)
{
addArrayToDbus<double>(dictPair.key(), dictPair.value(),
iface);
break;
}
if (setable)
{
iface->register_property(
std::string(dictPair.key()),
dictPair.value().get<double>(),
[&, key](const double &newVal, double &val) {
val = newVal;
if (!SetJsonFromPointer(key, val,
systemConfiguration))
{
std::cerr << "error writing json\n";
return -1;
}
return 1;
});
}
else
{
iface->register_property(std::string(dictPair.key()),
dictPair.value().get<double>());
}
break;
}
case (nlohmann::json::value_t::string):
{
if (array)
{
addArrayToDbus<std::string>(dictPair.key(),
dictPair.value(), iface);
break;
}
if (setable)
{
iface->register_property(
std::string(dictPair.key()),
dictPair.value().get<std::string>(),
[&, key](const std::string &newVal, std::string &val) {
val = newVal;
if (!SetJsonFromPointer(key, val,
systemConfiguration))
{
std::cerr << "error writing json\n";
return -1;
}
writeJsonFiles(systemConfiguration);
return 1;
});
}
else
{
iface->register_property(
std::string(dictPair.key()),
dictPair.value().get<std::string>());
}
break;
}
}
}
iface->initialize();
}
void postToDbus(const nlohmann::json &newConfiguration,
nlohmann::json &systemConfiguration,
sdbusplus::asio::object_server &objServer)
{
// iterate through boards
for (auto &boardPair : newConfiguration.items())
{
std::string boardKey = boardPair.key();
std::vector<std::string> path;
std::string jsonPointerPath = "/" + boardKey;
// loop through newConfiguration, but use values from system
// configuration to be able to modify via dbus later
auto boardValues = systemConfiguration[boardKey];
auto findBoardType = boardValues.find("type");
std::string boardType;
if (findBoardType != boardValues.end() &&
findBoardType->type() == nlohmann::json::value_t::string)
{
boardType = findBoardType->get<std::string>();
std::regex_replace(boardType.begin(), boardType.begin(),
boardType.end(), ILLEGAL_DBUS_REGEX, "_");
}
else
{
std::cerr << "Unable to find type for " << boardKey
<< " reverting to Chassis.\n";
boardType = "Chassis";
}
std::string boardtypeLower = boost::algorithm::to_lower_copy(boardType);
std::regex_replace(boardKey.begin(), boardKey.begin(), boardKey.end(),
ILLEGAL_DBUS_REGEX, "_");
std::string boardName = "/xyz/openbmc_project/inventory/system/" +
boardtypeLower + "/" + boardKey;
auto inventoryIface = objServer.add_interface(
boardName, "xyz.openbmc_project.Inventory.Item");
auto boardIface = objServer.add_interface(
boardName, "xyz.openbmc_project.Inventory.Item." + boardType);
populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
boardIface.get(), boardValues, objServer);
jsonPointerPath += "/";
// iterate through board properties
for (auto &boardField : boardValues.items())
{
if (boardField.value().type() == nlohmann::json::value_t::object)
{
auto iface =
objServer.add_interface(boardName, boardField.key());
populateInterfaceFromJson(
systemConfiguration, jsonPointerPath + boardField.key(),
iface.get(), boardField.value(), objServer);
}
}
auto exposes = boardValues.find("exposes");
if (exposes == boardValues.end())
{
continue;
}
// iterate through exposes
jsonPointerPath += "exposes/";
// store the board level pointer so we can modify it on the way down
std::string jsonPointerPathBoard = jsonPointerPath;
size_t exposesIndex = -1;
for (auto &item : *exposes)
{
exposesIndex++;
jsonPointerPath = jsonPointerPathBoard;
jsonPointerPath += std::to_string(exposesIndex);
auto findName = item.find("name");
if (findName == item.end())
{
std::cerr << "cannot find name in field " << item << "\n";
continue;
}
auto findStatus = item.find("status");
// if status is not found it is assumed to be status = 'okay'
if (findStatus != item.end())
{
if (*findStatus == "disabled")
{
continue;
}
}
auto findType = item.find("type");
std::string itemType;
if (findType != item.end())
{
itemType = findType->get<std::string>();
std::regex_replace(itemType.begin(), itemType.begin(),
itemType.end(), ILLEGAL_DBUS_REGEX, "_");
}
else
{
itemType = "unknown";
}
std::string itemName = findName->get<std::string>();
std::regex_replace(itemName.begin(), itemName.begin(),
itemName.end(), ILLEGAL_DBUS_REGEX, "_");
auto itemIface = objServer.add_interface(
boardName + "/" + itemName,
"xyz.openbmc_project.Configuration." + itemType);
populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
itemIface.get(), item, objServer);
for (auto &objectPair : item.items())
{
jsonPointerPath = jsonPointerPathBoard +
std::to_string(exposesIndex) + "/" +
objectPair.key();
if (objectPair.value().type() ==
nlohmann::json::value_t::object)
{
auto objectIface = objServer.add_interface(
boardName + "/" + itemName,
"xyz.openbmc_project.Configuration." + itemType + "." +
objectPair.key());
populateInterfaceFromJson(
systemConfiguration, jsonPointerPath, objectIface.get(),
objectPair.value(), objServer);
}
else if (objectPair.value().type() ==
nlohmann::json::value_t::array)
{
size_t index = 0;
if (!objectPair.value().size())
{
continue;
}
bool isLegal = true;
auto type = objectPair.value()[0].type();
if (type != nlohmann::json::value_t::object)
{
continue;
}
// verify legal json
for (const auto &arrayItem : objectPair.value())
{
if (arrayItem.type() != type)
{
isLegal = false;
break;
}
}
if (!isLegal)
{
std::cerr << "dbus format error" << objectPair.value()
<< "\n";
break;
}
for (auto &arrayItem : objectPair.value())
{
// limit what interfaces accept set for saftey
bool setable = std::find(SETTABLE_INTERFACES.begin(),
SETTABLE_INTERFACES.end(),
objectPair.key()) !=
SETTABLE_INTERFACES.end();
auto objectIface = objServer.add_interface(
boardName + "/" + itemName,
"xyz.openbmc_project.Configuration." + itemType +
"." + objectPair.key() +
std::to_string(index++));
populateInterfaceFromJson(
systemConfiguration,
jsonPointerPath + "/" + std::to_string(index),
objectIface.get(), arrayItem, objServer, setable);
}
}
}
}
}
}
// finds the template character (currently set to $) and replaces the value with
// the field found in a dbus object i.e. $ADDRESS would get populated with the
// ADDRESS field from a object on dbus
void templateCharReplace(
nlohmann::json::iterator &keyPair,
const boost::container::flat_map<std::string, BasicVariantType>
&foundDevice,
size_t &foundDeviceIdx)
{
if (keyPair.value().type() == nlohmann::json::value_t::object)
{
for (auto nextLayer = keyPair.value().begin();
nextLayer != keyPair.value().end(); nextLayer++)
{
templateCharReplace(nextLayer, foundDevice, foundDeviceIdx);
}
return;
}
else if (keyPair.value().type() != nlohmann::json::value_t::string)
{
return;
}
std::string value = keyPair.value();
if (value.find(TEMPLATE_CHAR) != std::string::npos)
{
std::string templateValue = value;
templateValue.erase(0, 1); // remove template character
// special case index
if ("index" == templateValue)
{
keyPair.value() = foundDeviceIdx;
}
else
{
bool found = false;
for (auto &foundDevicePair : foundDevice)
{
if (boost::iequals(foundDevicePair.first, templateValue))
{
mapbox::util::apply_visitor(
[&](auto &&val) { keyPair.value() = val; },
foundDevicePair.second);
found = true;
break;
}
}
if (!found)
{
std::cerr << "could not find symbol " << templateValue << "\n";
}
}
}
}
// reads json files out of the filesystem
bool findJsonFiles(std::list<nlohmann::json> &configurations)
{
// find configuration files
std::vector<fs::path> jsonPaths;
if (!find_files(fs::path(CONFIGURATION_DIR), R"(.*\.json)", jsonPaths, 0))
{
std::cerr << "Unable to find any configuration files in "
<< CONFIGURATION_DIR << "\n";
return false;
}
for (auto &jsonPath : jsonPaths)
{
std::ifstream jsonStream(jsonPath.c_str());
if (!jsonStream.good())
{
std::cerr << "unable to open " << jsonPath.string() << "\n";
continue;
}
auto data = nlohmann::json::parse(jsonStream, nullptr, false);
if (data.is_discarded())
{
std::cerr << "syntax error in " << jsonPath.string() << "\n";
continue;
}
if (data.type() == nlohmann::json::value_t::array)
{
for (auto &d : data)
{
configurations.emplace_back(d);
}
}
else
{
configurations.emplace_back(data);
}
}
}
struct PerformScan : std::enable_shared_from_this<PerformScan>
{
PerformScan(nlohmann::json &systemConfiguration,
std::list<nlohmann::json> &configurations,
std::function<void(void)> &&callback) :
_systemConfiguration(systemConfiguration),
_configurations(configurations), _callback(std::move(callback))
{
}
void run()
{
for (auto it = _configurations.begin(); it != _configurations.end();)
{
auto findProbe = it->find("probe");
auto findName = it->find("name");
nlohmann::json probeCommand;
// check for poorly formatted fields, probe must be an array
if (findProbe == it->end())
{
std::cerr << "configuration file missing probe:\n " << *it
<< "\n";
it = _configurations.erase(it);
continue;
}
else if ((*findProbe).type() != nlohmann::json::value_t::array)
{
probeCommand = nlohmann::json::array();
probeCommand.push_back(*findProbe);
}
else
{
probeCommand = *findProbe;
}
if (findName == it->end())
{
std::cerr << "configuration file missing name:\n " << *it
<< "\n";
it = _configurations.erase(it);
continue;
}
std::string name = *findName;
if (std::find(PASSED_PROBES.begin(), PASSED_PROBES.end(), name) !=
PASSED_PROBES.end())
{
it = _configurations.erase(it);
continue;
}
nlohmann::json *record = &(*it);
// store reference to this to children to makes sure we don't get
// destroyed too early
auto thisRef = shared_from_this();
auto p = std::make_shared<PerformProbe>(
probeCommand,
[&, record, name,
thisRef](std::vector<boost::container::flat_map<
std::string, BasicVariantType>> &foundDevices) {
_passed = true;
PASSED_PROBES.push_back(name);
size_t foundDeviceIdx = 0;
for (auto &foundDevice : foundDevices)
{
for (auto keyPair = record->begin();
keyPair != record->end(); keyPair++)
{
templateCharReplace(keyPair, foundDevice,
foundDeviceIdx);
}
auto findExpose = record->find("exposes");
if (findExpose == record->end())
{
continue;
}
for (auto &expose : *findExpose)
{
for (auto keyPair = expose.begin();
keyPair != expose.end(); keyPair++)
{
// fill in template characters with devices
// found
templateCharReplace(keyPair, foundDevice,
foundDeviceIdx);
// special case bind
if (boost::starts_with(keyPair.key(), "bind_"))
{
if (keyPair.value().type() !=
nlohmann::json::value_t::string)
{
std::cerr << "bind_ value must be of "
"type string "
<< keyPair.key() << "\n";
continue;
}
bool foundBind = false;
std::string bind = keyPair.key().substr(
sizeof("bind_") - 1);
for (auto &configurationPair :
_systemConfiguration.items())
{
auto configListFind =
configurationPair.value().find(
"exposes");
if (configListFind ==
configurationPair.value()
.end() ||
configListFind->type() !=
nlohmann::json::value_t::array)
{
continue;
}
for (auto &exposedObject :
*configListFind)
{
std::string foundObjectName =
(exposedObject)["name"];
if (boost::iequals(
foundObjectName,
keyPair.value()
.get<std::string>()))
{
exposedObject["status"] =
"okay";
expose[bind] = exposedObject;
foundBind = true;
break;
}
}
if (foundBind)
{
break;
}
}
if (!foundBind)
{
std::cerr << "configuration file "
"dependency error, "
"could not find bind "
<< keyPair.value() << "\n";
}
}
}
}
}
_systemConfiguration[name] = *record;
});
p->run();
it++;
}
}
~PerformScan()
{
if (_passed)
{
auto nextScan = std::make_shared<PerformScan>(
_systemConfiguration, _configurations, std::move(_callback));
nextScan->run();
}
else
{
_callback();
}
}
nlohmann::json &_systemConfiguration;
std::list<nlohmann::json> _configurations;
std::function<void(void)> _callback;
std::vector<std::shared_ptr<PerformProbe>> _probes;
bool _passed = false;
};
// main properties changed entry
void propertiesChangedCallback(
boost::asio::io_service &io,
std::vector<sdbusplus::bus::match::match> &dbusMatches,
nlohmann::json &systemConfiguration,
sdbusplus::asio::object_server &objServer)
{
static boost::asio::deadline_timer timer(io);
timer.expires_from_now(boost::posix_time::seconds(1));
// setup an async wait as we normally get flooded with new requests
timer.async_wait([&](const boost::system::error_code &ec) {
if (ec == boost::asio::error::operation_aborted)
{
// we were cancelled
return;
}
else if (ec)
{
std::cerr << "async wait error " << ec << "\n";
return;
}
nlohmann::json oldConfiguration = systemConfiguration;
DBUS_PROBE_OBJECTS.clear();
std::list<nlohmann::json> configurations;
if (!findJsonFiles(configurations))
{
std::cerr << "cannot find json files\n";
return;
}
auto perfScan = std::make_shared<PerformScan>(
systemConfiguration, configurations, [&, oldConfiguration]() {
nlohmann::json newConfiguration = systemConfiguration;
for (auto it = newConfiguration.begin();
it != newConfiguration.end();)
{
auto findKey = oldConfiguration.find(it.key());
if (findKey != oldConfiguration.end())
{
it = newConfiguration.erase(it);
}
else
{
it++;
}
}
registerCallbacks(io, dbusMatches, systemConfiguration,
objServer);
io.post([&, newConfiguration]() {
// todo: for now, only add new configurations,
// unload to come later unloadOverlays();
loadOverlays(newConfiguration);
io.post([&]() { writeJsonFiles(systemConfiguration); });
io.post([&, newConfiguration]() {
postToDbus(newConfiguration, systemConfiguration,
objServer);
});
});
});
perfScan->run();
});
}
void registerCallbacks(boost::asio::io_service &io,
std::vector<sdbusplus::bus::match::match> &dbusMatches,
nlohmann::json &systemConfiguration,
sdbusplus::asio::object_server &objServer)
{
static boost::container::flat_set<std::string> watchedObjects;
for (const auto &objectMap : DBUS_PROBE_OBJECTS)
{
auto findObject = watchedObjects.find(objectMap.first);
if (findObject != watchedObjects.end())
{
continue;
}
std::function<void(sdbusplus::message::message & message)>
eventHandler =
[&](sdbusplus::message::message &) {
propertiesChangedCallback(io, dbusMatches,
systemConfiguration, objServer);
};
sdbusplus::bus::match::match match(
static_cast<sdbusplus::bus::bus &>(*SYSTEM_BUS),
"type='signal',member='PropertiesChanged',arg0='" +
objectMap.first + "'",
eventHandler);
dbusMatches.emplace_back(std::move(match));
}
}
int main(int argc, char **argv)
{
// setup connection to dbus
boost::asio::io_service io;
SYSTEM_BUS = std::make_shared<sdbusplus::asio::connection>(io);
SYSTEM_BUS->request_name("xyz.openbmc_project.EntityManager");
sdbusplus::asio::object_server objServer(SYSTEM_BUS);
std::shared_ptr<sdbusplus::asio::dbus_interface> entityIface =
objServer.add_interface("/xyz/openbmc_project/EntityManager",
"xyz.openbmc_project.EntityManager");
std::shared_ptr<sdbusplus::asio::dbus_interface> inventoryIface =
objServer.add_interface("/xyz/openbmc_project/inventory",
"xyz.openbmc_project.Inventory.Manager");
// to keep reference to the match / filter objects so they don't get
// destroyed
std::vector<sdbusplus::bus::match::match> dbusMatches;
nlohmann::json systemConfiguration = nlohmann::json::object();
inventoryIface->register_method(
"Notify", [](const boost::container::flat_map<
std::string,
boost::container::flat_map<std::string, BasicVariantType>>
&object) { return; });
inventoryIface->initialize();
io.post([&]() {
unloadAllOverlays();
propertiesChangedCallback(io, dbusMatches, systemConfiguration,
objServer);
});
entityIface->register_method("ReScan", [&]() {
propertiesChangedCallback(io, dbusMatches, systemConfiguration,
objServer);
});
entityIface->initialize();
io.run();
return 0;
}