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.Configuration.IBMCompatibleSystem";
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 IBMCompatibleSystem interface
32      * to show up.
33      *
34      * @param[in] bus       - The D-Bus object
35      * @param[in] event     - sd event handler
36      */
37     JsonConfig(sdbusplus::bus::bus& bus, sdeventplus::Event& event) :
38         event(event)
39     {
40         match = std::make_unique<sdbusplus::bus::match_t>(
41             bus,
42             sdbusplus::bus::match::rules::interfacesAdded() +
43                 sdbusplus::bus::match::rules::sender(
44                     "xyz.openbmc_project.EntityManager"),
45             std::bind(&JsonConfig::ifacesAddedCallback, this,
46                       std::placeholders::_1));
47         getFilePath();
48 
49         if (!confFile.empty())
50         {
51             match.reset();
52         }
53     }
54 
55     /**
56      * @brief Get the configuration file
57      *
58      * @return filesystem path
59      */
60     inline const fs::path& getConfFile() const
61     {
62         return confFile;
63     }
64 
65   private:
66     /** @brief Check the file path exists
67      *
68      *  @param[in]  names   -  Vector of the confCompatible Property
69      *
70      *  @return             -  True or False
71      */
72     bool filePathExists(const std::vector<std::string>& names)
73     {
74         auto it =
75             std::find_if(names.begin(), names.end(), [this](const auto& name) {
76                 auto tempConfFile =
77                     fs::path{confBasePath} / name / confFileName;
78                 if (fs::exists(tempConfFile))
79                 {
80                     confFile = tempConfFile;
81                     return true;
82                 }
83                 return false;
84             });
85         return it == names.end() ? false : true;
86     }
87 
88     /**
89      * @brief The interfacesAdded callback function that looks for
90      *        the IBMCompatibleSystem interface.  If it finds it,
91      *        it uses the Names property in the interface to find
92      *        the JSON config file to use.
93      *
94      * @param[in] msg - The D-Bus message contents
95      */
96     void ifacesAddedCallback(sdbusplus::message::message& msg)
97     {
98         sdbusplus::message::object_path path;
99         std::unordered_map<
100             std::string,
101             std::unordered_map<std::string,
102                                std::variant<std::vector<std::string>>>>
103             interfaces;
104 
105         msg.read(path, interfaces);
106 
107         if (!interfaces.contains(confCompatibleInterface))
108         {
109             return;
110         }
111 
112         // Get the "Name" property value of the
113         // "xyz.openbmc_project.Configuration.IBMCompatibleSystem" interface
114         const auto& properties = interfaces.at(confCompatibleInterface);
115 
116         if (!properties.contains(confCompatibleProperty))
117         {
118             return;
119         }
120         auto names = std::get<std::vector<std::string>>(
121             properties.at(confCompatibleProperty));
122 
123         if (filePathExists(names))
124         {
125             match.reset();
126 
127             // This results in event.loop() exiting in getSystemLedMap
128             event.exit(0);
129         }
130     }
131 
132     /**
133      * Get the json configuration file. The first location found to contain the
134      * json config file from the following locations in order.
135      * confOverridePath: /etc/phosphor-led-manager/led-group-config.json
136      * confBasePath: /usr/shard/phosphor-led-manager/led-group-config.json
137      * the name property of the confCompatibleInterface:
138      * /usr/shard/phosphor-led-manager/${Name}/led-group-config.json
139      *
140      * @brief Get the configuration file to be used
141      *
142      * @return
143      */
144     void getFilePath()
145     {
146         // Check override location
147         confFile = fs::path{confOverridePath} / confFileName;
148         if (fs::exists(confFile))
149         {
150             return;
151         }
152 
153         // If the default file is there, use it
154         confFile = fs::path{confBasePath} / confFileName;
155         if (fs::exists(confFile))
156         {
157             return;
158         }
159         confFile.clear();
160 
161         try
162         {
163             // Get all objects implementing the compatible interface
164             auto objects =
165                 dBusHandler.getSubTreePaths("/", confCompatibleInterface);
166             for (const auto& path : objects)
167             {
168                 try
169                 {
170                     // Retrieve json config compatible relative path locations
171                     auto value = dBusHandler.getProperty(
172                         path, confCompatibleInterface, confCompatibleProperty);
173 
174                     auto confCompatValues =
175                         std::get<std::vector<std::string>>(value);
176 
177                     // Look for a config file at each name relative to the base
178                     // path and use the first one found
179                     if (filePathExists(confCompatValues))
180                     {
181                         // Use the first config file found at a listed location
182                         break;
183                     }
184                     confFile.clear();
185                 }
186                 catch (const sdbusplus::exception::exception& e)
187                 {
188                     // Property unavailable on object.
189                     lg2::error(
190                         "Failed to get Names property, ERROR = {ERROR}, INTERFACES = {INTERFACES}, PATH = {PATH}",
191                         "ERROR", e, "INTERFACE", confCompatibleInterface,
192                         "PATH", path);
193 
194                     confFile.clear();
195                 }
196             }
197         }
198         catch (const sdbusplus::exception::exception& e)
199         {
200             lg2::error(
201                 "Failed to call the SubTreePaths method, ERROR = {ERROR}, INTERFACE = {INTERFACE}",
202                 "ERROR", e, "INTERFACE", confCompatibleInterface);
203         }
204         return;
205     }
206 
207   private:
208     /**
209      * @brief sd event handler.
210      */
211     sdeventplus::Event& event;
212 
213     /**
214      * @brief The JSON config file
215      */
216     fs::path confFile;
217 
218     /**
219      * @brief The interfacesAdded match that is used to wait
220      *        for the IBMCompatibleSystem 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