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