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