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