xref: /openbmc/bmcweb/features/redfish/lib/update_service.hpp (revision 4dc23f3fb6c9a7cef84658f8ab3b703d29ec7d57)
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 <query.hpp>
23 #include <registries/privilege_registry.hpp>
24 #include <sdbusplus/asio/property.hpp>
25 #include <utils/fw_utils.hpp>
26 
27 namespace redfish
28 {
29 
30 // Match signals added on software path
31 static std::unique_ptr<sdbusplus::bus::match::match> fwUpdateMatcher;
32 static std::unique_ptr<sdbusplus::bus::match::match> fwUpdateErrorMatcher;
33 // Only allow one update at a time
34 static bool fwUpdateInProgress = false;
35 // Timer for software available
36 static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer;
37 
38 inline static void cleanUp()
39 {
40     fwUpdateInProgress = false;
41     fwUpdateMatcher = nullptr;
42     fwUpdateErrorMatcher = nullptr;
43 }
44 inline static void activateImage(const std::string& objPath,
45                                  const std::string& service)
46 {
47     BMCWEB_LOG_DEBUG << "Activate image for " << objPath << " " << service;
48     crow::connections::systemBus->async_method_call(
49         [](const boost::system::error_code errorCode) {
50             if (errorCode)
51             {
52                 BMCWEB_LOG_DEBUG << "error_code = " << errorCode;
53                 BMCWEB_LOG_DEBUG << "error msg = " << errorCode.message();
54             }
55         },
56         service, objPath, "org.freedesktop.DBus.Properties", "Set",
57         "xyz.openbmc_project.Software.Activation", "RequestedActivation",
58         dbus::utility::DbusVariantType(
59             "xyz.openbmc_project.Software.Activation.RequestedActivations.Active"));
60 }
61 
62 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
63 // then no asyncResp updates will occur
64 static void
65     softwareInterfaceAdded(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
66                            sdbusplus::message::message& m,
67                            task::Payload&& payload)
68 {
69     dbus::utility::DBusInteracesMap interfacesProperties;
70 
71     sdbusplus::message::object_path objPath;
72 
73     m.read(objPath, interfacesProperties);
74 
75     BMCWEB_LOG_DEBUG << "obj path = " << objPath.str;
76     for (auto& interface : interfacesProperties)
77     {
78         BMCWEB_LOG_DEBUG << "interface = " << interface.first;
79 
80         if (interface.first == "xyz.openbmc_project.Software.Activation")
81         {
82             // Retrieve service and activate
83             crow::connections::systemBus->async_method_call(
84                 [objPath, asyncResp, payload(std::move(payload))](
85                     const boost::system::error_code errorCode,
86                     const std::vector<
87                         std::pair<std::string, std::vector<std::string>>>&
88                         objInfo) mutable {
89                     if (errorCode)
90                     {
91                         BMCWEB_LOG_DEBUG << "error_code = " << errorCode;
92                         BMCWEB_LOG_DEBUG << "error msg = "
93                                          << errorCode.message();
94                         if (asyncResp)
95                         {
96                             messages::internalError(asyncResp->res);
97                         }
98                         cleanUp();
99                         return;
100                     }
101                     // Ensure we only got one service back
102                     if (objInfo.size() != 1)
103                     {
104                         BMCWEB_LOG_ERROR << "Invalid Object Size "
105                                          << objInfo.size();
106                         if (asyncResp)
107                         {
108                             messages::internalError(asyncResp->res);
109                         }
110                         cleanUp();
111                         return;
112                     }
113                     // cancel timer only when
114                     // xyz.openbmc_project.Software.Activation interface
115                     // is added
116                     fwAvailableTimer = nullptr;
117 
118                     activateImage(objPath.str, objInfo[0].first);
119                     if (asyncResp)
120                     {
121                         std::shared_ptr<task::TaskData> task =
122                             task::TaskData::createTask(
123                                 [](boost::system::error_code ec,
124                                    sdbusplus::message::message& msg,
125                                    const std::shared_ptr<task::TaskData>&
126                                        taskData) {
127                                     if (ec)
128                                     {
129                                         return task::completed;
130                                     }
131 
132                                     std::string iface;
133                                     dbus::utility::DBusPropertiesMap values;
134 
135                                     std::string index =
136                                         std::to_string(taskData->index);
137                                     msg.read(iface, values);
138 
139                                     if (iface ==
140                                         "xyz.openbmc_project.Software.Activation")
141                                     {
142                                         std::string* state = nullptr;
143                                         for (const auto& property : values)
144                                         {
145                                             if (property.first == "Activation")
146                                             {
147                                                 const std::string* state =
148                                                     std::get_if<std::string>(
149                                                         &property.second);
150                                                 if (state == nullptr)
151                                                 {
152                                                     taskData->messages
153                                                         .emplace_back(
154                                                             messages::
155                                                                 internalError());
156                                                     return task::completed;
157                                                 }
158                                             }
159                                         }
160 
161                                         if (state == nullptr)
162                                         {
163                                             return !task::completed;
164                                         }
165 
166                                         if (boost::ends_with(*state,
167                                                              "Invalid") ||
168                                             boost::ends_with(*state, "Failed"))
169                                         {
170                                             taskData->state = "Exception";
171                                             taskData->status = "Warning";
172                                             taskData->messages.emplace_back(
173                                                 messages::taskAborted(index));
174                                             return task::completed;
175                                         }
176 
177                                         if (boost::ends_with(*state, "Staged"))
178                                         {
179                                             taskData->state = "Stopping";
180                                             taskData->messages.emplace_back(
181                                                 messages::taskPaused(index));
182 
183                                             // its staged, set a long timer to
184                                             // allow them time to complete the
185                                             // update (probably cycle the
186                                             // system) if this expires then
187                                             // task will be cancelled
188                                             taskData->extendTimer(
189                                                 std::chrono::hours(5));
190                                             return !task::completed;
191                                         }
192 
193                                         if (boost::ends_with(*state, "Active"))
194                                         {
195                                             taskData->messages.emplace_back(
196                                                 messages::taskCompletedOK(
197                                                     index));
198                                             taskData->state = "Completed";
199                                             return task::completed;
200                                         }
201                                     }
202                                     else if (
203                                         iface ==
204                                         "xyz.openbmc_project.Software.ActivationProgress")
205                                     {
206 
207                                         const uint8_t* progress = nullptr;
208                                         for (const auto& property : values)
209                                         {
210                                             if (property.first == "Progress")
211                                             {
212                                                 const std::string* progress =
213                                                     std::get_if<std::string>(
214                                                         &property.second);
215                                                 if (progress == nullptr)
216                                                 {
217                                                     taskData->messages
218                                                         .emplace_back(
219                                                             messages::
220                                                                 internalError());
221                                                     return task::completed;
222                                                 }
223                                             }
224                                         }
225 
226                                         if (progress == nullptr)
227                                         {
228                                             return !task::completed;
229                                         }
230                                         taskData->percentComplete =
231                                             static_cast<int>(*progress);
232                                         taskData->messages.emplace_back(
233                                             messages::taskProgressChanged(
234                                                 index, static_cast<size_t>(
235                                                            *progress)));
236 
237                                         // if we're getting status updates it's
238                                         // still alive, update timer
239                                         taskData->extendTimer(
240                                             std::chrono::minutes(5));
241                                     }
242 
243                                     // as firmware update often results in a
244                                     // reboot, the task  may never "complete"
245                                     // unless it is an error
246 
247                                     return !task::completed;
248                                 },
249                                 "type='signal',interface='org.freedesktop.DBus.Properties',"
250                                 "member='PropertiesChanged',path='" +
251                                     objPath.str + "'");
252                         task->startTimer(std::chrono::minutes(5));
253                         task->populateResp(asyncResp->res);
254                         task->payload.emplace(std::move(payload));
255                     }
256                     fwUpdateInProgress = false;
257                 },
258                 "xyz.openbmc_project.ObjectMapper",
259                 "/xyz/openbmc_project/object_mapper",
260                 "xyz.openbmc_project.ObjectMapper", "GetObject", objPath.str,
261                 std::array<const char*, 1>{
262                     "xyz.openbmc_project.Software.Activation"});
263         }
264     }
265 }
266 
267 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
268 // then no asyncResp updates will occur
269 static void monitorForSoftwareAvailable(
270     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
271     const crow::Request& req, const std::string& url,
272     int timeoutTimeSeconds = 10)
273 {
274     // Only allow one FW update at a time
275     if (fwUpdateInProgress)
276     {
277         if (asyncResp)
278         {
279             messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
280         }
281         return;
282     }
283 
284     fwAvailableTimer =
285         std::make_unique<boost::asio::steady_timer>(*req.ioService);
286 
287     fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds));
288 
289     fwAvailableTimer->async_wait(
290         [asyncResp](const boost::system::error_code& ec) {
291             cleanUp();
292             if (ec == boost::asio::error::operation_aborted)
293             {
294                 // expected, we were canceled before the timer completed.
295                 return;
296             }
297             BMCWEB_LOG_ERROR
298                 << "Timed out waiting for firmware object being created";
299             BMCWEB_LOG_ERROR
300                 << "FW image may has already been uploaded to server";
301             if (ec)
302             {
303                 BMCWEB_LOG_ERROR << "Async_wait failed" << ec;
304                 return;
305             }
306             if (asyncResp)
307             {
308                 redfish::messages::internalError(asyncResp->res);
309             }
310         });
311     task::Payload payload(req);
312     auto callback = [asyncResp,
313                      payload](sdbusplus::message::message& m) mutable {
314         BMCWEB_LOG_DEBUG << "Match fired";
315         softwareInterfaceAdded(asyncResp, m, std::move(payload));
316     };
317 
318     fwUpdateInProgress = true;
319 
320     fwUpdateMatcher = std::make_unique<sdbusplus::bus::match::match>(
321         *crow::connections::systemBus,
322         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
323         "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
324         callback);
325 
326     fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match::match>(
327         *crow::connections::systemBus,
328         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
329         "member='InterfacesAdded',"
330         "path='/xyz/openbmc_project/logging'",
331         [asyncResp, url](sdbusplus::message::message& m) {
332             std::vector<
333                 std::pair<std::string, dbus::utility::DBusPropertiesMap>>
334                 interfacesProperties;
335             sdbusplus::message::object_path objPath;
336             m.read(objPath, interfacesProperties);
337             BMCWEB_LOG_DEBUG << "obj path = " << objPath.str;
338             for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>&
339                      interface : interfacesProperties)
340             {
341                 if (interface.first == "xyz.openbmc_project.Logging.Entry")
342                 {
343                     for (const std::pair<std::string,
344                                          dbus::utility::DbusVariantType>&
345                              value : interface.second)
346                     {
347                         if (value.first != "Message")
348                         {
349                             continue;
350                         }
351                         const std::string* type =
352                             std::get_if<std::string>(&value.second);
353                         if (type == nullptr)
354                         {
355                             // if this was our message, timeout will cover it
356                             return;
357                         }
358                         fwAvailableTimer = nullptr;
359                         if (*type ==
360                             "xyz.openbmc_project.Software.Image.Error.UnTarFailure")
361                         {
362                             redfish::messages::invalidUpload(
363                                 asyncResp->res, url, "Invalid archive");
364                         }
365                         else if (*type ==
366                                  "xyz.openbmc_project.Software.Image.Error."
367                                  "ManifestFileFailure")
368                         {
369                             redfish::messages::invalidUpload(
370                                 asyncResp->res, url, "Invalid manifest");
371                         }
372                         else if (
373                             *type ==
374                             "xyz.openbmc_project.Software.Image.Error.ImageFailure")
375                         {
376                             redfish::messages::invalidUpload(
377                                 asyncResp->res, url, "Invalid image format");
378                         }
379                         else if (
380                             *type ==
381                             "xyz.openbmc_project.Software.Version.Error.AlreadyExists")
382                         {
383                             redfish::messages::invalidUpload(
384                                 asyncResp->res, url,
385                                 "Image version already exists");
386 
387                             redfish::messages::resourceAlreadyExists(
388                                 asyncResp->res,
389                                 "UpdateService.v1_5_0.UpdateService", "Version",
390                                 "uploaded version");
391                         }
392                         else if (
393                             *type ==
394                             "xyz.openbmc_project.Software.Image.Error.BusyFailure")
395                         {
396                             redfish::messages::resourceExhaustion(
397                                 asyncResp->res, url);
398                         }
399                         else
400                         {
401                             redfish::messages::internalError(asyncResp->res);
402                         }
403                     }
404                 }
405             }
406         });
407 }
408 
409 /**
410  * UpdateServiceActionsSimpleUpdate class supports handle POST method for
411  * SimpleUpdate action.
412  */
413 inline void requestRoutesUpdateServiceActionsSimpleUpdate(App& app)
414 {
415     BMCWEB_ROUTE(
416         app, "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/")
417         .privileges(redfish::privileges::postUpdateService)
418         .methods(
419             boost::beast::http::verb::
420                 post)([&app](
421                           const crow::Request& req,
422                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
423             if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
424             {
425                 return;
426             }
427 
428             std::optional<std::string> transferProtocol;
429             std::string imageURI;
430 
431             BMCWEB_LOG_DEBUG << "Enter UpdateService.SimpleUpdate doPost";
432 
433             // User can pass in both TransferProtocol and ImageURI parameters or
434             // they can pass in just the ImageURI with the transfer protocol
435             // embedded within it.
436             // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin
437             // 2) ImageURI:tftp://1.1.1.1/myfile.bin
438 
439             if (!json_util::readJsonAction(req, asyncResp->res,
440                                            "TransferProtocol", transferProtocol,
441                                            "ImageURI", imageURI))
442             {
443                 BMCWEB_LOG_DEBUG
444                     << "Missing TransferProtocol or ImageURI parameter";
445                 return;
446             }
447             if (!transferProtocol)
448             {
449                 // Must be option 2
450                 // Verify ImageURI has transfer protocol in it
451                 size_t separator = imageURI.find(':');
452                 if ((separator == std::string::npos) ||
453                     ((separator + 1) > imageURI.size()))
454                 {
455                     messages::actionParameterValueTypeError(
456                         asyncResp->res, imageURI, "ImageURI",
457                         "UpdateService.SimpleUpdate");
458                     BMCWEB_LOG_ERROR << "ImageURI missing transfer protocol: "
459                                      << imageURI;
460                     return;
461                 }
462                 transferProtocol = imageURI.substr(0, separator);
463                 // Ensure protocol is upper case for a common comparison path
464                 // below
465                 boost::to_upper(*transferProtocol);
466                 BMCWEB_LOG_DEBUG << "Encoded transfer protocol "
467                                  << *transferProtocol;
468 
469                 // Adjust imageURI to not have the protocol on it for parsing
470                 // below
471                 // ex. tftp://1.1.1.1/myfile.bin -> 1.1.1.1/myfile.bin
472                 imageURI = imageURI.substr(separator + 3);
473                 BMCWEB_LOG_DEBUG << "Adjusted imageUri " << imageURI;
474             }
475 
476             // OpenBMC currently only supports TFTP
477             if (*transferProtocol != "TFTP")
478             {
479                 messages::actionParameterNotSupported(
480                     asyncResp->res, "TransferProtocol",
481                     "UpdateService.SimpleUpdate");
482                 BMCWEB_LOG_ERROR << "Request incorrect protocol parameter: "
483                                  << *transferProtocol;
484                 return;
485             }
486 
487             // Format should be <IP or Hostname>/<file> for imageURI
488             size_t separator = imageURI.find('/');
489             if ((separator == std::string::npos) ||
490                 ((separator + 1) > imageURI.size()))
491             {
492                 messages::actionParameterValueTypeError(
493                     asyncResp->res, imageURI, "ImageURI",
494                     "UpdateService.SimpleUpdate");
495                 BMCWEB_LOG_ERROR << "Invalid ImageURI: " << imageURI;
496                 return;
497             }
498 
499             std::string tftpServer = imageURI.substr(0, separator);
500             std::string fwFile = imageURI.substr(separator + 1);
501             BMCWEB_LOG_DEBUG << "Server: " << tftpServer + " File: " << fwFile;
502 
503             // Setup callback for when new software detected
504             // Give TFTP 10 minutes to complete
505             monitorForSoftwareAvailable(
506                 asyncResp, req,
507                 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate",
508                 600);
509 
510             // TFTP can take up to 10 minutes depending on image size and
511             // connection speed. Return to caller as soon as the TFTP operation
512             // has been started. The callback above will ensure the activate
513             // is started once the download has completed
514             redfish::messages::success(asyncResp->res);
515 
516             // Call TFTP service
517             crow::connections::systemBus->async_method_call(
518                 [](const boost::system::error_code ec) {
519                     if (ec)
520                     {
521                         // messages::internalError(asyncResp->res);
522                         cleanUp();
523                         BMCWEB_LOG_DEBUG << "error_code = " << ec;
524                         BMCWEB_LOG_DEBUG << "error msg = " << ec.message();
525                     }
526                     else
527                     {
528                         BMCWEB_LOG_DEBUG << "Call to DownloaViaTFTP Success";
529                     }
530                 },
531                 "xyz.openbmc_project.Software.Download",
532                 "/xyz/openbmc_project/software",
533                 "xyz.openbmc_project.Common.TFTP", "DownloadViaTFTP", fwFile,
534                 tftpServer);
535 
536             BMCWEB_LOG_DEBUG << "Exit UpdateService.SimpleUpdate doPost";
537         });
538 }
539 
540 inline void
541     handleUpdateServicePost(App& app, const crow::Request& req,
542                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
543 {
544     if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
545     {
546         return;
547     }
548     BMCWEB_LOG_DEBUG << "doPost...";
549 
550     // Setup callback for when new software detected
551     monitorForSoftwareAvailable(asyncResp, req, "/redfish/v1/UpdateService");
552 
553     std::string filepath(
554         "/tmp/images/" +
555         boost::uuids::to_string(boost::uuids::random_generator()()));
556     BMCWEB_LOG_DEBUG << "Writing file to " << filepath;
557     std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
558                                     std::ofstream::trunc);
559     out << req.body;
560     out.close();
561     BMCWEB_LOG_DEBUG << "file upload complete!!";
562 }
563 
564 inline void requestRoutesUpdateService(App& app)
565 {
566     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
567         .privileges(redfish::privileges::getUpdateService)
568         .methods(boost::beast::http::verb::get)([&app](const crow::Request& req,
569                                                        const std::shared_ptr<
570                                                            bmcweb::AsyncResp>&
571                                                            asyncResp) {
572             if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
573             {
574                 return;
575             }
576             asyncResp->res.jsonValue["@odata.type"] =
577                 "#UpdateService.v1_5_0.UpdateService";
578             asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService";
579             asyncResp->res.jsonValue["Id"] = "UpdateService";
580             asyncResp->res.jsonValue["Description"] =
581                 "Service for Software Update";
582             asyncResp->res.jsonValue["Name"] = "Update Service";
583 
584             asyncResp->res.jsonValue["HttpPushUri"] =
585                 "/redfish/v1/UpdateService/update";
586 
587             // UpdateService cannot be disabled
588             asyncResp->res.jsonValue["ServiceEnabled"] = true;
589             asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] =
590                 "/redfish/v1/UpdateService/FirmwareInventory";
591             // Get the MaxImageSizeBytes
592             asyncResp->res.jsonValue["MaxImageSizeBytes"] =
593                 bmcwebHttpReqBodyLimitMb * 1024 * 1024;
594 
595 #ifdef BMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE
596             // Update Actions object.
597             nlohmann::json& updateSvcSimpleUpdate =
598                 asyncResp->res
599                     .jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
600             updateSvcSimpleUpdate["target"] =
601                 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
602             updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] =
603                 {"TFTP"};
604 #endif
605             // Get the current ApplyTime value
606             sdbusplus::asio::getProperty<std::string>(
607                 *crow::connections::systemBus, "xyz.openbmc_project.Settings",
608                 "/xyz/openbmc_project/software/apply_time",
609                 "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime",
610                 [asyncResp](const boost::system::error_code ec,
611                             const std::string& applyTime) {
612                     if (ec)
613                     {
614                         BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
615                         messages::internalError(asyncResp->res);
616                         return;
617                     }
618 
619                     // Store the ApplyTime Value
620                     if (applyTime == "xyz.openbmc_project.Software.ApplyTime."
621                                      "RequestedApplyTimes.Immediate")
622                     {
623                         asyncResp->res
624                             .jsonValue["HttpPushUriOptions"]
625                                       ["HttpPushUriApplyTime"]["ApplyTime"] =
626                             "Immediate";
627                     }
628                     else if (applyTime ==
629                              "xyz.openbmc_project.Software.ApplyTime."
630                              "RequestedApplyTimes.OnReset")
631                     {
632                         asyncResp->res
633                             .jsonValue["HttpPushUriOptions"]
634                                       ["HttpPushUriApplyTime"]["ApplyTime"] =
635                             "OnReset";
636                     }
637                 });
638         });
639     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
640         .privileges(redfish::privileges::patchUpdateService)
641         .methods(
642             boost::beast::http::verb::patch)([&app](const crow::Request& req,
643                                                     const std::shared_ptr<
644                                                         bmcweb::AsyncResp>&
645                                                         asyncResp) {
646             if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
647             {
648                 return;
649             }
650             BMCWEB_LOG_DEBUG << "doPatch...";
651 
652             std::optional<nlohmann::json> pushUriOptions;
653             if (!json_util::readJsonPatch(req, asyncResp->res,
654                                           "HttpPushUriOptions", pushUriOptions))
655             {
656                 return;
657             }
658 
659             if (pushUriOptions)
660             {
661                 std::optional<nlohmann::json> pushUriApplyTime;
662                 if (!json_util::readJson(*pushUriOptions, asyncResp->res,
663                                          "HttpPushUriApplyTime",
664                                          pushUriApplyTime))
665                 {
666                     return;
667                 }
668 
669                 if (pushUriApplyTime)
670                 {
671                     std::optional<std::string> applyTime;
672                     if (!json_util::readJson(*pushUriApplyTime, asyncResp->res,
673                                              "ApplyTime", applyTime))
674                     {
675                         return;
676                     }
677 
678                     if (applyTime)
679                     {
680                         std::string applyTimeNewVal;
681                         if (applyTime == "Immediate")
682                         {
683                             applyTimeNewVal =
684                                 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
685                         }
686                         else if (applyTime == "OnReset")
687                         {
688                             applyTimeNewVal =
689                                 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
690                         }
691                         else
692                         {
693                             BMCWEB_LOG_INFO
694                                 << "ApplyTime value is not in the list of acceptable values";
695                             messages::propertyValueNotInList(
696                                 asyncResp->res, *applyTime, "ApplyTime");
697                             return;
698                         }
699 
700                         // Set the requested image apply time value
701                         crow::connections::systemBus->async_method_call(
702                             [asyncResp](const boost::system::error_code ec) {
703                                 if (ec)
704                                 {
705                                     BMCWEB_LOG_ERROR
706                                         << "D-Bus responses error: " << ec;
707                                     messages::internalError(asyncResp->res);
708                                     return;
709                                 }
710                                 messages::success(asyncResp->res);
711                             },
712                             "xyz.openbmc_project.Settings",
713                             "/xyz/openbmc_project/software/apply_time",
714                             "org.freedesktop.DBus.Properties", "Set",
715                             "xyz.openbmc_project.Software.ApplyTime",
716                             "RequestedApplyTime",
717                             dbus::utility::DbusVariantType{applyTimeNewVal});
718                     }
719                 }
720             }
721         });
722 
723 // The "old" behavior of the update service URI causes redfish-service validator
724 // failures when the Allow header is supported, given that in the spec,
725 // UpdateService does not allow POST.  in openbmc, we unfortunately reused that
726 // resource as our HttpPushUri as well.  A number of services, including the
727 // openbmc tests, and documentation have hardcoded that erroneous API, instead
728 // of relying on HttpPushUri as the spec requires.  This option will exist
729 // temporarily to allow the old behavior until Q4 2022, at which time it will be
730 // removed.
731 #ifdef BMCWEB_ENABLE_REDFISH_UPDATESERVICE_OLD_POST_URL
732     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
733         .privileges(redfish::privileges::postUpdateService)
734         .methods(
735             boost::beast::http::verb::
736                 post)([&app](
737                           const crow::Request& req,
738                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
739             asyncResp->res.addHeader(
740                 boost::beast::http::field::warning,
741                 "299 - \"POST to /redfish/v1/UpdateService is deprecated. Use "
742                 "the value contained within HttpPushUri.\"");
743             handleUpdateServicePost(app, req, asyncResp);
744         });
745 #endif
746     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/")
747         .privileges(redfish::privileges::postUpdateService)
748         .methods(boost::beast::http::verb::post)(
749             std::bind_front(handleUpdateServicePost, std::ref(app)));
750 }
751 
752 inline void requestRoutesSoftwareInventoryCollection(App& app)
753 {
754     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/")
755         .privileges(redfish::privileges::getSoftwareInventoryCollection)
756         .methods(boost::beast::http::verb::get)(
757             [&app](const crow::Request& req,
758                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
759                 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
760                 {
761                     return;
762                 }
763                 asyncResp->res.jsonValue["@odata.type"] =
764                     "#SoftwareInventoryCollection.SoftwareInventoryCollection";
765                 asyncResp->res.jsonValue["@odata.id"] =
766                     "/redfish/v1/UpdateService/FirmwareInventory";
767                 asyncResp->res.jsonValue["Name"] =
768                     "Software Inventory Collection";
769 
770                 crow::connections::systemBus->async_method_call(
771                     [asyncResp](const boost::system::error_code ec,
772                                 const dbus::utility::MapperGetSubTreeResponse&
773                                     subtree) {
774                         if (ec)
775                         {
776                             messages::internalError(asyncResp->res);
777                             return;
778                         }
779                         asyncResp->res.jsonValue["Members"] =
780                             nlohmann::json::array();
781                         asyncResp->res.jsonValue["Members@odata.count"] = 0;
782 
783                         for (const auto& obj : subtree)
784                         {
785                             sdbusplus::message::object_path path(obj.first);
786                             std::string swId = path.filename();
787                             if (swId.empty())
788                             {
789                                 messages::internalError(asyncResp->res);
790                                 BMCWEB_LOG_DEBUG << "Can't parse firmware ID!!";
791                                 return;
792                             }
793 
794                             nlohmann::json& members =
795                                 asyncResp->res.jsonValue["Members"];
796                             nlohmann::json::object_t member;
797                             member["@odata.id"] =
798                                 "/redfish/v1/UpdateService/FirmwareInventory/" +
799                                 swId;
800                             members.push_back(std::move(member));
801                             asyncResp->res.jsonValue["Members@odata.count"] =
802                                 members.size();
803                         }
804                     },
805                     // Note that only firmware levels associated with a device
806                     // are stored under /xyz/openbmc_project/software therefore
807                     // to ensure only real FirmwareInventory items are returned,
808                     // this full object path must be used here as input to
809                     // mapper
810                     "xyz.openbmc_project.ObjectMapper",
811                     "/xyz/openbmc_project/object_mapper",
812                     "xyz.openbmc_project.ObjectMapper", "GetSubTree",
813                     "/xyz/openbmc_project/software", static_cast<int32_t>(0),
814                     std::array<const char*, 1>{
815                         "xyz.openbmc_project.Software.Version"});
816             });
817 }
818 /* Fill related item links (i.e. bmc, bios) in for inventory */
819 inline static void
820     getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
821                     const std::string& purpose)
822 {
823     if (purpose == fw_util::bmcPurpose)
824     {
825         nlohmann::json& relatedItem = aResp->res.jsonValue["RelatedItem"];
826         nlohmann::json::object_t item;
827         item["@odata.id"] = "/redfish/v1/Managers/bmc";
828         relatedItem.push_back(std::move(item));
829         aResp->res.jsonValue["RelatedItem@odata.count"] = relatedItem.size();
830     }
831     else if (purpose == fw_util::biosPurpose)
832     {
833         nlohmann::json& relatedItem = aResp->res.jsonValue["RelatedItem"];
834         nlohmann::json::object_t item;
835         item["@odata.id"] = "/redfish/v1/Systems/system/Bios";
836         relatedItem.push_back(std::move(item));
837         aResp->res.jsonValue["RelatedItem@odata.count"] = relatedItem.size();
838     }
839     else
840     {
841         BMCWEB_LOG_ERROR << "Unknown software purpose " << purpose;
842     }
843 }
844 
845 inline void requestRoutesSoftwareInventory(App& app)
846 {
847     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/")
848         .privileges(redfish::privileges::getSoftwareInventory)
849         .methods(
850             boost::beast::http::verb::
851                 get)([&app](const crow::Request& req,
852                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
853                             const std::string& param) {
854             if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
855             {
856                 return;
857             }
858             std::shared_ptr<std::string> swId =
859                 std::make_shared<std::string>(param);
860 
861             asyncResp->res.jsonValue["@odata.id"] =
862                 "/redfish/v1/UpdateService/FirmwareInventory/" + *swId;
863 
864             crow::connections::systemBus->async_method_call(
865                 [asyncResp,
866                  swId](const boost::system::error_code ec,
867                        const dbus::utility::MapperGetSubTreeResponse& subtree) {
868                     BMCWEB_LOG_DEBUG << "doGet callback...";
869                     if (ec)
870                     {
871                         messages::internalError(asyncResp->res);
872                         return;
873                     }
874 
875                     // Ensure we find our input swId, otherwise return an error
876                     bool found = false;
877                     for (const std::pair<
878                              std::string,
879                              std::vector<std::pair<
880                                  std::string, std::vector<std::string>>>>& obj :
881                          subtree)
882                     {
883                         if (!boost::ends_with(obj.first, *swId))
884                         {
885                             continue;
886                         }
887 
888                         if (obj.second.empty())
889                         {
890                             continue;
891                         }
892 
893                         found = true;
894                         fw_util::getFwStatus(asyncResp, swId,
895                                              obj.second[0].first);
896 
897                         crow::connections::systemBus->async_method_call(
898                             [asyncResp,
899                              swId](const boost::system::error_code errorCode,
900                                    const dbus::utility::DBusPropertiesMap&
901                                        propertiesList) {
902                                 if (errorCode)
903                                 {
904                                     messages::internalError(asyncResp->res);
905                                     return;
906                                 }
907                                 const std::string* swInvPurpose = nullptr;
908                                 const std::string* version = nullptr;
909                                 for (const auto& property : propertiesList)
910                                 {
911                                     if (property.first == "Purpose")
912                                     {
913                                         swInvPurpose = std::get_if<std::string>(
914                                             &property.second);
915                                     }
916                                     if (property.first == "Version")
917                                     {
918                                         version = std::get_if<std::string>(
919                                             &property.second);
920                                     }
921                                 }
922 
923                                 if (swInvPurpose == nullptr)
924                                 {
925                                     BMCWEB_LOG_DEBUG
926                                         << "Can't find property \"Purpose\"!";
927                                     messages::internalError(asyncResp->res);
928                                     return;
929                                 }
930 
931                                 BMCWEB_LOG_DEBUG << "swInvPurpose = "
932                                                  << *swInvPurpose;
933 
934                                 if (version == nullptr)
935                                 {
936                                     BMCWEB_LOG_DEBUG
937                                         << "Can't find property \"Version\"!";
938 
939                                     messages::internalError(asyncResp->res);
940 
941                                     return;
942                                 }
943                                 asyncResp->res.jsonValue["Version"] = *version;
944                                 asyncResp->res.jsonValue["Id"] = *swId;
945 
946                                 // swInvPurpose is of format:
947                                 // xyz.openbmc_project.Software.Version.VersionPurpose.ABC
948                                 // Translate this to "ABC image"
949                                 size_t endDesc = swInvPurpose->rfind('.');
950                                 if (endDesc == std::string::npos)
951                                 {
952                                     messages::internalError(asyncResp->res);
953                                     return;
954                                 }
955                                 endDesc++;
956                                 if (endDesc >= swInvPurpose->size())
957                                 {
958                                     messages::internalError(asyncResp->res);
959                                     return;
960                                 }
961 
962                                 std::string formatDesc =
963                                     swInvPurpose->substr(endDesc);
964                                 asyncResp->res.jsonValue["Description"] =
965                                     formatDesc + " image";
966                                 getRelatedItems(asyncResp, *swInvPurpose);
967                             },
968                             obj.second[0].first, obj.first,
969                             "org.freedesktop.DBus.Properties", "GetAll",
970                             "xyz.openbmc_project.Software.Version");
971                     }
972                     if (!found)
973                     {
974                         BMCWEB_LOG_ERROR << "Input swID " << *swId
975                                          << " not found!";
976                         messages::resourceMissingAtURI(
977                             asyncResp->res,
978                             crow::utility::urlFromPieces(
979                                 "redfish", "v1", "UpdateService",
980                                 "FirmwareInventory", *swId));
981                         return;
982                     }
983                     asyncResp->res.jsonValue["@odata.type"] =
984                         "#SoftwareInventory.v1_1_0.SoftwareInventory";
985                     asyncResp->res.jsonValue["Name"] = "Software Inventory";
986                     asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
987 
988                     asyncResp->res.jsonValue["Updateable"] = false;
989                     fw_util::getFwUpdateableStatus(asyncResp, swId);
990                 },
991                 "xyz.openbmc_project.ObjectMapper",
992                 "/xyz/openbmc_project/object_mapper",
993                 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/",
994                 static_cast<int32_t>(0),
995                 std::array<const char*, 1>{
996                     "xyz.openbmc_project.Software.Version"});
997         });
998 }
999 
1000 } // namespace redfish
1001