xref: /openbmc/phosphor-watchdog/src/mainapp.cpp (revision 5c423703)
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