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