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 
13 #include <cstdio>
14 #include <cstdlib>
15 #include <fstream>
16 #include <iostream>
17 #include <thread>
18 #include <vector>
19 
20 namespace phosphor
21 {
22 namespace state
23 {
24 namespace manager
25 {
26 
27 PHOSPHOR_LOG2_USING;
28 
29 using namespace std::literals;
30 using namespace sdbusplus::xyz::openbmc_project::Condition::server;
31 
32 // Required strings for sending the msg to check on host
33 constexpr auto MAPPER_BUSNAME = "xyz.openbmc_project.ObjectMapper";
34 constexpr auto MAPPER_PATH = "/xyz/openbmc_project/object_mapper";
35 constexpr auto MAPPER_INTERFACE = "xyz.openbmc_project.ObjectMapper";
36 constexpr auto CONDITION_HOST_INTERFACE =
37     "xyz.openbmc_project.Condition.HostFirmware";
38 constexpr auto CONDITION_HOST_PROPERTY = "CurrentFirmwareCondition";
39 constexpr auto PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties";
40 
41 constexpr auto CHASSIS_STATE_SVC = "xyz.openbmc_project.State.Chassis";
42 constexpr auto CHASSIS_STATE_PATH = "/xyz/openbmc_project/state/chassis0";
43 constexpr auto CHASSIS_STATE_INTF = "xyz.openbmc_project.State.Chassis";
44 constexpr auto CHASSIS_STATE_POWER_PROP = "CurrentPowerState";
45 
46 // Find all implementations of Condition interface and check if host is
47 // running over it
48 bool checkFirmwareConditionRunning(sdbusplus::bus::bus& bus)
49 {
50     // Find all implementations of host firmware condition interface
51     auto mapper = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
52                                       MAPPER_INTERFACE, "GetSubTree");
53 
54     mapper.append("/", 0, std::vector<std::string>({CONDITION_HOST_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::exception& 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] :
88          boost::adaptors::reverse(mapperResponse))
89     {
90         for (const auto& serviceIter : services)
91         {
92             const std::string& service = serviceIter.first;
93 
94             try
95             {
96                 auto method = bus.new_method_call(service.c_str(), path.c_str(),
97                                                   PROPERTY_INTERFACE, "Get");
98                 method.append(CONDITION_HOST_INTERFACE,
99                               CONDITION_HOST_PROPERTY);
100 
101                 auto response = bus.call(method);
102 
103                 std::variant<std::string> currentFwCond;
104                 response.read(currentFwCond);
105 
106                 if (std::get<std::string>(currentFwCond) ==
107                     "xyz.openbmc_project.Condition.HostFirmware."
108                     "FirmwareCondition."
109                     "Running")
110                 {
111                     return true;
112                 }
113             }
114             catch (const sdbusplus::exception::exception& e)
115             {
116                 error("Error reading HostFirmware condition, error: {ERROR}, "
117                       "service: {SERVICE} path: {PATH}",
118                       "ERROR", e, "SERVICE", service, "PATH", path);
119                 throw;
120             }
121         }
122     }
123     return false;
124 }
125 
126 // Helper function to check if chassis power is on
127 bool isChassiPowerOn(sdbusplus::bus::bus& bus)
128 {
129     try
130     {
131         auto method = bus.new_method_call(CHASSIS_STATE_SVC, CHASSIS_STATE_PATH,
132                                           PROPERTY_INTERFACE, "Get");
133         method.append(CHASSIS_STATE_INTF, CHASSIS_STATE_POWER_PROP);
134 
135         auto response = bus.call(method);
136 
137         std::variant<std::string> currentPowerState;
138         response.read(currentPowerState);
139 
140         if (std::get<std::string>(currentPowerState) ==
141             "xyz.openbmc_project.State.Chassis.PowerState.On")
142         {
143             return true;
144         }
145     }
146     catch (const sdbusplus::exception::exception& e)
147     {
148         error("Error reading Chassis Power State, error: {ERROR}, "
149               "service: {SERVICE} path: {PATH}",
150               "ERROR", e, "SERVICE", CHASSIS_STATE_SVC, "PATH",
151               CHASSIS_STATE_PATH);
152         throw;
153     }
154     return false;
155 }
156 
157 bool isHostRunning()
158 {
159     info("Check if host is running");
160 
161     auto bus = sdbusplus::bus::new_default();
162 
163     // No need to check if chassis power is not on
164     if (!isChassiPowerOn(bus))
165     {
166         info("Chassis power not on, exit");
167         return false;
168     }
169 
170     // This applications systemd service is setup to only run after all other
171     // application that could possibly implement the needed interface have
172     // been started. However, the use of mapper to find those interfaces means
173     // we have a condition where the interface may be on D-Bus but not stored
174     // within mapper yet. Keep it simple and just build one retry into the
175     // check if it's found the host is not up. This service is only called if
176     // chassis power is on when the BMC comes up, so this wont impact most
177     // normal cases where the BMC is rebooted with chassis power off. In
178     // cases where chassis power is on, the host is likely running so we want
179     // to be sure we check all interfaces
180     for (int i = 0; i < 2; i++)
181     {
182         // Give mapper a small window to introspect new objects on bus
183         std::this_thread::sleep_for(std::chrono::milliseconds(500));
184         if (checkFirmwareConditionRunning(bus))
185         {
186             info("Host is running!");
187             // Create file for host instance and create in filesystem to
188             // indicate to services that host is running
189             auto size = std::snprintf(nullptr, 0, HOST_RUNNING_FILE, 0);
190             size++; // null
191             std::unique_ptr<char[]> buf(new char[size]);
192             std::snprintf(buf.get(), size, HOST_RUNNING_FILE, 0);
193             std::ofstream outfile(buf.get());
194             outfile.close();
195             return true;
196         }
197     }
198     info("Host is not running!");
199     return false;
200 }
201 
202 } // namespace manager
203 } // namespace state
204 } // namespace phosphor
205