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