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