blob: ccca13319088a977ca245b93ab5f1add61f6c6d5 [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.
*/
/// \file EntityManager.cpp
#include "EntityManager.hpp"
#include "Overlay.hpp"
#include "Utils.hpp"
#include "VariantVisitors.hpp"
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/container/flat_map.hpp>
#include <boost/container/flat_set.hpp>
#include <boost/range/iterator_range.hpp>
#include <nlohmann/json.hpp>
#include <sdbusplus/asio/connection.hpp>
#include <sdbusplus/asio/object_server.hpp>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <regex>
#include <variant>
constexpr const char* configurationDirectory = PACKAGE_DIR "configurations";
constexpr const char* schemaDirectory = PACKAGE_DIR "configurations/schemas";
constexpr const char* tempConfigDir = "/tmp/configuration/";
constexpr const char* lastConfiguration = "/tmp/configuration/last.json";
constexpr const char* currentConfiguration = "/var/configuration/system.json";
constexpr const char* globalSchema = "global.json";
constexpr const int32_t MAX_MAPPER_DEPTH = 0;
constexpr const bool DEBUG = false;
struct cmp_str
{
bool operator()(const char* a, const char* b) const
{
return std::strcmp(a, b) < 0;
}
};
// 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*, 6> settableInterfaces = {
"FanProfile", "Pid", "Pid.Zone", "Stepwise", "Thresholds", "Polling"};
using JsonVariantType =
std::variant<std::vector<std::string>, std::vector<double>, 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>>>;
// store reference to all interfaces so we can destroy them later
boost::container::flat_map<
std::string, std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>>
inventory;
// todo: pass this through nicer
std::shared_ptr<sdbusplus::asio::connection> SYSTEM_BUS;
static nlohmann::json lastJson;
boost::asio::io_context io;
const std::regex ILLEGAL_DBUS_PATH_REGEX("[^A-Za-z0-9_.]");
const std::regex ILLEGAL_DBUS_MEMBER_REGEX("[^A-Za-z0-9_]");
void registerCallback(nlohmann::json& systemConfiguration,
sdbusplus::asio::object_server& objServer,
const std::string& interfaces);
static std::shared_ptr<sdbusplus::asio::dbus_interface>
createInterface(sdbusplus::asio::object_server& objServer,
const std::string& path, const std::string& interface,
const std::string& parent, bool checkNull = false)
{
// on first add we have no reason to check for null before add, as there
// won't be any. For dynamically added interfaces, we check for null so that
// a constant delete/add will not create a memory leak
auto ptr = objServer.add_interface(path, interface);
auto& dataVector = inventory[parent];
if (checkNull)
{
auto it = std::find_if(dataVector.begin(), dataVector.end(),
[](const auto& p) { return p.expired(); });
if (it != dataVector.end())
{
*it = ptr;
return ptr;
}
}
dataVector.emplace_back(ptr);
return ptr;
}
void getInterfaces(
const std::tuple<std::string, std::string, std::string>& call,
const std::vector<std::shared_ptr<PerformProbe>>& probeVector,
std::shared_ptr<PerformScan> scan, size_t retries = 5)
{
if (!retries)
{
std::cerr << "retries exhausted on " << std::get<0>(call) << " "
<< std::get<1>(call) << " " << std::get<2>(call) << "\n";
return;
}
SYSTEM_BUS->async_method_call(
[call, scan, probeVector, retries](
boost::system::error_code& errc,
const boost::container::flat_map<std::string, BasicVariantType>&
resp) {
if (errc)
{
std::cerr << "error calling getall on " << std::get<0>(call)
<< " " << std::get<1>(call) << " "
<< std::get<2>(call) << "\n";
std::shared_ptr<boost::asio::steady_timer> timer =
std::make_shared<boost::asio::steady_timer>(io);
timer->expires_after(std::chrono::seconds(2));
timer->async_wait([timer, call, scan, probeVector,
retries](const boost::system::error_code&) {
getInterfaces(call, probeVector, scan, retries - 1);
});
return;
}
scan->dbusProbeObjects[std::get<2>(call)].emplace_back(resp);
},
std::get<0>(call), std::get<1>(call), "org.freedesktop.DBus.Properties",
"GetAll", std::get<2>(call));
if constexpr (DEBUG)
{
std::cerr << __func__ << " " << __LINE__ << "\n";
}
}
// 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::vector<std::shared_ptr<PerformProbe>>&& probeVector,
boost::container::flat_set<std::string>&& interfaces,
std::shared_ptr<PerformScan> scan, size_t retries = 5)
{
for (const auto& [interface, _] : scan->dbusProbeObjects)
{
interfaces.erase(interface);
}
if (interfaces.empty())
{
return;
}
// find all connections in the mapper that expose a specific type
SYSTEM_BUS->async_method_call(
[interfaces, probeVector{std::move(probeVector)}, scan,
retries](boost::system::error_code& ec,
const GetSubTreeType& interfaceSubtree) mutable {
boost::container::flat_set<
std::tuple<std::string, std::string, std::string>>
interfaceConnections;
if (ec)
{
if (ec.value() == ENOENT)
{
return; // wasn't found by mapper
}
std::cerr << "Error communicating to mapper.\n";
if (!retries)
{
// if we can't communicate to the mapper something is very
// wrong
std::exit(EXIT_FAILURE);
}
std::shared_ptr<boost::asio::steady_timer> timer =
std::make_shared<boost::asio::steady_timer>(io);
timer->expires_after(std::chrono::seconds(10));
timer->async_wait(
[timer, interfaces{std::move(interfaces)}, scan,
probeVector{std::move(probeVector)},
retries](const boost::system::error_code&) mutable {
findDbusObjects(std::move(probeVector),
std::move(interfaces), scan,
retries - 1);
});
return;
}
for (const auto& [path, object] : interfaceSubtree)
{
for (const auto& [busname, ifaces] : object)
{
for (const std::string& iface : ifaces)
{
auto ifaceObjFind = interfaces.find(iface);
if (ifaceObjFind != interfaces.end())
{
interfaceConnections.emplace(busname, path, iface);
}
}
}
}
if (interfaceConnections.empty())
{
return;
}
for (const auto& call : interfaceConnections)
{
getInterfaces(call, probeVector, scan);
}
},
"xyz.openbmc_project.ObjectMapper",
"/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", MAX_MAPPER_DEPTH,
interfaces);
if constexpr (DEBUG)
{
std::cerr << __func__ << " " << __LINE__ << "\n";
}
}
// 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,
FoundDeviceT& devices, std::shared_ptr<PerformScan> scan,
bool& foundProbe)
{
std::vector<boost::container::flat_map<std::string, BasicVariantType>>&
dbusObject = scan->dbusProbeObjects[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())
{
deviceMatches = matchProbe(match.second, deviceValue->second);
}
else
{
deviceMatches = false;
break;
}
}
if (deviceMatches)
{
devices.emplace_back(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::shared_ptr<PerformScan> scan,
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;
bool first = true;
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:
{
cur = false;
break;
}
case probe_type_codes::TRUE_T:
{
cur = 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 syntax error " << probe
<< "\n";
return false;
}
std::string commandStr = *(match.begin() + 1);
boost::replace_all(commandStr, "'", "");
cur = (std::find(scan->passedProbes.begin(),
scan->passedProbes.end(),
commandStr) != scan->passedProbes.end());
break;
}
default:
{
break;
}
}
}
// look on dbus for object
else
{
if (!std::regex_search(probe, match, command))
{
std::cerr << "dbus probe syntax 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 syntax 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, scan,
foundProbe);
}
// some functions like AND and OR only take affect after the
// fact
if (lastCommand == probe_type_codes::AND)
{
ret = cur && ret;
}
else if (lastCommand == probe_type_codes::OR)
{
ret = cur || ret;
}
if (first)
{
ret = cur;
first = false;
}
lastCommand = probeType != PROBE_TYPES.end()
? probeType->second
: probe_type_codes::FALSE_T;
}
// probe passed, but empty device
if (ret && foundDevs.size() == 0)
{
foundDevs.emplace_back(
boost::container::flat_map<std::string, BasicVariantType>{});
}
if (matchOne && ret)
{
// match the last one
auto last = foundDevs.back();
foundDevs.clear();
foundDevs.emplace_back(std::move(last));
}
return ret;
}
PerformProbe::PerformProbe(const std::vector<std::string>& probeCommand,
std::shared_ptr<PerformScan>& scanPtr,
std::function<void(FoundDeviceT&)>&& callback) :
_probeCommand(probeCommand),
scan(scanPtr), _callback(std::move(callback))
{}
PerformProbe::~PerformProbe()
{
FoundDeviceT foundDevs;
if (probe(_probeCommand, scan, foundDevs))
{
_callback(foundDevs);
}
}
// writes output files to persist data
bool writeJsonFiles(const nlohmann::json& systemConfiguration)
{
std::filesystem::create_directory(configurationOutDir);
std::ofstream output(currentConfiguration);
if (!output.good())
{
return false;
}
output << systemConfiguration.dump(4);
output.close();
return true;
}
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;
}
}
// 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,
sdbusplus::asio::PropertyPermission permission,
nlohmann::json& systemConfiguration,
const std::string& jsonPointerString)
{
std::vector<PropertyType> values;
for (const auto& property : array)
{
auto ptr = property.get_ptr<const PropertyType*>();
if (ptr != nullptr)
{
values.emplace_back(*ptr);
}
}
if (permission == sdbusplus::asio::PropertyPermission::readOnly)
{
iface->register_property(name, values);
}
else
{
iface->register_property(
name, values,
[&systemConfiguration,
jsonPointerString{std::string(jsonPointerString)}](
const std::vector<PropertyType>& newVal,
std::vector<PropertyType>& val) {
val = newVal;
if (!setJsonFromPointer(jsonPointerString, val,
systemConfiguration))
{
std::cerr << "error setting json field\n";
return -1;
}
if (!writeJsonFiles(systemConfiguration))
{
std::cerr << "error setting json file\n";
return -1;
}
return 1;
});
}
}
template <typename PropertyType>
void addProperty(const std::string& propertyName, const PropertyType& value,
sdbusplus::asio::dbus_interface* iface,
nlohmann::json& systemConfiguration,
const std::string& jsonPointerString,
sdbusplus::asio::PropertyPermission permission)
{
if (permission == sdbusplus::asio::PropertyPermission::readOnly)
{
iface->register_property(propertyName, value);
return;
}
iface->register_property(
propertyName, value,
[&systemConfiguration,
jsonPointerString{std::string(jsonPointerString)}](
const PropertyType& newVal, PropertyType& val) {
val = newVal;
if (!setJsonFromPointer(jsonPointerString, val,
systemConfiguration))
{
std::cerr << "error setting json field\n";
return -1;
}
if (!writeJsonFiles(systemConfiguration))
{
std::cerr << "error setting json file\n";
return -1;
}
return 1;
});
}
void createDeleteObjectMethod(
const std::string& jsonPointerPath,
const std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
sdbusplus::asio::object_server& objServer,
nlohmann::json& systemConfiguration)
{
std::weak_ptr<sdbusplus::asio::dbus_interface> interface = iface;
iface->register_method(
"Delete", [&objServer, &systemConfiguration, interface,
jsonPointerPath{std::string(jsonPointerPath)}]() {
std::shared_ptr<sdbusplus::asio::dbus_interface> dbusInterface =
interface.lock();
if (!dbusInterface)
{
// this technically can't happen as the pointer is pointing to
// us
throw DBusInternalError();
}
nlohmann::json::json_pointer ptr(jsonPointerPath);
systemConfiguration[ptr] = nullptr;
// todo(james): dig through sdbusplus to find out why we can't
// delete it in a method call
io.post([&objServer, dbusInterface]() mutable {
objServer.remove_interface(dbusInterface);
});
if (!writeJsonFiles(systemConfiguration))
{
std::cerr << "error setting json file\n";
throw DBusInternalError();
}
});
}
// adds simple json types to interface's properties
void populateInterfaceFromJson(
nlohmann::json& systemConfiguration, const std::string& jsonPointerPath,
std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
nlohmann::json& dict, sdbusplus::asio::object_server& objServer,
sdbusplus::asio::PropertyPermission permission =
sdbusplus::asio::PropertyPermission::readOnly)
{
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();
if (permission == sdbusplus::asio::PropertyPermission::readWrite)
{
// all setable numbers are doubles as it is difficult to always
// create a configuration file with all whole numbers as decimals
// i.e. 1.0
if (array)
{
if (dictPair.value()[0].is_number())
{
type = nlohmann::json::value_t::number_float;
}
}
else if (dictPair.value().is_number())
{
type = nlohmann::json::value_t::number_float;
}
}
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.get(), permission,
systemConfiguration, key);
}
else
{
addProperty(dictPair.key(), dictPair.value().get<bool>(),
iface.get(), systemConfiguration, key,
permission);
}
break;
}
case (nlohmann::json::value_t::number_integer):
{
if (array)
{
addArrayToDbus<int64_t>(dictPair.key(), dictPair.value(),
iface.get(), permission,
systemConfiguration, key);
}
else
{
addProperty(dictPair.key(), dictPair.value().get<int64_t>(),
iface.get(), systemConfiguration, key,
sdbusplus::asio::PropertyPermission::readOnly);
}
break;
}
case (nlohmann::json::value_t::number_unsigned):
{
if (array)
{
addArrayToDbus<uint64_t>(dictPair.key(), dictPair.value(),
iface.get(), permission,
systemConfiguration, key);
}
else
{
addProperty(dictPair.key(),
dictPair.value().get<uint64_t>(), iface.get(),
systemConfiguration, key,
sdbusplus::asio::PropertyPermission::readOnly);
}
break;
}
case (nlohmann::json::value_t::number_float):
{
if (array)
{
addArrayToDbus<double>(dictPair.key(), dictPair.value(),
iface.get(), permission,
systemConfiguration, key);
}
else
{
addProperty(dictPair.key(), dictPair.value().get<double>(),
iface.get(), systemConfiguration, key,
permission);
}
break;
}
case (nlohmann::json::value_t::string):
{
if (array)
{
addArrayToDbus<std::string>(
dictPair.key(), dictPair.value(), iface.get(),
permission, systemConfiguration, key);
}
else
{
addProperty(
dictPair.key(), dictPair.value().get<std::string>(),
iface.get(), systemConfiguration, key, permission);
}
break;
}
default:
{
std::cerr << "Unexpected json type in system configuration "
<< dictPair.key() << ": "
<< dictPair.value().type_name() << "\n";
break;
}
}
}
if (permission == sdbusplus::asio::PropertyPermission::readWrite)
{
createDeleteObjectMethod(jsonPointerPath, iface, objServer,
systemConfiguration);
}
iface->initialize();
}
sdbusplus::asio::PropertyPermission getPermission(const std::string& interface)
{
return std::find(settableInterfaces.begin(), settableInterfaces.end(),
interface) != settableInterfaces.end()
? sdbusplus::asio::PropertyPermission::readWrite
: sdbusplus::asio::PropertyPermission::readOnly;
}
void createAddObjectMethod(const std::string& jsonPointerPath,
const std::string& path,
nlohmann::json& systemConfiguration,
sdbusplus::asio::object_server& objServer,
const std::string& board)
{
std::shared_ptr<sdbusplus::asio::dbus_interface> iface = createInterface(
objServer, path, "xyz.openbmc_project.AddObject", board);
iface->register_method(
"AddObject",
[&systemConfiguration, &objServer,
jsonPointerPath{std::string(jsonPointerPath)}, path{std::string(path)},
board](const boost::container::flat_map<std::string, JsonVariantType>&
data) {
nlohmann::json::json_pointer ptr(jsonPointerPath);
nlohmann::json& base = systemConfiguration[ptr];
auto findExposes = base.find("Exposes");
if (findExposes == base.end())
{
throw std::invalid_argument("Entity must have children.");
}
// this will throw invalid-argument to sdbusplus if invalid json
nlohmann::json newData{};
for (const auto& item : data)
{
nlohmann::json& newJson = newData[item.first];
std::visit([&newJson](auto&& val) { newJson = std::move(val); },
item.second);
}
auto findName = newData.find("Name");
auto findType = newData.find("Type");
if (findName == newData.end() || findType == newData.end())
{
throw std::invalid_argument("AddObject missing Name or Type");
}
const std::string* type = findType->get_ptr<const std::string*>();
const std::string* name = findName->get_ptr<const std::string*>();
if (type == nullptr || name == nullptr)
{
throw std::invalid_argument("Type and Name must be a string.");
}
bool foundNull = false;
size_t lastIndex = 0;
// we add in the "exposes"
for (const auto& expose : *findExposes)
{
if (expose.is_null())
{
foundNull = true;
continue;
}
if (expose["Name"] == *name && expose["Type"] == *type)
{
throw std::invalid_argument(
"Field already in JSON, not adding");
}
if (foundNull)
{
continue;
}
lastIndex++;
}
std::ifstream schemaFile(std::string(schemaDirectory) + "/" +
*type + ".json");
// todo(james) we might want to also make a list of 'can add'
// interfaces but for now I think the assumption if there is a
// schema avaliable that it is allowed to update is fine
if (!schemaFile.good())
{
throw std::invalid_argument(
"No schema avaliable, cannot validate.");
}
nlohmann::json schema =
nlohmann::json::parse(schemaFile, nullptr, false);
if (schema.is_discarded())
{
std::cerr << "Schema not legal" << *type << ".json\n";
throw DBusInternalError();
}
if (!validateJson(schema, newData))
{
throw std::invalid_argument("Data does not match schema");
}
if (foundNull)
{
findExposes->at(lastIndex) = newData;
}
else
{
findExposes->push_back(newData);
}
if (!writeJsonFiles(systemConfiguration))
{
std::cerr << "Error writing json files\n";
throw DBusInternalError();
}
std::string dbusName = *name;
std::regex_replace(dbusName.begin(), dbusName.begin(),
dbusName.end(), ILLEGAL_DBUS_MEMBER_REGEX, "_");
std::shared_ptr<sdbusplus::asio::dbus_interface> interface =
createInterface(objServer, path + "/" + dbusName,
"xyz.openbmc_project.Configuration." + *type,
board, true);
// permission is read-write, as since we just created it, must be
// runtime modifiable
populateInterfaceFromJson(
systemConfiguration,
jsonPointerPath + "/Exposes/" + std::to_string(lastIndex),
interface, newData, objServer,
sdbusplus::asio::PropertyPermission::readWrite);
});
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.value()["Name"];
std::string boardKeyOrig = boardPair.value()["Name"];
std::string jsonPointerPath = "/" + boardPair.key();
// loop through newConfiguration, but use values from system
// configuration to be able to modify via dbus later
auto boardValues = systemConfiguration[boardPair.key()];
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_MEMBER_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_MEMBER_REGEX, "_");
std::string boardName = "/xyz/openbmc_project/inventory/system/" +
boardtypeLower + "/" + boardKey;
std::shared_ptr<sdbusplus::asio::dbus_interface> inventoryIface =
createInterface(objServer, boardName,
"xyz.openbmc_project.Inventory.Item", boardKey);
std::shared_ptr<sdbusplus::asio::dbus_interface> boardIface =
createInterface(objServer, boardName,
"xyz.openbmc_project.Inventory.Item." + boardType,
boardKeyOrig);
createAddObjectMethod(jsonPointerPath, boardName, systemConfiguration,
objServer, boardKeyOrig);
populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
boardIface, boardValues, objServer);
jsonPointerPath += "/";
// iterate through board properties
for (auto& boardField : boardValues.items())
{
if (boardField.value().type() == nlohmann::json::value_t::object)
{
std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
createInterface(objServer, boardName, boardField.key(),
boardKeyOrig);
populateInterfaceFromJson(systemConfiguration,
jsonPointerPath + boardField.key(),
iface, 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_PATH_REGEX,
"_");
}
else
{
itemType = "unknown";
}
std::string itemName = findName->get<std::string>();
std::regex_replace(itemName.begin(), itemName.begin(),
itemName.end(), ILLEGAL_DBUS_MEMBER_REGEX, "_");
std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface =
createInterface(objServer, boardName + "/" + itemName,
"xyz.openbmc_project.Configuration." + itemType,
boardKeyOrig);
populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
itemIface, item, objServer,
getPermission(itemType));
for (auto& objectPair : item.items())
{
jsonPointerPath = jsonPointerPathBoard +
std::to_string(exposesIndex) + "/" +
objectPair.key();
if (objectPair.value().type() ==
nlohmann::json::value_t::object)
{
std::shared_ptr<sdbusplus::asio::dbus_interface>
objectIface = createInterface(
objServer, boardName + "/" + itemName,
"xyz.openbmc_project.Configuration." + itemType +
"." + objectPair.key(),
boardKeyOrig);
populateInterfaceFromJson(systemConfiguration,
jsonPointerPath, objectIface,
objectPair.value(), objServer,
getPermission(objectPair.key()));
}
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())
{
std::shared_ptr<sdbusplus::asio::dbus_interface>
objectIface = createInterface(
objServer, boardName + "/" + itemName,
"xyz.openbmc_project.Configuration." +
itemType + "." + objectPair.key() +
std::to_string(index),
boardKeyOrig);
populateInterfaceFromJson(
systemConfiguration,
jsonPointerPath + "/" + std::to_string(index),
objectIface, arrayItem, objServer,
getPermission(objectPair.key()));
index++;
}
}
}
}
}
}
// reads json files out of the filesystem
bool findJsonFiles(std::list<nlohmann::json>& configurations)
{
// find configuration files
std::vector<std::filesystem::path> jsonPaths;
if (!findFiles(std::filesystem::path(configurationDirectory), R"(.*\.json)",
jsonPaths))
{
std::cerr << "Unable to find any configuration files in "
<< configurationDirectory << "\n";
return false;
}
std::ifstream schemaStream(std::string(schemaDirectory) + "/" +
globalSchema);
if (!schemaStream.good())
{
std::cerr
<< "Cannot open schema file, cannot validate JSON, exiting\n\n";
std::exit(EXIT_FAILURE);
return false;
}
nlohmann::json schema = nlohmann::json::parse(schemaStream, nullptr, false);
if (schema.is_discarded())
{
std::cerr
<< "Illegal schema file detected, cannot validate JSON, exiting\n";
std::exit(EXIT_FAILURE);
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;
}
/*
* todo(james): reenable this once less things are in flight
*
if (!validateJson(schema, data))
{
std::cerr << "Error validating " << 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);
}
}
return true;
}
std::string getRecordName(
const boost::container::flat_map<std::string, BasicVariantType>& probe,
const std::string& probeName)
{
if (probe.empty())
{
return probeName;
}
// use an array so alphabetical order from the
// flat_map is maintained
auto device = nlohmann::json::array();
for (auto& devPair : probe)
{
device.push_back(devPair.first);
std::visit([&device](auto&& v) { device.push_back(v); },
devPair.second);
}
size_t hash = std::hash<std::string>{}(probeName + device.dump());
// hashes are hard to distinguish, use the
// non-hashed version if we want debug
if constexpr (DEBUG)
{
return probeName + device.dump();
}
else
{
return std::to_string(hash);
}
}
PerformScan::PerformScan(nlohmann::json& systemConfiguration,
nlohmann::json& missingConfigurations,
std::list<nlohmann::json>& configurations,
sdbusplus::asio::object_server& objServerIn,
std::function<void()>&& callback) :
_systemConfiguration(systemConfiguration),
_missingConfigurations(missingConfigurations),
_configurations(configurations), objServer(objServerIn),
_callback(std::move(callback))
{}
void PerformScan::run()
{
boost::container::flat_set<std::string> dbusProbeInterfaces;
std::vector<std::shared_ptr<PerformProbe>> dbusProbePointers;
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 probeName = *findName;
if (std::find(passedProbes.begin(), passedProbes.end(), probeName) !=
passedProbes.end())
{
it = _configurations.erase(it);
continue;
}
nlohmann::json* recordPtr = &(*it);
// store reference to this to children to makes sure we don't get
// destroyed too early
auto thisRef = shared_from_this();
auto probePointer = std::make_shared<PerformProbe>(
probeCommand, thisRef,
[&, recordPtr, probeName](FoundDeviceT& foundDevices) {
_passed = true;
std::set<nlohmann::json> usedNames;
passedProbes.push_back(probeName);
std::list<size_t> indexes(foundDevices.size());
std::iota(indexes.begin(), indexes.end(), 1);
size_t indexIdx = probeName.find("$");
bool hasTemplateName = (indexIdx != std::string::npos);
// copy over persisted configurations and make sure we remove
// indexes that are already used
for (auto itr = foundDevices.begin();
itr != foundDevices.end();)
{
std::string recordName = getRecordName(*itr, probeName);
auto fromLastJson = lastJson.find(recordName);
if (fromLastJson != lastJson.end())
{
auto findExposes = fromLastJson->find("Exposes");
// delete nulls from any updates
if (findExposes != fromLastJson->end())
{
auto copy = nlohmann::json::array();
for (auto& expose : *findExposes)
{
if (expose.is_null())
{
continue;
}
copy.emplace_back(expose);
}
*findExposes = copy;
}
// keep user changes
_systemConfiguration[recordName] = *fromLastJson;
_missingConfigurations.erase(recordName);
itr = foundDevices.erase(itr);
if (hasTemplateName)
{
auto nameIt = fromLastJson->find("Name");
if (nameIt == fromLastJson->end())
{
std::cerr << "Last JSON Illegal\n";
continue;
}
int index = std::stoi(
nameIt->get<std::string>().substr(indexIdx),
nullptr, 0);
usedNames.insert(nameIt.value());
auto usedIt = std::find(indexes.begin(),
indexes.end(), index);
if (usedIt == indexes.end())
{
continue; // less items now
}
indexes.erase(usedIt);
}
continue;
}
itr++;
}
std::optional<std::string> replaceStr;
for (auto& foundDevice : foundDevices)
{
nlohmann::json record = *recordPtr;
std::string recordName =
getRecordName(foundDevice, probeName);
size_t foundDeviceIdx = indexes.front();
indexes.pop_front();
// check name first so we have no duplicate names
auto getName = record.find("Name");
if (getName == record.end())
{
std::cerr << "Record Missing Name! " << record.dump();
continue; // this should be impossible at this level
}
nlohmann::json copyForName = {{"Name", getName.value()}};
nlohmann::json::iterator copyIt = copyForName.begin();
std::optional<std::string> replaceVal = templateCharReplace(
copyIt, foundDevice, foundDeviceIdx, replaceStr);
if (!replaceStr && replaceVal)
{
if (usedNames.find(copyIt.value()) != usedNames.end())
{
replaceStr = replaceVal;
copyForName = {{"Name", getName.value()}};
copyIt = copyForName.begin();
templateCharReplace(copyIt, foundDevice,
foundDeviceIdx, replaceStr);
}
}
if (replaceStr)
{
std::cerr << "Duplicates found, replacing "
<< *replaceStr
<< " with found device index.\n Consider "
"fixing template to not have duplicates\n";
}
for (auto keyPair = record.begin(); keyPair != record.end();
keyPair++)
{
if (keyPair.key() == "Name")
{
keyPair.value() = copyIt.value();
usedNames.insert(copyIt.value());
continue; // already covered above
}
templateCharReplace(keyPair, foundDevice,
foundDeviceIdx, replaceStr);
}
// insert into configuration temporarily to be able to
// reference ourselves
_systemConfiguration[recordName] = record;
auto findExpose = record.find("Exposes");
if (findExpose == record.end())
{
_systemConfiguration[recordName] = record;
continue;
}
for (auto& expose : *findExpose)
{
for (auto keyPair = expose.begin();
keyPair != expose.end(); keyPair++)
{
templateCharReplace(keyPair, foundDevice,
foundDeviceIdx, replaceStr);
bool isBind =
boost::starts_with(keyPair.key(), "Bind");
bool isDisable = keyPair.key() == "DisableNode";
// special cases
if (!(isBind || isDisable))
{
continue;
}
if (keyPair.value().type() !=
nlohmann::json::value_t::string &&
keyPair.value().type() !=
nlohmann::json::value_t::array)
{
std::cerr << "Value is invalid type "
<< keyPair.key() << "\n";
continue;
}
std::vector<std::string> matches;
if (keyPair.value().type() ==
nlohmann::json::value_t::string)
{
matches.emplace_back(keyPair.value());
}
else
{
for (const auto& value : keyPair.value())
{
if (value.type() !=
nlohmann::json::value_t::string)
{
std::cerr << "Value is invalid type "
<< value << "\n";
break;
}
matches.emplace_back(value);
}
}
std::set<std::string> foundMatches;
for (auto& configurationPair :
_systemConfiguration.items())
{
if (isDisable)
{
// don't disable ourselves
if (configurationPair.key() == recordName)
{
continue;
}
}
auto configListFind =
configurationPair.value().find("Exposes");
if (configListFind ==
configurationPair.value().end() ||
configListFind->type() !=
nlohmann::json::value_t::array)
{
continue;
}
for (auto& exposedObject : *configListFind)
{
auto matchIt = std::find_if(
matches.begin(), matches.end(),
[name = (exposedObject)["Name"]
.get<std::string>()](
const std::string& s) {
return s == name;
});
if (matchIt == matches.end())
{
continue;
}
foundMatches.insert(*matchIt);
if (isBind)
{
std::string bind = keyPair.key().substr(
sizeof("Bind") - 1);
exposedObject["Status"] = "okay";
expose[bind] = exposedObject;
}
else if (isDisable)
{
exposedObject["Status"] = "disabled";
}
}
}
if (foundMatches.size() != matches.size())
{
std::cerr << "configuration file "
"dependency error, "
"could not find "
<< keyPair.key() << " "
<< keyPair.value() << "\n";
}
}
}
// overwrite ourselves with cleaned up version
_systemConfiguration[recordName] = record;
_missingConfigurations.erase(recordName);
}
});
// parse out dbus probes by discarding other probe types, store in a
// map
for (const 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);
dbusProbeInterfaces.emplace(interface);
dbusProbePointers.emplace_back(probePointer);
}
it++;
}
for (const std::string& interface : dbusProbeInterfaces)
{
registerCallback(_systemConfiguration, objServer, interface);
}
// probe vector stores a shared_ptr to each PerformProbe that cares
// about a dbus interface
findDbusObjects(std::move(dbusProbePointers),
std::move(dbusProbeInterfaces), shared_from_this());
if constexpr (DEBUG)
{
std::cerr << __func__ << " " << __LINE__ << "\n";
}
}
PerformScan::~PerformScan()
{
if (_passed)
{
auto nextScan = std::make_shared<PerformScan>(
_systemConfiguration, _missingConfigurations, _configurations,
objServer, std::move(_callback));
nextScan->passedProbes = std::move(passedProbes);
nextScan->dbusProbeObjects = std::move(dbusProbeObjects);
nextScan->run();
if constexpr (DEBUG)
{
std::cerr << __func__ << " " << __LINE__ << "\n";
}
}
else
{
_callback();
if constexpr (DEBUG)
{
std::cerr << __func__ << " " << __LINE__ << "\n";
}
}
}
void startRemovedTimer(boost::asio::steady_timer& timer,
nlohmann::json& systemConfiguration)
{
static bool scannedPowerOff = false;
static bool scannedPowerOn = false;
if (systemConfiguration.empty() || lastJson.empty())
{
return; // not ready yet
}
if (scannedPowerOn)
{
return;
}
if (!isPowerOn() && scannedPowerOff)
{
return;
}
timer.expires_after(std::chrono::seconds(10));
timer.async_wait(
[&systemConfiguration](const boost::system::error_code& ec) {
if (ec == boost::asio::error::operation_aborted)
{
// we were cancelled
return;
}
bool powerOff = !isPowerOn();
for (const auto& item : lastJson.items())
{
if (systemConfiguration.find(item.key()) ==
systemConfiguration.end())
{
bool isDetectedPowerOn = false;
auto powerState = item.value().find("PowerState");
if (powerState != item.value().end())
{
auto ptr = powerState->get_ptr<const std::string*>();
if (ptr)
{
if (*ptr == "On" || *ptr == "BiosPost")
{
isDetectedPowerOn = true;
}
}
}
if (powerOff && isDetectedPowerOn)
{
// power not on yet, don't know if it's there or not
continue;
}
if (!powerOff && scannedPowerOff && isDetectedPowerOn)
{
// already logged it when power was off
continue;
}
logDeviceRemoved(item.value());
}
}
scannedPowerOff = true;
if (!powerOff)
{
scannedPowerOn = true;
}
});
}
// main properties changed entry
void propertiesChangedCallback(nlohmann::json& systemConfiguration,
sdbusplus::asio::object_server& objServer)
{
static bool inProgress = false;
static boost::asio::steady_timer timer(io);
static size_t instance = 0;
instance++;
size_t count = instance;
timer.expires_after(std::chrono::seconds(5));
// setup an async wait as we normally get flooded with new requests
timer.async_wait([&systemConfiguration, &objServer,
count](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;
}
if (inProgress)
{
propertiesChangedCallback(systemConfiguration, objServer);
return;
}
inProgress = true;
nlohmann::json oldConfiguration = systemConfiguration;
auto missingConfigurations = std::make_shared<nlohmann::json>();
*missingConfigurations = systemConfiguration;
std::list<nlohmann::json> configurations;
if (!findJsonFiles(configurations))
{
std::cerr << "cannot find json files\n";
inProgress = false;
return;
}
auto perfScan = std::make_shared<PerformScan>(
systemConfiguration, *missingConfigurations, configurations,
objServer,
[&systemConfiguration, &objServer, count, oldConfiguration,
missingConfigurations]() {
// this is something that since ac has been applied to the bmc
// we saw, and we no longer see it
bool powerOff = !isPowerOn();
for (const auto& item : missingConfigurations->items())
{
bool isDetectedPowerOn = false;
auto powerState = item.value().find("PowerState");
if (powerState != item.value().end())
{
auto ptr = powerState->get_ptr<const std::string*>();
if (ptr)
{
if (*ptr == "On" || *ptr == "BiosPost")
{
isDetectedPowerOn = true;
}
}
}
if (powerOff && isDetectedPowerOn)
{
// power not on yet, don't know if it's there or not
continue;
}
std::string name = item.value()["Name"].get<std::string>();
std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>&
ifaces = inventory[name];
for (auto& iface : ifaces)
{
auto sharedPtr = iface.lock();
if (!sharedPtr)
{
continue; // was already deleted elsewhere
}
objServer.remove_interface(sharedPtr);
}
ifaces.clear();
systemConfiguration.erase(item.key());
logDeviceRemoved(item.value());
}
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++;
}
}
for (const auto& item : newConfiguration.items())
{
logDeviceAdded(item.value());
}
inProgress = false;
io.post([&, newConfiguration]() {
loadOverlays(newConfiguration);
io.post([&]() {
if (!writeJsonFiles(systemConfiguration))
{
std::cerr << "Error writing json files\n";
}
});
io.post([&, newConfiguration]() {
postToDbus(newConfiguration, systemConfiguration,
objServer);
if (count != instance)
{
return;
}
startRemovedTimer(timer, systemConfiguration);
});
});
});
perfScan->run();
});
}
void registerCallback(nlohmann::json& systemConfiguration,
sdbusplus::asio::object_server& objServer,
const std::string& interface)
{
static boost::container::flat_map<std::string, sdbusplus::bus::match::match>
dbusMatches;
auto find = dbusMatches.find(interface);
if (find != dbusMatches.end())
{
return;
}
std::function<void(sdbusplus::message::message & message)> eventHandler =
[&](sdbusplus::message::message&) {
propertiesChangedCallback(systemConfiguration, objServer);
};
sdbusplus::bus::match::match match(
static_cast<sdbusplus::bus::bus&>(*SYSTEM_BUS),
"type='signal',member='PropertiesChanged',arg0='" + interface + "'",
eventHandler);
dbusMatches.emplace(interface, std::move(match));
}
int main()
{
// setup connection to dbus
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");
// to keep reference to the match / filter objects so they don't get
// destroyed
nlohmann::json systemConfiguration = nlohmann::json::object();
// We need a poke from DBus for static providers that create all their
// objects prior to claiming a well-known name, and thus don't emit any
// org.freedesktop.DBus.Properties signals. Similarly if a process exits
// for any reason, expected or otherwise, we'll need a poke to remove
// entities from DBus.
sdbusplus::bus::match::match nameOwnerChangedMatch(
static_cast<sdbusplus::bus::bus&>(*SYSTEM_BUS),
sdbusplus::bus::match::rules::nameOwnerChanged(),
[&](sdbusplus::message::message&) {
propertiesChangedCallback(systemConfiguration, objServer);
});
// We also need a poke from DBus when new interfaces are created or
// destroyed.
sdbusplus::bus::match::match interfacesAddedMatch(
static_cast<sdbusplus::bus::bus&>(*SYSTEM_BUS),
sdbusplus::bus::match::rules::interfacesAdded(),
[&](sdbusplus::message::message&) {
propertiesChangedCallback(systemConfiguration, objServer);
});
sdbusplus::bus::match::match interfacesRemovedMatch(
static_cast<sdbusplus::bus::bus&>(*SYSTEM_BUS),
sdbusplus::bus::match::rules::interfacesRemoved(),
[&](sdbusplus::message::message&) {
propertiesChangedCallback(systemConfiguration, objServer);
});
io.post(
[&]() { propertiesChangedCallback(systemConfiguration, objServer); });
entityIface->register_method("ReScan", [&]() {
propertiesChangedCallback(systemConfiguration, objServer);
});
entityIface->initialize();
if (fwVersionIsSame())
{
if (std::filesystem::is_regular_file(currentConfiguration))
{
// this file could just be deleted, but it's nice for debug
std::filesystem::create_directory(tempConfigDir);
std::filesystem::remove(lastConfiguration);
std::filesystem::copy(currentConfiguration, lastConfiguration);
std::filesystem::remove(currentConfiguration);
std::ifstream jsonStream(lastConfiguration);
if (jsonStream.good())
{
auto data = nlohmann::json::parse(jsonStream, nullptr, false);
if (data.is_discarded())
{
std::cerr << "syntax error in " << lastConfiguration
<< "\n";
}
else
{
lastJson = std::move(data);
}
}
else
{
std::cerr << "unable to open " << lastConfiguration << "\n";
}
}
}
else
{
// not an error, just logging at this level to make it in the journal
std::cerr << "Clearing previous configuration\n";
std::filesystem::remove(currentConfiguration);
}
// some boards only show up after power is on, we want to not say they are
// removed until the same state happens
setupPowerMatch(SYSTEM_BUS);
io.run();
return 0;
}