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