1 // SPDX-License-Identifier: Apache-2.0
2 
3 /**@file functions.cpp*/
4 
5 #include "functions.hpp"
6 
7 #include <sdbusplus/bus.hpp>
8 #include <sdbusplus/bus/match.hpp>
9 #include <sdbusplus/message.hpp>
10 #include <sdeventplus/event.hpp>
11 
12 #include <filesystem>
13 #include <functional>
14 #include <iostream>
15 #include <map>
16 #include <memory>
17 #include <string>
18 #include <variant>
19 #include <vector>
20 
21 namespace functions
22 {
23 namespace process_hostfirmware
24 {
25 
26 /**
27  * @brief Issue callbacks safely
28  *
29  * std::function can be empty, so this wrapper method checks for that prior to
30  * calling it to avoid std::bad_function_call
31  *
32  * @tparam Sig the types of the std::function arguments
33  * @tparam Args the deduced argument types
34  * @param[in] callback the callback being wrapped
35  * @param[in] args the callback arguments
36  */
37 template <typename... Sig, typename... Args>
38 void makeCallback(const std::function<void(Sig...)>& callback, Args&&... args)
39 {
40     if (callback)
41     {
42         callback(std::forward<Args>(args)...);
43     }
44 }
45 
46 /**
47  * @brief Get file extensions for IBMCompatibleSystem
48  *
49  * IBM host firmware can be deployed as blobs (files) in a filesystem.  Host
50  * firmware blobs for different values of
51  * xyz.openbmc_project.Configuration.IBMCompatibleSystem are packaged with
52  * different filename extensions.  getExtensionsForIbmCompatibleSystem
53  * maintains the mapping from a given value of
54  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to an array of
55  * filename extensions.
56  *
57  * If a mapping is found getExtensionsForIbmCompatibleSystem returns true and
58  * the extensions parameter is reset with the map entry.  If no mapping is
59  * found getExtensionsForIbmCompatibleSystem returns false and extensions is
60  * unmodified.
61  *
62  * @param[in] extensionMap a map of
63  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
64  * file extensions.
65  * @param[in] ibmCompatibleSystem The names property of an instance of
66  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
67  * @param[out] extentions the host firmware blob file extensions
68  * @return true if an entry was found, otherwise false
69  */
70 bool getExtensionsForIbmCompatibleSystem(
71     const std::map<std::string, std::vector<std::string>>& extensionMap,
72     const std::vector<std::string>& ibmCompatibleSystem,
73     std::vector<std::string>& extensions)
74 {
75     for (const auto& system : ibmCompatibleSystem)
76     {
77         auto extensionMapIterator = extensionMap.find(system);
78         if (extensionMapIterator != extensionMap.end())
79         {
80             extensions = extensionMapIterator->second;
81             return true;
82         }
83     }
84 
85     return false;
86 }
87 
88 /**
89  * @brief Write host firmware well-known name
90  *
91  * A wrapper around std::filesystem::create_symlink that avoids EEXIST by
92  * deleting any pre-existing file.
93  *
94  * @param[in] linkTarget The link target argument to
95  * std::filesystem::create_symlink
96  * @param[in] linkPath The link path argument to std::filesystem::create_symlink
97  * @param[in] errorCallback A callback made in the event of filesystem errors.
98  */
99 void writeLink(const std::filesystem::path& linkTarget,
100                const std::filesystem::path& linkPath,
101                const ErrorCallbackType& errorCallback)
102 {
103     std::error_code ec;
104 
105     // remove files with the same name as the symlink to be created,
106     // otherwise symlink will fail with EEXIST.
107     if (!std::filesystem::remove(linkPath, ec))
108     {
109         if (ec)
110         {
111             makeCallback(errorCallback, linkPath, ec);
112             return;
113         }
114     }
115 
116     std::filesystem::create_symlink(linkTarget, linkPath, ec);
117     if (ec)
118     {
119         makeCallback(errorCallback, linkPath, ec);
120         return;
121     }
122 }
123 
124 /**
125  * @brief Find host firmware blob files that need well-known names
126  *
127  * The IBM host firmware runtime looks for data and/or additional code while
128  * bootstraping in files with well-known names.  findLinks uses the provided
129  * extensions argument to find host firmware blob files that require a
130  * well-known name.  When a blob is found, issue the provided callback
131  * (typically a function that will write a symlink).
132  *
133  * @param[in] hostFirmwareDirectory The directory in which findLinks should
134  * look for host firmware blob files that need well-known names.
135  * @param[in] extentions The extensions of the firmware blob files denote a
136  * host firmware blob file requires a well-known name.
137  * @param[in] errorCallback A callback made in the event of filesystem errors.
138  * @param[in] linkCallback A callback made when host firmware blob files
139  * needing a well known name are found.
140  */
141 void findLinks(const std::filesystem::path& hostFirmwareDirectory,
142                const std::vector<std::string>& extensions,
143                const ErrorCallbackType& errorCallback,
144                const LinkCallbackType& linkCallback)
145 {
146     std::error_code ec;
147     std::filesystem::directory_iterator directoryIterator(hostFirmwareDirectory,
148                                                           ec);
149     if (ec)
150     {
151         makeCallback(errorCallback, hostFirmwareDirectory, ec);
152         return;
153     }
154 
155     for (; directoryIterator != std::filesystem::end(directoryIterator);
156          directoryIterator.increment(ec))
157     {
158         const auto& file = directoryIterator->path();
159         if (ec)
160         {
161             makeCallback(errorCallback, file, ec);
162             // quit here if the increment call failed otherwise the loop may
163             // never finish
164             break;
165         }
166 
167         if (std::find(extensions.begin(), extensions.end(), file.extension()) ==
168             extensions.end())
169         {
170             // this file doesn't have an extension or doesn't match any of the
171             // provided extensions.
172             continue;
173         }
174 
175         auto linkPath(file.parent_path().append(
176             static_cast<const std::string&>(file.stem())));
177 
178         makeCallback(linkCallback, file.filename(), linkPath, errorCallback);
179     }
180 }
181 
182 /**
183  * @brief Make callbacks on
184  * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances.
185  *
186  * Look for an instance of
187  * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided
188  * argument and if found, issue the provided callback.
189  *
190  * @param[in] interfacesAndProperties the interfaces in which to look for an
191  * instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem
192  * @param[in] callback the user callback to make if
193  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in
194  * interfacesAndProperties
195  * @return true if interfacesAndProperties contained an instance of
196  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise
197  */
198 bool maybeCall(const std::map<std::string,
199                               std::map<std::string,
200                                        std::variant<std::vector<std::string>>>>&
201                    interfacesAndProperties,
202                const MaybeCallCallbackType& callback)
203 {
204     using namespace std::string_literals;
205 
206     static const auto interfaceName =
207         "xyz.openbmc_project.Configuration.IBMCompatibleSystem"s;
208     auto interfaceIterator = interfacesAndProperties.find(interfaceName);
209     if (interfaceIterator == interfacesAndProperties.cend())
210     {
211         // IBMCompatibleSystem interface not found, so instruct the caller to
212         // keep waiting or try again later.
213         return false;
214     }
215     auto propertyIterator = interfaceIterator->second.find("Names"s);
216     if (propertyIterator == interfaceIterator->second.cend())
217     {
218         // The interface exists but the property doesn't.  This is a bug in the
219         // IBMCompatibleSystem implementation.  The caller should not try
220         // again.
221         std::cerr << "Names property not implemented on " << interfaceName
222                   << "\n";
223         return true;
224     }
225 
226     const auto& ibmCompatibleSystem =
227         std::get<std::vector<std::string>>(propertyIterator->second);
228     if (callback)
229     {
230         callback(ibmCompatibleSystem);
231     }
232 
233     // IBMCompatibleSystem found and callback issued.
234     return true;
235 }
236 
237 /**
238  * @brief Make callbacks on
239  * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances.
240  *
241  * Look for an instance of
242  * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided
243  * argument and if found, issue the provided callback.
244  *
245  * @param[in] message the DBus message in which to look for an instance of
246  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
247  * @param[in] callback the user callback to make if
248  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in
249  * message
250  * @return true if message contained an instance of
251  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise
252  */
253 bool maybeCallMessage(sdbusplus::message::message& message,
254                       const MaybeCallCallbackType& callback)
255 {
256     std::map<std::string,
257              std::map<std::string, std::variant<std::vector<std::string>>>>
258         interfacesAndProperties;
259     sdbusplus::message::object_path _;
260     message.read(_, interfacesAndProperties);
261     return maybeCall(interfacesAndProperties, callback);
262 }
263 
264 /**
265  * @brief Determine system support for host firmware well-known names.
266  *
267  * Using the provided extensionMap and
268  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, determine if
269  * well-known names for host firmare blob files are necessary and if so, create
270  * them.
271  *
272  * @param[in] extensionMap a map of
273  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
274  * file extensions.
275  * @param[in] hostFirmwareDirectory The directory in which findLinks should
276  * look for host firmware blob files that need well-known names.
277  * @param[in] ibmCompatibleSystem The names property of an instance of
278  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
279  * @param[in] errorCallback A callback made in the event of filesystem errors.
280  */
281 void maybeMakeLinks(
282     const std::map<std::string, std::vector<std::string>>& extensionMap,
283     const std::filesystem::path& hostFirmwareDirectory,
284     const std::vector<std::string>& ibmCompatibleSystem,
285     const ErrorCallbackType& errorCallback)
286 {
287     std::vector<std::string> extensions;
288     if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem,
289                                             extensions))
290     {
291         findLinks(hostFirmwareDirectory, extensions, errorCallback, writeLink);
292     }
293 }
294 
295 /**
296  * @brief process host firmware
297  *
298  * Allocate a callback context and register for DBus.ObjectManager Interfaces
299  * added signals from entity manager.
300  *
301  * Check the current entity manager object tree for a
302  * xyz.openbmc_project.Configuration.IBMCompatibleSystem instance (entity
303  * manager will be dbus activated if it is not running).  If one is found,
304  * determine if symlinks need to be created and create them.  Instruct the
305  * program event loop to exit.
306  *
307  * If no instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem is
308  * found return the callback context to main, where the program will sleep
309  * until the callback is invoked one or more times and instructs the program
310  * event loop to exit when
311  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is added.
312  *
313  * @param[in] bus a DBus client connection
314  * @param[in] extensionMap a map of
315  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
316  * file extensions.
317  * @param[in] hostFirmwareDirectory The directory in which processHostFirmware
318  * should look for blob files.
319  * @param[in] errorCallback A callback made in the event of filesystem errors.
320  * @param[in] loop a program event loop
321  * @return nullptr if an instance of
322  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found, otherwise a
323  * pointer to an sdbusplus match object.
324  */
325 std::shared_ptr<void> processHostFirmware(
326     sdbusplus::bus::bus& bus,
327     std::map<std::string, std::vector<std::string>> extensionMap,
328     std::filesystem::path hostFirmwareDirectory,
329     ErrorCallbackType errorCallback, sdeventplus::Event& loop)
330 {
331     // ownership of extensionMap, hostFirmwareDirectory and errorCallback can't
332     // be transfered to the match callback because they are needed in the non
333     // async part of this function below, so they need to be moved to the heap.
334     auto pExtensionMap =
335         std::make_shared<decltype(extensionMap)>(std::move(extensionMap));
336     auto pHostFirmwareDirectory =
337         std::make_shared<decltype(hostFirmwareDirectory)>(
338             std::move(hostFirmwareDirectory));
339     auto pErrorCallback =
340         std::make_shared<decltype(errorCallback)>(std::move(errorCallback));
341 
342     // register for a callback in case the IBMCompatibleSystem interface has
343     // not yet been published by entity manager.
344     auto interfacesAddedMatch = std::make_shared<sdbusplus::bus::match::match>(
345         bus,
346         sdbusplus::bus::match::rules::interfacesAdded() +
347             sdbusplus::bus::match::rules::sender(
348                 "xyz.openbmc_project.EntityManager"),
349         [pExtensionMap, pHostFirmwareDirectory, pErrorCallback,
350          &loop](auto& message) {
351             // bind the extension map, host firmware directory, and error
352             // callback to the maybeMakeLinks function.
353             auto maybeMakeLinksWithArgsBound =
354                 std::bind(maybeMakeLinks, std::cref(*pExtensionMap),
355                           std::cref(*pHostFirmwareDirectory),
356                           std::placeholders::_1, std::cref(*pErrorCallback));
357 
358             // if the InterfacesAdded message contains an an instance of
359             // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to
360             // see if links are necessary on this system and if so, create
361             // them.
362             if (maybeCallMessage(message, maybeMakeLinksWithArgsBound))
363             {
364                 // The IBMCompatibleSystem interface was found and the links
365                 // were created if applicable.  Instruct the event loop /
366                 // subcommand to exit.
367                 loop.exit(0);
368             }
369         });
370 
371     // now that we'll get a callback in the event of an InterfacesAdded signal
372     // (potentially containing
373     // xyz.openbmc_project.Configuration.IBMCompatibleSystem), activate entity
374     // manager if it isn't running and enumerate its objects
375     auto getManagedObjects = bus.new_method_call(
376         "xyz.openbmc_project.EntityManager", "/",
377         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
378     auto reply = bus.call(getManagedObjects);
379     std::map<std::string,
380              std::map<std::string, std::variant<std::vector<std::string>>>>
381         interfacesAndProperties;
382     std::map<sdbusplus::message::object_path, decltype(interfacesAndProperties)>
383         objects;
384     reply.read(objects);
385 
386     // bind the extension map, host firmware directory, and error callback to
387     // the maybeMakeLinks function.
388     auto maybeMakeLinksWithArgsBound =
389         std::bind(maybeMakeLinks, std::cref(*pExtensionMap),
390                   std::cref(*pHostFirmwareDirectory), std::placeholders::_1,
391                   std::cref(*pErrorCallback));
392 
393     for (const auto& pair : objects)
394     {
395         std::tie(std::ignore, interfacesAndProperties) = pair;
396         // if interfacesAndProperties contains an an instance of
397         // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to see
398         // if links are necessary on this system and if so, create them
399         if (maybeCall(interfacesAndProperties, maybeMakeLinksWithArgsBound))
400         {
401             // The IBMCompatibleSystem interface is already on the bus and the
402             // links were created if applicable.  Instruct the event loop to
403             // exit.
404             loop.exit(0);
405             // The match object isn't needed anymore, so destroy it on return.
406             return nullptr;
407         }
408     }
409 
410     // The IBMCompatibleSystem interface has not yet been published.  Move
411     // ownership of the match callback to the caller.
412     return interfacesAddedMatch;
413 }
414 } // namespace process_hostfirmware
415 } // namespace functions
416