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