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