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