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