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