xref: /openbmc/bmcweb/redfish-core/include/utils/sw_utils.hpp (revision 40e9b92ec19acffb46f83a6e55b18974da5d708e)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #pragma once
4 #include "async_resp.hpp"
5 #include "dbus_utility.hpp"
6 #include "error_messages.hpp"
7 #include "generated/enums/resource.hpp"
8 #include "http/utility.hpp"
9 #include "utils/dbus_utils.hpp"
10 
11 #include <boost/system/error_code.hpp>
12 #include <boost/url/format.hpp>
13 #include <sdbusplus/asio/property.hpp>
14 #include <sdbusplus/unpack_properties.hpp>
15 
16 #include <algorithm>
17 #include <array>
18 #include <optional>
19 #include <ranges>
20 #include <string>
21 #include <string_view>
22 #include <vector>
23 
24 namespace redfish
25 {
26 namespace sw_util
27 {
28 /* @brief String that indicates a bios software instance */
29 constexpr const char* biosPurpose =
30     "xyz.openbmc_project.Software.Version.VersionPurpose.Host";
31 
32 /* @brief String that indicates a BMC software instance */
33 constexpr const char* bmcPurpose =
34     "xyz.openbmc_project.Software.Version.VersionPurpose.BMC";
35 
36 inline std::optional<sdbusplus::message::object_path>
getFunctionalSoftwarePath(const std::string & swType)37     getFunctionalSoftwarePath(const std::string& swType)
38 {
39     if (swType == bmcPurpose)
40     {
41         if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
42         {
43             return sdbusplus::message::object_path(
44                 "/xyz/openbmc_project/software/bmc/functional");
45         }
46         else
47         {
48             return sdbusplus::message::object_path(
49                 "/xyz/openbmc_project/software/functional");
50         }
51     }
52     else if (swType == biosPurpose)
53     {
54         if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
55         {
56             return sdbusplus::message::object_path(
57                 "/xyz/openbmc_project/software/bios/functional");
58         }
59         else
60         {
61             return sdbusplus::message::object_path(
62                 "/xyz/openbmc_project/software/functional");
63         }
64     }
65     else
66     {
67         BMCWEB_LOG_ERROR("No valid software path");
68         return std::nullopt;
69     }
70 }
71 
afterGetProperties(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & swId,bool runningImage,const std::string & swVersionPurpose,const std::string & activeVersionPropName,bool populateLinkToImages,const boost::system::error_code & ec,const dbus::utility::DBusPropertiesMap & propertiesList)72 inline void afterGetProperties(
73     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
74     const std::string& swId, bool runningImage,
75     const std::string& swVersionPurpose,
76     const std::string& activeVersionPropName, bool populateLinkToImages,
77     const boost::system::error_code& ec,
78     const dbus::utility::DBusPropertiesMap& propertiesList)
79 {
80     if (ec)
81     {
82         BMCWEB_LOG_ERROR("error_code = {}", ec);
83         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
84         // Have seen the code update app delete the
85         // D-Bus object, during code update, between
86         // the call to mapper and here. Just leave
87         // these properties off if resource not
88         // found.
89         if (ec.value() == EBADR)
90         {
91             return;
92         }
93         messages::internalError(asyncResp->res);
94         return;
95     }
96     // example propertiesList
97     // a{sv} 2 "Version" s
98     // "IBM-witherspoon-OP9-v2.0.10-2.22" "Purpose"
99     // s
100     // "xyz.openbmc_project.Software.Version.VersionPurpose.Host"
101     const std::string* version = nullptr;
102     const std::string* swInvPurpose = nullptr;
103 
104     const bool success = sdbusplus::unpackPropertiesNoThrow(
105         dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose",
106         swInvPurpose, "Version", version);
107 
108     if (!success)
109     {
110         messages::internalError(asyncResp->res);
111         return;
112     }
113 
114     if (version == nullptr || version->empty())
115     {
116         messages::internalError(asyncResp->res);
117         return;
118     }
119     if (swInvPurpose == nullptr || *swInvPurpose != swVersionPurpose)
120     {
121         // Not purpose we're looking for
122         return;
123     }
124 
125     BMCWEB_LOG_DEBUG("Image ID: {}", swId);
126     BMCWEB_LOG_DEBUG("Running image: {}", runningImage);
127     BMCWEB_LOG_DEBUG("Image purpose: {}", *swInvPurpose);
128 
129     if (populateLinkToImages)
130     {
131         nlohmann::json& softwareImageMembers =
132             asyncResp->res.jsonValue["Links"]["SoftwareImages"];
133         // Firmware images are at
134         // /redfish/v1/UpdateService/FirmwareInventory/<Id>
135         // e.g. .../FirmwareInventory/82d3ec86
136         nlohmann::json::object_t member;
137         member["@odata.id"] = boost::urls::format(
138             "/redfish/v1/UpdateService/FirmwareInventory/{}", swId);
139         softwareImageMembers.emplace_back(std::move(member));
140         asyncResp->res.jsonValue["Links"]["SoftwareImages@odata.count"] =
141             softwareImageMembers.size();
142 
143         if (runningImage)
144         {
145             nlohmann::json::object_t runningMember;
146             runningMember["@odata.id"] = boost::urls::format(
147                 "/redfish/v1/UpdateService/FirmwareInventory/{}", swId);
148             // Create the link to the running image
149             asyncResp->res.jsonValue["Links"]["ActiveSoftwareImage"] =
150                 std::move(runningMember);
151         }
152     }
153     if (!activeVersionPropName.empty() && runningImage)
154     {
155         asyncResp->res.jsonValue[activeVersionPropName] = *version;
156     }
157 }
158 
afterGetSubtree(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & swVersionPurpose,const std::string & activeVersionPropName,bool populateLinkToImages,const std::vector<std::string> & functionalSwIds,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)159 inline void afterGetSubtree(
160     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
161     const std::string& swVersionPurpose,
162     const std::string& activeVersionPropName, bool populateLinkToImages,
163     const std::vector<std::string>& functionalSwIds,
164     const boost::system::error_code& ec,
165     const dbus::utility::MapperGetSubTreeResponse& subtree)
166 {
167     if (ec)
168     {
169         BMCWEB_LOG_ERROR("error_code = {}", ec);
170         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
171         messages::internalError(asyncResp->res);
172         return;
173     }
174 
175     BMCWEB_LOG_DEBUG("Found {} images", subtree.size());
176 
177     for (const std::pair<
178              std::string,
179              std::vector<std::pair<std::string, std::vector<std::string>>>>&
180              obj : subtree)
181     {
182         sdbusplus::message::object_path path(obj.first);
183         std::string swId = path.filename();
184         if (swId.empty())
185         {
186             messages::internalError(asyncResp->res);
187             BMCWEB_LOG_ERROR("Invalid software ID");
188 
189             return;
190         }
191 
192         bool runningImage = false;
193         // Look at Ids from
194         // /xyz/openbmc_project/software/functional
195         // to determine if this is
196         // a running image
197         if (std::ranges::find(functionalSwIds, swId) != functionalSwIds.end())
198         {
199             runningImage = true;
200         }
201 
202         // Now grab its version
203         // info
204         dbus::utility::getAllProperties(
205             *crow::connections::systemBus, obj.second[0].first, obj.first,
206             "xyz.openbmc_project.Software.Version",
207             [asyncResp, swId, runningImage, swVersionPurpose,
208              activeVersionPropName, populateLinkToImages](
209                 const boost::system::error_code& ec3,
210                 const dbus::utility::DBusPropertiesMap& propertiesList) {
211                 afterGetProperties(asyncResp, swId, runningImage,
212                                    swVersionPurpose, activeVersionPropName,
213                                    populateLinkToImages, ec3, propertiesList);
214             });
215     }
216 }
217 
afterAssociatedEndpoints(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & swVersionPurpose,const std::string & activeVersionPropName,bool populateLinkToImages,const boost::system::error_code & ec,const dbus::utility::MapperEndPoints & functionalSw)218 inline void afterAssociatedEndpoints(
219     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
220     const std::string& swVersionPurpose,
221     const std::string& activeVersionPropName, bool populateLinkToImages,
222     const boost::system::error_code& ec,
223     const dbus::utility::MapperEndPoints& functionalSw)
224 {
225     BMCWEB_LOG_DEBUG("populateSoftwareInformation enter");
226     if (ec)
227     {
228         BMCWEB_LOG_DEBUG("error_code = {}", ec);
229         BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
230         // No functional software for this swVersionPurpose, so just
231         return;
232     }
233 
234     if (functionalSw.empty())
235     {
236         // Could keep going and try to populate SoftwareImages
237         // but something is seriously wrong, so just fail
238         BMCWEB_LOG_ERROR("Zero functional software in system");
239         messages::internalError(asyncResp->res);
240         return;
241     }
242 
243     std::vector<std::string> functionalSwIds;
244     // example functionalSw:
245     // v as 2 "/xyz/openbmc_project/software/ace821ef"
246     //        "/xyz/openbmc_project/software/230fb078"
247     for (const auto& sw : functionalSw)
248     {
249         sdbusplus::message::object_path path(sw);
250         std::string leaf = path.filename();
251         if (leaf.empty())
252         {
253             continue;
254         }
255 
256         functionalSwIds.push_back(leaf);
257     }
258 
259     constexpr std::array<std::string_view, 1> interfaces = {
260         "xyz.openbmc_project.Software.Version"};
261     dbus::utility::getSubTree(
262         "/xyz/openbmc_project/software", 0, interfaces,
263         [asyncResp, swVersionPurpose, activeVersionPropName,
264          populateLinkToImages, functionalSwIds](
265             const boost::system::error_code& ec2,
266             const dbus::utility::MapperGetSubTreeResponse& subtree) {
267             afterGetSubtree(asyncResp, swVersionPurpose, activeVersionPropName,
268                             populateLinkToImages, functionalSwIds, ec2,
269                             subtree);
270         });
271 }
272 
273 /**
274  * @brief Populate the running software version and image links
275  *
276  * @param[i,o] asyncResp             Async response object
277  * @param[i]   swVersionPurpose  Indicates what target to look for
278  * @param[i]   activeVersionPropName  Index in asyncResp->res.jsonValue to write
279  * the running software version to
280  * @param[i]   populateLinkToImages  Populate asyncResp->res "Links"
281  * "ActiveSoftwareImage" with a link to the running software image and
282  * "SoftwareImages" with a link to the all its software images
283  *
284  * @return void
285  */
populateSoftwareInformation(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & swVersionPurpose,const std::string & activeVersionPropName,const bool populateLinkToImages)286 inline void populateSoftwareInformation(
287     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
288     const std::string& swVersionPurpose,
289     const std::string& activeVersionPropName, const bool populateLinkToImages)
290 {
291     auto swPath = getFunctionalSoftwarePath(swVersionPurpose);
292     if (!swPath)
293     {
294         BMCWEB_LOG_ERROR("Invalid software type");
295         messages::internalError(asyncResp->res);
296         return;
297     }
298     // Used later to determine running (known on Redfish as active) Sw images
299     dbus::utility::getAssociationEndPoints(
300         swPath.value().str,
301         [asyncResp, swVersionPurpose, activeVersionPropName,
302          populateLinkToImages](
303             const boost::system::error_code& ec,
304             const dbus::utility::MapperEndPoints& functionalSw) {
305             afterAssociatedEndpoints(asyncResp, swVersionPurpose,
306                                      activeVersionPropName,
307                                      populateLinkToImages, ec, functionalSw);
308         });
309 }
310 
311 /**
312  * @brief Translate input swState to Redfish state
313  *
314  * This function will return the corresponding Redfish state
315  *
316  * @param[i]   swState  The OpenBMC software state
317  *
318  * @return The corresponding Redfish state
319  */
getRedfishSwState(const std::string & swState)320 inline resource::State getRedfishSwState(const std::string& swState)
321 {
322     if (swState == "xyz.openbmc_project.Software.Activation.Activations.Active")
323     {
324         return resource::State::Enabled;
325     }
326     if (swState == "xyz.openbmc_project.Software.Activation."
327                    "Activations.Activating")
328     {
329         return resource::State::Updating;
330     }
331     if (swState == "xyz.openbmc_project.Software.Activation."
332                    "Activations.StandbySpare")
333     {
334         return resource::State::StandbySpare;
335     }
336     BMCWEB_LOG_DEBUG("Default sw state {} to Disabled", swState);
337     return resource::State::Disabled;
338 }
339 
340 /**
341  * @brief Translate input swState to Redfish health state
342  *
343  * This function will return the corresponding Redfish health state
344  *
345  * @param[i]   swState  The OpenBMC software state
346  *
347  * @return The corresponding Redfish health state
348  */
getRedfishSwHealth(const std::string & swState)349 inline std::string getRedfishSwHealth(const std::string& swState)
350 {
351     if ((swState ==
352          "xyz.openbmc_project.Software.Activation.Activations.Active") ||
353         (swState == "xyz.openbmc_project.Software.Activation.Activations."
354                     "Activating") ||
355         (swState ==
356          "xyz.openbmc_project.Software.Activation.Activations.Ready"))
357     {
358         return "OK";
359     }
360     BMCWEB_LOG_DEBUG("Sw state {} to Warning", swState);
361     return "Warning";
362 }
363 
364 /**
365  * @brief Put status of input swId into json response
366  *
367  * This function will put the appropriate Redfish state of the input
368  * software id to ["Status"]["State"] within the json response
369  *
370  * @param[i,o] asyncResp    Async response object
371  * @param[i]   swId     The software ID to get status for
372  * @param[i]   dbusSvc  The dbus service implementing the software object
373  *
374  * @return void
375  */
getSwStatus(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::shared_ptr<std::string> & swId,const std::string & dbusSvc)376 inline void getSwStatus(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
377                         const std::shared_ptr<std::string>& swId,
378                         const std::string& dbusSvc)
379 {
380     BMCWEB_LOG_DEBUG("getSwStatus: swId {} svc {}", *swId, dbusSvc);
381 
382     dbus::utility::getAllProperties(
383         dbusSvc, "/xyz/openbmc_project/software/" + *swId,
384         "xyz.openbmc_project.Software.Activation",
385         [asyncResp,
386          swId](const boost::system::error_code& ec,
387                const dbus::utility::DBusPropertiesMap& propertiesList) {
388             if (ec)
389             {
390                 // not all swtypes are updateable, this is ok
391                 asyncResp->res.jsonValue["Status"]["State"] =
392                     resource::State::Enabled;
393                 return;
394             }
395 
396             const std::string* swInvActivation = nullptr;
397 
398             const bool success = sdbusplus::unpackPropertiesNoThrow(
399                 dbus_utils::UnpackErrorPrinter(), propertiesList, "Activation",
400                 swInvActivation);
401 
402             if (!success)
403             {
404                 messages::internalError(asyncResp->res);
405                 return;
406             }
407 
408             if (swInvActivation == nullptr)
409             {
410                 messages::internalError(asyncResp->res);
411                 return;
412             }
413 
414             BMCWEB_LOG_DEBUG("getSwStatus: Activation {}", *swInvActivation);
415             asyncResp->res.jsonValue["Status"]["State"] =
416                 getRedfishSwState(*swInvActivation);
417             asyncResp->res.jsonValue["Status"]["Health"] =
418                 getRedfishSwHealth(*swInvActivation);
419         });
420 }
421 
handleUpdateableEndpoints(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::shared_ptr<std::string> & swId,const boost::system::error_code & ec,const dbus::utility::MapperEndPoints & objPaths)422 inline void handleUpdateableEndpoints(
423     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
424     const std::shared_ptr<std::string>& swId,
425     const boost::system::error_code& ec,
426     const dbus::utility::MapperEndPoints& objPaths)
427 {
428     if (ec)
429     {
430         BMCWEB_LOG_DEBUG(" error_code = {} error msg =  {}", ec, ec.message());
431         // System can exist with no updateable software,
432         // so don't throw error here.
433         return;
434     }
435     sdbusplus::message::object_path reqSwObjPath(
436         "/xyz/openbmc_project/software");
437     reqSwObjPath = reqSwObjPath / *swId;
438 
439     if (std::ranges::find(objPaths, reqSwObjPath.str) != objPaths.end())
440     {
441         asyncResp->res.jsonValue["Updateable"] = true;
442         return;
443     }
444 }
445 
446 inline void
handleUpdateableObject(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec,const dbus::utility::MapperGetObject & objectInfo)447     handleUpdateableObject(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
448                            const boost::system::error_code& ec,
449                            const dbus::utility::MapperGetObject& objectInfo)
450 {
451     if (ec)
452     {
453         BMCWEB_LOG_DEBUG(" error_code = {} error msg =  {}", ec, ec.message());
454         // System can exist with no updateable software,
455         // so don't throw error here.
456         return;
457     }
458     if (objectInfo.empty())
459     {
460         BMCWEB_LOG_DEBUG("No updateable software found");
461         // System can exist with no updateable software,
462         // so don't throw error here.
463         return;
464     }
465     asyncResp->res.jsonValue["Updateable"] = true;
466 }
467 
468 /**
469  * @brief Updates programmable status of input swId into json response
470  *
471  * This function checks whether software inventory component
472  * can be programmable or not and fill's the "Updatable"
473  * Property.
474  *
475  * @param[i,o] asyncResp  Async response object
476  * @param[i]   swId       The software ID
477  */
478 inline void
getSwUpdatableStatus(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::shared_ptr<std::string> & swId)479     getSwUpdatableStatus(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
480                          const std::shared_ptr<std::string>& swId)
481 {
482     if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
483     {
484         sdbusplus::message::object_path swObjectPath(
485             "/xyz/openbmc_project/software");
486         swObjectPath = swObjectPath / *swId;
487         constexpr std::array<std::string_view, 1> interfaces = {
488             "xyz.openbmc_project.Software.Update"};
489         dbus::utility::getDbusObject(
490             swObjectPath.str, interfaces,
491             std::bind_front(handleUpdateableObject, asyncResp));
492     }
493     else
494     {
495         dbus::utility::getAssociationEndPoints(
496             "/xyz/openbmc_project/software/updateable",
497             std::bind_front(handleUpdateableEndpoints, asyncResp, swId));
498     }
499 }
500 
501 } // namespace sw_util
502 } // namespace redfish
503