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