blob: 7c2c5f4c8f88164b3c5e064a711af56f9f140584 [file] [log] [blame]
Jennifer Lee729dae72018-04-24 15:59:34 -07001/*
Ed Tanous6be832e2024-09-10 11:44:48 -07002Copyright (c) 2018 Intel Corporation
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
Jennifer Lee729dae72018-04-24 15:59:34 -070015*/
16#pragma once
17
Tejas Patild61e5192021-06-04 15:49:35 +053018#include "bmcweb_config.h"
19
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080020#include "app.hpp"
21#include "dbus_utility.hpp"
Ed Tanous5b904292024-04-16 11:10:17 -070022#include "error_messages.hpp"
Ed Tanous757178a2024-04-03 14:32:38 -070023#include "generated/enums/update_service.hpp"
George Liu0ed80c82020-05-12 16:06:27 +080024#include "multipart_parser.hpp"
Ed Tanous2c6ffdb2023-06-28 11:28:38 -070025#include "ossl_random.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080026#include "query.hpp"
27#include "registries/privilege_registry.hpp"
Ed Tanousa8e884f2023-01-13 17:40:03 -080028#include "task.hpp"
Ed Tanous5b904292024-04-16 11:10:17 -070029#include "task_messages.hpp"
John Edward Broadbent08d81ad2022-05-17 20:00:23 -070030#include "utils/collection.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080031#include "utils/dbus_utils.hpp"
Ed Tanous5b904292024-04-16 11:10:17 -070032#include "utils/json_utils.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080033#include "utils/sw_utils.hpp"
34
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -070035#include <sys/mman.h>
36
George Liue99073f2022-12-09 11:06:16 +080037#include <boost/system/error_code.hpp>
Ed Tanousef4c65b2023-04-24 15:28:50 -070038#include <boost/url/format.hpp>
Jonathan Doman1e1e5982021-06-11 09:36:17 -070039#include <sdbusplus/asio/property.hpp>
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080040#include <sdbusplus/bus/match.hpp>
Krzysztof Grobelnyd1bde9e2022-09-07 10:40:51 +020041#include <sdbusplus/unpack_properties.hpp>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050042
George Liu2b731192023-01-11 16:27:13 +080043#include <array>
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -070044#include <cstddef>
George Liu0ed80c82020-05-12 16:06:27 +080045#include <filesystem>
Jagpal Singh Gillc71b6c92024-04-29 16:50:53 -070046#include <functional>
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -070047#include <iterator>
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -070048#include <memory>
Ed Tanous7cb59f62022-05-05 11:48:31 -070049#include <optional>
50#include <string>
George Liu2b731192023-01-11 16:27:13 +080051#include <string_view>
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -070052#include <unordered_map>
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -070053#include <vector>
George Liu2b731192023-01-11 16:27:13 +080054
Ed Tanous1abe55e2018-09-05 08:30:59 -070055namespace redfish
56{
Ed Tanous27826b52018-10-29 11:40:58 -070057
Andrew Geissler0e7de462019-03-04 19:11:54 -060058// Match signals added on software path
Ed Tanouscf9e4172022-12-21 09:30:16 -080059// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
Patrick Williams59d494e2022-07-22 19:26:55 -050060static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateMatcher;
Ed Tanouscf9e4172022-12-21 09:30:16 -080061// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
Patrick Williams59d494e2022-07-22 19:26:55 -050062static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateErrorMatcher;
Andrew Geissler0e7de462019-03-04 19:11:54 -060063// Only allow one update at a time
Ed Tanouscf9e4172022-12-21 09:30:16 -080064// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
Andrew Geissler0e7de462019-03-04 19:11:54 -060065static bool fwUpdateInProgress = false;
Andrew Geissler86adcd62019-04-18 10:58:05 -050066// Timer for software available
Ed Tanouscf9e4172022-12-21 09:30:16 -080067// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
Ed Tanous271584a2019-07-09 16:24:22 -070068static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer;
Andrew Geissler86adcd62019-04-18 10:58:05 -050069
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -070070struct MemoryFileDescriptor
71{
72 int fd = -1;
73
74 explicit MemoryFileDescriptor(const std::string& filename) :
75 fd(memfd_create(filename.c_str(), 0))
76 {}
77
78 MemoryFileDescriptor(const MemoryFileDescriptor&) = default;
79 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
86 ~MemoryFileDescriptor()
87 {
88 if (fd != -1)
89 {
90 close(fd);
91 }
92 }
93
94 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
Ed Tanousdf254f22024-04-01 13:25:46 -0700105inline void cleanUp()
Andrew Geissler86adcd62019-04-18 10:58:05 -0500106{
107 fwUpdateInProgress = false;
108 fwUpdateMatcher = nullptr;
James Feist4cde5d92020-06-11 10:39:55 -0700109 fwUpdateErrorMatcher = nullptr;
Andrew Geissler86adcd62019-04-18 10:58:05 -0500110}
Ed Tanousdf254f22024-04-01 13:25:46 -0700111
112inline void activateImage(const std::string& objPath,
113 const std::string& service)
Andrew Geissler86adcd62019-04-18 10:58:05 -0500114{
Ed Tanous62598e32023-07-17 17:06:25 -0700115 BMCWEB_LOG_DEBUG("Activate image for {} {}", objPath, service);
George Liu9ae226f2023-06-21 17:56:46 +0800116 sdbusplus::asio::setProperty(
117 *crow::connections::systemBus, service, objPath,
118 "xyz.openbmc_project.Software.Activation", "RequestedActivation",
119 "xyz.openbmc_project.Software.Activation.RequestedActivations.Active",
Ed Tanous8b242752023-06-27 17:17:13 -0700120 [](const boost::system::error_code& ec) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400121 if (ec)
122 {
123 BMCWEB_LOG_DEBUG("error_code = {}", ec);
124 BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
125 }
126 });
Andrew Geissler86adcd62019-04-18 10:58:05 -0500127}
Andrew Geissler0554c982019-04-23 14:40:12 -0500128
Jagpal Singh Gillc71b6c92024-04-29 16:50:53 -0700129inline 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
230inline 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
Andrew Geissler0554c982019-04-23 14:40:12 -0500244// Note that asyncResp can be either a valid pointer or nullptr. If nullptr
245// then no asyncResp updates will occur
Ed Tanous4ff0f1f2024-09-04 17:27:37 -0700246inline void
zhanghch058d1b46d2021-04-01 11:18:24 +0800247 softwareInterfaceAdded(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
Patrick Williams59d494e2022-07-22 19:26:55 -0500248 sdbusplus::message_t& m, task::Payload&& payload)
Andrew Geissler86adcd62019-04-18 10:58:05 -0500249{
Michael Shen80f79a42023-08-24 13:41:53 +0000250 dbus::utility::DBusInterfacesMap interfacesProperties;
Andrew Geissler86adcd62019-04-18 10:58:05 -0500251
252 sdbusplus::message::object_path objPath;
253
254 m.read(objPath, interfacesProperties);
255
Ed Tanous62598e32023-07-17 17:06:25 -0700256 BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
Ed Tanouse3eb3d62022-12-21 11:56:07 -0800257 for (const auto& interface : interfacesProperties)
Andrew Geissler86adcd62019-04-18 10:58:05 -0500258 {
Ed Tanous62598e32023-07-17 17:06:25 -0700259 BMCWEB_LOG_DEBUG("interface = {}", interface.first);
Andrew Geissler86adcd62019-04-18 10:58:05 -0500260
261 if (interface.first == "xyz.openbmc_project.Software.Activation")
262 {
Andrew Geissler86adcd62019-04-18 10:58:05 -0500263 // Retrieve service and activate
George Liu2b731192023-01-11 16:27:13 +0800264 constexpr std::array<std::string_view, 1> interfaces = {
265 "xyz.openbmc_project.Software.Activation"};
266 dbus::utility::getDbusObject(
267 objPath.str, interfaces,
Ed Tanousa3e65892021-09-16 14:13:20 -0700268 [objPath, asyncResp, payload(std::move(payload))](
Ed Tanous8b242752023-06-27 17:17:13 -0700269 const boost::system::error_code& ec,
Ed Tanousa3e65892021-09-16 14:13:20 -0700270 const std::vector<
271 std::pair<std::string, std::vector<std::string>>>&
272 objInfo) mutable {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400273 if (ec)
Andrew Geissler0554c982019-04-23 14:40:12 -0500274 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400275 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;
Ed Tanous002d39b2022-05-31 08:59:27 -0700283 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400284 // Ensure we only got one service back
285 if (objInfo.size() != 1)
Ed Tanous002d39b2022-05-31 08:59:27 -0700286 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400287 BMCWEB_LOG_ERROR("Invalid Object Size {}",
288 objInfo.size());
289 if (asyncResp)
290 {
291 messages::internalError(asyncResp->res);
292 }
293 cleanUp();
294 return;
Ed Tanous002d39b2022-05-31 08:59:27 -0700295 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400296 // cancel timer only when
297 // xyz.openbmc_project.Software.Activation interface
298 // is added
299 fwAvailableTimer = nullptr;
Ed Tanous002d39b2022-05-31 08:59:27 -0700300
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400301 activateImage(objPath.str, objInfo[0].first);
302 if (asyncResp)
303 {
304 createTask(asyncResp, std::move(payload), objPath);
305 }
306 fwUpdateInProgress = false;
307 });
Patrick Williams62bafc02022-09-08 17:35:35 -0500308
309 break;
Andrew Geissler86adcd62019-04-18 10:58:05 -0500310 }
311 }
312}
313
Myung Bae8549b952023-08-16 15:18:19 -0400314inline 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
337inline void
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 }
Myung Bae4034a652023-08-17 08:47:35 -0400369 else if (type == "xyz.openbmc_project.Software.Version.Error.Incompatible")
Myung Bae8549b952023-08-16 15:18:19 -0400370 {
Myung Bae4034a652023-08-17 08:47:35 -0400371 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);
Myung Bae8549b952023-08-16 15:18:19 -0400391 redfish::messages::internalError(asyncResp->res);
392 }
Myung Bae4034a652023-08-17 08:47:35 -0400393 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;
Myung Bae8549b952023-08-16 15:18:19 -0400401}
402
403inline void
404 afterUpdateErrorMatcher(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
405 const std::string& url, sdbusplus::message_t& m)
406{
Michael Shen80f79a42023-08-24 13:41:53 +0000407 dbus::utility::DBusInterfacesMap interfacesProperties;
Myung Bae8549b952023-08-16 15:18:19 -0400408 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 }
Myung Bae8549b952023-08-16 15:18:19 -0400430 handleUpdateErrorType(asyncResp, url, *type);
431 }
432 }
433 }
434}
435
Andrew Geissler0554c982019-04-23 14:40:12 -0500436// Note that asyncResp can be either a valid pointer or nullptr. If nullptr
437// then no asyncResp updates will occur
Ed Tanousf5139332024-04-03 13:25:04 -0700438inline void monitorForSoftwareAvailable(
zhanghch058d1b46d2021-04-01 11:18:24 +0800439 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
440 const crow::Request& req, const std::string& url,
Gunnar Mills5d138942022-09-07 10:26:21 -0500441 int timeoutTimeSeconds = 25)
Andrew Geissler86adcd62019-04-18 10:58:05 -0500442{
443 // Only allow one FW update at a time
Ed Tanouse05aec52022-01-25 10:28:56 -0800444 if (fwUpdateInProgress)
Andrew Geissler86adcd62019-04-18 10:58:05 -0500445 {
Andrew Geissler0554c982019-04-23 14:40:12 -0500446 if (asyncResp)
447 {
Andrew Geissler0554c982019-04-23 14:40:12 -0500448 messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
449 }
Andrew Geissler86adcd62019-04-18 10:58:05 -0500450 return;
451 }
452
Ed Tanous8e8245d2024-04-11 22:21:38 -0700453 if (req.ioService == nullptr)
454 {
455 messages::internalError(asyncResp->res);
456 return;
457 }
458
Andrew Geissler0554c982019-04-23 14:40:12 -0500459 fwAvailableTimer =
Ed Tanous271584a2019-07-09 16:24:22 -0700460 std::make_unique<boost::asio::steady_timer>(*req.ioService);
Andrew Geissler86adcd62019-04-18 10:58:05 -0500461
Ed Tanous271584a2019-07-09 16:24:22 -0700462 fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds));
Andrew Geissler86adcd62019-04-18 10:58:05 -0500463
464 fwAvailableTimer->async_wait(
Myung Bae8549b952023-08-16 15:18:19 -0400465 std::bind_front(afterAvailbleTimerAsyncWait, asyncResp));
466
Ed Tanousa3e65892021-09-16 14:13:20 -0700467 task::Payload payload(req);
Patrick Williams59d494e2022-07-22 19:26:55 -0500468 auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable {
Ed Tanous62598e32023-07-17 17:06:25 -0700469 BMCWEB_LOG_DEBUG("Match fired");
Ed Tanousa3e65892021-09-16 14:13:20 -0700470 softwareInterfaceAdded(asyncResp, m, std::move(payload));
Andrew Geissler86adcd62019-04-18 10:58:05 -0500471 };
472
473 fwUpdateInProgress = true;
474
Patrick Williams59d494e2022-07-22 19:26:55 -0500475 fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>(
Andrew Geissler86adcd62019-04-18 10:58:05 -0500476 *crow::connections::systemBus,
477 "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
478 "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
479 callback);
James Feist4cde5d92020-06-11 10:39:55 -0700480
Patrick Williams59d494e2022-07-22 19:26:55 -0500481 fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>(
James Feist4cde5d92020-06-11 10:39:55 -0700482 *crow::connections::systemBus,
Brian Mae1cc4822021-12-01 17:05:54 +0800483 "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
484 "member='InterfacesAdded',"
485 "path='/xyz/openbmc_project/logging'",
Myung Bae8549b952023-08-16 15:18:19 -0400486 std::bind_front(afterUpdateErrorMatcher, asyncResp, url));
Andrew Geissler86adcd62019-04-18 10:58:05 -0500487}
Jennifer Lee729dae72018-04-24 15:59:34 -0700488
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400489inline std::optional<boost::urls::url> parseSimpleUpdateUrl(
490 std::string imageURI, std::optional<std::string> transferProtocol,
491 crow::Response& res)
Ed Tanousf86bcc82023-08-25 09:34:07 -0700492{
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 }
Ed Tanouse5cf7772024-04-03 13:45:31 -0700507 // OpenBMC currently only supports TFTP or HTTPS
Ed Tanous757178a2024-04-03 14:32:38 -0700508 if (*transferProtocol == "TFTP")
509 {
510 imageURI = "tftp://" + imageURI;
511 }
Ed Tanouse5cf7772024-04-03 13:45:31 -0700512 else if (*transferProtocol == "HTTPS")
513 {
514 imageURI = "https://" + imageURI;
515 }
Ed Tanous757178a2024-04-03 14:32:38 -0700516 else
Ed Tanousf86bcc82023-08-25 09:34:07 -0700517 {
518 messages::actionParameterNotSupported(res, "TransferProtocol",
519 *transferProtocol);
520 BMCWEB_LOG_ERROR("Request incorrect protocol parameter: {}",
521 *transferProtocol);
522 return std::nullopt;
523 }
Ed Tanousf86bcc82023-08-25 09:34:07 -0700524 }
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
Ed Tanous757178a2024-04-03 14:32:38 -0700537 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 }
Ed Tanouse5cf7772024-04-03 13:45:31 -0700546 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 }
Ed Tanous757178a2024-04-03 14:32:38 -0700554 else
Ed Tanousf86bcc82023-08-25 09:34:07 -0700555 {
556 messages::actionParameterNotSupported(res, "ImageURI", imageURI);
557 return std::nullopt;
558 }
Ed Tanous757178a2024-04-03 14:32:38 -0700559
560 if (url->encoded_path().empty())
Ed Tanousf86bcc82023-08-25 09:34:07 -0700561 {
Ed Tanous757178a2024-04-03 14:32:38 -0700562 messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
563 "UpdateService.SimpleUpdate");
Ed Tanousf86bcc82023-08-25 09:34:07 -0700564 return std::nullopt;
565 }
Ed Tanous757178a2024-04-03 14:32:38 -0700566
567 return *url;
Ed Tanousf86bcc82023-08-25 09:34:07 -0700568}
569
Ed Tanouse5cf7772024-04-03 13:45:31 -0700570inline 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
Ed Tanousf5139332024-04-03 13:25:04 -0700577inline void handleUpdateServiceSimpleUpdateAction(
578 crow::App& app, const crow::Request& req,
579 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
Andrew Geissler0554c982019-04-23 14:40:12 -0500580{
Ed Tanousf5139332024-04-03 13:25:04 -0700581 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
Myung Baeafc474a2024-10-09 00:53:29 -0700597 if (!json_util::readJsonAction( //
598 req, asyncResp->res, //
599 "ImageURI", imageURI, //
600 "TransferProtocol", transferProtocol //
601 ))
Ed Tanousf5139332024-04-03 13:25:04 -0700602 {
603 BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter");
604 return;
605 }
606
Ed Tanous757178a2024-04-03 14:32:38 -0700607 std::optional<boost::urls::url> url =
608 parseSimpleUpdateUrl(imageURI, transferProtocol, asyncResp->res);
609 if (!url)
Ed Tanousf5139332024-04-03 13:25:04 -0700610 {
611 return;
612 }
Jagpal Singh Gill4e338b22024-06-14 14:24:56 -0700613 if (url->scheme() == "https")
Ed Tanouse5cf7772024-04-03 13:45:31 -0700614 {
615 doHttpsUpdate(asyncResp, *url);
616 }
Ed Tanous757178a2024-04-03 14:32:38 -0700617 else
618 {
619 messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
620 url->buffer());
621 return;
622 }
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700623
Ed Tanousf5139332024-04-03 13:25:04 -0700624 BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost");
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700625}
626
George Liu0ed80c82020-05-12 16:06:27 +0800627inline void uploadImageFile(crow::Response& res, std::string_view body)
628{
Ed Tanous2c6ffdb2023-06-28 11:28:38 -0700629 std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID());
630
Ed Tanous62598e32023-07-17 17:06:25 -0700631 BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string());
George Liu0ed80c82020-05-12 16:06:27 +0800632 std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
633 std::ofstream::trunc);
634 // set the permission of the file to 640
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400635 std::filesystem::perms permission =
636 std::filesystem::perms::owner_read | std::filesystem::perms::group_read;
George Liu0ed80c82020-05-12 16:06:27 +0800637 std::filesystem::permissions(filepath, permission);
638 out << body;
639
640 if (out.bad())
641 {
642 messages::internalError(res);
643 cleanUp();
644 }
645}
646
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700647// Convert the Request Apply Time to the D-Bus value
648inline bool convertApplyTime(crow::Response& res, const std::string& applyTime,
649 std::string& applyTimeNewVal)
650{
651 if (applyTime == "Immediate")
652 {
653 applyTimeNewVal =
Jagpal Singh Gill049079f2024-06-02 18:11:13 -0700654 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700655 }
656 else if (applyTime == "OnReset")
657 {
658 applyTimeNewVal =
Jagpal Singh Gill049079f2024-06-02 18:11:13 -0700659 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700660 }
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
George Liu0ed80c82020-05-12 16:06:27 +0800672inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
673 const std::string& applyTime)
674{
675 std::string applyTimeNewVal;
Jagpal Singh Gill049079f2024-06-02 18:11:13 -0700676 if (!convertApplyTime(asyncResp->res, applyTime, applyTimeNewVal))
George Liu0ed80c82020-05-12 16:06:27 +0800677 {
George Liu0ed80c82020-05-12 16:06:27 +0800678 return;
679 }
680
Ginu Georgee93abac2024-06-14 17:35:27 +0530681 setDbusProperty(asyncResp, "ApplyTime", "xyz.openbmc_project.Settings",
Ed Tanousd02aad32024-02-13 14:43:34 -0800682 sdbusplus::message::object_path(
683 "/xyz/openbmc_project/software/apply_time"),
684 "xyz.openbmc_project.Software.ApplyTime",
Ginu Georgee93abac2024-06-14 17:35:27 +0530685 "RequestedApplyTime", applyTimeNewVal);
George Liu0ed80c82020-05-12 16:06:27 +0800686}
687
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700688struct MultiPartUpdateParameters
George Liu0ed80c82020-05-12 16:06:27 +0800689{
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700690 std::optional<std::string> applyTime;
691 std::string uploadData;
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700692 std::vector<std::string> targets;
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700693};
694
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700695inline std::optional<std::string>
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
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700722inline std::optional<MultiPartUpdateParameters>
723 extractMultipartUpdateParameters(
724 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
725 MultipartParser parser)
726{
727 MultiPartUpdateParameters multiRet;
728 for (FormPart& formpart : parser.mime_fields)
George Liu0ed80c82020-05-12 16:06:27 +0800729 {
730 boost::beast::http::fields::const_iterator it =
731 formpart.fields.find("Content-Disposition");
732 if (it == formpart.fields.end())
733 {
Ed Tanous62598e32023-07-17 17:06:25 -0700734 BMCWEB_LOG_ERROR("Couldn't find Content-Disposition");
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700735 return std::nullopt;
George Liu0ed80c82020-05-12 16:06:27 +0800736 }
Ed Tanous62598e32023-07-17 17:06:25 -0700737 BMCWEB_LOG_INFO("Parsing value {}", it->value());
George Liu0ed80c82020-05-12 16:06:27 +0800738
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
Patrick Williams89492a12023-05-10 07:51:34 -0500746 for (const auto& param :
George Liu0ed80c82020-05-12 16:06:27 +0800747 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 {
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700756 std::vector<std::string> tempTargets;
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400757 nlohmann::json content =
758 nlohmann::json::parse(formpart.content, nullptr, false);
Ed Tanousac1e1242024-07-10 22:10:14 -0700759 if (content.is_discarded())
760 {
761 return std::nullopt;
762 }
Ed Tanous7cb59f62022-05-05 11:48:31 -0700763 nlohmann::json::object_t* obj =
764 content.get_ptr<nlohmann::json::object_t*>();
765 if (obj == nullptr)
766 {
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700767 messages::propertyValueTypeError(
768 asyncResp->res, formpart.content, "UpdateParameters");
769 return std::nullopt;
Ed Tanous7cb59f62022-05-05 11:48:31 -0700770 }
771
Myung Baeafc474a2024-10-09 00:53:29 -0700772 if (!json_util::readJsonObject( //
773 *obj, asyncResp->res, //
774 "@Redfish.OperationApplyTime", multiRet.applyTime, //
775 "Targets", tempTargets //
776 ))
George Liu0ed80c82020-05-12 16:06:27 +0800777 {
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700778 return std::nullopt;
George Liu0ed80c82020-05-12 16:06:27 +0800779 }
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700780
781 for (size_t urlIndex = 0; urlIndex < tempTargets.size();
782 urlIndex++)
George Liu0ed80c82020-05-12 16:06:27 +0800783 {
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700784 const std::string& target = tempTargets[urlIndex];
785 boost::system::result<boost::urls::url_view> url =
786 boost::urls::parse_origin_form(target);
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700787 auto res = processUrl(url);
788 if (!res.has_value())
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700789 {
790 messages::propertyValueFormatError(
791 asyncResp->res, target,
792 std::format("Targets/{}", urlIndex));
793 return std::nullopt;
794 }
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700795 multiRet.targets.emplace_back(res.value());
George Liu0ed80c82020-05-12 16:06:27 +0800796 }
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700797 if (multiRet.targets.size() != 1)
George Liu0ed80c82020-05-12 16:06:27 +0800798 {
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700799 messages::propertyValueFormatError(
800 asyncResp->res, multiRet.targets, "Targets");
801 return std::nullopt;
George Liu0ed80c82020-05-12 16:06:27 +0800802 }
George Liu0ed80c82020-05-12 16:06:27 +0800803 }
804 else if (param.second == "UpdateFile")
805 {
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700806 multiRet.uploadData = std::move(formpart.content);
George Liu0ed80c82020-05-12 16:06:27 +0800807 }
808 }
809 }
810
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700811 if (multiRet.uploadData.empty())
George Liu0ed80c82020-05-12 16:06:27 +0800812 {
Ed Tanous62598e32023-07-17 17:06:25 -0700813 BMCWEB_LOG_ERROR("Upload data is NULL");
George Liu0ed80c82020-05-12 16:06:27 +0800814 messages::propertyMissing(asyncResp->res, "UpdateFile");
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700815 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
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400825inline 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)
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700829{
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
Jagpal Singh Gill587090c2024-08-12 00:24:16 -0700838 BMCWEB_LOG_INFO("Call to StartUpdate on {} Success, retPath = {}",
839 objectPath, retPath.str);
840 createTask(asyncResp, std::move(payload), retPath);
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700841}
842
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400843inline 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)
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700847{
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 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400852 handleStartUpdate(asyncResp, std::move(payload), objectPath, ec1,
853 retPath);
854 },
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700855 serviceName, objectPath, "xyz.openbmc_project.Software.Update",
856 "StartUpdate", sdbusplus::message::unix_fd(memfd.fd), applyTime);
857}
858
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700859inline 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)
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700864{
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700865 using SwInfoMap = std::unordered_map<
866 std::string, std::pair<sdbusplus::message::object_path, std::string>>;
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700867 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
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700878 for (const auto& entry : subtree)
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700879 {
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700880 sdbusplus::message::object_path path(entry.first);
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700881 std::string swId = path.filename();
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700882 swInfoMap.emplace(swId, make_pair(path, entry.second[0].first));
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700883 }
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
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700893 BMCWEB_LOG_DEBUG("Found software version path {} serviceName {}",
894 swEntry->second.first.str, swEntry->second.second);
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700895
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700896 startUpdate(asyncResp, std::move(payload), memfd, applyTime,
897 swEntry->second.first.str, swEntry->second.second);
898}
899
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400900inline 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)
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700905{
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");
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700923}
924
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400925inline 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)
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700929{
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700930 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
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700950 if (!targets.empty() && targets[0] == BMCWEB_REDFISH_MANAGER_URI_NAME)
951 {
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700952 dbus::utility::getAssociationEndPoints(
Jagpal Singh Gill89449bb2024-08-12 16:17:58 -0700953 "/xyz/openbmc_project/software/bmc/updateable",
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700954 [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 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400958 handleBMCUpdate(asyncResp, std::move(payload), memfd, applyTime,
959 ec, objectPaths);
960 });
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700961 }
962 else
963 {
964 constexpr std::array<std::string_view, 1> interfaces = {
965 "xyz.openbmc_project.Software.Version"};
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700966 dbus::utility::getSubTree(
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700967 "/xyz/openbmc_project/software", 1, interfaces,
968 [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700969 applyTime, targets](const boost::system::error_code& ec,
970 const dbus::utility::MapperGetSubTreeResponse&
971 subtree) mutable {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400972 getSwInfo(asyncResp, std::move(payload), memfd, applyTime,
973 targets[0], ec, subtree);
974 });
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700975 }
976}
977
978inline void
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700979 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 {
George Liu0ed80c82020-05-12 16:06:27 +0800986 return;
987 }
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700988 if (!multipart->applyTime)
George Liu0ed80c82020-05-12 16:06:27 +0800989 {
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700990 multipart->applyTime = "OnReset";
George Liu0ed80c82020-05-12 16:06:27 +0800991 }
992
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700993 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
994 {
Jagpal Singh Gill9dae4de2024-06-02 23:43:56 -0700995 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);
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -07001006 }
1007 else
1008 {
1009 setApplyTime(asyncResp, *multipart->applyTime);
George Liu0ed80c82020-05-12 16:06:27 +08001010
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -07001011 // Setup callback for when new software detected
1012 monitorForSoftwareAvailable(asyncResp, req,
1013 "/redfish/v1/UpdateService");
Ed Tanous6b54e4e2024-04-10 08:58:48 -07001014
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -07001015 uploadImageFile(asyncResp->res, multipart->uploadData);
1016 }
George Liu0ed80c82020-05-12 16:06:27 +08001017}
1018
Jagpal Singh Gill9dae4de2024-06-02 23:43:56 -07001019inline 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
Ed Tanousc2051d12022-05-11 12:21:55 -07001046inline void
1047 handleUpdateServicePost(App& app, const crow::Request& req,
1048 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1049{
Carson Labrado3ba00072022-06-06 19:40:56 +00001050 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
Ed Tanousc2051d12022-05-11 12:21:55 -07001051 {
1052 return;
1053 }
Ninad Palsuleb33a4322023-06-09 09:19:18 -05001054 std::string_view contentType = req.getHeaderValue("Content-Type");
Ed Tanousc2051d12022-05-11 12:21:55 -07001055
Ed Tanous62598e32023-07-17 17:06:25 -07001056 BMCWEB_LOG_DEBUG("doPost: contentType={}", contentType);
Ed Tanousc2051d12022-05-11 12:21:55 -07001057
Ninad Palsuleb33a4322023-06-09 09:19:18 -05001058 // Make sure that content type is application/octet-stream or
1059 // multipart/form-data
Ed Tanous18f8f602023-07-18 10:07:23 -07001060 if (bmcweb::asciiIEquals(contentType, "application/octet-stream"))
George Liu0ed80c82020-05-12 16:06:27 +08001061 {
Jagpal Singh Gill9dae4de2024-06-02 23:43:56 -07001062 doHTTPUpdate(asyncResp, req);
George Liu0ed80c82020-05-12 16:06:27 +08001063 }
Ninad Palsuleb33a4322023-06-09 09:19:18 -05001064 else if (contentType.starts_with("multipart/form-data"))
George Liu0ed80c82020-05-12 16:06:27 +08001065 {
Ninad Palsuleb33a4322023-06-09 09:19:18 -05001066 MultipartParser parser;
1067
Ninad Palsuleb33a4322023-06-09 09:19:18 -05001068 ParserError ec = parser.parse(req);
1069 if (ec != ParserError::PARSER_SUCCESS)
1070 {
1071 // handle error
Ed Tanous62598e32023-07-17 17:06:25 -07001072 BMCWEB_LOG_ERROR("MIME parse failed, ec : {}",
1073 static_cast<int>(ec));
Ninad Palsuleb33a4322023-06-09 09:19:18 -05001074 messages::internalError(asyncResp->res);
1075 return;
1076 }
Ed Tanous6b54e4e2024-04-10 08:58:48 -07001077
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -07001078 updateMultipartContext(asyncResp, req, std::move(parser));
George Liu0ed80c82020-05-12 16:06:27 +08001079 }
Ninad Palsuleb33a4322023-06-09 09:19:18 -05001080 else
1081 {
Ed Tanous62598e32023-07-17 17:06:25 -07001082 BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType);
Ninad Palsuleb33a4322023-06-09 09:19:18 -05001083 asyncResp->res.result(boost::beast::http::status::bad_request);
1084 }
Ed Tanousc2051d12022-05-11 12:21:55 -07001085}
1086
Ed Tanousf5139332024-04-03 13:25:04 -07001087inline void
1088 handleUpdateServiceGet(App& app, const crow::Request& req,
1089 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001090{
Ed Tanousf5139332024-04-03 13:25:04 -07001091 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";
Ed Tanous4dc23f32022-05-11 11:32:19 -07001101
Ed Tanousf5139332024-04-03 13:25:04 -07001102 asyncResp->res.jsonValue["HttpPushUri"] =
1103 "/redfish/v1/UpdateService/update";
1104 asyncResp->res.jsonValue["MultipartHttpPushUri"] =
1105 "/redfish/v1/UpdateService/update";
Ed Tanous4dc23f32022-05-11 11:32:19 -07001106
Ed Tanousf5139332024-04-03 13:25:04 -07001107 // 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
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001112 asyncResp->res.jsonValue["MaxImageSizeBytes"] =
1113 BMCWEB_HTTP_BODY_LIMIT * 1024 * 1024;
Tejas Patild61e5192021-06-04 15:49:35 +05301114
Ed Tanousf5139332024-04-03 13:25:04 -07001115 // Update Actions object.
1116 nlohmann::json& updateSvcSimpleUpdate =
1117 asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
1118 updateSvcSimpleUpdate["target"] =
1119 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
Ed Tanous757178a2024-04-03 14:32:38 -07001120
1121 nlohmann::json::array_t allowed;
Ed Tanouse5cf7772024-04-03 13:45:31 -07001122 allowed.emplace_back(update_service::TransferProtocolType::HTTPS);
Ed Tanous757178a2024-04-03 14:32:38 -07001123
Ed Tanous25b54db2024-04-17 15:40:31 -07001124 if constexpr (BMCWEB_INSECURE_PUSH_STYLE_NOTIFICATION)
1125 {
1126 allowed.emplace_back(update_service::TransferProtocolType::TFTP);
1127 }
Ed Tanous757178a2024-04-03 14:32:38 -07001128
1129 updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] =
1130 std::move(allowed);
1131
Ed Tanous539d8c62024-06-19 14:38:27 -07001132 asyncResp->res
1133 .jsonValue["HttpPushUriOptions"]["HttpPushUriApplyTime"]["ApplyTime"] =
1134 update_service::ApplyTime::Immediate;
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001135}
Ed Tanousf5139332024-04-03 13:25:04 -07001136
1137inline 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
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001159/* Fill related item links (i.e. bmc, bios) in for inventory */
Ed Tanousf5139332024-04-03 13:25:04 -07001160inline void getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1161 const std::string& purpose)
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001162{
Willy Tueee00132022-06-14 14:53:17 -07001163 if (purpose == sw_util::bmcPurpose)
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001164 {
Ed Tanousac106bf2023-06-07 09:24:59 -07001165 nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
Ed Tanous14766872022-03-15 10:44:42 -07001166 nlohmann::json::object_t item;
Ed Tanous253f11b2024-05-16 09:38:31 -07001167 item["@odata.id"] = boost::urls::format(
1168 "/redfish/v1/Managers/{}", BMCWEB_REDFISH_MANAGER_URI_NAME);
Patrick Williamsb2ba3072023-05-12 10:27:39 -05001169 relatedItem.emplace_back(std::move(item));
Ed Tanousac106bf2023-06-07 09:24:59 -07001170 asyncResp->res.jsonValue["RelatedItem@odata.count"] =
1171 relatedItem.size();
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001172 }
Willy Tueee00132022-06-14 14:53:17 -07001173 else if (purpose == sw_util::biosPurpose)
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001174 {
Ed Tanousac106bf2023-06-07 09:24:59 -07001175 nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
Ed Tanous14766872022-03-15 10:44:42 -07001176 nlohmann::json::object_t item;
Ed Tanous253f11b2024-05-16 09:38:31 -07001177 item["@odata.id"] = std::format("/redfish/v1/Systems/{}/Bios",
1178 BMCWEB_REDFISH_SYSTEM_URI_NAME);
Patrick Williamsb2ba3072023-05-12 10:27:39 -05001179 relatedItem.emplace_back(std::move(item));
Ed Tanousac106bf2023-06-07 09:24:59 -07001180 asyncResp->res.jsonValue["RelatedItem@odata.count"] =
1181 relatedItem.size();
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001182 }
1183 else
1184 {
Carson Labradobf2dded2023-08-10 00:37:06 +00001185 BMCWEB_LOG_DEBUG("Unknown software purpose {}", purpose);
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001186 }
1187}
1188
Willy Tuaf246602022-06-14 15:51:53 -07001189inline void
1190 getSoftwareVersion(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1191 const std::string& service, const std::string& path,
1192 const std::string& swId)
1193{
Krzysztof Grobelnyd1bde9e2022-09-07 10:40:51 +02001194 sdbusplus::asio::getAllProperties(
1195 *crow::connections::systemBus, service, path,
1196 "xyz.openbmc_project.Software.Version",
Willy Tuaf246602022-06-14 15:51:53 -07001197 [asyncResp,
Ed Tanous8b242752023-06-27 17:17:13 -07001198 swId](const boost::system::error_code& ec,
Willy Tuaf246602022-06-14 15:51:53 -07001199 const dbus::utility::DBusPropertiesMap& propertiesList) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001200 if (ec)
1201 {
1202 messages::internalError(asyncResp->res);
1203 return;
1204 }
Krzysztof Grobelnyd1bde9e2022-09-07 10:40:51 +02001205
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001206 const std::string* swInvPurpose = nullptr;
1207 const std::string* version = nullptr;
Krzysztof Grobelnyd1bde9e2022-09-07 10:40:51 +02001208
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001209 const bool success = sdbusplus::unpackPropertiesNoThrow(
1210 dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose",
1211 swInvPurpose, "Version", version);
Krzysztof Grobelnyd1bde9e2022-09-07 10:40:51 +02001212
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001213 if (!success)
1214 {
1215 messages::internalError(asyncResp->res);
1216 return;
1217 }
Willy Tuaf246602022-06-14 15:51:53 -07001218
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001219 if (swInvPurpose == nullptr)
1220 {
1221 BMCWEB_LOG_DEBUG("Can't find property \"Purpose\"!");
1222 messages::internalError(asyncResp->res);
1223 return;
1224 }
Willy Tuaf246602022-06-14 15:51:53 -07001225
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001226 BMCWEB_LOG_DEBUG("swInvPurpose = {}", *swInvPurpose);
Willy Tuaf246602022-06-14 15:51:53 -07001227
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001228 if (version == nullptr)
1229 {
1230 BMCWEB_LOG_DEBUG("Can't find property \"Version\"!");
Willy Tuaf246602022-06-14 15:51:53 -07001231
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001232 messages::internalError(asyncResp->res);
Willy Tuaf246602022-06-14 15:51:53 -07001233
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001234 return;
1235 }
1236 asyncResp->res.jsonValue["Version"] = *version;
1237 asyncResp->res.jsonValue["Id"] = swId;
Willy Tuaf246602022-06-14 15:51:53 -07001238
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001239 // 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 }
Willy Tuaf246602022-06-14 15:51:53 -07001254
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001255 std::string formatDesc = swInvPurpose->substr(endDesc);
1256 asyncResp->res.jsonValue["Description"] = formatDesc + " image";
1257 getRelatedItems(asyncResp, *swInvPurpose);
1258 });
Willy Tuaf246602022-06-14 15:51:53 -07001259}
1260
Ed Tanousf5139332024-04-03 13:25:04 -07001261inline void handleUpdateServiceFirmwareInventoryGet(
1262 App& app, const crow::Request& req,
1263 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1264 const std::string& param)
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001265{
Ed Tanousf5139332024-04-03 13:25:04 -07001266 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) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001282 BMCWEB_LOG_DEBUG("doGet callback...");
1283 if (ec)
Ed Tanous45ca1b82022-03-25 13:07:27 -07001284 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001285 messages::internalError(asyncResp->res);
1286 return;
Ed Tanous45ca1b82022-03-25 13:07:27 -07001287 }
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001288
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001289 // 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)
Ed Tanous002d39b2022-05-31 08:59:27 -07001295 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001296 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);
Ed Tanous002d39b2022-05-31 08:59:27 -07001310 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001311 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;
Ed Tanous1abe55e2018-09-05 08:30:59 -07001326
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001327 asyncResp->res.jsonValue["Updateable"] = false;
1328 sw_util::getSwUpdatableStatus(asyncResp, swId);
1329 });
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001330}
Ed Tanous1abe55e2018-09-05 08:30:59 -07001331
Ed Tanousf5139332024-04-03 13:25:04 -07001332inline 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
Ed Tanousf5139332024-04-03 13:25:04 -07001350 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
Ed Tanous1abe55e2018-09-05 08:30:59 -07001361} // namespace redfish