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