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