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 "account_service.hpp"
19 #include "app.hpp"
20 #include "async_resp.hpp"
21 #include "credential_pipe.hpp"
22 #include "dbus_utility.hpp"
23 #include "generated/enums/virtual_media.hpp"
24 #include "query.hpp"
25 #include "registries/privilege_registry.hpp"
26 #include "utils/json_utils.hpp"
27 
28 #include <boost/url/format.hpp>
29 #include <boost/url/url_view.hpp>
30 #include <boost/url/url_view_base.hpp>
31 
32 #include <array>
33 #include <ranges>
34 #include <string_view>
35 
36 namespace redfish
37 {
38 
39 enum class VmMode
40 {
41     Invalid,
42     Legacy,
43     Proxy
44 };
45 
46 inline VmMode parseObjectPathAndGetMode(
47     const sdbusplus::message::object_path& itemPath, const std::string& resName)
48 {
49     std::string thisPath = itemPath.filename();
50     BMCWEB_LOG_DEBUG("Filename: {}, ThisPath: {}", itemPath.str, thisPath);
51 
52     if (thisPath.empty())
53     {
54         return VmMode::Invalid;
55     }
56 
57     if (thisPath != resName)
58     {
59         return VmMode::Invalid;
60     }
61 
62     auto mode = itemPath.parent_path();
63     auto type = mode.parent_path();
64 
65     if (mode.filename().empty() || type.filename().empty())
66     {
67         return VmMode::Invalid;
68     }
69 
70     if (type.filename() != "VirtualMedia")
71     {
72         return VmMode::Invalid;
73     }
74     std::string modeStr = mode.filename();
75     if (modeStr == "Legacy")
76     {
77         return VmMode::Legacy;
78     }
79     if (modeStr == "Proxy")
80     {
81         return VmMode::Proxy;
82     }
83     return VmMode::Invalid;
84 }
85 
86 using CheckItemHandler =
87     std::function<void(const std::string& service, const std::string& resName,
88                        const std::shared_ptr<bmcweb::AsyncResp>&,
89                        const std::pair<sdbusplus::message::object_path,
90                                        dbus::utility::DBusInterfacesMap>&)>;
91 
92 inline void
93     findAndParseObject(const std::string& service, const std::string& resName,
94                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
95                        CheckItemHandler&& handler)
96 {
97     sdbusplus::message::object_path path("/xyz/openbmc_project/VirtualMedia");
98     dbus::utility::getManagedObjects(
99         service, path,
100         [service, resName, asyncResp, handler = std::move(handler)](
101             const boost::system::error_code& ec,
102             const dbus::utility::ManagedObjectType& subtree) {
103             if (ec)
104             {
105                 BMCWEB_LOG_DEBUG("DBUS response error");
106 
107                 return;
108             }
109 
110             for (const auto& item : subtree)
111             {
112                 VmMode mode = parseObjectPathAndGetMode(item.first, resName);
113                 if (mode != VmMode::Invalid)
114                 {
115                     handler(service, resName, asyncResp, item);
116                     return;
117                 }
118             }
119 
120             BMCWEB_LOG_DEBUG("Parent item not found");
121             asyncResp->res.result(boost::beast::http::status::not_found);
122         });
123 }
124 
125 /**
126  * @brief Function extracts transfer protocol name from URI.
127  */
128 inline std::string getTransferProtocolTypeFromUri(const std::string& imageUri)
129 {
130     boost::system::result<boost::urls::url_view> url =
131         boost::urls::parse_uri(imageUri);
132     if (!url)
133     {
134         return "None";
135     }
136     std::string_view scheme = url->scheme();
137     if (scheme == "smb")
138     {
139         return "CIFS";
140     }
141     if (scheme == "https")
142     {
143         return "HTTPS";
144     }
145 
146     return "None";
147 }
148 
149 /**
150  * @brief Read all known properties from VM object interfaces
151  */
152 inline void
153     vmParseInterfaceObject(const dbus::utility::DBusInterfacesMap& interfaces,
154                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
155 {
156     for (const auto& [interface, values] : interfaces)
157     {
158         if (interface == "xyz.openbmc_project.VirtualMedia.MountPoint")
159         {
160             for (const auto& [property, value] : values)
161             {
162                 if (property == "EndpointId")
163                 {
164                     const std::string* endpointIdValue =
165                         std::get_if<std::string>(&value);
166                     if (endpointIdValue == nullptr)
167                     {
168                         continue;
169                     }
170                     if (!endpointIdValue->empty())
171                     {
172                         // Proxy mode
173                         asyncResp->res
174                             .jsonValue["Oem"]["OpenBMC"]["WebSocketEndpoint"] =
175                             *endpointIdValue;
176                         asyncResp->res.jsonValue["TransferProtocolType"] =
177                             "OEM";
178                     }
179                 }
180                 if (property == "ImageURL")
181                 {
182                     const std::string* imageUrlValue =
183                         std::get_if<std::string>(&value);
184                     if (imageUrlValue != nullptr && !imageUrlValue->empty())
185                     {
186                         std::filesystem::path filePath = *imageUrlValue;
187                         if (!filePath.has_filename())
188                         {
189                             // this will handle https share, which not
190                             // necessarily has to have filename given.
191                             asyncResp->res.jsonValue["ImageName"] = "";
192                         }
193                         else
194                         {
195                             asyncResp->res.jsonValue["ImageName"] =
196                                 filePath.filename();
197                         }
198 
199                         asyncResp->res.jsonValue["Image"] = *imageUrlValue;
200                         asyncResp->res.jsonValue["TransferProtocolType"] =
201                             getTransferProtocolTypeFromUri(*imageUrlValue);
202 
203                         asyncResp->res.jsonValue["ConnectedVia"] =
204                             virtual_media::ConnectedVia::URI;
205                     }
206                 }
207                 if (property == "WriteProtected")
208                 {
209                     const bool* writeProtectedValue = std::get_if<bool>(&value);
210                     if (writeProtectedValue != nullptr)
211                     {
212                         asyncResp->res.jsonValue["WriteProtected"] =
213                             *writeProtectedValue;
214                     }
215                 }
216             }
217         }
218         if (interface == "xyz.openbmc_project.VirtualMedia.Process")
219         {
220             for (const auto& [property, value] : values)
221             {
222                 if (property == "Active")
223                 {
224                     const bool* activeValue = std::get_if<bool>(&value);
225                     if (activeValue == nullptr)
226                     {
227                         BMCWEB_LOG_DEBUG("Value Active not found");
228                         return;
229                     }
230                     asyncResp->res.jsonValue["Inserted"] = *activeValue;
231 
232                     if (*activeValue)
233                     {
234                         asyncResp->res.jsonValue["ConnectedVia"] =
235                             virtual_media::ConnectedVia::Applet;
236                     }
237                 }
238             }
239         }
240     }
241 }
242 
243 /**
244  * @brief Fill template for Virtual Media Item.
245  */
246 inline nlohmann::json vmItemTemplate(const std::string& name,
247                                      const std::string& resName)
248 {
249     nlohmann::json item;
250     item["@odata.id"] = boost::urls::format(
251         "/redfish/v1/Managers/{}/VirtualMedia/{}", name, resName);
252 
253     item["@odata.type"] = "#VirtualMedia.v1_3_0.VirtualMedia";
254     item["Name"] = "Virtual Removable Media";
255     item["Id"] = resName;
256     item["WriteProtected"] = true;
257     item["ConnectedVia"] = virtual_media::ConnectedVia::NotConnected;
258     item["MediaTypes"] = nlohmann::json::array_t({"CD", "USBStick"});
259     item["TransferMethod"] = virtual_media::TransferMethod::Stream;
260     item["Oem"]["OpenBMC"]["@odata.type"] =
261         "#OpenBMCVirtualMedia.v1_0_0.VirtualMedia";
262     item["Oem"]["OpenBMC"]["@odata.id"] = boost::urls::format(
263         "/redfish/v1/Managers/{}/VirtualMedia/{}#/Oem/OpenBMC", name, resName);
264 
265     return item;
266 }
267 
268 /**
269  *  @brief Fills collection data
270  */
271 inline void getVmResourceList(std::shared_ptr<bmcweb::AsyncResp> asyncResp,
272                               const std::string& service,
273                               const std::string& name)
274 {
275     BMCWEB_LOG_DEBUG("Get available Virtual Media resources.");
276     sdbusplus::message::object_path objPath(
277         "/xyz/openbmc_project/VirtualMedia");
278     dbus::utility::getManagedObjects(
279         service, objPath,
280         [name, asyncResp{std::move(asyncResp)}](
281             const boost::system::error_code& ec,
282             const dbus::utility::ManagedObjectType& subtree) {
283             if (ec)
284             {
285                 BMCWEB_LOG_DEBUG("DBUS response error");
286                 return;
287             }
288             nlohmann::json& members = asyncResp->res.jsonValue["Members"];
289             members = nlohmann::json::array();
290 
291             for (const auto& object : subtree)
292             {
293                 nlohmann::json item;
294                 std::string path = object.first.filename();
295                 if (path.empty())
296                 {
297                     continue;
298                 }
299 
300                 item["@odata.id"] = boost::urls::format(
301                     "/redfish/v1/Managers/{}/VirtualMedia/{}", name, path);
302                 members.emplace_back(std::move(item));
303             }
304             asyncResp->res.jsonValue["Members@odata.count"] = members.size();
305         });
306 }
307 
308 inline void
309     afterGetVmData(const std::string& name, const std::string& /*service*/,
310                    const std::string& resName,
311                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
312                    const std::pair<sdbusplus::message::object_path,
313                                    dbus::utility::DBusInterfacesMap>& item)
314 {
315     VmMode mode = parseObjectPathAndGetMode(item.first, resName);
316     if (mode == VmMode::Invalid)
317     {
318         return;
319     }
320 
321     asyncResp->res.jsonValue = vmItemTemplate(name, resName);
322 
323     // Check if dbus path is Legacy type
324     if (mode == VmMode::Legacy)
325     {
326         asyncResp->res.jsonValue["Actions"]["#VirtualMedia.InsertMedia"]
327                                 ["target"] = boost::urls::format(
328             "/redfish/v1/Managers/{}/VirtualMedia/{}/Actions/VirtualMedia.InsertMedia",
329             name, resName);
330     }
331 
332     vmParseInterfaceObject(item.second, asyncResp);
333 
334     asyncResp->res.jsonValue["Actions"]["#VirtualMedia.EjectMedia"]
335                             ["target"] = boost::urls::format(
336         "/redfish/v1/Managers/{}/VirtualMedia/{}/Actions/VirtualMedia.EjectMedia",
337         name, resName);
338 }
339 
340 /**
341  *  @brief Fills data for specific resource
342  */
343 inline void getVmData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
344                       const std::string& service, const std::string& name,
345                       const std::string& resName)
346 {
347     BMCWEB_LOG_DEBUG("Get Virtual Media resource data.");
348 
349     findAndParseObject(service, resName, asyncResp,
350                        std::bind_front(afterGetVmData, name));
351 }
352 
353 /**
354  * @brief Transfer protocols supported for InsertMedia action.
355  *
356  */
357 enum class TransferProtocol
358 {
359     https,
360     smb,
361     invalid
362 };
363 
364 /**
365  * @brief Function extracts transfer protocol type from URI.
366  *
367  */
368 inline std::optional<TransferProtocol>
369     getTransferProtocolFromUri(const boost::urls::url_view_base& imageUri)
370 {
371     std::string_view scheme = imageUri.scheme();
372     if (scheme == "smb")
373     {
374         return TransferProtocol::smb;
375     }
376     if (scheme == "https")
377     {
378         return TransferProtocol::https;
379     }
380     if (!scheme.empty())
381     {
382         return TransferProtocol::invalid;
383     }
384 
385     return {};
386 }
387 
388 /**
389  * @brief Function convert transfer protocol from string param.
390  *
391  */
392 inline std::optional<TransferProtocol> getTransferProtocolFromParam(
393     const std::optional<std::string>& transferProtocolType)
394 {
395     if (!transferProtocolType)
396     {
397         return {};
398     }
399 
400     if (*transferProtocolType == "CIFS")
401     {
402         return TransferProtocol::smb;
403     }
404 
405     if (*transferProtocolType == "HTTPS")
406     {
407         return TransferProtocol::https;
408     }
409 
410     return TransferProtocol::invalid;
411 }
412 
413 /**
414  * @brief Function extends URI with transfer protocol type.
415  *
416  */
417 inline std::string getUriWithTransferProtocol(
418     const std::string& imageUri, const TransferProtocol& transferProtocol)
419 {
420     if (transferProtocol == TransferProtocol::smb)
421     {
422         return "smb://" + imageUri;
423     }
424 
425     if (transferProtocol == TransferProtocol::https)
426     {
427         return "https://" + imageUri;
428     }
429 
430     return imageUri;
431 }
432 
433 struct InsertMediaActionParams
434 {
435     std::optional<std::string> imageUrl;
436     std::optional<std::string> userName;
437     std::optional<std::string> password;
438     std::optional<std::string> transferMethod;
439     std::optional<std::string> transferProtocolType;
440     std::optional<bool> writeProtected = true;
441     std::optional<bool> inserted;
442 };
443 
444 /**
445  * @brief Function transceives data with dbus directly.
446  *
447  * All BMC state properties will be retrieved before sending reset request.
448  */
449 inline void doMountVmLegacy(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
450                             const std::string& service, const std::string& name,
451                             const std::string& imageUrl, bool rw,
452                             std::string&& userName, std::string&& password)
453 {
454     int fd = -1;
455     std::shared_ptr<CredentialsPipe> secretPipe;
456     if (!userName.empty() || !password.empty())
457     {
458         // Payload must contain data + NULL delimiters
459         constexpr const size_t secretLimit = 1024;
460         if (userName.size() + password.size() + 2 > secretLimit)
461         {
462             BMCWEB_LOG_ERROR("Credentials too long to handle");
463             messages::unrecognizedRequestBody(asyncResp->res);
464             return;
465         }
466 
467         // Open pipe
468         secretPipe = std::make_shared<CredentialsPipe>(
469             crow::connections::systemBus->get_io_context());
470         fd = secretPipe->releaseFd();
471 
472         // Pass secret over pipe
473         secretPipe->asyncWrite(
474             std::move(userName), std::move(password),
475             [asyncResp,
476              secretPipe](const boost::system::error_code& ec, std::size_t) {
477                 if (ec)
478                 {
479                     BMCWEB_LOG_ERROR("Failed to pass secret: {}", ec);
480                     messages::internalError(asyncResp->res);
481                 }
482             });
483     }
484 
485     dbus::utility::DbusVariantType unixFd(
486         std::in_place_type<sdbusplus::message::unix_fd>, fd);
487 
488     sdbusplus::message::object_path path(
489         "/xyz/openbmc_project/VirtualMedia/Legacy");
490     path /= name;
491     crow::connections::systemBus->async_method_call(
492         [asyncResp,
493          secretPipe](const boost::system::error_code& ec, bool success) {
494             if (ec)
495             {
496                 BMCWEB_LOG_ERROR("Bad D-Bus request error: {}", ec);
497                 messages::internalError(asyncResp->res);
498                 return;
499             }
500             if (!success)
501             {
502                 BMCWEB_LOG_ERROR("Service responded with error");
503                 messages::internalError(asyncResp->res);
504             }
505         },
506         service, path.str, "xyz.openbmc_project.VirtualMedia.Legacy", "Mount",
507         imageUrl, rw, unixFd);
508 }
509 
510 /**
511  * @brief Function validate parameters of insert media request.
512  *
513  */
514 inline void validateParams(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
515                            const std::string& service,
516                            const std::string& resName,
517                            InsertMediaActionParams& actionParams)
518 {
519     BMCWEB_LOG_DEBUG("Validation started");
520     // required param imageUrl must not be empty
521     if (!actionParams.imageUrl)
522     {
523         BMCWEB_LOG_ERROR("Request action parameter Image is empty.");
524 
525         messages::propertyValueFormatError(asyncResp->res, "<empty>", "Image");
526 
527         return;
528     }
529 
530     // optional param inserted must be true
531     if (actionParams.inserted && !*actionParams.inserted)
532     {
533         BMCWEB_LOG_ERROR(
534             "Request action optional parameter Inserted must be true.");
535 
536         messages::actionParameterNotSupported(asyncResp->res, "Inserted",
537                                               "InsertMedia");
538 
539         return;
540     }
541 
542     // optional param transferMethod must be stream
543     if (actionParams.transferMethod &&
544         (*actionParams.transferMethod != "Stream"))
545     {
546         BMCWEB_LOG_ERROR("Request action optional parameter "
547                          "TransferMethod must be Stream.");
548 
549         messages::actionParameterNotSupported(asyncResp->res, "TransferMethod",
550                                               "InsertMedia");
551 
552         return;
553     }
554     boost::system::result<boost::urls::url_view> url =
555         boost::urls::parse_uri(*actionParams.imageUrl);
556     if (!url)
557     {
558         messages::actionParameterValueFormatError(
559             asyncResp->res, *actionParams.imageUrl, "Image", "InsertMedia");
560         return;
561     }
562     std::optional<TransferProtocol> uriTransferProtocolType =
563         getTransferProtocolFromUri(*url);
564 
565     std::optional<TransferProtocol> paramTransferProtocolType =
566         getTransferProtocolFromParam(actionParams.transferProtocolType);
567 
568     // ImageUrl does not contain valid protocol type
569     if (uriTransferProtocolType &&
570         *uriTransferProtocolType == TransferProtocol::invalid)
571     {
572         BMCWEB_LOG_ERROR("Request action parameter ImageUrl must "
573                          "contain specified protocol type from list: "
574                          "(smb, https).");
575 
576         messages::resourceAtUriInUnknownFormat(asyncResp->res, *url);
577 
578         return;
579     }
580 
581     // transferProtocolType should contain value from list
582     if (paramTransferProtocolType &&
583         *paramTransferProtocolType == TransferProtocol::invalid)
584     {
585         BMCWEB_LOG_ERROR("Request action parameter TransferProtocolType "
586                          "must be provided with value from list: "
587                          "(CIFS, HTTPS).");
588 
589         messages::propertyValueNotInList(
590             asyncResp->res, actionParams.transferProtocolType.value_or(""),
591             "TransferProtocolType");
592         return;
593     }
594 
595     // valid transfer protocol not provided either with URI nor param
596     if (!uriTransferProtocolType && !paramTransferProtocolType)
597     {
598         BMCWEB_LOG_ERROR("Request action parameter ImageUrl must "
599                          "contain specified protocol type or param "
600                          "TransferProtocolType must be provided.");
601 
602         messages::resourceAtUriInUnknownFormat(asyncResp->res, *url);
603 
604         return;
605     }
606 
607     // valid transfer protocol provided both with URI and param
608     if (paramTransferProtocolType && uriTransferProtocolType)
609     {
610         // check if protocol is the same for URI and param
611         if (*paramTransferProtocolType != *uriTransferProtocolType)
612         {
613             BMCWEB_LOG_ERROR("Request action parameter "
614                              "TransferProtocolType must  contain the "
615                              "same protocol type as protocol type "
616                              "provided with param imageUrl.");
617 
618             messages::actionParameterValueTypeError(
619                 asyncResp->res, actionParams.transferProtocolType.value_or(""),
620                 "TransferProtocolType", "InsertMedia");
621 
622             return;
623         }
624     }
625 
626     // validation passed, add protocol to URI if needed
627     if (!uriTransferProtocolType && paramTransferProtocolType)
628     {
629         actionParams.imageUrl = getUriWithTransferProtocol(
630             *actionParams.imageUrl, *paramTransferProtocolType);
631     }
632 
633     if (!actionParams.userName)
634     {
635         actionParams.userName = "";
636     }
637 
638     if (!actionParams.password)
639     {
640         actionParams.password = "";
641     }
642 
643     doMountVmLegacy(asyncResp, service, resName, *actionParams.imageUrl,
644                     !(actionParams.writeProtected.value_or(false)),
645                     std::move(*actionParams.userName),
646                     std::move(*actionParams.password));
647 }
648 
649 /**
650  * @brief Function transceives data with dbus directly.
651  *
652  * All BMC state properties will be retrieved before sending reset request.
653  */
654 inline void doEjectAction(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
655                           const std::string& service, const std::string& name,
656                           bool legacy)
657 {
658     // Legacy mount requires parameter with image
659     if (legacy)
660     {
661         crow::connections::systemBus->async_method_call(
662             [asyncResp](const boost::system::error_code& ec) {
663                 if (ec)
664                 {
665                     BMCWEB_LOG_ERROR("Bad D-Bus request error: {}", ec);
666 
667                     messages::internalError(asyncResp->res);
668                     return;
669                 }
670             },
671             service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name,
672             "xyz.openbmc_project.VirtualMedia.Legacy", "Unmount");
673     }
674     else // proxy
675     {
676         crow::connections::systemBus->async_method_call(
677             [asyncResp](const boost::system::error_code& ec) {
678                 if (ec)
679                 {
680                     BMCWEB_LOG_ERROR("Bad D-Bus request error: {}", ec);
681 
682                     messages::internalError(asyncResp->res);
683                     return;
684                 }
685             },
686             service, "/xyz/openbmc_project/VirtualMedia/Proxy/" + name,
687             "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount");
688     }
689 }
690 
691 inline void handleManagersVirtualMediaActionInsertPost(
692     crow::App& app, const crow::Request& req,
693     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
694     const std::string& name, const std::string& resName)
695 {
696     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
697     {
698         return;
699     }
700 
701     constexpr std::string_view action = "VirtualMedia.InsertMedia";
702     if (name != "bmc")
703     {
704         messages::resourceNotFound(asyncResp->res, action, resName);
705 
706         return;
707     }
708     InsertMediaActionParams actionParams;
709 
710     // Read obligatory parameters (url of image)
711     if (!json_util::readJsonAction(
712             req, asyncResp->res, "Image", actionParams.imageUrl,
713             "WriteProtected", actionParams.writeProtected, "UserName",
714             actionParams.userName, "Password", actionParams.password,
715             "Inserted", actionParams.inserted, "TransferMethod",
716             actionParams.transferMethod, "TransferProtocolType",
717             actionParams.transferProtocolType))
718     {
719         return;
720     }
721 
722     dbus::utility::getDbusObject(
723         "/xyz/openbmc_project/VirtualMedia", {},
724         [asyncResp, action, actionParams,
725          resName](const boost::system::error_code& ec,
726                   const dbus::utility::MapperGetObject& getObjectType) mutable {
727             if (ec)
728             {
729                 BMCWEB_LOG_ERROR("ObjectMapper::GetObject call failed: {}", ec);
730                 messages::resourceNotFound(asyncResp->res, action, resName);
731 
732                 return;
733             }
734 
735             std::string service = getObjectType.begin()->first;
736             BMCWEB_LOG_DEBUG("GetObjectType: {}", service);
737 
738             sdbusplus::message::object_path path(
739                 "/xyz/openbmc_project/VirtualMedia");
740             dbus::utility::getManagedObjects(
741                 service, path,
742                 [service, resName, action, actionParams, asyncResp](
743                     const boost::system::error_code& ec2,
744                     const dbus::utility::ManagedObjectType& subtree) mutable {
745                     if (ec2)
746                     {
747                         // Not possible in proxy mode
748                         BMCWEB_LOG_DEBUG("InsertMedia not "
749                                          "allowed in proxy mode");
750                         messages::resourceNotFound(asyncResp->res, action,
751                                                    resName);
752 
753                         return;
754                     }
755                     for (const auto& object : subtree)
756                     {
757                         VmMode mode =
758                             parseObjectPathAndGetMode(object.first, resName);
759                         if (mode == VmMode::Legacy)
760                         {
761                             validateParams(asyncResp, service, resName,
762                                            actionParams);
763 
764                             return;
765                         }
766                     }
767                     BMCWEB_LOG_DEBUG("Parent item not found");
768                     messages::resourceNotFound(asyncResp->res, "VirtualMedia",
769                                                resName);
770                 });
771         });
772 }
773 
774 inline void handleManagersVirtualMediaActionEject(
775     crow::App& app, const crow::Request& req,
776     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
777     const std::string& managerName, const std::string& resName)
778 {
779     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
780     {
781         return;
782     }
783 
784     constexpr std::string_view action = "VirtualMedia.EjectMedia";
785     if (managerName != "bmc")
786     {
787         messages::resourceNotFound(asyncResp->res, action, resName);
788 
789         return;
790     }
791 
792     dbus::utility::getDbusObject(
793         "/xyz/openbmc_project/VirtualMedia", {},
794         [asyncResp, action,
795          resName](const boost::system::error_code& ec2,
796                   const dbus::utility::MapperGetObject& getObjectType) {
797             if (ec2)
798             {
799                 BMCWEB_LOG_ERROR("ObjectMapper::GetObject call failed: {}",
800                                  ec2);
801                 messages::internalError(asyncResp->res);
802 
803                 return;
804             }
805             std::string service = getObjectType.begin()->first;
806             BMCWEB_LOG_DEBUG("GetObjectType: {}", service);
807 
808             sdbusplus::message::object_path path(
809                 "/xyz/openbmc_project/VirtualMedia");
810             dbus::utility::getManagedObjects(
811                 service, path,
812                 [resName, service, action,
813                  asyncResp](const boost::system::error_code& ec,
814                             const dbus::utility::ManagedObjectType& subtree) {
815                     if (ec)
816                     {
817                         BMCWEB_LOG_ERROR("ObjectMapper : No Service found");
818                         messages::resourceNotFound(asyncResp->res, action,
819                                                    resName);
820                         return;
821                     }
822 
823                     for (const auto& object : subtree)
824                     {
825                         VmMode mode =
826                             parseObjectPathAndGetMode(object.first, resName);
827                         if (mode != VmMode::Invalid)
828                         {
829                             doEjectAction(asyncResp, service, resName,
830                                           mode == VmMode::Legacy);
831                             return;
832                         }
833                     }
834                     BMCWEB_LOG_DEBUG("Parent item not found");
835                     messages::resourceNotFound(asyncResp->res, "VirtualMedia",
836                                                resName);
837                 });
838         });
839 }
840 
841 inline void handleManagersVirtualMediaCollectionGet(
842     crow::App& app, const crow::Request& req,
843     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
844     const std::string& name)
845 {
846     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
847     {
848         return;
849     }
850     if (name != "bmc")
851     {
852         messages::resourceNotFound(asyncResp->res, "VirtualMedia", name);
853 
854         return;
855     }
856 
857     asyncResp->res.jsonValue["@odata.type"] =
858         "#VirtualMediaCollection.VirtualMediaCollection";
859     asyncResp->res.jsonValue["Name"] = "Virtual Media Services";
860     asyncResp->res.jsonValue["@odata.id"] =
861         boost::urls::format("/redfish/v1/Managers/{}/VirtualMedia", name);
862 
863     dbus::utility::getDbusObject(
864         "/xyz/openbmc_project/VirtualMedia", {},
865         [asyncResp, name](const boost::system::error_code& ec,
866                           const dbus::utility::MapperGetObject& getObjectType) {
867             if (ec)
868             {
869                 BMCWEB_LOG_ERROR("ObjectMapper::GetObject call failed: {}", ec);
870                 messages::internalError(asyncResp->res);
871 
872                 return;
873             }
874             std::string service = getObjectType.begin()->first;
875             BMCWEB_LOG_DEBUG("GetObjectType: {}", service);
876 
877             getVmResourceList(asyncResp, service, name);
878         });
879 }
880 
881 inline void
882     handleVirtualMediaGet(crow::App& app, const crow::Request& req,
883                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
884                           const std::string& name, const std::string& resName)
885 {
886     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
887     {
888         return;
889     }
890     if (name != "bmc")
891     {
892         messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName);
893 
894         return;
895     }
896 
897     dbus::utility::getDbusObject(
898         "/xyz/openbmc_project/VirtualMedia", {},
899         [asyncResp, name,
900          resName](const boost::system::error_code& ec,
901                   const dbus::utility::MapperGetObject& getObjectType) {
902             if (ec)
903             {
904                 BMCWEB_LOG_ERROR("ObjectMapper::GetObject call failed: {}", ec);
905                 messages::internalError(asyncResp->res);
906 
907                 return;
908             }
909             std::string service = getObjectType.begin()->first;
910             BMCWEB_LOG_DEBUG("GetObjectType: {}", service);
911 
912             getVmData(asyncResp, service, name, resName);
913         });
914 }
915 
916 inline void requestNBDVirtualMediaRoutes(App& app)
917 {
918     BMCWEB_ROUTE(
919         app,
920         "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/VirtualMedia.InsertMedia")
921         .privileges(redfish::privileges::postVirtualMedia)
922         .methods(boost::beast::http::verb::post)(std::bind_front(
923             handleManagersVirtualMediaActionInsertPost, std::ref(app)));
924 
925     BMCWEB_ROUTE(
926         app,
927         "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/VirtualMedia.EjectMedia")
928         .privileges(redfish::privileges::postVirtualMedia)
929         .methods(boost::beast::http::verb::post)(std::bind_front(
930             handleManagersVirtualMediaActionEject, std::ref(app)));
931 
932     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/")
933         .privileges(redfish::privileges::getVirtualMediaCollection)
934         .methods(boost::beast::http::verb::get)(std::bind_front(
935             handleManagersVirtualMediaCollectionGet, std::ref(app)));
936 
937     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/<str>/")
938         .privileges(redfish::privileges::getVirtualMedia)
939         .methods(boost::beast::http::verb::get)(
940             std::bind_front(handleVirtualMediaGet, std::ref(app)));
941 }
942 
943 } // namespace redfish
944