111547c9bSMatthew Barth /** 211547c9bSMatthew Barth * Copyright © 2020 IBM Corporation 311547c9bSMatthew Barth * 411547c9bSMatthew Barth * Licensed under the Apache License, Version 2.0 (the "License"); 511547c9bSMatthew Barth * you may not use this file except in compliance with the License. 611547c9bSMatthew Barth * You may obtain a copy of the License at 711547c9bSMatthew Barth * 811547c9bSMatthew Barth * http://www.apache.org/licenses/LICENSE-2.0 911547c9bSMatthew Barth * 1011547c9bSMatthew Barth * Unless required by applicable law or agreed to in writing, software 1111547c9bSMatthew Barth * distributed under the License is distributed on an "AS IS" BASIS, 1211547c9bSMatthew Barth * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1311547c9bSMatthew Barth * See the License for the specific language governing permissions and 1411547c9bSMatthew Barth * limitations under the License. 1511547c9bSMatthew Barth */ 1611547c9bSMatthew Barth #pragma once 1711547c9bSMatthew Barth 1811547c9bSMatthew Barth #include "sdbusplus.hpp" 1911547c9bSMatthew Barth 2011547c9bSMatthew Barth #include <nlohmann/json.hpp> 2111547c9bSMatthew Barth #include <phosphor-logging/log.hpp> 2211547c9bSMatthew Barth #include <sdbusplus/bus.hpp> 2311547c9bSMatthew Barth #include <sdeventplus/source/signal.hpp> 2411547c9bSMatthew Barth 2511547c9bSMatthew Barth #include <filesystem> 26fbf4703fSPatrick Williams #include <format> 2711547c9bSMatthew Barth #include <fstream> 2811547c9bSMatthew Barth 2911547c9bSMatthew Barth namespace phosphor::fan 3011547c9bSMatthew Barth { 3111547c9bSMatthew Barth 3211547c9bSMatthew Barth namespace fs = std::filesystem; 3311547c9bSMatthew Barth using json = nlohmann::json; 3411547c9bSMatthew Barth using namespace phosphor::logging; 3511547c9bSMatthew Barth 3611547c9bSMatthew Barth constexpr auto confOverridePath = "/etc/phosphor-fan-presence"; 3711547c9bSMatthew Barth constexpr auto confBasePath = "/usr/share/phosphor-fan-presence"; 38b370ab3bSMatthew Barth constexpr auto confCompatServ = "xyz.openbmc_project.EntityManager"; 39fcbdc0e4SMatthew Barth constexpr auto confCompatIntf = 40b99ce0edSChau Ly "xyz.openbmc_project.Inventory.Decorator.Compatible"; 41fcbdc0e4SMatthew Barth constexpr auto confCompatProp = "Names"; 4211547c9bSMatthew Barth 436eb603e8SMatthew Barth /** 446eb603e8SMatthew Barth * @class NoConfigFound - A no JSON configuration found exception 456eb603e8SMatthew Barth * 466eb603e8SMatthew Barth * A no JSON configuration found exception that is used to denote that a JSON 476eb603e8SMatthew Barth * configuration has not been found yet. 486eb603e8SMatthew Barth */ 496eb603e8SMatthew Barth class NoConfigFound : public std::runtime_error 506eb603e8SMatthew Barth { 516eb603e8SMatthew Barth public: 526eb603e8SMatthew Barth NoConfigFound() = delete; 536eb603e8SMatthew Barth NoConfigFound(const NoConfigFound&) = delete; 546eb603e8SMatthew Barth NoConfigFound(NoConfigFound&&) = delete; 556eb603e8SMatthew Barth NoConfigFound& operator=(const NoConfigFound&) = delete; 566eb603e8SMatthew Barth NoConfigFound& operator=(NoConfigFound&&) = delete; 576eb603e8SMatthew Barth ~NoConfigFound() = default; 586eb603e8SMatthew Barth 596eb603e8SMatthew Barth /** 606eb603e8SMatthew Barth * @brief No JSON configuration found exception object 616eb603e8SMatthew Barth * 626eb603e8SMatthew Barth * When attempting to find the JSON configuration file(s), a NoConfigFound 636eb603e8SMatthew Barth * exception can be thrown to denote that at that time finding/loading the 646eb603e8SMatthew Barth * JSON configuration file(s) for a fan application failed. Details on what 656eb603e8SMatthew Barth * application and JSON configuration file that failed to be found will be 666eb603e8SMatthew Barth * logged resulting in the application being terminated. 676eb603e8SMatthew Barth * 686eb603e8SMatthew Barth * @param[in] details - Additional details 696eb603e8SMatthew Barth */ NoConfigFound(const std::string & appName,const std::string & fileName)706eb603e8SMatthew Barth NoConfigFound(const std::string& appName, const std::string& fileName) : 71fbf4703fSPatrick Williams std::runtime_error(std::format("JSON configuration not found [Could " 726eb603e8SMatthew Barth "not find fan {} conf file {}]", 736eb603e8SMatthew Barth appName, fileName) 746eb603e8SMatthew Barth .c_str()) 756eb603e8SMatthew Barth {} 766eb603e8SMatthew Barth }; 776eb603e8SMatthew Barth 7811547c9bSMatthew Barth class JsonConfig 7911547c9bSMatthew Barth { 8011547c9bSMatthew Barth public: 8159850df3SMatt Spinler /** 82d80c8753SMatthew Barth * @brief Get the object paths with the compatible interface 83d80c8753SMatthew Barth * 84d80c8753SMatthew Barth * Retrieve all the object paths implementing the compatible interface for 85d80c8753SMatthew Barth * configuration file loading. 86d80c8753SMatthew Barth */ getCompatObjPaths()87b99ce0edSChau Ly std::vector<std::string>& getCompatObjPaths() 88d80c8753SMatthew Barth { 89b99ce0edSChau Ly using SubTreeMap = 90b99ce0edSChau Ly std::map<std::string, 91b99ce0edSChau Ly std::map<std::string, std::vector<std::string>>>; 92b99ce0edSChau Ly SubTreeMap subTreeObjs = util::SDBusPlus::getSubTreeRaw( 93d80c8753SMatthew Barth util::SDBusPlus::getBus(), "/", confCompatIntf, 0); 94b99ce0edSChau Ly 95b99ce0edSChau Ly static std::vector<std::string> paths; 96b99ce0edSChau Ly for (auto& [path, serviceMap] : subTreeObjs) 97b99ce0edSChau Ly { 98b99ce0edSChau Ly // Only save objects under confCompatServ 99b99ce0edSChau Ly if (serviceMap.find(confCompatServ) != serviceMap.end()) 100b99ce0edSChau Ly { 101b99ce0edSChau Ly paths.emplace_back(path); 102b99ce0edSChau Ly } 103b99ce0edSChau Ly } 104d80c8753SMatthew Barth return paths; 105d80c8753SMatthew Barth } 106d80c8753SMatthew Barth 107d80c8753SMatthew Barth /** 10859850df3SMatt Spinler * @brief Constructor 10959850df3SMatt Spinler * 110b370ab3bSMatthew Barth * Attempts to set the list of compatible values from the compatible 111b370ab3bSMatthew Barth * interface and call the fan app's function to load its config file(s). If 112b370ab3bSMatthew Barth * the compatible interface is not found, it subscribes to the 113b370ab3bSMatthew Barth * interfacesAdded signal for that interface on the compatible service 114b370ab3bSMatthew Barth * defined above. 115b370ab3bSMatthew Barth * 116b370ab3bSMatthew Barth * @param[in] func - Fan app function to call to load its config file(s) 117b370ab3bSMatthew Barth */ JsonConfig(std::function<void ()> func)118b370ab3bSMatthew Barth JsonConfig(std::function<void()> func) : _loadFunc(func) 119b370ab3bSMatthew Barth { 1208d1193cbSMatthew Barth std::vector<std::string> compatObjPaths; 1218d1193cbSMatthew Barth 1223ea9ec2bSPatrick Williams _match = std::make_unique<sdbusplus::bus::match_t>( 123b370ab3bSMatthew Barth util::SDBusPlus::getBus(), 124b370ab3bSMatthew Barth sdbusplus::bus::match::rules::interfacesAdded() + 125b370ab3bSMatthew Barth sdbusplus::bus::match::rules::sender(confCompatServ), 126b370ab3bSMatthew Barth std::bind(&JsonConfig::compatIntfAdded, this, 127b370ab3bSMatthew Barth std::placeholders::_1)); 1281689cb6cSMatthew Barth 1298d1193cbSMatthew Barth try 1308d1193cbSMatthew Barth { 1318d1193cbSMatthew Barth compatObjPaths = getCompatObjPaths(); 1328d1193cbSMatthew Barth } 1338d1193cbSMatthew Barth catch (const util::DBusMethodError&) 1348d1193cbSMatthew Barth { 1358d1193cbSMatthew Barth // Compatible interface does not exist on any dbus object yet 1368d1193cbSMatthew Barth } 1378d1193cbSMatthew Barth 138b370ab3bSMatthew Barth if (!compatObjPaths.empty()) 139b370ab3bSMatthew Barth { 140b370ab3bSMatthew Barth for (auto& path : compatObjPaths) 141b370ab3bSMatthew Barth { 142b370ab3bSMatthew Barth try 143b370ab3bSMatthew Barth { 144b370ab3bSMatthew Barth // Retrieve json config compatible relative path 145b370ab3bSMatthew Barth // locations (last one found will be what's used if more 146b99ce0edSChau Ly // than one dbus object implementing the compatible 147b370ab3bSMatthew Barth // interface exists). 1481689cb6cSMatthew Barth _confCompatValues = 1491689cb6cSMatthew Barth util::SDBusPlus::getProperty<std::vector<std::string>>( 1501689cb6cSMatthew Barth util::SDBusPlus::getBus(), path, confCompatIntf, 151b370ab3bSMatthew Barth confCompatProp); 152b370ab3bSMatthew Barth } 153b370ab3bSMatthew Barth catch (const util::DBusError&) 154b370ab3bSMatthew Barth { 155b370ab3bSMatthew Barth // Compatible property unavailable on this dbus object 156b370ab3bSMatthew Barth // path's compatible interface, ignore 157b370ab3bSMatthew Barth } 158b370ab3bSMatthew Barth } 159b99ce0edSChau Ly try 160b99ce0edSChau Ly { 161b370ab3bSMatthew Barth _loadFunc(); 162b370ab3bSMatthew Barth } 163b99ce0edSChau Ly catch (const NoConfigFound&) 164b99ce0edSChau Ly { 165b99ce0edSChau Ly // The Decorator.Compatible interface is not unique to one 166b99ce0edSChau Ly // single object on DBus so this should not be treated as a 167b99ce0edSChau Ly // failure, wait for interfacesAdded signal. 168b99ce0edSChau Ly } 169b99ce0edSChau Ly } 170b370ab3bSMatthew Barth else 171b370ab3bSMatthew Barth { 172b370ab3bSMatthew Barth // Check if required config(s) are found not needing the 173b370ab3bSMatthew Barth // compatible interface, otherwise this is intended to catch the 174b370ab3bSMatthew Barth // exception thrown by the getConfFile function when the 175b370ab3bSMatthew Barth // required config file was not found. This would then result in 176b370ab3bSMatthew Barth // waiting for the compatible interfacesAdded signal 177b370ab3bSMatthew Barth try 178b370ab3bSMatthew Barth { 179b370ab3bSMatthew Barth _loadFunc(); 180b370ab3bSMatthew Barth } 1816eb603e8SMatthew Barth catch (const NoConfigFound&) 182b370ab3bSMatthew Barth { 183b370ab3bSMatthew Barth // Wait for compatible interfacesAdded signal 184b370ab3bSMatthew Barth } 185b370ab3bSMatthew Barth } 186b370ab3bSMatthew Barth } 187b370ab3bSMatthew Barth 188b370ab3bSMatthew Barth /** 189b370ab3bSMatthew Barth * @brief InterfacesAdded callback function for the compatible interface. 190b370ab3bSMatthew Barth * 191b370ab3bSMatthew Barth * @param[in] msg - The D-Bus message contents 192b370ab3bSMatthew Barth * 193b370ab3bSMatthew Barth * If the compatible interface is found, it uses the compatible property on 194b370ab3bSMatthew Barth * the interface to set the list of compatible values to be used when 195b370ab3bSMatthew Barth * attempting to get a configuration file. Once the list of compatible 196b370ab3bSMatthew Barth * values has been updated, it calls the load function. 197b370ab3bSMatthew Barth */ compatIntfAdded(sdbusplus::message_t & msg)198cb356d48SPatrick Williams void compatIntfAdded(sdbusplus::message_t& msg) 199b370ab3bSMatthew Barth { 200b99ce0edSChau Ly if (!_compatibleName.empty()) 201b99ce0edSChau Ly { 202b99ce0edSChau Ly // Do not process the interfaceAdded signal if one compatible name 203b99ce0edSChau Ly // has been successfully used to get config files 204b99ce0edSChau Ly return; 205b99ce0edSChau Ly } 206b370ab3bSMatthew Barth sdbusplus::message::object_path op; 207b370ab3bSMatthew Barth std::map<std::string, 208b370ab3bSMatthew Barth std::map<std::string, std::variant<std::vector<std::string>>>> 209b370ab3bSMatthew Barth intfProps; 210b370ab3bSMatthew Barth 211b370ab3bSMatthew Barth msg.read(op, intfProps); 212b370ab3bSMatthew Barth 213b370ab3bSMatthew Barth if (intfProps.find(confCompatIntf) == intfProps.end()) 214b370ab3bSMatthew Barth { 215b370ab3bSMatthew Barth return; 216b370ab3bSMatthew Barth } 217b370ab3bSMatthew Barth 218b370ab3bSMatthew Barth const auto& props = intfProps.at(confCompatIntf); 219b370ab3bSMatthew Barth // Only one dbus object with the compatible interface is used at a time 220b370ab3bSMatthew Barth _confCompatValues = 221b370ab3bSMatthew Barth std::get<std::vector<std::string>>(props.at(confCompatProp)); 222b370ab3bSMatthew Barth _loadFunc(); 223b370ab3bSMatthew Barth } 224b370ab3bSMatthew Barth 225b370ab3bSMatthew Barth /** 22611547c9bSMatthew Barth * Get the json configuration file. The first location found to contain 22711547c9bSMatthew Barth * the json config file for the given fan application is used from the 22811547c9bSMatthew Barth * following locations in order. 22911547c9bSMatthew Barth * 1.) From the confOverridePath location 230b67089bfSMatthew Barth * 2.) From the default confBasePath location 231b67089bfSMatthew Barth * 3.) From config file found using an entry from a list obtained from an 232fcbdc0e4SMatthew Barth * interface's property as a relative path extension on the base path where: 233fcbdc0e4SMatthew Barth * interface = Interface set in confCompatIntf with the property 234fcbdc0e4SMatthew Barth * property = Property set in confCompatProp containing a list of 235fcbdc0e4SMatthew Barth * subdirectories in priority order to find a config 23611547c9bSMatthew Barth * 23711547c9bSMatthew Barth * @brief Get the configuration file to be used 23811547c9bSMatthew Barth * 23911547c9bSMatthew Barth * @param[in] appName - The phosphor-fan-presence application name 24011547c9bSMatthew Barth * @param[in] fileName - Application's configuration file's name 241b599102dSMatthew Barth * @param[in] isOptional - Config file is optional, default to 'false' 24211547c9bSMatthew Barth * 24311547c9bSMatthew Barth * @return filesystem path 24411547c9bSMatthew Barth * The filesystem path to the configuration file to use 24511547c9bSMatthew Barth */ getConfFile(const std::string & appName,const std::string & fileName,bool isOptional=false)246808d7fe8SMike Capps static const fs::path getConfFile(const std::string& appName, 247b599102dSMatthew Barth const std::string& fileName, 248b599102dSMatthew Barth bool isOptional = false) 24911547c9bSMatthew Barth { 25011547c9bSMatthew Barth // Check override location 25111547c9bSMatthew Barth fs::path confFile = fs::path{confOverridePath} / appName / fileName; 25211547c9bSMatthew Barth if (fs::exists(confFile)) 25311547c9bSMatthew Barth { 25411547c9bSMatthew Barth return confFile; 25511547c9bSMatthew Barth } 25611547c9bSMatthew Barth 25759850df3SMatt Spinler // If the default file is there, use it 258fcbdc0e4SMatthew Barth confFile = fs::path{confBasePath} / appName / fileName; 25959850df3SMatt Spinler if (fs::exists(confFile)) 26059850df3SMatt Spinler { 26159850df3SMatt Spinler return confFile; 26259850df3SMatt Spinler } 263fcbdc0e4SMatthew Barth 264b67089bfSMatthew Barth // Look for a config file at each entry relative to the base 265b67089bfSMatthew Barth // path and use the first one found 266*dfddd648SPatrick Williams auto it = std::find_if( 267*dfddd648SPatrick Williams _confCompatValues.begin(), _confCompatValues.end(), 268b67089bfSMatthew Barth [&confFile, &appName, &fileName](const auto& value) { 269b67089bfSMatthew Barth confFile = fs::path{confBasePath} / appName / value / fileName; 270b99ce0edSChau Ly _compatibleName = value; 271b67089bfSMatthew Barth return fs::exists(confFile); 272b67089bfSMatthew Barth }); 273b67089bfSMatthew Barth if (it == _confCompatValues.end()) 274b67089bfSMatthew Barth { 275b67089bfSMatthew Barth confFile.clear(); 276b99ce0edSChau Ly _compatibleName.clear(); 277b67089bfSMatthew Barth } 278b67089bfSMatthew Barth 27959850df3SMatt Spinler if (confFile.empty() && !isOptional) 280a916f039SMatthew Barth { 2816eb603e8SMatthew Barth throw NoConfigFound(appName, fileName); 282a916f039SMatthew Barth } 28311547c9bSMatthew Barth 28411547c9bSMatthew Barth return confFile; 28511547c9bSMatthew Barth } 28611547c9bSMatthew Barth 28711547c9bSMatthew Barth /** 28811547c9bSMatthew Barth * @brief Load the JSON config file 28911547c9bSMatthew Barth * 29011547c9bSMatthew Barth * @param[in] confFile - File system path of the configuration file to load 29111547c9bSMatthew Barth * 29211547c9bSMatthew Barth * @return Parsed JSON object 29311547c9bSMatthew Barth * The parsed JSON configuration file object 29411547c9bSMatthew Barth */ load(const fs::path & confFile)29511547c9bSMatthew Barth static const json load(const fs::path& confFile) 29611547c9bSMatthew Barth { 29711547c9bSMatthew Barth std::ifstream file; 29811547c9bSMatthew Barth json jsonConf; 29911547c9bSMatthew Barth 300b599102dSMatthew Barth if (!confFile.empty() && fs::exists(confFile)) 30111547c9bSMatthew Barth { 3021826c730SMatthew Barth log<level::INFO>( 303fbf4703fSPatrick Williams std::format("Loading configuration from {}", confFile.string()) 3041826c730SMatthew Barth .c_str()); 30511547c9bSMatthew Barth file.open(confFile); 30611547c9bSMatthew Barth try 30711547c9bSMatthew Barth { 308de72d5d1SMatthew Barth // Enable ignoring `//` or `/* */` comments 309de72d5d1SMatthew Barth jsonConf = json::parse(file, nullptr, true, true); 31011547c9bSMatthew Barth } 311ddb773b2SPatrick Williams catch (const std::exception& e) 31211547c9bSMatthew Barth { 3131826c730SMatthew Barth log<level::ERR>( 314fbf4703fSPatrick Williams std::format( 3151826c730SMatthew Barth "Failed to parse JSON config file: {}, error: {}", 3161826c730SMatthew Barth confFile.string(), e.what()) 3171826c730SMatthew Barth .c_str()); 3181826c730SMatthew Barth throw std::runtime_error( 319fbf4703fSPatrick Williams std::format( 3201826c730SMatthew Barth "Failed to parse JSON config file: {}, error: {}", 3211826c730SMatthew Barth confFile.string(), e.what()) 3221826c730SMatthew Barth .c_str()); 32311547c9bSMatthew Barth } 32411547c9bSMatthew Barth } 32511547c9bSMatthew Barth else 32611547c9bSMatthew Barth { 327fbf4703fSPatrick Williams log<level::ERR>(std::format("Unable to open JSON config file: {}", 3281826c730SMatthew Barth confFile.string()) 3291826c730SMatthew Barth .c_str()); 3301826c730SMatthew Barth throw std::runtime_error( 331fbf4703fSPatrick Williams std::format("Unable to open JSON config file: {}", 3321826c730SMatthew Barth confFile.string()) 3331826c730SMatthew Barth .c_str()); 33411547c9bSMatthew Barth } 33511547c9bSMatthew Barth 33611547c9bSMatthew Barth return jsonConf; 33711547c9bSMatthew Barth } 33859850df3SMatt Spinler 339d4c7fb7eSMatt Spinler /** 340d4c7fb7eSMatt Spinler * @brief Return the compatible values property 341d4c7fb7eSMatt Spinler * 342d4c7fb7eSMatt Spinler * @return const std::vector<std::string>& - The values 343d4c7fb7eSMatt Spinler */ getCompatValues()344d4c7fb7eSMatt Spinler static const std::vector<std::string>& getCompatValues() 345d4c7fb7eSMatt Spinler { 346d4c7fb7eSMatt Spinler return _confCompatValues; 347d4c7fb7eSMatt Spinler } 348d4c7fb7eSMatt Spinler 34959850df3SMatt Spinler private: 350b370ab3bSMatthew Barth /* Load function to call for a fan app to load its config file(s). */ 351b370ab3bSMatthew Barth std::function<void()> _loadFunc; 352b370ab3bSMatthew Barth 35359850df3SMatt Spinler /** 35459850df3SMatt Spinler * @brief The interfacesAdded match that is used to wait 355b99ce0edSChau Ly * for the Inventory.Decorator.Compatible interface to show up. 35659850df3SMatt Spinler */ 3573ea9ec2bSPatrick Williams std::unique_ptr<sdbusplus::bus::match_t> _match; 358b67089bfSMatthew Barth 359b67089bfSMatthew Barth /** 360b67089bfSMatthew Barth * @brief List of compatible values from the compatible interface 361b67089bfSMatthew Barth * 362b67089bfSMatthew Barth * Only supports a single instance of the compatible interface on a dbus 363b67089bfSMatthew Barth * object. If more than one dbus object exists with the compatible 364b67089bfSMatthew Barth * interface, the last one found will be the list of compatible values used. 365b67089bfSMatthew Barth */ 366b67089bfSMatthew Barth inline static std::vector<std::string> _confCompatValues; 367b99ce0edSChau Ly 368b99ce0edSChau Ly /** 369b99ce0edSChau Ly * @brief The compatible value that is currently used to load configuration 370b99ce0edSChau Ly * 371b99ce0edSChau Ly * The value extracted from the achieved property value list that is used 372b99ce0edSChau Ly * as a sub-folder to append to the configuration location and really 373b99ce0edSChau Ly * contains the configruation files 374b99ce0edSChau Ly */ 375b99ce0edSChau Ly 376b99ce0edSChau Ly inline static std::string _compatibleName; 37711547c9bSMatthew Barth }; 37811547c9bSMatthew Barth 37911547c9bSMatthew Barth } // namespace phosphor::fan 380