1 /**
2 * Copyright © 2017 IBM Corporation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "watchdog.hpp"
18
19 #include <CLI/CLI.hpp>
20 #include <phosphor-logging/elog-errors.hpp>
21 #include <phosphor-logging/elog.hpp>
22 #include <phosphor-logging/log.hpp>
23 #include <sdbusplus/bus.hpp>
24 #include <sdbusplus/exception.hpp>
25 #include <sdbusplus/server/manager.hpp>
26 #include <sdeventplus/event.hpp>
27 #include <sdeventplus/source/signal.hpp>
28 #include <sdeventplus/utility/sdbus.hpp>
29 #include <stdplus/signal.hpp>
30 #include <xyz/openbmc_project/Common/error.hpp>
31
32 #include <functional>
33 #include <iostream>
34 #include <optional>
35 #include <string>
36
37 using phosphor::watchdog::Watchdog;
38 using sdbusplus::xyz::openbmc_project::State::server::convertForMessage;
39
printActionTargetMap(const Watchdog::ActionTargetMap & actionTargetMap)40 void printActionTargetMap(const Watchdog::ActionTargetMap& actionTargetMap)
41 {
42 std::cerr << "Action Targets:\n";
43 for (const auto& [action, target] : actionTargetMap)
44 {
45 std::cerr << " " << convertForMessage(action) << " -> " << target
46 << "\n";
47 }
48 std::cerr << std::flush;
49 }
50
printFallback(const Watchdog::Fallback & fallback)51 void printFallback(const Watchdog::Fallback& fallback)
52 {
53 std::cerr << "Fallback Options:\n";
54 std::cerr << " Action: " << convertForMessage(fallback.action) << "\n";
55 std::cerr << " Interval(ms): " << std::dec << fallback.interval << "\n";
56 std::cerr << " Always re-execute: " << std::boolalpha << fallback.always
57 << "\n";
58 std::cerr << std::flush;
59 }
60
main(int argc,char * argv[])61 int main(int argc, char* argv[])
62 {
63 using namespace phosphor::logging;
64 using InternalFailure =
65 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
66
67 CLI::App app{"Canonical openbmc host watchdog daemon"};
68
69 // Service related options
70 const std::string serviceGroup = "Service Options";
71 std::string path;
72 app.add_option("-p,--path", path,
73 "DBus Object Path. "
74 "Ex: /xyz/openbmc_project/state/watchdog/host0")
75 ->required()
76 ->group(serviceGroup);
77 std::string service;
78 app.add_option("-s,--service", service,
79 "DBus Service Name. "
80 "Ex: xyz.openbmc_project.State.Watchdog.Host")
81 ->required()
82 ->group(serviceGroup);
83 bool continueAfterTimeout{false};
84 app.add_flag("-c,--continue", continueAfterTimeout,
85 "Continue daemon after watchdog timeout")
86 ->group(serviceGroup);
87
88 // Target related options
89 const std::string targetGroup = "Target Options";
90 std::optional<std::string> target;
91 app.add_option("-t,--target", target,
92 "Systemd unit to be called on "
93 "timeout for all actions but NONE. "
94 "Deprecated, use --action_target instead.")
95 ->group(targetGroup);
96 std::vector<std::string> actionTargets;
97 app.add_option("-a,--action_target", actionTargets,
98 "Map of action to "
99 "systemd unit to be called on timeout if that action is "
100 "set for ExpireAction when the timer expires.")
101 ->group(targetGroup);
102
103 // Fallback related options
104 const std::string fallbackGroup = "Fallback Options";
105 std::optional<std::string> fallbackAction;
106 auto fallbackActionOpt =
107 app.add_option("-f,--fallback_action", fallbackAction,
108 "Enables the "
109 "watchdog even when disabled via the dbus interface. "
110 "Perform this action when the fallback expires.")
111 ->group(fallbackGroup);
112 std::optional<unsigned> fallbackIntervalMs;
113 auto fallbackIntervalOpt =
114 app.add_option("-i,--fallback_interval", fallbackIntervalMs,
115 "Enables the "
116 "watchdog even when disabled via the dbus interface. "
117 "Waits for this interval before performing the fallback "
118 "action.")
119 ->group(fallbackGroup);
120 fallbackIntervalOpt->needs(fallbackActionOpt);
121 fallbackActionOpt->needs(fallbackIntervalOpt);
122 bool fallbackAlways{false};
123 app.add_flag("-e,--fallback_always", fallbackAlways,
124 "Enables the "
125 "watchdog even when disabled by the dbus interface. "
126 "This option is only valid with a fallback specified")
127 ->group(fallbackGroup)
128 ->needs(fallbackActionOpt)
129 ->needs(fallbackIntervalOpt);
130
131 // Should we watch for postcodes
132 bool watchPostcodes{false};
133 app.add_flag("-w,--watch_postcodes", watchPostcodes,
134 "Should we reset the time remaining any time a postcode "
135 "is signaled.");
136
137 // Interval related options
138 uint64_t minInterval = phosphor::watchdog::DEFAULT_MIN_INTERVAL_MS;
139 app.add_option("-m,--min_interval", minInterval,
140 "Set minimum interval for watchdog in milliseconds");
141
142 // 0 to indicate to use default from PDI if not passed in
143 uint64_t defaultInterval = 0;
144 app.add_option("-d,--default_interval", defaultInterval,
145 "Set default interval for watchdog in milliseconds");
146
147 CLI11_PARSE(app, argc, argv);
148
149 // Put together a list of actions and associated systemd targets
150 // The new --action_target options take precedence over the legacy
151 // --target
152 Watchdog::ActionTargetMap actionTargetMap;
153 if (target)
154 {
155 actionTargetMap[Watchdog::Action::HardReset] = *target;
156 actionTargetMap[Watchdog::Action::PowerOff] = *target;
157 actionTargetMap[Watchdog::Action::PowerCycle] = *target;
158 }
159 for (const auto& actionTarget : actionTargets)
160 {
161 size_t keyValueSplit = actionTarget.find("=");
162 if (keyValueSplit == std::string::npos)
163 {
164 std::cerr << "Invalid action_target format, "
165 "expect <action>=<target>."
166 << std::endl;
167 return 1;
168 }
169
170 std::string key = actionTarget.substr(0, keyValueSplit);
171 std::string value = actionTarget.substr(keyValueSplit + 1);
172
173 // Convert an action from a fully namespaced value
174 Watchdog::Action action;
175 try
176 {
177 action = Watchdog::convertActionFromString(key);
178 }
179 catch (const sdbusplus::exception::InvalidEnumString&)
180 {
181 std::cerr << "Bad action specified: " << key << std::endl;
182 return 1;
183 }
184
185 // Detect duplicate action target arguments
186 if (actionTargetMap.find(action) != actionTargetMap.end())
187 {
188 std::cerr << "Got duplicate action: " << key << std::endl;
189 return 1;
190 }
191
192 actionTargetMap[action] = std::move(value);
193 }
194 printActionTargetMap(actionTargetMap);
195
196 // Build the fallback option used for the Watchdog
197 std::optional<Watchdog::Fallback> maybeFallback;
198 if (fallbackAction)
199 {
200 Watchdog::Fallback fallback;
201 try
202 {
203 fallback.action =
204 Watchdog::convertActionFromString(*fallbackAction);
205 }
206 catch (const sdbusplus::exception::InvalidEnumString&)
207 {
208 std::cerr << "Bad fallback action specified: " << *fallbackAction
209 << std::endl;
210 return 1;
211 }
212 fallback.interval = *fallbackIntervalMs;
213 fallback.always = fallbackAlways;
214
215 printFallback(fallback);
216 maybeFallback = fallback;
217 }
218
219 try
220 {
221 // Get a default event loop
222 auto event = sdeventplus::Event::get_default();
223
224 // Get a handle to system dbus.
225 auto bus = sdbusplus::bus::new_default();
226
227 // Add systemd object manager.
228 sdbusplus::server::manager_t watchdogManager(bus, path.c_str());
229
230 // Create a watchdog object
231 Watchdog watchdog(bus, path.c_str(), event, std::move(actionTargetMap),
232 std::move(maybeFallback), minInterval,
233 defaultInterval,
234 /*exitAfterTimeout=*/!continueAfterTimeout);
235
236 std::optional<sdbusplus::bus::match_t> watchPostcodeMatch;
237 if (watchPostcodes)
238 {
239 watchPostcodeMatch.emplace(
240 bus,
241 sdbusplus::bus::match::rules::propertiesChanged(
242 "/xyz/openbmc_project/state/boot/raw0",
243 "xyz.openbmc_project.State.Boot.Raw"),
244 std::bind(&Watchdog::resetTimeRemaining, std::ref(watchdog),
245 false));
246 }
247
248 // Claim the bus
249 bus.request_name(service.c_str());
250
251 auto intCb = [](sdeventplus::source::Signal& s,
252 const struct signalfd_siginfo*) {
253 s.get_event().exit(0);
254 };
255 stdplus::signal::block(SIGINT);
256 sdeventplus::source::Signal sigint(event, SIGINT, intCb);
257 stdplus::signal::block(SIGTERM);
258 sdeventplus::source::Signal sigterm(event, SIGTERM, std::move(intCb));
259 return sdeventplus::utility::loopWithBus(event, bus);
260 }
261 catch (const InternalFailure& e)
262 {
263 phosphor::logging::commit<InternalFailure>();
264
265 // Need a coredump in the error cases.
266 std::terminate();
267 }
268 return 1;
269 }
270