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