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             [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      */
58     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      */
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      */
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      */
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. */
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