treewide: comply with the OpenBMC style guidelines
The guidelines say cpp source code filenames should be lower_snake_case:
https://github.com/openbmc/docs/blob/master/cpp-style-and-conventions.md#files
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
Change-Id: Ia04017b0eb9a65ce1303af5b6dc36e730410fd91
diff --git a/src/entity_manager.cpp b/src/entity_manager.cpp
new file mode 100644
index 0000000..641d3f9
--- /dev/null
+++ b/src/entity_manager.cpp
@@ -0,0 +1,1166 @@
+/*
+// 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 entity_manager.cpp
+
+#include "entity_manager.hpp"
+
+#include "overlay.hpp"
+#include "utils.hpp"
+#include "variant_visitors.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 <charconv>
+#include <filesystem>
+#include <fstream>
+#include <functional>
+#include <iostream>
+#include <map>
+#include <regex>
+#include <variant>
+constexpr const char* hostConfigurationDirectory = SYSCONF_DIR "configurations";
+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";
+
+const boost::container::flat_map<const char*, probe_type_codes, CmpStr>
+ probeTypes{{{"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>;
+
+// 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> systemBus;
+nlohmann::json lastJson;
+
+boost::asio::io_context io;
+
+const std::regex illegalDbusPathRegex("[^A-Za-z0-9_.]");
+const std::regex illegalDbusMemberRegex("[^A-Za-z0-9_]");
+
+FoundProbeTypeT findProbeType(const std::string& probe)
+{
+ boost::container::flat_map<const char*, probe_type_codes,
+ CmpStr>::const_iterator probeType;
+ for (probeType = probeTypes.begin(); probeType != probeTypes.end();
+ ++probeType)
+ {
+ if (probe.find(probeType->first) != std::string::npos)
+ {
+ return probeType;
+ }
+ }
+
+ return std::nullopt;
+}
+
+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;
+}
+
+// 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& [key, value] : dict.items())
+ {
+ auto type = value.type();
+ bool array = false;
+ if (value.type() == nlohmann::json::value_t::array)
+ {
+ array = true;
+ if (!value.size())
+ {
+ continue;
+ }
+ type = value[0].type();
+ bool isLegal = true;
+ for (const auto& arrayItem : value)
+ {
+ if (arrayItem.type() != type)
+ {
+ isLegal = false;
+ break;
+ }
+ }
+ if (!isLegal)
+ {
+ std::cerr << "dbus format error" << value << "\n";
+ continue;
+ }
+ }
+ if (type == nlohmann::json::value_t::object)
+ {
+ continue; // handled elsewhere
+ }
+
+ std::string path = jsonPointerPath;
+ path.append("/").append(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 (value[0].is_number())
+ {
+ type = nlohmann::json::value_t::number_float;
+ }
+ }
+ else if (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>(key, value, iface.get(),
+ permission, systemConfiguration,
+ path);
+ }
+
+ else
+ {
+ addProperty(key, value.get<bool>(), iface.get(),
+ systemConfiguration, path, permission);
+ }
+ break;
+ }
+ case (nlohmann::json::value_t::number_integer):
+ {
+ if (array)
+ {
+ addArrayToDbus<int64_t>(key, value, iface.get(), permission,
+ systemConfiguration, path);
+ }
+ else
+ {
+ addProperty(key, value.get<int64_t>(), iface.get(),
+ systemConfiguration, path,
+ sdbusplus::asio::PropertyPermission::readOnly);
+ }
+ break;
+ }
+ case (nlohmann::json::value_t::number_unsigned):
+ {
+ if (array)
+ {
+ addArrayToDbus<uint64_t>(key, value, iface.get(),
+ permission, systemConfiguration,
+ path);
+ }
+ else
+ {
+ addProperty(key, value.get<uint64_t>(), iface.get(),
+ systemConfiguration, path,
+ sdbusplus::asio::PropertyPermission::readOnly);
+ }
+ break;
+ }
+ case (nlohmann::json::value_t::number_float):
+ {
+ if (array)
+ {
+ addArrayToDbus<double>(key, value, iface.get(), permission,
+ systemConfiguration, path);
+ }
+
+ else
+ {
+ addProperty(key, value.get<double>(), iface.get(),
+ systemConfiguration, path, permission);
+ }
+ break;
+ }
+ case (nlohmann::json::value_t::string):
+ {
+ if (array)
+ {
+ addArrayToDbus<std::string>(key, value, iface.get(),
+ permission, systemConfiguration,
+ path);
+ }
+ else
+ {
+ addProperty(key, value.get<std::string>(), iface.get(),
+ systemConfiguration, path, permission);
+ }
+ break;
+ }
+ default:
+ {
+ std::cerr << "Unexpected json type in system configuration "
+ << key << ": " << 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(), illegalDbusMemberRegex, "_");
+
+ 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& [boardId, boardConfig] : newConfiguration.items())
+ {
+ std::string boardKey = boardConfig["Name"];
+ std::string boardKeyOrig = boardConfig["Name"];
+ std::string jsonPointerPath = "/" + boardId;
+ // loop through newConfiguration, but use values from system
+ // configuration to be able to modify via dbus later
+ auto boardValues = systemConfiguration[boardId];
+ 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(), illegalDbusMemberRegex, "_");
+ }
+ 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(),
+ illegalDbusMemberRegex, "_");
+ std::string boardName = "/xyz/openbmc_project/inventory/system/";
+ boardName += boardtypeLower;
+ boardName += "/";
+ boardName += 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& [propName, propValue] : boardValues.items())
+ {
+ if (propValue.type() == nlohmann::json::value_t::object)
+ {
+ std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
+ createInterface(objServer, boardName, propName,
+ boardKeyOrig);
+
+ populateInterfaceFromJson(systemConfiguration,
+ jsonPointerPath + propName, iface,
+ propValue, 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(), illegalDbusPathRegex, "_");
+ }
+ else
+ {
+ itemType = "unknown";
+ }
+ std::string itemName = findName->get<std::string>();
+ std::regex_replace(itemName.begin(), itemName.begin(),
+ itemName.end(), illegalDbusMemberRegex, "_");
+ std::string ifacePath = boardName;
+ ifacePath += "/";
+ ifacePath += itemName;
+
+ std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface =
+ createInterface(objServer, ifacePath,
+ "xyz.openbmc_project.Configuration." + itemType,
+ boardKeyOrig);
+
+ populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
+ itemIface, item, objServer,
+ getPermission(itemType));
+
+ for (auto& [name, config] : item.items())
+ {
+ jsonPointerPath = jsonPointerPathBoard;
+ jsonPointerPath.append(std::to_string(exposesIndex))
+ .append("/")
+ .append(name);
+ if (config.type() == nlohmann::json::value_t::object)
+ {
+ std::string ifaceName =
+ "xyz.openbmc_project.Configuration.";
+ ifaceName.append(itemType).append(".").append(name);
+
+ std::shared_ptr<sdbusplus::asio::dbus_interface>
+ objectIface = createInterface(objServer, ifacePath,
+ ifaceName, boardKeyOrig);
+
+ populateInterfaceFromJson(
+ systemConfiguration, jsonPointerPath, objectIface,
+ config, objServer, getPermission(name));
+ }
+ else if (config.type() == nlohmann::json::value_t::array)
+ {
+ size_t index = 0;
+ if (!config.size())
+ {
+ continue;
+ }
+ bool isLegal = true;
+ auto type = config[0].type();
+ if (type != nlohmann::json::value_t::object)
+ {
+ continue;
+ }
+
+ // verify legal json
+ for (const auto& arrayItem : config)
+ {
+ if (arrayItem.type() != type)
+ {
+ isLegal = false;
+ break;
+ }
+ }
+ if (!isLegal)
+ {
+ std::cerr << "dbus format error" << config << "\n";
+ break;
+ }
+
+ for (auto& arrayItem : config)
+ {
+ std::string ifaceName =
+ "xyz.openbmc_project.Configuration.";
+ ifaceName.append(itemType).append(".").append(name);
+ ifaceName.append(std::to_string(index));
+
+ std::shared_ptr<sdbusplus::asio::dbus_interface>
+ objectIface = createInterface(
+ objServer, ifacePath, ifaceName, boardKeyOrig);
+
+ populateInterfaceFromJson(
+ systemConfiguration,
+ jsonPointerPath + "/" + std::to_string(index),
+ objectIface, arrayItem, objServer,
+ getPermission(name));
+ index++;
+ }
+ }
+ }
+ }
+ }
+}
+
+// reads json files out of the filesystem
+bool loadConfigurations(std::list<nlohmann::json>& configurations)
+{
+ // find configuration files
+ std::vector<std::filesystem::path> jsonPaths;
+ if (!findFiles(
+ std::vector<std::filesystem::path>{configurationDirectory,
+ hostConfigurationDirectory},
+ 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;
+}
+
+static bool deviceRequiresPowerOn(const nlohmann::json& entity)
+{
+ auto powerState = entity.find("PowerState");
+ if (powerState == entity.end())
+ {
+ return false;
+ }
+
+ auto ptr = powerState->get_ptr<const std::string*>();
+ if (!ptr)
+ {
+ return false;
+ }
+
+ return *ptr == "On" || *ptr == "BiosPost";
+}
+
+static void pruneDevice(const nlohmann::json& systemConfiguration,
+ const bool powerOff, const bool scannedPowerOff,
+ const std::string& name, const nlohmann::json& device)
+{
+ if (systemConfiguration.contains(name))
+ {
+ return;
+ }
+
+ if (deviceRequiresPowerOn(device) && (powerOff || scannedPowerOff))
+ {
+ return;
+ }
+
+ logDeviceRemoved(device);
+}
+
+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)
+ {
+ return;
+ }
+
+ bool powerOff = !isPowerOn();
+ for (const auto& [name, device] : lastJson.items())
+ {
+ pruneDevice(systemConfiguration, powerOff, scannedPowerOff,
+ name, device);
+ }
+
+ scannedPowerOff = true;
+ if (!powerOff)
+ {
+ scannedPowerOn = true;
+ }
+ });
+}
+
+static std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>&
+ getDeviceInterfaces(const nlohmann::json& device)
+{
+ return inventory[device["Name"].get<std::string>()];
+}
+
+static void pruneConfiguration(nlohmann::json& systemConfiguration,
+ sdbusplus::asio::object_server& objServer,
+ bool powerOff, const std::string& name,
+ const nlohmann::json& device)
+{
+ if (powerOff && deviceRequiresPowerOn(device))
+ {
+ // power not on yet, don't know if it's there or not
+ return;
+ }
+
+ auto& ifaces = getDeviceInterfaces(device);
+ for (auto& iface : ifaces)
+ {
+ auto sharedPtr = iface.lock();
+ if (!!sharedPtr)
+ {
+ objServer.remove_interface(sharedPtr);
+ }
+ }
+
+ ifaces.clear();
+ systemConfiguration.erase(name);
+ logDeviceRemoved(device);
+}
+
+static void deriveNewConfiguration(const nlohmann::json& oldConfiguration,
+ nlohmann::json& newConfiguration)
+{
+ for (auto it = newConfiguration.begin(); it != newConfiguration.end();)
+ {
+ auto findKey = oldConfiguration.find(it.key());
+ if (findKey != oldConfiguration.end())
+ {
+ it = newConfiguration.erase(it);
+ }
+ else
+ {
+ it++;
+ }
+ }
+}
+
+static void publishNewConfiguration(
+ const size_t& instance, const size_t count,
+ boost::asio::steady_timer& timer, nlohmann::json& systemConfiguration,
+ // Gerrit discussion:
+ // https://gerrit.openbmc-project.xyz/c/openbmc/entity-manager/+/52316/6
+ //
+ // Discord discussion:
+ // https://discord.com/channels/775381525260664832/867820390406422538/958048437729910854
+ //
+ // NOLINTNEXTLINE(performance-unnecessary-value-param)
+ const nlohmann::json newConfiguration,
+ sdbusplus::asio::object_server& objServer)
+{
+ loadOverlays(newConfiguration);
+
+ io.post([systemConfiguration]() {
+ if (!writeJsonFiles(systemConfiguration))
+ {
+ std::cerr << "Error writing json files\n";
+ }
+ });
+
+ io.post([&instance, count, &timer, newConfiguration, &systemConfiguration,
+ &objServer]() {
+ postToDbus(newConfiguration, systemConfiguration, objServer);
+ if (count == instance)
+ {
+ startRemovedTimer(timer, systemConfiguration);
+ }
+ });
+}
+
+// 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;
+ }
+ 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 (!loadConfigurations(configurations))
+ {
+ std::cerr << "Could not load configurations\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& [name, device] :
+ missingConfigurations->items())
+ {
+ pruneConfiguration(systemConfiguration, objServer, powerOff,
+ name, device);
+ }
+
+ nlohmann::json newConfiguration = systemConfiguration;
+
+ deriveNewConfiguration(oldConfiguration, newConfiguration);
+
+ for (const auto& [_, device] : newConfiguration.items())
+ {
+ logDeviceAdded(device);
+ }
+
+ inProgress = false;
+
+ io.post(std::bind_front(
+ publishNewConfiguration, std::ref(instance), count,
+ std::ref(timer), std::ref(systemConfiguration),
+ newConfiguration, std::ref(objServer)));
+ });
+ perfScan->run();
+ });
+}
+
+int main()
+{
+ // setup connection to dbus
+ systemBus = std::make_shared<sdbusplus::asio::connection>(io);
+ systemBus->request_name("xyz.openbmc_project.EntityManager");
+
+ sdbusplus::asio::object_server objServer(systemBus);
+
+ 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&>(*systemBus),
+ 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&>(*systemBus),
+ sdbusplus::bus::match::rules::interfacesAdded(),
+ [&](sdbusplus::message::message&) {
+ propertiesChangedCallback(systemConfiguration, objServer);
+ });
+ sdbusplus::bus::match::match interfacesRemovedMatch(
+ static_cast<sdbusplus::bus::bus&>(*systemBus),
+ 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(systemBus);
+
+ io.run();
+
+ return 0;
+}