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