1 #include "utils.hpp"
2
3 #include <phosphor-logging/lg2.hpp>
4 #include <sdbusplus/exception.hpp>
5 #include <sdeventplus/event.hpp>
6
7 #include <filesystem>
8 #include <fstream>
9
10 namespace fs = std::filesystem;
11
12 namespace phosphor
13 {
14 namespace led
15 {
16
17 static constexpr auto confFileName = "led-group-config.json";
18 static constexpr auto confOverridePath = "/etc/phosphor-led-manager";
19 static constexpr auto confBasePath = "/usr/share/phosphor-led-manager";
20 static constexpr auto confCompatibleInterface =
21 "xyz.openbmc_project.Inventory.Decorator.Compatible";
22 static constexpr auto confCompatibleProperty = "Names";
23
24 class JsonConfig
25 {
26 public:
27 /**
28 * @brief Constructor
29 *
30 * Looks for the JSON config file. If it can't find one, then it
31 * will watch entity-manager for the
32 * xyz.openbmc_project.Inventory.Decorator.Compatible interface to show up.
33 *
34 * @param[in] bus - The D-Bus object
35 * @param[in] event - sd event handler
36 */
JsonConfig(sdbusplus::bus_t & bus,sdeventplus::Event & event)37 JsonConfig(sdbusplus::bus_t& bus, sdeventplus::Event& event) : event(event)
38 {
39 match = std::make_unique<sdbusplus::bus::match_t>(
40 bus,
41 sdbusplus::bus::match::rules::interfacesAdded() +
42 sdbusplus::bus::match::rules::sender(
43 "xyz.openbmc_project.EntityManager"),
44 std::bind(&JsonConfig::ifacesAddedCallback, this,
45 std::placeholders::_1));
46 getFilePath();
47
48 if (!confFile.empty())
49 {
50 match.reset();
51 }
52 }
53
54 /**
55 * @brief Get the configuration file
56 *
57 * @return filesystem path
58 */
getConfFile() const59 inline const fs::path& getConfFile() const
60 {
61 return confFile;
62 }
63
64 private:
65 /** @brief Check the file path exists
66 *
67 * @param[in] names - Vector of the confCompatible Property
68 *
69 * @return - True or False
70 */
filePathExists(const std::vector<std::string> & names)71 bool filePathExists(const std::vector<std::string>& names)
72 {
73 auto it = std::find_if(names.begin(), names.end(),
74 [this](const auto& name) {
75 auto configFileName = name + ".json";
76 auto configFilePath = fs::path{confBasePath} / configFileName;
77 if (fs::exists(configFilePath))
78 {
79 confFile = configFilePath;
80 return true;
81 }
82 return false;
83 });
84 return it == names.end() ? false : true;
85 }
86
87 /**
88 * @brief The interfacesAdded callback function that looks for
89 * the xyz.openbmc_project.Inventory.Decorator.Compatible interface.
90 * If it finds it, it uses the Names property in the interface to find the
91 * JSON config file to use.
92 *
93 * @param[in] msg - The D-Bus message contents
94 */
ifacesAddedCallback(sdbusplus::message_t & msg)95 void ifacesAddedCallback(sdbusplus::message_t& msg)
96 {
97 sdbusplus::message::object_path path;
98 std::unordered_map<
99 std::string,
100 std::unordered_map<std::string,
101 std::variant<std::vector<std::string>>>>
102 interfaces;
103
104 msg.read(path, interfaces);
105
106 if (!interfaces.contains(confCompatibleInterface))
107 {
108 return;
109 }
110
111 // Get the "Name" property value of the
112 // "xyz.openbmc_project.Inventory.Decorator.Compatible" interface
113 const auto& properties = interfaces.at(confCompatibleInterface);
114
115 if (!properties.contains(confCompatibleProperty))
116 {
117 return;
118 }
119 auto names = std::get<std::vector<std::string>>(
120 properties.at(confCompatibleProperty));
121
122 if (filePathExists(names))
123 {
124 match.reset();
125
126 // This results in event.loop() exiting in getSystemLedMap
127 event.exit(0);
128 }
129 }
130
131 /**
132 * Get the json configuration file. The first location found to contain the
133 * json config file from the following locations in order.
134 * confOverridePath: /etc/phosphor-led-manager/led-group-config.json
135 * confBasePath: /usr/shard/phosphor-led-manager/led-group-config.json
136 * the name property of the confCompatibleInterface:
137 * /usr/shard/phosphor-led-manager/${Name}/led-group-config.json
138 *
139 * @brief Get the configuration file to be used
140 *
141 * @return
142 */
getFilePath()143 void getFilePath()
144 {
145 // Check override location
146 confFile = fs::path{confOverridePath} / confFileName;
147 if (fs::exists(confFile))
148 {
149 return;
150 }
151
152 // If the default file is there, use it
153 confFile = fs::path{confBasePath} / confFileName;
154 if (fs::exists(confFile))
155 {
156 return;
157 }
158 confFile.clear();
159
160 try
161 {
162 // Get all objects implementing the compatible interface
163 auto objects = dBusHandler.getSubTreePaths("/",
164 confCompatibleInterface);
165 for (const auto& path : objects)
166 {
167 try
168 {
169 // Retrieve json config compatible relative path locations
170 auto value = dBusHandler.getProperty(
171 path, confCompatibleInterface, confCompatibleProperty);
172
173 auto confCompatValues =
174 std::get<std::vector<std::string>>(value);
175
176 // Look for a config file at each name relative to the base
177 // path and use the first one found
178 if (filePathExists(confCompatValues))
179 {
180 // Use the first config file found at a listed location
181 break;
182 }
183 confFile.clear();
184 }
185 catch (const sdbusplus::exception_t& e)
186 {
187 // Property unavailable on object.
188 lg2::error(
189 "Failed to get Names property, ERROR = {ERROR}, INTERFACES = {INTERFACES}, PATH = {PATH}",
190 "ERROR", e, "INTERFACE", confCompatibleInterface,
191 "PATH", path);
192
193 confFile.clear();
194 }
195 }
196 }
197 catch (const sdbusplus::exception_t& e)
198 {
199 lg2::error(
200 "Failed to call the SubTreePaths method, ERROR = {ERROR}, INTERFACE = {INTERFACE}",
201 "ERROR", e, "INTERFACE", confCompatibleInterface);
202 }
203 return;
204 }
205
206 private:
207 /**
208 * @brief sd event handler.
209 */
210 sdeventplus::Event& event;
211
212 /**
213 * @brief The JSON config file
214 */
215 fs::path confFile;
216
217 /**
218 * @brief The interfacesAdded match that is used to wait
219 * for the xyz.openbmc_project.Inventory.Decorator.Compatible
220 * interface to show up.
221 */
222 std::unique_ptr<sdbusplus::bus::match_t> match;
223
224 /** DBusHandler class handles the D-Bus operations */
225 utils::DBusHandler dBusHandler;
226 };
227
228 /** Blocking call to find the JSON Config from DBus. */
getJsonConfig()229 auto getJsonConfig()
230 {
231 // Get a new Dbus
232 auto bus = sdbusplus::bus::new_bus();
233
234 // Get a new event loop
235 auto event = sdeventplus::Event::get_new();
236
237 // Attach the bus to sd_event to service user requests
238 bus.attach_event(event.get(), SD_EVENT_PRIORITY_IMPORTANT);
239 phosphor::led::JsonConfig jsonConfig(bus, event);
240
241 // The event loop will be terminated from inside of a function in JsonConfig
242 // after finding the configuration file
243 if (jsonConfig.getConfFile().empty())
244 {
245 event.loop();
246 }
247
248 // Detach the bus from its sd_event event loop object
249 bus.detach_event();
250
251 return jsonConfig.getConfFile();
252 }
253
254 } // namespace led
255 } // namespace phosphor
256