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