xref: /openbmc/phosphor-watchdog/src/mainapp.cpp (revision 6f80f80e)
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 <functional>
21 #include <iostream>
22 #include <optional>
23 #include <phosphor-logging/elog-errors.hpp>
24 #include <phosphor-logging/elog.hpp>
25 #include <phosphor-logging/log.hpp>
26 #include <sdbusplus/bus.hpp>
27 #include <sdbusplus/exception.hpp>
28 #include <sdbusplus/server/manager.hpp>
29 #include <sdeventplus/event.hpp>
30 #include <sdeventplus/source/signal.hpp>
31 #include <stdplus/signal.hpp>
32 #include <string>
33 #include <xyz/openbmc_project/Common/error.hpp>
34 
35 using phosphor::watchdog::Watchdog;
36 using sdbusplus::xyz::openbmc_project::State::server::convertForMessage;
37 
38 void printActionTargetMap(const Watchdog::ActionTargetMap& actionTargetMap)
39 {
40     std::cerr << "Action Targets:\n";
41     for (const auto& [action, target] : actionTargetMap)
42     {
43         std::cerr << "  " << convertForMessage(action) << " -> " << target
44                   << "\n";
45     }
46     std::cerr << std::flush;
47 }
48 
49 void printFallback(const Watchdog::Fallback& fallback)
50 {
51     std::cerr << "Fallback Options:\n";
52     std::cerr << "  Action: " << convertForMessage(fallback.action) << "\n";
53     std::cerr << "  Interval(ms): " << std::dec << fallback.interval << "\n";
54     std::cerr << "  Always re-execute: " << std::boolalpha << fallback.always
55               << "\n";
56     std::cerr << std::flush;
57 }
58 
59 int main(int argc, char* argv[])
60 {
61     using namespace phosphor::logging;
62     using InternalFailure =
63         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
64 
65     CLI::App app{"Canonical openbmc host watchdog daemon"};
66 
67     // Service related options
68     const std::string serviceGroup = "Service Options";
69     std::string path;
70     app.add_option("-p,--path", path,
71                    "DBus Object Path. "
72                    "Ex: /xyz/openbmc_project/state/watchdog/host0")
73         ->required()
74         ->group(serviceGroup);
75     std::string service;
76     app.add_option("-s,--service", service,
77                    "DBus Service Name. "
78                    "Ex: xyz.openbmc_project.State.Watchdog.Host")
79         ->required()
80         ->group(serviceGroup);
81     bool continueAfterTimeout{false};
82     app.add_flag("-c,--continue", continueAfterTimeout,
83                  "Continue daemon after watchdog timeout")
84         ->group(serviceGroup);
85 
86     // Target related options
87     const std::string targetGroup = "Target Options";
88     std::optional<std::string> target;
89     app.add_option("-t,--target", target,
90                    "Systemd unit to be called on "
91                    "timeout for all actions but NONE. "
92                    "Deprecated, use --action_target instead.")
93         ->group(targetGroup);
94     std::vector<std::string> actionTargets;
95     app.add_option("-a,--action_target", actionTargets,
96                    "Map of action to "
97                    "systemd unit to be called on timeout if that action is "
98                    "set for ExpireAction when the timer expires.")
99         ->group(targetGroup);
100 
101     // Fallback related options
102     const std::string fallbackGroup = "Fallback Options";
103     std::optional<std::string> fallbackAction;
104     auto fallbackActionOpt =
105         app.add_option("-f,--fallback_action", fallbackAction,
106                        "Enables the "
107                        "watchdog even when disabled via the dbus interface. "
108                        "Perform this action when the fallback expires.")
109             ->group(fallbackGroup);
110     std::optional<unsigned> fallbackIntervalMs;
111     auto fallbackIntervalOpt =
112         app.add_option("-i,--fallback_interval", fallbackIntervalMs,
113                        "Enables the "
114                        "watchdog even when disabled via the dbus interface. "
115                        "Waits for this interval before performing the fallback "
116                        "action.")
117             ->group(fallbackGroup);
118     fallbackIntervalOpt->needs(fallbackActionOpt);
119     fallbackActionOpt->needs(fallbackIntervalOpt);
120     bool fallbackAlways{false};
121     app.add_flag("-e,--fallback_always", fallbackAlways,
122                  "Enables the "
123                  "watchdog even when disabled by the dbus interface. "
124                  "This option is only valid with a fallback specified")
125         ->group(fallbackGroup)
126         ->needs(fallbackActionOpt)
127         ->needs(fallbackIntervalOpt);
128 
129     // Should we watch for postcodes
130     bool watchPostcodes{false};
131     app.add_flag("-w,--watch_postcodes", watchPostcodes,
132                  "Should we reset the time remaining any time a postcode "
133                  "is signaled.");
134 
135     // Interval related options
136     uint64_t minInterval = phosphor::watchdog::DEFAULT_MIN_INTERVAL_MS;
137     app.add_option("-m,--min_interval", minInterval,
138                    "Set minimum interval for watchdog in milliseconds");
139 
140     // 0 to indicate to use default from PDI if not passed in
141     uint64_t defaultInterval = 0;
142     app.add_option("-d,--default_interval", defaultInterval,
143                    "Set default interval for watchdog in milliseconds");
144 
145     CLI11_PARSE(app, argc, argv);
146 
147     // Put together a list of actions and associated systemd targets
148     // The new --action_target options take precedence over the legacy
149     // --target
150     Watchdog::ActionTargetMap actionTargetMap;
151     if (target)
152     {
153         actionTargetMap[Watchdog::Action::HardReset] = *target;
154         actionTargetMap[Watchdog::Action::PowerOff] = *target;
155         actionTargetMap[Watchdog::Action::PowerCycle] = *target;
156     }
157     for (const auto& actionTarget : actionTargets)
158     {
159         size_t keyValueSplit = actionTarget.find("=");
160         if (keyValueSplit == std::string::npos)
161         {
162             std::cerr << "Invalid action_target format, "
163                          "expect <action>=<target>."
164                       << std::endl;
165             return 1;
166         }
167 
168         std::string key = actionTarget.substr(0, keyValueSplit);
169         std::string value = actionTarget.substr(keyValueSplit + 1);
170 
171         // Convert an action from a fully namespaced value
172         Watchdog::Action action;
173         try
174         {
175             action = Watchdog::convertActionFromString(key);
176         }
177         catch (const sdbusplus::exception::InvalidEnumString&)
178         {
179             std::cerr << "Bad action specified: " << key << std::endl;
180             return 1;
181         }
182 
183         // Detect duplicate action target arguments
184         if (actionTargetMap.find(action) != actionTargetMap.end())
185         {
186             std::cerr << "Got duplicate action: " << key << std::endl;
187             return 1;
188         }
189 
190         actionTargetMap[action] = std::move(value);
191     }
192     printActionTargetMap(actionTargetMap);
193 
194     // Build the fallback option used for the Watchdog
195     std::optional<Watchdog::Fallback> maybeFallback;
196     if (fallbackAction)
197     {
198         Watchdog::Fallback fallback;
199         try
200         {
201             fallback.action =
202                 Watchdog::convertActionFromString(*fallbackAction);
203         }
204         catch (const sdbusplus::exception::InvalidEnumString&)
205         {
206             std::cerr << "Bad fallback action specified: " << *fallbackAction
207                       << std::endl;
208             return 1;
209         }
210         fallback.interval = *fallbackIntervalMs;
211         fallback.always = fallbackAlways;
212 
213         printFallback(fallback);
214         maybeFallback = fallback;
215     }
216 
217     try
218     {
219         // Get a default event loop
220         auto event = sdeventplus::Event::get_default();
221 
222         // Get a handle to system dbus.
223         auto bus = sdbusplus::bus::new_default();
224 
225         // Add systemd object manager.
226         sdbusplus::server::manager::manager watchdogManager(bus, path.c_str());
227 
228         // Attach the bus to sd_event to service user requests
229         bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
230 
231         // Create a watchdog object
232         Watchdog watchdog(bus, path.c_str(), event, std::move(actionTargetMap),
233                           std::move(maybeFallback), minInterval,
234                           defaultInterval);
235 
236         std::optional<sdbusplus::bus::match::match> 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         bool done = false;
252         auto intCb = [&](sdeventplus::source::Signal&,
253                          const struct signalfd_siginfo*) { done = true; };
254         stdplus::signal::block(SIGINT);
255         sdeventplus::source::Signal sigint(event, SIGINT, intCb);
256         stdplus::signal::block(SIGTERM);
257         sdeventplus::source::Signal sigterm(event, SIGTERM, std::move(intCb));
258 
259         // Loop until our timer expires and we don't want to continue
260         while (!done && (continueAfterTimeout || !watchdog.timerExpired()))
261         {
262             // Process all outstanding bus events before running the loop.
263             // This prevents the sd-bus handling logic from leaking memory.
264             // TODO: Remove when upstream fixes this bug
265             while (bus.process_discard() > 0)
266                 ;
267 
268             // Run and never timeout
269             event.run(std::nullopt);
270         }
271     }
272     catch (const InternalFailure& e)
273     {
274         phosphor::logging::commit<InternalFailure>();
275 
276         // Need a coredump in the error cases.
277         std::terminate();
278     }
279     return 0;
280 }
281