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 static 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