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