blob: 52dd5055a9779159cceeddf73cfa3b0bc1f7620c [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
20#include <nlohmann/json.hpp>
21#include <phosphor-logging/log.hpp>
22#include <sdbusplus/bus.hpp>
23#include <sdeventplus/source/signal.hpp>
24
25#include <filesystem>
Patrick Williamsfbf47032023-07-17 12:27:34 -050026#include <format>
Matthew Barth11547c92020-05-05 10:34:27 -050027#include <fstream>
28
29namespace phosphor::fan
30{
31
32namespace fs = std::filesystem;
33using json = nlohmann::json;
34using namespace phosphor::logging;
35
36constexpr auto confOverridePath = "/etc/phosphor-fan-presence";
37constexpr auto confBasePath = "/usr/share/phosphor-fan-presence";
Matthew Barthb370ab32021-06-10 14:38:02 -050038constexpr auto confCompatServ = "xyz.openbmc_project.EntityManager";
Matthew Barthfcbdc0e2020-10-28 14:11:07 -050039constexpr auto confCompatIntf =
Chau Lyb99ce0e2023-11-30 08:06:00 +000040 "xyz.openbmc_project.Inventory.Decorator.Compatible";
Matthew Barthfcbdc0e2020-10-28 14:11:07 -050041constexpr auto confCompatProp = "Names";
Matthew Barth11547c92020-05-05 10:34:27 -050042
Matthew Barth6eb603e2021-09-01 09:01:57 -050043/**
44 * @class NoConfigFound - A no JSON configuration found exception
45 *
46 * A no JSON configuration found exception that is used to denote that a JSON
47 * configuration has not been found yet.
48 */
49class NoConfigFound : public std::runtime_error
50{
51 public:
52 NoConfigFound() = delete;
53 NoConfigFound(const NoConfigFound&) = delete;
54 NoConfigFound(NoConfigFound&&) = delete;
55 NoConfigFound& operator=(const NoConfigFound&) = delete;
56 NoConfigFound& operator=(NoConfigFound&&) = delete;
57 ~NoConfigFound() = default;
58
59 /**
60 * @brief No JSON configuration found exception object
61 *
62 * When attempting to find the JSON configuration file(s), a NoConfigFound
63 * exception can be thrown to denote that at that time finding/loading the
64 * JSON configuration file(s) for a fan application failed. Details on what
65 * application and JSON configuration file that failed to be found will be
66 * logged resulting in the application being terminated.
67 *
68 * @param[in] details - Additional details
69 */
70 NoConfigFound(const std::string& appName, const std::string& fileName) :
Patrick Williamsfbf47032023-07-17 12:27:34 -050071 std::runtime_error(std::format("JSON configuration not found [Could "
Matthew Barth6eb603e2021-09-01 09:01:57 -050072 "not find fan {} conf file {}]",
73 appName, fileName)
74 .c_str())
75 {}
76};
77
Matthew Barth11547c92020-05-05 10:34:27 -050078class JsonConfig
79{
80 public:
Matt Spinler59850df2021-01-06 16:56:06 -060081 /**
Matthew Barthd80c8752021-06-01 14:46:02 -050082 * @brief Get the object paths with the compatible interface
83 *
84 * Retrieve all the object paths implementing the compatible interface for
85 * configuration file loading.
86 */
Chau Lyb99ce0e2023-11-30 08:06:00 +000087 std::vector<std::string>& getCompatObjPaths()
Matthew Barthd80c8752021-06-01 14:46:02 -050088 {
Chau Lyb99ce0e2023-11-30 08:06:00 +000089 using SubTreeMap =
90 std::map<std::string,
91 std::map<std::string, std::vector<std::string>>>;
92 SubTreeMap subTreeObjs = util::SDBusPlus::getSubTreeRaw(
Matthew Barthd80c8752021-06-01 14:46:02 -050093 util::SDBusPlus::getBus(), "/", confCompatIntf, 0);
Chau Lyb99ce0e2023-11-30 08:06:00 +000094
95 static std::vector<std::string> paths;
96 for (auto& [path, serviceMap] : subTreeObjs)
97 {
98 // Only save objects under confCompatServ
99 if (serviceMap.find(confCompatServ) != serviceMap.end())
100 {
101 paths.emplace_back(path);
102 }
103 }
Matthew Barthd80c8752021-06-01 14:46:02 -0500104 return paths;
105 }
106
107 /**
Matt Spinler59850df2021-01-06 16:56:06 -0600108 * @brief Constructor
109 *
Matthew Barthb370ab32021-06-10 14:38:02 -0500110 * Attempts to set the list of compatible values from the compatible
111 * interface and call the fan app's function to load its config file(s). If
112 * the compatible interface is not found, it subscribes to the
113 * interfacesAdded signal for that interface on the compatible service
114 * defined above.
115 *
116 * @param[in] func - Fan app function to call to load its config file(s)
117 */
118 JsonConfig(std::function<void()> func) : _loadFunc(func)
119 {
Matthew Barth8d1193c2021-09-14 10:36:06 -0500120 std::vector<std::string> compatObjPaths;
121
Patrick Williams3ea9ec22021-11-19 12:21:08 -0600122 _match = std::make_unique<sdbusplus::bus::match_t>(
Matthew Barthb370ab32021-06-10 14:38:02 -0500123 util::SDBusPlus::getBus(),
124 sdbusplus::bus::match::rules::interfacesAdded() +
125 sdbusplus::bus::match::rules::sender(confCompatServ),
126 std::bind(&JsonConfig::compatIntfAdded, this,
127 std::placeholders::_1));
Matthew Barth1689cb62021-08-27 12:34:36 -0500128
Matthew Barth8d1193c2021-09-14 10:36:06 -0500129 try
130 {
131 compatObjPaths = getCompatObjPaths();
132 }
133 catch (const util::DBusMethodError&)
134 {
135 // Compatible interface does not exist on any dbus object yet
136 }
137
Matthew Barth1689cb62021-08-27 12:34:36 -0500138 if (!compatObjPaths.empty())
Matthew Barthb370ab32021-06-10 14:38:02 -0500139 {
Matthew Barth1689cb62021-08-27 12:34:36 -0500140 for (auto& path : compatObjPaths)
Matthew Barthb370ab32021-06-10 14:38:02 -0500141 {
Matthew Barth1689cb62021-08-27 12:34:36 -0500142 try
Matthew Barthb370ab32021-06-10 14:38:02 -0500143 {
Matthew Barth1689cb62021-08-27 12:34:36 -0500144 // Retrieve json config compatible relative path
145 // locations (last one found will be what's used if more
Chau Lyb99ce0e2023-11-30 08:06:00 +0000146 // than one dbus object implementing the compatible
Matthew Barth1689cb62021-08-27 12:34:36 -0500147 // interface exists).
148 _confCompatValues =
149 util::SDBusPlus::getProperty<std::vector<std::string>>(
150 util::SDBusPlus::getBus(), path, confCompatIntf,
151 confCompatProp);
Matthew Barthb370ab32021-06-10 14:38:02 -0500152 }
Matthew Barth1689cb62021-08-27 12:34:36 -0500153 catch (const util::DBusError&)
154 {
155 // Compatible property unavailable on this dbus object
156 // path's compatible interface, ignore
157 }
158 }
Chau Lyb99ce0e2023-11-30 08:06:00 +0000159 try
160 {
161 _loadFunc();
162 }
163 catch (const NoConfigFound&)
164 {
165 // The Decorator.Compatible interface is not unique to one
166 // single object on DBus so this should not be treated as a
167 // failure, wait for interfacesAdded signal.
168 }
Matthew Barth1689cb62021-08-27 12:34:36 -0500169 }
170 else
171 {
172 // Check if required config(s) are found not needing the
173 // compatible interface, otherwise this is intended to catch the
174 // exception thrown by the getConfFile function when the
175 // required config file was not found. This would then result in
176 // waiting for the compatible interfacesAdded signal
177 try
178 {
Matthew Barthb370ab32021-06-10 14:38:02 -0500179 _loadFunc();
Matthew Barthb370ab32021-06-10 14:38:02 -0500180 }
Matthew Barth6eb603e2021-09-01 09:01:57 -0500181 catch (const NoConfigFound&)
Matthew Barthb370ab32021-06-10 14:38:02 -0500182 {
Matthew Barth1689cb62021-08-27 12:34:36 -0500183 // Wait for compatible interfacesAdded signal
Matthew Barthb370ab32021-06-10 14:38:02 -0500184 }
185 }
Matthew Barthb370ab32021-06-10 14:38:02 -0500186 }
187
188 /**
Matthew Barthb370ab32021-06-10 14:38:02 -0500189 * @brief InterfacesAdded callback function for the compatible interface.
190 *
191 * @param[in] msg - The D-Bus message contents
192 *
193 * If the compatible interface is found, it uses the compatible property on
194 * the interface to set the list of compatible values to be used when
195 * attempting to get a configuration file. Once the list of compatible
196 * values has been updated, it calls the load function.
197 */
Patrick Williamscb356d42022-07-22 19:26:53 -0500198 void compatIntfAdded(sdbusplus::message_t& msg)
Matthew Barthb370ab32021-06-10 14:38:02 -0500199 {
Chau Lyb99ce0e2023-11-30 08:06:00 +0000200 if (!_compatibleName.empty())
201 {
202 // Do not process the interfaceAdded signal if one compatible name
203 // has been successfully used to get config files
204 return;
205 }
Matthew Barthb370ab32021-06-10 14:38:02 -0500206 sdbusplus::message::object_path op;
207 std::map<std::string,
208 std::map<std::string, std::variant<std::vector<std::string>>>>
209 intfProps;
210
211 msg.read(op, intfProps);
212
213 if (intfProps.find(confCompatIntf) == intfProps.end())
214 {
215 return;
216 }
217
218 const auto& props = intfProps.at(confCompatIntf);
219 // Only one dbus object with the compatible interface is used at a time
220 _confCompatValues =
221 std::get<std::vector<std::string>>(props.at(confCompatProp));
222 _loadFunc();
223 }
224
225 /**
Matthew Barth11547c92020-05-05 10:34:27 -0500226 * Get the json configuration file. The first location found to contain
227 * the json config file for the given fan application is used from the
228 * following locations in order.
229 * 1.) From the confOverridePath location
Matthew Barthb67089b2021-06-10 14:32:00 -0500230 * 2.) From the default confBasePath location
231 * 3.) From config file found using an entry from a list obtained from an
Matthew Barthfcbdc0e2020-10-28 14:11:07 -0500232 * interface's property as a relative path extension on the base path where:
233 * interface = Interface set in confCompatIntf with the property
234 * property = Property set in confCompatProp containing a list of
235 * subdirectories in priority order to find a config
Matthew Barth11547c92020-05-05 10:34:27 -0500236 *
237 * @brief Get the configuration file to be used
238 *
Matthew Barth11547c92020-05-05 10:34:27 -0500239 * @param[in] appName - The phosphor-fan-presence application name
240 * @param[in] fileName - Application's configuration file's name
Matthew Barthb5991022020-08-05 11:08:50 -0500241 * @param[in] isOptional - Config file is optional, default to 'false'
Matthew Barth11547c92020-05-05 10:34:27 -0500242 *
243 * @return filesystem path
244 * The filesystem path to the configuration file to use
245 */
Mike Capps808d7fe2022-06-13 10:12:16 -0400246 static const fs::path getConfFile(const std::string& appName,
Matthew Barthb5991022020-08-05 11:08:50 -0500247 const std::string& fileName,
248 bool isOptional = false)
Matthew Barth11547c92020-05-05 10:34:27 -0500249 {
250 // Check override location
251 fs::path confFile = fs::path{confOverridePath} / appName / fileName;
252 if (fs::exists(confFile))
253 {
254 return confFile;
255 }
256
Matt Spinler59850df2021-01-06 16:56:06 -0600257 // If the default file is there, use it
Matthew Barthfcbdc0e2020-10-28 14:11:07 -0500258 confFile = fs::path{confBasePath} / appName / fileName;
Matt Spinler59850df2021-01-06 16:56:06 -0600259 if (fs::exists(confFile))
260 {
261 return confFile;
262 }
Matthew Barthfcbdc0e2020-10-28 14:11:07 -0500263
Matthew Barthb67089b2021-06-10 14:32:00 -0500264 // Look for a config file at each entry relative to the base
265 // path and use the first one found
Patrick Williamsdfddd642024-08-16 15:21:51 -0400266 auto it = std::find_if(
267 _confCompatValues.begin(), _confCompatValues.end(),
268 [&confFile, &appName, &fileName](const auto& value) {
269 confFile = fs::path{confBasePath} / appName / value / fileName;
270 _compatibleName = value;
271 return fs::exists(confFile);
272 });
Matthew Barthb67089b2021-06-10 14:32:00 -0500273 if (it == _confCompatValues.end())
274 {
275 confFile.clear();
Chau Lyb99ce0e2023-11-30 08:06:00 +0000276 _compatibleName.clear();
Matthew Barthb67089b2021-06-10 14:32:00 -0500277 }
278
Matt Spinler59850df2021-01-06 16:56:06 -0600279 if (confFile.empty() && !isOptional)
Matthew Barth11547c92020-05-05 10:34:27 -0500280 {
Matthew Barth6eb603e2021-09-01 09:01:57 -0500281 throw NoConfigFound(appName, fileName);
Matthew Barthb5991022020-08-05 11:08:50 -0500282 }
Matthew Barth11547c92020-05-05 10:34:27 -0500283
284 return confFile;
285 }
286
287 /**
288 * @brief Load the JSON config file
289 *
290 * @param[in] confFile - File system path of the configuration file to load
291 *
292 * @return Parsed JSON object
293 * The parsed JSON configuration file object
294 */
295 static const json load(const fs::path& confFile)
296 {
297 std::ifstream file;
298 json jsonConf;
299
Matthew Barthb5991022020-08-05 11:08:50 -0500300 if (!confFile.empty() && fs::exists(confFile))
Matthew Barth11547c92020-05-05 10:34:27 -0500301 {
Matthew Barth1826c732020-08-28 08:40:59 -0500302 log<level::INFO>(
Patrick Williamsfbf47032023-07-17 12:27:34 -0500303 std::format("Loading configuration from {}", confFile.string())
Matthew Barth1826c732020-08-28 08:40:59 -0500304 .c_str());
Matthew Barth11547c92020-05-05 10:34:27 -0500305 file.open(confFile);
306 try
307 {
Matthew Barthde72d5d2021-07-16 10:39:42 -0500308 // Enable ignoring `//` or `/* */` comments
309 jsonConf = json::parse(file, nullptr, true, true);
Matthew Barth11547c92020-05-05 10:34:27 -0500310 }
Patrick Williamsddb773b2021-10-06 11:24:49 -0500311 catch (const std::exception& e)
Matthew Barth11547c92020-05-05 10:34:27 -0500312 {
Matthew Barth1826c732020-08-28 08:40:59 -0500313 log<level::ERR>(
Patrick Williamsfbf47032023-07-17 12:27:34 -0500314 std::format(
Matthew Barth1826c732020-08-28 08:40:59 -0500315 "Failed to parse JSON config file: {}, error: {}",
316 confFile.string(), e.what())
317 .c_str());
318 throw std::runtime_error(
Patrick Williamsfbf47032023-07-17 12:27:34 -0500319 std::format(
Matthew Barth1826c732020-08-28 08:40:59 -0500320 "Failed to parse JSON config file: {}, error: {}",
321 confFile.string(), e.what())
322 .c_str());
Matthew Barth11547c92020-05-05 10:34:27 -0500323 }
324 }
325 else
326 {
Patrick Williamsfbf47032023-07-17 12:27:34 -0500327 log<level::ERR>(std::format("Unable to open JSON config file: {}",
Matthew Barth1826c732020-08-28 08:40:59 -0500328 confFile.string())
329 .c_str());
330 throw std::runtime_error(
Patrick Williamsfbf47032023-07-17 12:27:34 -0500331 std::format("Unable to open JSON config file: {}",
Matthew Barth1826c732020-08-28 08:40:59 -0500332 confFile.string())
333 .c_str());
Matthew Barth11547c92020-05-05 10:34:27 -0500334 }
335
336 return jsonConf;
337 }
Matt Spinler59850df2021-01-06 16:56:06 -0600338
Matt Spinlerd4c7fb72021-11-09 16:03:50 -0600339 /**
340 * @brief Return the compatible values property
341 *
342 * @return const std::vector<std::string>& - The values
343 */
344 static const std::vector<std::string>& getCompatValues()
345 {
346 return _confCompatValues;
347 }
348
Matt Spinler59850df2021-01-06 16:56:06 -0600349 private:
Matthew Barthb370ab32021-06-10 14:38:02 -0500350 /* Load function to call for a fan app to load its config file(s). */
351 std::function<void()> _loadFunc;
352
Matt Spinler59850df2021-01-06 16:56:06 -0600353 /**
Matt Spinler59850df2021-01-06 16:56:06 -0600354 * @brief The interfacesAdded match that is used to wait
Chau Lyb99ce0e2023-11-30 08:06:00 +0000355 * for the Inventory.Decorator.Compatible interface to show up.
Matt Spinler59850df2021-01-06 16:56:06 -0600356 */
Patrick Williams3ea9ec22021-11-19 12:21:08 -0600357 std::unique_ptr<sdbusplus::bus::match_t> _match;
Matthew Barthb67089b2021-06-10 14:32:00 -0500358
359 /**
360 * @brief List of compatible values from the compatible interface
361 *
362 * Only supports a single instance of the compatible interface on a dbus
363 * object. If more than one dbus object exists with the compatible
364 * interface, the last one found will be the list of compatible values used.
365 */
366 inline static std::vector<std::string> _confCompatValues;
Chau Lyb99ce0e2023-11-30 08:06:00 +0000367
368 /**
369 * @brief The compatible value that is currently used to load configuration
370 *
371 * The value extracted from the achieved property value list that is used
372 * as a sub-folder to append to the configuration location and really
373 * contains the configruation files
374 */
375
376 inline static std::string _compatibleName;
Matthew Barth11547c92020-05-05 10:34:27 -0500377};
378
379} // namespace phosphor::fan