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