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      */
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      */
59     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      */
71     bool filePathExists(const std::vector<std::string>& names)
72     {
73         auto it =
74             std::find_if(names.begin(), names.end(), [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      */
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      */
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 =
164                 dBusHandler.getSubTreePaths("/", 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. */
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