blob: 9c898b5aa7de86b34d3e89bcf39ba173adc03894 [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 Barthfcbdc0e2020-10-28 14:11:07 -050039constexpr auto confCompatIntf =
40 "xyz.openbmc_project.Configuration.IBMCompatibleSystem";
41constexpr auto confCompatProp = "Names";
Matthew Barth11547c92020-05-05 10:34:27 -050042
43class JsonConfig
44{
45 public:
Matt Spinler59850df2021-01-06 16:56:06 -060046 using ConfFileReadyFunc = std::function<void(const std::string&)>;
47
48 /**
Matthew Barthd80c8752021-06-01 14:46:02 -050049 * @brief Get the object paths with the compatible interface
50 *
51 * Retrieve all the object paths implementing the compatible interface for
52 * configuration file loading.
53 */
54 static auto& getCompatObjPaths() __attribute__((pure))
55 {
56 static auto paths = util::SDBusPlus::getSubTreePathsRaw(
57 util::SDBusPlus::getBus(), "/", confCompatIntf, 0);
58 return paths;
59 }
60
61 /**
Matt Spinler59850df2021-01-06 16:56:06 -060062 * @brief Constructor
63 *
64 * Looks for the JSON config file. If it can't find one, then it
65 * will watch entity-manager for the IBMCompatibleSystem interface
66 * to show up and then use that data to try again. If the config
67 * file is initially present, the callback function is executed
68 * with the path to the file.
69 *
70 * @param[in] bus - The dbus bus object
71 * @param[in] appName - The appName portion of the config file path
72 * @param[in] fileName - Application's configuration file's name
73 * @param[in] func - The function to call when the config file
74 * is found.
75 */
76 JsonConfig(sdbusplus::bus::bus& bus, const std::string& appName,
77 const std::string& fileName, ConfFileReadyFunc func) :
78 _appName(appName),
79 _fileName(fileName), _readyFunc(func)
80 {
81 _match = std::make_unique<sdbusplus::server::match::match>(
82 bus,
83 sdbusplus::bus::match::rules::interfacesAdded() +
84 sdbusplus::bus::match::rules::sender(
85 "xyz.openbmc_project.EntityManager"),
86 std::bind(&JsonConfig::ifacesAddedCallback, this,
87 std::placeholders::_1));
88 try
89 {
90 _confFile = getConfFile(bus, _appName, _fileName);
91 }
92 catch (const std::runtime_error& e)
93 {
94 // No conf file found, so let the interfacesAdded
95 // match callback handle finding it.
96 }
97
98 if (!_confFile.empty())
99 {
100 _match.reset();
101 _readyFunc(_confFile);
102 }
103 }
104
105 /**
106 * @brief The interfacesAdded callback function that looks for
107 * the IBMCompatibleSystem interface. If it finds it,
108 * it uses the Names property in the interface to find
109 * the JSON config file to use. If it finds one, it calls
110 * the _readyFunc function with the config file path.
111 *
112 * @param[in] msg - The D-Bus message contents
113 */
114 void ifacesAddedCallback(sdbusplus::message::message& msg)
115 {
116 sdbusplus::message::object_path path;
117 std::map<std::string,
118 std::map<std::string, std::variant<std::vector<std::string>>>>
119 interfaces;
120
121 msg.read(path, interfaces);
122
123 if (interfaces.find(confCompatIntf) == interfaces.end())
124 {
125 return;
126 }
127
128 const auto& properties = interfaces.at(confCompatIntf);
129 auto names =
130 std::get<std::vector<std::string>>(properties.at(confCompatProp));
131
132 auto it =
133 std::find_if(names.begin(), names.end(), [this](auto const& name) {
134 auto confFile =
135 fs::path{confBasePath} / _appName / name / _fileName;
136 if (fs::exists(confFile))
137 {
138 _confFile = confFile;
139 return true;
140 }
141 return false;
142 });
143
144 if (it != names.end())
145 {
146 _readyFunc(_confFile);
147 _match.reset();
148 }
Matt Spinler4031f102021-04-22 16:42:20 -0500149 else
150 {
151 log<level::ERR>(fmt::format("Could not find fan {} conf file {} "
152 "even after {} iface became available",
153 _appName, _fileName, confCompatIntf)
154 .c_str());
155 }
Matt Spinler59850df2021-01-06 16:56:06 -0600156 }
157
Matthew Barth11547c92020-05-05 10:34:27 -0500158 /**
159 * Get the json configuration file. The first location found to contain
160 * the json config file for the given fan application is used from the
161 * following locations in order.
162 * 1.) From the confOverridePath location
Matthew Barthfcbdc0e2020-10-28 14:11:07 -0500163 * 2.) From config file found using an entry from a list obtained from an
164 * interface's property as a relative path extension on the base path where:
165 * interface = Interface set in confCompatIntf with the property
166 * property = Property set in confCompatProp containing a list of
167 * subdirectories in priority order to find a config
Matthew Barth11547c92020-05-05 10:34:27 -0500168 * 3.) *DEFAULT* - From the confBasePath location
169 *
170 * @brief Get the configuration file to be used
171 *
172 * @param[in] bus - The dbus bus object
173 * @param[in] appName - The phosphor-fan-presence application name
174 * @param[in] fileName - Application's configuration file's name
Matthew Barthb5991022020-08-05 11:08:50 -0500175 * @param[in] isOptional - Config file is optional, default to 'false'
Matthew Barth11547c92020-05-05 10:34:27 -0500176 *
177 * @return filesystem path
178 * The filesystem path to the configuration file to use
179 */
180 static const fs::path getConfFile(sdbusplus::bus::bus& bus,
181 const std::string& appName,
Matthew Barthb5991022020-08-05 11:08:50 -0500182 const std::string& fileName,
183 bool isOptional = false)
Matthew Barth11547c92020-05-05 10:34:27 -0500184 {
185 // Check override location
186 fs::path confFile = fs::path{confOverridePath} / appName / fileName;
187 if (fs::exists(confFile))
188 {
189 return confFile;
190 }
191
Matt Spinler59850df2021-01-06 16:56:06 -0600192 // If the default file is there, use it
Matthew Barthfcbdc0e2020-10-28 14:11:07 -0500193 confFile = fs::path{confBasePath} / appName / fileName;
Matt Spinler59850df2021-01-06 16:56:06 -0600194 if (fs::exists(confFile))
195 {
196 return confFile;
197 }
198 confFile.clear();
Matthew Barthfcbdc0e2020-10-28 14:11:07 -0500199
Matthew Barthd80c8752021-06-01 14:46:02 -0500200 // Get all object paths implementing the compatible interface
201 auto paths = getCompatObjPaths();
202 for (auto& path : paths)
Matthew Barth11547c92020-05-05 10:34:27 -0500203 {
Matthew Barthfcbdc0e2020-10-28 14:11:07 -0500204 try
Matthew Barth11547c92020-05-05 10:34:27 -0500205 {
Matthew Barthfcbdc0e2020-10-28 14:11:07 -0500206 // Retrieve json config compatible relative path locations
207 auto confCompatValue =
208 util::SDBusPlus::getProperty<std::vector<std::string>>(
209 bus, path, confCompatIntf, confCompatProp);
210 // Look for a config file at each entry relative to the base
211 // path and use the first one found
212 auto it = std::find_if(
213 confCompatValue.begin(), confCompatValue.end(),
214 [&confFile, &appName, &fileName](auto const& entry) {
215 confFile =
216 fs::path{confBasePath} / appName / entry / fileName;
217 return fs::exists(confFile);
218 });
219 if (it != confCompatValue.end())
220 {
221 // Use the first config file found at a listed location
222 break;
223 }
Matt Spinler59850df2021-01-06 16:56:06 -0600224 confFile.clear();
Matthew Barth11547c92020-05-05 10:34:27 -0500225 }
Matthew Barthfcbdc0e2020-10-28 14:11:07 -0500226 catch (const util::DBusError&)
227 {
228 // Property unavailable on object.
Matthew Barthfcbdc0e2020-10-28 14:11:07 -0500229 }
Matthew Barth11547c92020-05-05 10:34:27 -0500230 }
231
Matthew Barthd80c8752021-06-01 14:46:02 -0500232 if (!isOptional && confFile.empty() && !paths.empty())
Matt Spinler4031f102021-04-22 16:42:20 -0500233 {
234 log<level::ERR>(fmt::format("Could not find fan {} conf file {}",
235 appName, fileName)
236 .c_str());
237 }
238
Matt Spinler59850df2021-01-06 16:56:06 -0600239 if (confFile.empty() && !isOptional)
Matthew Barth11547c92020-05-05 10:34:27 -0500240 {
Matt Spinler59850df2021-01-06 16:56:06 -0600241 throw std::runtime_error("No JSON config file found");
Matthew Barthb5991022020-08-05 11:08:50 -0500242 }
Matthew Barth11547c92020-05-05 10:34:27 -0500243
244 return confFile;
245 }
246
247 /**
248 * @brief Load the JSON config file
249 *
250 * @param[in] confFile - File system path of the configuration file to load
251 *
252 * @return Parsed JSON object
253 * The parsed JSON configuration file object
254 */
255 static const json load(const fs::path& confFile)
256 {
257 std::ifstream file;
258 json jsonConf;
259
Matthew Barthb5991022020-08-05 11:08:50 -0500260 if (!confFile.empty() && fs::exists(confFile))
Matthew Barth11547c92020-05-05 10:34:27 -0500261 {
Matthew Barth1826c732020-08-28 08:40:59 -0500262 log<level::INFO>(
263 fmt::format("Loading configuration from {}", confFile.string())
264 .c_str());
Matthew Barth11547c92020-05-05 10:34:27 -0500265 file.open(confFile);
266 try
267 {
268 jsonConf = json::parse(file);
269 }
270 catch (std::exception& e)
271 {
Matthew Barth1826c732020-08-28 08:40:59 -0500272 log<level::ERR>(
273 fmt::format(
274 "Failed to parse JSON config file: {}, error: {}",
275 confFile.string(), e.what())
276 .c_str());
277 throw std::runtime_error(
278 fmt::format(
279 "Failed to parse JSON config file: {}, error: {}",
280 confFile.string(), e.what())
281 .c_str());
Matthew Barth11547c92020-05-05 10:34:27 -0500282 }
283 }
284 else
285 {
Matthew Barth1826c732020-08-28 08:40:59 -0500286 log<level::ERR>(fmt::format("Unable to open JSON config file: {}",
287 confFile.string())
288 .c_str());
289 throw std::runtime_error(
290 fmt::format("Unable to open JSON config file: {}",
291 confFile.string())
292 .c_str());
Matthew Barth11547c92020-05-05 10:34:27 -0500293 }
294
295 return jsonConf;
296 }
Matt Spinler59850df2021-01-06 16:56:06 -0600297
298 private:
299 /**
300 * @brief The 'appName' portion of the config file path.
301 */
302 const std::string _appName;
303
304 /**
305 * @brief The config file name.
306 */
307 const std::string _fileName;
308
309 /**
310 * @brief The function to call when the config file is available.
311 */
312 ConfFileReadyFunc _readyFunc;
313
314 /**
315 * @brief The JSON config file
316 */
317 fs::path _confFile;
318
319 /**
320 * @brief The interfacesAdded match that is used to wait
321 * for the IBMCompatibleSystem interface to show up.
322 */
323 std::unique_ptr<sdbusplus::server::match::match> _match;
Matthew Barth11547c92020-05-05 10:34:27 -0500324};
325
326} // namespace phosphor::fan