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 #ifdef BMCWEB_ENABLE_REDFISH_UPDATESERVICE_OLD_POST_URL
585             // See note about later on in this file about why this is neccesary
586             // This is "Wrong" per the standard, but is done temporarily to
587             // avoid noise in failing tests as people transition to having this
588             // option disabled
589             asyncResp->res.addHeader(boost::beast::http::field::allow,
590                                      "GET, PATCH, HEAD");
591 #endif
592 
593             asyncResp->res.jsonValue["HttpPushUri"] =
594                 "/redfish/v1/UpdateService/update";
595 
596             // UpdateService cannot be disabled
597             asyncResp->res.jsonValue["ServiceEnabled"] = true;
598             asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] =
599                 "/redfish/v1/UpdateService/FirmwareInventory";
600             // Get the MaxImageSizeBytes
601             asyncResp->res.jsonValue["MaxImageSizeBytes"] =
602                 bmcwebHttpReqBodyLimitMb * 1024 * 1024;
603 
604 #ifdef BMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE
605             // Update Actions object.
606             nlohmann::json& updateSvcSimpleUpdate =
607                 asyncResp->res
608                     .jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
609             updateSvcSimpleUpdate["target"] =
610                 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
611             updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] =
612                 {"TFTP"};
613 #endif
614             // Get the current ApplyTime value
615             sdbusplus::asio::getProperty<std::string>(
616                 *crow::connections::systemBus, "xyz.openbmc_project.Settings",
617                 "/xyz/openbmc_project/software/apply_time",
618                 "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime",
619                 [asyncResp](const boost::system::error_code ec,
620                             const std::string& applyTime) {
621                     if (ec)
622                     {
623                         BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
624                         messages::internalError(asyncResp->res);
625                         return;
626                     }
627 
628                     // Store the ApplyTime Value
629                     if (applyTime == "xyz.openbmc_project.Software.ApplyTime."
630                                      "RequestedApplyTimes.Immediate")
631                     {
632                         asyncResp->res
633                             .jsonValue["HttpPushUriOptions"]
634                                       ["HttpPushUriApplyTime"]["ApplyTime"] =
635                             "Immediate";
636                     }
637                     else if (applyTime ==
638                              "xyz.openbmc_project.Software.ApplyTime."
639                              "RequestedApplyTimes.OnReset")
640                     {
641                         asyncResp->res
642                             .jsonValue["HttpPushUriOptions"]
643                                       ["HttpPushUriApplyTime"]["ApplyTime"] =
644                             "OnReset";
645                     }
646                 });
647         });
648     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
649         .privileges(redfish::privileges::patchUpdateService)
650         .methods(
651             boost::beast::http::verb::patch)([&app](const crow::Request& req,
652                                                     const std::shared_ptr<
653                                                         bmcweb::AsyncResp>&
654                                                         asyncResp) {
655             if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
656             {
657                 return;
658             }
659             BMCWEB_LOG_DEBUG << "doPatch...";
660 
661             std::optional<nlohmann::json> pushUriOptions;
662             if (!json_util::readJsonPatch(req, asyncResp->res,
663                                           "HttpPushUriOptions", pushUriOptions))
664             {
665                 return;
666             }
667 
668             if (pushUriOptions)
669             {
670                 std::optional<nlohmann::json> pushUriApplyTime;
671                 if (!json_util::readJson(*pushUriOptions, asyncResp->res,
672                                          "HttpPushUriApplyTime",
673                                          pushUriApplyTime))
674                 {
675                     return;
676                 }
677 
678                 if (pushUriApplyTime)
679                 {
680                     std::optional<std::string> applyTime;
681                     if (!json_util::readJson(*pushUriApplyTime, asyncResp->res,
682                                              "ApplyTime", applyTime))
683                     {
684                         return;
685                     }
686 
687                     if (applyTime)
688                     {
689                         std::string applyTimeNewVal;
690                         if (applyTime == "Immediate")
691                         {
692                             applyTimeNewVal =
693                                 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
694                         }
695                         else if (applyTime == "OnReset")
696                         {
697                             applyTimeNewVal =
698                                 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
699                         }
700                         else
701                         {
702                             BMCWEB_LOG_INFO
703                                 << "ApplyTime value is not in the list of acceptable values";
704                             messages::propertyValueNotInList(
705                                 asyncResp->res, *applyTime, "ApplyTime");
706                             return;
707                         }
708 
709                         // Set the requested image apply time value
710                         crow::connections::systemBus->async_method_call(
711                             [asyncResp](const boost::system::error_code ec) {
712                                 if (ec)
713                                 {
714                                     BMCWEB_LOG_ERROR
715                                         << "D-Bus responses error: " << ec;
716                                     messages::internalError(asyncResp->res);
717                                     return;
718                                 }
719                                 messages::success(asyncResp->res);
720                             },
721                             "xyz.openbmc_project.Settings",
722                             "/xyz/openbmc_project/software/apply_time",
723                             "org.freedesktop.DBus.Properties", "Set",
724                             "xyz.openbmc_project.Software.ApplyTime",
725                             "RequestedApplyTime",
726                             dbus::utility::DbusVariantType{applyTimeNewVal});
727                     }
728                 }
729             }
730         });
731 
732 // The "old" behavior of the update service URI causes redfish-service validator
733 // failures when the Allow header is supported, given that in the spec,
734 // UpdateService does not allow POST.  in openbmc, we unfortunately reused that
735 // resource as our HttpPushUri as well.  A number of services, including the
736 // openbmc tests, and documentation have hardcoded that erroneous API, instead
737 // of relying on HttpPushUri as the spec requires.  This option will exist
738 // temporarily to allow the old behavior until Q4 2022, at which time it will be
739 // removed.
740 #ifdef BMCWEB_ENABLE_REDFISH_UPDATESERVICE_OLD_POST_URL
741     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
742         .privileges(redfish::privileges::postUpdateService)
743         .methods(
744             boost::beast::http::verb::
745                 post)([&app](
746                           const crow::Request& req,
747                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
748             asyncResp->res.addHeader(
749                 boost::beast::http::field::warning,
750                 "299 - \"POST to /redfish/v1/UpdateService is deprecated. Use "
751                 "the value contained within HttpPushUri.\"");
752             handleUpdateServicePost(app, req, asyncResp);
753         });
754 #endif
755     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/")
756         .privileges(redfish::privileges::postUpdateService)
757         .methods(boost::beast::http::verb::post)(
758             std::bind_front(handleUpdateServicePost, std::ref(app)));
759 }
760 
761 inline void requestRoutesSoftwareInventoryCollection(App& app)
762 {
763     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/")
764         .privileges(redfish::privileges::getSoftwareInventoryCollection)
765         .methods(boost::beast::http::verb::get)(
766             [&app](const crow::Request& req,
767                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
768                 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
769                 {
770                     return;
771                 }
772                 asyncResp->res.jsonValue["@odata.type"] =
773                     "#SoftwareInventoryCollection.SoftwareInventoryCollection";
774                 asyncResp->res.jsonValue["@odata.id"] =
775                     "/redfish/v1/UpdateService/FirmwareInventory";
776                 asyncResp->res.jsonValue["Name"] =
777                     "Software Inventory Collection";
778 
779                 crow::connections::systemBus->async_method_call(
780                     [asyncResp](const boost::system::error_code ec,
781                                 const dbus::utility::MapperGetSubTreeResponse&
782                                     subtree) {
783                         if (ec)
784                         {
785                             messages::internalError(asyncResp->res);
786                             return;
787                         }
788                         asyncResp->res.jsonValue["Members"] =
789                             nlohmann::json::array();
790                         asyncResp->res.jsonValue["Members@odata.count"] = 0;
791 
792                         for (const auto& obj : subtree)
793                         {
794                             sdbusplus::message::object_path path(obj.first);
795                             std::string swId = path.filename();
796                             if (swId.empty())
797                             {
798                                 messages::internalError(asyncResp->res);
799                                 BMCWEB_LOG_DEBUG << "Can't parse firmware ID!!";
800                                 return;
801                             }
802 
803                             nlohmann::json& members =
804                                 asyncResp->res.jsonValue["Members"];
805                             nlohmann::json::object_t member;
806                             member["@odata.id"] =
807                                 "/redfish/v1/UpdateService/FirmwareInventory/" +
808                                 swId;
809                             members.push_back(std::move(member));
810                             asyncResp->res.jsonValue["Members@odata.count"] =
811                                 members.size();
812                         }
813                     },
814                     // Note that only firmware levels associated with a device
815                     // are stored under /xyz/openbmc_project/software therefore
816                     // to ensure only real FirmwareInventory items are returned,
817                     // this full object path must be used here as input to
818                     // mapper
819                     "xyz.openbmc_project.ObjectMapper",
820                     "/xyz/openbmc_project/object_mapper",
821                     "xyz.openbmc_project.ObjectMapper", "GetSubTree",
822                     "/xyz/openbmc_project/software", static_cast<int32_t>(0),
823                     std::array<const char*, 1>{
824                         "xyz.openbmc_project.Software.Version"});
825             });
826 }
827 /* Fill related item links (i.e. bmc, bios) in for inventory */
828 inline static void
829     getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
830                     const std::string& purpose)
831 {
832     if (purpose == fw_util::bmcPurpose)
833     {
834         nlohmann::json& relatedItem = aResp->res.jsonValue["RelatedItem"];
835         nlohmann::json::object_t item;
836         item["@odata.id"] = "/redfish/v1/Managers/bmc";
837         relatedItem.push_back(std::move(item));
838         aResp->res.jsonValue["RelatedItem@odata.count"] = relatedItem.size();
839     }
840     else if (purpose == fw_util::biosPurpose)
841     {
842         nlohmann::json& relatedItem = aResp->res.jsonValue["RelatedItem"];
843         nlohmann::json::object_t item;
844         item["@odata.id"] = "/redfish/v1/Systems/system/Bios";
845         relatedItem.push_back(std::move(item));
846         aResp->res.jsonValue["RelatedItem@odata.count"] = relatedItem.size();
847     }
848     else
849     {
850         BMCWEB_LOG_ERROR << "Unknown software purpose " << purpose;
851     }
852 }
853 
854 inline void requestRoutesSoftwareInventory(App& app)
855 {
856     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/")
857         .privileges(redfish::privileges::getSoftwareInventory)
858         .methods(
859             boost::beast::http::verb::
860                 get)([&app](const crow::Request& req,
861                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
862                             const std::string& param) {
863             if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
864             {
865                 return;
866             }
867             std::shared_ptr<std::string> swId =
868                 std::make_shared<std::string>(param);
869 
870             asyncResp->res.jsonValue["@odata.id"] =
871                 "/redfish/v1/UpdateService/FirmwareInventory/" + *swId;
872 
873             crow::connections::systemBus->async_method_call(
874                 [asyncResp,
875                  swId](const boost::system::error_code ec,
876                        const dbus::utility::MapperGetSubTreeResponse& subtree) {
877                     BMCWEB_LOG_DEBUG << "doGet callback...";
878                     if (ec)
879                     {
880                         messages::internalError(asyncResp->res);
881                         return;
882                     }
883 
884                     // Ensure we find our input swId, otherwise return an error
885                     bool found = false;
886                     for (const std::pair<
887                              std::string,
888                              std::vector<std::pair<
889                                  std::string, std::vector<std::string>>>>& obj :
890                          subtree)
891                     {
892                         if (!boost::ends_with(obj.first, *swId))
893                         {
894                             continue;
895                         }
896 
897                         if (obj.second.empty())
898                         {
899                             continue;
900                         }
901 
902                         found = true;
903                         fw_util::getFwStatus(asyncResp, swId,
904                                              obj.second[0].first);
905 
906                         crow::connections::systemBus->async_method_call(
907                             [asyncResp,
908                              swId](const boost::system::error_code errorCode,
909                                    const dbus::utility::DBusPropertiesMap&
910                                        propertiesList) {
911                                 if (errorCode)
912                                 {
913                                     messages::internalError(asyncResp->res);
914                                     return;
915                                 }
916                                 const std::string* swInvPurpose = nullptr;
917                                 const std::string* version = nullptr;
918                                 for (const auto& property : propertiesList)
919                                 {
920                                     if (property.first == "Purpose")
921                                     {
922                                         swInvPurpose = std::get_if<std::string>(
923                                             &property.second);
924                                     }
925                                     if (property.first == "Version")
926                                     {
927                                         version = std::get_if<std::string>(
928                                             &property.second);
929                                     }
930                                 }
931 
932                                 if (swInvPurpose == nullptr)
933                                 {
934                                     BMCWEB_LOG_DEBUG
935                                         << "Can't find property \"Purpose\"!";
936                                     messages::internalError(asyncResp->res);
937                                     return;
938                                 }
939 
940                                 BMCWEB_LOG_DEBUG << "swInvPurpose = "
941                                                  << *swInvPurpose;
942 
943                                 if (version == nullptr)
944                                 {
945                                     BMCWEB_LOG_DEBUG
946                                         << "Can't find property \"Version\"!";
947 
948                                     messages::internalError(asyncResp->res);
949 
950                                     return;
951                                 }
952                                 asyncResp->res.jsonValue["Version"] = *version;
953                                 asyncResp->res.jsonValue["Id"] = *swId;
954 
955                                 // swInvPurpose is of format:
956                                 // xyz.openbmc_project.Software.Version.VersionPurpose.ABC
957                                 // Translate this to "ABC image"
958                                 size_t endDesc = swInvPurpose->rfind('.');
959                                 if (endDesc == std::string::npos)
960                                 {
961                                     messages::internalError(asyncResp->res);
962                                     return;
963                                 }
964                                 endDesc++;
965                                 if (endDesc >= swInvPurpose->size())
966                                 {
967                                     messages::internalError(asyncResp->res);
968                                     return;
969                                 }
970 
971                                 std::string formatDesc =
972                                     swInvPurpose->substr(endDesc);
973                                 asyncResp->res.jsonValue["Description"] =
974                                     formatDesc + " image";
975                                 getRelatedItems(asyncResp, *swInvPurpose);
976                             },
977                             obj.second[0].first, obj.first,
978                             "org.freedesktop.DBus.Properties", "GetAll",
979                             "xyz.openbmc_project.Software.Version");
980                     }
981                     if (!found)
982                     {
983                         BMCWEB_LOG_ERROR << "Input swID " << *swId
984                                          << " not found!";
985                         messages::resourceMissingAtURI(
986                             asyncResp->res,
987                             crow::utility::urlFromPieces(
988                                 "redfish", "v1", "UpdateService",
989                                 "FirmwareInventory", *swId));
990                         return;
991                     }
992                     asyncResp->res.jsonValue["@odata.type"] =
993                         "#SoftwareInventory.v1_1_0.SoftwareInventory";
994                     asyncResp->res.jsonValue["Name"] = "Software Inventory";
995                     asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
996 
997                     asyncResp->res.jsonValue["Updateable"] = false;
998                     fw_util::getFwUpdateableStatus(asyncResp, swId);
999                 },
1000                 "xyz.openbmc_project.ObjectMapper",
1001                 "/xyz/openbmc_project/object_mapper",
1002                 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/",
1003                 static_cast<int32_t>(0),
1004                 std::array<const char*, 1>{
1005                     "xyz.openbmc_project.Software.Version"});
1006         });
1007 }
1008 
1009 } // namespace redfish
1010