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