xref: /openbmc/bmcweb/redfish-core/lib/update_service.hpp (revision de5259d11d1f932fc87f3720d49914a42f8d2c59)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
4 #pragma once
5 
6 #include "bmcweb_config.h"
7 
8 #include "app.hpp"
9 #include "async_resp.hpp"
10 #include "dbus_singleton.hpp"
11 #include "dbus_utility.hpp"
12 #include "error_messages.hpp"
13 #include "generated/enums/resource.hpp"
14 #include "generated/enums/update_service.hpp"
15 #include "http_request.hpp"
16 #include "http_response.hpp"
17 #include "io_context_singleton.hpp"
18 #include "logging.hpp"
19 #include "multipart_parser.hpp"
20 #include "ossl_random.hpp"
21 #include "query.hpp"
22 #include "registries/privilege_registry.hpp"
23 #include "str_utility.hpp"
24 #include "task.hpp"
25 #include "task_messages.hpp"
26 #include "utility.hpp"
27 #include "utils/collection.hpp"
28 #include "utils/dbus_utils.hpp"
29 #include "utils/json_utils.hpp"
30 #include "utils/sw_utils.hpp"
31 
32 #include <sys/mman.h>
33 #include <unistd.h>
34 
35 #include <boost/asio/error.hpp>
36 #include <boost/asio/steady_timer.hpp>
37 #include <boost/beast/http/fields.hpp>
38 #include <boost/beast/http/status.hpp>
39 #include <boost/beast/http/verb.hpp>
40 #include <boost/system/error_code.hpp>
41 #include <boost/system/result.hpp>
42 #include <boost/url/format.hpp>
43 #include <boost/url/parse.hpp>
44 #include <boost/url/url.hpp>
45 #include <boost/url/url_view.hpp>
46 #include <boost/url/url_view_base.hpp>
47 #include <sdbusplus/asio/property.hpp>
48 #include <sdbusplus/bus/match.hpp>
49 #include <sdbusplus/message.hpp>
50 #include <sdbusplus/message/native_types.hpp>
51 #include <sdbusplus/unpack_properties.hpp>
52 
53 #include <array>
54 #include <chrono>
55 #include <cstddef>
56 #include <cstdint>
57 #include <cstdio>
58 #include <filesystem>
59 #include <format>
60 #include <fstream>
61 #include <functional>
62 #include <memory>
63 #include <optional>
64 #include <string>
65 #include <string_view>
66 #include <unordered_map>
67 #include <utility>
68 #include <variant>
69 #include <vector>
70 
71 namespace redfish
72 {
73 
74 // Match signals added on software path
75 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
76 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateMatcher;
77 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
78 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateErrorMatcher;
79 // Only allow one update at a time
80 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
81 static bool fwUpdateInProgress = false;
82 // Timer for software available
83 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
84 static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer;
85 
86 struct MemoryFileDescriptor
87 {
88     int fd = -1;
89 
MemoryFileDescriptorredfish::MemoryFileDescriptor90     explicit MemoryFileDescriptor(const std::string& filename) :
91         fd(memfd_create(filename.c_str(), 0))
92     {}
93 
94     MemoryFileDescriptor(const MemoryFileDescriptor&) = default;
MemoryFileDescriptorredfish::MemoryFileDescriptor95     MemoryFileDescriptor(MemoryFileDescriptor&& other) noexcept : fd(other.fd)
96     {
97         other.fd = -1;
98     }
99     MemoryFileDescriptor& operator=(const MemoryFileDescriptor&) = delete;
100     MemoryFileDescriptor& operator=(MemoryFileDescriptor&&) = default;
101 
~MemoryFileDescriptorredfish::MemoryFileDescriptor102     ~MemoryFileDescriptor()
103     {
104         if (fd != -1)
105         {
106             close(fd);
107         }
108     }
109 
rewindredfish::MemoryFileDescriptor110     bool rewind() const
111     {
112         if (lseek(fd, 0, SEEK_SET) == -1)
113         {
114             BMCWEB_LOG_ERROR("Failed to seek to beginning of image memfd");
115             return false;
116         }
117         return true;
118     }
119 };
120 
cleanUp()121 inline void cleanUp()
122 {
123     fwUpdateInProgress = false;
124     fwUpdateMatcher = nullptr;
125     fwUpdateErrorMatcher = nullptr;
126 }
127 
activateImage(const std::string & objPath,const std::string & service)128 inline void activateImage(const std::string& objPath,
129                           const std::string& service)
130 {
131     BMCWEB_LOG_DEBUG("Activate image for {} {}", objPath, service);
132     sdbusplus::asio::setProperty(
133         *crow::connections::systemBus, service, objPath,
134         "xyz.openbmc_project.Software.Activation", "RequestedActivation",
135         "xyz.openbmc_project.Software.Activation.RequestedActivations.Active",
136         [](const boost::system::error_code& ec) {
137             if (ec)
138             {
139                 BMCWEB_LOG_DEBUG("error_code = {}", ec);
140                 BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
141             }
142         });
143 }
144 
handleCreateTask(const boost::system::error_code & ec2,sdbusplus::message_t & msg,const std::shared_ptr<task::TaskData> & taskData)145 inline bool handleCreateTask(const boost::system::error_code& ec2,
146                              sdbusplus::message_t& msg,
147                              const std::shared_ptr<task::TaskData>& taskData)
148 {
149     if (ec2)
150     {
151         return task::completed;
152     }
153 
154     std::string iface;
155     dbus::utility::DBusPropertiesMap values;
156 
157     std::string index = std::to_string(taskData->index);
158     msg.read(iface, values);
159 
160     if (iface == "xyz.openbmc_project.Software.Activation")
161     {
162         const std::string* state = nullptr;
163         for (const auto& property : values)
164         {
165             if (property.first == "Activation")
166             {
167                 state = std::get_if<std::string>(&property.second);
168                 if (state == nullptr)
169                 {
170                     taskData->messages.emplace_back(messages::internalError());
171                     return task::completed;
172                 }
173             }
174         }
175 
176         if (state == nullptr)
177         {
178             return !task::completed;
179         }
180 
181         if (state->ends_with("Invalid") || state->ends_with("Failed"))
182         {
183             taskData->state = "Exception";
184             taskData->status = "Warning";
185             taskData->messages.emplace_back(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(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(messages::taskCompletedOK(index));
206             taskData->state = "Completed";
207             return task::completed;
208         }
209     }
210     else if (iface == "xyz.openbmc_project.Software.ActivationProgress")
211     {
212         const uint8_t* progress = nullptr;
213         for (const auto& property : values)
214         {
215             if (property.first == "Progress")
216             {
217                 progress = std::get_if<uint8_t>(&property.second);
218                 if (progress == nullptr)
219                 {
220                     taskData->messages.emplace_back(messages::internalError());
221                     return task::completed;
222                 }
223             }
224         }
225 
226         if (progress == nullptr)
227         {
228             return !task::completed;
229         }
230         taskData->percentComplete = *progress;
231         taskData->messages.emplace_back(
232             messages::taskProgressChanged(index, *progress));
233 
234         // if we're getting status updates it's
235         // still alive, update timer
236         taskData->extendTimer(std::chrono::minutes(5));
237     }
238 
239     // as firmware update often results in a
240     // reboot, the task  may never "complete"
241     // unless it is an error
242 
243     return !task::completed;
244 }
245 
createTask(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload && payload,const sdbusplus::message::object_path & objPath)246 inline void createTask(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
247                        task::Payload&& payload,
248                        const sdbusplus::message::object_path& objPath)
249 {
250     std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
251         std::bind_front(handleCreateTask),
252         "type='signal',interface='org.freedesktop.DBus.Properties',"
253         "member='PropertiesChanged',path='" +
254             objPath.str + "'");
255     task->startTimer(std::chrono::minutes(5));
256     task->payload.emplace(std::move(payload));
257     task->populateResp(asyncResp->res);
258 }
259 
260 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
261 // then no asyncResp updates will occur
softwareInterfaceAdded(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,sdbusplus::message_t & m,task::Payload && payload)262 inline void softwareInterfaceAdded(
263     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
264     sdbusplus::message_t& m, task::Payload&& payload)
265 {
266     dbus::utility::DBusInterfacesMap interfacesProperties;
267 
268     sdbusplus::message::object_path objPath;
269 
270     m.read(objPath, interfacesProperties);
271 
272     BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
273     for (const auto& interface : interfacesProperties)
274     {
275         BMCWEB_LOG_DEBUG("interface = {}", interface.first);
276 
277         if (interface.first == "xyz.openbmc_project.Software.Activation")
278         {
279             // Retrieve service and activate
280             constexpr std::array<std::string_view, 1> interfaces = {
281                 "xyz.openbmc_project.Software.Activation"};
282             dbus::utility::getDbusObject(
283                 objPath.str, interfaces,
284                 [objPath, asyncResp, payload(std::move(payload))](
285                     const boost::system::error_code& ec,
286                     const std::vector<
287                         std::pair<std::string, std::vector<std::string>>>&
288                         objInfo) mutable {
289                     if (ec)
290                     {
291                         BMCWEB_LOG_DEBUG("error_code = {}", ec);
292                         BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
293                         if (asyncResp)
294                         {
295                             messages::internalError(asyncResp->res);
296                         }
297                         cleanUp();
298                         return;
299                     }
300                     // Ensure we only got one service back
301                     if (objInfo.size() != 1)
302                     {
303                         BMCWEB_LOG_ERROR("Invalid Object Size {}",
304                                          objInfo.size());
305                         if (asyncResp)
306                         {
307                             messages::internalError(asyncResp->res);
308                         }
309                         cleanUp();
310                         return;
311                     }
312                     // cancel timer only when
313                     // xyz.openbmc_project.Software.Activation interface
314                     // is added
315                     fwAvailableTimer = nullptr;
316 
317                     activateImage(objPath.str, objInfo[0].first);
318                     if (asyncResp)
319                     {
320                         createTask(asyncResp, std::move(payload), objPath);
321                     }
322                     fwUpdateInProgress = false;
323                 });
324 
325             break;
326         }
327     }
328 }
329 
afterAvailbleTimerAsyncWait(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec)330 inline void afterAvailbleTimerAsyncWait(
331     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
332     const boost::system::error_code& ec)
333 {
334     cleanUp();
335     if (ec == boost::asio::error::operation_aborted)
336     {
337         // expected, we were canceled before the timer completed.
338         return;
339     }
340     BMCWEB_LOG_ERROR("Timed out waiting for firmware object being created");
341     BMCWEB_LOG_ERROR("FW image may has already been uploaded to server");
342     if (ec)
343     {
344         BMCWEB_LOG_ERROR("Async_wait failed{}", ec);
345         return;
346     }
347     if (asyncResp)
348     {
349         redfish::messages::internalError(asyncResp->res);
350     }
351 }
352 
handleUpdateErrorType(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & url,const std::string & type)353 inline void handleUpdateErrorType(
354     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& url,
355     const std::string& type)
356 {
357     // NOLINTBEGIN(bugprone-branch-clone)
358     if (type == "xyz.openbmc_project.Software.Image.Error.UnTarFailure")
359     {
360         messages::missingOrMalformedPart(asyncResp->res);
361     }
362     else if (type ==
363              "xyz.openbmc_project.Software.Image.Error.ManifestFileFailure")
364     {
365         messages::missingOrMalformedPart(asyncResp->res);
366     }
367     else if (type == "xyz.openbmc_project.Software.Image.Error.ImageFailure")
368     {
369         messages::missingOrMalformedPart(asyncResp->res);
370     }
371     else if (type == "xyz.openbmc_project.Software.Version.Error.AlreadyExists")
372     {
373         messages::resourceAlreadyExists(asyncResp->res, "UpdateService",
374                                         "Version", "uploaded version");
375     }
376     else if (type == "xyz.openbmc_project.Software.Image.Error.BusyFailure")
377     {
378         messages::serviceTemporarilyUnavailable(asyncResp->res, url);
379     }
380     else if (type == "xyz.openbmc_project.Software.Version.Error.Incompatible")
381     {
382         messages::internalError(asyncResp->res);
383     }
384     else if (type ==
385              "xyz.openbmc_project.Software.Version.Error.ExpiredAccessKey")
386     {
387         messages::internalError(asyncResp->res);
388     }
389     else if (type ==
390              "xyz.openbmc_project.Software.Version.Error.InvalidSignature")
391     {
392         messages::missingOrMalformedPart(asyncResp->res);
393     }
394     else if (type ==
395                  "xyz.openbmc_project.Software.Image.Error.InternalFailure" ||
396              type == "xyz.openbmc_project.Software.Version.Error.HostFile")
397     {
398         BMCWEB_LOG_ERROR("Software Image Error type={}", type);
399         messages::internalError(asyncResp->res);
400     }
401     else
402     {
403         // Unrelated error types. Ignored
404         BMCWEB_LOG_INFO("Non-Software-related Error type={}. Ignored", type);
405         return;
406     }
407     // NOLINTEND(bugprone-branch-clone)
408     // Clear the timer
409     fwAvailableTimer = nullptr;
410 }
411 
afterUpdateErrorMatcher(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & url,sdbusplus::message_t & m)412 inline void afterUpdateErrorMatcher(
413     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& url,
414     sdbusplus::message_t& m)
415 {
416     dbus::utility::DBusInterfacesMap interfacesProperties;
417     sdbusplus::message::object_path objPath;
418     m.read(objPath, interfacesProperties);
419     BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
420     for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>&
421              interface : interfacesProperties)
422     {
423         if (interface.first == "xyz.openbmc_project.Logging.Entry")
424         {
425             for (const std::pair<std::string, dbus::utility::DbusVariantType>&
426                      value : interface.second)
427             {
428                 if (value.first != "Message")
429                 {
430                     continue;
431                 }
432                 const std::string* type =
433                     std::get_if<std::string>(&value.second);
434                 if (type == nullptr)
435                 {
436                     // if this was our message, timeout will cover it
437                     return;
438                 }
439                 handleUpdateErrorType(asyncResp, url, *type);
440             }
441         }
442     }
443 }
444 
445 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
446 // then no asyncResp updates will occur
monitorForSoftwareAvailable(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const crow::Request & req,const std::string & url,int timeoutTimeSeconds=50)447 inline void monitorForSoftwareAvailable(
448     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
449     const crow::Request& req, const std::string& url,
450     int timeoutTimeSeconds = 50)
451 {
452     // Only allow one FW update at a time
453     if (fwUpdateInProgress)
454     {
455         if (asyncResp)
456         {
457             messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
458         }
459         return;
460     }
461 
462     fwAvailableTimer =
463         std::make_unique<boost::asio::steady_timer>(getIoContext());
464 
465     fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds));
466 
467     fwAvailableTimer->async_wait(
468         std::bind_front(afterAvailbleTimerAsyncWait, asyncResp));
469 
470     task::Payload payload(req);
471     auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable {
472         BMCWEB_LOG_DEBUG("Match fired");
473         softwareInterfaceAdded(asyncResp, m, std::move(payload));
474     };
475 
476     fwUpdateInProgress = true;
477 
478     fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>(
479         *crow::connections::systemBus,
480         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
481         "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
482         callback);
483 
484     fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>(
485         *crow::connections::systemBus,
486         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
487         "member='InterfacesAdded',"
488         "path='/xyz/openbmc_project/logging'",
489         std::bind_front(afterUpdateErrorMatcher, asyncResp, url));
490 }
491 
parseSimpleUpdateUrl(std::string imageURI,std::optional<std::string> transferProtocol,crow::Response & res)492 inline std::optional<boost::urls::url> parseSimpleUpdateUrl(
493     std::string imageURI, std::optional<std::string> transferProtocol,
494     crow::Response& res)
495 {
496     if (imageURI.find("://") == std::string::npos)
497     {
498         if (imageURI.starts_with("/"))
499         {
500             messages::actionParameterValueTypeError(
501                 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
502             return std::nullopt;
503         }
504         if (!transferProtocol)
505         {
506             messages::actionParameterValueTypeError(
507                 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
508             return std::nullopt;
509         }
510         // OpenBMC currently only supports HTTPS
511         if (*transferProtocol == "HTTPS")
512         {
513             imageURI = "https://" + imageURI;
514         }
515         else
516         {
517             messages::actionParameterNotSupported(res, "TransferProtocol",
518                                                   *transferProtocol);
519             BMCWEB_LOG_ERROR("Request incorrect protocol parameter: {}",
520                              *transferProtocol);
521             return std::nullopt;
522         }
523     }
524 
525     boost::system::result<boost::urls::url> url =
526         boost::urls::parse_absolute_uri(imageURI);
527     if (!url)
528     {
529         messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
530                                                 "UpdateService.SimpleUpdate");
531 
532         return std::nullopt;
533     }
534     url->normalize();
535 
536     if (url->scheme() == "tftp")
537     {
538         if (url->encoded_path().size() < 2)
539         {
540             messages::actionParameterNotSupported(res, "ImageURI",
541                                                   url->buffer());
542             return std::nullopt;
543         }
544     }
545     else if (url->scheme() == "https")
546     {
547         // Empty paths default to "/"
548         if (url->encoded_path().empty())
549         {
550             url->set_encoded_path("/");
551         }
552     }
553     else
554     {
555         messages::actionParameterNotSupported(res, "ImageURI", imageURI);
556         return std::nullopt;
557     }
558 
559     if (url->encoded_path().empty())
560     {
561         messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
562                                                 "UpdateService.SimpleUpdate");
563         return std::nullopt;
564     }
565 
566     return *url;
567 }
568 
doHttpsUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::urls::url_view_base & url)569 inline void doHttpsUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
570                           const boost::urls::url_view_base& url)
571 {
572     messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
573                                           url.buffer());
574 }
575 
handleUpdateServiceSimpleUpdateAction(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)576 inline void handleUpdateServiceSimpleUpdateAction(
577     crow::App& app, const crow::Request& req,
578     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
579 {
580     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
581     {
582         return;
583     }
584 
585     std::optional<std::string> transferProtocol;
586     std::string imageURI;
587 
588     BMCWEB_LOG_DEBUG("Enter UpdateService.SimpleUpdate doPost");
589 
590     // User can pass in both TransferProtocol and ImageURI parameters or
591     // they can pass in just the ImageURI with the transfer protocol
592     // embedded within it.
593     // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin
594     // 2) ImageURI:tftp://1.1.1.1/myfile.bin
595 
596     if (!json_util::readJsonAction(              //
597             req, asyncResp->res,                 //
598             "ImageURI", imageURI,                //
599             "TransferProtocol", transferProtocol //
600             ))
601     {
602         BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter");
603         return;
604     }
605 
606     std::optional<boost::urls::url> url =
607         parseSimpleUpdateUrl(imageURI, transferProtocol, asyncResp->res);
608     if (!url)
609     {
610         return;
611     }
612     if (url->scheme() == "https")
613     {
614         doHttpsUpdate(asyncResp, *url);
615     }
616     else
617     {
618         messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
619                                               url->buffer());
620         return;
621     }
622 
623     BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost");
624 }
625 
uploadImageFile(crow::Response & res,std::string_view body)626 inline void uploadImageFile(crow::Response& res, std::string_view body)
627 {
628     std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID());
629 
630     BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string());
631     std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
632                                     std::ofstream::trunc);
633     // set the permission of the file to 640
634     std::filesystem::perms permission =
635         std::filesystem::perms::owner_read | std::filesystem::perms::group_read;
636     std::filesystem::permissions(filepath, permission);
637     out << body;
638 
639     if (out.bad())
640     {
641         messages::internalError(res);
642         cleanUp();
643     }
644 }
645 
646 // Convert the Request Apply Time to the D-Bus value
convertApplyTime(crow::Response & res,const std::string & applyTime,std::string & applyTimeNewVal)647 inline bool convertApplyTime(crow::Response& res, const std::string& applyTime,
648                              std::string& applyTimeNewVal)
649 {
650     if (applyTime == "Immediate")
651     {
652         applyTimeNewVal =
653             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
654     }
655     else if (applyTime == "OnReset")
656     {
657         applyTimeNewVal =
658             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
659     }
660     else
661     {
662         BMCWEB_LOG_WARNING(
663             "ApplyTime value {} is not in the list of acceptable values",
664             applyTime);
665         messages::propertyValueNotInList(res, applyTime, "ApplyTime");
666         return false;
667     }
668     return true;
669 }
670 
setApplyTime(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & applyTime)671 inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
672                          const std::string& applyTime)
673 {
674     std::string applyTimeNewVal;
675     if (!convertApplyTime(asyncResp->res, applyTime, applyTimeNewVal))
676     {
677         return;
678     }
679 
680     setDbusProperty(asyncResp, "ApplyTime", "xyz.openbmc_project.Settings",
681                     sdbusplus::message::object_path(
682                         "/xyz/openbmc_project/software/apply_time"),
683                     "xyz.openbmc_project.Software.ApplyTime",
684                     "RequestedApplyTime", applyTimeNewVal);
685 }
686 
687 struct MultiPartUpdate
688 {
689     std::string uploadData;
690     struct UpdateParameters
691     {
692         std::optional<std::string> applyTime;
693         std::vector<std::string> targets;
694     } params;
695 };
696 
processUrl(boost::system::result<boost::urls::url_view> & url)697 inline std::optional<std::string> processUrl(
698     boost::system::result<boost::urls::url_view>& url)
699 {
700     if (!url)
701     {
702         return std::nullopt;
703     }
704     if (crow::utility::readUrlSegments(*url, "redfish", "v1", "Managers",
705                                        BMCWEB_REDFISH_MANAGER_URI_NAME))
706     {
707         return std::make_optional(std::string(BMCWEB_REDFISH_MANAGER_URI_NAME));
708     }
709     if constexpr (!BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
710     {
711         return std::nullopt;
712     }
713     std::string firmwareId;
714     if (!crow::utility::readUrlSegments(*url, "redfish", "v1", "UpdateService",
715                                         "FirmwareInventory",
716                                         std::ref(firmwareId)))
717     {
718         return std::nullopt;
719     }
720 
721     return std::make_optional(firmwareId);
722 }
723 
parseFormPartName(const boost::beast::http::fields::const_iterator & contentDisposition)724 inline std::optional<std::string> parseFormPartName(
725     const boost::beast::http::fields::const_iterator& contentDisposition)
726 {
727     size_t semicolonPos = contentDisposition->value().find(';');
728     if (semicolonPos == std::string::npos)
729     {
730         return std::nullopt;
731     }
732 
733     for (const auto& param : boost::beast::http::param_list{
734              contentDisposition->value().substr(semicolonPos)})
735     {
736         if (param.first == "name" && !param.second.empty())
737         {
738             return std::string(param.second);
739         }
740     }
741     return std::nullopt;
742 }
743 
processUpdateParameters(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string_view content)744 inline std::optional<MultiPartUpdate::UpdateParameters> processUpdateParameters(
745     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
746     std::string_view content)
747 {
748     MultiPartUpdate::UpdateParameters multiRet;
749     nlohmann::json jsonContent = nlohmann::json::parse(content, nullptr, false);
750     if (jsonContent.is_discarded())
751     {
752         return std::nullopt;
753     }
754     nlohmann::json::object_t* obj =
755         jsonContent.get_ptr<nlohmann::json::object_t*>();
756     if (obj == nullptr)
757     {
758         messages::propertyValueTypeError(asyncResp->res, content,
759                                          "UpdateParameters");
760         return std::nullopt;
761     }
762 
763     std::vector<std::string> tempTargets;
764     if (!json_util::readJsonObject(                            //
765             *obj, asyncResp->res,                              //
766             "@Redfish.OperationApplyTime", multiRet.applyTime, //
767             "Targets", tempTargets                             //
768             ))
769     {
770         return std::nullopt;
771     }
772 
773     for (size_t urlIndex = 0; urlIndex < tempTargets.size(); urlIndex++)
774     {
775         const std::string& target = tempTargets[urlIndex];
776         boost::system::result<boost::urls::url_view> url =
777             boost::urls::parse_origin_form(target);
778         auto res = processUrl(url);
779         if (!res.has_value())
780         {
781             messages::propertyValueFormatError(
782                 asyncResp->res, target, std::format("Targets/{}", urlIndex));
783             return std::nullopt;
784         }
785         multiRet.targets.emplace_back(res.value());
786     }
787     if (multiRet.targets.size() != 1)
788     {
789         messages::propertyValueFormatError(asyncResp->res, multiRet.targets,
790                                            "Targets");
791         return std::nullopt;
792     }
793     return multiRet;
794 }
795 
extractMultipartUpdateParameters(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,MultipartParser parser)796 inline std::optional<MultiPartUpdate> extractMultipartUpdateParameters(
797     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, MultipartParser parser)
798 {
799     MultiPartUpdate multiRet;
800     for (FormPart& formpart : parser.mime_fields)
801     {
802         boost::beast::http::fields::const_iterator it =
803             formpart.fields.find("Content-Disposition");
804         if (it == formpart.fields.end())
805         {
806             BMCWEB_LOG_ERROR("Couldn't find Content-Disposition");
807             return std::nullopt;
808         }
809         BMCWEB_LOG_INFO("Parsing value {}", it->value());
810 
811         auto formFieldNameOpt = parseFormPartName(it);
812         if (!formFieldNameOpt.has_value())
813         {
814             continue;
815         }
816 
817         const std::string& formFieldName = formFieldNameOpt.value();
818 
819         if (formFieldName == "UpdateParameters")
820         {
821             std::optional<MultiPartUpdate::UpdateParameters> params =
822                 processUpdateParameters(asyncResp, formpart.content);
823             if (!params)
824             {
825                 return std::nullopt;
826             }
827             multiRet.params = std::move(*params);
828         }
829         else if (formFieldName == "UpdateFile")
830         {
831             multiRet.uploadData = std::move(formpart.content);
832         }
833     }
834 
835     if (multiRet.uploadData.empty())
836     {
837         BMCWEB_LOG_ERROR("Upload data is NULL");
838         messages::propertyMissing(asyncResp->res, "UpdateFile");
839         return std::nullopt;
840     }
841     if (multiRet.params.targets.empty())
842     {
843         messages::propertyMissing(asyncResp->res, "Targets");
844         return std::nullopt;
845     }
846     return multiRet;
847 }
848 
handleStartUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload payload,const std::string & objectPath,const boost::system::error_code & ec,const sdbusplus::message::object_path & retPath)849 inline void handleStartUpdate(
850     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
851     const std::string& objectPath, const boost::system::error_code& ec,
852     const sdbusplus::message::object_path& retPath)
853 {
854     if (ec)
855     {
856         BMCWEB_LOG_ERROR("error_code = {}", ec);
857         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
858         messages::internalError(asyncResp->res);
859         return;
860     }
861 
862     BMCWEB_LOG_INFO("Call to StartUpdate on {} Success, retPath = {}",
863                     objectPath, retPath.str);
864     createTask(asyncResp, std::move(payload), retPath);
865 }
866 
startUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload payload,const MemoryFileDescriptor & memfd,const std::string & applyTime,const std::string & objectPath,const std::string & serviceName)867 inline void startUpdate(
868     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
869     const MemoryFileDescriptor& memfd, const std::string& applyTime,
870     const std::string& objectPath, const std::string& serviceName)
871 {
872     dbus::utility::async_method_call(
873         asyncResp,
874         [asyncResp, payload = std::move(payload),
875          objectPath](const boost::system::error_code& ec1,
876                      const sdbusplus::message::object_path& retPath) mutable {
877             handleStartUpdate(asyncResp, std::move(payload), objectPath, ec1,
878                               retPath);
879         },
880         serviceName, objectPath, "xyz.openbmc_project.Software.Update",
881         "StartUpdate", sdbusplus::message::unix_fd(memfd.fd), applyTime);
882 }
883 
getSwInfo(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload payload,const MemoryFileDescriptor & memfd,const std::string & applyTime,const std::string & target,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)884 inline void getSwInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
885                       task::Payload payload, const MemoryFileDescriptor& memfd,
886                       const std::string& applyTime, const std::string& target,
887                       const boost::system::error_code& ec,
888                       const dbus::utility::MapperGetSubTreeResponse& subtree)
889 {
890     using SwInfoMap = std::unordered_map<
891         std::string, std::pair<sdbusplus::message::object_path, std::string>>;
892     SwInfoMap swInfoMap;
893 
894     if (ec)
895     {
896         BMCWEB_LOG_ERROR("error_code = {}", ec);
897         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
898         messages::internalError(asyncResp->res);
899         return;
900     }
901     BMCWEB_LOG_DEBUG("Found {} software version paths", subtree.size());
902 
903     for (const auto& entry : subtree)
904     {
905         sdbusplus::message::object_path path(entry.first);
906         std::string swId = path.filename();
907         swInfoMap.emplace(swId, make_pair(path, entry.second[0].first));
908     }
909 
910     auto swEntry = swInfoMap.find(target);
911     if (swEntry == swInfoMap.end())
912     {
913         BMCWEB_LOG_WARNING("No valid DBus path for Target URI {}", target);
914         messages::propertyValueFormatError(asyncResp->res, target, "Targets");
915         return;
916     }
917 
918     BMCWEB_LOG_DEBUG("Found software version path {} serviceName {}",
919                      swEntry->second.first.str, swEntry->second.second);
920 
921     startUpdate(asyncResp, std::move(payload), memfd, applyTime,
922                 swEntry->second.first.str, swEntry->second.second);
923 }
924 
handleBMCUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload payload,const MemoryFileDescriptor & memfd,const std::string & applyTime,const boost::system::error_code & ec,const dbus::utility::MapperEndPoints & functionalSoftware)925 inline void handleBMCUpdate(
926     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
927     const MemoryFileDescriptor& memfd, const std::string& applyTime,
928     const boost::system::error_code& ec,
929     const dbus::utility::MapperEndPoints& functionalSoftware)
930 {
931     if (ec)
932     {
933         BMCWEB_LOG_ERROR("error_code = {}", ec);
934         BMCWEB_LOG_ERROR("error msg = {}", ec.message());
935         messages::internalError(asyncResp->res);
936         return;
937     }
938     if (functionalSoftware.size() != 1)
939     {
940         BMCWEB_LOG_ERROR("Found {} functional software endpoints",
941                          functionalSoftware.size());
942         messages::internalError(asyncResp->res);
943         return;
944     }
945 
946     startUpdate(asyncResp, std::move(payload), memfd, applyTime,
947                 functionalSoftware[0], "xyz.openbmc_project.Software.Manager");
948 }
949 
processUpdateRequest(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,task::Payload && payload,std::string_view body,const std::string & applyTime,std::vector<std::string> & targets)950 inline void processUpdateRequest(
951     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
952     task::Payload&& payload, std::string_view body,
953     const std::string& applyTime, std::vector<std::string>& targets)
954 {
955     MemoryFileDescriptor memfd("update-image");
956     if (memfd.fd == -1)
957     {
958         BMCWEB_LOG_ERROR("Failed to create image memfd");
959         messages::internalError(asyncResp->res);
960         return;
961     }
962     if (write(memfd.fd, body.data(), body.length()) !=
963         static_cast<ssize_t>(body.length()))
964     {
965         BMCWEB_LOG_ERROR("Failed to write to image memfd");
966         messages::internalError(asyncResp->res);
967         return;
968     }
969     if (!memfd.rewind())
970     {
971         messages::internalError(asyncResp->res);
972         return;
973     }
974 
975     if (!targets.empty() && targets[0] == BMCWEB_REDFISH_MANAGER_URI_NAME)
976     {
977         dbus::utility::getAssociationEndPoints(
978             "/xyz/openbmc_project/software/bmc/updateable",
979             [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
980              applyTime](
981                 const boost::system::error_code& ec,
982                 const dbus::utility::MapperEndPoints& objectPaths) mutable {
983                 handleBMCUpdate(asyncResp, std::move(payload), memfd, applyTime,
984                                 ec, objectPaths);
985             });
986     }
987     else
988     {
989         constexpr std::array<std::string_view, 1> interfaces = {
990             "xyz.openbmc_project.Software.Version"};
991         dbus::utility::getSubTree(
992             "/xyz/openbmc_project/software", 1, interfaces,
993             [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
994              applyTime, targets](const boost::system::error_code& ec,
995                                  const dbus::utility::MapperGetSubTreeResponse&
996                                      subtree) mutable {
997                 getSwInfo(asyncResp, std::move(payload), memfd, applyTime,
998                           targets[0], ec, subtree);
999             });
1000     }
1001 }
1002 
updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const crow::Request & req,MultipartParser && parser)1003 inline void updateMultipartContext(
1004     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1005     const crow::Request& req, MultipartParser&& parser)
1006 {
1007     std::optional<MultiPartUpdate> multipart =
1008         extractMultipartUpdateParameters(asyncResp, std::move(parser));
1009     if (!multipart)
1010     {
1011         return;
1012     }
1013     if (!multipart->params.applyTime)
1014     {
1015         multipart->params.applyTime = "OnReset";
1016     }
1017 
1018     if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
1019     {
1020         std::string applyTimeNewVal;
1021         if (!convertApplyTime(asyncResp->res, *multipart->params.applyTime,
1022                               applyTimeNewVal))
1023         {
1024             return;
1025         }
1026         task::Payload payload(req);
1027 
1028         processUpdateRequest(asyncResp, std::move(payload),
1029                              multipart->uploadData, applyTimeNewVal,
1030                              multipart->params.targets);
1031     }
1032     else
1033     {
1034         setApplyTime(asyncResp, *multipart->params.applyTime);
1035 
1036         // Setup callback for when new software detected
1037         monitorForSoftwareAvailable(asyncResp, req,
1038                                     "/redfish/v1/UpdateService");
1039 
1040         uploadImageFile(asyncResp->res, multipart->uploadData);
1041     }
1042 }
1043 
doHTTPUpdate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const crow::Request & req)1044 inline void doHTTPUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1045                          const crow::Request& req)
1046 {
1047     if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
1048     {
1049         task::Payload payload(req);
1050         // HTTP push only supports BMC updates (with ApplyTime as immediate) for
1051         // backwards compatibility. Specific component updates will be handled
1052         // through Multipart form HTTP push.
1053         std::vector<std::string> targets;
1054         targets.emplace_back(BMCWEB_REDFISH_MANAGER_URI_NAME);
1055 
1056         processUpdateRequest(
1057             asyncResp, std::move(payload), req.body(),
1058             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate",
1059             targets);
1060     }
1061     else
1062     {
1063         // Setup callback for when new software detected
1064         monitorForSoftwareAvailable(asyncResp, req,
1065                                     "/redfish/v1/UpdateService");
1066 
1067         uploadImageFile(asyncResp->res, req.body());
1068     }
1069 }
1070 
handleUpdateServicePost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1071 inline void handleUpdateServicePost(
1072     App& app, const crow::Request& req,
1073     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1074 {
1075     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1076     {
1077         return;
1078     }
1079     std::string_view contentType = req.getHeaderValue("Content-Type");
1080 
1081     BMCWEB_LOG_DEBUG("doPost: contentType={}", contentType);
1082 
1083     // Make sure that content type is application/octet-stream
1084     if (bmcweb::asciiIEquals(contentType, "application/octet-stream"))
1085     {
1086         doHTTPUpdate(asyncResp, req);
1087     }
1088     else
1089     {
1090         BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType);
1091         asyncResp->res.result(boost::beast::http::status::bad_request);
1092     }
1093 }
1094 
handleUpdateServiceMultipartUpdatePost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1095 inline void handleUpdateServiceMultipartUpdatePost(
1096     App& app, const crow::Request& req,
1097     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1098 {
1099     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1100     {
1101         return;
1102     }
1103 
1104     std::string_view contentType = req.getHeaderValue("Content-Type");
1105     // Make sure that content type is multipart/form-data
1106     if (contentType.starts_with("multipart/form-data"))
1107     {
1108         MultipartParser parser;
1109 
1110         ParserError ec = parser.parse(req);
1111         if (ec != ParserError::PARSER_SUCCESS)
1112         {
1113             // handle error
1114             BMCWEB_LOG_ERROR("MIME parse failed, ec : {}",
1115                              static_cast<int>(ec));
1116             messages::internalError(asyncResp->res);
1117             return;
1118         }
1119 
1120         updateMultipartContext(asyncResp, req, std::move(parser));
1121     }
1122     else
1123     {
1124         BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType);
1125         asyncResp->res.result(
1126             boost::beast::http::status::unsupported_media_type);
1127     }
1128 }
1129 
handleUpdateServiceGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1130 inline void handleUpdateServiceGet(
1131     App& app, const crow::Request& req,
1132     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1133 {
1134     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1135     {
1136         return;
1137     }
1138     asyncResp->res.jsonValue["@odata.type"] =
1139         "#UpdateService.v1_11_1.UpdateService";
1140     asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService";
1141     asyncResp->res.jsonValue["Id"] = "UpdateService";
1142     asyncResp->res.jsonValue["Description"] = "Service for Software Update";
1143     asyncResp->res.jsonValue["Name"] = "Update Service";
1144 
1145     asyncResp->res.jsonValue["HttpPushUri"] =
1146         "/redfish/v1/UpdateService/update";
1147     asyncResp->res.jsonValue["MultipartHttpPushUri"] =
1148         "/redfish/v1/UpdateService/update-multipart";
1149 
1150     // UpdateService cannot be disabled
1151     asyncResp->res.jsonValue["ServiceEnabled"] = true;
1152     asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] =
1153         "/redfish/v1/UpdateService/FirmwareInventory";
1154     // Get the MaxImageSizeBytes
1155     asyncResp->res.jsonValue["MaxImageSizeBytes"] =
1156         BMCWEB_HTTP_BODY_LIMIT * 1024 * 1024;
1157 
1158     if constexpr (BMCWEB_REDFISH_ALLOW_SIMPLE_UPDATE)
1159     {
1160         // Update Actions object.
1161         nlohmann::json& updateSvcSimpleUpdate =
1162             asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
1163         updateSvcSimpleUpdate["target"] =
1164             "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
1165 
1166         nlohmann::json::array_t allowed;
1167         allowed.emplace_back(update_service::TransferProtocolType::HTTPS);
1168         updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] =
1169             std::move(allowed);
1170     }
1171 
1172     asyncResp->res
1173         .jsonValue["HttpPushUriOptions"]["HttpPushUriApplyTime"]["ApplyTime"] =
1174         update_service::ApplyTime::Immediate;
1175 }
1176 
handleUpdateServiceFirmwareInventoryCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1177 inline void handleUpdateServiceFirmwareInventoryCollectionGet(
1178     App& app, const crow::Request& req,
1179     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1180 {
1181     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1182     {
1183         return;
1184     }
1185     asyncResp->res.jsonValue["@odata.type"] =
1186         "#SoftwareInventoryCollection.SoftwareInventoryCollection";
1187     asyncResp->res.jsonValue["@odata.id"] =
1188         "/redfish/v1/UpdateService/FirmwareInventory";
1189     asyncResp->res.jsonValue["Name"] = "Software Inventory Collection";
1190     const std::array<const std::string_view, 1> iface = {
1191         "xyz.openbmc_project.Software.Version"};
1192 
1193     redfish::collection_util::getCollectionMembers(
1194         asyncResp,
1195         boost::urls::url("/redfish/v1/UpdateService/FirmwareInventory"), iface,
1196         "/xyz/openbmc_project/software");
1197 }
1198 
addRelatedItem(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::urls::url & url)1199 inline void addRelatedItem(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1200                            const boost::urls::url& url)
1201 {
1202     nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
1203     nlohmann::json::object_t item;
1204     item["@odata.id"] = url;
1205     relatedItem.emplace_back(std::move(item));
1206     asyncResp->res.jsonValue["RelatedItem@odata.count"] = relatedItem.size();
1207 }
1208 
1209 /* Fill related item links (i.e. bmc, bios) in for inventory */
getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & purpose)1210 inline void getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1211                             const std::string& purpose)
1212 {
1213     if (purpose == sw_util::bmcPurpose)
1214     {
1215         auto url = boost::urls::format("/redfish/v1/Managers/{}",
1216                                        BMCWEB_REDFISH_MANAGER_URI_NAME);
1217         addRelatedItem(asyncResp, url);
1218     }
1219     else if (purpose == sw_util::biosPurpose)
1220     {
1221         auto url = boost::urls::format("/redfish/v1/Systems/{}/Bios",
1222                                        BMCWEB_REDFISH_SYSTEM_URI_NAME);
1223 
1224         addRelatedItem(asyncResp, url);
1225     }
1226     else
1227     {
1228         BMCWEB_LOG_DEBUG("Unknown software purpose {}", purpose);
1229     }
1230 }
1231 
getSoftwareVersionCallback(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & swId,const boost::system::error_code & ec,const dbus::utility::DBusPropertiesMap & propertiesList)1232 inline void getSoftwareVersionCallback(
1233     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1234     const std::string& swId, const boost::system::error_code& ec,
1235     const dbus::utility::DBusPropertiesMap& propertiesList)
1236 {
1237     if (ec)
1238     {
1239         messages::internalError(asyncResp->res);
1240         return;
1241     }
1242     const std::string* swInvPurpose = nullptr;
1243     const std::string* version = nullptr;
1244     const bool success = sdbusplus::unpackPropertiesNoThrow(
1245         dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose",
1246         swInvPurpose, "Version", version);
1247     if (!success)
1248     {
1249         messages::internalError(asyncResp->res);
1250         return;
1251     }
1252     if (swInvPurpose == nullptr)
1253     {
1254         BMCWEB_LOG_DEBUG("Can't find property \"Purpose\"!");
1255         messages::internalError(asyncResp->res);
1256         return;
1257     }
1258     BMCWEB_LOG_DEBUG("swInvPurpose = {}", *swInvPurpose);
1259     if (version == nullptr)
1260     {
1261         BMCWEB_LOG_DEBUG("Can't find property \"Version\"!");
1262         messages::internalError(asyncResp->res);
1263         return;
1264     }
1265     asyncResp->res.jsonValue["Version"] = *version;
1266     asyncResp->res.jsonValue["Id"] = swId;
1267     // swInvPurpose is of format:
1268     // xyz.openbmc_project.Software.Version.VersionPurpose.ABC
1269     // Translate this to "ABC image"
1270     size_t endDesc = swInvPurpose->rfind('.');
1271     if (endDesc == std::string::npos)
1272     {
1273         messages::internalError(asyncResp->res);
1274         return;
1275     }
1276     endDesc++;
1277     if (endDesc >= swInvPurpose->size())
1278     {
1279         messages::internalError(asyncResp->res);
1280         return;
1281     }
1282     std::string formatDesc = swInvPurpose->substr(endDesc);
1283     asyncResp->res.jsonValue["Description"] = formatDesc + " image";
1284     getRelatedItems(asyncResp, *swInvPurpose);
1285 }
1286 
getSoftwareVersion(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & service,const std::string & path,const std::string & swId)1287 inline void getSoftwareVersion(
1288     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1289     const std::string& service, const std::string& path,
1290     const std::string& swId)
1291 {
1292     dbus::utility::getAllProperties(
1293         service, path, "xyz.openbmc_project.Software.Version",
1294         std::bind_front(getSoftwareVersionCallback, asyncResp, swId));
1295 }
1296 
handleUpdateServiceFirmwareInventoryGetCallback(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::shared_ptr<std::string> & swId,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)1297 inline void handleUpdateServiceFirmwareInventoryGetCallback(
1298     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1299     const std::shared_ptr<std::string>& swId,
1300     const boost::system::error_code& ec,
1301     const dbus::utility::MapperGetSubTreeResponse& subtree)
1302 {
1303     BMCWEB_LOG_DEBUG("doGet callback...");
1304     if (ec)
1305     {
1306         messages::internalError(asyncResp->res);
1307         return;
1308     }
1309     // Ensure we find our input swId, otherwise return an error
1310     bool found = false;
1311     for (const std::pair<
1312              std::string,
1313              std::vector<std::pair<std::string, std::vector<std::string>>>>&
1314              obj : subtree)
1315     {
1316         sdbusplus::message::object_path path(obj.first);
1317         std::string id = path.filename();
1318         if (id.empty())
1319         {
1320             BMCWEB_LOG_DEBUG("Failed to find software id in {}", obj.first);
1321             continue;
1322         }
1323         if (id != *swId)
1324         {
1325             continue;
1326         }
1327         if (obj.second.empty())
1328         {
1329             continue;
1330         }
1331         found = true;
1332         sw_util::getSwStatus(asyncResp, swId, obj.second[0].first);
1333         sw_util::getSwMinimumVersion(asyncResp, swId, obj.second[0].first);
1334         getSoftwareVersion(asyncResp, obj.second[0].first, obj.first, *swId);
1335     }
1336     if (!found)
1337     {
1338         BMCWEB_LOG_WARNING("Input swID {} not found!", *swId);
1339         messages::resourceMissingAtURI(
1340             asyncResp->res,
1341             boost::urls::format(
1342                 "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId));
1343         return;
1344     }
1345     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
1346         "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId);
1347     asyncResp->res.jsonValue["@odata.type"] =
1348         "#SoftwareInventory.v1_1_0.SoftwareInventory";
1349     asyncResp->res.jsonValue["Name"] = "Software Inventory";
1350     asyncResp->res.jsonValue["Status"]["HealthRollup"] = resource::Health::OK;
1351     asyncResp->res.jsonValue["Updateable"] = false;
1352     sw_util::getSwUpdatableStatus(asyncResp, swId);
1353 }
1354 
handleUpdateServiceFirmwareInventoryGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & param)1355 inline void handleUpdateServiceFirmwareInventoryGet(
1356     App& app, const crow::Request& req,
1357     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1358     const std::string& param)
1359 {
1360     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1361     {
1362         return;
1363     }
1364     std::shared_ptr<std::string> swId = std::make_shared<std::string>(param);
1365 
1366     constexpr std::array<std::string_view, 1> interfaces = {
1367         "xyz.openbmc_project.Software.Version"};
1368     dbus::utility::getSubTree(
1369         "/xyz/openbmc_project/software/", 0, interfaces,
1370         std::bind_front(handleUpdateServiceFirmwareInventoryGetCallback,
1371                         asyncResp, swId));
1372 }
1373 
requestRoutesUpdateService(App & app)1374 inline void requestRoutesUpdateService(App& app)
1375 {
1376     if constexpr (BMCWEB_REDFISH_ALLOW_SIMPLE_UPDATE)
1377     {
1378         BMCWEB_ROUTE(
1379             app,
1380             "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/")
1381             .privileges(redfish::privileges::postUpdateService)
1382             .methods(boost::beast::http::verb::post)(std::bind_front(
1383                 handleUpdateServiceSimpleUpdateAction, std::ref(app)));
1384     }
1385     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/")
1386         .privileges(redfish::privileges::getSoftwareInventory)
1387         .methods(boost::beast::http::verb::get)(std::bind_front(
1388             handleUpdateServiceFirmwareInventoryGet, std::ref(app)));
1389 
1390     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
1391         .privileges(redfish::privileges::getUpdateService)
1392         .methods(boost::beast::http::verb::get)(
1393             std::bind_front(handleUpdateServiceGet, std::ref(app)));
1394 
1395     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/")
1396         .privileges(redfish::privileges::postUpdateService)
1397         .methods(boost::beast::http::verb::post)(
1398             std::bind_front(handleUpdateServicePost, std::ref(app)));
1399 
1400     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update-multipart/")
1401         .privileges(redfish::privileges::postUpdateService)
1402         .methods(boost::beast::http::verb::post)(std::bind_front(
1403             handleUpdateServiceMultipartUpdatePost, std::ref(app)));
1404 
1405     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/")
1406         .privileges(redfish::privileges::getSoftwareInventoryCollection)
1407         .methods(boost::beast::http::verb::get)(std::bind_front(
1408             handleUpdateServiceFirmwareInventoryCollectionGet, std::ref(app)));
1409 }
1410 
1411 } // namespace redfish
1412