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