xref: /openbmc/phosphor-watchdog/src/mainapp.cpp (revision 120bc4cc)
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{false};
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{false};
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{false};
129     app.add_flag("-w,--watch_postcodes", watchPostcodes,
130                  "Should we reset the time remaining any time a postcode "
131                  "is signaled.");
132 
133     // Interval related options
134     uint64_t minInterval = phosphor::watchdog::DEFAULT_MIN_INTERVAL_MS;
135     app.add_option("-m,--min_interval", minInterval,
136                    "Set minimum interval for watchdog in milliseconds");
137 
138     // 0 to indicate to use default from PDI if not passed in
139     uint64_t defaultInterval = 0;
140     app.add_option("-d,--default_interval", defaultInterval,
141                    "Set default interval for watchdog in milliseconds");
142 
143     CLI11_PARSE(app, argc, argv);
144 
145     // Put together a list of actions and associated systemd targets
146     // The new --action_target options take precedence over the legacy
147     // --target
148     Watchdog::ActionTargetMap actionTargetMap;
149     if (target)
150     {
151         actionTargetMap[Watchdog::Action::HardReset] = *target;
152         actionTargetMap[Watchdog::Action::PowerOff] = *target;
153         actionTargetMap[Watchdog::Action::PowerCycle] = *target;
154     }
155     for (const auto& actionTarget : actionTargets)
156     {
157         size_t keyValueSplit = actionTarget.find("=");
158         if (keyValueSplit == std::string::npos)
159         {
160             std::cerr << "Invalid action_target format, "
161                          "expect <action>=<target>."
162                       << std::endl;
163             return 1;
164         }
165 
166         std::string key = actionTarget.substr(0, keyValueSplit);
167         std::string value = actionTarget.substr(keyValueSplit + 1);
168 
169         // Convert an action from a fully namespaced value
170         Watchdog::Action action;
171         try
172         {
173             action = Watchdog::convertActionFromString(key);
174         }
175         catch (const sdbusplus::exception::InvalidEnumString&)
176         {
177             std::cerr << "Bad action specified: " << key << std::endl;
178             return 1;
179         }
180 
181         // Detect duplicate action target arguments
182         if (actionTargetMap.find(action) != actionTargetMap.end())
183         {
184             std::cerr << "Got duplicate action: " << key << std::endl;
185             return 1;
186         }
187 
188         actionTargetMap[action] = std::move(value);
189     }
190     printActionTargetMap(actionTargetMap);
191 
192     // Build the fallback option used for the Watchdog
193     std::optional<Watchdog::Fallback> maybeFallback;
194     if (fallbackAction)
195     {
196         Watchdog::Fallback fallback;
197         try
198         {
199             fallback.action =
200                 Watchdog::convertActionFromString(*fallbackAction);
201         }
202         catch (const sdbusplus::exception::InvalidEnumString&)
203         {
204             std::cerr << "Bad fallback action specified: " << *fallbackAction
205                       << std::endl;
206             return 1;
207         }
208         fallback.interval = *fallbackIntervalMs;
209         fallback.always = fallbackAlways;
210 
211         printFallback(fallback);
212         maybeFallback = std::move(fallback);
213     }
214 
215     try
216     {
217         // Get a default event loop
218         auto event = sdeventplus::Event::get_default();
219 
220         // Get a handle to system dbus.
221         auto bus = sdbusplus::bus::new_default();
222 
223         // Add systemd object manager.
224         sdbusplus::server::manager::manager(bus, path.c_str());
225 
226         // Attach the bus to sd_event to service user requests
227         bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
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 
234         std::optional<sdbusplus::bus::match::match> watchPostcodeMatch;
235         if (watchPostcodes)
236         {
237             watchPostcodeMatch.emplace(
238                 bus,
239                 sdbusplus::bus::match::rules::propertiesChanged(
240                     "/xyz/openbmc_project/state/boot/raw0",
241                     "xyz.openbmc_project.State.Boot.Raw"),
242                 std::bind(&Watchdog::resetTimeRemaining, std::ref(watchdog),
243                           false));
244         }
245 
246         // Claim the bus
247         bus.request_name(service.c_str());
248 
249         // Loop until our timer expires and we don't want to continue
250         while (continueAfterTimeout || !watchdog.timerExpired())
251         {
252             // Run and never timeout
253             event.run(std::nullopt);
254         }
255     }
256     catch (const InternalFailure& e)
257     {
258         phosphor::logging::commit<InternalFailure>();
259 
260         // Need a coredump in the error cases.
261         std::terminate();
262     }
263     return 0;
264 }
265