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