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 "generated/enums/action_info.hpp"
23 #include "generated/enums/manager.hpp"
24 #include "generated/enums/resource.hpp"
25 #include "query.hpp"
26 #include "redfish_util.hpp"
27 #include "registries/privilege_registry.hpp"
28 #include "utils/dbus_utils.hpp"
29 #include "utils/json_utils.hpp"
30 #include "utils/sw_utils.hpp"
31 #include "utils/systemd_utils.hpp"
32 #include "utils/time_utils.hpp"
33 
34 #include <boost/system/error_code.hpp>
35 #include <boost/url/format.hpp>
36 #include <sdbusplus/asio/property.hpp>
37 #include <sdbusplus/unpack_properties.hpp>
38 
39 #include <algorithm>
40 #include <array>
41 #include <cstdint>
42 #include <memory>
43 #include <optional>
44 #include <ranges>
45 #include <sstream>
46 #include <string>
47 #include <string_view>
48 #include <variant>
49 
50 namespace redfish
51 {
52 
53 /**
54  * Function reboots the BMC.
55  *
56  * @param[in] asyncResp - Shared pointer for completing asynchronous calls
57  */
58 inline void
59     doBMCGracefulRestart(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
60 {
61     const char* processName = "xyz.openbmc_project.State.BMC";
62     const char* objectPath = "/xyz/openbmc_project/state/bmc0";
63     const char* interfaceName = "xyz.openbmc_project.State.BMC";
64     const std::string& propertyValue =
65         "xyz.openbmc_project.State.BMC.Transition.Reboot";
66     const char* destProperty = "RequestedBMCTransition";
67 
68     // Create the D-Bus variant for D-Bus call.
69     sdbusplus::asio::setProperty(
70         *crow::connections::systemBus, processName, objectPath, interfaceName,
71         destProperty, propertyValue,
72         [asyncResp](const boost::system::error_code& ec) {
73             // Use "Set" method to set the property value.
74             if (ec)
75             {
76                 BMCWEB_LOG_DEBUG("[Set] Bad D-Bus request error: {}", ec);
77                 messages::internalError(asyncResp->res);
78                 return;
79             }
80 
81             messages::success(asyncResp->res);
82         });
83 }
84 
85 inline void
86     doBMCForceRestart(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
87 {
88     const char* processName = "xyz.openbmc_project.State.BMC";
89     const char* objectPath = "/xyz/openbmc_project/state/bmc0";
90     const char* interfaceName = "xyz.openbmc_project.State.BMC";
91     const std::string& propertyValue =
92         "xyz.openbmc_project.State.BMC.Transition.HardReboot";
93     const char* destProperty = "RequestedBMCTransition";
94 
95     // Create the D-Bus variant for D-Bus call.
96     sdbusplus::asio::setProperty(
97         *crow::connections::systemBus, processName, objectPath, interfaceName,
98         destProperty, propertyValue,
99         [asyncResp](const boost::system::error_code& ec) {
100             // Use "Set" method to set the property value.
101             if (ec)
102             {
103                 BMCWEB_LOG_DEBUG("[Set] Bad D-Bus request error: {}", ec);
104                 messages::internalError(asyncResp->res);
105                 return;
106             }
107 
108             messages::success(asyncResp->res);
109         });
110 }
111 
112 /**
113  * ManagerResetAction class supports the POST method for the Reset (reboot)
114  * action.
115  */
116 inline void requestRoutesManagerResetAction(App& app)
117 {
118     /**
119      * Function handles POST method request.
120      * Analyzes POST body before sending Reset (Reboot) request data to D-Bus.
121      * OpenBMC supports ResetType "GracefulRestart" and "ForceRestart".
122      */
123 
124     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/Actions/Manager.Reset/")
125         .privileges(redfish::privileges::postManager)
126         .methods(boost::beast::http::verb::post)(
127             [&app](const crow::Request& req,
128                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
129                    const std::string& managerId) {
130                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
131                 {
132                     return;
133                 }
134                 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
135                 {
136                     messages::resourceNotFound(asyncResp->res, "Manager",
137                                                managerId);
138                     return;
139                 }
140 
141                 BMCWEB_LOG_DEBUG("Post Manager Reset.");
142 
143                 std::string resetType;
144 
145                 if (!json_util::readJsonAction(req, asyncResp->res, "ResetType",
146                                                resetType))
147                 {
148                     return;
149                 }
150 
151                 if (resetType == "GracefulRestart")
152                 {
153                     BMCWEB_LOG_DEBUG("Proceeding with {}", resetType);
154                     doBMCGracefulRestart(asyncResp);
155                     return;
156                 }
157                 if (resetType == "ForceRestart")
158                 {
159                     BMCWEB_LOG_DEBUG("Proceeding with {}", resetType);
160                     doBMCForceRestart(asyncResp);
161                     return;
162                 }
163                 BMCWEB_LOG_DEBUG("Invalid property value for ResetType: {}",
164                                  resetType);
165                 messages::actionParameterNotSupported(asyncResp->res, resetType,
166                                                       "ResetType");
167 
168                 return;
169             });
170 }
171 
172 /**
173  * ManagerResetToDefaultsAction class supports POST method for factory reset
174  * action.
175  */
176 inline void requestRoutesManagerResetToDefaultsAction(App& app)
177 {
178     /**
179      * Function handles ResetToDefaults POST method request.
180      *
181      * Analyzes POST body message and factory resets BMC by calling
182      * BMC code updater factory reset followed by a BMC reboot.
183      *
184      * BMC code updater factory reset wipes the whole BMC read-write
185      * filesystem which includes things like the network settings.
186      *
187      * OpenBMC only supports ResetToDefaultsType "ResetAll".
188      */
189 
190     BMCWEB_ROUTE(app,
191                  "/redfish/v1/Managers/<str>/Actions/Manager.ResetToDefaults/")
192         .privileges(redfish::privileges::postManager)
193         .methods(
194             boost::beast::http::verb::
195                 post)([&app](
196                           const crow::Request& req,
197                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
198                           const std::string& managerId) {
199             if (!redfish::setUpRedfishRoute(app, req, asyncResp))
200             {
201                 return;
202             }
203 
204             if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
205             {
206                 messages::resourceNotFound(asyncResp->res, "Manager",
207                                            managerId);
208                 return;
209             }
210 
211             BMCWEB_LOG_DEBUG("Post ResetToDefaults.");
212 
213             std::optional<std::string> resetType;
214             std::optional<std::string> resetToDefaultsType;
215 
216             if (!json_util::readJsonAction( //
217                     req, asyncResp->res, //
218                     "ResetToDefaultsType", resetToDefaultsType, //
219                     "ResetType", resetType //
220                     ))
221             {
222                 BMCWEB_LOG_DEBUG("Missing property ResetType.");
223 
224                 messages::actionParameterMissing(
225                     asyncResp->res, "ResetToDefaults", "ResetType");
226                 return;
227             }
228 
229             if (resetToDefaultsType && !resetType)
230             {
231                 BMCWEB_LOG_WARNING(
232                     "Using deprecated ResetToDefaultsType, should be ResetType."
233                     "Support for the ResetToDefaultsType will be dropped in 2Q24");
234                 resetType = resetToDefaultsType;
235             }
236 
237             if (resetType != "ResetAll")
238             {
239                 BMCWEB_LOG_DEBUG("Invalid property value for ResetType: {}",
240                                  *resetType);
241                 messages::actionParameterNotSupported(asyncResp->res,
242                                                       *resetType, "ResetType");
243                 return;
244             }
245 
246             crow::connections::systemBus->async_method_call(
247                 [asyncResp](const boost::system::error_code& ec) {
248                     if (ec)
249                     {
250                         BMCWEB_LOG_DEBUG("Failed to ResetToDefaults: {}", ec);
251                         messages::internalError(asyncResp->res);
252                         return;
253                     }
254                     // Factory Reset doesn't actually happen until a reboot
255                     // Can't erase what the BMC is running on
256                     doBMCGracefulRestart(asyncResp);
257                 },
258                 "xyz.openbmc_project.Software.BMC.Updater",
259                 "/xyz/openbmc_project/software",
260                 "xyz.openbmc_project.Common.FactoryReset", "Reset");
261         });
262 }
263 
264 /**
265  * ManagerResetActionInfo derived class for delivering Manager
266  * ResetType AllowableValues using ResetInfo schema.
267  */
268 inline void requestRoutesManagerResetActionInfo(App& app)
269 {
270     /**
271      * Functions triggers appropriate requests on DBus
272      */
273 
274     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/ResetActionInfo/")
275         .privileges(redfish::privileges::getActionInfo)
276         .methods(boost::beast::http::verb::get)(
277             [&app](const crow::Request& req,
278                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
279                    const std::string& managerId) {
280                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
281                 {
282                     return;
283                 }
284 
285                 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
286                 {
287                     messages::resourceNotFound(asyncResp->res, "Manager",
288                                                managerId);
289                     return;
290                 }
291 
292                 asyncResp->res.jsonValue["@odata.type"] =
293                     "#ActionInfo.v1_1_2.ActionInfo";
294                 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
295                     "/redfish/v1/Managers/{}/ResetActionInfo",
296                     BMCWEB_REDFISH_MANAGER_URI_NAME);
297                 asyncResp->res.jsonValue["Name"] = "Reset Action Info";
298                 asyncResp->res.jsonValue["Id"] = "ResetActionInfo";
299                 nlohmann::json::object_t parameter;
300                 parameter["Name"] = "ResetType";
301                 parameter["Required"] = true;
302                 parameter["DataType"] = action_info::ParameterTypes::String;
303 
304                 nlohmann::json::array_t allowableValues;
305                 allowableValues.emplace_back("GracefulRestart");
306                 allowableValues.emplace_back("ForceRestart");
307                 parameter["AllowableValues"] = std::move(allowableValues);
308 
309                 nlohmann::json::array_t parameters;
310                 parameters.emplace_back(std::move(parameter));
311 
312                 asyncResp->res.jsonValue["Parameters"] = std::move(parameters);
313             });
314 }
315 
316 static constexpr const char* objectManagerIface =
317     "org.freedesktop.DBus.ObjectManager";
318 static constexpr const char* pidConfigurationIface =
319     "xyz.openbmc_project.Configuration.Pid";
320 static constexpr const char* pidZoneConfigurationIface =
321     "xyz.openbmc_project.Configuration.Pid.Zone";
322 static constexpr const char* stepwiseConfigurationIface =
323     "xyz.openbmc_project.Configuration.Stepwise";
324 static constexpr const char* thermalModeIface =
325     "xyz.openbmc_project.Control.ThermalMode";
326 
327 inline void
328     asyncPopulatePid(const std::string& connection, const std::string& path,
329                      const std::string& currentProfile,
330                      const std::vector<std::string>& supportedProfiles,
331                      const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
332 {
333     sdbusplus::message::object_path objPath(path);
334     dbus::utility::getManagedObjects(
335         connection, objPath,
336         [asyncResp, currentProfile, supportedProfiles](
337             const boost::system::error_code& ec,
338             const dbus::utility::ManagedObjectType& managedObj) {
339             if (ec)
340             {
341                 BMCWEB_LOG_ERROR("{}", ec);
342                 messages::internalError(asyncResp->res);
343                 return;
344             }
345             nlohmann::json& configRoot =
346                 asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"];
347             nlohmann::json& fans = configRoot["FanControllers"];
348             fans["@odata.type"] =
349                 "#OpenBMCManager.v1_0_0.Manager.FanControllers";
350             fans["@odata.id"] = boost::urls::format(
351                 "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/FanControllers",
352                 BMCWEB_REDFISH_MANAGER_URI_NAME);
353 
354             nlohmann::json& pids = configRoot["PidControllers"];
355             pids["@odata.type"] =
356                 "#OpenBMCManager.v1_0_0.Manager.PidControllers";
357             pids["@odata.id"] = boost::urls::format(
358                 "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/PidControllers",
359                 BMCWEB_REDFISH_MANAGER_URI_NAME);
360 
361             nlohmann::json& stepwise = configRoot["StepwiseControllers"];
362             stepwise["@odata.type"] =
363                 "#OpenBMCManager.v1_0_0.Manager.StepwiseControllers";
364             stepwise["@odata.id"] = boost::urls::format(
365                 "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/StepwiseControllers",
366                 BMCWEB_REDFISH_MANAGER_URI_NAME);
367 
368             nlohmann::json& zones = configRoot["FanZones"];
369             zones["@odata.id"] = boost::urls::format(
370                 "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/FanZones",
371                 BMCWEB_REDFISH_MANAGER_URI_NAME);
372             zones["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager.FanZones";
373             configRoot["@odata.id"] =
374                 boost::urls::format("/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan",
375                                     BMCWEB_REDFISH_MANAGER_URI_NAME);
376             configRoot["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager.Fan";
377             configRoot["Profile@Redfish.AllowableValues"] = supportedProfiles;
378 
379             if (!currentProfile.empty())
380             {
381                 configRoot["Profile"] = currentProfile;
382             }
383             BMCWEB_LOG_DEBUG("profile = {} !", currentProfile);
384 
385             for (const auto& pathPair : managedObj)
386             {
387                 for (const auto& intfPair : pathPair.second)
388                 {
389                     if (intfPair.first != pidConfigurationIface &&
390                         intfPair.first != pidZoneConfigurationIface &&
391                         intfPair.first != stepwiseConfigurationIface)
392                     {
393                         continue;
394                     }
395 
396                     std::string name;
397 
398                     for (const std::pair<std::string,
399                                          dbus::utility::DbusVariantType>&
400                              propPair : intfPair.second)
401                     {
402                         if (propPair.first == "Name")
403                         {
404                             const std::string* namePtr =
405                                 std::get_if<std::string>(&propPair.second);
406                             if (namePtr == nullptr)
407                             {
408                                 BMCWEB_LOG_ERROR("Pid Name Field illegal");
409                                 messages::internalError(asyncResp->res);
410                                 return;
411                             }
412                             name = *namePtr;
413                             dbus::utility::escapePathForDbus(name);
414                         }
415                         else if (propPair.first == "Profiles")
416                         {
417                             const std::vector<std::string>* profiles =
418                                 std::get_if<std::vector<std::string>>(
419                                     &propPair.second);
420                             if (profiles == nullptr)
421                             {
422                                 BMCWEB_LOG_ERROR("Pid Profiles Field illegal");
423                                 messages::internalError(asyncResp->res);
424                                 return;
425                             }
426                             if (std::find(profiles->begin(), profiles->end(),
427                                           currentProfile) == profiles->end())
428                             {
429                                 BMCWEB_LOG_INFO(
430                                     "{} not supported in current profile",
431                                     name);
432                                 continue;
433                             }
434                         }
435                     }
436                     nlohmann::json* config = nullptr;
437                     const std::string* classPtr = nullptr;
438 
439                     for (const std::pair<std::string,
440                                          dbus::utility::DbusVariantType>&
441                              propPair : intfPair.second)
442                     {
443                         if (propPair.first == "Class")
444                         {
445                             classPtr =
446                                 std::get_if<std::string>(&propPair.second);
447                         }
448                     }
449 
450                     boost::urls::url url(
451                         boost::urls::format("/redfish/v1/Managers/{}",
452                                             BMCWEB_REDFISH_MANAGER_URI_NAME));
453                     if (intfPair.first == pidZoneConfigurationIface)
454                     {
455                         std::string chassis;
456                         if (!dbus::utility::getNthStringFromPath(
457                                 pathPair.first.str, 5, chassis))
458                         {
459                             chassis = "#IllegalValue";
460                         }
461                         nlohmann::json& zone = zones[name];
462                         zone["Chassis"]["@odata.id"] = boost::urls::format(
463                             "/redfish/v1/Chassis/{}", chassis);
464                         url.set_fragment(
465                             ("/Oem/OpenBmc/Fan/FanZones"_json_pointer / name)
466                                 .to_string());
467                         zone["@odata.id"] = std::move(url);
468                         zone["@odata.type"] =
469                             "#OpenBMCManager.v1_0_0.Manager.FanZone";
470                         config = &zone;
471                     }
472 
473                     else if (intfPair.first == stepwiseConfigurationIface)
474                     {
475                         if (classPtr == nullptr)
476                         {
477                             BMCWEB_LOG_ERROR("Pid Class Field illegal");
478                             messages::internalError(asyncResp->res);
479                             return;
480                         }
481 
482                         nlohmann::json& controller = stepwise[name];
483                         config = &controller;
484                         url.set_fragment(
485                             ("/Oem/OpenBmc/Fan/StepwiseControllers"_json_pointer /
486                              name)
487                                 .to_string());
488                         controller["@odata.id"] = std::move(url);
489                         controller["@odata.type"] =
490                             "#OpenBMCManager.v1_0_0.Manager.StepwiseController";
491 
492                         controller["Direction"] = *classPtr;
493                     }
494 
495                     // pid and fans are off the same configuration
496                     else if (intfPair.first == pidConfigurationIface)
497                     {
498                         if (classPtr == nullptr)
499                         {
500                             BMCWEB_LOG_ERROR("Pid Class Field illegal");
501                             messages::internalError(asyncResp->res);
502                             return;
503                         }
504                         bool isFan = *classPtr == "fan";
505                         nlohmann::json& element =
506                             isFan ? fans[name] : pids[name];
507                         config = &element;
508                         if (isFan)
509                         {
510                             url.set_fragment(
511                                 ("/Oem/OpenBmc/Fan/FanControllers"_json_pointer /
512                                  name)
513                                     .to_string());
514                             element["@odata.id"] = std::move(url);
515                             element["@odata.type"] =
516                                 "#OpenBMCManager.v1_0_0.Manager.FanController";
517                         }
518                         else
519                         {
520                             url.set_fragment(
521                                 ("/Oem/OpenBmc/Fan/PidControllers"_json_pointer /
522                                  name)
523                                     .to_string());
524                             element["@odata.id"] = std::move(url);
525                             element["@odata.type"] =
526                                 "#OpenBMCManager.v1_0_0.Manager.PidController";
527                         }
528                     }
529                     else
530                     {
531                         BMCWEB_LOG_ERROR("Unexpected configuration");
532                         messages::internalError(asyncResp->res);
533                         return;
534                     }
535 
536                     // used for making maps out of 2 vectors
537                     const std::vector<double>* keys = nullptr;
538                     const std::vector<double>* values = nullptr;
539 
540                     for (const auto& propertyPair : intfPair.second)
541                     {
542                         if (propertyPair.first == "Type" ||
543                             propertyPair.first == "Class" ||
544                             propertyPair.first == "Name")
545                         {
546                             continue;
547                         }
548 
549                         // zones
550                         if (intfPair.first == pidZoneConfigurationIface)
551                         {
552                             const double* ptr =
553                                 std::get_if<double>(&propertyPair.second);
554                             if (ptr == nullptr)
555                             {
556                                 BMCWEB_LOG_ERROR("Field Illegal {}",
557                                                  propertyPair.first);
558                                 messages::internalError(asyncResp->res);
559                                 return;
560                             }
561                             (*config)[propertyPair.first] = *ptr;
562                         }
563 
564                         if (intfPair.first == stepwiseConfigurationIface)
565                         {
566                             if (propertyPair.first == "Reading" ||
567                                 propertyPair.first == "Output")
568                             {
569                                 const std::vector<double>* ptr =
570                                     std::get_if<std::vector<double>>(
571                                         &propertyPair.second);
572 
573                                 if (ptr == nullptr)
574                                 {
575                                     BMCWEB_LOG_ERROR("Field Illegal {}",
576                                                      propertyPair.first);
577                                     messages::internalError(asyncResp->res);
578                                     return;
579                                 }
580 
581                                 if (propertyPair.first == "Reading")
582                                 {
583                                     keys = ptr;
584                                 }
585                                 else
586                                 {
587                                     values = ptr;
588                                 }
589                                 if (keys != nullptr && values != nullptr)
590                                 {
591                                     if (keys->size() != values->size())
592                                     {
593                                         BMCWEB_LOG_ERROR(
594                                             "Reading and Output size don't match ");
595                                         messages::internalError(asyncResp->res);
596                                         return;
597                                     }
598                                     nlohmann::json& steps = (*config)["Steps"];
599                                     steps = nlohmann::json::array();
600                                     for (size_t ii = 0; ii < keys->size(); ii++)
601                                     {
602                                         nlohmann::json::object_t step;
603                                         step["Target"] = (*keys)[ii];
604                                         step["Output"] = (*values)[ii];
605                                         steps.emplace_back(std::move(step));
606                                     }
607                                 }
608                             }
609                             if (propertyPair.first == "NegativeHysteresis" ||
610                                 propertyPair.first == "PositiveHysteresis")
611                             {
612                                 const double* ptr =
613                                     std::get_if<double>(&propertyPair.second);
614                                 if (ptr == nullptr)
615                                 {
616                                     BMCWEB_LOG_ERROR("Field Illegal {}",
617                                                      propertyPair.first);
618                                     messages::internalError(asyncResp->res);
619                                     return;
620                                 }
621                                 (*config)[propertyPair.first] = *ptr;
622                             }
623                         }
624 
625                         // pid and fans are off the same configuration
626                         if (intfPair.first == pidConfigurationIface ||
627                             intfPair.first == stepwiseConfigurationIface)
628                         {
629                             if (propertyPair.first == "Zones")
630                             {
631                                 const std::vector<std::string>* inputs =
632                                     std::get_if<std::vector<std::string>>(
633                                         &propertyPair.second);
634 
635                                 if (inputs == nullptr)
636                                 {
637                                     BMCWEB_LOG_ERROR("Zones Pid Field Illegal");
638                                     messages::internalError(asyncResp->res);
639                                     return;
640                                 }
641                                 auto& data = (*config)[propertyPair.first];
642                                 data = nlohmann::json::array();
643                                 for (std::string itemCopy : *inputs)
644                                 {
645                                     dbus::utility::escapePathForDbus(itemCopy);
646                                     nlohmann::json::object_t input;
647                                     boost::urls::url managerUrl =
648                                         boost::urls::format(
649                                             "/redfish/v1/Managers/{}#{}",
650                                             BMCWEB_REDFISH_MANAGER_URI_NAME,
651                                             ("/Oem/OpenBmc/Fan/FanZones"_json_pointer /
652                                              itemCopy)
653                                                 .to_string());
654                                     input["@odata.id"] = std::move(managerUrl);
655                                     data.emplace_back(std::move(input));
656                                 }
657                             }
658                             // todo(james): may never happen, but this
659                             // assumes configuration data referenced in the
660                             // PID config is provided by the same daemon, we
661                             // could add another loop to cover all cases,
662                             // but I'm okay kicking this can down the road a
663                             // bit
664 
665                             else if (propertyPair.first == "Inputs" ||
666                                      propertyPair.first == "Outputs")
667                             {
668                                 auto& data = (*config)[propertyPair.first];
669                                 const std::vector<std::string>* inputs =
670                                     std::get_if<std::vector<std::string>>(
671                                         &propertyPair.second);
672 
673                                 if (inputs == nullptr)
674                                 {
675                                     BMCWEB_LOG_ERROR("Field Illegal {}",
676                                                      propertyPair.first);
677                                     messages::internalError(asyncResp->res);
678                                     return;
679                                 }
680                                 data = *inputs;
681                             }
682                             else if (propertyPair.first == "SetPointOffset")
683                             {
684                                 const std::string* ptr =
685                                     std::get_if<std::string>(
686                                         &propertyPair.second);
687 
688                                 if (ptr == nullptr)
689                                 {
690                                     BMCWEB_LOG_ERROR("Field Illegal {}",
691                                                      propertyPair.first);
692                                     messages::internalError(asyncResp->res);
693                                     return;
694                                 }
695                                 // translate from dbus to redfish
696                                 if (*ptr == "WarningHigh")
697                                 {
698                                     (*config)["SetPointOffset"] =
699                                         "UpperThresholdNonCritical";
700                                 }
701                                 else if (*ptr == "WarningLow")
702                                 {
703                                     (*config)["SetPointOffset"] =
704                                         "LowerThresholdNonCritical";
705                                 }
706                                 else if (*ptr == "CriticalHigh")
707                                 {
708                                     (*config)["SetPointOffset"] =
709                                         "UpperThresholdCritical";
710                                 }
711                                 else if (*ptr == "CriticalLow")
712                                 {
713                                     (*config)["SetPointOffset"] =
714                                         "LowerThresholdCritical";
715                                 }
716                                 else
717                                 {
718                                     BMCWEB_LOG_ERROR("Value Illegal {}", *ptr);
719                                     messages::internalError(asyncResp->res);
720                                     return;
721                                 }
722                             }
723                             // doubles
724                             else if (propertyPair.first ==
725                                          "FFGainCoefficient" ||
726                                      propertyPair.first == "FFOffCoefficient" ||
727                                      propertyPair.first == "ICoefficient" ||
728                                      propertyPair.first == "ILimitMax" ||
729                                      propertyPair.first == "ILimitMin" ||
730                                      propertyPair.first ==
731                                          "PositiveHysteresis" ||
732                                      propertyPair.first ==
733                                          "NegativeHysteresis" ||
734                                      propertyPair.first == "OutLimitMax" ||
735                                      propertyPair.first == "OutLimitMin" ||
736                                      propertyPair.first == "PCoefficient" ||
737                                      propertyPair.first == "SetPoint" ||
738                                      propertyPair.first == "SlewNeg" ||
739                                      propertyPair.first == "SlewPos")
740                             {
741                                 const double* ptr =
742                                     std::get_if<double>(&propertyPair.second);
743                                 if (ptr == nullptr)
744                                 {
745                                     BMCWEB_LOG_ERROR("Field Illegal {}",
746                                                      propertyPair.first);
747                                     messages::internalError(asyncResp->res);
748                                     return;
749                                 }
750                                 (*config)[propertyPair.first] = *ptr;
751                             }
752                         }
753                     }
754                 }
755             }
756         });
757 }
758 
759 enum class CreatePIDRet
760 {
761     fail,
762     del,
763     patch
764 };
765 
766 inline bool
767     getZonesFromJsonReq(const std::shared_ptr<bmcweb::AsyncResp>& response,
768                         std::vector<nlohmann::json::object_t>& config,
769                         std::vector<std::string>& zones)
770 {
771     if (config.empty())
772     {
773         BMCWEB_LOG_ERROR("Empty Zones");
774         messages::propertyValueFormatError(response->res, config, "Zones");
775         return false;
776     }
777     for (auto& odata : config)
778     {
779         std::string path;
780         if (!redfish::json_util::readJsonObject(odata, response->res,
781                                                 "@odata.id", path))
782         {
783             return false;
784         }
785         std::string input;
786 
787         // 8 below comes from
788         // /redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones/Left
789         //     0    1     2      3    4    5      6     7      8
790         if (!dbus::utility::getNthStringFromPath(path, 8, input))
791         {
792             BMCWEB_LOG_ERROR("Got invalid path {}", path);
793             BMCWEB_LOG_ERROR("Illegal Type Zones");
794             messages::propertyValueFormatError(response->res, odata, "Zones");
795             return false;
796         }
797         std::replace(input.begin(), input.end(), '_', ' ');
798         zones.emplace_back(std::move(input));
799     }
800     return true;
801 }
802 
803 inline const dbus::utility::ManagedObjectType::value_type*
804     findChassis(const dbus::utility::ManagedObjectType& managedObj,
805                 std::string_view value, std::string& chassis)
806 {
807     BMCWEB_LOG_DEBUG("Find Chassis: {}", value);
808 
809     std::string escaped(value);
810     std::replace(escaped.begin(), escaped.end(), ' ', '_');
811     escaped = "/" + escaped;
812     auto it = std::ranges::find_if(managedObj, [&escaped](const auto& obj) {
813         if (obj.first.str.ends_with(escaped))
814         {
815             BMCWEB_LOG_DEBUG("Matched {}", obj.first.str);
816             return true;
817         }
818         return false;
819     });
820 
821     if (it == managedObj.end())
822     {
823         return nullptr;
824     }
825     // 5 comes from <chassis-name> being the 5th element
826     // /xyz/openbmc_project/inventory/system/chassis/<chassis-name>
827     if (dbus::utility::getNthStringFromPath(it->first.str, 5, chassis))
828     {
829         return &(*it);
830     }
831 
832     return nullptr;
833 }
834 
835 inline CreatePIDRet createPidInterface(
836     const std::shared_ptr<bmcweb::AsyncResp>& response, const std::string& type,
837     std::string_view name, nlohmann::json& jsonValue, const std::string& path,
838     const dbus::utility::ManagedObjectType& managedObj, bool createNewObject,
839     dbus::utility::DBusPropertiesMap& output, std::string& chassis,
840     const std::string& profile)
841 {
842     // common deleter
843     if (jsonValue == nullptr)
844     {
845         std::string iface;
846         if (type == "PidControllers" || type == "FanControllers")
847         {
848             iface = pidConfigurationIface;
849         }
850         else if (type == "FanZones")
851         {
852             iface = pidZoneConfigurationIface;
853         }
854         else if (type == "StepwiseControllers")
855         {
856             iface = stepwiseConfigurationIface;
857         }
858         else
859         {
860             BMCWEB_LOG_ERROR("Illegal Type {}", type);
861             messages::propertyUnknown(response->res, type);
862             return CreatePIDRet::fail;
863         }
864 
865         BMCWEB_LOG_DEBUG("del {} {}", path, iface);
866         // delete interface
867         crow::connections::systemBus->async_method_call(
868             [response, path](const boost::system::error_code& ec) {
869                 if (ec)
870                 {
871                     BMCWEB_LOG_ERROR("Error patching {}: {}", path, ec);
872                     messages::internalError(response->res);
873                     return;
874                 }
875                 messages::success(response->res);
876             },
877             "xyz.openbmc_project.EntityManager", path, iface, "Delete");
878         return CreatePIDRet::del;
879     }
880 
881     const dbus::utility::ManagedObjectType::value_type* managedItem = nullptr;
882     if (!createNewObject)
883     {
884         // if we aren't creating a new object, we should be able to find it on
885         // d-bus
886         managedItem = findChassis(managedObj, name, chassis);
887         if (managedItem == nullptr)
888         {
889             BMCWEB_LOG_ERROR("Failed to get chassis from config patch");
890             messages::invalidObject(
891                 response->res,
892                 boost::urls::format("/redfish/v1/Chassis/{}", chassis));
893             return CreatePIDRet::fail;
894         }
895     }
896 
897     if (!profile.empty() &&
898         (type == "PidControllers" || type == "FanControllers" ||
899          type == "StepwiseControllers"))
900     {
901         if (managedItem == nullptr)
902         {
903             output.emplace_back("Profiles", std::vector<std::string>{profile});
904         }
905         else
906         {
907             std::string interface;
908             if (type == "StepwiseControllers")
909             {
910                 interface = stepwiseConfigurationIface;
911             }
912             else
913             {
914                 interface = pidConfigurationIface;
915             }
916             bool ifaceFound = false;
917             for (const auto& iface : managedItem->second)
918             {
919                 if (iface.first == interface)
920                 {
921                     ifaceFound = true;
922                     for (const auto& prop : iface.second)
923                     {
924                         if (prop.first == "Profiles")
925                         {
926                             const std::vector<std::string>* curProfiles =
927                                 std::get_if<std::vector<std::string>>(
928                                     &(prop.second));
929                             if (curProfiles == nullptr)
930                             {
931                                 BMCWEB_LOG_ERROR(
932                                     "Illegal profiles in managed object");
933                                 messages::internalError(response->res);
934                                 return CreatePIDRet::fail;
935                             }
936                             if (std::find(curProfiles->begin(),
937                                           curProfiles->end(), profile) ==
938                                 curProfiles->end())
939                             {
940                                 std::vector<std::string> newProfiles =
941                                     *curProfiles;
942                                 newProfiles.push_back(profile);
943                                 output.emplace_back("Profiles", newProfiles);
944                             }
945                         }
946                     }
947                 }
948             }
949 
950             if (!ifaceFound)
951             {
952                 BMCWEB_LOG_ERROR("Failed to find interface in managed object");
953                 messages::internalError(response->res);
954                 return CreatePIDRet::fail;
955             }
956         }
957     }
958 
959     if (type == "PidControllers" || type == "FanControllers")
960     {
961         if (createNewObject)
962         {
963             output.emplace_back("Class",
964                                 type == "PidControllers" ? "temp" : "fan");
965             output.emplace_back("Type", "Pid");
966         }
967 
968         std::optional<std::vector<nlohmann::json::object_t>> zones;
969         std::optional<std::vector<std::string>> inputs;
970         std::optional<std::vector<std::string>> outputs;
971         std::map<std::string, std::optional<double>> doubles;
972         std::optional<std::string> setpointOffset;
973         if (!redfish::json_util::readJson( //
974                 jsonValue, response->res, //
975                 "FFGainCoefficient", doubles["FFGainCoefficient"], //
976                 "FFOffCoefficient", doubles["FFOffCoefficient"], //
977                 "ICoefficient", doubles["ICoefficient"], //
978                 "ILimitMax", doubles["ILimitMax"], //
979                 "ILimitMin", doubles["ILimitMin"], //
980                 "Inputs", inputs, //
981                 "NegativeHysteresis", doubles["NegativeHysteresis"], //
982                 "OutLimitMax", doubles["OutLimitMax"], //
983                 "OutLimitMin", doubles["OutLimitMin"], //
984                 "Outputs", outputs, //
985                 "PCoefficient", doubles["PCoefficient"], //
986                 "PositiveHysteresis", doubles["PositiveHysteresis"], //
987                 "SetPoint", doubles["SetPoint"], //
988                 "SetPointOffset", setpointOffset, //
989                 "SlewNeg", doubles["SlewNeg"], //
990                 "SlewPos", doubles["SlewPos"], //
991                 "Zones", zones //
992                 ))
993         {
994             return CreatePIDRet::fail;
995         }
996 
997         if (zones)
998         {
999             std::vector<std::string> zonesStr;
1000             if (!getZonesFromJsonReq(response, *zones, zonesStr))
1001             {
1002                 BMCWEB_LOG_ERROR("Illegal Zones");
1003                 return CreatePIDRet::fail;
1004             }
1005             if (chassis.empty() &&
1006                 findChassis(managedObj, zonesStr[0], chassis) == nullptr)
1007             {
1008                 BMCWEB_LOG_ERROR("Failed to get chassis from config patch");
1009                 messages::invalidObject(
1010                     response->res,
1011                     boost::urls::format("/redfish/v1/Chassis/{}", chassis));
1012                 return CreatePIDRet::fail;
1013             }
1014             output.emplace_back("Zones", std::move(zonesStr));
1015         }
1016 
1017         if (inputs)
1018         {
1019             for (std::string& value : *inputs)
1020             {
1021                 std::replace(value.begin(), value.end(), '_', ' ');
1022             }
1023             output.emplace_back("Inputs", *inputs);
1024         }
1025 
1026         if (outputs)
1027         {
1028             for (std::string& value : *outputs)
1029             {
1030                 std::replace(value.begin(), value.end(), '_', ' ');
1031             }
1032             output.emplace_back("Outputs", *outputs);
1033         }
1034 
1035         if (setpointOffset)
1036         {
1037             // translate between redfish and dbus names
1038             if (*setpointOffset == "UpperThresholdNonCritical")
1039             {
1040                 output.emplace_back("SetPointOffset", "WarningLow");
1041             }
1042             else if (*setpointOffset == "LowerThresholdNonCritical")
1043             {
1044                 output.emplace_back("SetPointOffset", "WarningHigh");
1045             }
1046             else if (*setpointOffset == "LowerThresholdCritical")
1047             {
1048                 output.emplace_back("SetPointOffset", "CriticalLow");
1049             }
1050             else if (*setpointOffset == "UpperThresholdCritical")
1051             {
1052                 output.emplace_back("SetPointOffset", "CriticalHigh");
1053             }
1054             else
1055             {
1056                 BMCWEB_LOG_ERROR("Invalid setpointoffset {}", *setpointOffset);
1057                 messages::propertyValueNotInList(response->res, name,
1058                                                  "SetPointOffset");
1059                 return CreatePIDRet::fail;
1060             }
1061         }
1062 
1063         // doubles
1064         for (const auto& pairs : doubles)
1065         {
1066             if (!pairs.second)
1067             {
1068                 continue;
1069             }
1070             BMCWEB_LOG_DEBUG("{} = {}", pairs.first, *pairs.second);
1071             output.emplace_back(pairs.first, *pairs.second);
1072         }
1073     }
1074 
1075     else if (type == "FanZones")
1076     {
1077         output.emplace_back("Type", "Pid.Zone");
1078 
1079         std::optional<std::string> chassisId;
1080         std::optional<double> failSafePercent;
1081         std::optional<double> minThermalOutput;
1082         if (!redfish::json_util::readJson( //
1083                 jsonValue, response->res, //
1084                 "Chassis/@odata.id", chassisId, //
1085                 "FailSafePercent", failSafePercent, //
1086                 "MinThermalOutput", minThermalOutput))
1087         {
1088             return CreatePIDRet::fail;
1089         }
1090 
1091         if (chassisId)
1092         {
1093             // /redfish/v1/chassis/chassis_name/
1094             if (!dbus::utility::getNthStringFromPath(*chassisId, 3, chassis))
1095             {
1096                 BMCWEB_LOG_ERROR("Got invalid path {}", *chassisId);
1097                 messages::invalidObject(
1098                     response->res,
1099                     boost::urls::format("/redfish/v1/Chassis/{}", *chassisId));
1100                 return CreatePIDRet::fail;
1101             }
1102         }
1103         if (minThermalOutput)
1104         {
1105             output.emplace_back("MinThermalOutput", *minThermalOutput);
1106         }
1107         if (failSafePercent)
1108         {
1109             output.emplace_back("FailSafePercent", *failSafePercent);
1110         }
1111     }
1112     else if (type == "StepwiseControllers")
1113     {
1114         output.emplace_back("Type", "Stepwise");
1115 
1116         std::optional<std::vector<nlohmann::json::object_t>> zones;
1117         std::optional<std::vector<nlohmann::json::object_t>> steps;
1118         std::optional<std::vector<std::string>> inputs;
1119         std::optional<double> positiveHysteresis;
1120         std::optional<double> negativeHysteresis;
1121         std::optional<std::string> direction; // upper clipping curve vs lower
1122         if (!redfish::json_util::readJson( //
1123                 jsonValue, response->res, //
1124                 "Direction", direction, //
1125                 "Inputs", inputs, //
1126                 "NegativeHysteresis", negativeHysteresis, //
1127                 "PositiveHysteresis", positiveHysteresis, //
1128                 "Steps", steps, //
1129                 "Zones", zones //
1130                 ))
1131         {
1132             return CreatePIDRet::fail;
1133         }
1134 
1135         if (zones)
1136         {
1137             std::vector<std::string> zonesStrs;
1138             if (!getZonesFromJsonReq(response, *zones, zonesStrs))
1139             {
1140                 BMCWEB_LOG_ERROR("Illegal Zones");
1141                 return CreatePIDRet::fail;
1142             }
1143             if (chassis.empty() &&
1144                 findChassis(managedObj, zonesStrs[0], chassis) == nullptr)
1145             {
1146                 BMCWEB_LOG_ERROR("Failed to get chassis from config patch");
1147                 messages::invalidObject(
1148                     response->res,
1149                     boost::urls::format("/redfish/v1/Chassis/{}", chassis));
1150                 return CreatePIDRet::fail;
1151             }
1152             output.emplace_back("Zones", std::move(zonesStrs));
1153         }
1154         if (steps)
1155         {
1156             std::vector<double> readings;
1157             std::vector<double> outputs;
1158             for (auto& step : *steps)
1159             {
1160                 double target = 0.0;
1161                 double out = 0.0;
1162 
1163                 if (!redfish::json_util::readJsonObject( //
1164                         step, response->res, //
1165                         "Output", out, //
1166                         "Target", target //
1167                         ))
1168                 {
1169                     return CreatePIDRet::fail;
1170                 }
1171                 readings.emplace_back(target);
1172                 outputs.emplace_back(out);
1173             }
1174             output.emplace_back("Reading", std::move(readings));
1175             output.emplace_back("Output", std::move(outputs));
1176         }
1177         if (inputs)
1178         {
1179             for (std::string& value : *inputs)
1180             {
1181                 std::replace(value.begin(), value.end(), '_', ' ');
1182             }
1183             output.emplace_back("Inputs", std::move(*inputs));
1184         }
1185         if (negativeHysteresis)
1186         {
1187             output.emplace_back("NegativeHysteresis", *negativeHysteresis);
1188         }
1189         if (positiveHysteresis)
1190         {
1191             output.emplace_back("PositiveHysteresis", *positiveHysteresis);
1192         }
1193         if (direction)
1194         {
1195             constexpr const std::array<const char*, 2> allowedDirections = {
1196                 "Ceiling", "Floor"};
1197             if (std::ranges::find(allowedDirections, *direction) ==
1198                 allowedDirections.end())
1199             {
1200                 messages::propertyValueTypeError(response->res, "Direction",
1201                                                  *direction);
1202                 return CreatePIDRet::fail;
1203             }
1204             output.emplace_back("Class", *direction);
1205         }
1206     }
1207     else
1208     {
1209         BMCWEB_LOG_ERROR("Illegal Type {}", type);
1210         messages::propertyUnknown(response->res, type);
1211         return CreatePIDRet::fail;
1212     }
1213     return CreatePIDRet::patch;
1214 }
1215 struct GetPIDValues : std::enable_shared_from_this<GetPIDValues>
1216 {
1217     struct CompletionValues
1218     {
1219         std::vector<std::string> supportedProfiles;
1220         std::string currentProfile;
1221         dbus::utility::MapperGetSubTreeResponse subtree;
1222     };
1223 
1224     explicit GetPIDValues(
1225         const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn) :
1226         asyncResp(asyncRespIn)
1227 
1228     {}
1229 
1230     void run()
1231     {
1232         std::shared_ptr<GetPIDValues> self = shared_from_this();
1233 
1234         // get all configurations
1235         constexpr std::array<std::string_view, 4> interfaces = {
1236             pidConfigurationIface, pidZoneConfigurationIface,
1237             objectManagerIface, stepwiseConfigurationIface};
1238         dbus::utility::getSubTree(
1239             "/", 0, interfaces,
1240             [self](
1241                 const boost::system::error_code& ec,
1242                 const dbus::utility::MapperGetSubTreeResponse& subtreeLocal) {
1243                 if (ec)
1244                 {
1245                     BMCWEB_LOG_ERROR("{}", ec);
1246                     messages::internalError(self->asyncResp->res);
1247                     return;
1248                 }
1249                 self->complete.subtree = subtreeLocal;
1250             });
1251 
1252         // at the same time get the selected profile
1253         constexpr std::array<std::string_view, 1> thermalModeIfaces = {
1254             thermalModeIface};
1255         dbus::utility::getSubTree(
1256             "/", 0, thermalModeIfaces,
1257             [self](
1258                 const boost::system::error_code& ec,
1259                 const dbus::utility::MapperGetSubTreeResponse& subtreeLocal) {
1260                 if (ec || subtreeLocal.empty())
1261                 {
1262                     return;
1263                 }
1264                 if (subtreeLocal[0].second.size() != 1)
1265                 {
1266                     // invalid mapper response, should never happen
1267                     BMCWEB_LOG_ERROR("GetPIDValues: Mapper Error");
1268                     messages::internalError(self->asyncResp->res);
1269                     return;
1270                 }
1271 
1272                 const std::string& path = subtreeLocal[0].first;
1273                 const std::string& owner = subtreeLocal[0].second[0].first;
1274 
1275                 sdbusplus::asio::getAllProperties(
1276                     *crow::connections::systemBus, owner, path,
1277                     thermalModeIface,
1278                     [path, owner,
1279                      self](const boost::system::error_code& ec2,
1280                            const dbus::utility::DBusPropertiesMap& resp) {
1281                         if (ec2)
1282                         {
1283                             BMCWEB_LOG_ERROR(
1284                                 "GetPIDValues: Can't get thermalModeIface {}",
1285                                 path);
1286                             messages::internalError(self->asyncResp->res);
1287                             return;
1288                         }
1289 
1290                         const std::string* current = nullptr;
1291                         const std::vector<std::string>* supported = nullptr;
1292 
1293                         const bool success = sdbusplus::unpackPropertiesNoThrow(
1294                             dbus_utils::UnpackErrorPrinter(), resp, "Current",
1295                             current, "Supported", supported);
1296 
1297                         if (!success)
1298                         {
1299                             messages::internalError(self->asyncResp->res);
1300                             return;
1301                         }
1302 
1303                         if (current == nullptr || supported == nullptr)
1304                         {
1305                             BMCWEB_LOG_ERROR(
1306                                 "GetPIDValues: thermal mode iface invalid {}",
1307                                 path);
1308                             messages::internalError(self->asyncResp->res);
1309                             return;
1310                         }
1311                         self->complete.currentProfile = *current;
1312                         self->complete.supportedProfiles = *supported;
1313                     });
1314             });
1315     }
1316 
1317     static void
1318         processingComplete(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1319                            const CompletionValues& completion)
1320     {
1321         if (asyncResp->res.result() != boost::beast::http::status::ok)
1322         {
1323             return;
1324         }
1325         // create map of <connection, path to objMgr>>
1326         boost::container::flat_map<
1327             std::string, std::string, std::less<>,
1328             std::vector<std::pair<std::string, std::string>>>
1329             objectMgrPaths;
1330         boost::container::flat_set<std::string, std::less<>,
1331                                    std::vector<std::string>>
1332             calledConnections;
1333         for (const auto& pathGroup : completion.subtree)
1334         {
1335             for (const auto& connectionGroup : pathGroup.second)
1336             {
1337                 auto findConnection =
1338                     calledConnections.find(connectionGroup.first);
1339                 if (findConnection != calledConnections.end())
1340                 {
1341                     break;
1342                 }
1343                 for (const std::string& interface : connectionGroup.second)
1344                 {
1345                     if (interface == objectManagerIface)
1346                     {
1347                         objectMgrPaths[connectionGroup.first] = pathGroup.first;
1348                     }
1349                     // this list is alphabetical, so we
1350                     // should have found the objMgr by now
1351                     if (interface == pidConfigurationIface ||
1352                         interface == pidZoneConfigurationIface ||
1353                         interface == stepwiseConfigurationIface)
1354                     {
1355                         auto findObjMgr =
1356                             objectMgrPaths.find(connectionGroup.first);
1357                         if (findObjMgr == objectMgrPaths.end())
1358                         {
1359                             BMCWEB_LOG_DEBUG("{}Has no Object Manager",
1360                                              connectionGroup.first);
1361                             continue;
1362                         }
1363 
1364                         calledConnections.insert(connectionGroup.first);
1365 
1366                         asyncPopulatePid(findObjMgr->first, findObjMgr->second,
1367                                          completion.currentProfile,
1368                                          completion.supportedProfiles,
1369                                          asyncResp);
1370                         break;
1371                     }
1372                 }
1373             }
1374         }
1375     }
1376 
1377     ~GetPIDValues()
1378     {
1379         boost::asio::post(crow::connections::systemBus->get_io_context(),
1380                           std::bind_front(&processingComplete, asyncResp,
1381                                           std::move(complete)));
1382     }
1383 
1384     GetPIDValues(const GetPIDValues&) = delete;
1385     GetPIDValues(GetPIDValues&&) = delete;
1386     GetPIDValues& operator=(const GetPIDValues&) = delete;
1387     GetPIDValues& operator=(GetPIDValues&&) = delete;
1388 
1389     std::shared_ptr<bmcweb::AsyncResp> asyncResp;
1390     CompletionValues complete;
1391 };
1392 
1393 struct SetPIDValues : std::enable_shared_from_this<SetPIDValues>
1394 {
1395     SetPIDValues(
1396         const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn,
1397         std::vector<
1398             std::pair<std::string, std::optional<nlohmann::json::object_t>>>&&
1399             configurationsIn,
1400         std::optional<std::string>& profileIn) :
1401         asyncResp(asyncRespIn), configuration(std::move(configurationsIn)),
1402         profile(std::move(profileIn))
1403     {}
1404 
1405     SetPIDValues(const SetPIDValues&) = delete;
1406     SetPIDValues(SetPIDValues&&) = delete;
1407     SetPIDValues& operator=(const SetPIDValues&) = delete;
1408     SetPIDValues& operator=(SetPIDValues&&) = delete;
1409 
1410     void run()
1411     {
1412         if (asyncResp->res.result() != boost::beast::http::status::ok)
1413         {
1414             return;
1415         }
1416 
1417         std::shared_ptr<SetPIDValues> self = shared_from_this();
1418 
1419         // todo(james): might make sense to do a mapper call here if this
1420         // interface gets more traction
1421         sdbusplus::message::object_path objPath(
1422             "/xyz/openbmc_project/inventory");
1423         dbus::utility::getManagedObjects(
1424             "xyz.openbmc_project.EntityManager", objPath,
1425             [self](const boost::system::error_code& ec,
1426                    const dbus::utility::ManagedObjectType& mObj) {
1427                 if (ec)
1428                 {
1429                     BMCWEB_LOG_ERROR("Error communicating to Entity Manager");
1430                     messages::internalError(self->asyncResp->res);
1431                     return;
1432                 }
1433                 const std::array<const char*, 3> configurations = {
1434                     pidConfigurationIface, pidZoneConfigurationIface,
1435                     stepwiseConfigurationIface};
1436 
1437                 for (const auto& [path, object] : mObj)
1438                 {
1439                     for (const auto& [interface, _] : object)
1440                     {
1441                         if (std::ranges::find(configurations, interface) !=
1442                             configurations.end())
1443                         {
1444                             self->objectCount++;
1445                             break;
1446                         }
1447                     }
1448                 }
1449                 self->managedObj = mObj;
1450             });
1451 
1452         // at the same time get the profile information
1453         constexpr std::array<std::string_view, 1> thermalModeIfaces = {
1454             thermalModeIface};
1455         dbus::utility::getSubTree(
1456             "/", 0, thermalModeIfaces,
1457             [self](const boost::system::error_code& ec,
1458                    const dbus::utility::MapperGetSubTreeResponse& subtree) {
1459                 if (ec || subtree.empty())
1460                 {
1461                     return;
1462                 }
1463                 if (subtree[0].second.empty())
1464                 {
1465                     // invalid mapper response, should never happen
1466                     BMCWEB_LOG_ERROR("SetPIDValues: Mapper Error");
1467                     messages::internalError(self->asyncResp->res);
1468                     return;
1469                 }
1470 
1471                 const std::string& path = subtree[0].first;
1472                 const std::string& owner = subtree[0].second[0].first;
1473                 sdbusplus::asio::getAllProperties(
1474                     *crow::connections::systemBus, owner, path,
1475                     thermalModeIface,
1476                     [self, path,
1477                      owner](const boost::system::error_code& ec2,
1478                             const dbus::utility::DBusPropertiesMap& r) {
1479                         if (ec2)
1480                         {
1481                             BMCWEB_LOG_ERROR(
1482                                 "SetPIDValues: Can't get thermalModeIface {}",
1483                                 path);
1484                             messages::internalError(self->asyncResp->res);
1485                             return;
1486                         }
1487                         const std::string* current = nullptr;
1488                         const std::vector<std::string>* supported = nullptr;
1489 
1490                         const bool success = sdbusplus::unpackPropertiesNoThrow(
1491                             dbus_utils::UnpackErrorPrinter(), r, "Current",
1492                             current, "Supported", supported);
1493 
1494                         if (!success)
1495                         {
1496                             messages::internalError(self->asyncResp->res);
1497                             return;
1498                         }
1499 
1500                         if (current == nullptr || supported == nullptr)
1501                         {
1502                             BMCWEB_LOG_ERROR(
1503                                 "SetPIDValues: thermal mode iface invalid {}",
1504                                 path);
1505                             messages::internalError(self->asyncResp->res);
1506                             return;
1507                         }
1508                         self->currentProfile = *current;
1509                         self->supportedProfiles = *supported;
1510                         self->profileConnection = owner;
1511                         self->profilePath = path;
1512                     });
1513             });
1514     }
1515     void pidSetDone()
1516     {
1517         if (asyncResp->res.result() != boost::beast::http::status::ok)
1518         {
1519             return;
1520         }
1521         std::shared_ptr<bmcweb::AsyncResp> response = asyncResp;
1522         if (profile)
1523         {
1524             if (std::ranges::find(supportedProfiles, *profile) ==
1525                 supportedProfiles.end())
1526             {
1527                 messages::actionParameterUnknown(response->res, "Profile",
1528                                                  *profile);
1529                 return;
1530             }
1531             currentProfile = *profile;
1532             sdbusplus::asio::setProperty(
1533                 *crow::connections::systemBus, profileConnection, profilePath,
1534                 thermalModeIface, "Current", *profile,
1535                 [response](const boost::system::error_code& ec) {
1536                     if (ec)
1537                     {
1538                         BMCWEB_LOG_ERROR("Error patching profile{}", ec);
1539                         messages::internalError(response->res);
1540                     }
1541                 });
1542         }
1543 
1544         for (auto& containerPair : configuration)
1545         {
1546             auto& container = containerPair.second;
1547             if (!container)
1548             {
1549                 continue;
1550             }
1551 
1552             const std::string& type = containerPair.first;
1553 
1554             for (auto& [name, value] : *container)
1555             {
1556                 std::string dbusObjName = name;
1557                 std::replace(dbusObjName.begin(), dbusObjName.end(), ' ', '_');
1558                 BMCWEB_LOG_DEBUG("looking for {}", name);
1559 
1560                 auto pathItr = std::ranges::find_if(
1561                     managedObj, [&dbusObjName](const auto& obj) {
1562                         return obj.first.filename() == dbusObjName;
1563                     });
1564                 dbus::utility::DBusPropertiesMap output;
1565 
1566                 output.reserve(16); // The pid interface length
1567 
1568                 // determines if we're patching entity-manager or
1569                 // creating a new object
1570                 bool createNewObject = (pathItr == managedObj.end());
1571                 BMCWEB_LOG_DEBUG("Found = {}", !createNewObject);
1572 
1573                 std::string iface;
1574                 if (!createNewObject)
1575                 {
1576                     bool findInterface = false;
1577                     for (const auto& interface : pathItr->second)
1578                     {
1579                         if (interface.first == pidConfigurationIface)
1580                         {
1581                             if (type == "PidControllers" ||
1582                                 type == "FanControllers")
1583                             {
1584                                 iface = pidConfigurationIface;
1585                                 findInterface = true;
1586                                 break;
1587                             }
1588                         }
1589                         else if (interface.first == pidZoneConfigurationIface)
1590                         {
1591                             if (type == "FanZones")
1592                             {
1593                                 iface = pidZoneConfigurationIface;
1594                                 findInterface = true;
1595                                 break;
1596                             }
1597                         }
1598                         else if (interface.first == stepwiseConfigurationIface)
1599                         {
1600                             if (type == "StepwiseControllers")
1601                             {
1602                                 iface = stepwiseConfigurationIface;
1603                                 findInterface = true;
1604                                 break;
1605                             }
1606                         }
1607                     }
1608 
1609                     // create new object if interface not found
1610                     if (!findInterface)
1611                     {
1612                         createNewObject = true;
1613                     }
1614                 }
1615 
1616                 if (createNewObject && value == nullptr)
1617                 {
1618                     // can't delete a non-existent object
1619                     messages::propertyValueNotInList(response->res, value,
1620                                                      name);
1621                     continue;
1622                 }
1623 
1624                 std::string path;
1625                 if (pathItr != managedObj.end())
1626                 {
1627                     path = pathItr->first.str;
1628                 }
1629 
1630                 BMCWEB_LOG_DEBUG("Create new = {}", createNewObject);
1631 
1632                 // arbitrary limit to avoid attacks
1633                 constexpr const size_t controllerLimit = 500;
1634                 if (createNewObject && objectCount >= controllerLimit)
1635                 {
1636                     messages::resourceExhaustion(response->res, type);
1637                     continue;
1638                 }
1639                 std::string escaped = name;
1640                 std::replace(escaped.begin(), escaped.end(), '_', ' ');
1641                 output.emplace_back("Name", escaped);
1642 
1643                 std::string chassis;
1644                 CreatePIDRet ret = createPidInterface(
1645                     response, type, name, value, path, managedObj,
1646                     createNewObject, output, chassis, currentProfile);
1647                 if (ret == CreatePIDRet::fail)
1648                 {
1649                     return;
1650                 }
1651                 if (ret == CreatePIDRet::del)
1652                 {
1653                     continue;
1654                 }
1655 
1656                 if (!createNewObject)
1657                 {
1658                     for (const auto& property : output)
1659                     {
1660                         crow::connections::systemBus->async_method_call(
1661                             [response,
1662                              propertyName{std::string(property.first)}](
1663                                 const boost::system::error_code& ec) {
1664                                 if (ec)
1665                                 {
1666                                     BMCWEB_LOG_ERROR("Error patching {}: {}",
1667                                                      propertyName, ec);
1668                                     messages::internalError(response->res);
1669                                     return;
1670                                 }
1671                                 messages::success(response->res);
1672                             },
1673                             "xyz.openbmc_project.EntityManager", path,
1674                             "org.freedesktop.DBus.Properties", "Set", iface,
1675                             property.first, property.second);
1676                     }
1677                 }
1678                 else
1679                 {
1680                     if (chassis.empty())
1681                     {
1682                         BMCWEB_LOG_ERROR("Failed to get chassis from config");
1683                         messages::internalError(response->res);
1684                         return;
1685                     }
1686 
1687                     bool foundChassis = false;
1688                     for (const auto& obj : managedObj)
1689                     {
1690                         if (obj.first.filename() == chassis)
1691                         {
1692                             chassis = obj.first.str;
1693                             foundChassis = true;
1694                             break;
1695                         }
1696                     }
1697                     if (!foundChassis)
1698                     {
1699                         BMCWEB_LOG_ERROR("Failed to find chassis on dbus");
1700                         messages::resourceMissingAtURI(
1701                             response->res,
1702                             boost::urls::format("/redfish/v1/Chassis/{}",
1703                                                 chassis));
1704                         return;
1705                     }
1706 
1707                     crow::connections::systemBus->async_method_call(
1708                         [response](const boost::system::error_code& ec) {
1709                             if (ec)
1710                             {
1711                                 BMCWEB_LOG_ERROR("Error Adding Pid Object {}",
1712                                                  ec);
1713                                 messages::internalError(response->res);
1714                                 return;
1715                             }
1716                             messages::success(response->res);
1717                         },
1718                         "xyz.openbmc_project.EntityManager", chassis,
1719                         "xyz.openbmc_project.AddObject", "AddObject", output);
1720                 }
1721             }
1722         }
1723     }
1724 
1725     ~SetPIDValues()
1726     {
1727         try
1728         {
1729             pidSetDone();
1730         }
1731         catch (...)
1732         {
1733             BMCWEB_LOG_CRITICAL("pidSetDone threw exception");
1734         }
1735     }
1736 
1737     std::shared_ptr<bmcweb::AsyncResp> asyncResp;
1738     std::vector<std::pair<std::string, std::optional<nlohmann::json::object_t>>>
1739         configuration;
1740     std::optional<std::string> profile;
1741     dbus::utility::ManagedObjectType managedObj;
1742     std::vector<std::string> supportedProfiles;
1743     std::string currentProfile;
1744     std::string profileConnection;
1745     std::string profilePath;
1746     size_t objectCount = 0;
1747 };
1748 
1749 /**
1750  * @brief Retrieves BMC manager location data over DBus
1751  *
1752  * @param[in] asyncResp Shared pointer for completing asynchronous calls
1753  * @param[in] connectionName - service name
1754  * @param[in] path - object path
1755  * @return none
1756  */
1757 inline void getLocation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1758                         const std::string& connectionName,
1759                         const std::string& path)
1760 {
1761     BMCWEB_LOG_DEBUG("Get BMC manager Location data.");
1762 
1763     sdbusplus::asio::getProperty<std::string>(
1764         *crow::connections::systemBus, connectionName, path,
1765         "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode",
1766         [asyncResp](const boost::system::error_code& ec,
1767                     const std::string& property) {
1768             if (ec)
1769             {
1770                 BMCWEB_LOG_DEBUG("DBUS response error for "
1771                                  "Location");
1772                 messages::internalError(asyncResp->res);
1773                 return;
1774             }
1775 
1776             asyncResp->res
1777                 .jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
1778                 property;
1779         });
1780 }
1781 // avoid name collision systems.hpp
1782 inline void
1783     managerGetLastResetTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1784 {
1785     BMCWEB_LOG_DEBUG("Getting Manager Last Reset Time");
1786 
1787     sdbusplus::asio::getProperty<uint64_t>(
1788         *crow::connections::systemBus, "xyz.openbmc_project.State.BMC",
1789         "/xyz/openbmc_project/state/bmc0", "xyz.openbmc_project.State.BMC",
1790         "LastRebootTime",
1791         [asyncResp](const boost::system::error_code& ec,
1792                     const uint64_t lastResetTime) {
1793             if (ec)
1794             {
1795                 BMCWEB_LOG_DEBUG("D-BUS response error {}", ec);
1796                 return;
1797             }
1798 
1799             // LastRebootTime is epoch time, in milliseconds
1800             // https://github.com/openbmc/phosphor-dbus-interfaces/blob/7f9a128eb9296e926422ddc312c148b625890bb6/xyz/openbmc_project/State/BMC.interface.yaml#L19
1801             uint64_t lastResetTimeStamp = lastResetTime / 1000;
1802 
1803             // Convert to ISO 8601 standard
1804             asyncResp->res.jsonValue["LastResetTime"] =
1805                 redfish::time_utils::getDateTimeUint(lastResetTimeStamp);
1806         });
1807 }
1808 
1809 /**
1810  * @brief Set the running firmware image
1811  *
1812  * @param[i,o] asyncResp - Async response object
1813  * @param[i] runningFirmwareTarget - Image to make the running image
1814  *
1815  * @return void
1816  */
1817 inline void
1818     setActiveFirmwareImage(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1819                            const std::string& runningFirmwareTarget)
1820 {
1821     // Get the Id from /redfish/v1/UpdateService/FirmwareInventory/<Id>
1822     std::string::size_type idPos = runningFirmwareTarget.rfind('/');
1823     if (idPos == std::string::npos)
1824     {
1825         messages::propertyValueNotInList(asyncResp->res, runningFirmwareTarget,
1826                                          "@odata.id");
1827         BMCWEB_LOG_DEBUG("Can't parse firmware ID!");
1828         return;
1829     }
1830     idPos++;
1831     if (idPos >= runningFirmwareTarget.size())
1832     {
1833         messages::propertyValueNotInList(asyncResp->res, runningFirmwareTarget,
1834                                          "@odata.id");
1835         BMCWEB_LOG_DEBUG("Invalid firmware ID.");
1836         return;
1837     }
1838     std::string firmwareId = runningFirmwareTarget.substr(idPos);
1839 
1840     // Make sure the image is valid before setting priority
1841     sdbusplus::message::object_path objPath("/xyz/openbmc_project/software");
1842     dbus::utility::getManagedObjects(
1843         "xyz.openbmc_project.Software.BMC.Updater", objPath,
1844         [asyncResp, firmwareId, runningFirmwareTarget](
1845             const boost::system::error_code& ec,
1846             const dbus::utility::ManagedObjectType& subtree) {
1847             if (ec)
1848             {
1849                 BMCWEB_LOG_DEBUG("D-Bus response error getting objects.");
1850                 messages::internalError(asyncResp->res);
1851                 return;
1852             }
1853 
1854             if (subtree.empty())
1855             {
1856                 BMCWEB_LOG_DEBUG("Can't find image!");
1857                 messages::internalError(asyncResp->res);
1858                 return;
1859             }
1860 
1861             bool foundImage = false;
1862             for (const auto& object : subtree)
1863             {
1864                 const std::string& path =
1865                     static_cast<const std::string&>(object.first);
1866                 std::size_t idPos2 = path.rfind('/');
1867 
1868                 if (idPos2 == std::string::npos)
1869                 {
1870                     continue;
1871                 }
1872 
1873                 idPos2++;
1874                 if (idPos2 >= path.size())
1875                 {
1876                     continue;
1877                 }
1878 
1879                 if (path.substr(idPos2) == firmwareId)
1880                 {
1881                     foundImage = true;
1882                     break;
1883                 }
1884             }
1885 
1886             if (!foundImage)
1887             {
1888                 messages::propertyValueNotInList(
1889                     asyncResp->res, runningFirmwareTarget, "@odata.id");
1890                 BMCWEB_LOG_DEBUG("Invalid firmware ID.");
1891                 return;
1892             }
1893 
1894             BMCWEB_LOG_DEBUG("Setting firmware version {} to priority 0.",
1895                              firmwareId);
1896 
1897             // Only support Immediate
1898             // An addition could be a Redfish Setting like
1899             // ActiveSoftwareImageApplyTime and support OnReset
1900             sdbusplus::asio::setProperty(
1901                 *crow::connections::systemBus,
1902                 "xyz.openbmc_project.Software.BMC.Updater",
1903                 "/xyz/openbmc_project/software/" + firmwareId,
1904                 "xyz.openbmc_project.Software.RedundancyPriority", "Priority",
1905                 static_cast<uint8_t>(0),
1906                 [asyncResp](const boost::system::error_code& ec2) {
1907                     if (ec2)
1908                     {
1909                         BMCWEB_LOG_DEBUG("D-Bus response error setting.");
1910                         messages::internalError(asyncResp->res);
1911                         return;
1912                     }
1913                     doBMCGracefulRestart(asyncResp);
1914                 });
1915         });
1916 }
1917 
1918 inline void afterSetDateTime(
1919     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1920     const boost::system::error_code& ec, const sdbusplus::message_t& msg)
1921 {
1922     if (ec)
1923     {
1924         BMCWEB_LOG_DEBUG("Failed to set elapsed time. DBUS response error {}",
1925                          ec);
1926         const sd_bus_error* dbusError = msg.get_error();
1927         if (dbusError != nullptr)
1928         {
1929             std::string_view errorName(dbusError->name);
1930             if (errorName ==
1931                 "org.freedesktop.timedate1.AutomaticTimeSyncEnabled")
1932             {
1933                 BMCWEB_LOG_DEBUG("Setting conflict");
1934                 messages::propertyValueConflict(
1935                     asyncResp->res, "DateTime",
1936                     "Managers/NetworkProtocol/NTPProcotolEnabled");
1937                 return;
1938             }
1939         }
1940         messages::internalError(asyncResp->res);
1941         return;
1942     }
1943     asyncResp->res.result(boost::beast::http::status::no_content);
1944 }
1945 
1946 inline void setDateTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1947                         const std::string& datetime)
1948 {
1949     BMCWEB_LOG_DEBUG("Set date time: {}", datetime);
1950 
1951     std::optional<redfish::time_utils::usSinceEpoch> us =
1952         redfish::time_utils::dateStringToEpoch(datetime);
1953     if (!us)
1954     {
1955         messages::propertyValueFormatError(asyncResp->res, datetime,
1956                                            "DateTime");
1957         return;
1958     }
1959     // Set the absolute datetime
1960     bool relative = false;
1961     bool interactive = false;
1962     crow::connections::systemBus->async_method_call(
1963         [asyncResp](const boost::system::error_code& ec,
1964                     const sdbusplus::message_t& msg) {
1965             afterSetDateTime(asyncResp, ec, msg);
1966         },
1967         "org.freedesktop.timedate1", "/org/freedesktop/timedate1",
1968         "org.freedesktop.timedate1", "SetTime", us->count(), relative,
1969         interactive);
1970 }
1971 
1972 inline void
1973     checkForQuiesced(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1974 {
1975     sdbusplus::asio::getProperty<std::string>(
1976         *crow::connections::systemBus, "org.freedesktop.systemd1",
1977         "/org/freedesktop/systemd1/unit/obmc-bmc-service-quiesce@0.target",
1978         "org.freedesktop.systemd1.Unit", "ActiveState",
1979         [asyncResp](const boost::system::error_code& ec,
1980                     const std::string& val) {
1981             if (!ec)
1982             {
1983                 if (val == "active")
1984                 {
1985                     asyncResp->res.jsonValue["Status"]["Health"] =
1986                         resource::Health::Critical;
1987                     asyncResp->res.jsonValue["Status"]["State"] =
1988                         resource::State::Quiesced;
1989                     return;
1990                 }
1991             }
1992             asyncResp->res.jsonValue["Status"]["Health"] = resource::Health::OK;
1993             asyncResp->res.jsonValue["Status"]["State"] =
1994                 resource::State::Enabled;
1995         });
1996 }
1997 
1998 inline void requestRoutesManager(App& app)
1999 {
2000     std::string uuid = persistent_data::getConfig().systemUuid;
2001 
2002     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/")
2003         .privileges(redfish::privileges::getManager)
2004         .methods(
2005             boost::beast::http::verb::
2006                 get)([&app,
2007                       uuid](const crow::Request& req,
2008                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2009                             const std::string& managerId) {
2010             if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2011             {
2012                 return;
2013             }
2014 
2015             if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2016             {
2017                 messages::resourceNotFound(asyncResp->res, "Manager",
2018                                            managerId);
2019                 return;
2020             }
2021 
2022             asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
2023                 "/redfish/v1/Managers/{}", BMCWEB_REDFISH_MANAGER_URI_NAME);
2024             asyncResp->res.jsonValue["@odata.type"] =
2025                 "#Manager.v1_14_0.Manager";
2026             asyncResp->res.jsonValue["Id"] = BMCWEB_REDFISH_MANAGER_URI_NAME;
2027             asyncResp->res.jsonValue["Name"] = "OpenBmc Manager";
2028             asyncResp->res.jsonValue["Description"] =
2029                 "Baseboard Management Controller";
2030             asyncResp->res.jsonValue["PowerState"] = resource::PowerState::On;
2031 
2032             asyncResp->res.jsonValue["ManagerType"] = manager::ManagerType::BMC;
2033             asyncResp->res.jsonValue["UUID"] = systemd_utils::getUuid();
2034             asyncResp->res.jsonValue["ServiceEntryPointUUID"] = uuid;
2035             asyncResp->res.jsonValue["Model"] =
2036                 "OpenBmc"; // TODO(ed), get model
2037 
2038             asyncResp->res.jsonValue["LogServices"]["@odata.id"] =
2039                 boost::urls::format("/redfish/v1/Managers/{}/LogServices",
2040                                     BMCWEB_REDFISH_MANAGER_URI_NAME);
2041             asyncResp->res.jsonValue["NetworkProtocol"]["@odata.id"] =
2042                 boost::urls::format("/redfish/v1/Managers/{}/NetworkProtocol",
2043                                     BMCWEB_REDFISH_MANAGER_URI_NAME);
2044             asyncResp->res.jsonValue["EthernetInterfaces"]["@odata.id"] =
2045                 boost::urls::format(
2046                     "/redfish/v1/Managers/{}/EthernetInterfaces",
2047                     BMCWEB_REDFISH_MANAGER_URI_NAME);
2048 
2049             if constexpr (BMCWEB_VM_NBDPROXY)
2050             {
2051                 asyncResp->res.jsonValue["VirtualMedia"]["@odata.id"] =
2052                     boost::urls::format("/redfish/v1/Managers/{}/VirtualMedia",
2053                                         BMCWEB_REDFISH_MANAGER_URI_NAME);
2054             }
2055 
2056             // default oem data
2057             nlohmann::json& oem = asyncResp->res.jsonValue["Oem"];
2058             nlohmann::json& oemOpenbmc = oem["OpenBmc"];
2059             oem["@odata.id"] =
2060                 boost::urls::format("/redfish/v1/Managers/{}#/Oem",
2061                                     BMCWEB_REDFISH_MANAGER_URI_NAME);
2062             oemOpenbmc["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager";
2063             oemOpenbmc["@odata.id"] =
2064                 boost::urls::format("/redfish/v1/Managers/{}#/Oem/OpenBmc",
2065                                     BMCWEB_REDFISH_MANAGER_URI_NAME);
2066 
2067             nlohmann::json::object_t certificates;
2068             certificates["@odata.id"] = boost::urls::format(
2069                 "/redfish/v1/Managers/{}/Truststore/Certificates",
2070                 BMCWEB_REDFISH_MANAGER_URI_NAME);
2071             oemOpenbmc["Certificates"] = std::move(certificates);
2072 
2073             // Manager.Reset (an action) can be many values, OpenBMC only
2074             // supports BMC reboot.
2075             nlohmann::json& managerReset =
2076                 asyncResp->res.jsonValue["Actions"]["#Manager.Reset"];
2077             managerReset["target"] = boost::urls::format(
2078                 "/redfish/v1/Managers/{}/Actions/Manager.Reset",
2079                 BMCWEB_REDFISH_MANAGER_URI_NAME);
2080             managerReset["@Redfish.ActionInfo"] =
2081                 boost::urls::format("/redfish/v1/Managers/{}/ResetActionInfo",
2082                                     BMCWEB_REDFISH_MANAGER_URI_NAME);
2083 
2084             // ResetToDefaults (Factory Reset) has values like
2085             // PreserveNetworkAndUsers and PreserveNetwork that aren't supported
2086             // on OpenBMC
2087             nlohmann::json& resetToDefaults =
2088                 asyncResp->res.jsonValue["Actions"]["#Manager.ResetToDefaults"];
2089             resetToDefaults["target"] = boost::urls::format(
2090                 "/redfish/v1/Managers/{}/Actions/Manager.ResetToDefaults",
2091                 BMCWEB_REDFISH_MANAGER_URI_NAME);
2092             resetToDefaults["ResetType@Redfish.AllowableValues"] =
2093                 nlohmann::json::array_t({"ResetAll"});
2094 
2095             std::pair<std::string, std::string> redfishDateTimeOffset =
2096                 redfish::time_utils::getDateTimeOffsetNow();
2097 
2098             asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
2099             asyncResp->res.jsonValue["DateTimeLocalOffset"] =
2100                 redfishDateTimeOffset.second;
2101 
2102             // TODO (Gunnar): Remove these one day since moved to ComputerSystem
2103             // Still used by OCP profiles
2104             // https://github.com/opencomputeproject/OCP-Profiles/issues/23
2105             // Fill in SerialConsole info
2106             asyncResp->res.jsonValue["SerialConsole"]["ServiceEnabled"] = true;
2107             asyncResp->res.jsonValue["SerialConsole"]["MaxConcurrentSessions"] =
2108                 15;
2109             asyncResp->res.jsonValue["SerialConsole"]["ConnectTypesSupported"] =
2110                 nlohmann::json::array_t({"IPMI", "SSH"});
2111             if constexpr (BMCWEB_KVM)
2112             {
2113                 // Fill in GraphicalConsole info
2114                 asyncResp->res.jsonValue["GraphicalConsole"]["ServiceEnabled"] =
2115                     true;
2116                 asyncResp->res
2117                     .jsonValue["GraphicalConsole"]["MaxConcurrentSessions"] = 4;
2118                 asyncResp->res
2119                     .jsonValue["GraphicalConsole"]["ConnectTypesSupported"] =
2120                     nlohmann::json::array_t({"KVMIP"});
2121             }
2122             if constexpr (!BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
2123             {
2124                 asyncResp->res
2125                     .jsonValue["Links"]["ManagerForServers@odata.count"] = 1;
2126 
2127                 nlohmann::json::array_t managerForServers;
2128                 nlohmann::json::object_t manager;
2129                 manager["@odata.id"] = std::format(
2130                     "/redfish/v1/Systems/{}", BMCWEB_REDFISH_SYSTEM_URI_NAME);
2131                 managerForServers.emplace_back(std::move(manager));
2132 
2133                 asyncResp->res.jsonValue["Links"]["ManagerForServers"] =
2134                     std::move(managerForServers);
2135             }
2136 
2137             sw_util::populateSoftwareInformation(asyncResp, sw_util::bmcPurpose,
2138                                                  "FirmwareVersion", true);
2139 
2140             managerGetLastResetTime(asyncResp);
2141 
2142             // ManagerDiagnosticData is added for all BMCs.
2143             nlohmann::json& managerDiagnosticData =
2144                 asyncResp->res.jsonValue["ManagerDiagnosticData"];
2145             managerDiagnosticData["@odata.id"] = boost::urls::format(
2146                 "/redfish/v1/Managers/{}/ManagerDiagnosticData",
2147                 BMCWEB_REDFISH_MANAGER_URI_NAME);
2148 
2149             if constexpr (BMCWEB_REDFISH_OEM_MANAGER_FAN_DATA)
2150             {
2151                 auto pids = std::make_shared<GetPIDValues>(asyncResp);
2152                 pids->run();
2153             }
2154 
2155             getMainChassisId(asyncResp, [](const std::string& chassisId,
2156                                            const std::shared_ptr<
2157                                                bmcweb::AsyncResp>& aRsp) {
2158                 aRsp->res.jsonValue["Links"]["ManagerForChassis@odata.count"] =
2159                     1;
2160                 nlohmann::json::array_t managerForChassis;
2161                 nlohmann::json::object_t managerObj;
2162                 boost::urls::url chassiUrl =
2163                     boost::urls::format("/redfish/v1/Chassis/{}", chassisId);
2164                 managerObj["@odata.id"] = chassiUrl;
2165                 managerForChassis.emplace_back(std::move(managerObj));
2166                 aRsp->res.jsonValue["Links"]["ManagerForChassis"] =
2167                     std::move(managerForChassis);
2168                 aRsp->res.jsonValue["Links"]["ManagerInChassis"]["@odata.id"] =
2169                     chassiUrl;
2170             });
2171 
2172             sdbusplus::asio::getProperty<double>(
2173                 *crow::connections::systemBus, "org.freedesktop.systemd1",
2174                 "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager",
2175                 "Progress",
2176                 [asyncResp](const boost::system::error_code& ec, double val) {
2177                     if (ec)
2178                     {
2179                         BMCWEB_LOG_ERROR("Error while getting progress");
2180                         messages::internalError(asyncResp->res);
2181                         return;
2182                     }
2183                     if (val < 1.0)
2184                     {
2185                         asyncResp->res.jsonValue["Status"]["Health"] =
2186                             resource::Health::OK;
2187                         asyncResp->res.jsonValue["Status"]["State"] =
2188                             resource::State::Starting;
2189                         return;
2190                     }
2191                     checkForQuiesced(asyncResp);
2192                 });
2193 
2194             constexpr std::array<std::string_view, 1> interfaces = {
2195                 "xyz.openbmc_project.Inventory.Item.Bmc"};
2196             dbus::utility::getSubTree(
2197                 "/xyz/openbmc_project/inventory", 0, interfaces,
2198                 [asyncResp](
2199                     const boost::system::error_code& ec,
2200                     const dbus::utility::MapperGetSubTreeResponse& subtree) {
2201                     if (ec)
2202                     {
2203                         BMCWEB_LOG_DEBUG(
2204                             "D-Bus response error on GetSubTree {}", ec);
2205                         return;
2206                     }
2207                     if (subtree.empty())
2208                     {
2209                         BMCWEB_LOG_DEBUG("Can't find bmc D-Bus object!");
2210                         return;
2211                     }
2212                     // Assume only 1 bmc D-Bus object
2213                     // Throw an error if there is more than 1
2214                     if (subtree.size() > 1)
2215                     {
2216                         BMCWEB_LOG_DEBUG("Found more than 1 bmc D-Bus object!");
2217                         messages::internalError(asyncResp->res);
2218                         return;
2219                     }
2220 
2221                     if (subtree[0].first.empty() ||
2222                         subtree[0].second.size() != 1)
2223                     {
2224                         BMCWEB_LOG_DEBUG("Error getting bmc D-Bus object!");
2225                         messages::internalError(asyncResp->res);
2226                         return;
2227                     }
2228 
2229                     const std::string& path = subtree[0].first;
2230                     const std::string& connectionName =
2231                         subtree[0].second[0].first;
2232 
2233                     for (const auto& interfaceName :
2234                          subtree[0].second[0].second)
2235                     {
2236                         if (interfaceName ==
2237                             "xyz.openbmc_project.Inventory.Decorator.Asset")
2238                         {
2239                             sdbusplus::asio::getAllProperties(
2240                                 *crow::connections::systemBus, connectionName,
2241                                 path,
2242                                 "xyz.openbmc_project.Inventory.Decorator.Asset",
2243                                 [asyncResp](
2244                                     const boost::system::error_code& ec2,
2245                                     const dbus::utility::DBusPropertiesMap&
2246                                         propertiesList) {
2247                                     if (ec2)
2248                                     {
2249                                         BMCWEB_LOG_DEBUG(
2250                                             "Can't get bmc asset!");
2251                                         return;
2252                                     }
2253 
2254                                     const std::string* partNumber = nullptr;
2255                                     const std::string* serialNumber = nullptr;
2256                                     const std::string* manufacturer = nullptr;
2257                                     const std::string* model = nullptr;
2258                                     const std::string* sparePartNumber =
2259                                         nullptr;
2260 
2261                                     const bool success =
2262                                         sdbusplus::unpackPropertiesNoThrow(
2263                                             dbus_utils::UnpackErrorPrinter(),
2264                                             propertiesList, "PartNumber",
2265                                             partNumber, "SerialNumber",
2266                                             serialNumber, "Manufacturer",
2267                                             manufacturer, "Model", model,
2268                                             "SparePartNumber", sparePartNumber);
2269 
2270                                     if (!success)
2271                                     {
2272                                         messages::internalError(asyncResp->res);
2273                                         return;
2274                                     }
2275 
2276                                     if (partNumber != nullptr)
2277                                     {
2278                                         asyncResp->res.jsonValue["PartNumber"] =
2279                                             *partNumber;
2280                                     }
2281 
2282                                     if (serialNumber != nullptr)
2283                                     {
2284                                         asyncResp->res
2285                                             .jsonValue["SerialNumber"] =
2286                                             *serialNumber;
2287                                     }
2288 
2289                                     if (manufacturer != nullptr)
2290                                     {
2291                                         asyncResp->res
2292                                             .jsonValue["Manufacturer"] =
2293                                             *manufacturer;
2294                                     }
2295 
2296                                     if (model != nullptr)
2297                                     {
2298                                         asyncResp->res.jsonValue["Model"] =
2299                                             *model;
2300                                     }
2301 
2302                                     if (sparePartNumber != nullptr)
2303                                     {
2304                                         asyncResp->res
2305                                             .jsonValue["SparePartNumber"] =
2306                                             *sparePartNumber;
2307                                     }
2308                                 });
2309                         }
2310                         else if (
2311                             interfaceName ==
2312                             "xyz.openbmc_project.Inventory.Decorator.LocationCode")
2313                         {
2314                             getLocation(asyncResp, connectionName, path);
2315                         }
2316                     }
2317                 });
2318         });
2319 
2320     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/")
2321         .privileges(redfish::privileges::patchManager)
2322         .methods(boost::beast::http::verb::patch)(
2323             [&app](const crow::Request& req,
2324                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2325                    const std::string& managerId) {
2326                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2327                 {
2328                     return;
2329                 }
2330 
2331                 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2332                 {
2333                     messages::resourceNotFound(asyncResp->res, "Manager",
2334                                                managerId);
2335                     return;
2336                 }
2337 
2338                 std::optional<std::string> activeSoftwareImageOdataId;
2339                 std::optional<std::string> datetime;
2340                 std::optional<nlohmann::json::object_t> pidControllers;
2341                 std::optional<nlohmann::json::object_t> fanControllers;
2342                 std::optional<nlohmann::json::object_t> fanZones;
2343                 std::optional<nlohmann::json::object_t> stepwiseControllers;
2344                 std::optional<std::string> profile;
2345 
2346                 if (!json_util::readJsonPatch( //
2347                         req, asyncResp->res, //
2348                         "DateTime", datetime, //
2349                         "Links/ActiveSoftwareImage/@odata.id",
2350                         activeSoftwareImageOdataId, //
2351                         "Oem/OpenBmc/Fan/FanControllers", fanControllers, //
2352                         "Oem/OpenBmc/Fan/FanZones", fanZones, //
2353                         "Oem/OpenBmc/Fan/PidControllers", pidControllers, //
2354                         "Oem/OpenBmc/Fan/Profile", profile, //
2355                         "Oem/OpenBmc/Fan/StepwiseControllers",
2356                         stepwiseControllers //
2357                         ))
2358                 {
2359                     return;
2360                 }
2361 
2362                 if (pidControllers || fanControllers || fanZones ||
2363                     stepwiseControllers || profile)
2364                 {
2365                     if constexpr (BMCWEB_REDFISH_OEM_MANAGER_FAN_DATA)
2366                     {
2367                         std::vector<
2368                             std::pair<std::string,
2369                                       std::optional<nlohmann::json::object_t>>>
2370                             configuration;
2371                         if (pidControllers)
2372                         {
2373                             configuration.emplace_back(
2374                                 "PidControllers", std::move(pidControllers));
2375                         }
2376                         if (fanControllers)
2377                         {
2378                             configuration.emplace_back(
2379                                 "FanControllers", std::move(fanControllers));
2380                         }
2381                         if (fanZones)
2382                         {
2383                             configuration.emplace_back("FanZones",
2384                                                        std::move(fanZones));
2385                         }
2386                         if (stepwiseControllers)
2387                         {
2388                             configuration.emplace_back(
2389                                 "StepwiseControllers",
2390                                 std::move(stepwiseControllers));
2391                         }
2392                         auto pid = std::make_shared<SetPIDValues>(
2393                             asyncResp, std::move(configuration), profile);
2394                         pid->run();
2395                     }
2396                     else
2397                     {
2398                         messages::propertyUnknown(asyncResp->res, "Oem");
2399                         return;
2400                     }
2401                 }
2402 
2403                 if (activeSoftwareImageOdataId)
2404                 {
2405                     setActiveFirmwareImage(asyncResp,
2406                                            *activeSoftwareImageOdataId);
2407                 }
2408 
2409                 if (datetime)
2410                 {
2411                     setDateTime(asyncResp, *datetime);
2412                 }
2413             });
2414 }
2415 
2416 inline void requestRoutesManagerCollection(App& app)
2417 {
2418     BMCWEB_ROUTE(app, "/redfish/v1/Managers/")
2419         .privileges(redfish::privileges::getManagerCollection)
2420         .methods(boost::beast::http::verb::get)(
2421             [&app](const crow::Request& req,
2422                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2423                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2424                 {
2425                     return;
2426                 }
2427                 // Collections don't include the static data added by SubRoute
2428                 // because it has a duplicate entry for members
2429                 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Managers";
2430                 asyncResp->res.jsonValue["@odata.type"] =
2431                     "#ManagerCollection.ManagerCollection";
2432                 asyncResp->res.jsonValue["Name"] = "Manager Collection";
2433                 asyncResp->res.jsonValue["Members@odata.count"] = 1;
2434                 nlohmann::json::array_t members;
2435                 nlohmann::json& bmc = members.emplace_back();
2436                 bmc["@odata.id"] = boost::urls::format(
2437                     "/redfish/v1/Managers/{}", BMCWEB_REDFISH_MANAGER_URI_NAME);
2438                 asyncResp->res.jsonValue["Members"] = std::move(members);
2439             });
2440 }
2441 } // namespace redfish
2442