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