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