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