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