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