blob: ea795709d2cd9ab3eced03801a53e244fa90c5b2 [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 /**
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 }
Matt Spinler4031f102021-04-22 16:42:20 -0500136 else
137 {
138 log<level::ERR>(fmt::format("Could not find fan {} conf file {} "
139 "even after {} iface became available",
140 _appName, _fileName, confCompatIntf)
141 .c_str());
142 }
Matt Spinler59850df2021-01-06 16:56:06 -0600143 }
144
Matthew Barth11547c92020-05-05 10:34:27 -0500145 /**
146 * Get the json configuration file. The first location found to contain
147 * the json config file for the given fan application is used from the
148 * following locations in order.
149 * 1.) From the confOverridePath location
Matthew Barthfcbdc0e2020-10-28 14:11:07 -0500150 * 2.) From config file found using an entry from a list obtained from an
151 * interface's property as a relative path extension on the base path where:
152 * interface = Interface set in confCompatIntf with the property
153 * property = Property set in confCompatProp containing a list of
154 * subdirectories in priority order to find a config
Matthew Barth11547c92020-05-05 10:34:27 -0500155 * 3.) *DEFAULT* - From the confBasePath location
156 *
157 * @brief Get the configuration file to be used
158 *
159 * @param[in] bus - The dbus bus object
160 * @param[in] appName - The phosphor-fan-presence application name
161 * @param[in] fileName - Application's configuration file's name
Matthew Barthb5991022020-08-05 11:08:50 -0500162 * @param[in] isOptional - Config file is optional, default to 'false'
Matthew Barth11547c92020-05-05 10:34:27 -0500163 *
164 * @return filesystem path
165 * The filesystem path to the configuration file to use
166 */
167 static const fs::path getConfFile(sdbusplus::bus::bus& bus,
168 const std::string& appName,
Matthew Barthb5991022020-08-05 11:08:50 -0500169 const std::string& fileName,
170 bool isOptional = false)
Matthew Barth11547c92020-05-05 10:34:27 -0500171 {
172 // Check override location
173 fs::path confFile = fs::path{confOverridePath} / appName / fileName;
174 if (fs::exists(confFile))
175 {
176 return confFile;
177 }
178
Matt Spinler59850df2021-01-06 16:56:06 -0600179 // If the default file is there, use it
Matthew Barthfcbdc0e2020-10-28 14:11:07 -0500180 confFile = fs::path{confBasePath} / appName / fileName;
Matt Spinler59850df2021-01-06 16:56:06 -0600181 if (fs::exists(confFile))
182 {
183 return confFile;
184 }
185 confFile.clear();
Matthew Barthfcbdc0e2020-10-28 14:11:07 -0500186
187 // Get all objects implementing the compatible interface
188 auto objects =
189 util::SDBusPlus::getSubTreePathsRaw(bus, "/", confCompatIntf, 0);
190 for (auto& path : objects)
Matthew Barth11547c92020-05-05 10:34:27 -0500191 {
Matthew Barthfcbdc0e2020-10-28 14:11:07 -0500192 try
Matthew Barth11547c92020-05-05 10:34:27 -0500193 {
Matthew Barthfcbdc0e2020-10-28 14:11:07 -0500194 // Retrieve json config compatible relative path locations
195 auto confCompatValue =
196 util::SDBusPlus::getProperty<std::vector<std::string>>(
197 bus, path, confCompatIntf, confCompatProp);
198 // Look for a config file at each entry relative to the base
199 // path and use the first one found
200 auto it = std::find_if(
201 confCompatValue.begin(), confCompatValue.end(),
202 [&confFile, &appName, &fileName](auto const& entry) {
203 confFile =
204 fs::path{confBasePath} / appName / entry / fileName;
205 return fs::exists(confFile);
206 });
207 if (it != confCompatValue.end())
208 {
209 // Use the first config file found at a listed location
210 break;
211 }
Matt Spinler59850df2021-01-06 16:56:06 -0600212 confFile.clear();
Matthew Barth11547c92020-05-05 10:34:27 -0500213 }
Matthew Barthfcbdc0e2020-10-28 14:11:07 -0500214 catch (const util::DBusError&)
215 {
216 // Property unavailable on object.
Matthew Barthfcbdc0e2020-10-28 14:11:07 -0500217 }
Matthew Barth11547c92020-05-05 10:34:27 -0500218 }
219
Matt Spinler4031f102021-04-22 16:42:20 -0500220 if (!isOptional && confFile.empty() && !objects.empty())
221 {
222 log<level::ERR>(fmt::format("Could not find fan {} conf file {}",
223 appName, fileName)
224 .c_str());
225 }
226
Matt Spinler59850df2021-01-06 16:56:06 -0600227 if (confFile.empty() && !isOptional)
Matthew Barth11547c92020-05-05 10:34:27 -0500228 {
Matt Spinler59850df2021-01-06 16:56:06 -0600229 throw std::runtime_error("No JSON config file found");
Matthew Barthb5991022020-08-05 11:08:50 -0500230 }
Matthew Barth11547c92020-05-05 10:34:27 -0500231
232 return confFile;
233 }
234
235 /**
236 * @brief Load the JSON config file
237 *
238 * @param[in] confFile - File system path of the configuration file to load
239 *
240 * @return Parsed JSON object
241 * The parsed JSON configuration file object
242 */
243 static const json load(const fs::path& confFile)
244 {
245 std::ifstream file;
246 json jsonConf;
247
Matthew Barthb5991022020-08-05 11:08:50 -0500248 if (!confFile.empty() && fs::exists(confFile))
Matthew Barth11547c92020-05-05 10:34:27 -0500249 {
Matthew Barth1826c732020-08-28 08:40:59 -0500250 log<level::INFO>(
251 fmt::format("Loading configuration from {}", confFile.string())
252 .c_str());
Matthew Barth11547c92020-05-05 10:34:27 -0500253 file.open(confFile);
254 try
255 {
256 jsonConf = json::parse(file);
257 }
258 catch (std::exception& e)
259 {
Matthew Barth1826c732020-08-28 08:40:59 -0500260 log<level::ERR>(
261 fmt::format(
262 "Failed to parse JSON config file: {}, error: {}",
263 confFile.string(), e.what())
264 .c_str());
265 throw std::runtime_error(
266 fmt::format(
267 "Failed to parse JSON config file: {}, error: {}",
268 confFile.string(), e.what())
269 .c_str());
Matthew Barth11547c92020-05-05 10:34:27 -0500270 }
271 }
272 else
273 {
Matthew Barth1826c732020-08-28 08:40:59 -0500274 log<level::ERR>(fmt::format("Unable to open JSON config file: {}",
275 confFile.string())
276 .c_str());
277 throw std::runtime_error(
278 fmt::format("Unable to open JSON config file: {}",
279 confFile.string())
280 .c_str());
Matthew Barth11547c92020-05-05 10:34:27 -0500281 }
282
283 return jsonConf;
284 }
Matt Spinler59850df2021-01-06 16:56:06 -0600285
286 private:
287 /**
288 * @brief The 'appName' portion of the config file path.
289 */
290 const std::string _appName;
291
292 /**
293 * @brief The config file name.
294 */
295 const std::string _fileName;
296
297 /**
298 * @brief The function to call when the config file is available.
299 */
300 ConfFileReadyFunc _readyFunc;
301
302 /**
303 * @brief The JSON config file
304 */
305 fs::path _confFile;
306
307 /**
308 * @brief The interfacesAdded match that is used to wait
309 * for the IBMCompatibleSystem interface to show up.
310 */
311 std::unique_ptr<sdbusplus::server::match::match> _match;
Matthew Barth11547c92020-05-05 10:34:27 -0500312};
313
314} // namespace phosphor::fan