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