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