/** * Copyright © 2020 IBM 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. */ #pragma once #include "sdbusplus.hpp" #include #include #include #include #include #include #include namespace phosphor::fan { namespace fs = std::filesystem; using json = nlohmann::json; using namespace phosphor::logging; constexpr auto confOverridePath = "/etc/phosphor-fan-presence"; constexpr auto confBasePath = "/usr/share/phosphor-fan-presence"; constexpr auto confCompatIntf = "xyz.openbmc_project.Configuration.IBMCompatibleSystem"; constexpr auto confCompatProp = "Names"; class JsonConfig { public: using ConfFileReadyFunc = std::function; /** * @brief Constructor * * Looks for the JSON config file. If it can't find one, then it * will watch entity-manager for the IBMCompatibleSystem interface * to show up and then use that data to try again. If the config * file is initially present, the callback function is executed * with the path to the file. * * @param[in] bus - The dbus bus object * @param[in] appName - The appName portion of the config file path * @param[in] fileName - Application's configuration file's name * @param[in] func - The function to call when the config file * is found. */ JsonConfig(sdbusplus::bus::bus& bus, const std::string& appName, const std::string& fileName, ConfFileReadyFunc func) : _appName(appName), _fileName(fileName), _readyFunc(func) { _match = std::make_unique( bus, sdbusplus::bus::match::rules::interfacesAdded() + sdbusplus::bus::match::rules::sender( "xyz.openbmc_project.EntityManager"), std::bind(&JsonConfig::ifacesAddedCallback, this, std::placeholders::_1)); try { _confFile = getConfFile(bus, _appName, _fileName); } catch (const std::runtime_error& e) { // No conf file found, so let the interfacesAdded // match callback handle finding it. } if (!_confFile.empty()) { _match.reset(); _readyFunc(_confFile); } } /** * @brief The interfacesAdded callback function that looks for * the IBMCompatibleSystem interface. If it finds it, * it uses the Names property in the interface to find * the JSON config file to use. If it finds one, it calls * the _readyFunc function with the config file path. * * @param[in] msg - The D-Bus message contents */ void ifacesAddedCallback(sdbusplus::message::message& msg) { sdbusplus::message::object_path path; std::map>>> interfaces; msg.read(path, interfaces); if (interfaces.find(confCompatIntf) == interfaces.end()) { return; } const auto& properties = interfaces.at(confCompatIntf); auto names = std::get>(properties.at(confCompatProp)); auto it = std::find_if(names.begin(), names.end(), [this](auto const& name) { auto confFile = fs::path{confBasePath} / _appName / name / _fileName; if (fs::exists(confFile)) { _confFile = confFile; return true; } return false; }); if (it != names.end()) { _readyFunc(_confFile); _match.reset(); } } /** * Get the json configuration file. The first location found to contain * the json config file for the given fan application is used from the * following locations in order. * 1.) From the confOverridePath location * 2.) From config file found using an entry from a list obtained from an * interface's property as a relative path extension on the base path where: * interface = Interface set in confCompatIntf with the property * property = Property set in confCompatProp containing a list of * subdirectories in priority order to find a config * 3.) *DEFAULT* - From the confBasePath location * * @brief Get the configuration file to be used * * @param[in] bus - The dbus bus object * @param[in] appName - The phosphor-fan-presence application name * @param[in] fileName - Application's configuration file's name * @param[in] isOptional - Config file is optional, default to 'false' * * @return filesystem path * The filesystem path to the configuration file to use */ static const fs::path getConfFile(sdbusplus::bus::bus& bus, const std::string& appName, const std::string& fileName, bool isOptional = false) { // Check override location fs::path confFile = fs::path{confOverridePath} / appName / fileName; if (fs::exists(confFile)) { return confFile; } // If the default file is there, use it confFile = fs::path{confBasePath} / appName / fileName; if (fs::exists(confFile)) { return confFile; } confFile.clear(); // Get all objects implementing the compatible interface auto objects = util::SDBusPlus::getSubTreePathsRaw(bus, "/", confCompatIntf, 0); for (auto& path : objects) { try { // Retrieve json config compatible relative path locations auto confCompatValue = util::SDBusPlus::getProperty>( bus, path, confCompatIntf, confCompatProp); // Look for a config file at each entry relative to the base // path and use the first one found auto it = std::find_if( confCompatValue.begin(), confCompatValue.end(), [&confFile, &appName, &fileName](auto const& entry) { confFile = fs::path{confBasePath} / appName / entry / fileName; return fs::exists(confFile); }); if (it != confCompatValue.end()) { // Use the first config file found at a listed location break; } confFile.clear(); } catch (const util::DBusError&) { // Property unavailable on object. } } if (confFile.empty() && !isOptional) { throw std::runtime_error("No JSON config file found"); } return confFile; } /** * @brief Load the JSON config file * * @param[in] confFile - File system path of the configuration file to load * * @return Parsed JSON object * The parsed JSON configuration file object */ static const json load(const fs::path& confFile) { std::ifstream file; json jsonConf; if (!confFile.empty() && fs::exists(confFile)) { log( fmt::format("Loading configuration from {}", confFile.string()) .c_str()); file.open(confFile); try { jsonConf = json::parse(file); } catch (std::exception& e) { log( fmt::format( "Failed to parse JSON config file: {}, error: {}", confFile.string(), e.what()) .c_str()); throw std::runtime_error( fmt::format( "Failed to parse JSON config file: {}, error: {}", confFile.string(), e.what()) .c_str()); } } else { log(fmt::format("Unable to open JSON config file: {}", confFile.string()) .c_str()); throw std::runtime_error( fmt::format("Unable to open JSON config file: {}", confFile.string()) .c_str()); } return jsonConf; } private: /** * @brief The 'appName' portion of the config file path. */ const std::string _appName; /** * @brief The config file name. */ const std::string _fileName; /** * @brief The function to call when the config file is available. */ ConfFileReadyFunc _readyFunc; /** * @brief The JSON config file */ fs::path _confFile; /** * @brief The interfacesAdded match that is used to wait * for the IBMCompatibleSystem interface to show up. */ std::unique_ptr _match; }; } // namespace phosphor::fan