1 #include "systemd_target_signal.hpp"
2 
3 #include "utils.hpp"
4 
5 #include <phosphor-logging/elog-errors.hpp>
6 #include <phosphor-logging/lg2.hpp>
7 #include <sdbusplus/exception.hpp>
8 #include <sdbusplus/server/manager.hpp>
9 #include <xyz/openbmc_project/Common/error.hpp>
10 #include <xyz/openbmc_project/Logging/Create/client.hpp>
11 #include <xyz/openbmc_project/Logging/Entry/client.hpp>
12 
13 namespace phosphor
14 {
15 namespace state
16 {
17 namespace manager
18 {
19 
20 using phosphor::logging::elog;
21 PHOSPHOR_LOG2_USING;
22 
23 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
24 
25 using LoggingCreate =
26     sdbusplus::client::xyz::openbmc_project::logging::Create<>;
27 using LoggingEntry = sdbusplus::client::xyz::openbmc_project::logging::Entry<>;
28 
29 void SystemdTargetLogging::startBmcQuiesceTarget()
30 {
31     auto method = this->bus.new_method_call(
32         "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
33         "org.freedesktop.systemd1.Manager", "StartUnit");
34 
35     // TODO: Enhance when needed to support multiple-bmc instance systems
36     method.append("obmc-bmc-service-quiesce@0.target");
37     method.append("replace");
38     try
39     {
40         this->bus.call_noreply(method);
41     }
42     catch (const sdbusplus::exception_t& e)
43     {
44         error("Failed to start BMC quiesce target, exception:{ERROR}", "ERROR",
45               e);
46         // just continue, this is error path anyway so we're just doing what
47         // we can
48     }
49 
50     return;
51 }
52 
53 void SystemdTargetLogging::logError(const std::string& errorLog,
54                                     const std::string& result,
55                                     const std::string& unit)
56 {
57     auto method = this->bus.new_method_call(LoggingCreate::default_service,
58                                             LoggingCreate::instance_path,
59                                             LoggingCreate::interface, "Create");
60     // Signature is ssa{ss}
61     method.append(
62         errorLog, LoggingEntry::Level::Critical,
63         std::array<std::pair<std::string, std::string>, 2>(
64             {std::pair<std::string, std::string>({"SYSTEMD_RESULT", result}),
65              std::pair<std::string, std::string>({"SYSTEMD_UNIT", unit})}));
66     try
67     {
68         this->bus.call_noreply(method);
69     }
70     catch (const sdbusplus::exception_t& e)
71     {
72         error("Failed to create systemd target error, error:{ERROR_MSG}, "
73               "result:{RESULT}, exception:{ERROR}",
74               "ERROR_MSG", errorLog, "RESULT", result, "ERROR", e);
75     }
76 }
77 
78 std::string SystemdTargetLogging::processError(const std::string& unit,
79                                                const std::string& result)
80 {
81     auto targetEntry = this->targetData.find(unit);
82     if (targetEntry != this->targetData.end())
83     {
84         // Check if its result matches any of our monitored errors
85         if (std::find(targetEntry->second.errorsToMonitor.begin(),
86                       targetEntry->second.errorsToMonitor.end(),
87                       result) != targetEntry->second.errorsToMonitor.end())
88         {
89             info(
90                 "Monitored systemd unit has hit an error, unit:{UNIT}, result:{RESULT}",
91                 "UNIT", unit, "RESULT", result);
92 
93             // Generate a BMC dump when a monitored target fails
94             utils::createBmcDump(this->bus);
95             return (targetEntry->second.errorToLog);
96         }
97     }
98 
99     // Check if it's in our list of services to monitor
100     if (std::find(this->serviceData.begin(), this->serviceData.end(), unit) !=
101         this->serviceData.end())
102     {
103         if (result == "failed")
104         {
105             info(
106                 "Monitored systemd service has hit an error, unit:{UNIT}, result:{RESULT}",
107                 "UNIT", unit, "RESULT", result);
108 
109             // Generate a BMC dump when a critical service fails
110             utils::createBmcDump(this->bus);
111             // Enter BMC Quiesce when a critical service fails
112             startBmcQuiesceTarget();
113             return (std::string{
114                 "xyz.openbmc_project.State.Error.CriticalServiceFailure"});
115         }
116     }
117 
118     return (std::string{});
119 }
120 
121 void SystemdTargetLogging::systemdUnitChange(sdbusplus::message_t& msg)
122 {
123     uint32_t id;
124     sdbusplus::message::object_path objPath;
125     std::string unit{};
126     std::string result{};
127 
128     msg.read(id, objPath, unit, result);
129 
130     // In most cases it will just be success, in which case just return
131     if (result != "done")
132     {
133         const std::string error = processError(unit, result);
134 
135         // If this is a monitored error then log it
136         if (!error.empty())
137         {
138             logError(error, result, unit);
139         }
140     }
141     return;
142 }
143 
144 void SystemdTargetLogging::processNameChangeSignal(sdbusplus::message_t& msg)
145 {
146     std::string name;      // well-known
147     std::string old_owner; // unique-name
148     std::string new_owner; // unique-name
149 
150     msg.read(name, old_owner, new_owner);
151 
152     // Looking for systemd to be on dbus so we can call it
153     if (name == "org.freedesktop.systemd1")
154     {
155         info("org.freedesktop.systemd1 is now on dbus");
156         subscribeToSystemdSignals();
157     }
158     return;
159 }
160 
161 void SystemdTargetLogging::subscribeToSystemdSignals()
162 {
163     auto method = this->bus.new_method_call(
164         "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
165         "org.freedesktop.systemd1.Manager", "Subscribe");
166 
167     try
168     {
169         this->bus.call(method);
170     }
171     catch (const sdbusplus::exception_t& e)
172     {
173         // If error indicates systemd is not on dbus yet then do nothing.
174         // The systemdNameChangeSignals callback will detect when it is on
175         // dbus and then call this function again
176         const std::string noDbus("org.freedesktop.DBus.Error.ServiceUnknown");
177         if (noDbus == e.name())
178         {
179             info("org.freedesktop.systemd1 not on dbus yet");
180         }
181         else
182         {
183             error("Failed to subscribe to systemd signals: {ERROR}", "ERROR",
184                   e);
185             elog<InternalFailure>();
186         }
187         return;
188     }
189 
190     // Call destructor on match callback since application is now subscribed to
191     // systemd signals
192     this->systemdNameOwnedChangedSignal.~match();
193 
194     return;
195 }
196 
197 } // namespace manager
198 } // namespace state
199 } // namespace phosphor
200