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