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