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