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 /** 47 * Get the json configuration file. The first location found to contain 48 * the json config file for the given fan application is used from the 49 * following locations in order. 50 * 1.) From the confOverridePath location 51 * 2.) From config file found using an entry from a list obtained from an 52 * interface's property as a relative path extension on the base path where: 53 * interface = Interface set in confCompatIntf with the property 54 * property = Property set in confCompatProp containing a list of 55 * subdirectories in priority order to find a config 56 * 3.) *DEFAULT* - From the confBasePath location 57 * 58 * @brief Get the configuration file to be used 59 * 60 * @param[in] bus - The dbus bus object 61 * @param[in] appName - The phosphor-fan-presence application name 62 * @param[in] fileName - Application's configuration file's name 63 * @param[in] isOptional - Config file is optional, default to 'false' 64 * 65 * @return filesystem path 66 * The filesystem path to the configuration file to use 67 */ 68 static const fs::path getConfFile(sdbusplus::bus::bus& bus, 69 const std::string& appName, 70 const std::string& fileName, 71 bool isOptional = false) 72 { 73 // Check override location 74 fs::path confFile = fs::path{confOverridePath} / appName / fileName; 75 if (fs::exists(confFile)) 76 { 77 return confFile; 78 } 79 80 // Default base path used if no config file found at any locations 81 // provided on dbus objects with the compatible interface 82 confFile = fs::path{confBasePath} / appName / fileName; 83 84 // Get all objects implementing the compatible interface 85 auto objects = 86 util::SDBusPlus::getSubTreePathsRaw(bus, "/", confCompatIntf, 0); 87 for (auto& path : objects) 88 { 89 try 90 { 91 // Retrieve json config compatible relative path locations 92 auto confCompatValue = 93 util::SDBusPlus::getProperty<std::vector<std::string>>( 94 bus, path, confCompatIntf, confCompatProp); 95 // Look for a config file at each entry relative to the base 96 // path and use the first one found 97 auto it = std::find_if( 98 confCompatValue.begin(), confCompatValue.end(), 99 [&confFile, &appName, &fileName](auto const& entry) { 100 confFile = 101 fs::path{confBasePath} / appName / entry / fileName; 102 return fs::exists(confFile); 103 }); 104 if (it != confCompatValue.end()) 105 { 106 // Use the first config file found at a listed location 107 break; 108 } 109 } 110 catch (const util::DBusError&) 111 { 112 // Property unavailable on object. 113 // Set to default base path and continue to check next object 114 } 115 confFile = fs::path{confBasePath} / appName / fileName; 116 } 117 118 if (!fs::exists(confFile)) 119 { 120 if (!isOptional) 121 { 122 log<level::ERR>( 123 fmt::format("No JSON config file found. Default file: {}", 124 confFile.string()) 125 .c_str()); 126 throw std::runtime_error( 127 fmt::format("No JSON config file found. Default file: {}", 128 confFile.string()) 129 .c_str()); 130 } 131 else 132 { 133 confFile.clear(); 134 } 135 } 136 137 return confFile; 138 } 139 140 /** 141 * @brief Load the JSON config file 142 * 143 * @param[in] confFile - File system path of the configuration file to load 144 * 145 * @return Parsed JSON object 146 * The parsed JSON configuration file object 147 */ 148 static const json load(const fs::path& confFile) 149 { 150 std::ifstream file; 151 json jsonConf; 152 153 if (!confFile.empty() && fs::exists(confFile)) 154 { 155 log<level::INFO>( 156 fmt::format("Loading configuration from {}", confFile.string()) 157 .c_str()); 158 file.open(confFile); 159 try 160 { 161 jsonConf = json::parse(file); 162 } 163 catch (std::exception& e) 164 { 165 log<level::ERR>( 166 fmt::format( 167 "Failed to parse JSON config file: {}, error: {}", 168 confFile.string(), e.what()) 169 .c_str()); 170 throw std::runtime_error( 171 fmt::format( 172 "Failed to parse JSON config file: {}, error: {}", 173 confFile.string(), e.what()) 174 .c_str()); 175 } 176 } 177 else 178 { 179 log<level::ERR>(fmt::format("Unable to open JSON config file: {}", 180 confFile.string()) 181 .c_str()); 182 throw std::runtime_error( 183 fmt::format("Unable to open JSON config file: {}", 184 confFile.string()) 185 .c_str()); 186 } 187 188 return jsonConf; 189 } 190 }; 191 192 } // namespace phosphor::fan 193