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