1 /** 2 * Copyright © 2020 IBM Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 #pragma once 17 18 #include "sdbusplus.hpp" 19 20 #include <fmt/format.h> 21 22 #include <nlohmann/json.hpp> 23 #include <phosphor-logging/log.hpp> 24 #include <sdbusplus/bus.hpp> 25 #include <sdeventplus/source/signal.hpp> 26 27 #include <filesystem> 28 #include <fstream> 29 30 namespace phosphor::fan 31 { 32 33 namespace fs = std::filesystem; 34 using json = nlohmann::json; 35 using namespace phosphor::logging; 36 37 constexpr auto confOverridePath = "/etc/phosphor-fan-presence"; 38 constexpr auto confBasePath = "/usr/share/phosphor-fan-presence"; 39 constexpr auto confCompatIntf = 40 "xyz.openbmc_project.Configuration.IBMCompatibleSystem"; 41 constexpr auto confCompatProp = "Names"; 42 43 class JsonConfig 44 { 45 public: 46 using ConfFileReadyFunc = std::function<void(const std::string&)>; 47 48 /** 49 * @brief Constructor 50 * 51 * Looks for the JSON config file. If it can't find one, then it 52 * will watch entity-manager for the IBMCompatibleSystem interface 53 * to show up and then use that data to try again. If the config 54 * file is initially present, the callback function is executed 55 * with the path to the file. 56 * 57 * @param[in] bus - The dbus bus object 58 * @param[in] appName - The appName portion of the config file path 59 * @param[in] fileName - Application's configuration file's name 60 * @param[in] func - The function to call when the config file 61 * is found. 62 */ 63 JsonConfig(sdbusplus::bus::bus& bus, const std::string& appName, 64 const std::string& fileName, ConfFileReadyFunc func) : 65 _appName(appName), 66 _fileName(fileName), _readyFunc(func) 67 { 68 _match = std::make_unique<sdbusplus::server::match::match>( 69 bus, 70 sdbusplus::bus::match::rules::interfacesAdded() + 71 sdbusplus::bus::match::rules::sender( 72 "xyz.openbmc_project.EntityManager"), 73 std::bind(&JsonConfig::ifacesAddedCallback, this, 74 std::placeholders::_1)); 75 try 76 { 77 _confFile = getConfFile(bus, _appName, _fileName); 78 } 79 catch (const std::runtime_error& e) 80 { 81 // No conf file found, so let the interfacesAdded 82 // match callback handle finding it. 83 } 84 85 if (!_confFile.empty()) 86 { 87 _match.reset(); 88 _readyFunc(_confFile); 89 } 90 } 91 92 /** 93 * @brief The interfacesAdded callback function that looks for 94 * the IBMCompatibleSystem interface. If it finds it, 95 * it uses the Names property in the interface to find 96 * the JSON config file to use. If it finds one, it calls 97 * the _readyFunc function with the config file path. 98 * 99 * @param[in] msg - The D-Bus message contents 100 */ 101 void ifacesAddedCallback(sdbusplus::message::message& msg) 102 { 103 sdbusplus::message::object_path path; 104 std::map<std::string, 105 std::map<std::string, std::variant<std::vector<std::string>>>> 106 interfaces; 107 108 msg.read(path, interfaces); 109 110 if (interfaces.find(confCompatIntf) == interfaces.end()) 111 { 112 return; 113 } 114 115 const auto& properties = interfaces.at(confCompatIntf); 116 auto names = 117 std::get<std::vector<std::string>>(properties.at(confCompatProp)); 118 119 auto it = 120 std::find_if(names.begin(), names.end(), [this](auto const& name) { 121 auto confFile = 122 fs::path{confBasePath} / _appName / name / _fileName; 123 if (fs::exists(confFile)) 124 { 125 _confFile = confFile; 126 return true; 127 } 128 return false; 129 }); 130 131 if (it != names.end()) 132 { 133 _readyFunc(_confFile); 134 _match.reset(); 135 } 136 } 137 138 /** 139 * Get the json configuration file. The first location found to contain 140 * the json config file for the given fan application is used from the 141 * following locations in order. 142 * 1.) From the confOverridePath location 143 * 2.) From config file found using an entry from a list obtained from an 144 * interface's property as a relative path extension on the base path where: 145 * interface = Interface set in confCompatIntf with the property 146 * property = Property set in confCompatProp containing a list of 147 * subdirectories in priority order to find a config 148 * 3.) *DEFAULT* - From the confBasePath location 149 * 150 * @brief Get the configuration file to be used 151 * 152 * @param[in] bus - The dbus bus object 153 * @param[in] appName - The phosphor-fan-presence application name 154 * @param[in] fileName - Application's configuration file's name 155 * @param[in] isOptional - Config file is optional, default to 'false' 156 * 157 * @return filesystem path 158 * The filesystem path to the configuration file to use 159 */ 160 static const fs::path getConfFile(sdbusplus::bus::bus& bus, 161 const std::string& appName, 162 const std::string& fileName, 163 bool isOptional = false) 164 { 165 // Check override location 166 fs::path confFile = fs::path{confOverridePath} / appName / fileName; 167 if (fs::exists(confFile)) 168 { 169 return confFile; 170 } 171 172 // If the default file is there, use it 173 confFile = fs::path{confBasePath} / appName / fileName; 174 if (fs::exists(confFile)) 175 { 176 return confFile; 177 } 178 confFile.clear(); 179 180 // Get all objects implementing the compatible interface 181 auto objects = 182 util::SDBusPlus::getSubTreePathsRaw(bus, "/", confCompatIntf, 0); 183 for (auto& path : objects) 184 { 185 try 186 { 187 // Retrieve json config compatible relative path locations 188 auto confCompatValue = 189 util::SDBusPlus::getProperty<std::vector<std::string>>( 190 bus, path, confCompatIntf, confCompatProp); 191 // Look for a config file at each entry relative to the base 192 // path and use the first one found 193 auto it = std::find_if( 194 confCompatValue.begin(), confCompatValue.end(), 195 [&confFile, &appName, &fileName](auto const& entry) { 196 confFile = 197 fs::path{confBasePath} / appName / entry / fileName; 198 return fs::exists(confFile); 199 }); 200 if (it != confCompatValue.end()) 201 { 202 // Use the first config file found at a listed location 203 break; 204 } 205 confFile.clear(); 206 } 207 catch (const util::DBusError&) 208 { 209 // Property unavailable on object. 210 } 211 } 212 213 if (confFile.empty() && !isOptional) 214 { 215 throw std::runtime_error("No JSON config file found"); 216 } 217 218 return confFile; 219 } 220 221 /** 222 * @brief Load the JSON config file 223 * 224 * @param[in] confFile - File system path of the configuration file to load 225 * 226 * @return Parsed JSON object 227 * The parsed JSON configuration file object 228 */ 229 static const json load(const fs::path& confFile) 230 { 231 std::ifstream file; 232 json jsonConf; 233 234 if (!confFile.empty() && fs::exists(confFile)) 235 { 236 log<level::INFO>( 237 fmt::format("Loading configuration from {}", confFile.string()) 238 .c_str()); 239 file.open(confFile); 240 try 241 { 242 jsonConf = json::parse(file); 243 } 244 catch (std::exception& e) 245 { 246 log<level::ERR>( 247 fmt::format( 248 "Failed to parse JSON config file: {}, error: {}", 249 confFile.string(), e.what()) 250 .c_str()); 251 throw std::runtime_error( 252 fmt::format( 253 "Failed to parse JSON config file: {}, error: {}", 254 confFile.string(), e.what()) 255 .c_str()); 256 } 257 } 258 else 259 { 260 log<level::ERR>(fmt::format("Unable to open JSON config file: {}", 261 confFile.string()) 262 .c_str()); 263 throw std::runtime_error( 264 fmt::format("Unable to open JSON config file: {}", 265 confFile.string()) 266 .c_str()); 267 } 268 269 return jsonConf; 270 } 271 272 private: 273 /** 274 * @brief The 'appName' portion of the config file path. 275 */ 276 const std::string _appName; 277 278 /** 279 * @brief The config file name. 280 */ 281 const std::string _fileName; 282 283 /** 284 * @brief The function to call when the config file is available. 285 */ 286 ConfFileReadyFunc _readyFunc; 287 288 /** 289 * @brief The JSON config file 290 */ 291 fs::path _confFile; 292 293 /** 294 * @brief The interfacesAdded match that is used to wait 295 * for the IBMCompatibleSystem interface to show up. 296 */ 297 std::unique_ptr<sdbusplus::server::match::match> _match; 298 }; 299 300 } // namespace phosphor::fan 301