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