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