1 #include "config.h"
2 
3 #include "host_check.hpp"
4 
5 #include <unistd.h>
6 
7 #include <phosphor-logging/lg2.hpp>
8 #include <sdbusplus/bus.hpp>
9 #include <sdbusplus/exception.hpp>
10 #include <xyz/openbmc_project/Condition/HostFirmware/client.hpp>
11 #include <xyz/openbmc_project/ObjectMapper/client.hpp>
12 #include <xyz/openbmc_project/State/Chassis/client.hpp>
13 
14 #include <cstdio>
15 #include <cstdlib>
16 #include <fstream>
17 #include <iostream>
18 #include <ranges>
19 #include <thread>
20 #include <vector>
21 
22 namespace phosphor
23 {
24 namespace state
25 {
26 namespace manager
27 {
28 
29 PHOSPHOR_LOG2_USING;
30 
31 using namespace std::literals;
32 
33 using ObjectMapper = sdbusplus::client::xyz::openbmc_project::ObjectMapper<>;
34 using Chassis = sdbusplus::client::xyz::openbmc_project::state::Chassis<>;
35 using HostFirmware =
36     sdbusplus::client::xyz::openbmc_project::condition::HostFirmware<>;
37 
38 // Required strings for sending the msg to check on host
39 constexpr auto CONDITION_HOST_PROPERTY = "CurrentFirmwareCondition";
40 constexpr auto PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties";
41 
42 constexpr auto CHASSIS_STATE_SVC = "xyz.openbmc_project.State.Chassis";
43 constexpr auto CHASSIS_STATE_POWER_PROP = "CurrentPowerState";
44 
45 // Find all implementations of Condition interface and check if host is
46 // running over it
47 bool checkFirmwareConditionRunning(sdbusplus::bus_t& bus)
48 {
49     // Find all implementations of host firmware condition interface
50     auto mapper = bus.new_method_call(ObjectMapper::default_service,
51                                       ObjectMapper::instance_path,
52                                       ObjectMapper::interface, "GetSubTree");
53 
54     mapper.append("/", 0, std::vector<std::string>({HostFirmware::interface}));
55 
56     std::map<std::string, std::map<std::string, std::vector<std::string>>>
57         mapperResponse;
58 
59     try
60     {
61         auto mapperResponseMsg = bus.call(mapper);
62         mapperResponseMsg.read(mapperResponse);
63     }
64     catch (const sdbusplus::exception_t& e)
65     {
66         error(
67             "Error in mapper GetSubTree call for HostFirmware condition: {ERROR}",
68             "ERROR", e);
69         throw;
70     }
71 
72     if (mapperResponse.empty())
73     {
74         info("Mapper response for HostFirmware conditions is empty!");
75         return false;
76     }
77 
78     // Now read the CurrentFirmwareCondition from all interfaces we found
79     // Currently there are two implementations of this interface. One by IPMI
80     // and one by PLDM. The IPMI interface does a realtime check with the host
81     // when the interface is called. This means if the host is not running,
82     // we will have to wait for the timeout (currently set to 3 seconds). The
83     // PLDM interface reads a cached state. The PLDM service does not put itself
84     // on D-Bus until it has checked with the host. Therefore it's most
85     // efficient to call the PLDM interface first. Do that by going in reverse
86     // of the interfaces returned to us (PLDM will be last if available)
87     for (const auto& [path, services] : std::views::reverse(mapperResponse))
88     {
89         for (const auto& serviceIter : services)
90         {
91             const std::string& service = serviceIter.first;
92 
93             try
94             {
95                 auto method = bus.new_method_call(service.c_str(), path.c_str(),
96                                                   PROPERTY_INTERFACE, "Get");
97                 method.append(HostFirmware::interface, CONDITION_HOST_PROPERTY);
98 
99                 auto response = bus.call(method);
100                 std::variant<HostFirmware::FirmwareCondition> currentFwCondV;
101                 response.read(currentFwCondV);
102                 auto currentFwCond =
103                     std::get<HostFirmware::FirmwareCondition>(currentFwCondV);
104 
105                 info(
106                     "Read host fw condition {COND_VALUE} from {COND_SERVICE}, {COND_PATH}",
107                     "COND_VALUE", currentFwCond, "COND_SERVICE", service,
108                     "COND_PATH", path);
109 
110                 if (currentFwCond == HostFirmware::FirmwareCondition::Running)
111                 {
112                     return true;
113                 }
114             }
115             catch (const sdbusplus::exception_t& e)
116             {
117                 error("Error reading HostFirmware condition, error: {ERROR}, "
118                       "service: {SERVICE} path: {PATH}",
119                       "ERROR", e, "SERVICE", service, "PATH", path);
120                 throw;
121             }
122         }
123     }
124     return false;
125 }
126 
127 // Helper function to check if chassis power is on
128 bool isChassiPowerOn(sdbusplus::bus_t& bus, size_t id)
129 {
130     auto svcname = std::string{CHASSIS_STATE_SVC} + std::to_string(id);
131     auto objpath = std::string{Chassis::namespace_path::value} + "/" +
132                    std::string{Chassis::namespace_path::chassis} +
133                    std::to_string(id);
134 
135     try
136     {
137         auto method = bus.new_method_call(svcname.c_str(), objpath.c_str(),
138                                           PROPERTY_INTERFACE, "Get");
139         method.append(Chassis::interface, CHASSIS_STATE_POWER_PROP);
140 
141         auto response = bus.call(method);
142         std::variant<Chassis::PowerState> currentPowerStateV;
143         response.read(currentPowerStateV);
144 
145         auto currentPowerState =
146             std::get<Chassis::PowerState>(currentPowerStateV);
147 
148         if (currentPowerState == Chassis::PowerState::On)
149         {
150             return true;
151         }
152     }
153     catch (const sdbusplus::exception_t& e)
154     {
155         error("Error reading Chassis Power State, error: {ERROR}, "
156               "service: {SERVICE} path: {PATH}",
157               "ERROR", e, "SERVICE", svcname.c_str(), "PATH", objpath.c_str());
158         throw;
159     }
160     return false;
161 }
162 
163 bool isHostRunning(size_t id)
164 {
165     info("Check if host is running");
166 
167     auto bus = sdbusplus::bus::new_default();
168 
169     // No need to check if chassis power is not on
170     if (!isChassiPowerOn(bus, id))
171     {
172         info("Chassis power not on, exit");
173         return false;
174     }
175 
176     // This applications systemd service is setup to only run after all other
177     // application that could possibly implement the needed interface have
178     // been started. However, the use of mapper to find those interfaces means
179     // we have a condition where the interface may be on D-Bus but not stored
180     // within mapper yet. There are five built in retries to check if it's
181     // found the host is not up. This service is only called if chassis power
182     // is on when the BMC comes up, so this wont impact most normal cases
183     // where the BMC is rebooted with chassis power off. In cases where
184     // chassis power is on, the host is likely running so we want to be sure
185     // we check all interfaces
186     for (int i = 0; i < 5; i++)
187     {
188         debug(
189             "Introspecting new bus objects for bus id: {ID} sleeping for 1 second.",
190             "ID", id);
191         // Give mapper a small window to introspect new objects on bus
192         std::this_thread::sleep_for(std::chrono::milliseconds(1000));
193         if (checkFirmwareConditionRunning(bus))
194         {
195             info("Host is running!");
196             // Create file for host instance and create in filesystem to
197             // indicate to services that host is running
198             auto size = std::snprintf(nullptr, 0, HOST_RUNNING_FILE, 0);
199             size++; // null
200             std::unique_ptr<char[]> buf(new char[size]);
201             std::snprintf(buf.get(), size, HOST_RUNNING_FILE, 0);
202             std::ofstream outfile(buf.get());
203             outfile.close();
204             return true;
205         }
206     }
207     info("Host is not running!");
208     return false;
209 }
210 
211 } // namespace manager
212 } // namespace state
213 } // namespace phosphor
214