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::xyz::openbmc_project::Condition::server;
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::bus& 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::exception& 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 
105                 std::variant<FirmwareCondition> currentFwCondV;
106                 response.read(currentFwCondV);
107                 auto currentFwCond =
108                     std::get<FirmwareCondition>(currentFwCondV);
109 
110                 if (currentFwCond == FirmwareCondition::Running)
111                 {
112                     return true;
113                 }
114             }
115             catch (const sdbusplus::exception::exception& 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::bus& bus, size_t id)
129 {
130     auto svcname = std::string{CHASSIS_STATE_SVC} + std::to_string(id);
131     auto objpath = std::string{CHASSIS_STATE_PATH} + std::to_string(id);
132 
133     try
134     {
135         using PowerState =
136             sdbusplus::xyz::openbmc_project::State::server::Chassis::PowerState;
137         auto method = bus.new_method_call(svcname.c_str(), objpath.c_str(),
138                                           PROPERTY_INTERFACE, "Get");
139         method.append(CHASSIS_STATE_INTF, CHASSIS_STATE_POWER_PROP);
140 
141         auto response = bus.call(method);
142 
143         std::variant<PowerState> currentPowerStateV;
144         response.read(currentPowerStateV);
145         auto currentPowerState = std::get<PowerState>(currentPowerStateV);
146 
147         if (currentPowerState == PowerState::On)
148         {
149             return true;
150         }
151     }
152     catch (const sdbusplus::exception::exception& e)
153     {
154         error("Error reading Chassis Power State, error: {ERROR}, "
155               "service: {SERVICE} path: {PATH}",
156               "ERROR", e, "SERVICE", svcname.c_str(), "PATH", objpath.c_str());
157         throw;
158     }
159     return false;
160 }
161 
162 bool isHostRunning(size_t id)
163 {
164     info("Check if host is running");
165 
166     auto bus = sdbusplus::bus::new_default();
167 
168     // No need to check if chassis power is not on
169     if (!isChassiPowerOn(bus, id))
170     {
171         info("Chassis power not on, exit");
172         return false;
173     }
174 
175     // This applications systemd service is setup to only run after all other
176     // application that could possibly implement the needed interface have
177     // been started. However, the use of mapper to find those interfaces means
178     // we have a condition where the interface may be on D-Bus but not stored
179     // within mapper yet. Keep it simple and just build one retry into the
180     // check if it's found the host is not up. This service is only called if
181     // chassis power is on when the BMC comes up, so this wont impact most
182     // normal cases where the BMC is rebooted with chassis power off. In
183     // cases where chassis power is on, the host is likely running so we want
184     // to be sure we check all interfaces
185     for (int i = 0; i < 2; i++)
186     {
187         // Give mapper a small window to introspect new objects on bus
188         std::this_thread::sleep_for(std::chrono::milliseconds(500));
189         if (checkFirmwareConditionRunning(bus))
190         {
191             info("Host is running!");
192             // Create file for host instance and create in filesystem to
193             // indicate to services that host is running
194             auto size = std::snprintf(nullptr, 0, HOST_RUNNING_FILE, 0);
195             size++; // null
196             std::unique_ptr<char[]> buf(new char[size]);
197             std::snprintf(buf.get(), size, HOST_RUNNING_FILE, 0);
198             std::ofstream outfile(buf.get());
199             outfile.close();
200             return true;
201         }
202     }
203     info("Host is not running!");
204     return false;
205 }
206 
207 } // namespace manager
208 } // namespace state
209 } // namespace phosphor
210