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