1 #include "config.h"
2
3 #include <unistd.h>
4
5 #include <phosphor-logging/elog.hpp>
6 #include <phosphor-logging/lg2.hpp>
7 #include <sdbusplus/bus.hpp>
8 #include <sdbusplus/exception.hpp>
9 #include <xyz/openbmc_project/Logging/Create/client.hpp>
10 #include <xyz/openbmc_project/Logging/Entry/client.hpp>
11 #include <xyz/openbmc_project/State/Boot/Progress/client.hpp>
12
13 #include <cstdlib>
14 #include <format>
15 #include <fstream>
16 #include <string>
17
18 namespace phosphor
19 {
20 namespace state
21 {
22 namespace manager
23 {
24
25 PHOSPHOR_LOG2_USING;
26
27 using BootProgress =
28 sdbusplus::client::xyz::openbmc_project::state::boot::Progress<>;
29 using LoggingCreate =
30 sdbusplus::client::xyz::openbmc_project::logging::Create<>;
31 using LoggingEntry = sdbusplus::client::xyz::openbmc_project::logging::Entry<>;
32
33 constexpr auto HOST_STATE_SVC = "xyz.openbmc_project.State.Host";
34 constexpr auto HOST_STATE_PATH = "/xyz/openbmc_project/state/host0";
35 constexpr auto PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties";
36 constexpr auto BOOT_PROGRESS_PROP = "BootProgress";
37
38 constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1";
39 constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1";
40 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager";
41 constexpr auto HOST_STATE_QUIESCE_TGT = "obmc-host-quiesce@0.target";
42
wasHostBooting(sdbusplus::bus_t & bus)43 bool wasHostBooting(sdbusplus::bus_t& bus)
44 {
45 try
46 {
47 using ProgressStages = BootProgress::ProgressStages;
48
49 auto method = bus.new_method_call(HOST_STATE_SVC, HOST_STATE_PATH,
50 PROPERTY_INTERFACE, "Get");
51 method.append(BootProgress::interface, BOOT_PROGRESS_PROP);
52
53 auto response = bus.call(method);
54
55 std::variant<ProgressStages> bootProgressV;
56 response.read(bootProgressV);
57 auto bootProgress = std::get<ProgressStages>(bootProgressV);
58
59 if (bootProgress == ProgressStages::Unspecified)
60 {
61 info("Host was not booting before BMC reboot");
62 return false;
63 }
64
65 info("Host was booting before BMC reboot: {BOOTPROGRESS}",
66 "BOOTPROGRESS", bootProgress);
67 }
68 catch (const sdbusplus::exception_t& e)
69 {
70 error("Error reading BootProgress, error {ERROR}, service {SERVICE}, "
71 "path {PATH}",
72 "ERROR", e, "SERVICE", HOST_STATE_SVC, "PATH", HOST_STATE_PATH);
73
74 throw;
75 }
76
77 return true;
78 }
79
createErrorLog(sdbusplus::bus_t & bus)80 void createErrorLog(sdbusplus::bus_t& bus)
81 {
82 try
83 {
84 // Create interface requires something for additionalData
85 std::map<std::string, std::string> additionalData;
86 additionalData.emplace("_PID", std::to_string(getpid()));
87
88 static constexpr auto errorMessage =
89 "xyz.openbmc_project.State.Error.HostNotRunning";
90 auto method = bus.new_method_call(LoggingCreate::default_service,
91 LoggingCreate::instance_path,
92 LoggingCreate::interface, "Create");
93 method.append(errorMessage, LoggingEntry::Level::Error, additionalData);
94 auto resp = bus.call(method);
95 }
96 catch (const sdbusplus::exception_t& e)
97 {
98 error(
99 "sdbusplus D-Bus call exception, error {ERROR}, objpath {OBJPATH}, "
100 "interface {INTERFACE}",
101 "ERROR", e, "OBJPATH", LoggingCreate::instance_path, "INTERFACE",
102 LoggingCreate::interface);
103
104 throw std::runtime_error(
105 "Error in invoking D-Bus logging create interface");
106 }
107 catch (const std::exception& e)
108 {
109 error("D-bus call exception: {ERROR}", "ERROR", e);
110 throw e;
111 }
112 }
113
114 // Once CHASSIS_ON_FILE is removed, the obmc-chassis-poweron@.target has
115 // completed and the phosphor-chassis-state-manager code has processed it.
isChassisTargetComplete()116 bool isChassisTargetComplete()
117 {
118 auto chassisFile = std::format(CHASSIS_ON_FILE, 0);
119
120 std::ifstream f(chassisFile);
121 return !f.good();
122 }
123
moveToHostQuiesce(sdbusplus::bus_t & bus)124 void moveToHostQuiesce(sdbusplus::bus_t& bus)
125 {
126 try
127 {
128 auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
129 SYSTEMD_INTERFACE, "StartUnit");
130
131 method.append(HOST_STATE_QUIESCE_TGT);
132 method.append("replace");
133
134 bus.call_noreply(method);
135 }
136 catch (const sdbusplus::exception_t& e)
137 {
138 error("sdbusplus call exception starting quiesce target: {ERROR}",
139 "ERROR", e);
140
141 throw std::runtime_error(
142 "Error in invoking D-Bus systemd StartUnit method");
143 }
144 }
145
146 } // namespace manager
147 } // namespace state
148 } // namespace phosphor
149
main()150 int main()
151 {
152 using namespace phosphor::state::manager;
153 PHOSPHOR_LOG2_USING;
154
155 auto bus = sdbusplus::bus::new_default();
156
157 // Chassis power is on if this service starts but need to wait for the
158 // obmc-chassis-poweron@.target to complete before potentially initiating
159 // another systemd target transition (i.e. Quiesce->Reboot)
160 while (!isChassisTargetComplete())
161 {
162 debug("Waiting for chassis on target to complete");
163 std::this_thread::sleep_for(std::chrono::seconds(1));
164
165 // There is no timeout here, wait until it happens or until system
166 // is powered off and this service is stopped
167 }
168
169 info("Chassis power on has completed, checking if host is "
170 "still running after the BMC reboot");
171
172 // Check the last BootProgeress to see if the host was booting before
173 // the BMC reboot occurred
174 if (!wasHostBooting(bus))
175 {
176 return 0;
177 }
178
179 // Host was booting before the BMC reboot so log an error and go to host
180 // quiesce target
181 createErrorLog(bus);
182 moveToHostQuiesce(bus);
183
184 return 0;
185 }
186