| /** |
| * 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 <fmt/format.h> |
| |
| #include <nlohmann/json.hpp> |
| #include <phosphor-logging/log.hpp> |
| #include <sdbusplus/bus.hpp> |
| #include <sdeventplus/source/signal.hpp> |
| |
| #include <filesystem> |
| #include <fstream> |
| |
| 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<void(const std::string&)>; |
| |
| /** |
| * @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<sdbusplus::server::match::match>( |
| 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<std::string, |
| std::map<std::string, std::variant<std::vector<std::string>>>> |
| interfaces; |
| |
| msg.read(path, interfaces); |
| |
| if (interfaces.find(confCompatIntf) == interfaces.end()) |
| { |
| return; |
| } |
| |
| const auto& properties = interfaces.at(confCompatIntf); |
| auto names = |
| std::get<std::vector<std::string>>(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(); |
| } |
| else |
| { |
| log<level::ERR>(fmt::format("Could not find fan {} conf file {} " |
| "even after {} iface became available", |
| _appName, _fileName, confCompatIntf) |
| .c_str()); |
| } |
| } |
| |
| /** |
| * 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<std::vector<std::string>>( |
| 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 (!isOptional && confFile.empty() && !objects.empty()) |
| { |
| log<level::ERR>(fmt::format("Could not find fan {} conf file {}", |
| appName, fileName) |
| .c_str()); |
| } |
| |
| 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<level::INFO>( |
| fmt::format("Loading configuration from {}", confFile.string()) |
| .c_str()); |
| file.open(confFile); |
| try |
| { |
| jsonConf = json::parse(file); |
| } |
| catch (std::exception& e) |
| { |
| log<level::ERR>( |
| 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<level::ERR>(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<sdbusplus::server::match::match> _match; |
| }; |
| |
| } // namespace phosphor::fan |