1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
4 #pragma once
5
6 #include "bmcweb_config.h"
7
8 #include "app.hpp"
9 #include "async_resp.hpp"
10 #include "dbus_singleton.hpp"
11 #include "dbus_utility.hpp"
12 #include "error_messages.hpp"
13 #include "generated/enums/resource.hpp"
14 #include "generated/enums/update_service.hpp"
15 #include "http_request.hpp"
16 #include "http_response.hpp"
17 #include "io_context_singleton.hpp"
18 #include "logging.hpp"
19 #include "multipart_parser.hpp"
20 #include "ossl_random.hpp"
21 #include "query.hpp"
22 #include "registries/privilege_registry.hpp"
23 #include "str_utility.hpp"
24 #include "task.hpp"
25 #include "task_messages.hpp"
26 #include "utility.hpp"
27 #include "utils/collection.hpp"
28 #include "utils/dbus_utils.hpp"
29 #include "utils/json_utils.hpp"
30 #include "utils/sw_utils.hpp"
31
32 #include <sys/mman.h>
33 #include <unistd.h>
34
35 #include <boost/asio/error.hpp>
36 #include <boost/asio/steady_timer.hpp>
37 #include <boost/beast/http/fields.hpp>
38 #include <boost/beast/http/status.hpp>
39 #include <boost/beast/http/verb.hpp>
40 #include <boost/system/error_code.hpp>
41 #include <boost/system/result.hpp>
42 #include <boost/url/format.hpp>
43 #include <boost/url/parse.hpp>
44 #include <boost/url/url.hpp>
45 #include <boost/url/url_view.hpp>
46 #include <boost/url/url_view_base.hpp>
47 #include <sdbusplus/asio/property.hpp>
48 #include <sdbusplus/bus/match.hpp>
49 #include <sdbusplus/message.hpp>
50 #include <sdbusplus/message/native_types.hpp>
51 #include <sdbusplus/unpack_properties.hpp>
52
53 #include <algorithm>
54 #include <array>
55 #include <chrono>
56 #include <cstddef>
57 #include <cstdint>
58 #include <cstdio>
59 #include <filesystem>
60 #include <format>
61 #include <fstream>
62 #include <functional>
63 #include <memory>
64 #include <optional>
65 #include <string>
66 #include <string_view>
67 #include <unordered_map>
68 #include <utility>
69 #include <variant>
70 #include <vector>
71
72 namespace redfish
73 {
74
75 // Match signals added on software path
76 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
77 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateMatcher;
78 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
79 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateErrorMatcher;
80 // Only allow one update at a time
81 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
82 static bool fwUpdateInProgress = false;
83 // Timer for software available
84 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
85 static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer;
86
87 /* @brief String that indicates the Software Update D-Bus interface */
88 constexpr const char* updateInterface = "xyz.openbmc_project.Software.Update";
89
90 struct MemoryFileDescriptor
91 {
92 int fd = -1;
93
MemoryFileDescriptorredfish::MemoryFileDescriptor94 explicit MemoryFileDescriptor(const std::string& filename) :
95 fd(memfd_create(filename.c_str(), 0))
96 {}
97
98 MemoryFileDescriptor(const MemoryFileDescriptor&) = default;
MemoryFileDescriptorredfish::MemoryFileDescriptor99 MemoryFileDescriptor(MemoryFileDescriptor&& other) noexcept : fd(other.fd)
100 {
101 other.fd = -1;
102 }
103 MemoryFileDescriptor& operator=(const MemoryFileDescriptor&) = delete;
104 MemoryFileDescriptor& operator=(MemoryFileDescriptor&&) = default;
105
~MemoryFileDescriptorredfish::MemoryFileDescriptor106 ~MemoryFileDescriptor()
107 {
108 if (fd != -1)
109 {
110 close(fd);
111 }
112 }
113
rewindredfish::MemoryFileDescriptor114 bool rewind() const
115 {
116 if (lseek(fd, 0, SEEK_SET) == -1)
117 {
118 BMCWEB_LOG_ERROR("Failed to seek to beginning of image memfd");
119 return false;
120 }
121 return true;
122 }
123 };
124
cleanUp()125 inline void cleanUp()
126 {
127 fwUpdateInProgress = false;
128 fwUpdateMatcher = nullptr;
129 fwUpdateErrorMatcher = nullptr;
130 }
131
activateImage(const std::string & objPath,const std::string & service)132 inline void activateImage(const std::string& objPath,
133 const std::string& service)
134 {
135 BMCWEB_LOG_DEBUG("Activate image for {} {}", objPath, service);
136 sdbusplus::asio::setProperty(
137 *crow::connections::systemBus, service, objPath,
138 "xyz.openbmc_project.Software.Activation", "RequestedActivation",
139 "xyz.openbmc_project.Software.Activation.RequestedActivations.Active",
140 [](const boost::system::error_code& ec) {
141 if (ec)
142 {
143 BMCWEB_LOG_DEBUG("error_code = {}", ec);
144 BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
145 }
146 });
147 }
148
handleCreateTask(const boost::system::error_code & ec2,sdbusplus::message_t & msg,const std::shared_ptr<task::TaskData> & taskData)149 inline bool handleCreateTask(const boost::system::error_code& ec2,
150 sdbusplus::message_t& msg,
151 const std::shared_ptr<task::TaskData>& taskData)
152 {
153 if (ec2)
154 {
155 return task::completed;
156 }
157
158 std::string iface;
159 dbus::utility::DBusPropertiesMap values;
160
161 std::string index = std::to_string(taskData->index);
162 msg.read(iface, values);
163
164 if (iface == "xyz.openbmc_project.Software.Activation")
165 {
166 const std::string* state = nullptr;
167 for (const auto& property : values)
168 {
169 if (property.first == "Activation")
170 {
171 state = std::get_if<std::string>(&property.second);
172 if (state == nullptr)
173 {
174 taskData->messages.emplace_back(messages::internalError());
175 return task::completed;
176 }
177 }
178 }
179
180 if (state == nullptr)
181 {
182 return !task::completed;
183 }
184
185 if (state->ends_with("Invalid") || state->ends_with("Failed"))
186 {
187 taskData->state = "Exception";
188 taskData->status = "Warning";
189 taskData->messages.emplace_back(messages::taskAborted(index));
190 return task::completed;
191 }
192
193 if (state->ends_with("Staged"))
194 {
195 taskData->state = "Stopping";
196 taskData->messages.emplace_back(messages::taskPaused(index));
197
198 // its staged, set a long timer to
199 // allow them time to complete the
200 // update (probably cycle the
201 // system) if this expires then
202 // task will be canceled
203 taskData->extendTimer(std::chrono::hours(5));
204 return !task::completed;
205 }
206
207 if (state->ends_with("Active"))
208 {
209 taskData->messages.emplace_back(messages::taskCompletedOK(index));
210 taskData->state = "Completed";
211 return task::completed;
212 }
213 }
214 else if (iface == "xyz.openbmc_project.Software.ActivationProgress")
215 {
216 const uint8_t* progress = nullptr;
217 for (const auto& property : values)
218 {
219 if (property.first == "Progress")
220 {
221 progress = std::get_if<uint8_t>(&property.second);
222 if (progress == nullptr)
223 {
224 taskData->messages.emplace_back(messages::internalError());
225 return task::completed;
226 }
227 }
228 }
229
230 if (progress == nullptr)
231 {
232 return !task::completed;
233 }
234 taskData->percentComplete = *progress;
235 taskData->messages.emplace_back(
236 messages::taskProgressChanged(index, *progress));
237
238 // if we're getting status updates it's
239 // still alive, update timer
240 taskData->extendTimer(std::chrono::minutes(5));
241 }
242
243 // as firmware update often results in a
244 // reboot, the task may never "complete"
245 // unless it is an error
246
247 return !task::completed;
248 }
249
createTask(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload && payload,const sdbusplus::message::object_path & objPath)250 inline void createTask(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
251 task::Payload&& payload,
252 const sdbusplus::message::object_path& objPath)
253 {
254 std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
255 std::bind_front(handleCreateTask),
256 "type='signal',interface='org.freedesktop.DBus.Properties',"
257 "member='PropertiesChanged',path='" +
258 objPath.str + "'");
259 task->startTimer(std::chrono::minutes(5));
260 task->payload.emplace(std::move(payload));
261 task->populateResp(asyncResp->res);
262 }
263
264 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
265 // then no asyncResp updates will occur
softwareInterfaceAdded(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,sdbusplus::message_t & m,task::Payload && payload)266 inline void softwareInterfaceAdded(
267 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
268 sdbusplus::message_t& m, task::Payload&& payload)
269 {
270 dbus::utility::DBusInterfacesMap interfacesProperties;
271
272 sdbusplus::message::object_path objPath;
273
274 m.read(objPath, interfacesProperties);
275
276 BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
277 for (const auto& interface : interfacesProperties)
278 {
279 BMCWEB_LOG_DEBUG("interface = {}", interface.first);
280
281 if (interface.first == "xyz.openbmc_project.Software.Activation")
282 {
283 // Retrieve service and activate
284 constexpr std::array<std::string_view, 1> interfaces = {
285 "xyz.openbmc_project.Software.Activation"};
286 dbus::utility::getDbusObject(
287 objPath.str, interfaces,
288 [objPath, asyncResp, payload(std::move(payload))](
289 const boost::system::error_code& ec,
290 const std::vector<
291 std::pair<std::string, std::vector<std::string>>>&
292 objInfo) mutable {
293 if (ec)
294 {
295 BMCWEB_LOG_DEBUG("error_code = {}", ec);
296 BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
297 if (asyncResp)
298 {
299 messages::internalError(asyncResp->res);
300 }
301 cleanUp();
302 return;
303 }
304 // Ensure we only got one service back
305 if (objInfo.size() != 1)
306 {
307 BMCWEB_LOG_ERROR("Invalid Object Size {}",
308 objInfo.size());
309 if (asyncResp)
310 {
311 messages::internalError(asyncResp->res);
312 }
313 cleanUp();
314 return;
315 }
316 // cancel timer only when
317 // xyz.openbmc_project.Software.Activation interface
318 // is added
319 fwAvailableTimer = nullptr;
320
321 activateImage(objPath.str, objInfo[0].first);
322 if (asyncResp)
323 {
324 createTask(asyncResp, std::move(payload), objPath);
325 }
326 fwUpdateInProgress = false;
327 });
328
329 break;
330 }
331 }
332 }
333
afterAvailbleTimerAsyncWait(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec)334 inline void afterAvailbleTimerAsyncWait(
335 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
336 const boost::system::error_code& ec)
337 {
338 cleanUp();
339 if (ec == boost::asio::error::operation_aborted)
340 {
341 // expected, we were canceled before the timer completed.
342 return;
343 }
344 BMCWEB_LOG_ERROR("Timed out waiting for firmware object being created");
345 BMCWEB_LOG_ERROR("FW image may has already been uploaded to server");
346 if (ec)
347 {
348 BMCWEB_LOG_ERROR("Async_wait failed{}", ec);
349 return;
350 }
351 if (asyncResp)
352 {
353 redfish::messages::internalError(asyncResp->res);
354 }
355 }
356
handleUpdateErrorType(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & url,const std::string & type)357 inline void handleUpdateErrorType(
358 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& url,
359 const std::string& type)
360 {
361 // NOLINTBEGIN(bugprone-branch-clone)
362 if (type == "xyz.openbmc_project.Software.Image.Error.UnTarFailure")
363 {
364 messages::missingOrMalformedPart(asyncResp->res);
365 }
366 else if (type ==
367 "xyz.openbmc_project.Software.Image.Error.ManifestFileFailure")
368 {
369 messages::missingOrMalformedPart(asyncResp->res);
370 }
371 else if (type == "xyz.openbmc_project.Software.Image.Error.ImageFailure")
372 {
373 messages::missingOrMalformedPart(asyncResp->res);
374 }
375 else if (type == "xyz.openbmc_project.Software.Version.Error.AlreadyExists")
376 {
377 messages::resourceAlreadyExists(asyncResp->res, "UpdateService",
378 "Version", "uploaded version");
379 }
380 else if (type == "xyz.openbmc_project.Software.Image.Error.BusyFailure")
381 {
382 messages::serviceTemporarilyUnavailable(asyncResp->res, url);
383 }
384 else if (type == "xyz.openbmc_project.Software.Version.Error.Incompatible")
385 {
386 messages::internalError(asyncResp->res);
387 }
388 else if (type ==
389 "xyz.openbmc_project.Software.Version.Error.ExpiredAccessKey")
390 {
391 messages::internalError(asyncResp->res);
392 }
393 else if (type ==
394 "xyz.openbmc_project.Software.Version.Error.InvalidSignature")
395 {
396 messages::missingOrMalformedPart(asyncResp->res);
397 }
398 else if (type ==
399 "xyz.openbmc_project.Software.Image.Error.InternalFailure" ||
400 type == "xyz.openbmc_project.Software.Version.Error.HostFile")
401 {
402 BMCWEB_LOG_ERROR("Software Image Error type={}", type);
403 messages::internalError(asyncResp->res);
404 }
405 else
406 {
407 // Unrelated error types. Ignored
408 BMCWEB_LOG_INFO("Non-Software-related Error type={}. Ignored", type);
409 return;
410 }
411 // NOLINTEND(bugprone-branch-clone)
412 // Clear the timer
413 fwAvailableTimer = nullptr;
414 }
415
afterUpdateErrorMatcher(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & url,sdbusplus::message_t & m)416 inline void afterUpdateErrorMatcher(
417 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& url,
418 sdbusplus::message_t& m)
419 {
420 dbus::utility::DBusInterfacesMap interfacesProperties;
421 sdbusplus::message::object_path objPath;
422 m.read(objPath, interfacesProperties);
423 BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
424 for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>&
425 interface : interfacesProperties)
426 {
427 if (interface.first == "xyz.openbmc_project.Logging.Entry")
428 {
429 for (const std::pair<std::string, dbus::utility::DbusVariantType>&
430 value : interface.second)
431 {
432 if (value.first != "Message")
433 {
434 continue;
435 }
436 const std::string* type =
437 std::get_if<std::string>(&value.second);
438 if (type == nullptr)
439 {
440 // if this was our message, timeout will cover it
441 return;
442 }
443 handleUpdateErrorType(asyncResp, url, *type);
444 }
445 }
446 }
447 }
448
449 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
450 // then no asyncResp updates will occur
monitorForSoftwareAvailable(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const crow::Request & req,const std::string & url,int timeoutTimeSeconds=50)451 inline void monitorForSoftwareAvailable(
452 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
453 const crow::Request& req, const std::string& url,
454 int timeoutTimeSeconds = 50)
455 {
456 // Only allow one FW update at a time
457 if (fwUpdateInProgress)
458 {
459 if (asyncResp)
460 {
461 messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
462 }
463 return;
464 }
465
466 fwAvailableTimer =
467 std::make_unique<boost::asio::steady_timer>(getIoContext());
468
469 fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds));
470
471 fwAvailableTimer->async_wait(
472 std::bind_front(afterAvailbleTimerAsyncWait, asyncResp));
473
474 task::Payload payload(req);
475 auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable {
476 BMCWEB_LOG_DEBUG("Match fired");
477 softwareInterfaceAdded(asyncResp, m, std::move(payload));
478 };
479
480 fwUpdateInProgress = true;
481
482 fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>(
483 *crow::connections::systemBus,
484 "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
485 "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
486 callback);
487
488 fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>(
489 *crow::connections::systemBus,
490 "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
491 "member='InterfacesAdded',"
492 "path='/xyz/openbmc_project/logging'",
493 std::bind_front(afterUpdateErrorMatcher, asyncResp, url));
494 }
495
parseSimpleUpdateUrl(std::string imageURI,std::optional<std::string> transferProtocol,crow::Response & res)496 inline std::optional<boost::urls::url> parseSimpleUpdateUrl(
497 std::string imageURI, std::optional<std::string> transferProtocol,
498 crow::Response& res)
499 {
500 if (imageURI.find("://") == std::string::npos)
501 {
502 if (imageURI.starts_with("/"))
503 {
504 messages::actionParameterValueTypeError(
505 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
506 return std::nullopt;
507 }
508 if (!transferProtocol)
509 {
510 messages::actionParameterValueTypeError(
511 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
512 return std::nullopt;
513 }
514 // OpenBMC currently only supports HTTPS
515 if (*transferProtocol == "HTTPS")
516 {
517 imageURI = "https://" + imageURI;
518 }
519 else
520 {
521 messages::actionParameterNotSupported(res, "TransferProtocol",
522 *transferProtocol);
523 BMCWEB_LOG_ERROR("Request incorrect protocol parameter: {}",
524 *transferProtocol);
525 return std::nullopt;
526 }
527 }
528
529 boost::system::result<boost::urls::url> url =
530 boost::urls::parse_absolute_uri(imageURI);
531 if (!url)
532 {
533 messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
534 "UpdateService.SimpleUpdate");
535
536 return std::nullopt;
537 }
538 url->normalize();
539
540 if (url->scheme() == "tftp")
541 {
542 if (url->encoded_path().size() < 2)
543 {
544 messages::actionParameterNotSupported(res, "ImageURI",
545 url->buffer());
546 return std::nullopt;
547 }
548 }
549 else if (url->scheme() == "https")
550 {
551 // Empty paths default to "/"
552 if (url->encoded_path().empty())
553 {
554 url->set_encoded_path("/");
555 }
556 }
557 else
558 {
559 messages::actionParameterNotSupported(res, "ImageURI", imageURI);
560 return std::nullopt;
561 }
562
563 if (url->encoded_path().empty())
564 {
565 messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
566 "UpdateService.SimpleUpdate");
567 return std::nullopt;
568 }
569
570 return *url;
571 }
572
doHttpsUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::urls::url_view_base & url)573 inline void doHttpsUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
574 const boost::urls::url_view_base& url)
575 {
576 messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
577 url.buffer());
578 }
579
handleUpdateServiceSimpleUpdateAction(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)580 inline void handleUpdateServiceSimpleUpdateAction(
581 crow::App& app, const crow::Request& req,
582 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
583 {
584 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
585 {
586 return;
587 }
588
589 std::optional<std::string> transferProtocol;
590 std::string imageURI;
591
592 BMCWEB_LOG_DEBUG("Enter UpdateService.SimpleUpdate doPost");
593
594 // User can pass in both TransferProtocol and ImageURI parameters or
595 // they can pass in just the ImageURI with the transfer protocol
596 // embedded within it.
597 // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin
598 // 2) ImageURI:tftp://1.1.1.1/myfile.bin
599
600 if (!json_util::readJsonAction( //
601 req, asyncResp->res, //
602 "ImageURI", imageURI, //
603 "TransferProtocol", transferProtocol //
604 ))
605 {
606 BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter");
607 return;
608 }
609
610 std::optional<boost::urls::url> url =
611 parseSimpleUpdateUrl(imageURI, transferProtocol, asyncResp->res);
612 if (!url)
613 {
614 return;
615 }
616 if (url->scheme() == "https")
617 {
618 doHttpsUpdate(asyncResp, *url);
619 }
620 else
621 {
622 messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
623 url->buffer());
624 return;
625 }
626
627 BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost");
628 }
629
uploadImageFile(crow::Response & res,std::string_view body)630 inline void uploadImageFile(crow::Response& res, std::string_view body)
631 {
632 std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID());
633
634 BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string());
635 std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
636 std::ofstream::trunc);
637 // set the permission of the file to 640
638 std::filesystem::perms permission =
639 std::filesystem::perms::owner_read | std::filesystem::perms::group_read;
640 std::filesystem::permissions(filepath, permission);
641 out << body;
642
643 if (out.bad())
644 {
645 messages::internalError(res);
646 cleanUp();
647 }
648 }
649
650 // Convert the Request Apply Time to the D-Bus value
convertApplyTime(crow::Response & res,const std::string & applyTime,std::string & applyTimeNewVal)651 inline bool convertApplyTime(crow::Response& res, const std::string& applyTime,
652 std::string& applyTimeNewVal)
653 {
654 if (applyTime == "Immediate")
655 {
656 applyTimeNewVal =
657 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
658 }
659 else if (applyTime == "OnReset")
660 {
661 applyTimeNewVal =
662 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
663 }
664 else
665 {
666 BMCWEB_LOG_WARNING(
667 "ApplyTime value {} is not in the list of acceptable values",
668 applyTime);
669 messages::propertyValueNotInList(res, applyTime, "ApplyTime");
670 return false;
671 }
672 return true;
673 }
674
setApplyTime(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & applyTime)675 inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
676 const std::string& applyTime)
677 {
678 std::string applyTimeNewVal;
679 if (!convertApplyTime(asyncResp->res, applyTime, applyTimeNewVal))
680 {
681 return;
682 }
683
684 setDbusProperty(asyncResp, "ApplyTime", "xyz.openbmc_project.Settings",
685 sdbusplus::message::object_path(
686 "/xyz/openbmc_project/software/apply_time"),
687 "xyz.openbmc_project.Software.ApplyTime",
688 "RequestedApplyTime", applyTimeNewVal);
689 }
690
691 struct MultiPartUpdate
692 {
693 std::string uploadData;
694 struct UpdateParameters
695 {
696 std::optional<std::string> applyTime;
697 std::optional<std::vector<std::string>> targets;
698 } params;
699 };
700
processUrl(boost::system::result<boost::urls::url_view> & url)701 inline std::optional<std::string> processUrl(
702 boost::system::result<boost::urls::url_view>& url)
703 {
704 if (!url)
705 {
706 return std::nullopt;
707 }
708 if (crow::utility::readUrlSegments(*url, "redfish", "v1", "Managers",
709 BMCWEB_REDFISH_MANAGER_URI_NAME))
710 {
711 return std::make_optional(std::string(BMCWEB_REDFISH_MANAGER_URI_NAME));
712 }
713 if constexpr (!BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
714 {
715 return std::nullopt;
716 }
717 std::string firmwareId;
718 if (!crow::utility::readUrlSegments(*url, "redfish", "v1", "UpdateService",
719 "FirmwareInventory",
720 std::ref(firmwareId)))
721 {
722 return std::nullopt;
723 }
724
725 return std::make_optional(firmwareId);
726 }
727
parseFormPartName(const boost::beast::http::fields::const_iterator & contentDisposition)728 inline std::optional<std::string> parseFormPartName(
729 const boost::beast::http::fields::const_iterator& contentDisposition)
730 {
731 size_t semicolonPos = contentDisposition->value().find(';');
732 if (semicolonPos == std::string::npos)
733 {
734 return std::nullopt;
735 }
736
737 for (const auto& param : boost::beast::http::param_list{
738 contentDisposition->value().substr(semicolonPos)})
739 {
740 if (param.first == "name" && !param.second.empty())
741 {
742 return std::string(param.second);
743 }
744 }
745 return std::nullopt;
746 }
747
processUpdateParameters(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string_view content)748 inline std::optional<MultiPartUpdate::UpdateParameters> processUpdateParameters(
749 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
750 std::string_view content)
751 {
752 MultiPartUpdate::UpdateParameters multiRet;
753 nlohmann::json jsonContent = nlohmann::json::parse(content, nullptr, false);
754 if (jsonContent.is_discarded())
755 {
756 return std::nullopt;
757 }
758 nlohmann::json::object_t* obj =
759 jsonContent.get_ptr<nlohmann::json::object_t*>();
760 if (obj == nullptr)
761 {
762 messages::propertyValueTypeError(asyncResp->res, content,
763 "UpdateParameters");
764 return std::nullopt;
765 }
766
767 if (!json_util::readJsonObject( //
768 *obj, asyncResp->res, //
769 "@Redfish.OperationApplyTime", multiRet.applyTime, //
770 "Targets", multiRet.targets //
771 ))
772 {
773 return std::nullopt;
774 }
775
776 if (multiRet.targets)
777 {
778 if (multiRet.targets->size() > 1)
779 {
780 messages::propertyValueFormatError(asyncResp->res,
781 *multiRet.targets, "Targets");
782 return std::nullopt;
783 }
784
785 for (auto& target : *multiRet.targets)
786 {
787 boost::system::result<boost::urls::url_view> url =
788 boost::urls::parse_origin_form(target);
789 auto res = processUrl(url);
790 if (!res.has_value())
791 {
792 messages::propertyValueFormatError(asyncResp->res, target,
793 "Targets");
794 return std::nullopt;
795 }
796 target = res.value();
797 }
798 }
799
800 return multiRet;
801 }
802
extractMultipartUpdateParameters(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,MultipartParser parser)803 inline std::optional<MultiPartUpdate> extractMultipartUpdateParameters(
804 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, MultipartParser parser)
805 {
806 MultiPartUpdate multiRet;
807 for (FormPart& formpart : parser.mime_fields)
808 {
809 boost::beast::http::fields::const_iterator it =
810 formpart.fields.find("Content-Disposition");
811 if (it == formpart.fields.end())
812 {
813 BMCWEB_LOG_ERROR("Couldn't find Content-Disposition");
814 return std::nullopt;
815 }
816 BMCWEB_LOG_INFO("Parsing value {}", it->value());
817
818 auto formFieldNameOpt = parseFormPartName(it);
819 if (!formFieldNameOpt.has_value())
820 {
821 continue;
822 }
823
824 const std::string& formFieldName = formFieldNameOpt.value();
825
826 if (formFieldName == "UpdateParameters")
827 {
828 std::optional<MultiPartUpdate::UpdateParameters> params =
829 processUpdateParameters(asyncResp, formpart.content);
830 if (!params)
831 {
832 return std::nullopt;
833 }
834 multiRet.params = std::move(*params);
835 }
836 else if (formFieldName == "UpdateFile")
837 {
838 multiRet.uploadData = std::move(formpart.content);
839 }
840 }
841
842 if (multiRet.uploadData.empty())
843 {
844 BMCWEB_LOG_ERROR("Upload data is NULL");
845 messages::propertyMissing(asyncResp->res, "UpdateFile");
846 return std::nullopt;
847 }
848
849 return multiRet;
850 }
851
handleStartUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload payload,const std::string & objectPath,const boost::system::error_code & ec,const sdbusplus::message::object_path & retPath)852 inline void handleStartUpdate(
853 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
854 const std::string& objectPath, const boost::system::error_code& ec,
855 const sdbusplus::message::object_path& retPath)
856 {
857 if (ec)
858 {
859 BMCWEB_LOG_ERROR("error_code = {}", ec);
860 BMCWEB_LOG_ERROR("error msg = {}", ec.message());
861 messages::internalError(asyncResp->res);
862 return;
863 }
864
865 BMCWEB_LOG_INFO("Call to StartUpdate on {} Success, retPath = {}",
866 objectPath, retPath.str);
867 createTask(asyncResp, std::move(payload), retPath);
868 }
869
startUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload payload,const MemoryFileDescriptor & memfd,const std::string & applyTime,const std::string & objectPath,const std::string & serviceName)870 inline void startUpdate(
871 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
872 const MemoryFileDescriptor& memfd, const std::string& applyTime,
873 const std::string& objectPath, const std::string& serviceName)
874 {
875 dbus::utility::async_method_call(
876 asyncResp,
877 [asyncResp, payload = std::move(payload),
878 objectPath](const boost::system::error_code& ec1,
879 const sdbusplus::message::object_path& retPath) mutable {
880 handleStartUpdate(asyncResp, std::move(payload), objectPath, ec1,
881 retPath);
882 },
883 serviceName, objectPath, updateInterface, "StartUpdate",
884 sdbusplus::message::unix_fd(memfd.fd), applyTime);
885 }
886
getSwInfo(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload payload,const MemoryFileDescriptor & memfd,const std::string & applyTime,const std::string & target,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)887 inline void getSwInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
888 task::Payload payload, const MemoryFileDescriptor& memfd,
889 const std::string& applyTime, const std::string& target,
890 const boost::system::error_code& ec,
891 const dbus::utility::MapperGetSubTreeResponse& subtree)
892 {
893 using SwInfoMap = std::unordered_map<
894 std::string, std::pair<sdbusplus::message::object_path, std::string>>;
895 SwInfoMap swInfoMap;
896
897 if (ec)
898 {
899 BMCWEB_LOG_ERROR("error_code = {}", ec);
900 BMCWEB_LOG_ERROR("error msg = {}", ec.message());
901 messages::internalError(asyncResp->res);
902 return;
903 }
904 BMCWEB_LOG_DEBUG("Found {} software version paths", subtree.size());
905
906 for (const auto& entry : subtree)
907 {
908 sdbusplus::message::object_path path(entry.first);
909 std::string swId = path.filename();
910 swInfoMap.emplace(swId, make_pair(path, entry.second[0].first));
911 }
912
913 auto swEntry = swInfoMap.find(target);
914 if (swEntry == swInfoMap.end())
915 {
916 BMCWEB_LOG_WARNING("No valid DBus path for Target URI {}", target);
917 messages::propertyValueFormatError(asyncResp->res, target, "Targets");
918 return;
919 }
920
921 BMCWEB_LOG_DEBUG("Found software version path {} serviceName {}",
922 swEntry->second.first.str, swEntry->second.second);
923
924 startUpdate(asyncResp, std::move(payload), memfd, applyTime,
925 swEntry->second.first.str, swEntry->second.second);
926 }
927
handleBMCUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload payload,const MemoryFileDescriptor & memfd,const std::string & applyTime,const boost::system::error_code & ec,const dbus::utility::MapperEndPoints & functionalSoftware)928 inline void handleBMCUpdate(
929 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
930 const MemoryFileDescriptor& memfd, const std::string& applyTime,
931 const boost::system::error_code& ec,
932 const dbus::utility::MapperEndPoints& functionalSoftware)
933 {
934 if (ec)
935 {
936 BMCWEB_LOG_ERROR("error_code = {}", ec);
937 BMCWEB_LOG_ERROR("error msg = {}", ec.message());
938 messages::internalError(asyncResp->res);
939 return;
940 }
941 if (functionalSoftware.size() != 1)
942 {
943 BMCWEB_LOG_ERROR("Found {} functional software endpoints",
944 functionalSoftware.size());
945 messages::internalError(asyncResp->res);
946 return;
947 }
948
949 startUpdate(asyncResp, std::move(payload), memfd, applyTime,
950 functionalSoftware[0], "xyz.openbmc_project.Software.Manager");
951 }
952
handleMultipartManagerUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload payload,const MemoryFileDescriptor & memfd,const std::string & applyTime,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)953 inline void handleMultipartManagerUpdate(
954 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
955 const MemoryFileDescriptor& memfd, const std::string& applyTime,
956 const boost::system::error_code& ec,
957 const dbus::utility::MapperGetSubTreeResponse& subtree)
958 {
959 if (ec)
960 {
961 BMCWEB_LOG_ERROR("error_code = {}", ec);
962 BMCWEB_LOG_ERROR("error msg = {}", ec.message());
963 messages::internalError(asyncResp->res);
964 return;
965 }
966 if (subtree.size() != 1)
967 {
968 BMCWEB_LOG_ERROR("Found {} MultipartUpdate objects, expected exactly 1",
969 subtree.size());
970 messages::internalError(asyncResp->res);
971 return;
972 }
973
974 const auto& [objectPath, services] = subtree[0];
975 for (const auto& [serviceName, ifaces] : services)
976 {
977 if (std::ranges::find(ifaces, updateInterface) != ifaces.end())
978 {
979 startUpdate(asyncResp, std::move(payload), memfd, applyTime,
980 objectPath, serviceName);
981 return;
982 }
983 }
984 BMCWEB_LOG_ERROR(
985 "MultipartUpdate object at {} does not implement {} interface",
986 objectPath, updateInterface);
987 messages::internalError(asyncResp->res);
988 }
989
processUpdateRequest(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload && payload,std::string_view body,const std::string & applyTime,const std::vector<std::string> & targets)990 inline void processUpdateRequest(
991 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
992 task::Payload&& payload, std::string_view body,
993 const std::string& applyTime, const std::vector<std::string>& targets)
994 {
995 MemoryFileDescriptor memfd("update-image");
996 if (memfd.fd == -1)
997 {
998 BMCWEB_LOG_ERROR("Failed to create image memfd");
999 messages::internalError(asyncResp->res);
1000 return;
1001 }
1002 if (write(memfd.fd, body.data(), body.length()) !=
1003 static_cast<ssize_t>(body.length()))
1004 {
1005 BMCWEB_LOG_ERROR("Failed to write to image memfd");
1006 messages::internalError(asyncResp->res);
1007 return;
1008 }
1009 if (!memfd.rewind())
1010 {
1011 messages::internalError(asyncResp->res);
1012 return;
1013 }
1014
1015 if (targets.empty())
1016 {
1017 constexpr std::array<std::string_view, 1> interfaces = {
1018 "xyz.openbmc_project.Software.MultipartUpdate"};
1019 dbus::utility::getSubTree(
1020 "/xyz/openbmc_project/software", 1, interfaces,
1021 [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
1022 applyTime](const boost::system::error_code& ec,
1023 const dbus::utility::MapperGetSubTreeResponse&
1024 subtree) mutable {
1025 handleMultipartManagerUpdate(asyncResp, std::move(payload),
1026 memfd, applyTime, ec, subtree);
1027 });
1028 return;
1029 }
1030
1031 if (targets[0] == BMCWEB_REDFISH_MANAGER_URI_NAME)
1032 {
1033 dbus::utility::getAssociationEndPoints(
1034 "/xyz/openbmc_project/software/bmc/updateable",
1035 [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
1036 applyTime](
1037 const boost::system::error_code& ec,
1038 const dbus::utility::MapperEndPoints& objectPaths) mutable {
1039 handleBMCUpdate(asyncResp, std::move(payload), memfd, applyTime,
1040 ec, objectPaths);
1041 });
1042 }
1043 else
1044 {
1045 constexpr std::array<std::string_view, 1> interfaces = {
1046 "xyz.openbmc_project.Software.Version"};
1047 dbus::utility::getSubTree(
1048 "/xyz/openbmc_project/software", 1, interfaces,
1049 [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
1050 applyTime, targets](const boost::system::error_code& ec,
1051 const dbus::utility::MapperGetSubTreeResponse&
1052 subtree) mutable {
1053 getSwInfo(asyncResp, std::move(payload), memfd, applyTime,
1054 targets[0], ec, subtree);
1055 });
1056 }
1057 }
1058
updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const crow::Request & req,MultipartParser && parser)1059 inline void updateMultipartContext(
1060 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1061 const crow::Request& req, MultipartParser&& parser)
1062 {
1063 std::optional<MultiPartUpdate> multipart =
1064 extractMultipartUpdateParameters(asyncResp, std::move(parser));
1065 if (!multipart)
1066 {
1067 return;
1068 }
1069 if (!multipart->params.applyTime)
1070 {
1071 multipart->params.applyTime = "OnReset";
1072 }
1073
1074 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
1075 {
1076 std::string applyTimeNewVal;
1077 if (!convertApplyTime(asyncResp->res, *multipart->params.applyTime,
1078 applyTimeNewVal))
1079 {
1080 return;
1081 }
1082 task::Payload payload(req);
1083
1084 processUpdateRequest(
1085 asyncResp, std::move(payload), multipart->uploadData,
1086 applyTimeNewVal,
1087 multipart->params.targets.value_or(std::vector<std::string>{}));
1088 }
1089 else
1090 {
1091 setApplyTime(asyncResp, *multipart->params.applyTime);
1092
1093 // Setup callback for when new software detected
1094 monitorForSoftwareAvailable(asyncResp, req,
1095 "/redfish/v1/UpdateService");
1096
1097 uploadImageFile(asyncResp->res, multipart->uploadData);
1098 }
1099 }
1100
doHTTPUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const crow::Request & req)1101 inline void doHTTPUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1102 const crow::Request& req)
1103 {
1104 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
1105 {
1106 task::Payload payload(req);
1107 // HTTP push only supports BMC updates (with ApplyTime as immediate) for
1108 // backwards compatibility. Specific component updates will be handled
1109 // through Multipart form HTTP push.
1110 std::vector<std::string> targets;
1111 targets.emplace_back(BMCWEB_REDFISH_MANAGER_URI_NAME);
1112
1113 processUpdateRequest(
1114 asyncResp, std::move(payload), req.body(),
1115 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate",
1116 targets);
1117 }
1118 else
1119 {
1120 // Setup callback for when new software detected
1121 monitorForSoftwareAvailable(asyncResp, req,
1122 "/redfish/v1/UpdateService");
1123
1124 uploadImageFile(asyncResp->res, req.body());
1125 }
1126 }
1127
handleUpdateServicePost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1128 inline void handleUpdateServicePost(
1129 App& app, const crow::Request& req,
1130 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1131 {
1132 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1133 {
1134 return;
1135 }
1136 std::string_view contentType = req.getHeaderValue("Content-Type");
1137
1138 BMCWEB_LOG_DEBUG("doPost: contentType={}", contentType);
1139
1140 // Make sure that content type is application/octet-stream
1141 if (bmcweb::asciiIEquals(contentType, "application/octet-stream"))
1142 {
1143 doHTTPUpdate(asyncResp, req);
1144 }
1145 else
1146 {
1147 BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType);
1148 asyncResp->res.result(boost::beast::http::status::bad_request);
1149 }
1150 }
1151
handleUpdateServiceMultipartUpdatePost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1152 inline void handleUpdateServiceMultipartUpdatePost(
1153 App& app, const crow::Request& req,
1154 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1155 {
1156 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1157 {
1158 return;
1159 }
1160
1161 std::string_view contentType = req.getHeaderValue("Content-Type");
1162 // Make sure that content type is multipart/form-data
1163 if (contentType.starts_with("multipart/form-data"))
1164 {
1165 MultipartParser parser;
1166
1167 ParserError ec = parser.parse(req);
1168 if (ec != ParserError::PARSER_SUCCESS)
1169 {
1170 // handle error
1171 BMCWEB_LOG_ERROR("MIME parse failed, ec : {}",
1172 static_cast<int>(ec));
1173 messages::internalError(asyncResp->res);
1174 return;
1175 }
1176
1177 updateMultipartContext(asyncResp, req, std::move(parser));
1178 }
1179 else
1180 {
1181 BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType);
1182 asyncResp->res.result(
1183 boost::beast::http::status::unsupported_media_type);
1184 }
1185 }
1186
handleUpdateServiceGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1187 inline void handleUpdateServiceGet(
1188 App& app, const crow::Request& req,
1189 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1190 {
1191 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1192 {
1193 return;
1194 }
1195 asyncResp->res.jsonValue["@odata.type"] =
1196 "#UpdateService.v1_11_1.UpdateService";
1197 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService";
1198 asyncResp->res.jsonValue["Id"] = "UpdateService";
1199 asyncResp->res.jsonValue["Description"] = "Service for Software Update";
1200 asyncResp->res.jsonValue["Name"] = "Update Service";
1201
1202 asyncResp->res.jsonValue["HttpPushUri"] =
1203 "/redfish/v1/UpdateService/update";
1204 asyncResp->res.jsonValue["MultipartHttpPushUri"] =
1205 "/redfish/v1/UpdateService/update-multipart";
1206
1207 // UpdateService cannot be disabled
1208 asyncResp->res.jsonValue["ServiceEnabled"] = true;
1209 asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] =
1210 "/redfish/v1/UpdateService/FirmwareInventory";
1211 // Get the MaxImageSizeBytes
1212 asyncResp->res.jsonValue["MaxImageSizeBytes"] =
1213 BMCWEB_HTTP_BODY_LIMIT * 1024 * 1024;
1214
1215 if constexpr (BMCWEB_REDFISH_ALLOW_SIMPLE_UPDATE)
1216 {
1217 // Update Actions object.
1218 nlohmann::json& updateSvcSimpleUpdate =
1219 asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
1220 updateSvcSimpleUpdate["target"] =
1221 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
1222
1223 nlohmann::json::array_t allowed;
1224 allowed.emplace_back(update_service::TransferProtocolType::HTTPS);
1225 updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] =
1226 std::move(allowed);
1227 }
1228
1229 asyncResp->res
1230 .jsonValue["HttpPushUriOptions"]["HttpPushUriApplyTime"]["ApplyTime"] =
1231 update_service::ApplyTime::Immediate;
1232 }
1233
handleUpdateServiceFirmwareInventoryCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1234 inline void handleUpdateServiceFirmwareInventoryCollectionGet(
1235 App& app, const crow::Request& req,
1236 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1237 {
1238 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1239 {
1240 return;
1241 }
1242 asyncResp->res.jsonValue["@odata.type"] =
1243 "#SoftwareInventoryCollection.SoftwareInventoryCollection";
1244 asyncResp->res.jsonValue["@odata.id"] =
1245 "/redfish/v1/UpdateService/FirmwareInventory";
1246 asyncResp->res.jsonValue["Name"] = "Software Inventory Collection";
1247 const std::array<const std::string_view, 1> iface = {
1248 "xyz.openbmc_project.Software.Version"};
1249
1250 redfish::collection_util::getCollectionMembers(
1251 asyncResp,
1252 boost::urls::url("/redfish/v1/UpdateService/FirmwareInventory"), iface,
1253 "/xyz/openbmc_project/software");
1254 }
1255
addRelatedItem(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::urls::url & url)1256 inline void addRelatedItem(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1257 const boost::urls::url& url)
1258 {
1259 nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
1260 nlohmann::json::object_t item;
1261 item["@odata.id"] = url;
1262 relatedItem.emplace_back(std::move(item));
1263 asyncResp->res.jsonValue["RelatedItem@odata.count"] = relatedItem.size();
1264 }
1265
1266 /* Fill related item links (i.e. bmc, bios) in for inventory */
getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & purpose)1267 inline void getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1268 const std::string& purpose)
1269 {
1270 if (purpose == sw_util::bmcPurpose)
1271 {
1272 auto url = boost::urls::format("/redfish/v1/Managers/{}",
1273 BMCWEB_REDFISH_MANAGER_URI_NAME);
1274 addRelatedItem(asyncResp, url);
1275 }
1276 else if (purpose == sw_util::biosPurpose)
1277 {
1278 auto url = boost::urls::format("/redfish/v1/Systems/{}/Bios",
1279 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1280
1281 addRelatedItem(asyncResp, url);
1282 }
1283 else
1284 {
1285 BMCWEB_LOG_DEBUG("Unknown software purpose {}", purpose);
1286 }
1287 }
1288
getSoftwareVersionCallback(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & swId,const boost::system::error_code & ec,const dbus::utility::DBusPropertiesMap & propertiesList)1289 inline void getSoftwareVersionCallback(
1290 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1291 const std::string& swId, const boost::system::error_code& ec,
1292 const dbus::utility::DBusPropertiesMap& propertiesList)
1293 {
1294 if (ec)
1295 {
1296 messages::internalError(asyncResp->res);
1297 return;
1298 }
1299 const std::string* swInvPurpose = nullptr;
1300 const std::string* version = nullptr;
1301 const bool success = sdbusplus::unpackPropertiesNoThrow(
1302 dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose",
1303 swInvPurpose, "Version", version);
1304 if (!success)
1305 {
1306 messages::internalError(asyncResp->res);
1307 return;
1308 }
1309 if (swInvPurpose == nullptr)
1310 {
1311 BMCWEB_LOG_DEBUG("Can't find property \"Purpose\"!");
1312 messages::internalError(asyncResp->res);
1313 return;
1314 }
1315 BMCWEB_LOG_DEBUG("swInvPurpose = {}", *swInvPurpose);
1316 if (version == nullptr)
1317 {
1318 BMCWEB_LOG_DEBUG("Can't find property \"Version\"!");
1319 messages::internalError(asyncResp->res);
1320 return;
1321 }
1322 asyncResp->res.jsonValue["Version"] = *version;
1323 asyncResp->res.jsonValue["Id"] = swId;
1324 // swInvPurpose is of format:
1325 // xyz.openbmc_project.Software.Version.VersionPurpose.ABC
1326 // Translate this to "ABC image"
1327 size_t endDesc = swInvPurpose->rfind('.');
1328 if (endDesc == std::string::npos)
1329 {
1330 messages::internalError(asyncResp->res);
1331 return;
1332 }
1333 endDesc++;
1334 if (endDesc >= swInvPurpose->size())
1335 {
1336 messages::internalError(asyncResp->res);
1337 return;
1338 }
1339 std::string formatDesc = swInvPurpose->substr(endDesc);
1340 asyncResp->res.jsonValue["Description"] = formatDesc + " image";
1341 getRelatedItems(asyncResp, *swInvPurpose);
1342 }
1343
getSoftwareVersion(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & service,const std::string & path,const std::string & swId)1344 inline void getSoftwareVersion(
1345 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1346 const std::string& service, const std::string& path,
1347 const std::string& swId)
1348 {
1349 dbus::utility::getAllProperties(
1350 service, path, "xyz.openbmc_project.Software.Version",
1351 std::bind_front(getSoftwareVersionCallback, asyncResp, swId));
1352 }
1353
handleUpdateServiceFirmwareInventoryGetCallback(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::shared_ptr<std::string> & swId,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)1354 inline void handleUpdateServiceFirmwareInventoryGetCallback(
1355 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1356 const std::shared_ptr<std::string>& swId,
1357 const boost::system::error_code& ec,
1358 const dbus::utility::MapperGetSubTreeResponse& subtree)
1359 {
1360 BMCWEB_LOG_DEBUG("doGet callback...");
1361 if (ec)
1362 {
1363 messages::internalError(asyncResp->res);
1364 return;
1365 }
1366 // Ensure we find our input swId, otherwise return an error
1367 bool found = false;
1368 for (const std::pair<
1369 std::string,
1370 std::vector<std::pair<std::string, std::vector<std::string>>>>&
1371 obj : subtree)
1372 {
1373 sdbusplus::message::object_path path(obj.first);
1374 std::string id = path.filename();
1375 if (id.empty())
1376 {
1377 BMCWEB_LOG_DEBUG("Failed to find software id in {}", obj.first);
1378 continue;
1379 }
1380 if (id != *swId)
1381 {
1382 continue;
1383 }
1384 if (obj.second.empty())
1385 {
1386 continue;
1387 }
1388 found = true;
1389 sw_util::getSwStatus(asyncResp, swId, obj.second[0].first);
1390 sw_util::getSwMinimumVersion(asyncResp, swId, obj.second[0].first);
1391 getSoftwareVersion(asyncResp, obj.second[0].first, obj.first, *swId);
1392 }
1393 if (!found)
1394 {
1395 BMCWEB_LOG_WARNING("Input swID {} not found!", *swId);
1396 messages::resourceMissingAtURI(
1397 asyncResp->res,
1398 boost::urls::format(
1399 "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId));
1400 return;
1401 }
1402 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
1403 "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId);
1404 asyncResp->res.jsonValue["@odata.type"] =
1405 "#SoftwareInventory.v1_1_0.SoftwareInventory";
1406 asyncResp->res.jsonValue["Name"] = "Software Inventory";
1407 asyncResp->res.jsonValue["Status"]["HealthRollup"] = resource::Health::OK;
1408 asyncResp->res.jsonValue["Updateable"] = false;
1409 sw_util::getSwUpdatableStatus(asyncResp, swId);
1410 }
1411
handleUpdateServiceFirmwareInventoryGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & param)1412 inline void handleUpdateServiceFirmwareInventoryGet(
1413 App& app, const crow::Request& req,
1414 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1415 const std::string& param)
1416 {
1417 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1418 {
1419 return;
1420 }
1421 std::shared_ptr<std::string> swId = std::make_shared<std::string>(param);
1422
1423 constexpr std::array<std::string_view, 1> interfaces = {
1424 "xyz.openbmc_project.Software.Version"};
1425 dbus::utility::getSubTree(
1426 "/xyz/openbmc_project/software/", 0, interfaces,
1427 std::bind_front(handleUpdateServiceFirmwareInventoryGetCallback,
1428 asyncResp, swId));
1429 }
1430
requestRoutesUpdateService(App & app)1431 inline void requestRoutesUpdateService(App& app)
1432 {
1433 if constexpr (BMCWEB_REDFISH_ALLOW_SIMPLE_UPDATE)
1434 {
1435 BMCWEB_ROUTE(
1436 app,
1437 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/")
1438 .privileges(redfish::privileges::postUpdateService)
1439 .methods(boost::beast::http::verb::post)(std::bind_front(
1440 handleUpdateServiceSimpleUpdateAction, std::ref(app)));
1441 }
1442 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/")
1443 .privileges(redfish::privileges::getSoftwareInventory)
1444 .methods(boost::beast::http::verb::get)(std::bind_front(
1445 handleUpdateServiceFirmwareInventoryGet, std::ref(app)));
1446
1447 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
1448 .privileges(redfish::privileges::getUpdateService)
1449 .methods(boost::beast::http::verb::get)(
1450 std::bind_front(handleUpdateServiceGet, std::ref(app)));
1451
1452 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/")
1453 .privileges(redfish::privileges::postUpdateService)
1454 .methods(boost::beast::http::verb::post)(
1455 std::bind_front(handleUpdateServicePost, std::ref(app)));
1456
1457 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update-multipart/")
1458 .privileges(redfish::privileges::postUpdateService)
1459 .methods(boost::beast::http::verb::post)(std::bind_front(
1460 handleUpdateServiceMultipartUpdatePost, std::ref(app)));
1461
1462 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/")
1463 .privileges(redfish::privileges::getSoftwareInventoryCollection)
1464 .methods(boost::beast::http::verb::get)(std::bind_front(
1465 handleUpdateServiceFirmwareInventoryCollectionGet, std::ref(app)));
1466 }
1467
1468 } // namespace redfish
1469