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