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