blob: 129e2221fb45e9693b4bdcb5ed8006498fc883a2 [file] [log] [blame]
Matthew Barth11547c92020-05-05 10:34:27 -05001/**
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
Matthew Barth1826c732020-08-28 08:40:59 -050020#include <fmt/format.h>
21
Matthew Barth11547c92020-05-05 10:34:27 -050022#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
30namespace phosphor::fan
31{
32
33namespace fs = std::filesystem;
34using json = nlohmann::json;
35using namespace phosphor::logging;
36
37constexpr auto confOverridePath = "/etc/phosphor-fan-presence";
38constexpr auto confBasePath = "/usr/share/phosphor-fan-presence";
Matthew Barthb370ab32021-06-10 14:38:02 -050039constexpr auto confCompatServ = "xyz.openbmc_project.EntityManager";
Matthew Barthfcbdc0e2020-10-28 14:11:07 -050040constexpr auto confCompatIntf =
41 "xyz.openbmc_project.Configuration.IBMCompatibleSystem";
42constexpr auto confCompatProp = "Names";
Matthew Barth11547c92020-05-05 10:34:27 -050043
44class JsonConfig
45{
46 public:
Matt Spinler59850df2021-01-06 16:56:06 -060047 /**
Matthew Barthd80c8752021-06-01 14:46:02 -050048 * @brief Get the object paths with the compatible interface
49 *
50 * Retrieve all the object paths implementing the compatible interface for
51 * configuration file loading.
52 */
53 static auto& getCompatObjPaths() __attribute__((pure))
54 {
55 static auto paths = util::SDBusPlus::getSubTreePathsRaw(
56 util::SDBusPlus::getBus(), "/", confCompatIntf, 0);
57 return paths;
58 }
59
60 /**
Matt Spinler59850df2021-01-06 16:56:06 -060061 * @brief Constructor
62 *
Matthew Barthb370ab32021-06-10 14:38:02 -050063 * Attempts to set the list of compatible values from the compatible
64 * interface and call the fan app's function to load its config file(s). If
65 * the compatible interface is not found, it subscribes to the
66 * interfacesAdded signal for that interface on the compatible service
67 * defined above.
68 *
69 * @param[in] func - Fan app function to call to load its config file(s)
70 */
71 JsonConfig(std::function<void()> func) : _loadFunc(func)
72 {
73 _match = std::make_unique<sdbusplus::server::match::match>(
74 util::SDBusPlus::getBus(),
75 sdbusplus::bus::match::rules::interfacesAdded() +
76 sdbusplus::bus::match::rules::sender(confCompatServ),
77 std::bind(&JsonConfig::compatIntfAdded, this,
78 std::placeholders::_1));
79 try
80 {
81 auto compatObjPaths = getCompatObjPaths();
82 if (!compatObjPaths.empty())
83 {
84 for (auto& path : compatObjPaths)
85 {
86 try
87 {
88 // Retrieve json config compatible relative path
89 // locations (last one found will be what's used if more
90 // than one dbus object implementing the comptaible
91 // interface exists).
92 _confCompatValues = util::SDBusPlus::getProperty<
93 std::vector<std::string>>(util::SDBusPlus::getBus(),
94 path, confCompatIntf,
95 confCompatProp);
96 }
97 catch (const util::DBusError&)
98 {
99 // Compatible property unavailable on this dbus object
100 // path's compatible interface, ignore
101 }
102 }
103 _loadFunc();
104 _match.reset();
105 }
106 else
107 {
108 // Check if required config(s) are found not needing the
109 // compatible interface, otherwise this is intended to catch the
110 // exception thrown by the getConfFile function when the
111 // required config file was not found. This would then result in
112 // waiting for the compatible interfacesAdded signal
113 try
114 {
115 _loadFunc();
116 _match.reset();
117 }
118 catch (const std::runtime_error&)
119 {
120 // Wait for compatible interfacesAdded signal
121 }
122 }
123 }
124 catch (const std::runtime_error&)
125 {
126 // Wait for compatible interfacesAdded signal
127 }
128 }
129
130 /**
Matthew Barthb370ab32021-06-10 14:38:02 -0500131 * @brief InterfacesAdded callback function for the compatible interface.
132 *
133 * @param[in] msg - The D-Bus message contents
134 *
135 * If the compatible interface is found, it uses the compatible property on
136 * the interface to set the list of compatible values to be used when
137 * attempting to get a configuration file. Once the list of compatible
138 * values has been updated, it calls the load function.
139 */
140 void compatIntfAdded(sdbusplus::message::message& msg)
141 {
142 sdbusplus::message::object_path op;
143 std::map<std::string,
144 std::map<std::string, std::variant<std::vector<std::string>>>>
145 intfProps;
146
147 msg.read(op, intfProps);
148
149 if (intfProps.find(confCompatIntf) == intfProps.end())
150 {
151 return;
152 }
153
154 const auto& props = intfProps.at(confCompatIntf);
155 // Only one dbus object with the compatible interface is used at a time
156 _confCompatValues =
157 std::get<std::vector<std::string>>(props.at(confCompatProp));
158 _loadFunc();
159 }
160
161 /**
Matthew Barth11547c92020-05-05 10:34:27 -0500162 * Get the json configuration file. The first location found to contain
163 * the json config file for the given fan application is used from the
164 * following locations in order.
165 * 1.) From the confOverridePath location
Matthew Barthb67089b2021-06-10 14:32:00 -0500166 * 2.) From the default confBasePath location
167 * 3.) From config file found using an entry from a list obtained from an
Matthew Barthfcbdc0e2020-10-28 14:11:07 -0500168 * interface's property as a relative path extension on the base path where:
169 * interface = Interface set in confCompatIntf with the property
170 * property = Property set in confCompatProp containing a list of
171 * subdirectories in priority order to find a config
Matthew Barth11547c92020-05-05 10:34:27 -0500172 *
173 * @brief Get the configuration file to be used
174 *
175 * @param[in] bus - The dbus bus object
176 * @param[in] appName - The phosphor-fan-presence application name
177 * @param[in] fileName - Application's configuration file's name
Matthew Barthb5991022020-08-05 11:08:50 -0500178 * @param[in] isOptional - Config file is optional, default to 'false'
Matthew Barth11547c92020-05-05 10:34:27 -0500179 *
180 * @return filesystem path
181 * The filesystem path to the configuration file to use
182 */
183 static const fs::path getConfFile(sdbusplus::bus::bus& bus,
184 const std::string& appName,
Matthew Barthb5991022020-08-05 11:08:50 -0500185 const std::string& fileName,
186 bool isOptional = false)
Matthew Barth11547c92020-05-05 10:34:27 -0500187 {
188 // Check override location
189 fs::path confFile = fs::path{confOverridePath} / appName / fileName;
190 if (fs::exists(confFile))
191 {
192 return confFile;
193 }
194
Matt Spinler59850df2021-01-06 16:56:06 -0600195 // If the default file is there, use it
Matthew Barthfcbdc0e2020-10-28 14:11:07 -0500196 confFile = fs::path{confBasePath} / appName / fileName;
Matt Spinler59850df2021-01-06 16:56:06 -0600197 if (fs::exists(confFile))
198 {
199 return confFile;
200 }
Matthew Barthfcbdc0e2020-10-28 14:11:07 -0500201
Matthew Barthb67089b2021-06-10 14:32:00 -0500202 // Look for a config file at each entry relative to the base
203 // path and use the first one found
204 auto it = std::find_if(
205 _confCompatValues.begin(), _confCompatValues.end(),
206 [&confFile, &appName, &fileName](const auto& value) {
207 confFile = fs::path{confBasePath} / appName / value / fileName;
208 return fs::exists(confFile);
209 });
210 if (it == _confCompatValues.end())
211 {
212 confFile.clear();
213 }
214
215 if (!isOptional && confFile.empty() && !_confCompatValues.empty())
Matt Spinler4031f102021-04-22 16:42:20 -0500216 {
217 log<level::ERR>(fmt::format("Could not find fan {} conf file {}",
218 appName, fileName)
219 .c_str());
220 }
221
Matt Spinler59850df2021-01-06 16:56:06 -0600222 if (confFile.empty() && !isOptional)
Matthew Barth11547c92020-05-05 10:34:27 -0500223 {
Matt Spinler59850df2021-01-06 16:56:06 -0600224 throw std::runtime_error("No JSON config file found");
Matthew Barthb5991022020-08-05 11:08:50 -0500225 }
Matthew Barth11547c92020-05-05 10:34:27 -0500226
227 return confFile;
228 }
229
230 /**
231 * @brief Load the JSON config file
232 *
233 * @param[in] confFile - File system path of the configuration file to load
234 *
235 * @return Parsed JSON object
236 * The parsed JSON configuration file object
237 */
238 static const json load(const fs::path& confFile)
239 {
240 std::ifstream file;
241 json jsonConf;
242
Matthew Barthb5991022020-08-05 11:08:50 -0500243 if (!confFile.empty() && fs::exists(confFile))
Matthew Barth11547c92020-05-05 10:34:27 -0500244 {
Matthew Barth1826c732020-08-28 08:40:59 -0500245 log<level::INFO>(
246 fmt::format("Loading configuration from {}", confFile.string())
247 .c_str());
Matthew Barth11547c92020-05-05 10:34:27 -0500248 file.open(confFile);
249 try
250 {
Matthew Barthde72d5d2021-07-16 10:39:42 -0500251 // Enable ignoring `//` or `/* */` comments
252 jsonConf = json::parse(file, nullptr, true, true);
Matthew Barth11547c92020-05-05 10:34:27 -0500253 }
254 catch (std::exception& e)
255 {
Matthew Barth1826c732020-08-28 08:40:59 -0500256 log<level::ERR>(
257 fmt::format(
258 "Failed to parse JSON config file: {}, error: {}",
259 confFile.string(), e.what())
260 .c_str());
261 throw std::runtime_error(
262 fmt::format(
263 "Failed to parse JSON config file: {}, error: {}",
264 confFile.string(), e.what())
265 .c_str());
Matthew Barth11547c92020-05-05 10:34:27 -0500266 }
267 }
268 else
269 {
Matthew Barth1826c732020-08-28 08:40:59 -0500270 log<level::ERR>(fmt::format("Unable to open JSON config file: {}",
271 confFile.string())
272 .c_str());
273 throw std::runtime_error(
274 fmt::format("Unable to open JSON config file: {}",
275 confFile.string())
276 .c_str());
Matthew Barth11547c92020-05-05 10:34:27 -0500277 }
278
279 return jsonConf;
280 }
Matt Spinler59850df2021-01-06 16:56:06 -0600281
282 private:
Matthew Barthb370ab32021-06-10 14:38:02 -0500283 /* Load function to call for a fan app to load its config file(s). */
284 std::function<void()> _loadFunc;
285
Matt Spinler59850df2021-01-06 16:56:06 -0600286 /**
Matt Spinler59850df2021-01-06 16:56:06 -0600287 * @brief The interfacesAdded match that is used to wait
288 * for the IBMCompatibleSystem interface to show up.
289 */
290 std::unique_ptr<sdbusplus::server::match::match> _match;
Matthew Barthb67089b2021-06-10 14:32:00 -0500291
292 /**
293 * @brief List of compatible values from the compatible interface
294 *
295 * Only supports a single instance of the compatible interface on a dbus
296 * object. If more than one dbus object exists with the compatible
297 * interface, the last one found will be the list of compatible values used.
298 */
299 inline static std::vector<std::string> _confCompatValues;
Matthew Barth11547c92020-05-05 10:34:27 -0500300};
301
302} // namespace phosphor::fan