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