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