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( //
598 req, asyncResp->res, //
599 "ImageURI", imageURI, //
600 "TransferProtocol", transferProtocol //
601 ))
602 {
603 BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter");
604 return;
605 }
606
607 std::optional<boost::urls::url> url =
608 parseSimpleUpdateUrl(imageURI, transferProtocol, asyncResp->res);
609 if (!url)
610 {
611 return;
612 }
613 if (url->scheme() == "https")
614 {
615 doHttpsUpdate(asyncResp, *url);
616 }
617 else
618 {
619 messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
620 url->buffer());
621 return;
622 }
623
624 BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost");
625 }
626
uploadImageFile(crow::Response & res,std::string_view body)627 inline void uploadImageFile(crow::Response& res, std::string_view body)
628 {
629 std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID());
630
631 BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string());
632 std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
633 std::ofstream::trunc);
634 // set the permission of the file to 640
635 std::filesystem::perms permission =
636 std::filesystem::perms::owner_read | std::filesystem::perms::group_read;
637 std::filesystem::permissions(filepath, permission);
638 out << body;
639
640 if (out.bad())
641 {
642 messages::internalError(res);
643 cleanUp();
644 }
645 }
646
647 // Convert the Request Apply Time to the D-Bus value
convertApplyTime(crow::Response & res,const std::string & applyTime,std::string & applyTimeNewVal)648 inline bool convertApplyTime(crow::Response& res, const std::string& applyTime,
649 std::string& applyTimeNewVal)
650 {
651 if (applyTime == "Immediate")
652 {
653 applyTimeNewVal =
654 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
655 }
656 else if (applyTime == "OnReset")
657 {
658 applyTimeNewVal =
659 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
660 }
661 else
662 {
663 BMCWEB_LOG_WARNING(
664 "ApplyTime value {} is not in the list of acceptable values",
665 applyTime);
666 messages::propertyValueNotInList(res, applyTime, "ApplyTime");
667 return false;
668 }
669 return true;
670 }
671
setApplyTime(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & applyTime)672 inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
673 const std::string& applyTime)
674 {
675 std::string applyTimeNewVal;
676 if (!convertApplyTime(asyncResp->res, applyTime, applyTimeNewVal))
677 {
678 return;
679 }
680
681 setDbusProperty(asyncResp, "ApplyTime", "xyz.openbmc_project.Settings",
682 sdbusplus::message::object_path(
683 "/xyz/openbmc_project/software/apply_time"),
684 "xyz.openbmc_project.Software.ApplyTime",
685 "RequestedApplyTime", applyTimeNewVal);
686 }
687
688 struct MultiPartUpdateParameters
689 {
690 std::optional<std::string> applyTime;
691 std::string uploadData;
692 std::vector<std::string> targets;
693 };
694
695 inline std::optional<std::string>
processUrl(boost::system::result<boost::urls::url_view> & url)696 processUrl(boost::system::result<boost::urls::url_view>& url)
697 {
698 if (!url)
699 {
700 return std::nullopt;
701 }
702 if (crow::utility::readUrlSegments(*url, "redfish", "v1", "Managers",
703 BMCWEB_REDFISH_MANAGER_URI_NAME))
704 {
705 return std::make_optional(std::string(BMCWEB_REDFISH_MANAGER_URI_NAME));
706 }
707 if constexpr (!BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
708 {
709 return std::nullopt;
710 }
711 std::string firmwareId;
712 if (!crow::utility::readUrlSegments(*url, "redfish", "v1", "UpdateService",
713 "FirmwareInventory",
714 std::ref(firmwareId)))
715 {
716 return std::nullopt;
717 }
718
719 return std::make_optional(firmwareId);
720 }
721
722 inline std::optional<MultiPartUpdateParameters>
extractMultipartUpdateParameters(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,MultipartParser parser)723 extractMultipartUpdateParameters(
724 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
725 MultipartParser parser)
726 {
727 MultiPartUpdateParameters multiRet;
728 for (FormPart& formpart : parser.mime_fields)
729 {
730 boost::beast::http::fields::const_iterator it =
731 formpart.fields.find("Content-Disposition");
732 if (it == formpart.fields.end())
733 {
734 BMCWEB_LOG_ERROR("Couldn't find Content-Disposition");
735 return std::nullopt;
736 }
737 BMCWEB_LOG_INFO("Parsing value {}", it->value());
738
739 // The construction parameters of param_list must start with `;`
740 size_t index = it->value().find(';');
741 if (index == std::string::npos)
742 {
743 continue;
744 }
745
746 for (const auto& param :
747 boost::beast::http::param_list{it->value().substr(index)})
748 {
749 if (param.first != "name" || param.second.empty())
750 {
751 continue;
752 }
753
754 if (param.second == "UpdateParameters")
755 {
756 std::vector<std::string> tempTargets;
757 nlohmann::json content =
758 nlohmann::json::parse(formpart.content, nullptr, false);
759 if (content.is_discarded())
760 {
761 return std::nullopt;
762 }
763 nlohmann::json::object_t* obj =
764 content.get_ptr<nlohmann::json::object_t*>();
765 if (obj == nullptr)
766 {
767 messages::propertyValueTypeError(
768 asyncResp->res, formpart.content, "UpdateParameters");
769 return std::nullopt;
770 }
771
772 if (!json_util::readJsonObject( //
773 *obj, asyncResp->res, //
774 "@Redfish.OperationApplyTime", multiRet.applyTime, //
775 "Targets", tempTargets //
776 ))
777 {
778 return std::nullopt;
779 }
780
781 for (size_t urlIndex = 0; urlIndex < tempTargets.size();
782 urlIndex++)
783 {
784 const std::string& target = tempTargets[urlIndex];
785 boost::system::result<boost::urls::url_view> url =
786 boost::urls::parse_origin_form(target);
787 auto res = processUrl(url);
788 if (!res.has_value())
789 {
790 messages::propertyValueFormatError(
791 asyncResp->res, target,
792 std::format("Targets/{}", urlIndex));
793 return std::nullopt;
794 }
795 multiRet.targets.emplace_back(res.value());
796 }
797 if (multiRet.targets.size() != 1)
798 {
799 messages::propertyValueFormatError(
800 asyncResp->res, multiRet.targets, "Targets");
801 return std::nullopt;
802 }
803 }
804 else if (param.second == "UpdateFile")
805 {
806 multiRet.uploadData = std::move(formpart.content);
807 }
808 }
809 }
810
811 if (multiRet.uploadData.empty())
812 {
813 BMCWEB_LOG_ERROR("Upload data is NULL");
814 messages::propertyMissing(asyncResp->res, "UpdateFile");
815 return std::nullopt;
816 }
817 if (multiRet.targets.empty())
818 {
819 messages::propertyMissing(asyncResp->res, "Targets");
820 return std::nullopt;
821 }
822 return multiRet;
823 }
824
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)825 inline void handleStartUpdate(
826 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
827 const std::string& objectPath, const boost::system::error_code& ec,
828 const sdbusplus::message::object_path& retPath)
829 {
830 if (ec)
831 {
832 BMCWEB_LOG_ERROR("error_code = {}", ec);
833 BMCWEB_LOG_ERROR("error msg = {}", ec.message());
834 messages::internalError(asyncResp->res);
835 return;
836 }
837
838 BMCWEB_LOG_INFO("Call to StartUpdate on {} Success, retPath = {}",
839 objectPath, retPath.str);
840 createTask(asyncResp, std::move(payload), retPath);
841 }
842
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)843 inline void startUpdate(
844 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
845 const MemoryFileDescriptor& memfd, const std::string& applyTime,
846 const std::string& objectPath, const std::string& serviceName)
847 {
848 crow::connections::systemBus->async_method_call(
849 [asyncResp, payload = std::move(payload),
850 objectPath](const boost::system::error_code& ec1,
851 const sdbusplus::message::object_path& retPath) mutable {
852 handleStartUpdate(asyncResp, std::move(payload), objectPath, ec1,
853 retPath);
854 },
855 serviceName, objectPath, "xyz.openbmc_project.Software.Update",
856 "StartUpdate", sdbusplus::message::unix_fd(memfd.fd), applyTime);
857 }
858
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)859 inline void getSwInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
860 task::Payload payload, const MemoryFileDescriptor& memfd,
861 const std::string& applyTime, const std::string& target,
862 const boost::system::error_code& ec,
863 const dbus::utility::MapperGetSubTreeResponse& subtree)
864 {
865 using SwInfoMap = std::unordered_map<
866 std::string, std::pair<sdbusplus::message::object_path, std::string>>;
867 SwInfoMap swInfoMap;
868
869 if (ec)
870 {
871 BMCWEB_LOG_ERROR("error_code = {}", ec);
872 BMCWEB_LOG_ERROR("error msg = {}", ec.message());
873 messages::internalError(asyncResp->res);
874 return;
875 }
876 BMCWEB_LOG_DEBUG("Found {} software version paths", subtree.size());
877
878 for (const auto& entry : subtree)
879 {
880 sdbusplus::message::object_path path(entry.first);
881 std::string swId = path.filename();
882 swInfoMap.emplace(swId, make_pair(path, entry.second[0].first));
883 }
884
885 auto swEntry = swInfoMap.find(target);
886 if (swEntry == swInfoMap.end())
887 {
888 BMCWEB_LOG_WARNING("No valid DBus path for Target URI {}", target);
889 messages::propertyValueFormatError(asyncResp->res, target, "Targets");
890 return;
891 }
892
893 BMCWEB_LOG_DEBUG("Found software version path {} serviceName {}",
894 swEntry->second.first.str, swEntry->second.second);
895
896 startUpdate(asyncResp, std::move(payload), memfd, applyTime,
897 swEntry->second.first.str, swEntry->second.second);
898 }
899
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)900 inline void handleBMCUpdate(
901 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
902 const MemoryFileDescriptor& memfd, const std::string& applyTime,
903 const boost::system::error_code& ec,
904 const dbus::utility::MapperEndPoints& functionalSoftware)
905 {
906 if (ec)
907 {
908 BMCWEB_LOG_ERROR("error_code = {}", ec);
909 BMCWEB_LOG_ERROR("error msg = {}", ec.message());
910 messages::internalError(asyncResp->res);
911 return;
912 }
913 if (functionalSoftware.size() != 1)
914 {
915 BMCWEB_LOG_ERROR("Found {} functional software endpoints",
916 functionalSoftware.size());
917 messages::internalError(asyncResp->res);
918 return;
919 }
920
921 startUpdate(asyncResp, std::move(payload), memfd, applyTime,
922 functionalSoftware[0], "xyz.openbmc_project.Software.Manager");
923 }
924
processUpdateRequest(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload && payload,std::string_view body,const std::string & applyTime,std::vector<std::string> & targets)925 inline void processUpdateRequest(
926 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
927 task::Payload&& payload, std::string_view body,
928 const std::string& applyTime, std::vector<std::string>& targets)
929 {
930 MemoryFileDescriptor memfd("update-image");
931 if (memfd.fd == -1)
932 {
933 BMCWEB_LOG_ERROR("Failed to create image memfd");
934 messages::internalError(asyncResp->res);
935 return;
936 }
937 if (write(memfd.fd, body.data(), body.length()) !=
938 static_cast<ssize_t>(body.length()))
939 {
940 BMCWEB_LOG_ERROR("Failed to write to image memfd");
941 messages::internalError(asyncResp->res);
942 return;
943 }
944 if (!memfd.rewind())
945 {
946 messages::internalError(asyncResp->res);
947 return;
948 }
949
950 if (!targets.empty() && targets[0] == BMCWEB_REDFISH_MANAGER_URI_NAME)
951 {
952 dbus::utility::getAssociationEndPoints(
953 "/xyz/openbmc_project/software/bmc/updateable",
954 [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
955 applyTime](
956 const boost::system::error_code& ec,
957 const dbus::utility::MapperEndPoints& objectPaths) mutable {
958 handleBMCUpdate(asyncResp, std::move(payload), memfd, applyTime,
959 ec, objectPaths);
960 });
961 }
962 else
963 {
964 constexpr std::array<std::string_view, 1> interfaces = {
965 "xyz.openbmc_project.Software.Version"};
966 dbus::utility::getSubTree(
967 "/xyz/openbmc_project/software", 1, interfaces,
968 [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
969 applyTime, targets](const boost::system::error_code& ec,
970 const dbus::utility::MapperGetSubTreeResponse&
971 subtree) mutable {
972 getSwInfo(asyncResp, std::move(payload), memfd, applyTime,
973 targets[0], ec, subtree);
974 });
975 }
976 }
977
978 inline void
updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const crow::Request & req,MultipartParser && parser)979 updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
980 const crow::Request& req, MultipartParser&& parser)
981 {
982 std::optional<MultiPartUpdateParameters> multipart =
983 extractMultipartUpdateParameters(asyncResp, std::move(parser));
984 if (!multipart)
985 {
986 return;
987 }
988 if (!multipart->applyTime)
989 {
990 multipart->applyTime = "OnReset";
991 }
992
993 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
994 {
995 std::string applyTimeNewVal;
996 if (!convertApplyTime(asyncResp->res, *multipart->applyTime,
997 applyTimeNewVal))
998 {
999 return;
1000 }
1001 task::Payload payload(req);
1002
1003 processUpdateRequest(asyncResp, std::move(payload),
1004 multipart->uploadData, applyTimeNewVal,
1005 multipart->targets);
1006 }
1007 else
1008 {
1009 setApplyTime(asyncResp, *multipart->applyTime);
1010
1011 // Setup callback for when new software detected
1012 monitorForSoftwareAvailable(asyncResp, req,
1013 "/redfish/v1/UpdateService");
1014
1015 uploadImageFile(asyncResp->res, multipart->uploadData);
1016 }
1017 }
1018
doHTTPUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const crow::Request & req)1019 inline void doHTTPUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1020 const crow::Request& req)
1021 {
1022 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
1023 {
1024 task::Payload payload(req);
1025 // HTTP push only supports BMC updates (with ApplyTime as immediate) for
1026 // backwards compatibility. Specific component updates will be handled
1027 // through Multipart form HTTP push.
1028 std::vector<std::string> targets;
1029 targets.emplace_back(BMCWEB_REDFISH_MANAGER_URI_NAME);
1030
1031 processUpdateRequest(
1032 asyncResp, std::move(payload), req.body(),
1033 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate",
1034 targets);
1035 }
1036 else
1037 {
1038 // Setup callback for when new software detected
1039 monitorForSoftwareAvailable(asyncResp, req,
1040 "/redfish/v1/UpdateService");
1041
1042 uploadImageFile(asyncResp->res, req.body());
1043 }
1044 }
1045
1046 inline void
handleUpdateServicePost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1047 handleUpdateServicePost(App& app, const crow::Request& req,
1048 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1049 {
1050 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1051 {
1052 return;
1053 }
1054 std::string_view contentType = req.getHeaderValue("Content-Type");
1055
1056 BMCWEB_LOG_DEBUG("doPost: contentType={}", contentType);
1057
1058 // Make sure that content type is application/octet-stream or
1059 // multipart/form-data
1060 if (bmcweb::asciiIEquals(contentType, "application/octet-stream"))
1061 {
1062 doHTTPUpdate(asyncResp, req);
1063 }
1064 else if (contentType.starts_with("multipart/form-data"))
1065 {
1066 MultipartParser parser;
1067
1068 ParserError ec = parser.parse(req);
1069 if (ec != ParserError::PARSER_SUCCESS)
1070 {
1071 // handle error
1072 BMCWEB_LOG_ERROR("MIME parse failed, ec : {}",
1073 static_cast<int>(ec));
1074 messages::internalError(asyncResp->res);
1075 return;
1076 }
1077
1078 updateMultipartContext(asyncResp, req, std::move(parser));
1079 }
1080 else
1081 {
1082 BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType);
1083 asyncResp->res.result(boost::beast::http::status::bad_request);
1084 }
1085 }
1086
1087 inline void
handleUpdateServiceGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1088 handleUpdateServiceGet(App& app, const crow::Request& req,
1089 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1090 {
1091 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1092 {
1093 return;
1094 }
1095 asyncResp->res.jsonValue["@odata.type"] =
1096 "#UpdateService.v1_11_1.UpdateService";
1097 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService";
1098 asyncResp->res.jsonValue["Id"] = "UpdateService";
1099 asyncResp->res.jsonValue["Description"] = "Service for Software Update";
1100 asyncResp->res.jsonValue["Name"] = "Update Service";
1101
1102 asyncResp->res.jsonValue["HttpPushUri"] =
1103 "/redfish/v1/UpdateService/update";
1104 asyncResp->res.jsonValue["MultipartHttpPushUri"] =
1105 "/redfish/v1/UpdateService/update";
1106
1107 // UpdateService cannot be disabled
1108 asyncResp->res.jsonValue["ServiceEnabled"] = true;
1109 asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] =
1110 "/redfish/v1/UpdateService/FirmwareInventory";
1111 // Get the MaxImageSizeBytes
1112 asyncResp->res.jsonValue["MaxImageSizeBytes"] =
1113 BMCWEB_HTTP_BODY_LIMIT * 1024 * 1024;
1114
1115 // Update Actions object.
1116 nlohmann::json& updateSvcSimpleUpdate =
1117 asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
1118 updateSvcSimpleUpdate["target"] =
1119 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
1120
1121 nlohmann::json::array_t allowed;
1122 allowed.emplace_back(update_service::TransferProtocolType::HTTPS);
1123
1124 if constexpr (BMCWEB_INSECURE_PUSH_STYLE_NOTIFICATION)
1125 {
1126 allowed.emplace_back(update_service::TransferProtocolType::TFTP);
1127 }
1128
1129 updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] =
1130 std::move(allowed);
1131
1132 asyncResp->res
1133 .jsonValue["HttpPushUriOptions"]["HttpPushUriApplyTime"]["ApplyTime"] =
1134 update_service::ApplyTime::Immediate;
1135 }
1136
handleUpdateServiceFirmwareInventoryCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1137 inline void handleUpdateServiceFirmwareInventoryCollectionGet(
1138 App& app, const crow::Request& req,
1139 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1140 {
1141 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1142 {
1143 return;
1144 }
1145 asyncResp->res.jsonValue["@odata.type"] =
1146 "#SoftwareInventoryCollection.SoftwareInventoryCollection";
1147 asyncResp->res.jsonValue["@odata.id"] =
1148 "/redfish/v1/UpdateService/FirmwareInventory";
1149 asyncResp->res.jsonValue["Name"] = "Software Inventory Collection";
1150 const std::array<const std::string_view, 1> iface = {
1151 "xyz.openbmc_project.Software.Version"};
1152
1153 redfish::collection_util::getCollectionMembers(
1154 asyncResp,
1155 boost::urls::url("/redfish/v1/UpdateService/FirmwareInventory"), iface,
1156 "/xyz/openbmc_project/software");
1157 }
1158
1159 /* Fill related item links (i.e. bmc, bios) in for inventory */
getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & purpose)1160 inline void getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1161 const std::string& purpose)
1162 {
1163 if (purpose == sw_util::bmcPurpose)
1164 {
1165 nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
1166 nlohmann::json::object_t item;
1167 item["@odata.id"] = boost::urls::format(
1168 "/redfish/v1/Managers/{}", BMCWEB_REDFISH_MANAGER_URI_NAME);
1169 relatedItem.emplace_back(std::move(item));
1170 asyncResp->res.jsonValue["RelatedItem@odata.count"] =
1171 relatedItem.size();
1172 }
1173 else if (purpose == sw_util::biosPurpose)
1174 {
1175 nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
1176 nlohmann::json::object_t item;
1177 item["@odata.id"] = std::format("/redfish/v1/Systems/{}/Bios",
1178 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1179 relatedItem.emplace_back(std::move(item));
1180 asyncResp->res.jsonValue["RelatedItem@odata.count"] =
1181 relatedItem.size();
1182 }
1183 else
1184 {
1185 BMCWEB_LOG_DEBUG("Unknown software purpose {}", purpose);
1186 }
1187 }
1188
1189 inline void
getSoftwareVersion(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & service,const std::string & path,const std::string & swId)1190 getSoftwareVersion(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1191 const std::string& service, const std::string& path,
1192 const std::string& swId)
1193 {
1194 sdbusplus::asio::getAllProperties(
1195 *crow::connections::systemBus, service, path,
1196 "xyz.openbmc_project.Software.Version",
1197 [asyncResp,
1198 swId](const boost::system::error_code& ec,
1199 const dbus::utility::DBusPropertiesMap& propertiesList) {
1200 if (ec)
1201 {
1202 messages::internalError(asyncResp->res);
1203 return;
1204 }
1205
1206 const std::string* swInvPurpose = nullptr;
1207 const std::string* version = nullptr;
1208
1209 const bool success = sdbusplus::unpackPropertiesNoThrow(
1210 dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose",
1211 swInvPurpose, "Version", version);
1212
1213 if (!success)
1214 {
1215 messages::internalError(asyncResp->res);
1216 return;
1217 }
1218
1219 if (swInvPurpose == nullptr)
1220 {
1221 BMCWEB_LOG_DEBUG("Can't find property \"Purpose\"!");
1222 messages::internalError(asyncResp->res);
1223 return;
1224 }
1225
1226 BMCWEB_LOG_DEBUG("swInvPurpose = {}", *swInvPurpose);
1227
1228 if (version == nullptr)
1229 {
1230 BMCWEB_LOG_DEBUG("Can't find property \"Version\"!");
1231
1232 messages::internalError(asyncResp->res);
1233
1234 return;
1235 }
1236 asyncResp->res.jsonValue["Version"] = *version;
1237 asyncResp->res.jsonValue["Id"] = swId;
1238
1239 // swInvPurpose is of format:
1240 // xyz.openbmc_project.Software.Version.VersionPurpose.ABC
1241 // Translate this to "ABC image"
1242 size_t endDesc = swInvPurpose->rfind('.');
1243 if (endDesc == std::string::npos)
1244 {
1245 messages::internalError(asyncResp->res);
1246 return;
1247 }
1248 endDesc++;
1249 if (endDesc >= swInvPurpose->size())
1250 {
1251 messages::internalError(asyncResp->res);
1252 return;
1253 }
1254
1255 std::string formatDesc = swInvPurpose->substr(endDesc);
1256 asyncResp->res.jsonValue["Description"] = formatDesc + " image";
1257 getRelatedItems(asyncResp, *swInvPurpose);
1258 });
1259 }
1260
handleUpdateServiceFirmwareInventoryGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & param)1261 inline void handleUpdateServiceFirmwareInventoryGet(
1262 App& app, const crow::Request& req,
1263 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1264 const std::string& param)
1265 {
1266 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1267 {
1268 return;
1269 }
1270 std::shared_ptr<std::string> swId = std::make_shared<std::string>(param);
1271
1272 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
1273 "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId);
1274
1275 constexpr std::array<std::string_view, 1> interfaces = {
1276 "xyz.openbmc_project.Software.Version"};
1277 dbus::utility::getSubTree(
1278 "/", 0, interfaces,
1279 [asyncResp,
1280 swId](const boost::system::error_code& ec,
1281 const dbus::utility::MapperGetSubTreeResponse& subtree) {
1282 BMCWEB_LOG_DEBUG("doGet callback...");
1283 if (ec)
1284 {
1285 messages::internalError(asyncResp->res);
1286 return;
1287 }
1288
1289 // Ensure we find our input swId, otherwise return an error
1290 bool found = false;
1291 for (const std::pair<std::string,
1292 std::vector<std::pair<
1293 std::string, std::vector<std::string>>>>&
1294 obj : subtree)
1295 {
1296 if (!obj.first.ends_with(*swId))
1297 {
1298 continue;
1299 }
1300
1301 if (obj.second.empty())
1302 {
1303 continue;
1304 }
1305
1306 found = true;
1307 sw_util::getSwStatus(asyncResp, swId, obj.second[0].first);
1308 getSoftwareVersion(asyncResp, obj.second[0].first, obj.first,
1309 *swId);
1310 }
1311 if (!found)
1312 {
1313 BMCWEB_LOG_WARNING("Input swID {} not found!", *swId);
1314 messages::resourceMissingAtURI(
1315 asyncResp->res,
1316 boost::urls::format(
1317 "/redfish/v1/UpdateService/FirmwareInventory/{}",
1318 *swId));
1319 return;
1320 }
1321 asyncResp->res.jsonValue["@odata.type"] =
1322 "#SoftwareInventory.v1_1_0.SoftwareInventory";
1323 asyncResp->res.jsonValue["Name"] = "Software Inventory";
1324 asyncResp->res.jsonValue["Status"]["HealthRollup"] =
1325 resource::Health::OK;
1326
1327 asyncResp->res.jsonValue["Updateable"] = false;
1328 sw_util::getSwUpdatableStatus(asyncResp, swId);
1329 });
1330 }
1331
requestRoutesUpdateService(App & app)1332 inline void requestRoutesUpdateService(App& app)
1333 {
1334 BMCWEB_ROUTE(
1335 app, "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/")
1336 .privileges(redfish::privileges::postUpdateService)
1337 .methods(boost::beast::http::verb::post)(std::bind_front(
1338 handleUpdateServiceSimpleUpdateAction, std::ref(app)));
1339
1340 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/")
1341 .privileges(redfish::privileges::getSoftwareInventory)
1342 .methods(boost::beast::http::verb::get)(std::bind_front(
1343 handleUpdateServiceFirmwareInventoryGet, std::ref(app)));
1344
1345 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
1346 .privileges(redfish::privileges::getUpdateService)
1347 .methods(boost::beast::http::verb::get)(
1348 std::bind_front(handleUpdateServiceGet, std::ref(app)));
1349
1350 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/")
1351 .privileges(redfish::privileges::postUpdateService)
1352 .methods(boost::beast::http::verb::post)(
1353 std::bind_front(handleUpdateServicePost, std::ref(app)));
1354
1355 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/")
1356 .privileges(redfish::privileges::getSoftwareInventoryCollection)
1357 .methods(boost::beast::http::verb::get)(std::bind_front(
1358 handleUpdateServiceFirmwareInventoryCollectionGet, std::ref(app)));
1359 }
1360
1361 } // namespace redfish
1362